Compare commits

...

10 Commits

Author SHA1 Message Date
ChestnutYueyue db35d8762c build(mingw): 添加对mingw平台的支持
添加mingw平台的工具链配置,使其能够在该平台下进行构建
2026-02-11 16:56:10 +08:00
ChestnutYueyue 39a0ab7124 docs: 更新API教程文档和构建系统文档
refactor: 重构xmake.lua中的示例构建逻辑
style: 统一文档格式和代码风格
2026-02-11 16:42:20 +08:00
ChestnutYueyue 6975f69d64 build: 将项目版本从1.0.0升级级到1.1.0 2026-02-11 15:53:01 +08:00
ChestnutYueyue 52d82763e1 refactor: 移除未使用的Squirrel库文件和头文件
清理项目中不再使用的Squirrel库文件和相关头文件,包括数学库、系统库、内存管理、字符串处理等模块。这些文件在当前项目中已不再需要,移除以减少代码冗余和维护成本。

主要变更包括:
- 删除sqstdmath.h/cpp、sqstdsystem.h/cpp等标准库文件
- 移除基础数据结构如sqarray.h、sqtable.h等
- 清理编译器、虚拟机相关的核心头文件
- 删除已废弃的辅助功能和工具类

此次重构不涉及功能变更,仅移除不再使用的代码文件。
2026-02-11 15:48:06 +08:00
ChestnutYueyue c6a5557d89 refactor(输入处理): 将SDL控制器按钮替换为引擎定义的GamepadButton枚举
统一使用引擎定义的GamepadButton枚举来处理游戏手柄输入,提高代码可维护性并减少对SDL的直接依赖
2026-02-11 15:47:25 +08:00
ChestnutYueyue 1f10ce999c refactor(audio): 示例程序重构音频系统为单例管理器模式
feat(text): 为Text类添加格式化文本创建和设置功能
refactor(ui): 将碰撞演示和空间索引演示的UI改为Text组件实现
chore: 移除不再使用的音频上下文和菜单按钮相关代码
2026-02-11 15:30:32 +08:00
ChestnutYueyue 2751b27d90 feat(ui): 为UI组件添加链式调用构建器方法和坐标空间支持
为ProgressBar、Slider、RadioButton、Label、CheckBox、Button等UI组件添加链式调用构建器方法,简化组件创建和配置流程。
新增CoordinateSpace枚举支持三种坐标空间:Screen(屏幕空间)、World(世界空间)和Camera(相机空间)。
重构onDraw方法为onDrawWidget,统一处理不同坐标空间的渲染逻辑。
更新示例代码展示如何使用链式调用和坐标空间功能。
2026-02-11 13:10:47 +08:00
ChestnutYueyue d0314447ee refactor(ui): 重构UI组件并移除Squirrel脚本支持
重构UI组件系统,将Text组件从scene模块移动到ui模块,并新增CheckBox、RadioButton、Slider等UI控件
移除Squirrel脚本引擎及相关绑定代码,简化项目结构
调整.gitignore文件,添加.trae目录忽略
优化String类实现,使用Windows API进行GBK/UTF-8转换
更新构建配置,移除Squirrel相关依赖
2026-02-11 12:20:43 +08:00
ChestnutYueyue 8d086a97e8 chore: 删除旧的构建系统文档文件
该文档已过时且不再维护,项目构建系统已迁移至新文档。
2026-02-11 00:47:04 +08:00
ChestnutYueyue b4036cd8dd feat(build): 为示例项目添加打包时资源复制功能
为 push_box、hello_world、collision_demo 和 spatial_index_demo 示例项目添加以下功能:
1. 构建后生成 NRO 文件并打印日志
2. 打包时将 NRO 文件复制到 package 目录
3. 改进资源复制逻辑,支持子目录递归复制
4. 打包时将资源文件复制到 package 目录
2026-02-10 20:46:53 +08:00
122 changed files with 6095 additions and 21487 deletions

2
.gitignore vendored
View File

@ -14,7 +14,7 @@
/x64/
/x86/
/.build/
/.trae/
# --------------------------------------------
# xmake 构建系统
# --------------------------------------------

View File

@ -1,327 +0,0 @@
# Extra2D 构建系统文档
## 概述
Extra2D 使用 **Xmake** 作为构建系统,支持 **MinGW (Windows)****Nintendo Switch** 两个平台。
## 项目结构
```
Extra2D/
├── xmake.lua # 主构建脚本
├── xmake/
│ ├── engine.lua # 引擎库定义
│ └── toolchains/
│ └── switch.lua # Switch 工具链定义
├── Extra2D/
│ ├── src/ # 引擎源码
│ └── include/ # 引擎头文件
├── squirrel/ # Squirrel 脚本引擎
└── examples/ # 示例程序
├── hello_world/
├── collision_demo/
├── push_box/
└── spatial_index_demo/
```
## 环境准备
### MinGW (Windows) 平台
1. **安装 MinGW-w64**
- 下载地址: https://www.mingw-w64.org/downloads/
- 或使用 MSYS2: `pacman -S mingw-w64-x86_64-toolchain`
2. **安装 Xmake**
- 下载地址: https://xmake.io/#/zh-cn/guide/installation
3. **安装依赖包**
```bash
xmake require -y
```
### Nintendo Switch 平台
1. **安装 devkitPro**
- 下载地址: https://devkitpro.org/wiki/Getting_Started
- Windows 安装程序会自动设置环境变量
2. **设置环境变量**
```powershell
# PowerShell
$env:DEVKITPRO="C:/devkitPro"
# 或永久设置(系统属性 -> 环境变量)
[Environment]::SetEnvironmentVariable("DEVKITPRO", "C:/devkitPro", "User")
```
3. **在 MSYS2 中安装 Switch 库**
```bash
# 打开 MSYS2 (devkitPro 提供的)
pacman -S switch-sdl2 switch-sdl2_mixer switch-glm
# 或安装所有 Switch 开发库
pacman -S $(pacman -Slq dkp-libs | grep switch-)
```
4. **验证安装**
```bash
ls $DEVKITPRO/portlibs/switch/include/SDL2
ls $DEVKITPRO/portlibs/switch/include/glm
```
## 主构建脚本 (xmake.lua)
### 项目元信息
- **项目名称**: Extra2D
- **版本**: 3.1.0
- **许可证**: MIT
- **语言标准**: C++17
- **编码**: UTF-8
### 构建选项
| 选项 | 默认值 | 描述 |
|------|--------|------|
| `examples` | true | 构建示例程序 |
| `debug_logs` | false | 启用调试日志 |
### 平台检测逻辑
1. 获取主机平台: `os.host()`
2. 获取目标平台: `get_config("plat")` 或主机平台
3. 平台回退: 如果不支持Windows 回退到 `mingw`
4. 设置平台: `set_plat(target_plat)`
5. 设置架构:
- Switch: `arm64`
- MinGW: `x86_64`
### 依赖包 (MinGW 平台)
```lua
add_requires("glm", "libsdl2", "libsdl2_mixer")
```
## 引擎库定义 (xmake/engine.lua)
### 目标: extra2d
- **类型**: 静态库 (`static`)
- **源文件**:
- `Extra2D/src/**.cpp`
- `Extra2D/src/glad/glad.c`
- `squirrel/squirrel/*.cpp`
- `squirrel/sqstdlib/*.cpp`
### 头文件路径
- `Extra2D/include` (public)
- `squirrel/include` (public)
- `Extra2D/include/extra2d/platform` (public)
### 平台配置
#### Switch 平台
```lua
add_includedirs(devkitPro .. "/portlibs/switch/include")
add_linkdirs(devkitPro .. "/portlibs/switch/lib")
add_syslinks("SDL2_mixer", "SDL2", "opusfile", "opus", ...)
```
#### MinGW 平台
```lua
add_packages("glm", "libsdl2", "libsdl2_mixer")
add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi")
```
### 编译器标志
- `-Wall`, `-Wextra`
- `-Wno-unused-variable`, `-Wno-unused-function`
- `-Wno-deprecated-copy`, `-Wno-class-memaccess`
### 构建模式
- **Debug**: `-O0`, `-g`, 定义 `E2D_DEBUG`, `_DEBUG`
- **Release**: `-O2`, 定义 `NDEBUG`
## Switch 工具链 (xmake/toolchains/switch.lua)
### 工具链: switch
- **类型**: standalone
- **描述**: Nintendo Switch devkitA64 工具链
### 工具路径
- **CC**: `aarch64-none-elf-gcc.exe`
- **CXX**: `aarch64-none-elf-g++.exe`
- **LD**: `aarch64-none-elf-g++.exe`
- **AR**: `aarch64-none-elf-gcc-ar.exe`
### 架构标志
```
-march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
```
### 链接标志
```
-specs=switch.specs -g
```
### 预定义宏
- `__SWITCH__`
- `__NX__`
- `MA_SWITCH`
- `PFD_SWITCH`
### 系统库
- `nx` (libnx)
- `m` (math)
## 示例程序构建脚本
### 通用结构
```lua
-- 使用与主项目相同的平台配置
if is_plat("switch") then
-- Switch 平台配置
elseif is_plat("mingw") then
-- MinGW 平台配置
end
```
### Switch 平台配置
- 设置平台: `set_plat("switch")`
- 设置架构: `set_arch("arm64")`
- 设置工具链: `set_toolchains("switch")`
- 设置输出目录: `set_targetdir("../../build/examples/xxx")`
- 构建后生成 NRO 文件
### MinGW 平台配置
- 设置平台: `set_plat("mingw")`
- 设置架构: `set_arch("x86_64")`
- 设置输出目录: `set_targetdir("../../build/examples/xxx")`
- 链接标志: `-mwindows`
- 构建后复制资源文件
## 构建命令
### 配置项目
```bash
# 默认配置 (MinGW)
xmake f -c
# 指定平台 (使用 -p 参数)
xmake f -c -p mingw
xmake f -c -p switch
# 指定 MinGW 路径(如果不在默认位置)
xmake f -c -p mingw --mingw=C:\mingw
# 禁用示例
xmake f --examples=n
# 启用调试日志
xmake f --debug_logs=y
```
### 安装依赖 (MinGW)
```bash
xmake require -y
```
### 构建项目
```bash
# 构建所有目标
xmake
# 构建特定目标
xmake -r extra2d
xmake -r push_box
# 并行构建
xmake -j4
```
### 运行程序
```bash
# 运行示例
xmake run push_box
xmake run hello_world
```
### 清理构建
```bash
xmake clean
xmake f -c # 重新配置
```
## 输出目录结构
```
build/
├── examples/
│ ├── hello_world/
│ │ ├── hello_world.exe # MinGW
│ │ ├── hello_world.nro # Switch
│ │ └── assets/ # 资源文件
│ ├── push_box/
│ ├── collision_demo/
│ └── spatial_index_demo/
└── ...
```
## 关键设计决策
### 1. 平台检测
- 使用 `is_plat()` 而不是手动检测,确保与主项目一致
- 示例脚本继承主项目的平台配置
### 2. 资源处理
- **Switch**: 使用 romfs 嵌入 NRO 文件
- **MinGW**: 构建后复制到输出目录
### 3. 依赖管理
- **MinGW**: 使用 Xmake 包管理器 (`add_requires`)
- **Switch**: 使用 devkitPro 提供的库
### 4. 工具链隔离
- Switch 工具链定义在单独文件中
- 通过 `set_toolchains("switch")` 切换
## 常见问题
### 1. 依赖包找不到
```bash
xmake repo -u
xmake require -y
```
### 2. Switch 工具链找不到
- 确保 DEVKITPRO 环境变量设置正确
- 默认路径: `C:/devkitPro`
### 3. 平台配置不匹配
- 使用 `xmake show` 查看当前配置
- 使用 `xmake f -c` 重新配置
### 4. MinGW 路径问题
如果 MinGW 安装在非默认位置,使用 `--mingw` 参数指定:
```bash
xmake f -c -p mingw --mingw=D:\Tools\mingw64
```
### 5. Switch 库找不到
确保在 MSYS2 中安装了必要的库:
```bash
pacman -S switch-sdl2 switch-sdl2_mixer switch-glm
```
## 扩展指南
### 添加新示例
1. 在 `examples/` 下创建新目录
2. 创建 `xmake.lua` 构建脚本
3. 在 `xmake.lua` 中添加 `includes("examples/new_example")`
### 添加新平台
1. 在 `xmake/toolchains/` 下创建工具链定义
2. 在 `xmake.lua` 中添加平台检测逻辑
3. 在 `xmake/engine.lua` 中添加平台配置

View File

@ -1,228 +1,39 @@
#pragma once
#include <extra2d/core/types.h>
#include <string>
#include <vector>
#include <cstring>
namespace extra2d {
// ============================================================================
// String 类 - 跨平台字符串,内部统一使用 UTF-8 存储
// 字符串编码转换工具函数
// 统一使用 std::string (UTF-8) 作为项目标准字符串类型
// ============================================================================
class String {
public:
// ------------------------------------------------------------------------
// 构造函数
// ------------------------------------------------------------------------
String() = default;
String(const char* utf8) : data_(utf8 ? utf8 : "") {}
String(const std::string& utf8) : data_(utf8) {}
explicit String(const wchar_t* wide);
explicit String(const std::wstring& wide);
explicit String(const char16_t* utf16);
explicit String(const std::u16string& utf16);
explicit String(const char32_t* utf32);
explicit String(const std::u32string& utf32);
// UTF-8 ↔ UTF-16 转换
std::u16string utf8ToUtf16(const std::string& utf8);
std::string utf16ToUtf8(const std::u16string& utf16);
// ------------------------------------------------------------------------
// 静态工厂方法
// ------------------------------------------------------------------------
static String fromUtf8(const char* utf8);
static String fromUtf8(const std::string& utf8);
static String fromWide(const wchar_t* wide);
static String fromWide(const std::wstring& wide);
static String fromUtf16(const char16_t* utf16);
static String fromUtf16(const std::u16string& utf16);
static String fromUtf32(const char32_t* utf32);
static String fromUtf32(const std::u32string& utf32);
// UTF-8 ↔ UTF-32 转换
std::u32string utf8ToUtf32(const std::string& utf8);
std::string utf32ToUtf8(const std::u32string& utf32);
/// 从 GBK/GB2312 编码构造Windows 中文系统常用)
static String fromGBK(const char* gbk);
static String fromGBK(const std::string& gbk);
// UTF-8 ↔ Wide String 转换
std::wstring utf8ToWide(const std::string& utf8);
std::string wideToUtf8(const std::wstring& wide);
// ------------------------------------------------------------------------
// 编码转换
// ------------------------------------------------------------------------
const std::string& toUtf8() const { return data_; }
std::string toUtf8String() const { return data_; }
std::wstring toWide() const;
std::u16string toUtf16() const;
std::u32string toUtf32() const;
/// 转换为 GBK/GB2312 编码Windows 中文系统常用)
std::string toGBK() const;
// ------------------------------------------------------------------------
// 基础操作
// ------------------------------------------------------------------------
/// 返回 Unicode 字符数(不是字节数)
size_t length() const;
/// 返回 UTF-8 字节数
size_t byteSize() const { return data_.size(); }
bool empty() const { return data_.empty(); }
const char* c_str() const { return data_.c_str(); }
const std::string& str() const { return data_; }
std::string& str() { return data_; }
// ------------------------------------------------------------------------
// 字符串操作
// ------------------------------------------------------------------------
void clear() { data_.clear(); }
String& append(const String& other);
String& append(const char* utf8);
String substring(size_t start, size_t len = npos) const;
/// 查找子串,返回 Unicode 字符索引,未找到返回 npos
size_t find(const String& substr, size_t start = 0) const;
/// 是否以指定字符串开头
bool startsWith(const String& prefix) const;
/// 是否以指定字符串结尾
bool endsWith(const String& suffix) const;
/// 去除首尾空白字符
String trim() const;
/// 分割字符串
std::vector<String> split(const String& delimiter) const;
/// 替换所有匹配子串
String replaceAll(const String& from, const String& to) const;
// ------------------------------------------------------------------------
// 运算符重载
// ------------------------------------------------------------------------
String operator+(const String& other) const;
String& operator+=(const String& other);
bool operator==(const String& other) const { return data_ == other.data_; }
bool operator!=(const String& other) const { return data_ != other.data_; }
bool operator<(const String& other) const { return data_ < other.data_; }
bool operator>(const String& other) const { return data_ > other.data_; }
char operator[](size_t index) const { return data_[index]; }
// ------------------------------------------------------------------------
// 迭代器支持
// ------------------------------------------------------------------------
auto begin() { return data_.begin(); }
auto end() { return data_.end(); }
auto begin() const { return data_.begin(); }
auto end() const { return data_.end(); }
auto cbegin() const { return data_.cbegin(); }
auto cend() const { return data_.cend(); }
// ------------------------------------------------------------------------
// 静态常量
// ------------------------------------------------------------------------
static constexpr size_t npos = static_cast<size_t>(-1);
// ------------------------------------------------------------------------
// 格式化字符串(类似 sprintf
// ------------------------------------------------------------------------
template<typename... Args>
static String format(const char* fmt, Args&&... args);
private:
std::string data_; // 内部使用 UTF-8 存储
// UTF-8 辅助函数
static size_t utf8Length(const std::string& str);
static std::string utf8Substring(const std::string& str, size_t start, size_t len);
static size_t utf8CharIndexToByteIndex(const std::string& str, size_t charIndex);
};
// UTF-8 ↔ GBK/GB2312 转换Windows 中文系统常用)
std::string utf8ToGbk(const std::string& utf8);
std::string gbkToUtf8(const std::string& gbk);
// ============================================================================
// 内联实现
// ============================================================================
inline String::String(const wchar_t* wide) {
if (wide) {
std::wstring wstr(wide);
*this = fromWide(wstr);
}
}
inline String::String(const std::wstring& wide) {
*this = fromWide(wide);
}
inline String::String(const char16_t* utf16) {
if (utf16) {
std::u16string u16str(utf16);
*this = fromUtf16(u16str);
}
}
inline String::String(const std::u16string& utf16) {
*this = fromUtf16(utf16);
}
inline String::String(const char32_t* utf32) {
if (utf32) {
std::u32string u32str(utf32);
*this = fromUtf32(u32str);
}
}
inline String::String(const std::u32string& utf32) {
*this = fromUtf32(utf32);
}
// 静态工厂方法
inline String String::fromUtf8(const char* utf8) {
return String(utf8 ? utf8 : "");
}
inline String String::fromUtf8(const std::string& utf8) {
return String(utf8);
}
// 编码转换实现
inline std::wstring String::toWide() const {
if (data_.empty()) return std::wstring();
if constexpr (sizeof(wchar_t) == 4) {
// wchar_t is 32-bit (Linux/Switch): same as UTF-32
std::u32string u32 = toUtf32();
return std::wstring(u32.begin(), u32.end());
} else {
// wchar_t is 16-bit (Windows): same as UTF-16
std::u16string u16 = toUtf16();
return std::wstring(u16.begin(), u16.end());
}
}
inline String String::fromWide(const std::wstring& wide) {
if (wide.empty()) return String();
if constexpr (sizeof(wchar_t) == 4) {
std::u32string u32(wide.begin(), wide.end());
return fromUtf32(u32);
} else {
std::u16string u16(wide.begin(), wide.end());
return fromUtf16(u16);
}
}
inline String String::fromWide(const wchar_t* wide) {
return wide ? fromWide(std::wstring(wide)) : String();
}
inline std::u16string String::toUtf16() const {
if (data_.empty()) return std::u16string();
inline std::u16string utf8ToUtf16(const std::string& utf8) {
if (utf8.empty()) return std::u16string();
// UTF-8 → UTF-32 → UTF-16 (with surrogate pairs)
std::u32string u32 = toUtf32();
std::u32string u32 = utf8ToUtf32(utf8);
std::u16string result;
result.reserve(u32.size());
@ -240,8 +51,8 @@ inline std::u16string String::toUtf16() const {
return result;
}
inline String String::fromUtf16(const std::u16string& utf16) {
if (utf16.empty()) return String();
inline std::string utf16ToUtf8(const std::u16string& utf16) {
if (utf16.empty()) return std::string();
// UTF-16 → UTF-32 → UTF-8
std::u32string u32;
@ -266,19 +77,15 @@ inline String String::fromUtf16(const std::u16string& utf16) {
u32.push_back(ch);
}
return fromUtf32(u32);
return utf32ToUtf8(u32);
}
inline String String::fromUtf16(const char16_t* utf16) {
return utf16 ? fromUtf16(std::u16string(utf16)) : String();
}
inline std::u32string String::toUtf32() const {
inline std::u32string utf8ToUtf32(const std::string& utf8) {
std::u32string result;
result.reserve(length());
result.reserve(utf8.size());
const char* ptr = data_.c_str();
const char* end = ptr + data_.size();
const char* ptr = utf8.c_str();
const char* end = ptr + utf8.size();
while (ptr < end) {
char32_t ch = 0;
@ -318,7 +125,7 @@ inline std::u32string String::toUtf32() const {
return result;
}
inline String String::fromUtf32(const std::u32string& utf32) {
inline std::string utf32ToUtf8(const std::u32string& utf32) {
std::string result;
for (char32_t ch : utf32) {
@ -343,165 +150,60 @@ inline String String::fromUtf32(const std::u32string& utf32) {
}
}
return String(result);
}
inline String String::fromUtf32(const char32_t* utf32) {
return utf32 ? fromUtf32(std::u32string(utf32)) : String();
}
// UTF-8 辅助函数
inline size_t String::utf8Length(const std::string& str) {
size_t len = 0;
for (unsigned char c : str) {
if ((c & 0xC0) != 0x80) {
++len;
}
}
return len;
}
inline size_t String::length() const {
return utf8Length(data_);
}
inline size_t String::utf8CharIndexToByteIndex(const std::string& str, size_t charIndex) {
size_t charCount = 0;
size_t byteIndex = 0;
while (byteIndex < str.size() && charCount < charIndex) {
unsigned char c = static_cast<unsigned char>(str[byteIndex]);
if ((c & 0xC0) != 0x80) {
++charCount;
}
++byteIndex;
}
return byteIndex;
}
inline std::string String::utf8Substring(const std::string& str, size_t start, size_t len) {
size_t startByte = utf8CharIndexToByteIndex(str, start);
size_t endByte = (len == npos) ? str.size() : utf8CharIndexToByteIndex(str, start + len);
return str.substr(startByte, endByte - startByte);
}
inline String String::substring(size_t start, size_t len) const {
return String(utf8Substring(data_, start, len));
}
// 字符串操作
inline String& String::append(const String& other) {
data_.append(other.data_);
return *this;
}
inline String& String::append(const char* utf8) {
if (utf8) data_.append(utf8);
return *this;
}
inline size_t String::find(const String& substr, size_t start) const {
if (substr.empty() || start >= length()) return npos;
size_t startByte = utf8CharIndexToByteIndex(data_, start);
size_t pos = data_.find(substr.data_, startByte);
if (pos == std::string::npos) return npos;
// 转换字节索引到字符索引
return utf8Length(data_.substr(0, pos));
}
inline bool String::startsWith(const String& prefix) const {
if (prefix.data_.size() > data_.size()) return false;
return data_.compare(0, prefix.data_.size(), prefix.data_) == 0;
}
inline bool String::endsWith(const String& suffix) const {
if (suffix.data_.size() > data_.size()) return false;
return data_.compare(data_.size() - suffix.data_.size(), suffix.data_.size(), suffix.data_) == 0;
}
inline String String::trim() const {
size_t start = 0;
while (start < data_.size() && std::isspace(static_cast<unsigned char>(data_[start]))) {
++start;
}
size_t end = data_.size();
while (end > start && std::isspace(static_cast<unsigned char>(data_[end - 1]))) {
--end;
}
return String(data_.substr(start, end - start));
}
inline std::vector<String> String::split(const String& delimiter) const {
std::vector<String> result;
if (delimiter.empty()) {
result.push_back(*this);
return result;
}
size_t start = 0;
size_t end = data_.find(delimiter.data_, start);
while (end != std::string::npos) {
result.push_back(String(data_.substr(start, end - start)));
start = end + delimiter.data_.size();
end = data_.find(delimiter.data_, start);
}
result.push_back(String(data_.substr(start)));
return result;
}
inline String String::replaceAll(const String& from, const String& to) const {
if (from.empty()) return *this;
inline std::wstring utf8ToWide(const std::string& utf8) {
if (utf8.empty()) return std::wstring();
std::string result = data_;
size_t pos = 0;
while ((pos = result.find(from.data_, pos)) != std::string::npos) {
result.replace(pos, from.data_.size(), to.data_);
pos += to.data_.size();
if constexpr (sizeof(wchar_t) == 4) {
// wchar_t is 32-bit (Linux/Switch): same as UTF-32
std::u32string u32 = utf8ToUtf32(utf8);
return std::wstring(u32.begin(), u32.end());
} else {
// wchar_t is 16-bit (Windows): same as UTF-16
std::u16string u16 = utf8ToUtf16(utf8);
return std::wstring(u16.begin(), u16.end());
}
return String(result);
}
// 运算符重载
inline String String::operator+(const String& other) const {
String result(*this);
result.append(other);
return result;
inline std::string wideToUtf8(const std::wstring& wide) {
if (wide.empty()) return std::string();
if constexpr (sizeof(wchar_t) == 4) {
std::u32string u32(wide.begin(), wide.end());
return utf32ToUtf8(u32);
} else {
std::u16string u16(wide.begin(), wide.end());
return utf16ToUtf8(u16);
}
}
inline String& String::operator+=(const String& other) {
return append(other);
// GBK/GB2312 转换Windows 平台实现)
// 注意Windows 实现在 .cpp 文件中,避免头文件包含 windows.h 导致冲突
#ifdef _WIN32
// 前向声明,实现在 .cpp 文件中
std::string utf8ToGbkImpl(const std::string& utf8);
std::string gbkToUtf8Impl(const std::string& gbk);
inline std::string utf8ToGbk(const std::string& utf8) {
return utf8ToGbkImpl(utf8);
}
// 格式化字符串
#include <cstdio>
template<typename... Args>
String String::format(const char* fmt, Args&&... args) {
int size = std::snprintf(nullptr, 0, fmt, std::forward<Args>(args)...);
if (size <= 0) return String();
std::string result(size, '\0');
std::snprintf(&result[0], size + 1, fmt, std::forward<Args>(args)...);
return String(result);
inline std::string gbkToUtf8(const std::string& gbk) {
return gbkToUtf8Impl(gbk);
}
#else
// 非 Windows 平台GBK 转换使用 iconv 或返回原字符串
inline std::string utf8ToGbk(const std::string& utf8) {
// TODO: 使用 iconv 实现
return utf8;
}
// 全局运算符
inline String operator+(const char* lhs, const String& rhs) {
return String(lhs) + rhs;
inline std::string gbkToUtf8(const std::string& gbk) {
// TODO: 使用 iconv 实现
return gbk;
}
#endif
} // namespace extra2d

View File

@ -3,9 +3,15 @@
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
namespace extra2d {
// ---------------------------------------------------------------------------
// 字符串别名 - 统一使用 std::string (UTF-8)
// ---------------------------------------------------------------------------
using String = std::string;
// ---------------------------------------------------------------------------
// 智能指针别名
// ---------------------------------------------------------------------------

View File

@ -27,7 +27,6 @@
#include <extra2d/scene/node.h>
#include <extra2d/scene/scene.h>
#include <extra2d/scene/sprite.h>
#include <extra2d/scene/text.h>
#include <extra2d/scene/shape_node.h>
#include <extra2d/scene/scene_manager.h>
#include <extra2d/scene/transition.h>
@ -53,6 +52,12 @@
// 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>
// Action
#include <extra2d/action/action.h>
@ -92,10 +97,6 @@
// Application
#include <extra2d/app/application.h>
// Script
#include <extra2d/script/script_engine.h>
#include <extra2d/script/script_node.h>
#ifdef __SWITCH__
#include <switch.h>
#endif

View File

@ -2,7 +2,6 @@
#include <extra2d/core/color.h>
#include <extra2d/core/math_types.h>
#include <extra2d/core/string.h>
#include <extra2d/core/types.h>
namespace extra2d {

View File

@ -2,7 +2,6 @@
#include <extra2d/core/color.h>
#include <extra2d/core/math_types.h>
#include <extra2d/core/string.h>
#include <extra2d/core/types.h>
#include <glm/mat4x4.hpp>

View File

@ -1,81 +0,0 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/string.h>
#include <extra2d/graphics/font.h>
#include <extra2d/scene/node.h>
namespace extra2d {
// ============================================================================
// 文字节点
// ============================================================================
class Text : public Node {
public:
Text();
explicit Text(const String &text);
~Text() override = default;
// ------------------------------------------------------------------------
// 文字内容
// ------------------------------------------------------------------------
void setText(const String &text);
const 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 color_; }
void setFontSize(int size);
int getFontSize() const { return fontSize_; }
// ------------------------------------------------------------------------
// 对齐方式
// ------------------------------------------------------------------------
enum class Alignment { Left, Center, Right };
void setAlignment(Alignment align);
Alignment getAlignment() const { return alignment_; }
// ------------------------------------------------------------------------
// 尺寸计算
// ------------------------------------------------------------------------
Vec2 getTextSize() const;
float getLineHeight() const;
// ------------------------------------------------------------------------
// 静态创建方法
// ------------------------------------------------------------------------
static Ptr<Text> create();
static Ptr<Text> create(const String &text);
static Ptr<Text> create(const String &text, Ptr<FontAtlas> font);
Rect getBoundingBox() const override;
protected:
void onDraw(RenderBackend &renderer) override;
void generateRenderCommand(std::vector<RenderCommand> &commands,
int zOrder) override;
private:
String text_;
Ptr<FontAtlas> font_;
Color color_ = Colors::White;
int fontSize_ = 16;
Alignment alignment_ = Alignment::Left;
mutable Vec2 cachedSize_ = Vec2::Zero();
mutable bool sizeDirty_ = true;
void updateCache() const;
};
} // namespace extra2d

View File

@ -1,41 +0,0 @@
#pragma once
#include <extra2d/core/types.h>
#include <squirrel.h>
#include <string>
namespace extra2d {
class ScriptEngine {
public:
static ScriptEngine &getInstance();
ScriptEngine(const ScriptEngine &) = delete;
ScriptEngine &operator=(const ScriptEngine &) = delete;
bool initialize();
void shutdown();
bool executeString(const std::string &code);
bool executeFile(const std::string &filepath);
HSQUIRRELVM getVM() const { return vm_; }
bool isInitialized() const { return vm_ != nullptr; }
private:
ScriptEngine() = default;
~ScriptEngine();
bool compileAndRun(const std::string &source, const std::string &sourceName);
static void printFunc(HSQUIRRELVM vm, const SQChar *fmt, ...);
static void errorFunc(HSQUIRRELVM vm, const SQChar *fmt, ...);
static SQInteger errorHandler(HSQUIRRELVM vm);
static void compilerError(HSQUIRRELVM vm, const SQChar *desc,
const SQChar *source, SQInteger line,
SQInteger column);
HSQUIRRELVM vm_ = nullptr;
};
} // namespace extra2d

View File

@ -1,33 +0,0 @@
#pragma once
#include <extra2d/scene/node.h>
#include <extra2d/script/script_engine.h>
#include <string>
namespace extra2d {
class ScriptNode : public Node {
public:
ScriptNode();
~ScriptNode() override;
static Ptr<ScriptNode> create(const std::string &scriptPath);
bool loadScript(const std::string &scriptPath);
const std::string &getScriptPath() const { return scriptPath_; }
void onEnter() override;
void onExit() override;
void onUpdate(float dt) override;
private:
bool callMethod(const char *name);
bool callMethodWithFloat(const char *name, float arg);
void pushSelf();
std::string scriptPath_;
HSQOBJECT scriptTable_;
bool tableValid_ = false;
};
} // namespace extra2d

View File

@ -1,263 +0,0 @@
#pragma once
#include <extra2d/core/types.h>
#include <squirrel.h>
#include <string>
namespace extra2d {
namespace sq {
// ============================================================================
// Type tag helpers — unique address per type
// ============================================================================
template <typename T> inline SQUserPointer typeTag() {
static int tag;
return &tag;
}
// ============================================================================
// SqClassName trait — maps C++ types to Squirrel class names
// ============================================================================
template <typename T> struct SqClassName {
static const char *name();
};
// ============================================================================
// push / get primitives
// ============================================================================
inline void push(HSQUIRRELVM vm, int v) {
sq_pushinteger(vm, static_cast<SQInteger>(v));
}
inline void push(HSQUIRRELVM vm, float v) {
sq_pushfloat(vm, static_cast<SQFloat>(v));
}
inline void push(HSQUIRRELVM vm, double v) {
sq_pushfloat(vm, static_cast<SQFloat>(v));
}
inline void push(HSQUIRRELVM vm, bool v) {
sq_pushbool(vm, v ? SQTrue : SQFalse);
}
inline void push(HSQUIRRELVM vm, const char *v) { sq_pushstring(vm, v, -1); }
inline void push(HSQUIRRELVM vm, const std::string &v) {
sq_pushstring(vm, v.c_str(), static_cast<SQInteger>(v.size()));
}
inline void pushNull(HSQUIRRELVM vm) { sq_pushnull(vm); }
inline SQInteger getInt(HSQUIRRELVM vm, SQInteger idx) {
SQInteger val = 0;
sq_getinteger(vm, idx, &val);
return val;
}
inline SQFloat getFloat(HSQUIRRELVM vm, SQInteger idx) {
SQFloat val = 0;
if (SQ_FAILED(sq_getfloat(vm, idx, &val))) {
SQInteger ival = 0;
if (SQ_SUCCEEDED(sq_getinteger(vm, idx, &ival)))
val = static_cast<SQFloat>(ival);
}
return val;
}
inline bool getBool(HSQUIRRELVM vm, SQInteger idx) {
SQBool val = SQFalse;
sq_getbool(vm, idx, &val);
return val != SQFalse;
}
inline std::string getString(HSQUIRRELVM vm, SQInteger idx) {
const SQChar *str = nullptr;
sq_getstring(vm, idx, &str);
return str ? std::string(str) : std::string();
}
// ============================================================================
// Value type userdata helpers
// ============================================================================
template <typename T> T *pushValueInstance(HSQUIRRELVM vm, const T &val) {
sq_pushroottable(vm);
sq_pushstring(vm, SqClassName<T>::name(), -1);
if (SQ_FAILED(sq_get(vm, -2))) {
sq_pop(vm, 1);
return nullptr;
}
if (SQ_FAILED(sq_createinstance(vm, -1))) {
sq_pop(vm, 2);
return nullptr;
}
T *ud = nullptr;
sq_getinstanceup(vm, -1, reinterpret_cast<SQUserPointer *>(&ud), nullptr,
SQFalse);
if (ud)
*ud = val;
sq_remove(vm, -2); // class
sq_remove(vm, -2); // roottable
return ud;
}
template <typename T> T *getValueInstance(HSQUIRRELVM vm, SQInteger idx) {
T *ud = nullptr;
sq_getinstanceup(vm, idx, reinterpret_cast<SQUserPointer *>(&ud),
typeTag<T>(), SQFalse);
return ud;
}
// ============================================================================
// Shared pointer bridge for reference types
// ============================================================================
template <typename T> void pushPtr(HSQUIRRELVM vm, Ptr<T> ptr) {
if (!ptr) {
sq_pushnull(vm);
return;
}
sq_pushroottable(vm);
sq_pushstring(vm, SqClassName<T>::name(), -1);
if (SQ_FAILED(sq_get(vm, -2))) {
sq_pop(vm, 1);
sq_pushnull(vm);
return;
}
if (SQ_FAILED(sq_createinstance(vm, -1))) {
sq_pop(vm, 2);
sq_pushnull(vm);
return;
}
auto *storage = new Ptr<T>(std::move(ptr));
sq_setinstanceup(vm, -1, storage);
sq_setreleasehook(vm, -1, [](SQUserPointer p, SQInteger) -> SQInteger {
delete static_cast<Ptr<T> *>(p);
return 0;
});
sq_remove(vm, -2); // class
sq_remove(vm, -2); // roottable
}
template <typename T> Ptr<T> getPtr(HSQUIRRELVM vm, SQInteger idx) {
SQUserPointer up = nullptr;
sq_getinstanceup(vm, idx, &up, typeTag<T>(), SQFalse);
if (!up)
return nullptr;
return *static_cast<Ptr<T> *>(up);
}
template <typename T> T *getRawPtr(HSQUIRRELVM vm, SQInteger idx) {
auto p = getPtr<T>(vm, idx);
return p ? p.get() : nullptr;
}
// ============================================================================
// Singleton pointer (no release hook)
// ============================================================================
template <typename T>
void pushSingleton(HSQUIRRELVM vm, T *ptr, const char *className) {
sq_pushroottable(vm);
sq_pushstring(vm, className, -1);
if (SQ_FAILED(sq_get(vm, -2))) {
sq_pop(vm, 1);
sq_pushnull(vm);
return;
}
if (SQ_FAILED(sq_createinstance(vm, -1))) {
sq_pop(vm, 2);
sq_pushnull(vm);
return;
}
sq_setinstanceup(vm, -1, ptr);
sq_remove(vm, -2); // class
sq_remove(vm, -2); // roottable
}
template <typename T> T *getSingleton(HSQUIRRELVM vm, SQInteger idx) {
SQUserPointer up = nullptr;
sq_getinstanceup(vm, idx, &up, nullptr, SQFalse);
return static_cast<T *>(up);
}
// ============================================================================
// ClassDef — fluent API for registering a class
// ============================================================================
struct ClassDef {
HSQUIRRELVM vm;
ClassDef(HSQUIRRELVM v, const char *name, const char *base = nullptr)
: vm(v) {
sq_pushroottable(vm);
sq_pushstring(vm, name, -1);
if (base) {
sq_pushstring(vm, base, -1);
if (SQ_FAILED(sq_get(vm, -3))) {
sq_newclass(vm, SQFalse);
} else {
sq_newclass(vm, SQTrue);
}
} else {
sq_newclass(vm, SQFalse);
}
}
ClassDef &setTypeTag(SQUserPointer tag) {
sq_settypetag(vm, -1, tag);
return *this;
}
ClassDef &method(const char *name, SQFUNCTION fn, SQInteger nparams = 0,
const char *typemask = nullptr) {
sq_pushstring(vm, name, -1);
sq_newclosure(vm, fn, 0);
if (nparams > 0 && typemask)
sq_setparamscheck(vm, nparams, typemask);
sq_newslot(vm, -3, SQFalse);
return *this;
}
ClassDef &staticMethod(const char *name, SQFUNCTION fn, SQInteger nparams = 0,
const char *typemask = nullptr) {
sq_pushstring(vm, name, -1);
sq_newclosure(vm, fn, 0);
if (nparams > 0 && typemask)
sq_setparamscheck(vm, nparams, typemask);
sq_newslot(vm, -3, SQTrue);
return *this;
}
template <typename T> ClassDef &setValueType(SQFUNCTION constructor) {
sq_settypetag(vm, -1, typeTag<T>());
sq_setclassudsize(vm, -1, sizeof(T));
sq_pushstring(vm, "constructor", -1);
sq_newclosure(vm, constructor, 0);
sq_newslot(vm, -3, SQFalse);
return *this;
}
void commit() {
sq_newslot(vm, -3, SQFalse);
sq_pop(vm, 1);
}
};
// ============================================================================
// Register a table of integer constants
// ============================================================================
inline void registerConstTable(HSQUIRRELVM vm, const char *tableName,
const char *const *names,
const SQInteger *values, int count) {
sq_pushroottable(vm);
sq_pushstring(vm, tableName, -1);
sq_newtable(vm);
for (int i = 0; i < count; ++i) {
sq_pushstring(vm, names[i], -1);
sq_pushinteger(vm, values[i]);
sq_newslot(vm, -3, SQFalse);
}
sq_newslot(vm, -3, SQFalse);
sq_pop(vm, 1);
}
} // namespace sq
} // namespace extra2d

View File

@ -1,11 +0,0 @@
#pragma once
#include <extra2d/script/sq_binding.h>
namespace extra2d {
namespace sq {
void registerActionBindings(HSQUIRRELVM vm);
} // namespace sq
} // namespace extra2d

View File

@ -1,11 +0,0 @@
#pragma once
#include <extra2d/script/sq_binding.h>
namespace extra2d {
namespace sq {
void registerAnimationBindings(HSQUIRRELVM vm);
} // namespace sq
} // namespace extra2d

View File

@ -1,11 +0,0 @@
#pragma once
#include <extra2d/script/sq_binding.h>
namespace extra2d {
namespace sq {
void registerAudioBindings(HSQUIRRELVM vm);
} // namespace sq
} // namespace extra2d

View File

@ -1,13 +0,0 @@
#pragma once
#include <extra2d/script/sq_binding.h>
namespace extra2d {
namespace sq {
void registerInput(HSQUIRRELVM vm);
void registerKeyConstants(HSQUIRRELVM vm);
void registerInputBindings(HSQUIRRELVM vm);
} // namespace sq
} // namespace extra2d

View File

@ -1,16 +0,0 @@
#pragma once
#include <extra2d/script/sq_binding.h>
namespace extra2d {
namespace sq {
void registerNode(HSQUIRRELVM vm);
void registerSprite(HSQUIRRELVM vm);
void registerScene(HSQUIRRELVM vm);
void registerSceneManager(HSQUIRRELVM vm);
void registerApplication(HSQUIRRELVM vm);
void registerNodeBindings(HSQUIRRELVM vm);
} // namespace sq
} // namespace extra2d

View File

@ -1,31 +0,0 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/math_types.h>
#include <extra2d/script/sq_binding.h>
namespace extra2d {
namespace sq {
// SqClassName specializations for value types
template <> struct SqClassName<Vec2> {
static const char *name() { return "Vec2"; }
};
template <> struct SqClassName<Size> {
static const char *name() { return "Size"; }
};
template <> struct SqClassName<Rect> {
static const char *name() { return "Rect"; }
};
template <> struct SqClassName<Color> {
static const char *name() { return "Color"; }
};
void registerVec2(HSQUIRRELVM vm);
void registerSize(HSQUIRRELVM vm);
void registerRect(HSQUIRRELVM vm);
void registerColor(HSQUIRRELVM vm);
void registerValueTypes(HSQUIRRELVM vm);
} // namespace sq
} // namespace extra2d

View File

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

View File

@ -0,0 +1,95 @@
#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 String &label);
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
CheckBox *withPosition(float x, float y);
CheckBox *withPosition(const Vec2 &pos);
CheckBox *withAnchor(float x, float y);
CheckBox *withAnchor(const Vec2 &anchor);
CheckBox *withText(const String &text);
CheckBox *withFont(Ptr<FontAtlas> font);
CheckBox *withTextColor(const Color &color);
CheckBox *withSize(float width, float height);
// ------------------------------------------------------------------------
// 链式调用 - 坐标空间设置
// ------------------------------------------------------------------------
CheckBox *withCoordinateSpace(CoordinateSpace space);
CheckBox *withScreenPosition(float x, float y);
CheckBox *withScreenPosition(const Vec2 &pos);
CheckBox *withCameraOffset(float x, float y);
CheckBox *withCameraOffset(const Vec2 &offset);
void setChecked(bool checked);
bool isChecked() const { return checked_; }
void toggle();
void setLabel(const String &label);
const 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;
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

@ -0,0 +1,170 @@
#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 String &text);
~Label() override = default;
// ------------------------------------------------------------------------
// 静态创建方法
// ------------------------------------------------------------------------
static Ptr<Label> create();
static Ptr<Label> create(const String &text);
static Ptr<Label> create(const String &text, Ptr<FontAtlas> font);
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
Label *withPosition(float x, float y);
Label *withPosition(const Vec2 &pos);
Label *withAnchor(float x, float y);
Label *withAnchor(const Vec2 &anchor);
Label *withText(const String &text);
Label *withFont(Ptr<FontAtlas> font);
Label *withTextColor(const Color &color);
Label *withFontSize(int size);
// ------------------------------------------------------------------------
// 坐标空间设置(链式调用)
// ------------------------------------------------------------------------
Label *withCoordinateSpace(CoordinateSpace space);
Label *withScreenPosition(float x, float y);
Label *withScreenPosition(const Vec2 &pos);
Label *withCameraOffset(float x, float y);
Label *withCameraOffset(const Vec2 &offset);
// ------------------------------------------------------------------------
// 文本内容
// ------------------------------------------------------------------------
void setText(const String &text);
const 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:
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<String> splitLines() const;
};
} // namespace extra2d

View File

@ -0,0 +1,233 @@
#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);
// ------------------------------------------------------------------------
// 链式调用构建器方法 - 坐标空间支持
// ------------------------------------------------------------------------
ProgressBar *withPosition(float x, float y);
ProgressBar *withPosition(const Vec2 &pos);
ProgressBar *withAnchor(float x, float y);
ProgressBar *withAnchor(const Vec2 &anchor);
ProgressBar *withSize(float width, float height);
ProgressBar *withProgress(float progress);
ProgressBar *withCoordinateSpace(CoordinateSpace space);
ProgressBar *withScreenPosition(float x, float y);
ProgressBar *withScreenPosition(const Vec2 &pos);
ProgressBar *withCameraOffset(float x, float y);
ProgressBar *withCameraOffset(const Vec2 &offset);
// ------------------------------------------------------------------------
// 数值范围
// ------------------------------------------------------------------------
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 String &format);
const 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;
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;
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

@ -0,0 +1,113 @@
#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 String &label);
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
RadioButton *withPosition(float x, float y);
RadioButton *withPosition(const Vec2 &pos);
RadioButton *withAnchor(float x, float y);
RadioButton *withAnchor(const Vec2 &anchor);
RadioButton *withText(const String &text);
RadioButton *withFont(Ptr<FontAtlas> font);
RadioButton *withTextColor(const Color &color);
RadioButton *withSize(float width, float height);
RadioButton *withCoordinateSpace(CoordinateSpace space);
RadioButton *withScreenPosition(float x, float y);
RadioButton *withScreenPosition(const Vec2 &pos);
RadioButton *withCameraOffset(float x, float y);
RadioButton *withCameraOffset(const Vec2 &offset);
void setSelected(bool selected);
bool isSelected() const { return selected_; }
void setLabel(const String &label);
const 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;
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

@ -0,0 +1,142 @@
#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);
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
Slider *withPosition(float x, float y);
Slider *withPosition(const Vec2 &pos);
Slider *withAnchor(float x, float y);
Slider *withAnchor(const Vec2 &anchor);
Slider *withSize(float width, float height);
Slider *withMinValue(float min);
Slider *withMaxValue(float max);
Slider *withValue(float value);
Slider *withCoordinateSpace(CoordinateSpace space);
Slider *withScreenPosition(float x, float y);
Slider *withScreenPosition(const Vec2 &pos);
Slider *withCameraOffset(float x, float y);
Slider *withCameraOffset(const Vec2 &offset);
void setRange(float min, float max);
float getMin() const { return min_; }
float getMax() const { return max_; }
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 String &format);
const 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; // 0表示无步进
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;
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;
String formatText() const;
float snapToStep(float value) const;
};
} // namespace extra2d

View File

@ -0,0 +1,157 @@
#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 String &text);
~Text() override = default;
// ------------------------------------------------------------------------
// 静态创建方法
// ------------------------------------------------------------------------
static Ptr<Text> create();
static Ptr<Text> create(const String &text);
static Ptr<Text> create(const 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, ...);
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
Text *withPosition(float x, float y);
Text *withPosition(const Vec2 &pos);
Text *withAnchor(float x, float y);
Text *withAnchor(const Vec2 &anchor);
Text *withTextColor(const Color &color);
Text *withFont(Ptr<FontAtlas> font);
Text *withFontSize(int size);
Text *withAlignment(Alignment align);
Text *withVerticalAlignment(VerticalAlignment align);
// ------------------------------------------------------------------------
// 文字内容
// ------------------------------------------------------------------------
void setText(const String &text);
const 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();
}
// 链式调用的格式化方法
// 注意由于C++可变参数的限制,链式调用需要单独设置格式和值
Text *withFormattedText(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();
return this;
}
// ------------------------------------------------------------------------
// 字体
// ------------------------------------------------------------------------
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;
// ------------------------------------------------------------------------
// 链式调用 - 坐标空间设置
// ------------------------------------------------------------------------
Text *withCoordinateSpace(CoordinateSpace space);
Text *withScreenPosition(float x, float y);
Text *withScreenPosition(const Vec2 &pos);
Text *withCameraOffset(float x, float y);
Text *withCameraOffset(const Vec2 &offset);
protected:
void onDrawWidget(RenderBackend &renderer) override;
private:
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,9 +1,34 @@
#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();
@ -15,8 +40,72 @@ public:
Rect getBoundingBox() const override;
// ------------------------------------------------------------------------
// 坐标空间设置
// ------------------------------------------------------------------------
void setCoordinateSpace(CoordinateSpace space);
CoordinateSpace getCoordinateSpace() const { return coordinateSpace_; }
// ------------------------------------------------------------------------
// 屏幕空间位置设置(仅在 Screen 空间下有效)
// ------------------------------------------------------------------------
void setScreenPosition(const Vec2 &pos);
void setScreenPosition(float x, float y);
Vec2 getScreenPosition() const { return screenPosition_; }
// ------------------------------------------------------------------------
// 相机空间偏移设置(仅在 Camera 空间下有效)
// ------------------------------------------------------------------------
void setCameraOffset(const Vec2 &offset);
void setCameraOffset(float x, float y);
Vec2 getCameraOffset() const { return cameraOffset_; }
// ------------------------------------------------------------------------
// 鼠标事件处理(子类可重写)
// ------------------------------------------------------------------------
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

@ -1,218 +1,48 @@
#include <cstdint>
#include <cstring>
#include <extra2d/core/string.h>
#ifdef _WIN32
#include <windows.h>
namespace extra2d {
// ============================================================================
// GBK/GB2312 到 UTF-8 转换表
// 使用简化的转换表,覆盖常用中文字符
// ============================================================================
std::string utf8ToGbkImpl(const std::string& utf8) {
if (utf8.empty()) return std::string();
// GBK 编码范围:
// - 单字节0x00-0x7F (ASCII)
// - 双字节0x81-0xFE 0x40-0xFE
// UTF-8 → Wide → GBK
int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, nullptr, 0);
if (wideLen <= 0) return std::string();
// 将 GBK 双字节解码为 Unicode 码点
// 这是简化的实现,使用 GBK 到 Unicode 的映射公式
static uint32_t gbkToUnicode(uint16_t gbkCode) {
// GBK 编码转 Unicode 的简化算法
// 对于常见汉字,使用近似转换
std::wstring wide(wideLen - 1, 0);
MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, &wide[0], wideLen);
uint8_t high = (gbkCode >> 8) & 0xFF;
uint8_t low = gbkCode & 0xFF;
int gbkLen = WideCharToMultiByte(CP_ACP, 0, wide.c_str(), -1, nullptr, 0, nullptr, nullptr);
if (gbkLen <= 0) return std::string();
// ASCII 范围
if (high < 0x80) {
return high;
}
std::string gbk(gbkLen - 1, 0);
WideCharToMultiByte(CP_ACP, 0, wide.c_str(), -1, &gbk[0], gbkLen, nullptr, nullptr);
// GBK 双字节范围
if (high >= 0x81 && high <= 0xFE && low >= 0x40 && low <= 0xFE) {
// GBK 到 Unicode 的偏移计算(近似)
// GB2312 区域0xB0A1-0xF7FE -> Unicode 0x4E00-0x9FA5
if (high >= 0xB0 && high <= 0xF7 && low >= 0xA1 && low <= 0xFE) {
uint32_t area = high - 0xB0;
uint32_t pos = low - 0xA1;
return 0x4E00 + area * 94 + pos;
}
// 其他 GBK 区域使用线性映射
uint32_t gbkOffset = (high - 0x81) * 190 + (low - 0x40);
if (low > 0x7F) {
gbkOffset--;
}
// 映射到扩展 Unicode 区域
return 0x4E00 + (gbkOffset % 0x51A5);
}
// 无效字符返回替换字符
return 0xFFFD;
return gbk;
}
// 将 Unicode 码点编码为 UTF-8
static std::string unicodeToUtf8(uint32_t codepoint) {
std::string result;
std::string gbkToUtf8Impl(const std::string& gbk) {
if (gbk.empty()) return std::string();
if (codepoint <= 0x7F) {
// 1-byte
result.push_back(static_cast<char>(codepoint));
} else if (codepoint <= 0x7FF) {
// 2-byte
result.push_back(static_cast<char>(0xC0 | ((codepoint >> 6) & 0x1F)));
result.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
} else if (codepoint <= 0xFFFF) {
// 3-byte
result.push_back(static_cast<char>(0xE0 | ((codepoint >> 12) & 0x0F)));
result.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
result.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
} else if (codepoint <= 0x10FFFF) {
// 4-byte
result.push_back(static_cast<char>(0xF0 | ((codepoint >> 18) & 0x07)));
result.push_back(static_cast<char>(0x80 | ((codepoint >> 12) & 0x3F)));
result.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
result.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
}
// GBK → Wide → UTF-8
int wideLen = MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), -1, nullptr, 0);
if (wideLen <= 0) return std::string();
return result;
}
std::wstring wide(wideLen - 1, 0);
MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), -1, &wide[0], wideLen);
// 将 UTF-8 解码为 Unicode 码点
// 返回解码后的码点和消耗的 UTF-8 字节数
static std::pair<uint32_t, size_t> utf8ToUnicode(const char *utf8) {
unsigned char byte = static_cast<unsigned char>(*utf8);
int utf8Len = WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, nullptr, 0, nullptr, nullptr);
if (utf8Len <= 0) return std::string();
if ((byte & 0x80) == 0) {
// 1-byte
return {byte, 1};
} else if ((byte & 0xE0) == 0xC0) {
// 2-byte
uint32_t ch = (byte & 0x1F) << 6;
ch |= (static_cast<unsigned char>(utf8[1]) & 0x3F);
return {ch, 2};
} else if ((byte & 0xF0) == 0xE0) {
// 3-byte
uint32_t ch = (byte & 0x0F) << 12;
ch |= (static_cast<unsigned char>(utf8[1]) & 0x3F) << 6;
ch |= (static_cast<unsigned char>(utf8[2]) & 0x3F);
return {ch, 3};
} else if ((byte & 0xF8) == 0xF0) {
// 4-byte
uint32_t ch = (byte & 0x07) << 18;
ch |= (static_cast<unsigned char>(utf8[1]) & 0x3F) << 12;
ch |= (static_cast<unsigned char>(utf8[2]) & 0x3F) << 6;
ch |= (static_cast<unsigned char>(utf8[3]) & 0x3F);
return {ch, 4};
}
std::string utf8(utf8Len - 1, 0);
WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, &utf8[0], utf8Len, nullptr, nullptr);
// Invalid UTF-8
return {0xFFFD, 1};
}
// 将 Unicode 码点编码为 GBK 双字节
static uint16_t unicodeToGbk(uint32_t unicode) {
// ASCII 范围
if (unicode <= 0x7F) {
return static_cast<uint16_t>(unicode);
}
// CJK Unified Ideographs (U+4E00 - U+9FA5) -> GB2312
if (unicode >= 0x4E00 && unicode <= 0x9FA5) {
uint32_t offset = unicode - 0x4E00;
uint32_t area = offset / 94;
uint32_t pos = offset % 94;
uint8_t high = static_cast<uint8_t>(0xB0 + area);
uint8_t low = static_cast<uint8_t>(0xA1 + pos);
return (static_cast<uint16_t>(high) << 8) | low;
}
// 其他字符无法转换,返回 '?'
return 0x3F;
}
// ============================================================================
// String 类 GBK 转换实现
// ============================================================================
String String::fromGBK(const char *gbk) {
if (!gbk || std::strlen(gbk) == 0) {
return String();
}
return fromGBK(std::string(gbk));
}
String String::fromGBK(const std::string &gbk) {
if (gbk.empty()) {
return String();
}
std::string utf8Result;
utf8Result.reserve(gbk.size() * 2); // 预估 UTF-8 可能比 GBK 大
size_t i = 0;
while (i < gbk.size()) {
unsigned char byte = static_cast<unsigned char>(gbk[i]);
if (byte < 0x80) {
// ASCII 字符,直接复制
utf8Result.push_back(static_cast<char>(byte));
i++;
} else if (i + 1 < gbk.size()) {
// GBK 双字节字符
uint8_t high = byte;
uint8_t low = static_cast<unsigned char>(gbk[i + 1]);
uint16_t gbkCode = (static_cast<uint16_t>(high) << 8) | low;
uint32_t unicode = gbkToUnicode(gbkCode);
utf8Result += unicodeToUtf8(unicode);
i += 2;
} else {
// 不完整的 GBK 字符,跳过
i++;
}
}
return String(utf8Result);
}
std::string String::toGBK() const {
if (data_.empty()) {
return std::string();
}
std::string gbkResult;
gbkResult.reserve(data_.size()); // 预估 GBK 可能更小或与 UTF-8 相当
size_t i = 0;
while (i < data_.size()) {
auto [unicode, bytes] = utf8ToUnicode(data_.c_str() + i);
if (unicode <= 0x7F) {
// ASCII 字符
gbkResult.push_back(static_cast<char>(unicode));
} else {
// 转换为 GBK 双字节
uint16_t gbkCode = unicodeToGbk(unicode);
if (gbkCode > 0xFF) {
// 双字节 GBK
gbkResult.push_back(static_cast<char>((gbkCode >> 8) & 0xFF));
gbkResult.push_back(static_cast<char>(gbkCode & 0xFF));
} else {
// 单字节ASCII
gbkResult.push_back(static_cast<char>(gbkCode));
}
}
i += bytes;
}
return gbkResult;
return utf8;
}
} // namespace extra2d
#endif // _WIN32

View File

@ -1,11 +1,12 @@
#include <extra2d/graphics/opengl/gl_font_atlas.h>
#include <extra2d/core/string.h>
#include <extra2d/utils/logger.h>
#include <fstream>
#define STB_TRUETYPE_IMPLEMENTATION
#include <stb/stb_truetype.h>
#define STB_RECT_PACK_IMPLEMENTATION
#include <algorithm>
#include <extra2d/utils/logger.h>
#include <fstream>
#include <stb/stb_rect_pack.h>
#include <algorithm>
namespace extra2d {
@ -74,7 +75,7 @@ Vec2 GLFontAtlas::measureText(const String &text) {
float height = getAscent() - getDescent();
float currentWidth = 0.0f;
for (char32_t codepoint : text.toUtf32()) {
for (char32_t codepoint : utf8ToUtf32(text)) {
if (codepoint == '\n') {
width = std::max(width, currentWidth);
currentWidth = 0.0f;

View File

@ -2,6 +2,7 @@
#include <algorithm>
#include <cmath>
#include <cstring>
#include <extra2d/core/string.h>
#include <extra2d/graphics/opengl/gl_font_atlas.h>
#include <extra2d/graphics/opengl/gl_renderer.h>
#include <extra2d/graphics/opengl/gl_texture.h>
@ -366,7 +367,7 @@ void GLRenderer::drawText(const FontAtlas &font, const String &text, float x,
float cursorY = y;
float baselineY = cursorY + font.getAscent();
for (char32_t codepoint : text.toUtf32()) {
for (char32_t codepoint : utf8ToUtf32(text)) {
if (codepoint == '\n') {
cursorX = x;
cursorY += font.getLineHeight();

View File

@ -1,145 +0,0 @@
#include <extra2d/graphics/render_backend.h>
#include <extra2d/graphics/render_command.h>
#include <extra2d/scene/text.h>
namespace extra2d {
Text::Text() {
// 文字默认锚点为左上角这样setPosition(0, 0)会在左上角显示
setAnchor(0.0f, 0.0f);
}
Text::Text(const String &text) : text_(text) {
sizeDirty_ = true;
// 文字默认锚点为左上角这样setPosition(0, 0)会在左上角显示
setAnchor(0.0f, 0.0f);
}
void Text::setText(const String &text) {
text_ = text;
sizeDirty_ = true;
updateSpatialIndex();
}
void Text::setFont(Ptr<FontAtlas> font) {
font_ = font;
sizeDirty_ = true;
updateSpatialIndex();
}
void Text::setTextColor(const Color &color) { color_ = color; }
void Text::setFontSize(int size) {
fontSize_ = size;
sizeDirty_ = true;
updateSpatialIndex();
}
void Text::setAlignment(Alignment align) {
alignment_ = align;
updateSpatialIndex();
}
Vec2 Text::getTextSize() const {
updateCache();
return cachedSize_;
}
float Text::getLineHeight() const {
if (font_) {
return font_->getLineHeight();
}
return static_cast<float>(fontSize_);
}
void Text::updateCache() const {
if (!sizeDirty_ || !font_) {
return;
}
cachedSize_ = font_->measureText(text_);
sizeDirty_ = false;
}
Ptr<Text> Text::create() { return makePtr<Text>(); }
Ptr<Text> Text::create(const String &text) { return makePtr<Text>(text); }
Ptr<Text> Text::create(const String &text, Ptr<FontAtlas> font) {
auto t = makePtr<Text>(text);
t->setFont(font);
return t;
}
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 = getPosition();
if (alignment_ != Alignment::Left) {
if (alignment_ == Alignment::Center) {
pos.x -= size.x * 0.5f;
} else if (alignment_ == Alignment::Right) {
pos.x -= size.x;
}
}
return Rect(pos.x, pos.y, size.x, size.y);
}
void Text::onDraw(RenderBackend &renderer) {
if (!font_ || text_.empty()) {
return;
}
Vec2 pos = getPosition();
// Calculate horizontal offset based on alignment
if (alignment_ != Alignment::Left) {
Vec2 size = getTextSize();
if (alignment_ == Alignment::Center) {
pos.x -= size.x * 0.5f;
} else if (alignment_ == Alignment::Right) {
pos.x -= size.x;
}
}
renderer.drawText(*font_, text_, pos, color_);
}
void Text::generateRenderCommand(std::vector<RenderCommand> &commands,
int zOrder) {
if (!font_ || text_.empty()) {
return;
}
Vec2 pos = getPosition();
// 计算对齐偏移(与 onDraw 一致)
if (alignment_ != Alignment::Left) {
Vec2 size = getTextSize();
if (alignment_ == Alignment::Center) {
pos.x -= size.x * 0.5f;
} else if (alignment_ == Alignment::Right) {
pos.x -= size.x;
}
}
// 创建渲染命令
RenderCommand cmd;
cmd.type = RenderCommandType::Text;
cmd.zOrder = zOrder;
cmd.data = TextData{font_, text_, pos, color_};
commands.push_back(std::move(cmd));
}
} // namespace extra2d

View File

@ -1,187 +0,0 @@
#include <cstdarg>
#include <extra2d/script/script_engine.h>
#include <extra2d/script/sq_binding_action.h>
#include <extra2d/script/sq_binding_animation.h>
#include <extra2d/script/sq_binding_audio.h>
#include <extra2d/script/sq_binding_input.h>
#include <extra2d/script/sq_binding_node.h>
#include <extra2d/script/sq_binding_types.h>
#include <extra2d/utils/logger.h>
#include <fstream>
#include <sqstdaux.h>
#include <sqstdblob.h>
#include <sqstdio.h>
#include <sqstdmath.h>
#include <sqstdstring.h>
#include <sstream>
namespace extra2d {
ScriptEngine &ScriptEngine::getInstance() {
static ScriptEngine instance;
return instance;
}
ScriptEngine::~ScriptEngine() { shutdown(); }
bool ScriptEngine::initialize() {
if (vm_)
return true;
vm_ = sq_open(1024);
if (!vm_) {
E2D_ERROR("ScriptEngine: failed to create Squirrel VM");
return false;
}
sq_setprintfunc(vm_, printFunc, errorFunc);
sq_setcompilererrorhandler(vm_, compilerError);
sq_pushroottable(vm_);
// Register standard libraries
sqstd_register_mathlib(vm_);
sqstd_register_stringlib(vm_);
sqstd_register_bloblib(vm_);
sqstd_register_iolib(vm_);
// Set error handler
sq_newclosure(vm_, errorHandler, 0);
sq_seterrorhandler(vm_);
sq_pop(vm_, 1); // pop root table
// Register Easy2D bindings
sq::registerValueTypes(vm_);
sq::registerNodeBindings(vm_);
sq::registerInputBindings(vm_);
sq::registerActionBindings(vm_);
sq::registerAudioBindings(vm_);
sq::registerAnimationBindings(vm_);
// Register global log function
sq_pushroottable(vm_);
sq_pushstring(vm_, "log", -1);
sq_newclosure(
vm_,
[](HSQUIRRELVM v) -> SQInteger {
const SQChar *msg = nullptr;
sq_getstring(v, 2, &msg);
if (msg)
E2D_INFO("[Script] {}", msg);
return 0;
},
0);
sq_newslot(vm_, -3, SQFalse);
sq_pop(vm_, 1);
E2D_INFO("ScriptEngine: Squirrel VM initialized (v{})", SQUIRREL_VERSION);
return true;
}
void ScriptEngine::shutdown() {
if (vm_) {
sq_close(vm_);
vm_ = nullptr;
E2D_INFO("ScriptEngine: Squirrel VM shut down");
}
}
bool ScriptEngine::executeString(const std::string &code) {
if (!vm_) {
E2D_ERROR("ScriptEngine: VM not initialized");
return false;
}
return compileAndRun(code, "<string>");
}
bool ScriptEngine::executeFile(const std::string &filepath) {
if (!vm_) {
E2D_ERROR("ScriptEngine: VM not initialized");
return false;
}
std::ifstream file(filepath);
if (!file.is_open()) {
E2D_ERROR("ScriptEngine: cannot open file '{}'", filepath);
return false;
}
std::ostringstream ss;
ss << file.rdbuf();
return compileAndRun(ss.str(), filepath);
}
bool ScriptEngine::compileAndRun(const std::string &source,
const std::string &sourceName) {
SQInteger top = sq_gettop(vm_);
sq_pushroottable(vm_);
if (SQ_FAILED(sq_compilebuffer(vm_, source.c_str(),
static_cast<SQInteger>(source.size()),
sourceName.c_str(), SQTrue))) {
sq_settop(vm_, top);
return false;
}
sq_push(vm_, -2); // push root table as 'this'
if (SQ_FAILED(sq_call(vm_, 1, SQFalse, SQTrue))) {
sq_settop(vm_, top);
return false;
}
sq_settop(vm_, top);
return true;
}
void ScriptEngine::printFunc(HSQUIRRELVM, const SQChar *fmt, ...) {
va_list args;
va_start(args, fmt);
char buf[2048];
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
E2D_INFO("[Squirrel] {}", buf);
}
void ScriptEngine::errorFunc(HSQUIRRELVM, const SQChar *fmt, ...) {
va_list args;
va_start(args, fmt);
char buf[2048];
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
E2D_ERROR("[Squirrel] {}", buf);
}
SQInteger ScriptEngine::errorHandler(HSQUIRRELVM vm) {
const SQChar *errMsg = nullptr;
if (sq_gettop(vm) >= 1) {
if (SQ_FAILED(sq_getstring(vm, 2, &errMsg))) {
errMsg = "unknown error";
}
}
// Print call stack
SQStackInfos si;
SQInteger level = 1;
E2D_ERROR("[Squirrel] Runtime error: {}", errMsg ? errMsg : "unknown");
while (SQ_SUCCEEDED(sq_stackinfos(vm, level, &si))) {
const SQChar *fn = si.funcname ? si.funcname : "unknown";
const SQChar *src = si.source ? si.source : "unknown";
E2D_ERROR(" [{}] {}:{} in {}", level, src, si.line, fn);
++level;
}
return 0;
}
void ScriptEngine::compilerError(HSQUIRRELVM, const SQChar *desc,
const SQChar *source, SQInteger line,
SQInteger column) {
E2D_ERROR("[Squirrel] Compile error: {}:{}:{}: {}", source, line, column,
desc);
}
} // namespace extra2d

View File

@ -1,202 +0,0 @@
#include <extra2d/script/script_node.h>
#include <extra2d/script/sq_binding.h>
#include <extra2d/script/sq_binding_node.h>
#include <extra2d/utils/logger.h>
#include <fstream>
#include <sstream>
namespace extra2d {
// SqClassName needed for pushPtr<Node> which ScriptNode inherits
namespace sq {
// Already defined in sq_binding_node.cpp, but we need it here for pushPtr
template <> struct SqClassName<Node> {
static const char *name() { return "Node"; }
};
} // namespace sq
ScriptNode::ScriptNode() { sq_resetobject(&scriptTable_); }
ScriptNode::~ScriptNode() {
if (tableValid_) {
auto &engine = ScriptEngine::getInstance();
if (engine.isInitialized()) {
sq_release(engine.getVM(), &scriptTable_);
}
tableValid_ = false;
}
}
Ptr<ScriptNode> ScriptNode::create(const std::string &scriptPath) {
auto node = makePtr<ScriptNode>();
if (!node->loadScript(scriptPath)) {
E2D_ERROR("ScriptNode: failed to load '{}'", scriptPath);
}
return node;
}
bool ScriptNode::loadScript(const std::string &scriptPath) {
scriptPath_ = scriptPath;
auto &engine = ScriptEngine::getInstance();
if (!engine.isInitialized()) {
E2D_ERROR("ScriptNode: ScriptEngine not initialized");
return false;
}
HSQUIRRELVM vm = engine.getVM();
// Read file
std::ifstream file(scriptPath);
if (!file.is_open()) {
E2D_ERROR("ScriptNode: cannot open '{}'", scriptPath);
return false;
}
std::ostringstream ss;
ss << file.rdbuf();
std::string source = ss.str();
SQInteger top = sq_gettop(vm);
// Compile
sq_pushroottable(vm);
if (SQ_FAILED(sq_compilebuffer(vm, source.c_str(),
static_cast<SQInteger>(source.size()),
scriptPath.c_str(), SQTrue))) {
sq_settop(vm, top);
return false;
}
sq_push(vm, -2); // root table as 'this'
// Execute — expect a return value (the table)
if (SQ_FAILED(sq_call(vm, 1, SQTrue, SQTrue))) {
sq_settop(vm, top);
return false;
}
// Check if return value is a table
if (sq_gettype(vm, -1) == OT_TABLE) {
sq_getstackobj(vm, -1, &scriptTable_);
sq_addref(vm, &scriptTable_);
tableValid_ = true;
} else {
E2D_WARN("ScriptNode: '{}' did not return a table", scriptPath);
}
sq_settop(vm, top);
return tableValid_;
}
void ScriptNode::onEnter() {
Node::onEnter();
if (tableValid_) {
auto &engine = ScriptEngine::getInstance();
HSQUIRRELVM vm = engine.getVM();
SQInteger top = sq_gettop(vm);
sq_pushobject(vm, scriptTable_);
sq_pushstring(vm, "onEnter", -1);
if (SQ_SUCCEEDED(sq_get(vm, -2))) {
sq_pushobject(vm, scriptTable_); // 'this' = script table
pushSelf(); // arg: node
sq_call(vm, 2, SQFalse, SQTrue);
sq_pop(vm, 1); // pop closure
}
sq_settop(vm, top);
}
}
void ScriptNode::onExit() {
if (tableValid_) {
auto &engine = ScriptEngine::getInstance();
HSQUIRRELVM vm = engine.getVM();
SQInteger top = sq_gettop(vm);
sq_pushobject(vm, scriptTable_);
sq_pushstring(vm, "onExit", -1);
if (SQ_SUCCEEDED(sq_get(vm, -2))) {
sq_pushobject(vm, scriptTable_);
pushSelf();
sq_call(vm, 2, SQFalse, SQTrue);
sq_pop(vm, 1);
}
sq_settop(vm, top);
}
Node::onExit();
}
void ScriptNode::onUpdate(float dt) {
Node::onUpdate(dt);
if (tableValid_) {
auto &engine = ScriptEngine::getInstance();
HSQUIRRELVM vm = engine.getVM();
SQInteger top = sq_gettop(vm);
sq_pushobject(vm, scriptTable_);
sq_pushstring(vm, "onUpdate", -1);
if (SQ_SUCCEEDED(sq_get(vm, -2))) {
sq_pushobject(vm, scriptTable_);
pushSelf();
sq_pushfloat(vm, dt);
sq_call(vm, 3, SQFalse, SQTrue);
sq_pop(vm, 1);
}
sq_settop(vm, top);
}
}
void ScriptNode::pushSelf() {
auto &engine = ScriptEngine::getInstance();
HSQUIRRELVM vm = engine.getVM();
// Push this node as a Node instance with shared_ptr
auto self = std::dynamic_pointer_cast<Node>(shared_from_this());
sq::pushPtr(vm, self);
}
bool ScriptNode::callMethod(const char *name) {
if (!tableValid_)
return false;
auto &engine = ScriptEngine::getInstance();
HSQUIRRELVM vm = engine.getVM();
SQInteger top = sq_gettop(vm);
sq_pushobject(vm, scriptTable_);
sq_pushstring(vm, name, -1);
if (SQ_FAILED(sq_get(vm, -2))) {
sq_settop(vm, top);
return false;
}
sq_pushobject(vm, scriptTable_);
pushSelf();
bool ok = SQ_SUCCEEDED(sq_call(vm, 2, SQFalse, SQTrue));
sq_settop(vm, top);
return ok;
}
bool ScriptNode::callMethodWithFloat(const char *name, float arg) {
if (!tableValid_)
return false;
auto &engine = ScriptEngine::getInstance();
HSQUIRRELVM vm = engine.getVM();
SQInteger top = sq_gettop(vm);
sq_pushobject(vm, scriptTable_);
sq_pushstring(vm, name, -1);
if (SQ_FAILED(sq_get(vm, -2))) {
sq_settop(vm, top);
return false;
}
sq_pushobject(vm, scriptTable_);
pushSelf();
sq_pushfloat(vm, arg);
bool ok = SQ_SUCCEEDED(sq_call(vm, 3, SQFalse, SQTrue));
sq_settop(vm, top);
return ok;
}
} // namespace extra2d

View File

@ -1,222 +0,0 @@
#include <extra2d/action/action.h>
#include <extra2d/action/actions.h>
#include <extra2d/script/sq_binding_action.h>
#include <extra2d/script/sq_binding_types.h>
namespace extra2d {
namespace sq {
// Actions are stored as Ptr<Action> in userdata (they use raw new in C++ API
// but we wrap them in shared_ptr for the script bridge)
template <> struct SqClassName<Action> {
static const char *name() { return "Action"; }
};
// Helper: wrap a raw Action* into Ptr<Action> and push it
static void pushAction(HSQUIRRELVM vm, Action *raw) {
Ptr<Action> ptr(raw);
sq_pushroottable(vm);
sq_pushstring(vm, "Action", -1);
if (SQ_FAILED(sq_get(vm, -2))) {
sq_pop(vm, 1);
sq_pushnull(vm);
return;
}
if (SQ_FAILED(sq_createinstance(vm, -1))) {
sq_pop(vm, 2);
sq_pushnull(vm);
return;
}
auto *storage = new Ptr<Action>(std::move(ptr));
sq_setinstanceup(vm, -1, storage);
sq_setreleasehook(vm, -1, [](SQUserPointer p, SQInteger) -> SQInteger {
delete static_cast<Ptr<Action> *>(p);
return 0;
});
sq_remove(vm, -2); // class
sq_remove(vm, -2); // roottable
}
static Ptr<Action> getAction(HSQUIRRELVM vm, SQInteger idx) {
SQUserPointer up = nullptr;
sq_getinstanceup(vm, idx, &up, nullptr, SQFalse);
if (!up)
return nullptr;
return *static_cast<Ptr<Action> *>(up);
}
// ============================================================================
// Factory functions (registered as global functions)
// ============================================================================
static SQInteger sqMoveTo(HSQUIRRELVM vm) {
float dur = static_cast<float>(getFloat(vm, 2));
Vec2 *pos = getValueInstance<Vec2>(vm, 3);
if (!pos)
return sq_throwerror(vm, "expected Vec2");
pushAction(vm, new MoveTo(dur, *pos));
return 1;
}
static SQInteger sqMoveBy(HSQUIRRELVM vm) {
float dur = static_cast<float>(getFloat(vm, 2));
Vec2 *delta = getValueInstance<Vec2>(vm, 3);
if (!delta)
return sq_throwerror(vm, "expected Vec2");
pushAction(vm, new MoveBy(dur, *delta));
return 1;
}
static SQInteger sqScaleTo(HSQUIRRELVM vm) {
float dur = static_cast<float>(getFloat(vm, 2));
SQInteger argc = sq_gettop(vm);
if (argc >= 4) {
float sx = static_cast<float>(getFloat(vm, 3));
float sy = static_cast<float>(getFloat(vm, 4));
pushAction(vm, new ScaleTo(dur, sx, sy));
} else {
float s = static_cast<float>(getFloat(vm, 3));
pushAction(vm, new ScaleTo(dur, s));
}
return 1;
}
static SQInteger sqRotateTo(HSQUIRRELVM vm) {
float dur = static_cast<float>(getFloat(vm, 2));
float angle = static_cast<float>(getFloat(vm, 3));
pushAction(vm, new RotateTo(dur, angle));
return 1;
}
static SQInteger sqRotateBy(HSQUIRRELVM vm) {
float dur = static_cast<float>(getFloat(vm, 2));
float angle = static_cast<float>(getFloat(vm, 3));
pushAction(vm, new RotateBy(dur, angle));
return 1;
}
static SQInteger sqFadeIn(HSQUIRRELVM vm) {
float dur = static_cast<float>(getFloat(vm, 2));
pushAction(vm, new FadeIn(dur));
return 1;
}
static SQInteger sqFadeOut(HSQUIRRELVM vm) {
float dur = static_cast<float>(getFloat(vm, 2));
pushAction(vm, new FadeOut(dur));
return 1;
}
static SQInteger sqFadeTo(HSQUIRRELVM vm) {
float dur = static_cast<float>(getFloat(vm, 2));
float opacity = static_cast<float>(getFloat(vm, 3));
pushAction(vm, new FadeTo(dur, opacity));
return 1;
}
static SQInteger sqDelay(HSQUIRRELVM vm) {
float dur = static_cast<float>(getFloat(vm, 2));
pushAction(vm, new Delay(dur));
return 1;
}
static SQInteger sqSequence(HSQUIRRELVM vm) {
// arg 2 is an array of actions
SQInteger size = sq_getsize(vm, 2);
std::vector<Action *> actions;
actions.reserve(size);
for (SQInteger i = 0; i < size; ++i) {
sq_pushinteger(vm, i);
sq_get(vm, 2);
auto a = getAction(vm, -1);
if (a)
actions.push_back(a->clone());
sq_pop(vm, 1);
}
if (actions.empty())
return sq_throwerror(vm, "empty sequence");
pushAction(vm, new Sequence(actions));
return 1;
}
static SQInteger sqSpawn(HSQUIRRELVM vm) {
SQInteger size = sq_getsize(vm, 2);
std::vector<Action *> actions;
actions.reserve(size);
for (SQInteger i = 0; i < size; ++i) {
sq_pushinteger(vm, i);
sq_get(vm, 2);
auto a = getAction(vm, -1);
if (a)
actions.push_back(a->clone());
sq_pop(vm, 1);
}
if (actions.empty())
return sq_throwerror(vm, "empty spawn");
pushAction(vm, new Spawn(actions));
return 1;
}
static SQInteger sqLoop(HSQUIRRELVM vm) {
auto a = getAction(vm, 2);
if (!a)
return sq_throwerror(vm, "null action");
int times = -1;
if (sq_gettop(vm) >= 3)
times = static_cast<int>(getInt(vm, 3));
pushAction(vm, new Loop(a->clone(), times));
return 1;
}
static SQInteger sqCallFunc(HSQUIRRELVM vm) {
// arg 2 is a Squirrel closure
HSQOBJECT closure;
sq_resetobject(&closure);
sq_getstackobj(vm, 2, &closure);
sq_addref(vm, &closure);
HSQUIRRELVM capturedVM = vm;
HSQOBJECT capturedClosure = closure;
pushAction(vm, new CallFunc([capturedVM, capturedClosure]() mutable {
sq_pushobject(capturedVM, capturedClosure);
sq_pushroottable(capturedVM);
sq_call(capturedVM, 1, SQFalse, SQTrue);
sq_pop(capturedVM, 1); // pop closure
}));
return 1;
}
void registerActionBindings(HSQUIRRELVM vm) {
// Base Action class (used as container)
ClassDef(vm, "Action").setTypeTag(typeTag<Action>()).commit();
// Register global factory functions
auto regFunc = [&](const char *name, SQFUNCTION fn) {
sq_pushroottable(vm);
sq_pushstring(vm, name, -1);
sq_newclosure(vm, fn, 0);
sq_newslot(vm, -3, SQFalse);
sq_pop(vm, 1);
};
regFunc("MoveTo", sqMoveTo);
regFunc("MoveBy", sqMoveBy);
regFunc("ScaleTo", sqScaleTo);
regFunc("RotateTo", sqRotateTo);
regFunc("RotateBy", sqRotateBy);
regFunc("FadeIn", sqFadeIn);
regFunc("FadeOut", sqFadeOut);
regFunc("FadeTo", sqFadeTo);
regFunc("Delay", sqDelay);
regFunc("Sequence", sqSequence);
regFunc("Spawn", sqSpawn);
regFunc("Loop", sqLoop);
regFunc("CallFunc", sqCallFunc);
}
} // namespace sq
} // namespace extra2d

View File

@ -1,277 +0,0 @@
#include <extra2d/animation/animated_sprite.h>
#include <extra2d/animation/animation_cache.h>
#include <extra2d/animation/animation_clip.h>
#include <extra2d/app/application.h>
#include <extra2d/graphics/texture.h>
#include <extra2d/resource/resource_manager.h>
#include <extra2d/script/sq_binding_animation.h>
#include <extra2d/script/sq_binding_node.h>
#include <extra2d/script/sq_binding_types.h>
#include <extra2d/utils/logger.h>
namespace extra2d {
namespace sq {
// SqClassName specialization for AnimatedSprite
template <> struct SqClassName<AnimatedSprite> {
static const char *name() { return "AnimatedSprite"; }
};
// AnimatedSprite inherits Sprite -> Node, all stored as Ptr<Node>
// 从精灵图创建 AnimatedSprite网格布局
// AnimatedSprite.createFromGrid(texturePath, frameWidth, frameHeight,
// frameDurationMs, frameCount)
static SQInteger animSpriteCreateFromGrid(HSQUIRRELVM vm) {
SQInteger argc = sq_gettop(vm);
if (argc < 4) {
return sq_throwerror(vm, "createFromGrid requires at least 3 arguments: "
"texturePath, frameWidth, frameHeight");
}
std::string path = getString(vm, 2);
int frameWidth = static_cast<int>(getInt(vm, 3));
int frameHeight = static_cast<int>(getInt(vm, 4));
float frameDurationMs = 100.0f;
if (argc >= 5) {
frameDurationMs = static_cast<float>(getFloat(vm, 5));
}
int frameCount = -1;
if (argc >= 6) {
frameCount = static_cast<int>(getInt(vm, 6));
}
// 加载纹理
auto &resources = Application::instance().resources();
auto texture = resources.loadTexture(path);
if (!texture) {
E2D_ERROR("Failed to load texture: {}", path);
pushNull(vm);
return 1;
}
// 创建动画片段
auto clip = AnimationClip::createFromGrid(texture, frameWidth, frameHeight,
frameDurationMs, frameCount);
if (!clip || clip->empty()) {
E2D_ERROR("Failed to create animation clip from grid");
pushNull(vm);
return 1;
}
clip->setLooping(true);
// 创建 AnimatedSprite
auto sprite = AnimatedSprite::create(clip);
sprite->setApplyFrameTransform(false);
pushPtr<AnimatedSprite>(vm, sprite);
return 1;
}
static SQInteger animSpriteCreate(HSQUIRRELVM vm) {
SQInteger argc = sq_gettop(vm);
Ptr<AnimatedSprite> sprite;
if (argc >= 2 && sq_gettype(vm, 2) == OT_STRING) {
std::string path = getString(vm, 2);
sprite = AnimatedSprite::create(path);
} else {
sprite = AnimatedSprite::create();
}
pushPtr<AnimatedSprite>(vm, sprite);
return 1;
}
static SQInteger animSpritePlay(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *anim = dynamic_cast<AnimatedSprite *>(node.get());
if (!anim)
return sq_throwerror(vm, "not an AnimatedSprite");
SQInteger argc = sq_gettop(vm);
if (argc >= 2 && sq_gettype(vm, 2) == OT_STRING) {
std::string name = getString(vm, 2);
bool loop = true;
if (argc >= 3)
loop = getBool(vm, 3);
anim->play(name, loop);
} else {
anim->play();
}
return 0;
}
static SQInteger animSpritePause(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *anim = dynamic_cast<AnimatedSprite *>(node.get());
if (!anim)
return sq_throwerror(vm, "not an AnimatedSprite");
anim->pause();
return 0;
}
static SQInteger animSpriteResume(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *anim = dynamic_cast<AnimatedSprite *>(node.get());
if (!anim)
return sq_throwerror(vm, "not an AnimatedSprite");
anim->resume();
return 0;
}
static SQInteger animSpriteStop(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *anim = dynamic_cast<AnimatedSprite *>(node.get());
if (!anim)
return sq_throwerror(vm, "not an AnimatedSprite");
anim->stop();
return 0;
}
static SQInteger animSpriteReset(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *anim = dynamic_cast<AnimatedSprite *>(node.get());
if (!anim)
return sq_throwerror(vm, "not an AnimatedSprite");
anim->reset();
return 0;
}
static SQInteger animSpriteIsPlaying(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *anim = dynamic_cast<AnimatedSprite *>(node.get());
if (!anim)
return sq_throwerror(vm, "not an AnimatedSprite");
push(vm, anim->isPlaying());
return 1;
}
static SQInteger animSpriteSetLooping(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *anim = dynamic_cast<AnimatedSprite *>(node.get());
if (!anim)
return sq_throwerror(vm, "not an AnimatedSprite");
anim->setLooping(getBool(vm, 2));
return 0;
}
static SQInteger animSpriteSetPlaybackSpeed(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *anim = dynamic_cast<AnimatedSprite *>(node.get());
if (!anim)
return sq_throwerror(vm, "not an AnimatedSprite");
anim->setPlaybackSpeed(static_cast<float>(getFloat(vm, 2)));
return 0;
}
static SQInteger animSpriteAddAnimation(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *anim = dynamic_cast<AnimatedSprite *>(node.get());
if (!anim)
return sq_throwerror(vm, "not an AnimatedSprite");
std::string name = getString(vm, 2);
std::string path = getString(vm, 3);
auto clip = AnimationCache::getInstance().loadClip(path);
if (clip)
anim->addAnimation(name, clip);
return 0;
}
static SQInteger animSpriteLoadAnimation(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *anim = dynamic_cast<AnimatedSprite *>(node.get());
if (!anim)
return sq_throwerror(vm, "not an AnimatedSprite");
std::string path = getString(vm, 2);
anim->loadAnimation(path);
return 0;
}
static SQInteger animSpriteSetAutoPlay(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *anim = dynamic_cast<AnimatedSprite *>(node.get());
if (!anim)
return sq_throwerror(vm, "not an AnimatedSprite");
anim->setAutoPlay(getBool(vm, 2));
return 0;
}
static SQInteger animSpriteGetCurrentFrameIndex(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *anim = dynamic_cast<AnimatedSprite *>(node.get());
if (!anim)
return sq_throwerror(vm, "not an AnimatedSprite");
push(vm, static_cast<int>(anim->getCurrentFrameIndex()));
return 1;
}
static SQInteger animSpriteGetTotalFrames(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *anim = dynamic_cast<AnimatedSprite *>(node.get());
if (!anim)
return sq_throwerror(vm, "not an AnimatedSprite");
push(vm, static_cast<int>(anim->getTotalFrames()));
return 1;
}
static SQInteger animSpriteSetFrameRange(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *anim = dynamic_cast<AnimatedSprite *>(node.get());
if (!anim)
return sq_throwerror(vm, "not an AnimatedSprite");
int start = static_cast<int>(getInt(vm, 2));
int end = -1;
if (sq_gettop(vm) >= 3) {
end = static_cast<int>(getInt(vm, 3));
}
anim->setFrameRange(start, end);
return 0;
}
static SQInteger animSpriteSetFrameIndex(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *anim = dynamic_cast<AnimatedSprite *>(node.get());
if (!anim)
return sq_throwerror(vm, "not an AnimatedSprite");
int index = static_cast<int>(getInt(vm, 2));
anim->setFrameIndex(static_cast<size_t>(index));
return 0;
}
static SQInteger animSpriteSetApplyFrameTransform(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *anim = dynamic_cast<AnimatedSprite *>(node.get());
if (!anim)
return sq_throwerror(vm, "not an AnimatedSprite");
anim->setApplyFrameTransform(getBool(vm, 2));
return 0;
}
void registerAnimationBindings(HSQUIRRELVM vm) {
// Register SqClassName for AnimatedSprite
ClassDef(vm, "AnimatedSprite", "Sprite")
.setTypeTag(typeTag<AnimatedSprite>())
.staticMethod("create", animSpriteCreate)
.staticMethod("createFromGrid", animSpriteCreateFromGrid)
.method("play", animSpritePlay)
.method("pause", animSpritePause)
.method("resume", animSpriteResume)
.method("stop", animSpriteStop)
.method("reset", animSpriteReset)
.method("isPlaying", animSpriteIsPlaying)
.method("setLooping", animSpriteSetLooping)
.method("setPlaybackSpeed", animSpriteSetPlaybackSpeed)
.method("addAnimation", animSpriteAddAnimation)
.method("loadAnimation", animSpriteLoadAnimation)
.method("setAutoPlay", animSpriteSetAutoPlay)
.method("getCurrentFrameIndex", animSpriteGetCurrentFrameIndex)
.method("getTotalFrames", animSpriteGetTotalFrames)
.method("setFrameRange", animSpriteSetFrameRange)
.method("setFrameIndex", animSpriteSetFrameIndex)
.method("setApplyFrameTransform", animSpriteSetApplyFrameTransform)
.commit();
}
} // namespace sq
} // namespace extra2d

View File

@ -1,91 +0,0 @@
#include <extra2d/audio/audio_engine.h>
#include <extra2d/audio/sound.h>
#include <extra2d/script/sq_binding_audio.h>
#include <extra2d/script/sq_binding_types.h>
namespace extra2d {
namespace sq {
// ============================================================================
// AudioEngine singleton
// ============================================================================
static SQInteger audioLoadSound(HSQUIRRELVM vm) {
SQInteger argc = sq_gettop(vm);
std::shared_ptr<Sound> snd;
if (argc >= 3) {
std::string name = getString(vm, 2);
std::string path = getString(vm, 3);
snd = AudioEngine::getInstance().loadSound(name, path);
} else {
std::string path = getString(vm, 2);
snd = AudioEngine::getInstance().loadSound(path);
}
push(vm, snd != nullptr);
return 1;
}
static SQInteger audioPlaySound(HSQUIRRELVM vm) {
std::string name = getString(vm, 2);
auto snd = AudioEngine::getInstance().getSound(name);
if (snd)
snd->play();
return 0;
}
static SQInteger audioStopSound(HSQUIRRELVM vm) {
std::string name = getString(vm, 2);
auto snd = AudioEngine::getInstance().getSound(name);
if (snd)
snd->stop();
return 0;
}
static SQInteger audioSetMasterVolume(HSQUIRRELVM vm) {
float vol = static_cast<float>(getFloat(vm, 2));
AudioEngine::getInstance().setMasterVolume(vol);
return 0;
}
static SQInteger audioGetMasterVolume(HSQUIRRELVM vm) {
push(vm, AudioEngine::getInstance().getMasterVolume());
return 1;
}
static SQInteger audioStopAll(HSQUIRRELVM vm) {
AudioEngine::getInstance().stopAll();
return 0;
}
static SQInteger audioPauseAll(HSQUIRRELVM vm) {
AudioEngine::getInstance().pauseAll();
return 0;
}
static SQInteger audioResumeAll(HSQUIRRELVM vm) {
AudioEngine::getInstance().resumeAll();
return 0;
}
void registerAudioBindings(HSQUIRRELVM vm) {
ClassDef(vm, "AudioEngineClass")
.method("loadSound", audioLoadSound)
.method("playSound", audioPlaySound)
.method("stopSound", audioStopSound)
.method("setMasterVolume", audioSetMasterVolume)
.method("getMasterVolume", audioGetMasterVolume)
.method("stopAll", audioStopAll)
.method("pauseAll", audioPauseAll)
.method("resumeAll", audioResumeAll)
.commit();
// Global "Audio" instance
pushSingleton(vm, &AudioEngine::getInstance(), "AudioEngineClass");
sq_pushroottable(vm);
sq_pushstring(vm, "Audio", -1);
sq_push(vm, -3);
sq_newslot(vm, -3, SQFalse);
sq_pop(vm, 2);
}
} // namespace sq
} // namespace extra2d

View File

@ -1,288 +0,0 @@
#include <extra2d/app/application.h>
#include <extra2d/event/input_codes.h>
#include <extra2d/platform/input.h>
#include <extra2d/script/sq_binding_input.h>
#include <extra2d/script/sq_binding_types.h>
namespace extra2d {
namespace sq {
// ============================================================================
// Input singleton bindings
// ============================================================================
static SQInteger inputIsKeyDown(HSQUIRRELVM vm) {
int key = static_cast<int>(getInt(vm, 2));
push(vm, Application::instance().input().isKeyDown(key));
return 1;
}
static SQInteger inputIsKeyPressed(HSQUIRRELVM vm) {
int key = static_cast<int>(getInt(vm, 2));
push(vm, Application::instance().input().isKeyPressed(key));
return 1;
}
static SQInteger inputIsKeyReleased(HSQUIRRELVM vm) {
int key = static_cast<int>(getInt(vm, 2));
push(vm, Application::instance().input().isKeyReleased(key));
return 1;
}
static SQInteger inputIsMouseDown(HSQUIRRELVM vm) {
int btn = static_cast<int>(getInt(vm, 2));
push(vm, Application::instance().input().isMouseDown(
static_cast<MouseButton>(btn)));
return 1;
}
static SQInteger inputIsMousePressed(HSQUIRRELVM vm) {
int btn = static_cast<int>(getInt(vm, 2));
push(vm, Application::instance().input().isMousePressed(
static_cast<MouseButton>(btn)));
return 1;
}
static SQInteger inputIsMouseReleased(HSQUIRRELVM vm) {
int btn = static_cast<int>(getInt(vm, 2));
push(vm, Application::instance().input().isMouseReleased(
static_cast<MouseButton>(btn)));
return 1;
}
static SQInteger inputGetMousePosition(HSQUIRRELVM vm) {
pushValueInstance(vm, Application::instance().input().getMousePosition());
return 1;
}
static SQInteger inputGetMouseDelta(HSQUIRRELVM vm) {
pushValueInstance(vm, Application::instance().input().getMouseDelta());
return 1;
}
static SQInteger inputGetMouseScroll(HSQUIRRELVM vm) {
push(vm, Application::instance().input().getMouseScroll());
return 1;
}
void registerInput(HSQUIRRELVM vm) {
ClassDef(vm, "InputClass")
.method("isKeyDown", inputIsKeyDown)
.method("isKeyPressed", inputIsKeyPressed)
.method("isKeyReleased", inputIsKeyReleased)
.method("isMouseDown", inputIsMouseDown)
.method("isMousePressed", inputIsMousePressed)
.method("isMouseReleased", inputIsMouseReleased)
.method("getMousePosition", inputGetMousePosition)
.method("getMouseDelta", inputGetMouseDelta)
.method("getMouseScroll", inputGetMouseScroll)
.commit();
// Global "Input" instance
pushSingleton(vm, &Application::instance().input(), "InputClass");
sq_pushroottable(vm);
sq_pushstring(vm, "Input", -1);
sq_push(vm, -3);
sq_newslot(vm, -3, SQFalse);
sq_pop(vm, 2);
}
void registerKeyConstants(HSQUIRRELVM vm) {
// Key constants table
static const char *const keyNames[] = {"Space",
"Apostrophe",
"Comma",
"Minus",
"Period",
"Slash",
"Num0",
"Num1",
"Num2",
"Num3",
"Num4",
"Num5",
"Num6",
"Num7",
"Num8",
"Num9",
"Semicolon",
"Equal",
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
"LeftBracket",
"Backslash",
"RightBracket",
"GraveAccent",
"Escape",
"Enter",
"Tab",
"Backspace",
"Insert",
"Delete",
"Right",
"Left",
"Down",
"Up",
"PageUp",
"PageDown",
"Home",
"End",
"CapsLock",
"ScrollLock",
"NumLock",
"PrintScreen",
"Pause",
"F1",
"F2",
"F3",
"F4",
"F5",
"F6",
"F7",
"F8",
"F9",
"F10",
"F11",
"F12",
"LeftShift",
"LeftControl",
"LeftAlt",
"LeftSuper",
"RightShift",
"RightControl",
"RightAlt",
"RightSuper",
"Menu"};
static const SQInteger keyValues[] = {Key::Space,
Key::Apostrophe,
Key::Comma,
Key::Minus,
Key::Period,
Key::Slash,
Key::Num0,
Key::Num1,
Key::Num2,
Key::Num3,
Key::Num4,
Key::Num5,
Key::Num6,
Key::Num7,
Key::Num8,
Key::Num9,
Key::Semicolon,
Key::Equal,
Key::A,
Key::B,
Key::C,
Key::D,
Key::E,
Key::F,
Key::G,
Key::H,
Key::I,
Key::J,
Key::K,
Key::L,
Key::M,
Key::N,
Key::O,
Key::P,
Key::Q,
Key::R,
Key::S,
Key::T,
Key::U,
Key::V,
Key::W,
Key::X,
Key::Y,
Key::Z,
Key::LeftBracket,
Key::Backslash,
Key::RightBracket,
Key::GraveAccent,
Key::Escape,
Key::Enter,
Key::Tab,
Key::Backspace,
Key::Insert,
Key::Delete,
Key::Right,
Key::Left,
Key::Down,
Key::Up,
Key::PageUp,
Key::PageDown,
Key::Home,
Key::End,
Key::CapsLock,
Key::ScrollLock,
Key::NumLock,
Key::PrintScreen,
Key::Pause,
Key::F1,
Key::F2,
Key::F3,
Key::F4,
Key::F5,
Key::F6,
Key::F7,
Key::F8,
Key::F9,
Key::F10,
Key::F11,
Key::F12,
Key::LeftShift,
Key::LeftControl,
Key::LeftAlt,
Key::LeftSuper,
Key::RightShift,
Key::RightControl,
Key::RightAlt,
Key::RightSuper,
Key::Menu};
registerConstTable(vm, "Key", keyNames, keyValues,
static_cast<int>(sizeof(keyNames) / sizeof(keyNames[0])));
// Mouse button constants
static const char *const mouseNames[] = {"Left", "Right", "Middle", "Button4",
"Button5"};
static const SQInteger mouseValues[] = {Mouse::ButtonLeft, Mouse::ButtonRight,
Mouse::ButtonMiddle, Mouse::Button4,
Mouse::Button5};
registerConstTable(
vm, "Mouse", mouseNames, mouseValues,
static_cast<int>(sizeof(mouseNames) / sizeof(mouseNames[0])));
}
void registerInputBindings(HSQUIRRELVM vm) {
registerInput(vm);
registerKeyConstants(vm);
}
} // namespace sq
} // namespace extra2d

View File

@ -1,561 +0,0 @@
#include <extra2d/app/application.h>
#include <extra2d/graphics/texture.h>
#include <extra2d/resource/resource_manager.h>
#include <extra2d/scene/node.h>
#include <extra2d/scene/scene.h>
#include <extra2d/scene/scene_manager.h>
#include <extra2d/scene/sprite.h>
#include <extra2d/script/sq_binding_node.h>
#include <extra2d/script/sq_binding_types.h>
namespace extra2d {
namespace sq {
// SqClassName specializations
template <> struct SqClassName<Node> {
static const char *name() { return "Node"; }
};
template <> struct SqClassName<Sprite> {
static const char *name() { return "Sprite"; }
};
template <> struct SqClassName<Scene> {
static const char *name() { return "Scene"; }
};
// ============================================================================
// Node
// ============================================================================
static SQInteger nodeCreate(HSQUIRRELVM vm) {
auto node = makePtr<Node>();
pushPtr(vm, node);
return 1;
}
static SQInteger nodeSetPosition(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
SQInteger argc = sq_gettop(vm);
if (argc >= 3) {
float x = static_cast<float>(getFloat(vm, 2));
float y = static_cast<float>(getFloat(vm, 3));
node->setPosition(x, y);
} else {
Vec2 *v = getValueInstance<Vec2>(vm, 2);
if (v)
node->setPosition(*v);
}
return 0;
}
static SQInteger nodeGetPosition(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
pushValueInstance(vm, node->getPosition());
return 1;
}
static SQInteger nodeSetRotation(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
node->setRotation(static_cast<float>(getFloat(vm, 2)));
return 0;
}
static SQInteger nodeGetRotation(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
push(vm, node->getRotation());
return 1;
}
static SQInteger nodeSetScale(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
SQInteger argc = sq_gettop(vm);
if (argc >= 3) {
node->setScale(static_cast<float>(getFloat(vm, 2)),
static_cast<float>(getFloat(vm, 3)));
} else {
// Could be float or Vec2
SQObjectType t = sq_gettype(vm, 2);
if (t == OT_INSTANCE) {
Vec2 *v = getValueInstance<Vec2>(vm, 2);
if (v)
node->setScale(*v);
} else {
node->setScale(static_cast<float>(getFloat(vm, 2)));
}
}
return 0;
}
static SQInteger nodeGetScale(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
pushValueInstance(vm, node->getScale());
return 1;
}
static SQInteger nodeSetAnchor(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
SQInteger argc = sq_gettop(vm);
if (argc >= 3) {
node->setAnchor(static_cast<float>(getFloat(vm, 2)),
static_cast<float>(getFloat(vm, 3)));
} else {
Vec2 *v = getValueInstance<Vec2>(vm, 2);
if (v)
node->setAnchor(*v);
}
return 0;
}
static SQInteger nodeGetAnchor(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
pushValueInstance(vm, node->getAnchor());
return 1;
}
static SQInteger nodeSetOpacity(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
node->setOpacity(static_cast<float>(getFloat(vm, 2)));
return 0;
}
static SQInteger nodeGetOpacity(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
push(vm, node->getOpacity());
return 1;
}
static SQInteger nodeSetVisible(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
node->setVisible(getBool(vm, 2));
return 0;
}
static SQInteger nodeIsVisible(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
push(vm, node->isVisible());
return 1;
}
static SQInteger nodeSetZOrder(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
node->setZOrder(static_cast<int>(getInt(vm, 2)));
return 0;
}
static SQInteger nodeGetZOrder(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
push(vm, node->getZOrder());
return 1;
}
static SQInteger nodeSetName(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
node->setName(getString(vm, 2));
return 0;
}
static SQInteger nodeGetName(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
push(vm, node->getName());
return 1;
}
static SQInteger nodeSetTag(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
node->setTag(static_cast<int>(getInt(vm, 2)));
return 0;
}
static SQInteger nodeGetTag(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
push(vm, node->getTag());
return 1;
}
static SQInteger nodeAddChild(HSQUIRRELVM vm) {
auto parent = getPtr<Node>(vm, 1);
auto child = getPtr<Node>(vm, 2);
if (!parent || !child)
return sq_throwerror(vm, "null node");
parent->addChild(child);
return 0;
}
static SQInteger nodeRemoveFromParent(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
node->removeFromParent();
return 0;
}
static SQInteger nodeRemoveAllChildren(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
node->removeAllChildren();
return 0;
}
static SQInteger nodeGetChildByName(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
auto child = node->getChildByName(getString(vm, 2));
if (child)
pushPtr(vm, child);
else
pushNull(vm);
return 1;
}
static SQInteger nodeRunAction(HSQUIRRELVM vm) {
// Will be implemented in action bindings
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
// Get action pointer from userdata
SQUserPointer up = nullptr;
sq_getinstanceup(vm, 2, &up, nullptr, SQFalse);
if (!up)
return sq_throwerror(vm, "null action");
auto actionPtr = *static_cast<Ptr<Action> *>(up);
node->runAction(actionPtr);
return 0;
}
static SQInteger nodeStopAllActions(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
node->stopAllActions();
return 0;
}
static SQInteger nodeGetBoundingBox(HSQUIRRELVM vm) {
auto *node = getRawPtr<Node>(vm, 1);
if (!node)
return sq_throwerror(vm, "null node");
pushValueInstance(vm, node->getBoundingBox());
return 1;
}
void registerNode(HSQUIRRELVM vm) {
// Node class — uses shared_ptr bridge
ClassDef(vm, "Node")
.setTypeTag(typeTag<Node>())
.staticMethod("create", nodeCreate)
.method("setPosition", nodeSetPosition)
.method("getPosition", nodeGetPosition)
.method("setRotation", nodeSetRotation)
.method("getRotation", nodeGetRotation)
.method("setScale", nodeSetScale)
.method("getScale", nodeGetScale)
.method("setAnchor", nodeSetAnchor)
.method("getAnchor", nodeGetAnchor)
.method("setOpacity", nodeSetOpacity)
.method("getOpacity", nodeGetOpacity)
.method("setVisible", nodeSetVisible)
.method("isVisible", nodeIsVisible)
.method("setZOrder", nodeSetZOrder)
.method("getZOrder", nodeGetZOrder)
.method("setName", nodeSetName)
.method("getName", nodeGetName)
.method("setTag", nodeSetTag)
.method("getTag", nodeGetTag)
.method("addChild", nodeAddChild)
.method("removeFromParent", nodeRemoveFromParent)
.method("removeAllChildren", nodeRemoveAllChildren)
.method("getChildByName", nodeGetChildByName)
.method("runAction", nodeRunAction)
.method("stopAllActions", nodeStopAllActions)
.method("getBoundingBox", nodeGetBoundingBox)
.commit();
}
// ============================================================================
// Sprite
// ============================================================================
static SQInteger spriteCreate(HSQUIRRELVM vm) {
SQInteger argc = sq_gettop(vm);
Ptr<Sprite> sprite;
if (argc >= 2 && sq_gettype(vm, 2) == OT_STRING) {
std::string path = getString(vm, 2);
auto tex = Application::instance().resources().loadTexture(path);
if (tex)
sprite = Sprite::create(tex);
else
sprite = Sprite::create();
} else {
sprite = Sprite::create();
}
pushPtr<Node>(vm, sprite);
return 1;
}
static SQInteger spriteSetColor(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *sprite = dynamic_cast<Sprite *>(node.get());
if (!sprite)
return sq_throwerror(vm, "not a sprite");
Color *c = getValueInstance<Color>(vm, 2);
if (c)
sprite->setColor(*c);
return 0;
}
static SQInteger spriteGetColor(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *sprite = dynamic_cast<Sprite *>(node.get());
if (!sprite)
return sq_throwerror(vm, "not a sprite");
pushValueInstance(vm, sprite->getColor());
return 1;
}
static SQInteger spriteSetFlipX(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *sprite = dynamic_cast<Sprite *>(node.get());
if (!sprite)
return sq_throwerror(vm, "not a sprite");
sprite->setFlipX(getBool(vm, 2));
return 0;
}
static SQInteger spriteSetFlipY(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *sprite = dynamic_cast<Sprite *>(node.get());
if (!sprite)
return sq_throwerror(vm, "not a sprite");
sprite->setFlipY(getBool(vm, 2));
return 0;
}
static SQInteger spriteIsFlipX(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *sprite = dynamic_cast<Sprite *>(node.get());
if (!sprite)
return sq_throwerror(vm, "not a sprite");
push(vm, sprite->isFlipX());
return 1;
}
static SQInteger spriteIsFlipY(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *sprite = dynamic_cast<Sprite *>(node.get());
if (!sprite)
return sq_throwerror(vm, "not a sprite");
push(vm, sprite->isFlipY());
return 1;
}
void registerSprite(HSQUIRRELVM vm) {
ClassDef(vm, "Sprite", "Node")
.setTypeTag(typeTag<Sprite>())
.staticMethod("create", spriteCreate)
.method("setColor", spriteSetColor)
.method("getColor", spriteGetColor)
.method("setFlipX", spriteSetFlipX)
.method("setFlipY", spriteSetFlipY)
.method("isFlipX", spriteIsFlipX)
.method("isFlipY", spriteIsFlipY)
.commit();
}
// ============================================================================
// Scene
// ============================================================================
static SQInteger sceneCreate(HSQUIRRELVM vm) {
auto scene = Scene::create();
pushPtr<Node>(vm, scene);
return 1;
}
static SQInteger sceneSetBackgroundColor(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *scene = dynamic_cast<Scene *>(node.get());
if (!scene)
return sq_throwerror(vm, "not a scene");
Color *c = getValueInstance<Color>(vm, 2);
if (c)
scene->setBackgroundColor(*c);
return 0;
}
static SQInteger sceneGetBackgroundColor(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 1);
auto *scene = dynamic_cast<Scene *>(node.get());
if (!scene)
return sq_throwerror(vm, "not a scene");
pushValueInstance(vm, scene->getBackgroundColor());
return 1;
}
void registerScene(HSQUIRRELVM vm) {
ClassDef(vm, "Scene", "Node")
.setTypeTag(typeTag<Scene>())
.staticMethod("create", sceneCreate)
.method("setBackgroundColor", sceneSetBackgroundColor)
.method("getBackgroundColor", sceneGetBackgroundColor)
.commit();
}
// ============================================================================
// SceneManager (global "Scenes" variable)
// ============================================================================
static SQInteger smRunWithScene(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 2);
auto scene = std::dynamic_pointer_cast<Scene>(node);
if (!scene)
return sq_throwerror(vm, "expected Scene");
SceneManager::getInstance().runWithScene(scene);
return 0;
}
static SQInteger smReplaceScene(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 2);
auto scene = std::dynamic_pointer_cast<Scene>(node);
if (!scene)
return sq_throwerror(vm, "expected Scene");
SceneManager::getInstance().replaceScene(scene);
return 0;
}
static SQInteger smPushScene(HSQUIRRELVM vm) {
auto node = getPtr<Node>(vm, 2);
auto scene = std::dynamic_pointer_cast<Scene>(node);
if (!scene)
return sq_throwerror(vm, "expected Scene");
SceneManager::getInstance().pushScene(scene);
return 0;
}
static SQInteger smPopScene(HSQUIRRELVM vm) {
SceneManager::getInstance().popScene();
return 0;
}
static SQInteger smGetCurrentScene(HSQUIRRELVM vm) {
auto scene = SceneManager::getInstance().getCurrentScene();
if (scene)
pushPtr<Node>(vm, scene);
else
pushNull(vm);
return 1;
}
void registerSceneManager(HSQUIRRELVM vm) {
ClassDef(vm, "SceneManagerClass")
.method("runWithScene", smRunWithScene)
.method("replaceScene", smReplaceScene)
.method("pushScene", smPushScene)
.method("popScene", smPopScene)
.method("getCurrentScene", smGetCurrentScene)
.commit();
// Create global "Scenes" instance
pushSingleton(vm, &SceneManager::getInstance(), "SceneManagerClass");
sq_pushroottable(vm);
sq_pushstring(vm, "Scenes", -1);
sq_push(vm, -3); // push the instance
sq_newslot(vm, -3, SQFalse);
sq_pop(vm, 2); // pop root table and instance
}
// ============================================================================
// Application (global "App" variable)
// ============================================================================
static SQInteger appQuit(HSQUIRRELVM vm) {
Application::instance().quit();
return 0;
}
static SQInteger appGetDeltaTime(HSQUIRRELVM vm) {
push(vm, Application::instance().deltaTime());
return 1;
}
static SQInteger appGetTotalTime(HSQUIRRELVM vm) {
push(vm, Application::instance().totalTime());
return 1;
}
static SQInteger appGetFps(HSQUIRRELVM vm) {
push(vm, Application::instance().fps());
return 1;
}
static SQInteger appIsPaused(HSQUIRRELVM vm) {
push(vm, Application::instance().isPaused());
return 1;
}
void registerApplication(HSQUIRRELVM vm) {
ClassDef(vm, "ApplicationClass")
.method("quit", appQuit)
.method("getDeltaTime", appGetDeltaTime)
.method("getTotalTime", appGetTotalTime)
.method("getFps", appGetFps)
.method("isPaused", appIsPaused)
.commit();
// Global "App" instance
pushSingleton(vm, &Application::instance(), "ApplicationClass");
sq_pushroottable(vm);
sq_pushstring(vm, "App", -1);
sq_push(vm, -3);
sq_newslot(vm, -3, SQFalse);
sq_pop(vm, 2);
}
// ============================================================================
// Register all
// ============================================================================
void registerNodeBindings(HSQUIRRELVM vm) {
registerNode(vm);
registerSprite(vm);
registerScene(vm);
registerSceneManager(vm);
registerApplication(vm);
}
} // namespace sq
} // namespace extra2d

View File

@ -1,398 +0,0 @@
#include <extra2d/script/sq_binding_types.h>
namespace extra2d {
namespace sq {
// ============================================================================
// Vec2
// ============================================================================
static SQInteger vec2Constructor(HSQUIRRELVM vm) {
Vec2 *self = nullptr;
sq_getinstanceup(vm, 1, reinterpret_cast<SQUserPointer *>(&self), nullptr,
SQFalse);
SQInteger argc = sq_gettop(vm);
if (argc >= 3) {
self->x = static_cast<float>(getFloat(vm, 2));
self->y = static_cast<float>(getFloat(vm, 3));
} else {
self->x = 0.0f;
self->y = 0.0f;
}
return 0;
}
static SQInteger vec2GetX(HSQUIRRELVM vm) {
Vec2 *v = getValueInstance<Vec2>(vm, 1);
sq_pushfloat(vm, v->x);
return 1;
}
static SQInteger vec2SetX(HSQUIRRELVM vm) {
Vec2 *v = getValueInstance<Vec2>(vm, 1);
v->x = static_cast<float>(getFloat(vm, 2));
return 0;
}
static SQInteger vec2GetY(HSQUIRRELVM vm) {
Vec2 *v = getValueInstance<Vec2>(vm, 1);
sq_pushfloat(vm, v->y);
return 1;
}
static SQInteger vec2SetY(HSQUIRRELVM vm) {
Vec2 *v = getValueInstance<Vec2>(vm, 1);
v->y = static_cast<float>(getFloat(vm, 2));
return 0;
}
static SQInteger vec2Length(HSQUIRRELVM vm) {
Vec2 *v = getValueInstance<Vec2>(vm, 1);
sq_pushfloat(vm, v->length());
return 1;
}
static SQInteger vec2Normalized(HSQUIRRELVM vm) {
Vec2 *v = getValueInstance<Vec2>(vm, 1);
pushValueInstance(vm, v->normalized());
return 1;
}
static SQInteger vec2Dot(HSQUIRRELVM vm) {
Vec2 *a = getValueInstance<Vec2>(vm, 1);
Vec2 *b = getValueInstance<Vec2>(vm, 2);
sq_pushfloat(vm, a->dot(*b));
return 1;
}
static SQInteger vec2Distance(HSQUIRRELVM vm) {
Vec2 *a = getValueInstance<Vec2>(vm, 1);
Vec2 *b = getValueInstance<Vec2>(vm, 2);
sq_pushfloat(vm, a->distance(*b));
return 1;
}
static SQInteger vec2Add(HSQUIRRELVM vm) {
Vec2 *a = getValueInstance<Vec2>(vm, 1);
Vec2 *b = getValueInstance<Vec2>(vm, 2);
pushValueInstance(vm, *a + *b);
return 1;
}
static SQInteger vec2Sub(HSQUIRRELVM vm) {
Vec2 *a = getValueInstance<Vec2>(vm, 1);
Vec2 *b = getValueInstance<Vec2>(vm, 2);
pushValueInstance(vm, *a - *b);
return 1;
}
static SQInteger vec2Mul(HSQUIRRELVM vm) {
Vec2 *a = getValueInstance<Vec2>(vm, 1);
SQFloat s = getFloat(vm, 2);
pushValueInstance(vm, *a * static_cast<float>(s));
return 1;
}
static SQInteger vec2Div(HSQUIRRELVM vm) {
Vec2 *a = getValueInstance<Vec2>(vm, 1);
SQFloat s = getFloat(vm, 2);
if (s == 0.0f)
return sq_throwerror(vm, "division by zero");
pushValueInstance(vm, *a / static_cast<float>(s));
return 1;
}
static SQInteger vec2Neg(HSQUIRRELVM vm) {
Vec2 *a = getValueInstance<Vec2>(vm, 1);
pushValueInstance(vm, -*a);
return 1;
}
static SQInteger vec2ToString(HSQUIRRELVM vm) {
Vec2 *v = getValueInstance<Vec2>(vm, 1);
char buf[64];
snprintf(buf, sizeof(buf), "Vec2(%.3f, %.3f)", v->x, v->y);
sq_pushstring(vm, buf, -1);
return 1;
}
void registerVec2(HSQUIRRELVM vm) {
ClassDef(vm, "Vec2")
.setValueType<Vec2>(vec2Constructor)
.method("getX", vec2GetX)
.method("setX", vec2SetX)
.method("getY", vec2GetY)
.method("setY", vec2SetY)
.method("length", vec2Length)
.method("normalized", vec2Normalized)
.method("dot", vec2Dot, 2, "xx")
.method("distance", vec2Distance, 2, "xx")
.method("_add", vec2Add, 2, "xx")
.method("_sub", vec2Sub, 2, "xx")
.method("_mul", vec2Mul)
.method("_div", vec2Div)
.method("_unm", vec2Neg)
.method("_tostring", vec2ToString)
.commit();
}
// ============================================================================
// Size
// ============================================================================
static SQInteger sizeConstructor(HSQUIRRELVM vm) {
Size *self = nullptr;
sq_getinstanceup(vm, 1, reinterpret_cast<SQUserPointer *>(&self), nullptr,
SQFalse);
SQInteger argc = sq_gettop(vm);
if (argc >= 3) {
self->width = static_cast<float>(getFloat(vm, 2));
self->height = static_cast<float>(getFloat(vm, 3));
} else {
self->width = 0.0f;
self->height = 0.0f;
}
return 0;
}
static SQInteger sizeGetWidth(HSQUIRRELVM vm) {
Size *s = getValueInstance<Size>(vm, 1);
sq_pushfloat(vm, s->width);
return 1;
}
static SQInteger sizeSetWidth(HSQUIRRELVM vm) {
Size *s = getValueInstance<Size>(vm, 1);
s->width = static_cast<float>(getFloat(vm, 2));
return 0;
}
static SQInteger sizeGetHeight(HSQUIRRELVM vm) {
Size *s = getValueInstance<Size>(vm, 1);
sq_pushfloat(vm, s->height);
return 1;
}
static SQInteger sizeSetHeight(HSQUIRRELVM vm) {
Size *s = getValueInstance<Size>(vm, 1);
s->height = static_cast<float>(getFloat(vm, 2));
return 0;
}
static SQInteger sizeArea(HSQUIRRELVM vm) {
Size *s = getValueInstance<Size>(vm, 1);
sq_pushfloat(vm, s->area());
return 1;
}
static SQInteger sizeToString(HSQUIRRELVM vm) {
Size *s = getValueInstance<Size>(vm, 1);
char buf[64];
snprintf(buf, sizeof(buf), "Size(%.3f, %.3f)", s->width, s->height);
sq_pushstring(vm, buf, -1);
return 1;
}
void registerSize(HSQUIRRELVM vm) {
ClassDef(vm, "Size")
.setValueType<Size>(sizeConstructor)
.method("getWidth", sizeGetWidth)
.method("setWidth", sizeSetWidth)
.method("getHeight", sizeGetHeight)
.method("setHeight", sizeSetHeight)
.method("area", sizeArea)
.method("_tostring", sizeToString)
.commit();
}
// ============================================================================
// Rect
// ============================================================================
static SQInteger rectConstructor(HSQUIRRELVM vm) {
Rect *self = nullptr;
sq_getinstanceup(vm, 1, reinterpret_cast<SQUserPointer *>(&self), nullptr,
SQFalse);
SQInteger argc = sq_gettop(vm);
if (argc >= 5) {
self->origin.x = static_cast<float>(getFloat(vm, 2));
self->origin.y = static_cast<float>(getFloat(vm, 3));
self->size.width = static_cast<float>(getFloat(vm, 4));
self->size.height = static_cast<float>(getFloat(vm, 5));
} else {
*self = Rect();
}
return 0;
}
static SQInteger rectGetX(HSQUIRRELVM vm) {
Rect *r = getValueInstance<Rect>(vm, 1);
sq_pushfloat(vm, r->origin.x);
return 1;
}
static SQInteger rectGetY(HSQUIRRELVM vm) {
Rect *r = getValueInstance<Rect>(vm, 1);
sq_pushfloat(vm, r->origin.y);
return 1;
}
static SQInteger rectGetWidth(HSQUIRRELVM vm) {
Rect *r = getValueInstance<Rect>(vm, 1);
sq_pushfloat(vm, r->size.width);
return 1;
}
static SQInteger rectGetHeight(HSQUIRRELVM vm) {
Rect *r = getValueInstance<Rect>(vm, 1);
sq_pushfloat(vm, r->size.height);
return 1;
}
static SQInteger rectContainsPoint(HSQUIRRELVM vm) {
Rect *r = getValueInstance<Rect>(vm, 1);
Vec2 *p = getValueInstance<Vec2>(vm, 2);
sq_pushbool(vm, r->containsPoint(*p) ? SQTrue : SQFalse);
return 1;
}
static SQInteger rectIntersects(HSQUIRRELVM vm) {
Rect *a = getValueInstance<Rect>(vm, 1);
Rect *b = getValueInstance<Rect>(vm, 2);
sq_pushbool(vm, a->intersects(*b) ? SQTrue : SQFalse);
return 1;
}
static SQInteger rectToString(HSQUIRRELVM vm) {
Rect *r = getValueInstance<Rect>(vm, 1);
char buf[96];
snprintf(buf, sizeof(buf), "Rect(%.1f, %.1f, %.1f, %.1f)", r->origin.x,
r->origin.y, r->size.width, r->size.height);
sq_pushstring(vm, buf, -1);
return 1;
}
void registerRect(HSQUIRRELVM vm) {
ClassDef(vm, "Rect")
.setValueType<Rect>(rectConstructor)
.method("getX", rectGetX)
.method("getY", rectGetY)
.method("getWidth", rectGetWidth)
.method("getHeight", rectGetHeight)
.method("containsPoint", rectContainsPoint, 2, "xx")
.method("intersects", rectIntersects, 2, "xx")
.method("_tostring", rectToString)
.commit();
}
// ============================================================================
// Color
// ============================================================================
static SQInteger colorConstructor(HSQUIRRELVM vm) {
Color *self = nullptr;
sq_getinstanceup(vm, 1, reinterpret_cast<SQUserPointer *>(&self), nullptr,
SQFalse);
SQInteger argc = sq_gettop(vm);
if (argc >= 5) {
self->r = static_cast<float>(getFloat(vm, 2));
self->g = static_cast<float>(getFloat(vm, 3));
self->b = static_cast<float>(getFloat(vm, 4));
self->a = static_cast<float>(getFloat(vm, 5));
} else if (argc >= 4) {
self->r = static_cast<float>(getFloat(vm, 2));
self->g = static_cast<float>(getFloat(vm, 3));
self->b = static_cast<float>(getFloat(vm, 4));
self->a = 1.0f;
} else {
*self = Color(1.0f, 1.0f, 1.0f, 1.0f);
}
return 0;
}
static SQInteger colorGetR(HSQUIRRELVM vm) {
Color *c = getValueInstance<Color>(vm, 1);
sq_pushfloat(vm, c->r);
return 1;
}
static SQInteger colorGetG(HSQUIRRELVM vm) {
Color *c = getValueInstance<Color>(vm, 1);
sq_pushfloat(vm, c->g);
return 1;
}
static SQInteger colorGetB(HSQUIRRELVM vm) {
Color *c = getValueInstance<Color>(vm, 1);
sq_pushfloat(vm, c->b);
return 1;
}
static SQInteger colorGetA(HSQUIRRELVM vm) {
Color *c = getValueInstance<Color>(vm, 1);
sq_pushfloat(vm, c->a);
return 1;
}
static SQInteger colorSetR(HSQUIRRELVM vm) {
Color *c = getValueInstance<Color>(vm, 1);
c->r = static_cast<float>(getFloat(vm, 2));
return 0;
}
static SQInteger colorSetG(HSQUIRRELVM vm) {
Color *c = getValueInstance<Color>(vm, 1);
c->g = static_cast<float>(getFloat(vm, 2));
return 0;
}
static SQInteger colorSetB(HSQUIRRELVM vm) {
Color *c = getValueInstance<Color>(vm, 1);
c->b = static_cast<float>(getFloat(vm, 2));
return 0;
}
static SQInteger colorSetA(HSQUIRRELVM vm) {
Color *c = getValueInstance<Color>(vm, 1);
c->a = static_cast<float>(getFloat(vm, 2));
return 0;
}
static SQInteger colorFromRGBA(HSQUIRRELVM vm) {
auto r = static_cast<uint8_t>(getInt(vm, 2));
auto g = static_cast<uint8_t>(getInt(vm, 3));
auto b = static_cast<uint8_t>(getInt(vm, 4));
uint8_t a = 255;
if (sq_gettop(vm) >= 5)
a = static_cast<uint8_t>(getInt(vm, 5));
pushValueInstance(vm, Color::fromRGBA(r, g, b, a));
return 1;
}
static SQInteger colorToString(HSQUIRRELVM vm) {
Color *c = getValueInstance<Color>(vm, 1);
char buf[80];
snprintf(buf, sizeof(buf), "Color(%.3f, %.3f, %.3f, %.3f)", c->r, c->g, c->b,
c->a);
sq_pushstring(vm, buf, -1);
return 1;
}
void registerColor(HSQUIRRELVM vm) {
ClassDef(vm, "Color")
.setValueType<Color>(colorConstructor)
.method("getR", colorGetR)
.method("setR", colorSetR)
.method("getG", colorGetG)
.method("setG", colorSetG)
.method("getB", colorGetB)
.method("setB", colorSetB)
.method("getA", colorGetA)
.method("setA", colorSetA)
.staticMethod("fromRGBA", colorFromRGBA)
.method("_tostring", colorToString)
.commit();
}
// ============================================================================
// Register all value types
// ============================================================================
void registerValueTypes(HSQUIRRELVM vm) {
registerVec2(vm);
registerSize(vm);
registerRect(vm);
registerColor(vm);
}
} // namespace sq
} // namespace extra2d

View File

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

View File

@ -0,0 +1,206 @@
#include <extra2d/ui/check_box.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/core/string.h>
namespace extra2d {
CheckBox::CheckBox() {
setAnchor(0.0f, 0.0f);
setSize(boxSize_, boxSize_);
}
Ptr<CheckBox> CheckBox::create() {
return makePtr<CheckBox>();
}
Ptr<CheckBox> CheckBox::create(const String &label) {
auto cb = makePtr<CheckBox>();
cb->setLabel(label);
return cb;
}
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
CheckBox *CheckBox::withPosition(float x, float y) {
setPosition(x, y);
return this;
}
CheckBox *CheckBox::withPosition(const Vec2 &pos) {
setPosition(pos);
return this;
}
CheckBox *CheckBox::withAnchor(float x, float y) {
setAnchor(x, y);
return this;
}
CheckBox *CheckBox::withAnchor(const Vec2 &anchor) {
setAnchor(anchor);
return this;
}
CheckBox *CheckBox::withText(const String &text) {
setLabel(text);
return this;
}
CheckBox *CheckBox::withFont(Ptr<FontAtlas> font) {
setFont(font);
return this;
}
CheckBox *CheckBox::withTextColor(const Color &color) {
setTextColor(color);
return this;
}
CheckBox *CheckBox::withSize(float width, float height) {
setSize(width, height);
return this;
}
// ------------------------------------------------------------------------
// 链式调用 - 坐标空间设置
// ------------------------------------------------------------------------
CheckBox *CheckBox::withCoordinateSpace(CoordinateSpace space) {
setCoordinateSpace(space);
return this;
}
CheckBox *CheckBox::withScreenPosition(float x, float y) {
setScreenPosition(x, y);
return this;
}
CheckBox *CheckBox::withScreenPosition(const Vec2 &pos) {
setScreenPosition(pos);
return this;
}
CheckBox *CheckBox::withCameraOffset(float x, float y) {
setCameraOffset(x, y);
return this;
}
CheckBox *CheckBox::withCameraOffset(const Vec2 &offset) {
setCameraOffset(offset);
return this;
}
void CheckBox::setChecked(bool checked) {
if (checked_ != checked) {
checked_ = checked;
if (onStateChange_) {
onStateChange_(checked_);
}
}
}
void CheckBox::toggle() {
setChecked(!checked_);
}
void CheckBox::setLabel(const String &label) {
label_ = label;
}
void CheckBox::setFont(Ptr<FontAtlas> font) {
font_ = font;
}
void CheckBox::setTextColor(const Color &color) {
textColor_ = color;
}
void CheckBox::setBoxSize(float size) {
boxSize_ = size;
}
void CheckBox::setSpacing(float spacing) {
spacing_ = spacing;
}
void CheckBox::setCheckedColor(const Color &color) {
checkedColor_ = color;
}
void CheckBox::setUncheckedColor(const Color &color) {
uncheckedColor_ = color;
}
void CheckBox::setCheckMarkColor(const Color &color) {
checkMarkColor_ = color;
}
void CheckBox::setOnStateChange(Function<void(bool)> callback) {
onStateChange_ = callback;
}
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_);
}
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_);
}
}
bool CheckBox::onMousePress(const MouseEvent &event) {
if (event.button == MouseButton::Left) {
pressed_ = true;
return true;
}
return false;
}
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

384
Extra2D/src/ui/label.cpp Normal file
View File

@ -0,0 +1,384 @@
#include <extra2d/ui/label.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/core/string.h>
namespace extra2d {
Label::Label() {
// 标签默认锚点为左上角
setAnchor(0.0f, 0.0f);
}
Label::Label(const String &text) : text_(text) {
setAnchor(0.0f, 0.0f);
sizeDirty_ = true;
}
Ptr<Label> Label::create() {
return makePtr<Label>();
}
Ptr<Label> Label::create(const String &text) {
return makePtr<Label>(text);
}
Ptr<Label> Label::create(const String &text, Ptr<FontAtlas> font) {
auto label = makePtr<Label>(text);
label->setFont(font);
return label;
}
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
Label *Label::withPosition(float x, float y) {
setPosition(x, y);
return this;
}
Label *Label::withPosition(const Vec2 &pos) {
setPosition(pos);
return this;
}
Label *Label::withAnchor(float x, float y) {
setAnchor(x, y);
return this;
}
Label *Label::withAnchor(const Vec2 &anchor) {
setAnchor(anchor);
return this;
}
Label *Label::withText(const String &text) {
setText(text);
return this;
}
Label *Label::withFont(Ptr<FontAtlas> font) {
setFont(font);
return this;
}
Label *Label::withTextColor(const Color &color) {
setTextColor(color);
return this;
}
Label *Label::withFontSize(int size) {
setFontSize(size);
return this;
}
// ------------------------------------------------------------------------
// 坐标空间设置(链式调用)
// ------------------------------------------------------------------------
Label *Label::withCoordinateSpace(CoordinateSpace space) {
setCoordinateSpace(space);
return this;
}
Label *Label::withScreenPosition(float x, float y) {
setScreenPosition(x, y);
return this;
}
Label *Label::withScreenPosition(const Vec2 &pos) {
setScreenPosition(pos);
return this;
}
Label *Label::withCameraOffset(float x, float y) {
setCameraOffset(x, y);
return this;
}
Label *Label::withCameraOffset(const Vec2 &offset) {
setCameraOffset(offset);
return this;
}
void Label::setText(const String &text) {
text_ = text;
sizeDirty_ = true;
updateSpatialIndex();
}
void Label::setFont(Ptr<FontAtlas> font) {
font_ = font;
sizeDirty_ = true;
updateSpatialIndex();
}
void Label::setTextColor(const Color &color) {
textColor_ = color;
}
void Label::setFontSize(int size) {
fontSize_ = size;
sizeDirty_ = true;
updateSpatialIndex();
}
void Label::setHorizontalAlign(HorizontalAlign align) {
hAlign_ = align;
}
void Label::setVerticalAlign(VerticalAlign align) {
vAlign_ = align;
}
void Label::setShadowEnabled(bool enabled) {
shadowEnabled_ = enabled;
}
void Label::setShadowColor(const Color &color) {
shadowColor_ = color;
}
void Label::setShadowOffset(const Vec2 &offset) {
shadowOffset_ = offset;
}
void Label::setOutlineEnabled(bool enabled) {
outlineEnabled_ = enabled;
}
void Label::setOutlineColor(const Color &color) {
outlineColor_ = color;
}
void Label::setOutlineWidth(float width) {
outlineWidth_ = width;
}
void Label::setMultiLine(bool multiLine) {
multiLine_ = multiLine;
sizeDirty_ = true;
updateSpatialIndex();
}
void Label::setLineSpacing(float spacing) {
lineSpacing_ = spacing;
sizeDirty_ = true;
updateSpatialIndex();
}
void Label::setMaxWidth(float maxWidth) {
maxWidth_ = maxWidth;
sizeDirty_ = true;
updateSpatialIndex();
}
Vec2 Label::getTextSize() const {
updateCache();
return cachedSize_;
}
float Label::getLineHeight() const {
if (font_) {
return font_->getLineHeight() * lineSpacing_;
}
return static_cast<float>(fontSize_) * lineSpacing_;
}
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;
}
std::vector<String> Label::splitLines() const {
std::vector<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 != String::npos) {
String line = text_.substr(start, end - start);
// 如果单行超过最大宽度,需要自动换行
Vec2 lineSize = font_->measureText(line);
if (lineSize.x > maxWidth_) {
// 简单实现:按字符逐个尝试
String currentLine;
for (size_t i = 0; i < line.length(); ++i) {
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()) {
String line = text_.substr(start);
Vec2 lineSize = font_->measureText(line);
if (lineSize.x > maxWidth_) {
String currentLine;
for (size_t i = 0; i < line.length(); ++i) {
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;
}
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;
}
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);
}
}
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);
}
void Label::onDrawWidget(RenderBackend &renderer) {
if (!font_ || text_.empty()) {
return;
}
Vec2 pos = calculateDrawPosition();
// 绘制阴影
if (shadowEnabled_) {
Vec2 shadowPos = pos + shadowOffset_;
drawText(renderer, shadowPos, shadowColor_);
}
// 绘制描边简化实现向8个方向偏移绘制
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

@ -0,0 +1,432 @@
#include <extra2d/ui/progress_bar.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/core/string.h>
#include <cmath>
namespace extra2d {
ProgressBar::ProgressBar() {
setAnchor(0.0f, 0.0f);
setSize(200.0f, 20.0f);
}
Ptr<ProgressBar> ProgressBar::create() {
return makePtr<ProgressBar>();
}
Ptr<ProgressBar> ProgressBar::create(float min, float max, float value) {
auto bar = makePtr<ProgressBar>();
bar->setRange(min, max);
bar->setValue(value);
return bar;
}
// ============================================================================
// 链式调用构建器方法实现
// ============================================================================
ProgressBar *ProgressBar::withPosition(float x, float y) {
setPosition(x, y);
return this;
}
ProgressBar *ProgressBar::withPosition(const Vec2 &pos) {
setPosition(pos);
return this;
}
ProgressBar *ProgressBar::withAnchor(float x, float y) {
setAnchor(x, y);
return this;
}
ProgressBar *ProgressBar::withAnchor(const Vec2 &anchor) {
setAnchor(anchor);
return this;
}
ProgressBar *ProgressBar::withSize(float width, float height) {
setSize(width, height);
return this;
}
ProgressBar *ProgressBar::withProgress(float progress) {
setValue(min_ + progress * (max_ - min_));
return this;
}
ProgressBar *ProgressBar::withCoordinateSpace(CoordinateSpace space) {
setCoordinateSpace(space);
return this;
}
ProgressBar *ProgressBar::withScreenPosition(float x, float y) {
setScreenPosition(x, y);
return this;
}
ProgressBar *ProgressBar::withScreenPosition(const Vec2 &pos) {
setScreenPosition(pos);
return this;
}
ProgressBar *ProgressBar::withCameraOffset(float x, float y) {
setCameraOffset(x, y);
return this;
}
ProgressBar *ProgressBar::withCameraOffset(const Vec2 &offset) {
setCameraOffset(offset);
return this;
}
void ProgressBar::setRange(float min, float max) {
min_ = min;
max_ = max;
setValue(value_);
}
void ProgressBar::setValue(float value) {
value_ = std::clamp(value, min_, max_);
if (!animatedChangeEnabled_) {
displayValue_ = value_;
}
if (delayedDisplayEnabled_) {
delayTimer_ = delayTime_;
}
}
float ProgressBar::getPercent() const {
if (max_ <= min_) return 0.0f;
return (displayValue_ - min_) / (max_ - min_);
}
void ProgressBar::setDirection(Direction dir) {
direction_ = dir;
}
void ProgressBar::setBackgroundColor(const Color &color) {
bgColor_ = color;
}
void ProgressBar::setFillColor(const Color &color) {
fillColor_ = color;
}
void ProgressBar::setGradientFillEnabled(bool enabled) {
gradientEnabled_ = enabled;
}
void ProgressBar::setFillColorEnd(const Color &color) {
fillColorEnd_ = color;
}
void ProgressBar::setSegmentedColorsEnabled(bool enabled) {
segmentedColorsEnabled_ = enabled;
}
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; });
}
void ProgressBar::clearColorSegments() {
colorSegments_.clear();
}
void ProgressBar::setCornerRadius(float radius) {
cornerRadius_ = radius;
}
void ProgressBar::setRoundedCornersEnabled(bool enabled) {
roundedCornersEnabled_ = enabled;
}
void ProgressBar::setBorderEnabled(bool enabled) {
borderEnabled_ = enabled;
}
void ProgressBar::setBorderColor(const Color &color) {
borderColor_ = color;
}
void ProgressBar::setBorderWidth(float width) {
borderWidth_ = width;
}
void ProgressBar::setPadding(float padding) {
padding_ = padding;
}
void ProgressBar::setTextEnabled(bool enabled) {
textEnabled_ = enabled;
}
void ProgressBar::setFont(Ptr<FontAtlas> font) {
font_ = font;
}
void ProgressBar::setTextColor(const Color &color) {
textColor_ = color;
}
void ProgressBar::setTextFormat(const String &format) {
textFormat_ = format;
}
void ProgressBar::setAnimatedChangeEnabled(bool enabled) {
animatedChangeEnabled_ = enabled;
if (!enabled) {
displayValue_ = value_;
}
}
void ProgressBar::setAnimationSpeed(float speed) {
animationSpeed_ = speed;
}
void ProgressBar::setDelayedDisplayEnabled(bool enabled) {
delayedDisplayEnabled_ = enabled;
}
void ProgressBar::setDelayTime(float seconds) {
delayTime_ = seconds;
}
void ProgressBar::setDelayedFillColor(const Color &color) {
delayedFillColor_ = color;
}
void ProgressBar::setStripedEnabled(bool enabled) {
stripedEnabled_ = enabled;
}
void ProgressBar::setStripeColor(const Color &color) {
stripeColor_ = color;
}
void ProgressBar::setStripeSpeed(float speed) {
stripeSpeed_ = speed;
}
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_;
}
String ProgressBar::formatText() const {
String result = textFormat_;
size_t pos = result.find("{value}");
if (pos != String::npos) {
result.replace(pos, 7, std::to_string(static_cast<int>(displayValue_)));
}
pos = result.find("{max}");
if (pos != String::npos) {
result.replace(pos, 5, std::to_string(static_cast<int>(max_)));
}
pos = result.find("{percent}");
if (pos != String::npos) {
int percent = static_cast<int>(getPercent() * 100);
result.replace(pos, 9, std::to_string(percent));
}
return result;
}
Rect ProgressBar::getBoundingBox() const {
return Rect(getPosition().x, getPosition().y, getSize().width, getSize().height);
}
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;
}
}
}
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_) {
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_);
}
}
void ProgressBar::drawRoundedRect(RenderBackend &renderer, const Rect &rect, const Color &color, float radius) {
renderer.drawRect(rect, color, borderWidth_);
}
void ProgressBar::fillRoundedRect(RenderBackend &renderer, const Rect &rect, const Color &color, float radius) {
renderer.fillRect(rect, color);
}
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

@ -0,0 +1,259 @@
#include <extra2d/ui/radio_button.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/core/string.h>
namespace extra2d {
RadioButton::RadioButton() {
setAnchor(0.0f, 0.0f);
setSize(circleSize_, circleSize_);
}
Ptr<RadioButton> RadioButton::create() {
return makePtr<RadioButton>();
}
Ptr<RadioButton> RadioButton::create(const String &label) {
auto rb = makePtr<RadioButton>();
rb->setLabel(label);
return rb;
}
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
RadioButton *RadioButton::withPosition(float x, float y) {
setPosition(x, y);
return this;
}
RadioButton *RadioButton::withPosition(const Vec2 &pos) {
setPosition(pos);
return this;
}
RadioButton *RadioButton::withAnchor(float x, float y) {
setAnchor(x, y);
return this;
}
RadioButton *RadioButton::withAnchor(const Vec2 &anchor) {
setAnchor(anchor);
return this;
}
RadioButton *RadioButton::withText(const String &text) {
setLabel(text);
return this;
}
RadioButton *RadioButton::withFont(Ptr<FontAtlas> font) {
setFont(font);
return this;
}
RadioButton *RadioButton::withTextColor(const Color &color) {
setTextColor(color);
return this;
}
RadioButton *RadioButton::withSize(float width, float height) {
setSize(width, height);
return this;
}
// ------------------------------------------------------------------------
// 链式调用 - 坐标空间设置
// ------------------------------------------------------------------------
RadioButton *RadioButton::withCoordinateSpace(CoordinateSpace space) {
setCoordinateSpace(space);
return this;
}
RadioButton *RadioButton::withScreenPosition(float x, float y) {
setScreenPosition(x, y);
return this;
}
RadioButton *RadioButton::withScreenPosition(const Vec2 &pos) {
setScreenPosition(pos);
return this;
}
RadioButton *RadioButton::withCameraOffset(float x, float y) {
setCameraOffset(x, y);
return this;
}
RadioButton *RadioButton::withCameraOffset(const Vec2 &offset) {
setCameraOffset(offset);
return this;
}
// ------------------------------------------------------------------------
// 普通设置方法
// ------------------------------------------------------------------------
void RadioButton::setSelected(bool selected) {
if (selected_ != selected) {
selected_ = selected;
if (onStateChange_) {
onStateChange_(selected_);
}
}
}
void RadioButton::setLabel(const String &label) {
label_ = label;
}
void RadioButton::setFont(Ptr<FontAtlas> font) {
font_ = font;
}
void RadioButton::setTextColor(const Color &color) {
textColor_ = color;
}
void RadioButton::setCircleSize(float size) {
circleSize_ = size;
}
void RadioButton::setSpacing(float spacing) {
spacing_ = spacing;
}
void RadioButton::setSelectedColor(const Color &color) {
selectedColor_ = color;
}
void RadioButton::setUnselectedColor(const Color &color) {
unselectedColor_ = color;
}
void RadioButton::setDotColor(const Color &color) {
dotColor_ = color;
}
void RadioButton::setGroupId(int groupId) {
groupId_ = groupId;
}
void RadioButton::setOnStateChange(Function<void(bool)> callback) {
onStateChange_ = callback;
}
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_);
}
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_);
}
}
bool RadioButton::onMousePress(const MouseEvent &event) {
if (event.button == MouseButton::Left) {
pressed_ = true;
return true;
}
return false;
}
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 实现
// ============================================================================
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;
}
}
}
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;
}
}
}
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);
}
}
void RadioButtonGroup::setOnSelectionChange(Function<void(RadioButton*)> callback) {
onSelectionChange_ = callback;
}
} // namespace extra2d

478
Extra2D/src/ui/slider.cpp Normal file
View File

@ -0,0 +1,478 @@
#include <extra2d/ui/slider.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/core/string.h>
#include <cmath>
namespace extra2d {
Slider::Slider() {
setAnchor(0.0f, 0.0f);
setSize(200.0f, 20.0f);
}
Ptr<Slider> Slider::create() {
return makePtr<Slider>();
}
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 x X坐标
* @param y Y坐标
* @return this指针
*/
Slider *Slider::withPosition(float x, float y) {
setPosition(x, y);
return this;
}
/**
* @brief Vec2坐标
* @param pos
* @return this指针
*/
Slider *Slider::withPosition(const Vec2 &pos) {
setPosition(pos);
return this;
}
/**
* @brief
* @param x X锚点0-1
* @param y Y锚点0-1
* @return this指针
*/
Slider *Slider::withAnchor(float x, float y) {
setAnchor(x, y);
return this;
}
/**
* @brief Vec2坐标
* @param anchor
* @return this指针
*/
Slider *Slider::withAnchor(const Vec2 &anchor) {
setAnchor(anchor);
return this;
}
/**
* @brief
* @param width
* @param height
* @return this指针
*/
Slider *Slider::withSize(float width, float height) {
setSize(width, height);
return this;
}
/**
* @brief
* @param min
* @return this指针
*/
Slider *Slider::withMinValue(float min) {
min_ = min;
setValue(value_);
return this;
}
/**
* @brief
* @param max
* @return this指针
*/
Slider *Slider::withMaxValue(float max) {
max_ = max;
setValue(value_);
return this;
}
/**
* @brief
* @param value
* @return this指针
*/
Slider *Slider::withValue(float value) {
setValue(value);
return this;
}
/**
* @brief
* @param space
* @return this指针
*/
Slider *Slider::withCoordinateSpace(CoordinateSpace space) {
setCoordinateSpace(space);
return this;
}
/**
* @brief
* @param x X屏幕坐标
* @param y Y屏幕坐标
* @return this指针
*/
Slider *Slider::withScreenPosition(float x, float y) {
setScreenPosition(x, y);
return this;
}
/**
* @brief Vec2坐标
* @param pos
* @return this指针
*/
Slider *Slider::withScreenPosition(const Vec2 &pos) {
setScreenPosition(pos);
return this;
}
/**
* @brief
* @param x X偏移量
* @param y Y偏移量
* @return this指针
*/
Slider *Slider::withCameraOffset(float x, float y) {
setCameraOffset(x, y);
return this;
}
/**
* @brief Vec2坐标
* @param offset
* @return this指针
*/
Slider *Slider::withCameraOffset(const Vec2 &offset) {
setCameraOffset(offset);
return this;
}
void Slider::setRange(float min, float max) {
min_ = min;
max_ = max;
setValue(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_);
}
}
}
void Slider::setStep(float step) {
step_ = step;
if (step_ > 0.0f) {
setValue(value_);
}
}
void Slider::setVertical(bool vertical) {
vertical_ = vertical;
}
void Slider::setTrackSize(float size) {
trackSize_ = size;
}
void Slider::setThumbSize(float size) {
thumbSize_ = size;
}
void Slider::setTrackColor(const Color &color) {
trackColor_ = color;
}
void Slider::setFillColor(const Color &color) {
fillColor_ = color;
}
void Slider::setThumbColor(const Color &color) {
thumbColor_ = color;
}
void Slider::setThumbHoverColor(const Color &color) {
thumbHoverColor_ = color;
}
void Slider::setThumbPressedColor(const Color &color) {
thumbPressedColor_ = color;
}
void Slider::setShowThumb(bool show) {
showThumb_ = show;
}
void Slider::setShowFill(bool show) {
showFill_ = show;
}
void Slider::setTextEnabled(bool enabled) {
textEnabled_ = enabled;
}
void Slider::setFont(Ptr<FontAtlas> font) {
font_ = font;
}
void Slider::setTextColor(const Color &color) {
textColor_ = color;
}
void Slider::setTextFormat(const String &format) {
textFormat_ = format;
}
void Slider::setOnValueChange(Function<void(float)> callback) {
onValueChange_ = callback;
}
void Slider::setOnDragStart(Function<void()> callback) {
onDragStart_ = callback;
}
void Slider::setOnDragEnd(Function<void()> callback) {
onDragEnd_ = callback;
}
Rect Slider::getBoundingBox() const {
return Rect(getPosition().x, getPosition().y, getSize().width, getSize().height);
}
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;
}
}
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_);
}
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_
);
}
}
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_
);
}
}
String Slider::formatText() const {
String result = textFormat_;
size_t pos = result.find("{value}");
if (pos != String::npos) {
result.replace(pos, 7, std::to_string(static_cast<int>(value_)));
}
pos = result.find("{value:");
if (pos != String::npos) {
size_t endPos = result.find("}", pos);
if (endPos != String::npos) {
String format = result.substr(pos + 7, endPos - pos - 8);
result.replace(pos, endPos - pos + 1, std::to_string(static_cast<int>(value_)));
}
}
return result;
}
float Slider::snapToStep(float value) const {
float steps = std::round((value - min_) / step_);
return min_ + steps * step_;
}
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_) {
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_);
}
}
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;
}
bool Slider::onMouseRelease(const MouseEvent &event) {
if (event.button == MouseButton::Left && dragging_) {
dragging_ = false;
if (onDragEnd_) {
onDragEnd_();
}
return true;
}
return false;
}
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;
}
void Slider::onMouseEnter() {
hovered_ = true;
}
void Slider::onMouseLeave() {
hovered_ = false;
}
} // namespace extra2d

255
Extra2D/src/ui/text.cpp Normal file
View File

@ -0,0 +1,255 @@
#include <extra2d/ui/text.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/core/string.h>
#include <cstdarg>
#include <cstdio>
namespace extra2d {
Text::Text() {
// 文字默认锚点为左上角这样setPosition(0, 0)会在左上角显示
setAnchor(0.0f, 0.0f);
}
Text::Text(const String &text) : text_(text) {
sizeDirty_ = true;
// 文字默认锚点为左上角这样setPosition(0, 0)会在左上角显示
setAnchor(0.0f, 0.0f);
}
// ------------------------------------------------------------------------
// 链式调用构建器方法
// ------------------------------------------------------------------------
Text *Text::withPosition(float x, float y) {
setPosition(x, y);
return this;
}
Text *Text::withPosition(const Vec2 &pos) {
setPosition(pos);
return this;
}
Text *Text::withAnchor(float x, float y) {
setAnchor(x, y);
return this;
}
Text *Text::withAnchor(const Vec2 &anchor) {
setAnchor(anchor);
return this;
}
Text *Text::withTextColor(const Color &color) {
setTextColor(color);
return this;
}
Text *Text::withFont(Ptr<FontAtlas> font) {
setFont(font);
return this;
}
Text *Text::withFontSize(int size) {
setFontSize(size);
return this;
}
Text *Text::withAlignment(Alignment align) {
setAlignment(align);
return this;
}
Text *Text::withVerticalAlignment(VerticalAlignment align) {
setVerticalAlignment(align);
return this;
}
// ------------------------------------------------------------------------
// 链式调用 - 坐标空间设置
// ------------------------------------------------------------------------
Text *Text::withCoordinateSpace(CoordinateSpace space) {
setCoordinateSpace(space);
return this;
}
Text *Text::withScreenPosition(float x, float y) {
setScreenPosition(x, y);
return this;
}
Text *Text::withScreenPosition(const Vec2 &pos) {
setScreenPosition(pos);
return this;
}
Text *Text::withCameraOffset(float x, float y) {
setCameraOffset(x, y);
return this;
}
Text *Text::withCameraOffset(const Vec2 &offset) {
setCameraOffset(offset);
return this;
}
// ------------------------------------------------------------------------
// 普通设置方法
// ------------------------------------------------------------------------
void Text::setText(const String &text) {
text_ = text;
sizeDirty_ = true;
updateSpatialIndex();
}
void Text::setFont(Ptr<FontAtlas> font) {
font_ = font;
sizeDirty_ = true;
updateSpatialIndex();
}
void Text::setTextColor(const Color &color) { color_ = color; }
void Text::setFontSize(int size) {
fontSize_ = size;
sizeDirty_ = true;
updateSpatialIndex();
}
void Text::setAlignment(Alignment align) {
alignment_ = align;
updateSpatialIndex();
}
void Text::setVerticalAlignment(VerticalAlignment align) {
verticalAlignment_ = align;
updateSpatialIndex();
}
Vec2 Text::getTextSize() const {
updateCache();
return cachedSize_;
}
float Text::getLineHeight() const {
if (font_) {
return font_->getLineHeight();
}
return static_cast<float>(fontSize_);
}
void Text::updateCache() const {
if (!sizeDirty_ || !font_) {
return;
}
cachedSize_ = font_->measureText(text_);
sizeDirty_ = false;
}
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;
// 锚点调整:锚点(0.5, 0.5)表示文本中心在pos位置
// 需要将文本向左上方偏移锚点比例 * 文本大小
pos.x -= textSize.x * anchor.x;
pos.y -= textSize.y * anchor.y;
// 水平对齐(仅在设置了控件大小时生效)
if (!widgetSize.empty()) {
switch (alignment_) {
case Alignment::Center:
pos.x += (refWidth - textSize.x) * 0.5f;
break;
case Alignment::Right:
pos.x += refWidth - textSize.x;
break;
case Alignment::Left:
default:
break;
}
}
// 垂直对齐(仅在设置了控件大小时生效)
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;
}
Ptr<Text> Text::create() { return makePtr<Text>(); }
Ptr<Text> Text::create(const String &text) { return makePtr<Text>(text); }
Ptr<Text> Text::create(const String &text, Ptr<FontAtlas> font) {
auto t = makePtr<Text>(text);
t->setFont(font);
return t;
}
// ------------------------------------------------------------------------
// 格式化创建方法
// ------------------------------------------------------------------------
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);
}
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;
}
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);
}
void Text::onDrawWidget(RenderBackend &renderer) {
if (!font_ || text_.empty()) {
return;
}
Vec2 pos = calculateDrawPosition();
renderer.drawText(*font_, text_, pos, color_);
}
} // namespace extra2d

View File

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

404
README.md
View File

@ -25,7 +25,7 @@
<i>高性能、易用、原生支持 Switch 平台</i>
</p>
[📖 构建指南](./SWITCH_BUILD_GUIDE.md) | [🚀 快速开始](#快速开始) | [📦 项目结构](#项目结构) | [💬 问题反馈](https://github.com/ChestnutYueyue/extra2d/issues)
[📖 构建指南](./docs/Extra2D%20构建系统文档.md) | [🚀 快速开始](#快速开始) | [📦 示例程序](#示例程序) | [📚 API 教程](./docs/API_Tutorial/01_Quick_Start.md)
</div>
@ -40,68 +40,12 @@
### ✨ 核心特性
- **🎯 Switch 原生支持**:专为 Nintendo Switch 硬件优化,支持掌机/主机双模式
- **🎬 高级动画系统**:支持骨骼动画、精灵动画、补间动画,提供 ALS 动画格式支持
- **📜 脚本系统**:集成 Squirrel 脚本引擎,支持热更新和快速迭代开发
- **🎵 音频系统**:基于 SDL2 的高质量音频播放,支持 BGM 和音效
- **🎨 特效系统**:粒子系统、后处理效果、自定义着色器支持
- **🎬 高级动画系统**:支持骨骼动画、精灵动画、补间动画
- **🎵 音频系统**:基于 SDL2_mixer 的高质量音频播放,支持 BGM 和音效
- **🎨 渲染系统**:基于 OpenGL ES 的 2D 渲染,支持自定义着色器
- **💾 数据持久化**:游戏存档、配置文件的便捷读写
---
## 🗺️ 架构概览
```mermaid
mindmap
root((Extra2D 引擎架构))
核心系统
应用管理 Application
渲染后端 RenderBackend
窗口管理 Window
输入处理 Input
音频引擎 AudioEngine
资源管理 ResourceManager
事件系统 EventDispatcher
场景管理
场景 Scene
场景管理器 SceneManager
过渡动画 Transition
空间索引 SpatialManager
节点系统
基础节点 Node
精灵 Sprite
文本 Text
形状 ShapeNode
摄像机 Camera
动画节点 AnimationNode
动画系统
动作系统 Action
精灵动画 AnimatedSprite
骨骼动画支持
动画缓存 AnimationCache
动画事件 AnimationEvent
脚本系统
Squirrel 脚本引擎
脚本节点 ScriptNode
完整 API 绑定
特效系统
粒子系统 ParticleSystem
后处理 PostProcess
自定义效果管理器
UI 系统
基础控件 Widget
按钮 Button
工具库
音频播放 Sound
数据持久化 Data
随机数 Random
定时器 Timer
字体 FontAtlas
数学库
向量 Vec2/Vec3
矩形 Rect
大小 Size
颜色 Color
```
- **🔧 空间索引**:内置四叉树和空间哈希碰撞检测系统
- **🖱️ UI 系统**:完整的 UI 控件支持(按钮、文本、滑块等)
---
@ -116,319 +60,24 @@ mindmap
| 构建工具 | xmake |
| 目标平台 | Nintendo Switch / Windows (MinGW) |
### 安装 devkitPro
### 安装 xmake
```bash
# Windows (以管理员身份运行 PowerShell)
Invoke-WebRequest -Uri "https://github.com/devkitPro/pacman/releases/latest/download/devkitpro-pacman.amd64.exe" -OutFile "devkitpro-pacman.exe"
.\devkitpro-pacman.exe
# Windows (PowerShell)
Invoke-Expression (Invoke-WebRequest 'https://xmake.io/psget.text' -UseBasicParsing).Content
# 安装 Switch 开发工具链
pacman -S switch-dev switch-portlibs
# macOS
brew install xmake
# Linux
sudo add-apt-repository ppa:xmake-io/xmake
sudo apt update
sudo apt install xmake
```
### 构建项目
## 📚 文档
#### Switch 平台
```bash
# 克隆仓库
git clone https://github.com/ChestnutYueyue/extra2d.git
cd extra2d
# 配置 Switch 平台构建
xmake f -p switch --mode=release
# 构建引擎
xmake
# 构建示例游戏
xmake -g examples
```
#### Windows (MinGW) 平台
```bash
# 克隆仓库
git clone https://github.com/ChestnutYueyue/extra2d.git
cd extra2d
# 配置 MinGW 平台构建
xmake f -p mingw --mode=release
# 安装依赖
xmake require -y
# 构建引擎和示例
xmake
# 运行示例
xmake run hello_world
```
### 生成 NSP 可运行文件
```bash
# 打包推箱子游戏示例
xmake package push_box
# 生成的文件位于
# build/switch/release/push_box/push_box.nsp
```
---
## 📝 Hello World 示例
```cpp
#include <extra2d/extra2d.h>
using namespace extra2d;
int main()
{
// 初始化日志
Logger::init();
Logger::setLevel(LogLevel::Info);
// 配置应用
AppConfig config;
config.title = "Hello Extra2D";
config.width = 1280;
config.height = 720;
config.vsync = true;
// 初始化应用
auto& app = Application::instance();
if (!app.init(config)) {
Logger::shutdown();
return -1;
}
// 创建场景
auto scene = makePtr<Scene>();
scene->setBackgroundColor(Color(0.1f, 0.1f, 0.15f, 1.0f));
// 创建文本节点
auto text = Text::create("Hello, Extra2D on Switch!");
text->setPosition(Vec2(640, 360));
text->setAnchor(Vec2(0.5f, 0.5f));
text->setTextColor(Color(1.0f, 0.5f, 0.2f, 1.0f));
text->setFontSize(48);
// 添加动画效果
text->runAction(makePtr<Repeat>(
makePtr<Sequence>(std::vector<Ptr<Action>>{
makePtr<ScaleTo>(1.0f, Vec2(1.5f, 1.5f)),
makePtr<ScaleTo>(1.0f, Vec2(1.0f, 1.0f))
})
));
// 添加到场景
scene->addChild(text);
// 进入场景
app.enterScene(scene);
// 运行主循环
app.run();
// 清理
app.shutdown();
Logger::shutdown();
return 0;
}
```
---
## 🏗️ 项目结构
```
Extra2D/
├── 📁 Extra2D/ # 引擎核心代码
│ ├── 📁 include/ # 头文件
│ │ ├── 📁 extra2d/ # 引擎头文件
│ │ │ ├── extra2d.h # 主头文件
│ │ │ ├── app/ # 应用管理
│ │ │ ├── action/ # 动作系统
│ │ │ ├── animation/ # 动画系统
│ │ │ ├── audio/ # 音频系统
│ │ │ ├── core/ # 核心类型
│ │ │ ├── effects/ # 特效系统
│ │ │ ├── event/ # 事件系统
│ │ │ ├── graphics/ # 图形渲染
│ │ │ ├── platform/ # 平台抽象
│ │ │ ├── resource/ # 资源管理
│ │ │ ├── scene/ # 场景系统
│ │ │ ├── script/ # 脚本系统
│ │ │ ├── spatial/ # 空间索引
│ │ │ ├── ui/ # UI 系统
│ │ │ └── utils/ # 工具库
│ │ ├── 📁 glad/ # OpenGL Loader
│ │ ├── 📁 json/ # JSON 库
│ │ ├── 📁 simpleini/ # INI 配置文件库
│ │ └── 📁 stb/ # STB 图像库
│ └── 📁 src/ # 源文件
│ ├── action/ # 动作系统实现
│ ├── animation/ # 动画系统实现
│ ├── app/ # 应用管理实现
│ ├── audio/ # 音频系统实现
│ ├── core/ # 核心类型实现
│ ├── effects/ # 特效系统实现
│ ├── event/ # 事件系统实现
│ ├── glad/ # GLAD 实现
│ ├── graphics/ # 图形渲染实现
│ ├── platform/ # 平台抽象实现
│ ├── resource/ # 资源管理实现
│ ├── scene/ # 场景系统实现
│ ├── script/ # 脚本系统实现
│ ├── spatial/ # 空间索引实现
│ ├── ui/ # UI 系统实现
│ └── utils/ # 工具库实现
├── 📁 docs/ # 文档
│ ├── 📁 API_Tutorial/ # API 教程
│ └── Extra2D 构建系统文档.md # 构建系统文档
├── 📁 examples/ # 示例程序
│ ├── hello_world/ # Hello World 示例
│ ├── collision_demo/ # 碰撞检测示例
│ ├── push_box/ # 推箱子游戏
│ └── spatial_index_demo/ # 空间索引示例
├── 📁 logo/ # Logo 资源
├── 📁 squirrel/ # Squirrel 脚本引擎
├── <20> xmake/ # Xmake 构建配置
│ └── toolchains/ # 工具链定义
├── 📄 xmake.lua # 主构建配置
├── 📄 LICENSE # MIT 许可证
└── 📄 README.md # 本文件
```
---
## 🎮 Switch 特定功能
### 双模式支持
```cpp
// 检测当前模式
if (app.isDocked()) {
// 主机模式:可使用更高分辨率
config.width = 1920;
config.height = 1080;
} else {
// 掌机模式
config.width = 1280;
config.height = 720;
}
```
### 控制器输入
```cpp
auto& input = app.input();
// Joy-Con 支持
if (input.isKeyDown(KeyCode::ButtonA)) {
// A 键按下
}
if (input.isKeyDown(KeyCode::ButtonLeft)) {
// 左摇杆向左
}
```
### ROMFS 资源加载
```cpp
// 自动从 ROMFS 加载资源
auto texture = resources.loadTexture("romfs:/images/player.png");
auto sound = audio.loadSound("romfs:/audio/jump.wav");
```
---
## 📋 API 速查
### 应用控制
```cpp
auto& app = Application::instance();
app.init(config);
app.run();
app.quit();
```
### 场景管理
```cpp
auto scene = makePtr<Scene>();
app.enterScene(scene);
app.enterScene(scene, makePtr<FadeTransition>(1.0f));
```
### 节点操作
```cpp
auto sprite = Sprite::create(texture);
sprite->setPosition(Vec2(100, 200));
sprite->setRotation(45.0f);
sprite->runAction(makePtr<MoveTo>(1.0f, Vec2(200, 300)));
```
### 动画系统
```cpp
// 精灵动画
auto anim = AnimatedSprite::createFromGrid(
"player.png", 96, 96, 125.0f, 16);
anim->setFrameRange(0, 3);
anim->play();
// 动作动画
node->runAction(makePtr<Sequence>(
makePtr<MoveTo>(1.0f, Vec2(100, 200)),
makePtr<ScaleTo>(0.5f, Vec2(2.0f, 2.0f))
));
```
### 输入处理
```cpp
auto& input = app.input();
if (input.isKeyDown(KeyCode::ButtonA)) {}
if (input.isKeyPressed(KeyCode::ButtonB)) {}
auto pos = input.getMousePosition();
```
### 音频播放
```cpp
auto& audio = app.audio();
auto sound = audio.loadSound("jump.wav");
sound->play();
sound->setVolume(0.8f);
```
---
## 🛠️ 技术栈
| 技术 | 用途 | 版本 |
|:----:|:-----|:----:|
| OpenGL | 2D 图形渲染 | ES 3.0+ |
| GLFW | 窗口和输入管理 | 3.3+ |
| GLM | 数学库 | 0.9.9+ |
| miniaudio | 音频播放 | 最新版 |
| spdlog | 日志系统 | 最新版 |
| stb_image | 图像加载 | 最新版 |
| Squirrel | 脚本引擎 | 3.2+ |
| xmake | 构建系统 | 2.5+ |
---
## 📖 相关文档
- [📚 API 教程](./docs/API_Tutorial/01_Quick_Start.md) - 完整的 API 使用教程
- [📖 API 教程](./docs/API_Tutorial/01_Quick_Start.md) - 完整的 API 使用教程
- [01. 快速开始](./docs/API_Tutorial/01_Quick_Start.md)
- [02. 场景系统](./docs/API_Tutorial/02_Scene_System.md)
- [03. 节点系统](./docs/API_Tutorial/03_Node_System.md)
@ -438,8 +87,21 @@ sound->setVolume(0.8f);
- [07. UI 系统](./docs/API_Tutorial/07_UI_System.md)
- [08. 音频系统](./docs/API_Tutorial/08_Audio_System.md)
- [🔧 构建系统文档](./docs/Extra2D%20构建系统文档.md) - 详细的构建系统说明
- [🎮 Switch 构建指南](./SWITCH_BUILD_GUIDE.md) - Switch 平台构建教程
- [📝 迁移完成记录](./SWITCH_MIGRATION_COMPLETE.md) - 项目迁移历史记录
---
## 🛠️ 技术栈
| 技术 | 用途 | 版本 |
|:----:|:-----|:----:|
| OpenGL ES | 2D 图形渲染 | 3.0+ |
| GLFW | 窗口和输入管理 | 3.3+ |
| GLM | 数学库 | 0.9.9+ |
| SDL2_mixer | 音频播放 | 2.0+ |
| spdlog | 日志系统 | 最新版 |
| stb_image | 图像加载 | 最新版 |
| freetype | 字体渲染 | 最新版 |
| xmake | 构建系统 | 2.5+ |
---

View File

@ -1,68 +1,215 @@
# Extra2D API 教程 - 01. 快速开始
# 01. 快速开始
## 简介
本教程将带你快速上手 Extra2D 引擎,通过一个简单的 Hello World 示例了解引擎的基本使用方法。
Extra2D 是一个跨平台的 2D 游戏引擎,支持 Windows (MinGW) 和 Nintendo Switch 平台。
## 示例代码
## 最小示例
完整示例位于 `examples/hello_world/main.cpp`
```cpp
#include <extra2d/extra2d.h>
using namespace extra2d;
int main(int argc, char **argv) {
// 1. 初始化日志系统
Logger::init();
Logger::setLevel(LogLevel::Debug);
// ============================================================================
// Hello World 场景
// ============================================================================
// 2. 获取应用实例
auto &app = Application::instance();
/**
* @brief Hello World 场景类
* 显示简单的 "Hello World" 文字
*/
class HelloWorldScene : public Scene {
public:
/**
* @brief 场景进入时调用
*/
void onEnter() override {
E2D_LOG_INFO("HelloWorldScene::onEnter - 进入场景");
// 3. 配置应用
AppConfig config;
config.title = "My Game";
config.width = 1280;
config.height = 720;
config.vsync = true;
config.fpsLimit = 60;
// 设置背景颜色为深蓝色
setBackgroundColor(Color(0.1f, 0.1f, 0.3f, 1.0f));
// 4. 初始化应用
if (!app.init(config)) {
E2D_LOG_ERROR("应用初始化失败!");
return -1;
// 加载字体(支持多种字体后备)
auto &resources = Application::instance().resources();
font_ = resources.loadFont("assets/font.ttf", 48, true);
if (!font_) {
E2D_LOG_ERROR("字体加载失败,文字渲染将不可用!");
return;
}
// 5. 进入场景
app.enterScene(makePtr<MyScene>());
// 创建 "你好世界" 文本组件 - 使用屏幕空间(固定位置,不随相机移动)
auto text1 = Text::create("你好世界", font_);
text1->withCoordinateSpace(CoordinateSpace::Screen)
->withScreenPosition(640.0f, 360.0f) // 屏幕中心
->withAnchor(0.5f, 0.5f) // 中心锚点,让文字中心对准位置
->withTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f));
addChild(text1);
// 6. 运行应用
app.run();
// 创建提示文本组件 - 使用屏幕空间,固定在屏幕底部
auto text2 = Text::create("退出按键START 按钮)", font_);
text2->withCoordinateSpace(CoordinateSpace::Screen)
->withScreenPosition(640.0f, 650.0f) // 屏幕底部
->withAnchor(0.5f, 0.5f)
->withTextColor(Color(1.0f, 1.0f, 0.0f, 1.0f));
addChild(text2);
}
return 0;
/**
* @brief 每帧更新时调用
* @param dt 时间间隔(秒)
*/
void onUpdate(float dt) override {
Scene::onUpdate(dt);
// 检查退出按键
auto &input = Application::instance().input();
// 使用手柄 START 按钮退出 (GamepadButton::Start)
if (input.isButtonPressed(GamepadButton::Start)) {
E2D_LOG_INFO("退出应用 (START 按钮)");
Application::instance().quit();
}
}
private:
Ptr<FontAtlas> font_; // 字体图集
};
// ============================================================================
// 程序入口
// ============================================================================
int main(int argc, char **argv)
{
// 初始化日志系统
Logger::init();
Logger::setLevel(LogLevel::Debug);
// 获取应用实例
auto &app = Application::instance();
// 配置应用
AppConfig config;
config.title = "Easy2D - Hello World";
config.width = 1280;
config.height = 720;
config.vsync = true;
config.fpsLimit = 60;
// 初始化应用
if (!app.init(config)) {
E2D_LOG_ERROR("应用初始化失败!");
return -1;
}
// 进入 Hello World 场景
app.enterScene(makePtr<HelloWorldScene>());
// 运行应用
app.run();
return 0;
}
```
## 核心概念
### 应用生命周期
### 1. 应用生命周期
Extra2D 应用遵循以下生命周期:
```
Logger::init() → Application::init() → enterScene() → run() → 退出
初始化 (Application::init)
进入场景 (enterScene)
主循环 (run) → 更新 (onUpdate) → 渲染 (onRender)
退出 (quit)
```
### 场景生命周期
### 2. 场景系统
场景是游戏内容的容器,通过继承 `Scene` 类并重写以下方法:
| 方法 | 说明 |
|------|------|
| `onEnter()` | 场景进入时调用,用于初始化资源 |
| `onExit()` | 场景退出时调用,用于清理资源 |
| `onUpdate(dt)` | 每帧更新时调用,用于处理游戏逻辑 |
| `onRender(renderer)` | 渲染时调用,用于自定义绘制 |
### 3. 坐标空间
Extra2D 支持三种坐标空间:
```cpp
// 屏幕空间 - 固定位置,不随相机移动
text->withCoordinateSpace(CoordinateSpace::Screen)
->withScreenPosition(640.0f, 360.0f);
// 相机空间 - 跟随相机但保持相对偏移
text->withCoordinateSpace(CoordinateSpace::Camera)
->withCameraOffset(50.0f, 50.0f);
// 世界空间 - 随相机移动(默认行为)
text->withCoordinateSpace(CoordinateSpace::World)
->withPosition(100.0f, 100.0f);
```
onEnter() → onUpdate(dt) → onRender() → onExit()
### 4. 输入处理
支持手柄输入检测:
```cpp
auto &input = Application::instance().input();
// 检测按键按下(持续触发)
if (input.isButtonDown(GamepadButton::A)) { }
// 检测按键按下(单次触发)
if (input.isButtonPressed(GamepadButton::A)) { }
// 检测按键释放
if (input.isButtonReleased(GamepadButton::A)) { }
```
常用按键:
- `GamepadButton::A` - A 键
- `GamepadButton::B` - B 键
- `GamepadButton::X` - X 键
- `GamepadButton::Y` - Y 键
- `GamepadButton::Start` - + 键 (Switch)
- `GamepadButton::DPadUp/Down/Left/Right` - 方向键
### 5. 资源加载
通过资源管理器加载字体、纹理等资源:
```cpp
auto &resources = Application::instance().resources();
// 加载字体
auto font = resources.loadFont("assets/font.ttf", 48, true);
// 加载纹理
auto texture = resources.loadTexture("assets/image.png");
```
### 6. 日志系统
使用宏进行日志输出:
```cpp
E2D_LOG_DEBUG("调试信息");
E2D_LOG_INFO("普通信息");
E2D_LOG_WARN("警告信息");
E2D_LOG_ERROR("错误信息");
```
## 下一步
- [02. 场景系统](02_Scene_System.md)
- [03. 节点系统](03_Node_System.md)
- [04. 资源管理](04_Resource_Management.md)
- [05. 输入处理](05_Input_Handling.md)
- [06. 碰撞检测](06_Collision_Detection.md)
- [07. UI 系统](07_UI_System.md)
- [08. 音频系统](08_Audio_System.md)
- [02. 场景系统](./02_Scene_System.md) - 深入了解场景管理
- [03. 节点系统](./03_Node_System.md) - 学习节点和精灵的使用

View File

@ -1,9 +1,17 @@
# Extra2D API 教程 - 02. 场景系统
# 02. 场景系统
Extra2D 的场景系统提供了游戏内容的分层管理和切换功能。本教程将详细介绍场景的生命周期、切换和过渡效果。
## 完整示例
参考 `examples/push_box/` 中的实现:
- `StartScene.h/cpp` - 开始菜单场景
- `PlayScene.h/cpp` - 游戏主场景
- `SuccessScene.h/cpp` - 通关场景
## 场景基础
场景(Scene)是游戏的基本组织单位,负责管理节点和渲染。
### 创建场景
```cpp
@ -11,71 +19,54 @@
using namespace extra2d;
class MyScene : public Scene {
class GameScene : public Scene {
public:
// 场景进入时调用
void onEnter() override {
// 必须先调用父类的 onEnter()
// 必须先调用父类方法
Scene::onEnter();
// 设置背景
// 设置背景色
setBackgroundColor(Color(0.1f, 0.1f, 0.3f, 1.0f));
E2D_LOG_INFO("场景已进入");
// 设置视口大小用于UI布局
setViewportSize(1280.0f, 720.0f);
}
// 每帧更新时调用
void onUpdate(float dt) override {
Scene::onUpdate(dt);
// dt 是时间间隔(秒)
}
// 渲染时调用
void onRender(RenderBackend &renderer) override {
Scene::onRender(renderer);
// 绘制自定义内容
}
// 场景退出时调用
void onExit() override {
// 清理资源
removeAllChildren();
Scene::onExit();
}
void onUpdate(float dt) override {
Scene::onUpdate(dt);
// 游戏逻辑更新
}
};
```
### 重要提示
**必须调用 `Scene::onEnter()`**
```cpp
void onEnter() override {
Scene::onEnter(); // 必须调用!
// 你的初始化代码
}
```
如果不调用,会导致:
- `running_` 状态未设置
- 子节点无法正确注册到空间索引
- 碰撞检测失效
## 场景管理
### 进入场景
### 场景切换
```cpp
// 进入场景
app.enterScene(makePtr<MyScene>());
// 进入场景(无过渡)
app.enterScene(makePtr<GameScene>());
// 替换当前场景(带过渡效果)
app.scenes().replaceScene(
makePtr<PlayScene>(),
TransitionType::Fade, // 淡入淡出
0.25f // 过渡时间(秒)
);
// 进入场景(有过渡效果)
app.enterScene(makePtr<GameScene>(), TransitionType::Fade, 0.5f);
// 替换当前场景
app.scenes().replaceScene(makePtr<NewScene>());
// 推入场景(保留当前场景)
app.scenes().pushScene(makePtr<NewScene>());
// 弹出场景(返回上一个场景)
app.scenes().popScene();
```
### 场景过渡类型
### 过渡效果类型
```cpp
enum class TransitionType {
@ -88,84 +79,184 @@ enum class TransitionType {
};
```
## 场景配置
## 场景管理器
### 视口设置
通过 `app.scenes()` 访问场景管理器:
```cpp
void onEnter() override {
Scene::onEnter();
auto& scenes = app.scenes();
// 设置视口大小(影响坐标系)
setViewportSize(1280.0f, 720.0f);
// 获取当前场景
auto current = scenes.currentScene();
// 设置背景颜色
setBackgroundColor(Colors::Black);
// 获取场景栈深度
size_t depth = scenes.stackDepth();
// 启用/禁用空间索引
setSpatialIndexingEnabled(true);
}
// 清空场景栈
scenes.clearStack();
```
### 空间索引
## 场景生命周期
```cpp
// 获取空间管理器
auto &spatialManager = getSpatialManager();
// 切换空间索引策略
spatialManager.setStrategy(SpatialStrategy::QuadTree); // 四叉树
spatialManager.setStrategy(SpatialStrategy::SpatialHash); // 空间哈希
// 查询所有碰撞
auto collisions = queryCollisions();
```
创建场景 (makePtr<Scene>)
进入场景 (enterScene)
onEnter() - 初始化资源
主循环
├── onUpdate(dt) - 每帧更新
└── onRender(renderer) - 每帧渲染
退出场景
onExit() - 清理资源
场景销毁
```
## 完整示例
## 推箱子示例场景结构
```
┌─────────────────────────────────────┐
│ StartScene │
│ ┌─────────────────────────────┐ │
│ │ 开始菜单界面 │ │
│ │ - 新游戏 │ │
│ │ - 继续游戏 │ │
│ │ - 退出 │ │
│ └─────────────────────────────┘ │
└──────────────┬──────────────────────┘
│ 选择"新游戏"
┌─────────────────────────────────────┐
│ PlayScene │
│ ┌─────────────────────────────┐ │
│ │ 游戏主界面 │ │
│ │ - 地图渲染 │ │
│ │ - 玩家控制 │ │
│ │ - 关卡信息 │ │
│ └─────────────────────────────┘ │
└──────────────┬──────────────────────┘
│ 通关
┌─────────────────────────────────────┐
│ SuccessScene │
│ ┌─────────────────────────────┐ │
│ │ 通关界面 │ │
│ │ - 显示成绩 │ │
│ │ - 下一关/返回菜单 │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
```
## 代码示例:菜单场景
```cpp
class GameScene : public Scene {
class MenuScene : public Scene {
public:
void onEnter() override {
Scene::onEnter();
// 设置视口和背景
setViewportSize(1280.0f, 720.0f);
setBackgroundColor(Color(0.1f, 0.2f, 0.3f, 1.0f));
auto& app = Application::instance();
auto& resources = app.resources();
// 启用空间索引
setSpatialIndexingEnabled(true);
// 加载背景
auto bgTex = resources.loadTexture("assets/bg.jpg");
if (bgTex) {
auto bg = Sprite::create(bgTex);
bg->setAnchor(0.0f, 0.0f);
addChild(bg);
}
E2D_LOG_INFO("游戏场景已加载");
// 加载字体
font_ = resources.loadFont("assets/font.ttf", 28, true);
// 创建菜单按钮
createMenuButtons();
}
void onUpdate(float dt) override {
Scene::onUpdate(dt);
// 检查退出按键
auto &input = Application::instance().input();
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_START)) {
Application::instance().quit();
auto& input = Application::instance().input();
// 方向键导航
if (input.isButtonPressed(GamepadButton::DPadUp)) {
selectedIndex_ = (selectedIndex_ - 1 + menuCount_) % menuCount_;
updateMenuColors();
}
else if (input.isButtonPressed(GamepadButton::DPadDown)) {
selectedIndex_ = (selectedIndex_ + 1) % menuCount_;
updateMenuColors();
}
// A键确认
if (input.isButtonPressed(GamepadButton::A)) {
executeMenuItem();
}
}
void onRender(RenderBackend &renderer) override {
Scene::onRender(renderer);
private:
void createMenuButtons() {
float centerX = 640.0f;
float startY = 300.0f;
float spacing = 50.0f;
// 绘制 FPS
auto &app = Application::instance();
std::string fpsText = "FPS: " + std::to_string(app.fps());
// ...
for (int i = 0; i < menuCount_; ++i) {
auto btn = Button::create();
btn->setFont(font_);
btn->setAnchor(0.5f, 0.5f);
btn->setPosition(centerX, startY + i * spacing);
addChild(btn);
buttons_.push_back(btn);
}
buttons_[0]->setText("开始游戏");
buttons_[1]->setText("设置");
buttons_[2]->setText("退出");
updateMenuColors();
}
void onExit() override {
E2D_LOG_INFO("游戏场景退出");
Scene::onExit();
void updateMenuColors() {
for (int i = 0; i < buttons_.size(); ++i) {
auto color = (i == selectedIndex_) ? Colors::Red : Colors::White;
buttons_[i]->setTextColor(color);
}
}
void executeMenuItem() {
switch (selectedIndex_) {
case 0:
Application::instance().scenes().replaceScene(
makePtr<GameScene>(), TransitionType::Fade, 0.25f);
break;
case 1:
// 打开设置
break;
case 2:
Application::instance().quit();
break;
}
}
Ptr<FontAtlas> font_;
std::vector<Ptr<Button>> buttons_;
int selectedIndex_ = 0;
int menuCount_ = 3;
};
```
## 最佳实践
1. **始终在 onEnter 中调用 Scene::onEnter()** - 确保场景正确初始化
2. **在 onExit 中清理资源** - 避免内存泄漏
3. **使用过渡效果** - 提升用户体验
4. **分离场景逻辑** - 每个场景负责自己的功能
## 下一步
- [03. 节点系统](03_Node_System.md)
- [04. 资源管理](04_Resource_Management.md)
- [03. 节点系统](./03_Node_System.md) - 学习节点和精灵的使用
- [04. 资源管理](./04_Resource_Management.md) - 深入了解资源加载

View File

@ -1,219 +1,353 @@
# Extra2D API 教程 - 03. 节点系统
# 03. 节点系统
## 节点基础
Extra2D 的节点系统是构建游戏对象的基础。所有可见的游戏元素都是节点的子类。
节点(Node)是游戏对象的基本单位,可以包含子节点,形成树形结构。
## 核心节点类型
### 创建节点
```cpp
#include <extra2d/extra2d.h>
using namespace extra2d;
class MyNode : public Node {
public:
MyNode() {
// 设置位置
setPosition(Vec2(100.0f, 200.0f));
// 设置旋转(度)
setRotation(45.0f);
// 设置缩放
setScale(Vec2(2.0f, 2.0f));
// 设置锚点0-1范围默认0.5是中心)
setAnchor(0.5f, 0.5f);
// 设置可见性
setVisible(true);
}
// 每帧更新
void onUpdate(float dt) override {
Node::onUpdate(dt);
// 自定义更新逻辑
}
// 渲染
void onRender(RenderBackend &renderer) override {
Node::onRender(renderer);
// 自定义渲染
}
};
```
Node (基类)
├── Sprite (精灵)
├── Text (文本)
├── Button (按钮)
├── Widget (UI控件基类)
│ ├── Label (标签)
│ ├── CheckBox (复选框)
│ ├── RadioButton (单选按钮)
│ ├── Slider (滑块)
│ └── ProgressBar (进度条)
└── 自定义节点...
```
## 节点层级
## 基础节点操作
### 添加节点
### 创建和添加节点
```cpp
void onEnter() override {
Scene::onEnter();
// 创建精灵
auto sprite = Sprite::create(texture);
sprite->setPosition(Vec2(100, 200));
sprite->setAnchor(Vec2(0.5f, 0.5f)); // 中心锚点
addChild(sprite);
// 创建子节点
auto child = makePtr<MyNode>();
// 添加到场景
addChild(child);
// 在指定位置添加
addChild(child, 0); // z-order = 0
}
// 创建文本
auto text = Text::create("Hello World", font);
text->setPosition(Vec2(400, 300));
text->setTextColor(Color(1, 1, 1, 1));
addChild(text);
```
### 移除子节点
### 节点属性
```cpp
// 移除指定子节点
removeChild(child);
// 位置
node->setPosition(Vec2(x, y));
Vec2 pos = node->getPosition();
// 移除所有子节点
removeAllChildren();
// 旋转(角度)
node->setRotation(45.0f);
float angle = node->getRotation();
// 通过名称移除
removeChildByName("myNode");
// 缩放
node->setScale(Vec2(2.0f, 2.0f));
Vec2 scale = node->getScale();
// 锚点0,0 左上角0.5,0.5 中心1,1 右下角)
node->setAnchor(Vec2(0.5f, 0.5f));
// 可见性
node->setVisible(true);
bool visible = node->isVisible();
// Z轴顺序
node->setZOrder(10);
```
### 获取子节点
### 链式调用Builder 模式)
```cpp
// 获取子节点数量
size_t count = getChildren().size();
// 通过名称查找
auto node = getChildByName("myNode");
// 遍历子节点
for (auto &child : getChildren()) {
// 处理子节点
}
// 使用链式调用快速配置节点
auto text = Text::create("标题", font)
->withPosition(640.0f, 100.0f)
->withAnchor(0.5f, 0.5f)
->withTextColor(Color(1.0f, 1.0f, 0.0f, 1.0f))
->withCoordinateSpace(CoordinateSpace::Screen);
addChild(text);
```
## 空间索引
### 启用空间索引
```cpp
class PhysicsNode : public Node {
public:
PhysicsNode() {
// 启用空间索引(用于碰撞检测)
setSpatialIndexed(true);
}
// 必须实现 getBoundingBox()
Rect getBoundingBox() const override {
Vec2 pos = getPosition();
return Rect(pos.x - 25.0f, pos.y - 25.0f, 50.0f, 50.0f);
}
};
```
### 边界框
```cpp
// 获取节点边界框
Rect bounds = node->getBoundingBox();
// 检查点是否在边界框内
if (bounds.contains(Vec2(x, y))) {
// 点在边界框内
}
// 检查两个边界框是否相交
if (bounds.intersects(otherBounds)) {
// 边界框相交
}
```
## 精灵节点
## 精灵Sprite
### 创建精灵
```cpp
// 加载纹理
auto texture = resources.loadTexture("assets/player.png");
auto& resources = Application::instance().resources();
// 创建精灵
// 从纹理创建
auto texture = resources.loadTexture("assets/player.png");
auto sprite = Sprite::create(texture);
// 设置位置
sprite->setPosition(Vec2(640.0f, 360.0f));
// 设置精灵属性
sprite->setPosition(Vec2(400, 300));
sprite->setAnchor(Vec2(0.5f, 0.5f));
// 设置锚点(中心)
sprite->setAnchor(0.5f, 0.5f);
// 添加到场景
addChild(sprite);
// 切换纹理
auto newTexture = resources.loadTexture("assets/player2.png");
sprite->setTexture(newTexture);
```
### 精灵动画
```cpp
// 创建动画
auto animation = Animation::create("walk", 0.1f);
animation->addFrame(resources.loadTexture("assets/walk1.png"));
animation->addFrame(resources.loadTexture("assets/walk2.png"));
animation->addFrame(resources.loadTexture("assets/walk3.png"));
// 创建帧动画
auto anim = AnimatedSprite::createFromGrid(
"player.png", // 纹理
32, 32, // 单帧宽高
100.0f, // 帧间隔(ms)
8 // 总帧数
);
// 播放动画
sprite->playAnimation(animation, true); // true = 循环播放
anim->play();
// 设置帧范围
anim->setFrameRange(0, 3);
// 循环播放
anim->setLoop(true);
// 停止动画
sprite->stopAnimation();
anim->stop();
```
## 完整示例
## 文本Text
### 创建文本
```cpp
// 加载字体
auto font = resources.loadFont("assets/font.ttf", 24, true);
// 创建文本
auto text = Text::create("Hello World", font);
text->setPosition(Vec2(400, 300));
addChild(text);
// 动态修改文本
text->setText("新的文本内容");
// 使用格式化文本
text->setFormat("得分: %d", score);
```
### 文本样式
```cpp
text->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f)); // 颜色
text->setFontSize(48); // 字体大小
text->setAnchor(Vec2(0.5f, 0.5f)); // 锚点
// 坐标空间
text->withCoordinateSpace(CoordinateSpace::Screen) // 屏幕空间
->withScreenPosition(100.0f, 50.0f);
```
## 按钮Button
### 创建按钮
```cpp
auto button = Button::create();
button->setFont(font);
button->setText("点击我");
button->setPosition(Vec2(400, 300));
button->setCustomSize(200.0f, 60.0f);
// 设置颜色
button->setTextColor(Colors::White);
button->setBackgroundColor(
Colors::Blue, // 正常状态
Colors::Green, // 悬停状态
Colors::Red // 按下状态
);
// 设置点击回调
button->onClick([]() {
E2D_LOG_INFO("按钮被点击!");
});
addChild(button);
```
### 透明按钮(用于菜单)
```cpp
// 创建纯文本按钮(透明背景)
auto menuBtn = Button::create();
menuBtn->setFont(font);
menuBtn->setText("菜单项");
menuBtn->setTextColor(Colors::Black);
menuBtn->setBackgroundColor(
Colors::Transparent,
Colors::Transparent,
Colors::Transparent
);
menuBtn->setBorder(Colors::Transparent, 0.0f);
menuBtn->setAnchor(0.5f, 0.5f);
menuBtn->setPosition(centerX, centerY);
addChild(menuBtn);
```
## 节点层级管理
### 父子关系
```cpp
// 添加子节点
parent->addChild(child);
// 移除子节点
parent->removeChild(child);
// 移除所有子节点
parent->removeAllChildren();
// 获取父节点
Node* parent = child->getParent();
// 获取子节点列表
const auto& children = parent->getChildren();
```
### Z轴顺序
```cpp
// 设置Z轴顺序值越大越在上层
node->setZOrder(10);
// 重新排序子节点
parent->reorderChild(child, newZOrder);
```
## 自定义节点
### 继承 Node 创建自定义节点
```cpp
class Player : public Node {
public:
Player() {
setSpatialIndexed(true);
static Ptr<Player> create(Ptr<Texture> texture) {
auto player = makePtr<Player>();
if (player->init(texture)) {
return player;
}
return nullptr;
}
// 加载精灵
auto &resources = Application::instance().resources();
auto texture = resources.loadTexture("assets/player.png");
bool init(Ptr<Texture> texture) {
sprite_ = Sprite::create(texture);
sprite_->setAnchor(0.5f, 0.5f);
addChild(sprite_);
return true;
}
void onUpdate(float dt) override {
Node::onUpdate(dt);
// 移动
Vec2 pos = getPosition();
pos = pos + velocity_ * dt;
setPosition(pos);
// 边界检查
auto &app = Application::instance();
float width = static_cast<float>(app.getConfig().width);
float height = static_cast<float>(app.getConfig().height);
if (pos.x < 0 || pos.x > width) {
velocity_.x = -velocity_.x;
}
if (pos.y < 0 || pos.y > height) {
velocity_.y = -velocity_.y;
}
void update(float dt) override {
// 更新逻辑
velocity_.y += gravity_ * dt;
setPosition(getPosition() + velocity_ * dt);
}
Rect getBoundingBox() const override {
Vec2 pos = getPosition();
return Rect(pos.x - 25.0f, pos.y - 25.0f, 50.0f, 50.0f);
void jump() {
velocity_.y = jumpForce_;
}
private:
Ptr<Sprite> sprite_;
Vec2 velocity_{100.0f, 100.0f};
Vec2 velocity_;
float gravity_ = -980.0f;
float jumpForce_ = 500.0f;
};
// 使用
auto player = Player::create(texture);
scene->addChild(player);
```
## 空间索引(碰撞检测)
### 启用空间索引
```cpp
class CollidableNode : public Node {
public:
CollidableNode() {
// 启用空间索引
setSpatialIndexed(true);
}
// 必须实现 getBoundingBox
Rect getBoundingBox() const override {
Vec2 pos = getPosition();
return Rect(pos.x - 25, pos.y - 25, 50, 50);
}
};
```
### 查询碰撞
```cpp
// 在场景中查询所有碰撞
auto collisions = scene->queryCollisions();
for (const auto& [nodeA, nodeB] : collisions) {
// 处理碰撞
handleCollision(nodeA, nodeB);
}
```
## 完整示例
参考 `examples/push_box/PlayScene.cpp` 中的节点使用:
```cpp
void PlayScene::onEnter() {
Scene::onEnter();
auto& resources = app.resources();
// 加载纹理资源
texWall_ = resources.loadTexture("assets/images/wall.gif");
texBox_ = resources.loadTexture("assets/images/box.gif");
// 创建地图层
mapLayer_ = makePtr<Node>();
addChild(mapLayer_);
// 创建地图元素
for (int y = 0; y < mapHeight; ++y) {
for (int x = 0; x < mapWidth; ++x) {
char cell = map_[y][x];
if (cell == '#') {
auto wall = Sprite::create(texWall_);
wall->setPosition(x * tileSize, y * tileSize);
mapLayer_->addChild(wall);
}
else if (cell == '$') {
auto box = Sprite::create(texBox_);
box->setPosition(x * tileSize, y * tileSize);
mapLayer_->addChild(box);
}
}
}
// 创建UI文本
font28_ = resources.loadFont("assets/font.ttf", 28, true);
levelText_ = Text::create("Level: 1", font28_);
levelText_->setPosition(50, 30);
addChild(levelText_);
}
```
## 下一步
- [04. 资源管理](04_Resource_Management.md)
- [05. 输入处理](05_Input_Handling.md)
- [04. 资源管理](./04_Resource_Management.md) - 深入了解资源加载
- [05. 输入处理](./05_Input_Handling.md) - 学习输入处理
- [06. 碰撞检测](./06_Collision_Detection.md) - 学习碰撞检测系统

View File

@ -1,148 +1,167 @@
# Extra2D API 教程 - 04. 资源管理
# 04. 资源管理
Extra2D 提供了统一的资源管理系统,用于加载和管理游戏中的各种资源。
## 资源管理器
Extra2D 使用资源管理器来统一加载和管理资源。
### 获取资源管理器
通过 `Application::instance().resources()` 访问资源管理器:
```cpp
auto &resources = Application::instance().resources();
auto& resources = Application::instance().resources();
```
## 字体资源
## 支持的资源类型
### 加载字体
| 资源类型 | 加载方法 | 说明 |
|---------|---------|------|
| 纹理 | `loadTexture()` | 图片文件 (PNG, JPG, etc.) |
| 字体 | `loadFont()` | TrueType 字体文件 |
| 音频 | `loadSound()` / `loadMusic()` | 音频文件 |
```cpp
// 加载字体(路径,大小,使用后备字体)
auto font = resources.loadFont("assets/font.ttf", 48, true);
## 纹理加载
if (!font) {
E2D_LOG_ERROR("字体加载失败!");
}
```
### 使用字体
```cpp
void onRender(RenderBackend &renderer) override {
if (font) {
renderer.drawText(*font, "Hello World", Vec2(100.0f, 100.0f),
Colors::White);
}
}
```
## 纹理资源
### 加载纹理
### 基本用法
```cpp
// 加载纹理
auto texture = resources.loadTexture("assets/player.png");
auto texture = resources.loadTexture("assets/images/player.png");
if (!texture) {
E2D_LOG_ERROR("纹理加载失败!");
if (texture) {
// 创建精灵
auto sprite = Sprite::create(texture);
addChild(sprite);
}
```
### 创建精灵
### 纹理缓存
资源管理器会自动缓存已加载的纹理,多次加载同一文件会返回缓存的实例:
```cpp
auto sprite = Sprite::create(texture);
sprite->setPosition(Vec2(640.0f, 360.0f));
addChild(sprite);
// 第一次加载 - 从文件读取
auto tex1 = resources.loadTexture("assets/image.png");
// 第二次加载 - 返回缓存
auto tex2 = resources.loadTexture("assets/image.png");
// tex1 和 tex2 指向同一个纹理对象
```
## 音效资源
## 字体加载
### 加载音效
### 基本用法
```cpp
// 加载音效
auto sound = resources.loadSound("assets/jump.wav");
// 加载字体(指定字号)
auto font24 = resources.loadFont("assets/font.ttf", 24, true);
// 播放音效
sound->play();
// 循环播放
sound->play(true);
// 停止播放
sound->stop();
// 创建文本
auto text = Text::create("Hello World", font24);
addChild(text);
```
## 资源路径解析
### 字体后备
Extra2D 的资源管理器支持多平台路径解析:
### 路径优先级
1. **原始路径**: `assets/font.ttf`
2. **romfs 路径**: `romfs:/assets/font.ttf` (Switch)
3. **sdmc 路径**: `sdmc:/assets/font.ttf` (Switch SD卡)
4. **可执行文件相对路径** (Windows)
### 使用示例
支持设置后备字体,当主字体缺少某些字符时自动使用后备字体:
```cpp
// 所有平台使用相同的路径
auto font = resources.loadFont("assets/font.ttf", 48, true);
auto texture = resources.loadTexture("assets/images/player.png");
auto sound = resources.loadSound("assets/audio/jump.wav");
// 加载主字体和后备字体
auto mainFont = resources.loadFont("assets/main.ttf", 24, true);
auto fallbackFont = resources.loadFont("assets/fallback.ttf", 24, true);
// 设置后备字体
mainFont->setFallback(fallbackFont);
```
## 资源路径
### 路径格式
```cpp
// 相对路径(相对于工作目录)
auto tex = resources.loadTexture("assets/images/player.png");
// Switch 平台使用 romfs
auto tex = resources.loadTexture("romfs:/images/player.png");
// SD 卡路径
auto tex = resources.loadTexture("sdmc:/switch/game/images/player.png");
```
### 路径辅助函数
```cpp
// 获取平台特定的资源路径
std::string path = ResourceManager::getPlatformPath("images/player.png");
// Windows: "assets/images/player.png"
// Switch: "romfs:/images/player.png"
```
## 资源释放
### 自动释放
资源使用智能指针管理,当没有引用时会自动释放:
```cpp
{
auto tex = resources.loadTexture("assets/temp.png");
// 使用纹理...
} // 超出作用域,如果没有其他引用,纹理自动释放
```
### 手动清理缓存
```cpp
// 清理未使用的资源
resources.cleanupUnused();
// 清空所有缓存(谨慎使用)
resources.clearCache();
```
## 完整示例
参考 `examples/push_box/StartScene.cpp`
```cpp
class GameScene : public Scene {
public:
void onEnter() override {
Scene::onEnter();
void StartScene::onEnter() {
Scene::onEnter();
auto &resources = Application::instance().resources();
auto& app = Application::instance();
auto& resources = app.resources();
// 加载字体
titleFont_ = resources.loadFont("assets/font.ttf", 60, true);
infoFont_ = resources.loadFont("assets/font.ttf", 24, true);
// 加载纹理
playerTexture_ = resources.loadTexture("assets/player.png");
enemyTexture_ = resources.loadTexture("assets/enemy.png");
// 创建精灵
player_ = Sprite::create(playerTexture_);
player_->setPosition(Vec2(640.0f, 360.0f));
addChild(player_);
// 加载背景纹理
auto bgTex = resources.loadTexture("assets/images/start.jpg");
if (bgTex) {
auto background = Sprite::create(bgTex);
background->setAnchor(0.0f, 0.0f);
addChild(background);
}
void onRender(RenderBackend &renderer) override {
Scene::onRender(renderer);
// 绘制文字
if (titleFont_) {
renderer.drawText(*titleFont_, "Game Title",
Vec2(50.0f, 50.0f), Colors::White);
}
if (infoFont_) {
std::string fps = "FPS: " + std::to_string(Application::instance().fps());
renderer.drawText(*infoFont_, fps,
Vec2(50.0f, 100.0f), Colors::Yellow);
}
// 加载音效图标纹理
auto soundOn = resources.loadTexture("assets/images/soundon.png");
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
if (soundOn && soundOff) {
soundIcon_ = Sprite::create(g_SoundOpen ? soundOn : soundOff);
addChild(soundIcon_);
}
private:
Ptr<FontAtlas> titleFont_;
Ptr<FontAtlas> infoFont_;
Ptr<Texture> playerTexture_;
Ptr<Texture> enemyTexture_;
Ptr<Sprite> player_;
};
// 加载字体
font_ = resources.loadFont("assets/font.ttf", 28, true);
// 创建按钮...
}
```
## 最佳实践
1. **预加载资源** - 在场景 `onEnter()` 中加载所需资源
2. **检查资源有效性** - 始终检查加载结果是否为 nullptr
3. **复用资源** - 多次使用同一资源时保存指针,避免重复加载
4. **合理设置字号** - 字体加载时会生成对应字号的图集
## 下一步
- [05. 输入处理](05_Input_Handling.md)
- [06. 碰撞检测](06_Collision_Detection.md)
- [05. 输入处理](./05_Input_Handling.md) - 学习输入处理
- [06. 碰撞检测](./06_Collision_Detection.md) - 学习碰撞检测系统

View File

@ -1,216 +1,189 @@
# Extra2D API 教程 - 05. 输入处理
# 05. 输入处理
## 输入系统
Extra2D 提供了统一的输入处理系统,支持手柄、键盘等多种输入设备。
Extra2D 提供统一的输入处理接口,支持键盘和游戏手柄。
## 输入管理器
### 获取输入管理器
通过 `Application::instance().input()` 访问输入管理器:
```cpp
auto &input = Application::instance().input();
auto& input = Application::instance().input();
```
## 游戏手柄输入
## 按键检测
Extra2D 提供了 `GamepadButton``GamepadAxis` 命名空间来映射 SDL 按键。
### 检测按键按下
### 检测方法
```cpp
void onUpdate(float dt) override {
auto &input = Application::instance().input();
// 按键是否按下(持续触发)
if (input.isButtonDown(GamepadButton::A)) {
// 每帧都会触发,只要按键保持按下
}
// 检测按键按下(每帧只触发一次)
if (input.isButtonPressed(GamepadButton::A)) {
// A 键被按下
jump();
}
// 按键是否刚按下(单次触发)
if (input.isButtonPressed(GamepadButton::A)) {
// 只在按下瞬间触发一次
}
if (input.isButtonPressed(GamepadButton::B)) {
// B 键被按下
attack();
}
// 按键是否刚释放
if (input.isButtonReleased(GamepadButton::A)) {
// 只在释放瞬间触发一次
}
```
### 检测按键按住
### 常用按键
```cpp
void onUpdate(float dt) override {
auto &input = Application::instance().input();
// 检测按键按住(每帧都触发)
if (input.isButtonDown(GamepadButton::DPadLeft)) {
// 左方向键按住
moveLeft();
}
if (input.isButtonDown(GamepadButton::DPadRight)) {
// 右方向键按住
moveRight();
}
}
```
### 按键映射表
| Extra2D 枚举 | 对应按键 |
|-------------|----------|
| `GamepadButton::A` | A 键 (Xbox) / × 键 (PlayStation) |
| `GamepadButton::B` | B 键 (Xbox) / ○ 键 (PlayStation) |
| `GamepadButton::X` | X 键 (Xbox) / □ 键 (PlayStation) |
| `GamepadButton::Y` | Y 键 (Xbox) / △ 键 (PlayStation) |
| `GamepadButton::LeftBumper` | 左肩键 (LB/L1) |
| `GamepadButton::RightBumper` | 右肩键 (RB/R1) |
| `GamepadButton::Back` | 返回键 (View/Share) |
| `GamepadButton::Start` | 开始键 (Menu/Options) |
| `GamepadButton::Guide` | 主页键 (Xbox/PS) |
| `GamepadButton::LeftThumb` | 左摇杆按下 (L3) |
| `GamepadButton::RightThumb` | 右摇杆按下 (R3) |
| `GamepadButton::DPadUp` | 方向键上 |
| `GamepadButton::DPadDown` | 方向键下 |
| `GamepadButton::DPadLeft` | 方向键左 |
| `GamepadButton::DPadRight` | 方向键右 |
### PlayStation 风格别名
| Extra2D 枚举 | 对应按键 |
|-------------|----------|
| `GamepadButton::Cross` | A |
| `GamepadButton::Circle` | B |
| `GamepadButton::Square` | X |
| `GamepadButton::Triangle` | Y |
| 按键 | 说明 | Switch 对应 |
|------|------|------------|
| `GamepadButton::A` | A 键 | A 键 |
| `GamepadButton::B` | B 键 | B 键 |
| `GamepadButton::X` | X 键 | X 键 |
| `GamepadButton::Y` | Y 键 | Y 键 |
| `GamepadButton::Start` | 开始键 | + 键 |
| `GamepadButton::Select` | 选择键 | - 键 |
| `GamepadButton::DPadUp` | 方向上 | 方向键上 |
| `GamepadButton::DPadDown` | 方向下 | 方向键下 |
| `GamepadButton::DPadLeft` | 方向左 | 方向键左 |
| `GamepadButton::DPadRight` | 方向右 | 方向键右 |
| `GamepadButton::LeftStick` | 左摇杆按下 | L3 |
| `GamepadButton::RightStick` | 右摇杆按下 | R3 |
| `GamepadButton::LeftShoulder` | 左肩键 | L |
| `GamepadButton::RightShoulder` | 右肩键 | R |
## 摇杆输入
### 获取摇杆值
```cpp
void onUpdate(float dt) override {
auto &input = Application::instance().input();
// 获取左摇杆位置(范围 -1.0 到 1.0
Vec2 leftStick = input.getLeftStick();
// 左摇杆(范围 -1.0 到 1.0
float leftX = input.getAxis(GamepadAxis::LeftX);
float leftY = input.getAxis(GamepadAxis::LeftY);
// 获取右摇杆位置
Vec2 rightStick = input.getRightStick();
// 右摇杆
float rightX = input.getAxis(GamepadAxis::RightX);
float rightY = input.getAxis(GamepadAxis::RightY);
// 使用摇杆值移动
if (std::abs(leftX) > 0.1f || std::abs(leftY) > 0.1f) {
Vec2 velocity(leftX * speed, leftY * speed);
player->setPosition(player->getPosition() + velocity * dt);
}
}
// 应用摇杆输入
float speed = 200.0f;
player->setPosition(player->getPosition() + leftStick * speed * dt);
```
### 摇杆轴映射表
| Extra2D 枚举 | 说明 |
|-------------|------|
| `GamepadAxis::LeftX` | 左摇杆 X 轴 |
| `GamepadAxis::LeftY` | 左摇杆 Y 轴 |
| `GamepadAxis::RightX` | 右摇杆 X 轴 |
| `GamepadAxis::RightY` | 右摇杆 Y 轴 |
| `GamepadAxis::LeftTrigger` | 左扳机 (LT/L2) |
| `GamepadAxis::RightTrigger` | 右扳机 (RT/R2) |
## 键盘输入
### 检测键盘按键
### 摇杆死区
```cpp
void onUpdate(float dt) override {
auto &input = Application::instance().input();
// 检测按键按下
if (input.isKeyPressed(SDLK_SPACE)) {
jump();
}
// 检测按键按住
if (input.isKeyDown(SDLK_LEFT)) {
moveLeft();
}
if (input.isKeyDown(SDLK_RIGHT)) {
moveRight();
}
}
// 设置摇杆死区(默认 0.15
input.setStickDeadZone(0.2f);
```
## 完整示例
### 菜单导航
参考 `examples/push_box/StartScene.cpp`
```cpp
class Player : public Node {
public:
void onUpdate(float dt) override {
Node::onUpdate(dt);
void StartScene::onUpdate(float dt) {
Scene::onUpdate(dt);
auto &input = Application::instance().input();
Vec2 velocity(0.0f, 0.0f);
auto& input = Application::instance().input();
// 方向键移动
if (input.isButtonDown(GamepadButton::DPadLeft)) {
velocity.x = -speed_;
} else if (input.isButtonDown(GamepadButton::DPadRight)) {
velocity.x = speed_;
}
if (input.isButtonDown(GamepadButton::DPadUp)) {
velocity.y = -speed_;
} else if (input.isButtonDown(GamepadButton::DPadDown)) {
velocity.y = speed_;
}
// 摇杆移动(如果方向键没有按下)
if (velocity.x == 0.0f && velocity.y == 0.0f) {
float axisX = input.getAxis(GamepadAxis::LeftX);
float axisY = input.getAxis(GamepadAxis::LeftY);
if (std::abs(axisX) > 0.1f) {
velocity.x = axisX * speed_;
}
if (std::abs(axisY) > 0.1f) {
velocity.y = axisY * speed_;
}
}
// 应用移动
Vec2 pos = getPosition();
pos = pos + velocity * dt;
setPosition(pos);
// 动作键
if (input.isButtonPressed(GamepadButton::A)) {
jump();
}
if (input.isButtonPressed(GamepadButton::B)) {
attack();
}
// 退出游戏
if (input.isButtonPressed(GamepadButton::Start)) {
Application::instance().quit();
}
// 方向键上下切换选择
if (input.isButtonPressed(GamepadButton::DPadUp)) {
selectedIndex_ = (selectedIndex_ - 1 + menuCount_) % menuCount_;
updateMenuColors();
}
else if (input.isButtonPressed(GamepadButton::DPadDown)) {
selectedIndex_ = (selectedIndex_ + 1) % menuCount_;
updateMenuColors();
}
private:
float speed_ = 200.0f;
void jump() {
// 跳跃逻辑
// A键确认
if (input.isButtonPressed(GamepadButton::A)) {
executeMenuItem();
}
void attack() {
// 攻击逻辑
// X键切换音效
if (input.isButtonPressed(GamepadButton::X)) {
g_SoundOpen = !g_SoundOpen;
AudioManager::instance().setEnabled(g_SoundOpen);
updateSoundIcon();
}
};
}
```
### 玩家移动
```cpp
void Player::update(float dt) {
auto& input = Application::instance().input();
Vec2 moveDir;
// 方向键移动
if (input.isButtonDown(GamepadButton::DPadLeft)) {
moveDir.x -= 1;
}
if (input.isButtonDown(GamepadButton::DPadRight)) {
moveDir.x += 1;
}
if (input.isButtonDown(GamepadButton::DPadUp)) {
moveDir.y -= 1;
}
if (input.isButtonDown(GamepadButton::DPadDown)) {
moveDir.y += 1;
}
// 摇杆移动
Vec2 stick = input.getLeftStick();
if (stick.length() > 0.1f) {
moveDir = stick;
}
// 应用移动
if (moveDir.length() > 0) {
moveDir.normalize();
setPosition(getPosition() + moveDir * speed_ * dt);
}
// 跳跃
if (input.isButtonPressed(GamepadButton::A)) {
jump();
}
}
```
## 输入映射
### 自定义按键映射
```cpp
// 定义动作
enum class Action {
Jump,
Attack,
Pause
};
// 映射按键到动作
std::unordered_map<Action, GamepadButton> actionMap = {
{Action::Jump, GamepadButton::A},
{Action::Attack, GamepadButton::B},
{Action::Pause, GamepadButton::Start}
};
// 检查动作
bool isActionPressed(Action action) {
auto& input = Application::instance().input();
return input.isButtonPressed(actionMap[action]);
}
```
## 最佳实践
1. **使用 isButtonPressed 进行菜单操作** - 避免持续触发
2. **使用 isButtonDown 进行移动控制** - 实现流畅移动
3. **支持多种输入方式** - 同时支持方向键和摇杆
4. **添加输入缓冲** - 提升操作手感
## 下一步
- [06. 碰撞检测](06_Collision_Detection.md)
- [07. UI 系统](07_UI_System.md)
- [06. 碰撞检测](./06_Collision_Detection.md) - 学习碰撞检测系统
- [07. UI 系统](./07_UI_System.md) - 学习 UI 控件使用

View File

@ -1,81 +1,90 @@
# Extra2D API 教程 - 06. 碰撞检测
# 06. 碰撞检测
## 空间索引系统
Extra2D 提供了基于空间索引的高效碰撞检测系统,支持四叉树和空间哈希两种策略。
Extra2D 内置了空间索引系统,用于高效地进行碰撞检测。
## 完整示例
### 启用空间索引
参考示例代码:
- `examples/collision_demo/main.cpp` - 基础碰撞检测演示
- `examples/spatial_index_demo/main.cpp` - 空间索引性能演示
## 启用碰撞检测
### 1. 创建可碰撞节点
```cpp
void onEnter() override {
Scene::onEnter();
// 启用空间索引
setSpatialIndexingEnabled(true);
}
```
## 碰撞节点
### 创建可碰撞节点
```cpp
class PhysicsNode : public Node {
class CollidableBox : public Node {
public:
PhysicsNode(float size, const Color &color)
: size_(size), color_(color), isColliding_(false) {
// 启用空间索引(关键!)
CollidableBox(float width, float height, const Color& color)
: width_(width), height_(height), color_(color), isColliding_(false) {
// 启用空间索引 - 这是关键!
setSpatialIndexed(true);
}
// 必须实现 getBoundingBox()
// 必须实现 getBoundingBox 方法
Rect getBoundingBox() const override {
Vec2 pos = getPosition();
return Rect(pos.x - size_ / 2, pos.y - size_ / 2, size_, size_);
return Rect(pos.x - width_ / 2, pos.y - height_ / 2, width_, height_);
}
void setColliding(bool colliding) { isColliding_ = colliding; }
void onRender(RenderBackend &renderer) override {
void onRender(RenderBackend& renderer) override {
Vec2 pos = getPosition();
// 碰撞时变红色
Color fillColor = isColliding_ ? Color(1.0f, 0.2f, 0.2f, 0.9f) : color_;
renderer.fillRect(Rect(pos.x - size_ / 2, pos.y - size_ / 2, size_, size_),
fillColor);
Color fillColor = isColliding_ ? Color(1.0f, 0.2f, 0.2f, 0.8f) : color_;
renderer.fillRect(
Rect(pos.x - width_ / 2, pos.y - height_ / 2, width_, height_),
fillColor
);
// 绘制边框
Color borderColor = isColliding_ ? Color(1.0f, 0.0f, 0.0f, 1.0f)
: Color(1.0f, 1.0f, 1.0f, 0.5f);
renderer.drawRect(
Rect(pos.x - width_ / 2, pos.y - height_ / 2, width_, height_),
borderColor, 2.0f
);
}
private:
float size_;
float width_, height_;
Color color_;
bool isColliding_;
};
```
## 碰撞检测
### 查询所有碰撞
### 2. 执行碰撞检测
```cpp
void performCollisionDetection() {
// 清除之前的碰撞状态
for (auto &node : nodes_) {
node->setColliding(false);
}
class GameScene : public Scene {
public:
void onUpdate(float dt) override {
Scene::onUpdate(dt);
// 查询所有碰撞(使用空间索引)
auto collisions = queryCollisions();
// 标记碰撞的节点
for (const auto &[nodeA, nodeB] : collisions) {
if (auto boxA = dynamic_cast<PhysicsNode *>(nodeA)) {
boxA->setColliding(true);
// 清除之前的碰撞状态
for (auto& box : boxes_) {
box->setColliding(false);
}
if (auto boxB = dynamic_cast<PhysicsNode *>(nodeB)) {
boxB->setColliding(true);
// 使用场景的空间索引查询所有碰撞
auto collisions = queryCollisions();
// 处理碰撞
for (const auto& [nodeA, nodeB] : collisions) {
if (auto boxA = dynamic_cast<CollidableBox*>(nodeA)) {
boxA->setColliding(true);
}
if (auto boxB = dynamic_cast<CollidableBox*>(nodeB)) {
boxB->setColliding(true);
}
}
}
}
private:
std::vector<Ptr<CollidableBox>> boxes_;
};
```
## 空间索引策略
@ -83,141 +92,105 @@ void performCollisionDetection() {
### 切换策略
```cpp
void onEnter() override {
Scene::onEnter();
// 获取空间管理器
auto& spatialManager = getSpatialManager();
// 启用空间索引
setSpatialIndexingEnabled(true);
// 切换到四叉树
spatialManager.setStrategy(SpatialStrategy::QuadTree);
// 设置空间索引策略
auto &spatialManager = getSpatialManager();
spatialManager.setStrategy(SpatialStrategy::QuadTree); // 四叉树
// 或
spatialManager.setStrategy(SpatialStrategy::SpatialHash); // 空间哈希
}
// 切换到空间哈希
spatialManager.setStrategy(SpatialStrategy::SpatialHash);
// 切换策略
void toggleStrategy() {
auto &spatialManager = getSpatialManager();
SpatialStrategy current = spatialManager.getCurrentStrategy();
if (current == SpatialStrategy::QuadTree) {
spatialManager.setStrategy(SpatialStrategy::SpatialHash);
} else {
spatialManager.setStrategy(SpatialStrategy::QuadTree);
}
}
// 获取当前策略名称
const char* name = spatialManager.getStrategyName();
```
### 策略对比
| 策略 | 适用场景 | 特点 |
|------|----------|------|
| QuadTree | 节点分布不均匀 | 适合稀疏场景 |
| SpatialHash | 节点分布均匀 | 适合密集场景 |
|------|---------|------|
| QuadTree | 节点分布不均匀 | 分层划分,适合稀疏分布 |
| SpatialHash | 节点分布均匀 | 均匀网格,适合密集分布 |
## 完整示例
## 性能演示
`examples/spatial_index_demo/main.cpp` 展示了空间索引的性能优势:
```cpp
class CollisionScene : public Scene {
class SpatialIndexDemoScene : public Scene {
public:
void onEnter() override {
Scene::onEnter();
// 启用空间索引
setSpatialIndexingEnabled(true);
// 创建碰撞节点
// 创建100个碰撞节点
createNodes(100);
E2D_LOG_INFO("创建了 {} 个碰撞节点", nodes_.size());
E2D_LOG_INFO("空间索引已启用: {}", isSpatialIndexingEnabled());
}
void onUpdate(float dt) override {
Scene::onUpdate(dt);
// 更新节点位置
for (auto &node : nodes_) {
node->update(dt);
// 更新所有节点位置
for (auto& node : nodes_) {
node->update(dt, screenWidth_, screenHeight_);
}
// 行碰撞检测
// 使用空间索引进行碰撞检测
performCollisionDetection();
}
void onRender(RenderBackend &renderer) override {
Scene::onRender(renderer);
// 绘制碰撞统计
std::string text = "Collisions: " + std::to_string(collisionCount_);
// ...
// 按 X 键切换索引策略
auto& input = Application::instance().input();
if (input.isButtonPressed(GamepadButton::X)) {
toggleSpatialStrategy();
}
}
private:
std::vector<Ptr<PhysicsNode>> nodes_;
size_t collisionCount_ = 0;
void createNodes(size_t count) {
for (size_t i = 0; i < count; ++i) {
auto node = makePtr<PhysicsNode>(20.0f, Color(0.5f, 0.5f, 0.9f, 0.7f));
node->setPosition(randomPosition());
addChild(node);
nodes_.push_back(node);
}
}
void performCollisionDetection() {
// 清除碰撞状态
for (auto &node : nodes_) {
// 清除之前的碰撞状态
for (auto& node : nodes_) {
node->setColliding(false);
}
// 查询碰撞
// 使用引擎自带的空间索引进行碰撞检测
auto collisions = queryCollisions();
collisionCount_ = collisions.size();
// 标记碰撞节点
for (const auto &[nodeA, nodeB] : collisions) {
if (auto boxA = dynamic_cast<PhysicsNode *>(nodeA)) {
// 标记碰撞的节点
for (const auto& [nodeA, nodeB] : collisions) {
if (auto boxA = dynamic_cast<PhysicsNode*>(nodeA)) {
boxA->setColliding(true);
}
if (auto boxB = dynamic_cast<PhysicsNode *>(nodeB)) {
if (auto boxB = dynamic_cast<PhysicsNode*>(nodeB)) {
boxB->setColliding(true);
}
}
}
};
```
## 注意事项
void toggleSpatialStrategy() {
auto& spatialManager = getSpatialManager();
SpatialStrategy currentStrategy = spatialManager.getCurrentStrategy();
### 必须调用 Scene::onEnter()
```cpp
void onEnter() override {
Scene::onEnter(); // 必须调用!
// 否则子节点无法注册到空间索引
setSpatialIndexingEnabled(true);
}
```
### 必须实现 getBoundingBox()
```cpp
class MyNode : public Node {
public:
MyNode() {
setSpatialIndexed(true);
}
// 必须实现!
Rect getBoundingBox() const override {
Vec2 pos = getPosition();
return Rect(pos.x - width_/2, pos.y - height_/2, width_, height_);
if (currentStrategy == SpatialStrategy::QuadTree) {
spatialManager.setStrategy(SpatialStrategy::SpatialHash);
E2D_LOG_INFO("切换到空间哈希策略");
} else {
spatialManager.setStrategy(SpatialStrategy::QuadTree);
E2D_LOG_INFO("切换到四叉树策略");
}
}
};
```
## 关键要点
1. **必须调用 `setSpatialIndexed(true)`** - 启用节点的空间索引
2. **必须实现 `getBoundingBox()`** - 返回准确的边界框
3. **在 `onEnter()` 中调用 `Scene::onEnter()`** - 确保节点正确注册到空间索引
4. **使用 `queryCollisions()`** - 自动利用空间索引优化检测
## 下一步
- [07. UI 系统](07_UI_System.md)
- [08. 音频系统](08_Audio_System.md)
- [07. UI 系统](./07_UI_System.md) - 学习 UI 控件使用
- [08. 音频系统](./08_Audio_System.md) - 学习音频播放

View File

@ -1,337 +1,606 @@
# Extra2D API 教程 - 07. UI 系统
# 07. UI 系统
## 按钮 (Button)
Extra2D 提供了一套完整的 UI 系统,支持按钮、文本、标签、复选框、单选按钮、滑块、进度条等常用控件。
Extra2D 提供 Button 组件用于创建交互式按钮。
## UI 控件类型
```
Widget (UI控件基类)
├── Button (按钮)
├── Text (文本)
├── Label (标签)
├── CheckBox (复选框)
├── RadioButton (单选按钮)
├── Slider (滑块)
└── ProgressBar (进度条)
```
## 坐标空间
UI 控件支持三种坐标空间:
```cpp
enum class CoordinateSpace {
World, // 世界空间 - 随相机移动(默认)
Screen, // 屏幕空间 - 固定位置,不随相机移动
Camera, // 相机空间 - 相对于相机位置的偏移
};
```
设置坐标空间:
```cpp
// 屏幕空间UI 常用)
button->setCoordinateSpace(CoordinateSpace::Screen);
button->setScreenPosition(100.0f, 50.0f);
// 或使用链式调用
button->withCoordinateSpace(CoordinateSpace::Screen)
->withScreenPosition(100.0f, 50.0f);
```
## 通用链式调用方法
所有 UI 组件都支持以下链式调用方法:
```cpp
widget->withPosition(x, y) // 设置位置
->withAnchor(x, y) // 设置锚点 (0-1)
->withCoordinateSpace(space) // 设置坐标空间
->withScreenPosition(x, y) // 设置屏幕位置
->withCameraOffset(x, y); // 设置相机偏移
```
## 按钮Button
### 创建按钮
```cpp
// 创建按钮
auto& resources = Application::instance().resources();
auto font = resources.loadFont("assets/font.ttf", 24);
// 方式1简单创建
auto button = Button::create();
button->setText("点击我");
button->setFont(font);
button->setPosition(Vec2(400, 300));
// 设置位置
button->setPosition(Vec2(640.0f, 360.0f));
// 方式2链式调用创建
auto button = Button::create()
->withText("点击我")
->withFont(font)
->withPosition(400, 300)
->withSize(200, 60)
->withTextColor(Colors::White)
->withBackgroundColor(Colors::Blue, Colors::Green, Colors::Red)
->withBorder(Colors::White, 2.0f);
// 设置锚点(中心)
button->setAnchor(0.5f, 0.5f);
// 设置点击回调
button->setOnClick([]() {
E2D_LOG_INFO("按钮被点击!");
});
// 添加到场景
addChild(button);
```
### 设置按钮文本
### 按钮属性设置
```cpp
// 加载字体
auto font = resources.loadFont("assets/font.ttf", 28, true);
// 设置按钮字体和文本
// 文本和字体
button->setText("新文本");
button->setFont(font);
button->setText("点击我");
button->setTextColor(Colors::Black);
```
button->setTextColor(Colors::White);
### 设置按钮样式
// 尺寸和内边距
button->setCustomSize(200.0f, 60.0f);
button->setPadding(Vec2(10.0f, 5.0f));
```cpp
// 设置背景颜色(普通、悬停、按下)
// 背景颜色(正常、悬停、按下三种状态)
button->setBackgroundColor(
Colors::White, // 普通状态
Colors::LightGray, // 悬停状态
Colors::Gray // 按下状态
Colors::Blue, // 正常状态
Colors::Green, // 悬停状态
Colors::Red // 按下状态
);
// 设置边框
button->setBorder(Colors::Black, 2.0f);
// 边框
button->setBorder(Colors::White, 2.0f);
// 设置内边距
button->setPadding(Vec2(20.0f, 10.0f));
// 圆角
button->setRoundedCornersEnabled(true);
button->setCornerRadius(8.0f);
// 设置固定大小
button->setCustomSize(200.0f, 50.0f);
// 图片背景
button->setBackgroundImage(normalTex, hoverTex, pressedTex);
button->setBackgroundImageScaleMode(ImageScaleMode::ScaleFit);
// 悬停光标
button->setHoverCursor(CursorShape::Hand);
```
### 透明按钮
### 图片缩放模式
```cpp
// 创建透明按钮(仅文本可点击)
auto button = Button::create();
button->setFont(font);
button->setText("菜单项");
button->setTextColor(Colors::Black);
enum class ImageScaleMode {
Original, // 使用原图大小
Stretch, // 拉伸填充
ScaleFit, // 等比缩放,保持完整显示
ScaleFill // 等比缩放,填充整个区域(可能裁剪)
};
```
// 透明背景
button->setBackgroundColor(
Colors::Transparent,
Colors::Transparent,
Colors::Transparent
### 透明按钮(菜单项)
```cpp
// 创建纯文本按钮(透明背景,用于菜单)
auto menuBtn = Button::create();
menuBtn->setFont(font);
menuBtn->setText("新游戏");
menuBtn->setTextColor(Colors::Black);
menuBtn->setBackgroundColor(
Colors::Transparent, // 正常
Colors::Transparent, // 悬停
Colors::Transparent // 按下
);
// 无边框
button->setBorder(Colors::Transparent, 0.0f);
// 无内边距
button->setPadding(Vec2(0.0f, 0.0f));
menuBtn->setBorder(Colors::Transparent, 0.0f);
menuBtn->setPadding(Vec2(0.0f, 0.0f));
menuBtn->setCustomSize(200.0f, 40.0f);
menuBtn->setAnchor(0.5f, 0.5f); // 中心锚点
menuBtn->setPosition(centerX, centerY);
addChild(menuBtn);
```
## 菜单系统
## 文本Text
### 创建菜单
### 创建文本
```cpp
class MenuScene : public Scene {
// 方式1简单创建
auto text = Text::create("Hello World", font);
text->setPosition(Vec2(100, 50));
// 方式2链式调用
auto text = Text::create("Hello World")
->withFont(font)
->withPosition(100, 50)
->withTextColor(Colors::White)
->withFontSize(24)
->withAlignment(Alignment::Center);
addChild(text);
```
### 文本属性设置
```cpp
// 设置文本
text->setText("新文本");
text->setFormat("Score: %d", score); // 格式化文本
// 字体和颜色
text->setFont(font);
text->setTextColor(Colors::White);
text->setFontSize(24);
// 对齐方式
text->setAlignment(Alignment::Left); // 水平:左/中/右
text->setVerticalAlignment(VerticalAlignment::Middle); // 垂直:上/中/下
// 获取文本尺寸
Size size = text->getTextSize();
float lineHeight = text->getLineHeight();
```
### 对齐方式枚举
```cpp
enum class Alignment { Left, Center, Right };
enum class VerticalAlignment { Top, Middle, Bottom };
```
## 标签Label
Label 是功能更丰富的文本组件,支持阴影、描边、多行文本。
### 创建标签
```cpp
// 创建标签
auto label = Label::create("玩家名称", font);
label->setPosition(Vec2(100, 50));
label->setTextColor(Colors::White);
// 链式调用
auto label = Label::create("玩家名称")
->withFont(font)
->withPosition(100, 50)
->withTextColor(Colors::White)
->withFontSize(24);
addChild(label);
```
### 标签特效
```cpp
// 阴影
label->setShadowEnabled(true);
label->setShadowColor(Colors::Black);
label->setShadowOffset(Vec2(2.0f, 2.0f));
// 描边
label->setOutlineEnabled(true);
label->setOutlineColor(Colors::Black);
label->setOutlineWidth(1.0f);
// 多行文本
label->setMultiLine(true);
label->setLineSpacing(1.2f);
label->setMaxWidth(300.0f); // 自动换行宽度
// 对齐方式
label->setHorizontalAlign(HorizontalAlign::Center);
label->setVerticalAlign(VerticalAlign::Middle);
```
## 复选框CheckBox
### 创建复选框
```cpp
// 方式1简单创建
auto checkBox = CheckBox::create();
checkBox->setPosition(Vec2(100, 200));
checkBox->setChecked(true);
// 方式2带标签
auto checkBox = CheckBox::create("启用音效");
checkBox->setPosition(Vec2(100, 200));
// 方式3链式调用
auto checkBox = CheckBox::create("启用音效")
->withPosition(100, 200)
->withFont(font)
->withTextColor(Colors::White);
// 状态改变回调
checkBox->setOnStateChange([](bool checked) {
E2D_LOG_INFO("复选框状态: {}", checked);
});
addChild(checkBox);
```
### 复选框属性
```cpp
// 状态
checkBox->setChecked(true);
checkBox->toggle();
bool isChecked = checkBox->isChecked();
// 标签
checkBox->setLabel("新标签");
checkBox->setFont(font);
checkBox->setTextColor(Colors::White);
// 外观
checkBox->setBoxSize(20.0f); // 复选框大小
checkBox->setSpacing(10.0f); // 复选框与标签间距
checkBox->setCheckedColor(Colors::Green);
checkBox->setUncheckedColor(Colors::Gray);
checkBox->setCheckMarkColor(Colors::White);
```
## 单选按钮RadioButton
### 创建单选按钮
```cpp
// 创建单选按钮
auto radio1 = RadioButton::create("选项 A");
radio1->setPosition(Vec2(100, 300));
radio1->setSelected(true);
auto radio2 = RadioButton::create("选项 B");
radio2->setPosition(Vec2(100, 340));
auto radio3 = RadioButton::create("选项 C");
radio3->setPosition(Vec2(100, 380));
// 添加到组(互斥选择)
radio1->setGroupId(1);
radio2->setGroupId(1);
radio3->setGroupId(1);
// 或使用 RadioButtonGroup
auto group = std::make_shared<RadioButtonGroup>();
group->addButton(radio1.get());
group->addButton(radio2.get());
group->addButton(radio3.get());
// 选择改变回调
group->setOnSelectionChange([](RadioButton* selected) {
if (selected) {
E2D_LOG_INFO("选中: {}", selected->getLabel());
}
});
addChild(radio1);
addChild(radio2);
addChild(radio3);
```
### 单选按钮属性
```cpp
// 状态
radio->setSelected(true);
bool isSelected = radio->isSelected();
// 标签
radio->setLabel("新标签");
radio->setFont(font);
radio->setTextColor(Colors::White);
// 外观
radio->setCircleSize(16.0f); // 圆形大小
radio->setSpacing(10.0f); // 圆形与标签间距
radio->setSelectedColor(Colors::Green);
radio->setUnselectedColor(Colors::Gray);
radio->setDotColor(Colors::White);
// 分组
radio->setGroupId(1); // 相同 groupId 的按钮互斥
```
## 滑块Slider
### 创建滑块
```cpp
// 方式1简单创建
auto slider = Slider::create();
slider->setPosition(Vec2(200, 400));
slider->setRange(0.0f, 100.0f);
slider->setValue(50.0f);
// 方式2带初始值创建
auto slider = Slider::create(0.0f, 100.0f, 50.0f);
// 方式3链式调用
auto slider = Slider::create()
->withPosition(200, 400)
->withSize(200, 20)
->withMinValue(0.0f)
->withMaxValue(100.0f)
->withValue(50.0f);
// 值改变回调
slider->setOnValueChange([](float value) {
E2D_LOG_INFO("滑块值: {}", value);
});
// 拖动开始/结束回调
slider->setOnDragStart([]() {
E2D_LOG_INFO("开始拖动");
});
slider->setOnDragEnd([]() {
E2D_LOG_INFO("结束拖动");
});
addChild(slider);
```
### 滑块属性
```cpp
// 值和范围
slider->setRange(0.0f, 100.0f);
slider->setValue(50.0f);
slider->setStep(5.0f); // 步进值0表示无步进
float value = slider->getValue();
float min = slider->getMin();
float max = slider->getMax();
// 方向
slider->setVertical(false); // false=水平, true=垂直
// 外观
slider->setTrackSize(4.0f); // 轨道粗细
slider->setThumbSize(16.0f); // 滑块大小
// 颜色
slider->setTrackColor(Colors::Gray);
slider->setFillColor(Colors::Green);
slider->setThumbColor(Colors::White);
slider->setThumbHoverColor(Colors::Yellow);
slider->setThumbPressedColor(Colors::Orange);
// 显示选项
slider->setShowThumb(true); // 显示滑块
slider->setShowFill(true); // 显示填充
slider->setTextEnabled(true); // 显示数值文本
slider->setTextFormat("{:.0f}%"); // 数值格式
slider->setFont(font);
slider->setTextColor(Colors::White);
```
## 进度条ProgressBar
### 创建进度条
```cpp
// 方式1简单创建
auto progressBar = ProgressBar::create();
progressBar->setPosition(Vec2(200, 500));
progressBar->setSize(300.0f, 30.0f);
progressBar->setValue(75.0f); // 75%
// 方式2带范围创建
auto progressBar = ProgressBar::create(0.0f, 100.0f, 75.0f);
// 方式3链式调用
auto progressBar = ProgressBar::create()
->withPosition(200, 500)
->withSize(300, 30)
->withProgress(0.75f); // 0-1 的进度值
addChild(progressBar);
```
### 进度条属性
```cpp
// 值和范围
progressBar->setRange(0.0f, 100.0f);
progressBar->setValue(75.0f);
float value = progressBar->getValue();
float percent = progressBar->getPercent(); // 0.0-1.0
// 方向
progressBar->setDirection(Direction::LeftToRight);
// Direction::LeftToRight, RightToLeft, BottomToTop, TopToBottom
// 颜色
progressBar->setBackgroundColor(Colors::DarkGray);
progressBar->setFillColor(Colors::Green);
// 渐变填充
progressBar->setGradientFillEnabled(true);
progressBar->setFillColorEnd(Colors::LightGreen);
// 分段颜色(根据进度显示不同颜色)
progressBar->setSegmentedColorsEnabled(true);
progressBar->addColorSegment(0.3f, Colors::Red); // <30% 红色
progressBar->addColorSegment(0.7f, Colors::Yellow); // 30-70% 黄色
// >70% 使用默认填充色(绿色)
// 圆角
progressBar->setRoundedCornersEnabled(true);
progressBar->setCornerRadius(8.0f);
// 边框
progressBar->setBorderEnabled(true);
progressBar->setBorderColor(Colors::White);
progressBar->setBorderWidth(2.0f);
progressBar->setPadding(2.0f);
// 文本
progressBar->setTextEnabled(true);
progressBar->setTextFormat("{:.0f}%");
progressBar->setFont(font);
progressBar->setTextColor(Colors::White);
// 动画效果
progressBar->setAnimatedChangeEnabled(true);
progressBar->setAnimationSpeed(5.0f);
// 延迟显示效果
progressBar->setDelayedDisplayEnabled(true);
progressBar->setDelayTime(0.5f);
progressBar->setDelayedFillColor(Colors::Yellow);
// 条纹效果
progressBar->setStripedEnabled(true);
progressBar->setStripeColor(Colors::White);
progressBar->setStripeSpeed(1.0f);
```
## 完整示例:设置场景
```cpp
class SettingsScene : public Scene {
public:
void onEnter() override {
Scene::onEnter();
auto &resources = Application::instance().resources();
font_ = resources.loadFont("assets/font.ttf", 28, true);
auto& resources = Application::instance().resources();
font_ = resources.loadFont("assets/font.ttf", 24);
float centerX = 640.0f;
float startY = 300.0f;
// 标题
auto title = Text::create("设置", font_);
title->setPosition(Vec2(400, 100));
title->setAlignment(Alignment::Center);
addChild(title);
// 创建菜单按钮
createMenuButton("开始游戏", centerX, startY, 0);
createMenuButton("继续游戏", centerX, startY + 50.0f, 1);
createMenuButton("退出", centerX, startY + 100.0f, 2);
// 音效开关
auto soundLabel = Label::create("音效", font_);
soundLabel->setPosition(Vec2(200, 200));
addChild(soundLabel);
menuCount_ = 3;
selectedIndex_ = 0;
updateMenuColors();
}
soundCheck_ = CheckBox::create();
soundCheck_->setPosition(Vec2(350, 200));
soundCheck_->setChecked(true);
soundCheck_->setOnStateChange([this](bool checked) {
E2D_LOG_INFO("音效: {}", checked ? "开启" : "关闭");
});
addChild(soundCheck_);
void onUpdate(float dt) override {
Scene::onUpdate(dt);
// 音量滑块
auto volumeLabel = Label::create("音量", font_);
volumeLabel->setPosition(Vec2(200, 280));
addChild(volumeLabel);
auto &input = Application::instance().input();
volumeSlider_ = Slider::create(0.0f, 1.0f, 0.8f);
volumeSlider_->setPosition(Vec2(350, 280));
volumeSlider_->setSize(200, 20);
volumeSlider_->setOnValueChange([](float value) {
E2D_LOG_INFO("音量: {:.0f}%", value * 100);
});
addChild(volumeSlider_);
// 方向键切换选择
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_UP)) {
selectedIndex_ = (selectedIndex_ - 1 + menuCount_) % menuCount_;
updateMenuColors();
} else if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_DOWN)) {
selectedIndex_ = (selectedIndex_ + 1) % menuCount_;
updateMenuColors();
}
// 难度选择
auto difficultyLabel = Label::create("难度", font_);
difficultyLabel->setPosition(Vec2(200, 360));
addChild(difficultyLabel);
// A键确认
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_A)) {
executeMenuItem();
}
auto easyRadio = RadioButton::create("简单");
easyRadio->setPosition(Vec2(350, 360));
easyRadio->setSelected(true);
easyRadio->setGroupId(1);
auto normalRadio = RadioButton::create("普通");
normalRadio->setPosition(Vec2(450, 360));
normalRadio->setGroupId(1);
auto hardRadio = RadioButton::create("困难");
hardRadio->setPosition(Vec2(550, 360));
hardRadio->setGroupId(1);
addChild(easyRadio);
addChild(normalRadio);
addChild(hardRadio);
// 返回按钮
auto backBtn = Button::create("返回", font_);
backBtn->setPosition(Vec2(400, 500));
backBtn->setCustomSize(150.0f, 50.0f);
backBtn->setBackgroundColor(
Colors::Blue,
Colors::LightBlue,
Colors::DarkBlue
);
backBtn->setOnClick([]() {
Application::instance().scenes().popScene();
});
addChild(backBtn);
}
private:
Ptr<FontAtlas> font_;
std::vector<Ptr<Button>> buttons_;
int selectedIndex_ = 0;
int menuCount_ = 0;
void createMenuButton(const std::string &text, float x, float y, int index) {
auto button = Button::create();
button->setFont(font_);
button->setText(text);
button->setTextColor(Colors::Black);
button->setBackgroundColor(
Colors::Transparent,
Colors::Transparent,
Colors::Transparent
);
button->setBorder(Colors::Transparent, 0.0f);
button->setPadding(Vec2(0.0f, 0.0f));
button->setCustomSize(200.0f, 40.0f);
button->setAnchor(0.5f, 0.5f);
button->setPosition(x, y);
addChild(button);
buttons_.push_back(button);
}
void updateMenuColors() {
for (int i = 0; i < buttons_.size(); ++i) {
if (buttons_[i]) {
buttons_[i]->setTextColor(
i == selectedIndex_ ? Colors::Red : Colors::Black
);
}
}
}
void executeMenuItem() {
switch (selectedIndex_) {
case 0: startGame(); break;
case 1: continueGame(); break;
case 2: exitGame(); break;
}
}
void startGame() {
// 切换到游戏场景
}
void continueGame() {
// 继续游戏
}
void exitGame() {
Application::instance().quit();
}
Ptr<CheckBox> soundCheck_;
Ptr<Slider> volumeSlider_;
};
```
## 绘制文字
## 最佳实践
### 基本文字绘制
```cpp
void onRender(RenderBackend &renderer) override {
Scene::onRender(renderer);
if (font_) {
// 绘制文字
renderer.drawText(*font_, "Hello World",
Vec2(100.0f, 100.0f), Colors::White);
// 绘制带颜色的文字
renderer.drawText(*font_, "红色文字",
Vec2(100.0f, 150.0f), Colors::Red);
}
}
```
### 格式化文字
```cpp
void onRender(RenderBackend &renderer) override {
Scene::onRender(renderer);
if (infoFont_) {
auto &app = Application::instance();
// 绘制 FPS
std::stringstream ss;
ss << "FPS: " << app.fps();
renderer.drawText(*infoFont_, ss.str(),
Vec2(50.0f, 50.0f), Colors::Yellow);
// 绘制节点数量
ss.str("");
ss << "Nodes: " << nodes_.size();
renderer.drawText(*infoFont_, ss.str(),
Vec2(50.0f, 80.0f), Colors::White);
}
}
```
## 完整示例
```cpp
class StartScene : public Scene {
public:
void onEnter() override {
Scene::onEnter();
auto &app = Application::instance();
auto &resources = app.resources();
// 加载背景
auto bgTex = resources.loadTexture("assets/background.jpg");
if (bgTex) {
auto bg = Sprite::create(bgTex);
bg->setAnchor(0.0f, 0.0f);
addChild(bg);
}
// 加载字体
font_ = resources.loadFont("assets/font.ttf", 32, true);
float centerX = app.getConfig().width / 2.0f;
// 创建标题
titleBtn_ = Button::create();
titleBtn_->setFont(font_);
titleBtn_->setText("游戏标题");
titleBtn_->setTextColor(Colors::Gold);
titleBtn_->setBackgroundColor(
Colors::Transparent,
Colors::Transparent,
Colors::Transparent
);
titleBtn_->setBorder(Colors::Transparent, 0.0f);
titleBtn_->setAnchor(0.5f, 0.5f);
titleBtn_->setPosition(centerX, 200.0f);
addChild(titleBtn_);
// 创建菜单按钮
createMenuButton("新游戏", centerX, 350.0f, 0);
createMenuButton("继续", centerX, 400.0f, 1);
createMenuButton("退出", centerX, 450.0f, 2);
menuCount_ = 3;
updateMenuColors();
}
void onUpdate(float dt) override {
Scene::onUpdate(dt);
auto &input = Application::instance().input();
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_UP)) {
selectedIndex_ = (selectedIndex_ - 1 + menuCount_) % menuCount_;
updateMenuColors();
} else if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_DOWN)) {
selectedIndex_ = (selectedIndex_ + 1) % menuCount_;
updateMenuColors();
}
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_A)) {
executeMenuItem();
}
}
private:
Ptr<FontAtlas> font_;
Ptr<Button> titleBtn_;
std::vector<Ptr<Button>> menuButtons_;
int selectedIndex_ = 0;
int menuCount_ = 0;
void createMenuButton(const std::string &text, float x, float y, int index) {
auto btn = Button::create();
btn->setFont(font_);
btn->setText(text);
btn->setTextColor(Colors::White);
btn->setBackgroundColor(
Colors::Transparent,
Colors::Transparent,
Colors::Transparent
);
btn->setBorder(Colors::Transparent, 0.0f);
btn->setAnchor(0.5f, 0.5f);
btn->setPosition(x, y);
addChild(btn);
menuButtons_.push_back(btn);
}
void updateMenuColors() {
for (int i = 0; i < menuButtons_.size(); ++i) {
if (menuButtons_[i]) {
menuButtons_[i]->setTextColor(
i == selectedIndex_ ? Colors::Red : Colors::White
);
}
}
}
void executeMenuItem() {
switch (selectedIndex_) {
case 0: /* 新游戏 */ break;
case 1: /* 继续 */ break;
case 2: Application::instance().quit(); break;
}
}
};
```
1. **使用屏幕空间** - UI 控件通常使用 `CoordinateSpace::Screen` 固定在屏幕上
2. **使用链式调用** - 创建控件时优先使用链式调用,代码更简洁
3. **设置合适的锚点** - 使用锚点0.5, 0.5)让控件中心对齐,方便布局
4. **复用字体资源** - 避免重复加载相同字体
5. **使用回调函数** - 使用 `setOnClick`、`setOnValueChange` 等回调响应用户操作
## 下一步
- [08. 音频系统](08_Audio_System.md)
- [08. 音频系统](./08_Audio_System.md) - 学习音频播放

View File

@ -1,323 +1,279 @@
# Extra2D API 教程 - 08. 音频系统
# 08. 音频系统
## 音频系统概述
Extra2D 提供了基于 SDL2_mixer 的音频播放系统,支持音效播放。
Extra2D 使用 SDL2_mixer 作为音频后端,支持 WAV、MP3、OGG 等格式。
## 音频引擎
## 音效播放
### 加载和播放音效
通过 `Application::instance().audio()` 访问音频引擎:
```cpp
// 获取资源管理器
auto &resources = Application::instance().resources();
auto& audio = Application::instance().audio();
```
## 播放音效
### 基本用法
```cpp
// 加载音效
auto jumpSound = resources.loadSound("assets/jump.wav");
auto attackSound = resources::loadSound("assets/attack.ogg");
auto sound = audio.loadSound("assets/audio/jump.wav");
// 播放音效(一次)
jumpSound->play();
// 播放音效
sound->play();
// 设置音量 (0.0 - 1.0)
sound->setVolume(0.8f);
```
### 音效控制
```cpp
// 停止播放
sound->stop();
// 暂停
sound->pause();
// 恢复
sound->resume();
// 循环播放
backgroundMusic->play(true);
sound->setLooping(true);
// 停止播放
jumpSound->stop();
// 检查播放状态
bool playing = sound->isPlaying();
bool paused = sound->isPaused();
```
## 音频控制器
### 创建音频控制器节点
### 音调和播放位置
```cpp
class AudioController : public Node {
public:
static Ptr<AudioController> create() {
return makePtr<AudioController>();
}
// 设置音调(当前实现不支持)
sound->setPitch(1.0f);
void onEnter() override {
Node::onEnter();
// 获取/设置播放位置(当前实现不支持)
float cursor = sound->getCursor();
sound->setCursor(0.0f);
auto &resources = Application::instance().resources();
// 加载音效
sounds_["jump"] = resources.loadSound("assets/jump.wav");
sounds_["attack"] = resources.loadSound("assets/attack.wav");
sounds_["bgm"] = resources.loadSound("assets/bgm.mp3");
// 播放背景音乐
if (sounds_["bgm"]) {
sounds_["bgm"]->play(true);
}
}
void playSound(const std::string &name) {
auto it = sounds_.find(name);
if (it != sounds_.end() && it->second) {
it->second->play();
}
}
void setEnabled(bool enabled) {
enabled_ = enabled;
if (!enabled) {
// 停止所有音效
for (auto &[name, sound] : sounds_) {
if (sound) {
sound->stop();
}
}
}
}
private:
std::unordered_map<std::string, Ptr<Sound>> sounds_;
bool enabled_ = true;
};
// 获取音频时长(当前实现不支持)
float duration = sound->getDuration();
```
### 在场景中使用
## 全局音量控制
```cpp
class GameScene : public Scene {
public:
void onEnter() override {
Scene::onEnter();
// 设置主音量
audio.setMasterVolume(0.8f);
// 创建音频控制器
auto audio = AudioController::create();
audio->setName("audio_controller");
addChild(audio);
setAudioController(audio);
}
// 获取主音量
float volume = audio.getMasterVolume();
```
void playJumpSound() {
if (auto audio = getAudioController()) {
audio->playSound("jump");
}
}
## 全局播放控制
void playAttackSound() {
if (auto audio = getAudioController()) {
audio->playSound("attack");
}
}
```cpp
// 暂停所有音效
audio.pauseAll();
void toggleSound() {
if (auto audio = getAudioController()) {
audio->setEnabled(!audio->isEnabled());
}
}
// 恢复所有音效
audio.resumeAll();
private:
Ptr<AudioController> audioController_;
// 停止所有音效
audio.stopAll();
void setAudioController(Ptr<AudioController> controller) {
audioController_ = controller;
}
// 卸载指定音效
audio.unloadSound("jump");
AudioController* getAudioController() const {
return audioController_.get();
}
};
// 卸载所有音效
audio.unloadAllSounds();
```
## 完整示例
```cpp
// AudioController.h
// audio_manager.h
#pragma once
#include <extra2d/extra2d.h>
#include <unordered_map>
namespace game {
namespace pushbox {
class AudioController : public extra2d::Node {
class AudioManager {
public:
static extra2d::Ptr<AudioController> create();
static AudioManager& instance() {
static AudioManager instance;
return instance;
}
void onEnter() override;
void playSound(const std::string &name);
void init();
void setEnabled(bool enabled);
bool isEnabled() const { return enabled_; }
void playMoveSound();
void playBoxMoveSound();
void playWinSound();
private:
std::unordered_map<std::string, extra2d::Ptr<extra2d::Sound>> sounds_;
AudioManager() = default;
bool enabled_ = true;
extra2d::Ptr<extra2d::Sound> moveSound_;
extra2d::Ptr<extra2d::Sound> boxMoveSound_;
extra2d::Ptr<extra2d::Sound> winSound_;
};
} // namespace game
// AudioController.cpp
#include "AudioController.h"
using namespace extra2d;
Ptr<AudioController> AudioController::create() {
return makePtr<AudioController>();
}
void AudioController::onEnter() {
Node::onEnter();
auto &resources = Application::instance().resources();
// 加载所有音效
sounds_["jump"] = resources.loadSound("assets/audio/jump.wav");
sounds_["attack"] = resources.loadSound("assets/audio/attack.wav");
sounds_["hit"] = resources.loadSound("assets/audio/hit.wav");
sounds_["bgm"] = resources.loadSound("assets/audio/background.mp3");
// 播放背景音乐
if (sounds_["bgm"]) {
sounds_["bgm"]->play(true);
}
}
void AudioController::playSound(const std::string &name) {
if (!enabled_) return;
auto it = sounds_.find(name);
if (it != sounds_.end() && it->second) {
it->second->play();
}
}
void AudioController::setEnabled(bool enabled) {
enabled_ = enabled;
if (!enabled) {
// 停止所有音效
for (auto &[name, sound] : sounds_) {
if (sound) {
sound->stop();
}
}
} else {
// 重新播放背景音乐
auto it = sounds_.find("bgm");
if (it != sounds_.end() && it->second) {
it->second->play(true);
}
}
}
// GameScene.cpp
#include "AudioController.h"
class GameScene : public Scene {
public:
void onEnter() override {
Scene::onEnter();
// 创建音频控制器
auto audio = game::AudioController::create();
audio->setName("audio_controller");
addChild(audio);
audioController_ = audio;
}
void onUpdate(float dt) override {
Scene::onUpdate(dt);
auto &input = Application::instance().input();
// A键跳跃
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_A)) {
jump();
}
// B键攻击
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_B)) {
attack();
}
// X键切换音效
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_X)) {
toggleSound();
}
}
private:
Ptr<game::AudioController> audioController_;
void jump() {
// 播放跳跃音效
if (audioController_) {
audioController_->playSound("jump");
}
}
void attack() {
// 播放攻击音效
if (audioController_) {
audioController_->playSound("attack");
}
}
void toggleSound() {
if (audioController_) {
audioController_->setEnabled(!audioController_->isEnabled());
}
}
};
} // namespace pushbox
```
## 音频格式支持
| 格式 | 支持 |
|------|------|
| WAV | ✓ |
| MP3 | ✓ |
| OGG | ✓ |
| FLAC | ✓ |
| MOD | ✓ |
## 音量控制
```cpp
// 设置音效音量 (0-128)
Mix_Volume(-1, MIX_MAX_VOLUME / 2); // 所有音效
Mix_Volume(channel, volume); // 指定通道
// audio_manager.cpp
#include "audio_manager.h"
// 设置音乐音量 (0-128)
Mix_VolumeMusic(volume);
namespace pushbox {
void AudioManager::init() {
auto& audio = extra2d::Application::instance().audio();
// 加载音效
moveSound_ = audio.loadSound("move", "assets/audio/manmove.wav");
boxMoveSound_ = audio.loadSound("boxmove", "assets/audio/boxmove.wav");
winSound_ = audio.loadSound("win", "assets/audio/win.wav");
}
void AudioManager::setEnabled(bool enabled) {
enabled_ = enabled;
if (!enabled) {
extra2d::Application::instance().audio().stopAll();
}
}
void AudioManager::playMoveSound() {
if (enabled_ && moveSound_) {
moveSound_->play();
}
}
void AudioManager::playBoxMoveSound() {
if (enabled_ && boxMoveSound_) {
boxMoveSound_->play();
}
}
void AudioManager::playWinSound() {
if (enabled_ && winSound_) {
winSound_->play();
}
}
} // namespace pushbox
```
## 使用音频管理器
```cpp
// main.cpp
int main(int argc, char** argv) {
// ... 初始化应用 ...
// 初始化音频管理器
pushbox::AudioManager::instance().init();
// ... 运行应用 ...
}
// PlayScene.cpp
void PlayScene::move(int dx, int dy, int direct) {
// ... 移动逻辑 ...
if (isBoxMoved) {
// 播放推箱子音效
pushbox::AudioManager::instance().playBoxMoveSound();
} else {
// 播放移动音效
pushbox::AudioManager::instance().playMoveSound();
}
}
```
## 音频开关控制
```cpp
// 在菜单中切换音效
void StartScene::onUpdate(float dt) {
auto& input = Application::instance().input();
// X键切换音效
if (input.isButtonPressed(GamepadButton::X)) {
g_SoundOpen = !g_SoundOpen;
AudioManager::instance().setEnabled(g_SoundOpen);
updateSoundIcon();
}
}
```
## 支持的音频格式
- WAV
- OGG
- MP3需要 SDL2_mixer 支持)
## 最佳实践
1. **预加载音效**: 在 `onEnter()` 中加载所有需要的音效
2. **使用音频控制器**: 统一管理音效,方便控制开关
3. **音效开关**: 提供用户选项控制音效开关
4. **资源释放**: 音效资源会自动管理,无需手动释放
1. **使用单例管理器** - 集中管理音频资源
2. **预加载常用音效** - 在初始化时加载
3. **提供开关选项** - 让用户控制音效
4. **合理设置音量** - 避免音量过大
5. **及时卸载不用的音效** - 释放内存资源
## API 参考
### Sound 类
| 方法 | 说明 |
|------|------|
| `play()` | 播放音效 |
| `pause()` | 暂停播放 |
| `resume()` | 恢复播放 |
| `stop()` | 停止播放 |
| `isPlaying()` | 是否正在播放 |
| `isPaused()` | 是否已暂停 |
| `setVolume(float)` | 设置音量 (0.0-1.0) |
| `getVolume()` | 获取音量 |
| `setLooping(bool)` | 设置循环播放 |
| `isLooping()` | 是否循环播放 |
| `setPitch(float)` | 设置音调(当前不支持) |
| `getPitch()` | 获取音调 |
| `getDuration()` | 获取时长(当前不支持) |
| `getCursor()` | 获取播放位置(当前不支持) |
| `setCursor(float)` | 设置播放位置(当前不支持) |
### AudioEngine 类
| 方法 | 说明 |
|------|------|
| `getInstance()` | 获取单例实例 |
| `initialize()` | 初始化音频引擎 |
| `shutdown()` | 关闭音频引擎 |
| `loadSound(path)` | 加载音效(以路径为名称) |
| `loadSound(name, path)` | 加载音效(指定名称) |
| `getSound(name)` | 获取已加载的音效 |
| `unloadSound(name)` | 卸载指定音效 |
| `unloadAllSounds()` | 卸载所有音效 |
| `setMasterVolume(float)` | 设置主音量 |
| `getMasterVolume()` | 获取主音量 |
| `pauseAll()` | 暂停所有音效 |
| `resumeAll()` | 恢复所有音效 |
| `stopAll()` | 停止所有音效 |
## 总结
Extra2D 的音频系统简单易用:
至此,你已经学习了 Extra2D 引擎的核心功能
```cpp
// 加载
auto sound = resources.loadSound("assets/sound.wav");
1. [快速开始](./01_Quick_Start.md) - 引擎基础
2. [场景系统](./02_Scene_System.md) - 场景管理
3. [节点系统](./03_Node_System.md) - 游戏对象
4. [资源管理](./04_Resource_Management.md) - 资源加载
5. [输入处理](./05_Input_Handling.md) - 输入控制
6. [碰撞检测](./06_Collision_Detection.md) - 空间索引
7. [UI 系统](./07_UI_System.md) - 界面控件
8. [音频系统](./08_Audio_System.md) - 音频播放
// 播放
sound->play(); // 一次
sound->play(true); // 循环
// 停止
sound->stop();
```
---
**教程完成!** 您已经学习了 Extra2D 的所有核心功能:
1. [快速开始](01_Quick_Start.md)
2. [场景系统](02_Scene_System.md)
3. [节点系统](03_Node_System.md)
4. [资源管理](04_Resource_Management.md)
5. [输入处理](05_Input_Handling.md)
6. [碰撞检测](06_Collision_Detection.md)
7. [UI 系统](07_UI_System.md)
8. [音频系统](08_Audio_System.md)
开始你的游戏开发之旅吧!

View File

@ -1,327 +1,218 @@
# Extra2D 构建系统文档
## 概述
本文档详细介绍 Extra2D 引擎的构建系统配置和使用方法。
Extra2D 使用 **Xmake** 作为构建系统,支持 **MinGW (Windows)****Nintendo Switch** 两个平台。
## 构建工具
## 项目结构
Extra2D 使用 [xmake](https://xmake.io/) 作为构建工具,支持多平台构建。
```
Extra2D/
├── xmake.lua # 主构建脚本
├── xmake/
│ ├── engine.lua # 引擎库定义
│ └── toolchains/
│ └── switch.lua # Switch 工具链定义
├── Extra2D/
│ ├── src/ # 引擎源码
│ └── include/ # 引擎头文件
├── squirrel/ # Squirrel 脚本引擎
└── examples/ # 示例程序
├── hello_world/
├── collision_demo/
├── push_box/
└── spatial_index_demo/
```
## 环境准备
### MinGW (Windows) 平台
1. **安装 MinGW-w64**
- 下载地址: https://www.mingw-w64.org/downloads/
- 或使用 MSYS2: `pacman -S mingw-w64-x86_64-toolchain`
2. **安装 Xmake**
- 下载地址: https://xmake.io/#/zh-cn/guide/installation
3. **安装依赖包**
```bash
xmake require -y
```
### Nintendo Switch 平台
1. **安装 devkitPro**
- 下载地址: https://devkitpro.org/wiki/Getting_Started
- Windows 安装程序会自动设置环境变量
2. **设置环境变量**
```powershell
# PowerShell
$env:DEVKITPRO="C:/devkitPro"
# 或永久设置(系统属性 -> 环境变量)
[Environment]::SetEnvironmentVariable("DEVKITPRO", "C:/devkitPro", "User")
```
3. **在 MSYS2 中安装 Switch 库**
```bash
# 打开 MSYS2 (devkitPro 提供的)
pacman -S switch-sdl2 switch-sdl2_mixer switch-glm
# 或安装所有 Switch 开发库
pacman -S $(pacman -Slq dkp-libs | grep switch-)
```
4. **验证安装**
```bash
ls $DEVKITPRO/portlibs/switch/include/SDL2
ls $DEVKITPRO/portlibs/switch/include/glm
```
## 主构建脚本 (xmake.lua)
### 项目元信息
- **项目名称**: Extra2D
- **版本**: 3.1.0
- **许可证**: MIT
- **语言标准**: C++17
- **编码**: UTF-8
### 构建选项
| 选项 | 默认值 | 描述 |
|------|--------|------|
| `examples` | true | 构建示例程序 |
| `debug_logs` | false | 启用调试日志 |
### 平台检测逻辑
1. 获取主机平台: `os.host()`
2. 获取目标平台: `get_config("plat")` 或主机平台
3. 平台回退: 如果不支持Windows 回退到 `mingw`
4. 设置平台: `set_plat(target_plat)`
5. 设置架构:
- Switch: `arm64`
- MinGW: `x86_64`
### 依赖包 (MinGW 平台)
```lua
add_requires("glm", "libsdl2", "libsdl2_mixer")
```
## 引擎库定义 (xmake/engine.lua)
### 目标: extra2d
- **类型**: 静态库 (`static`)
- **源文件**:
- `Extra2D/src/**.cpp`
- `Extra2D/src/glad/glad.c`
- `squirrel/squirrel/*.cpp`
- `squirrel/sqstdlib/*.cpp`
### 头文件路径
- `Extra2D/include` (public)
- `squirrel/include` (public)
- `Extra2D/include/extra2d/platform` (public)
### 平台配置
#### Switch 平台
```lua
add_includedirs(devkitPro .. "/portlibs/switch/include")
add_linkdirs(devkitPro .. "/portlibs/switch/lib")
add_syslinks("SDL2_mixer", "SDL2", "opusfile", "opus", ...)
```
#### MinGW 平台
```lua
add_packages("glm", "libsdl2", "libsdl2_mixer")
add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi")
```
### 编译器标志
- `-Wall`, `-Wextra`
- `-Wno-unused-variable`, `-Wno-unused-function`
- `-Wno-deprecated-copy`, `-Wno-class-memaccess`
### 构建模式
- **Debug**: `-O0`, `-g`, 定义 `E2D_DEBUG`, `_DEBUG`
- **Release**: `-O2`, 定义 `NDEBUG`
## Switch 工具链 (xmake/toolchains/switch.lua)
### 工具链: switch
- **类型**: standalone
- **描述**: Nintendo Switch devkitA64 工具链
### 工具路径
- **CC**: `aarch64-none-elf-gcc.exe`
- **CXX**: `aarch64-none-elf-g++.exe`
- **LD**: `aarch64-none-elf-g++.exe`
- **AR**: `aarch64-none-elf-gcc-ar.exe`
### 架构标志
```
-march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
```
### 链接标志
```
-specs=switch.specs -g
```
### 预定义宏
- `__SWITCH__`
- `__NX__`
- `MA_SWITCH`
- `PFD_SWITCH`
### 系统库
- `nx` (libnx)
- `m` (math)
## 示例程序构建脚本
### 通用结构
```lua
-- 使用与主项目相同的平台配置
if is_plat("switch") then
-- Switch 平台配置
elseif is_plat("mingw") then
-- MinGW 平台配置
end
```
### Switch 平台配置
- 设置平台: `set_plat("switch")`
- 设置架构: `set_arch("arm64")`
- 设置工具链: `set_toolchains("switch")`
- 设置输出目录: `set_targetdir("../../build/examples/xxx")`
- 构建后生成 NRO 文件
### MinGW 平台配置
- 设置平台: `set_plat("mingw")`
- 设置架构: `set_arch("x86_64")`
- 设置输出目录: `set_targetdir("../../build/examples/xxx")`
- 链接标志: `-mwindows`
- 构建后复制资源文件
## 构建命令
### 配置项目
### 安装 xmake
```bash
# 默认配置 (MinGW)
xmake f -c
# Windows (使用 PowerShell)
Invoke-Expression (Invoke-WebRequest 'https://xmake.io/psget.text' -UseBasicParsing).Content
# 指定平台 (使用 -p 参数)
xmake f -c -p mingw
xmake f -c -p switch
# macOS
brew install xmake
# 指定 MinGW 路径(如果不在默认位置)
xmake f -c -p mingw --mingw=C:\mingw
# 禁用示例
xmake f --examples=n
# 启用调试日志
xmake f --debug_logs=y
# Linux
sudo add-apt-repository ppa:xmake-io/xmake
sudo apt update
sudo apt install xmake
```
### 安装依赖 (MinGW)
## 平台支持
| 平台 | 目标 | 说明 |
|------|------|------|
| Windows | `mingw` | MinGW-w64 工具链 |
| Nintendo Switch | `switch` | devkitPro 工具链 |
## 构建配置
### Windows (MinGW)
```bash
# 配置构建
xmake f -p mingw --mode=release
# 安装依赖
xmake require -y
# 构建引擎
xmake
# 构建示例
xmake -g examples
# 运行示例
xmake run hello_world
```
### 构建项目
### Nintendo Switch
```bash
# 配置构建
xmake f -p switch --mode=release
# 构建引擎
xmake
# 构建示例
xmake -g examples
# 打包 NSP
xmake package push_box
```
## 构建选项
### 配置参数
```bash
# 设置构建模式
xmake f --mode=debug # 调试模式
xmake f --mode=release # 发布模式
# 设置目标平台
xmake f -p mingw # Windows
xmake f -p switch # Nintendo Switch
```
### 构建目标
```bash
# 构建所有目标
xmake
# 构建特定目标
xmake -r extra2d
xmake -r push_box
xmake -t extra2d # 引擎库
xmake -t hello_world # Hello World 示例
xmake -t push_box # 推箱子游戏
xmake -t collision_demo # 碰撞检测演示
xmake -t spatial_index_demo # 空间索引演示
# 并行构建
xmake -j4
# 构建示例组
xmake -g examples
```
### 运行程序
```bash
# 运行示例
xmake run push_box
xmake run hello_world
```
### 清理构建
```bash
xmake clean
xmake f -c # 重新配置
```
## 输出目录结构
## 项目结构
```
build/
├── examples/
│ ├── hello_world/
│ │ ├── hello_world.exe # MinGW
│ │ ├── hello_world.nro # Switch
│ │ └── assets/ # 资源文件
│ ├── push_box/
│ ├── collision_demo/
│ └── spatial_index_demo/
└── ...
Extra2D/
├── xmake.lua # 主构建配置
├── xmake/
│ ├── engine.lua # 引擎构建规则
│ └── toolchains/ # 工具链定义
├── Extra2D/ # 引擎源码
│ ├── include/ # 头文件
│ └── src/ # 源文件
└── examples/ # 示例程序
├── hello_world/
├── push_box/
├── collision_demo/
└── spatial_index_demo/
```
## 关键设计决策
## 添加新示例
### 1. 平台检测
- 使用 `is_plat()` 而不是手动检测,确保与主项目一致
- 示例脚本继承主项目的平台配置
创建新的示例程序:
### 2. 资源处理
- **Switch**: 使用 romfs 嵌入 NRO 文件
- **MinGW**: 构建后复制到输出目录
1. 在 `examples/` 下创建目录
2. 添加 `main.cpp``xmake.lua`
### 3. 依赖管理
- **MinGW**: 使用 Xmake 包管理器 (`add_requires`)
- **Switch**: 使用 devkitPro 提供的库
### 示例 xmake.lua
### 4. 工具链隔离
- Switch 工具链定义在单独文件中
- 通过 `set_toolchains("switch")` 切换
```lua
-- examples/my_demo/xmake.lua
target("my_demo")
set_kind("binary")
add_deps("extra2d")
add_files("*.cpp")
add_packages("spdlog", "glm")
-- 资源文件
add_files("romfs/**", {install = true})
-- Switch 特定配置
if is_plat("switch") then
add_rules("switch.nro")
add_files("icon.jpg")
end
```
## 常见问题
### 1. 依赖包找不到
### 依赖安装失败
```bash
xmake repo -u
xmake require -y
# 强制重新安装依赖
xmake require -f -y
# 清理构建缓存
xmake clean
xmake f -c
```
### 2. Switch 工具链找不到
- 确保 DEVKITPRO 环境变量设置正确
- 默认路径: `C:/devkitPro`
### Switch 构建失败
### 3. 平台配置不匹配
- 使用 `xmake show` 查看当前配置
- 使用 `xmake f -c` 重新配置
确保已安装 devkitPro
### 4. MinGW 路径问题
如果 MinGW 安装在非默认位置,使用 `--mingw` 参数指定:
```bash
xmake f -c -p mingw --mingw=D:\Tools\mingw64
# 安装 Switch 开发工具链
pacman -S switch-dev switch-portlibs
# 设置环境变量
$env:DEVKITPRO = "C:\devkitPro"
$env:DEVKITA64 = "C:\devkitPro\devkitA64"
```
### 5. Switch 库找不到
确保在 MSYS2 中安装了必要的库:
```bash
pacman -S switch-sdl2 switch-sdl2_mixer switch-glm
### 运行时找不到资源
确保资源文件已正确配置:
```lua
-- 在 xmake.lua 中添加资源
add_files("romfs/**", {install = true})
```
## 扩展指南
## 高级配置
### 添加新示例
1. 在 `examples/` 下创建新目录
2. 创建 `xmake.lua` 构建脚本
3. 在 `xmake.lua` 中添加 `includes("examples/new_example")`
### 自定义编译选项
### 添加新平台
1. 在 `xmake/toolchains/` 下创建工具链定义
2. 在 `xmake.lua` 中添加平台检测逻辑
3. 在 `xmake/engine.lua` 中添加平台配置
```lua
-- 添加编译选项
add_cxxflags("-O3", "-ffast-math")
-- 添加宏定义
add_defines("E2D_ENABLE_PROFILING")
-- 添加包含路径
add_includedirs("third_party/include")
-- 添加链接库
add_links("pthread")
```
### 条件编译
```lua
if is_plat("windows") then
add_defines("E2D_PLATFORM_WINDOWS")
elseif is_plat("switch") then
add_defines("E2D_PLATFORM_SWITCH")
end
if is_mode("debug") then
add_defines("E2D_DEBUG")
add_cxxflags("-g", "-O0")
else
add_defines("E2D_RELEASE")
add_cxxflags("-O3")
end
```
## 参考链接
- [xmake 官方文档](https://xmake.io/#/zh-cn/)
- [devkitPro 官网](https://devkitpro.org/)

View File

@ -1,7 +1,5 @@
#include <cmath>
#include <extra2d/extra2d.h>
#include <sstream>
using namespace extra2d;
@ -73,7 +71,7 @@ public:
centerBox_->setPosition(Vec2(centerX, centerY));
addChild(centerBox_);
// 加载字体
// 加载字体并创建UI
loadFonts();
E2D_LOG_INFO("创建了 {} 个碰撞框", boxes_.size() + 1);
@ -102,6 +100,9 @@ public:
// 执行碰撞检测
performCollisionDetection();
// 更新UI文本
updateUI();
// 检查退出按键
auto &input = Application::instance().input();
if (input.isButtonPressed(GamepadButton::Start)) {
@ -110,28 +111,60 @@ public:
}
}
void onRender(RenderBackend &renderer) override {
Scene::onRender(renderer);
// 绘制说明文字
drawUI(renderer);
}
private:
/**
* @brief
* @brief UI文本
*/
void loadFonts() {
auto &resources = Application::instance().resources();
titleFont_ = resources.loadFont("assets/font.ttf", 60, true);
infoFont_ = resources.loadFont("assets/font.ttf", 28, true);
if (!titleFont_) {
E2D_LOG_WARN("无法加载标题字体");
}
if (!infoFont_) {
E2D_LOG_WARN("无法加载信息字体");
}
// 创建标题文本
titleText_ = Text::create("碰撞检测演示", titleFont_);
titleText_->setPosition(50.0f, 30.0f);
titleText_->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f));
addChild(titleText_);
// 创建说明文本
descText_ = Text::create("蓝色方块旋转并检测碰撞", infoFont_);
descText_->setPosition(50.0f, 80.0f);
descText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f));
addChild(descText_);
collideHintText_ = Text::create("红色 = 检测到碰撞", infoFont_);
collideHintText_->setPosition(50.0f, 105.0f);
collideHintText_->setTextColor(Color(1.0f, 0.5f, 0.5f, 1.0f));
addChild(collideHintText_);
// 创建动态统计文本
collisionText_ = Text::create("", infoFont_);
collisionText_->setPosition(50.0f, 150.0f);
collisionText_->setTextColor(Color(1.0f, 1.0f, 0.5f, 1.0f));
addChild(collisionText_);
fpsText_ = Text::create("", infoFont_);
fpsText_->setPosition(50.0f, 175.0f);
fpsText_->setTextColor(Color(0.8f, 1.0f, 0.8f, 1.0f));
addChild(fpsText_);
// 创建退出提示文本
float screenHeight = static_cast<float>(Application::instance().getConfig().height);
exitHintText_ = Text::create("按 + 键退出", infoFont_);
exitHintText_->setPosition(50.0f, screenHeight - 50.0f);
exitHintText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f));
addChild(exitHintText_);
}
/**
* @brief UI文本
*/
void updateUI() {
auto &app = Application::instance();
// 使用 setFormat 更新动态文本
collisionText_->setFormat("碰撞数: %zu", collisionCount_);
fpsText_->setFormat("FPS: %u", app.fps());
}
/**
@ -182,44 +215,6 @@ private:
}
}
/**
* @brief UI界面
*/
void drawUI(RenderBackend &renderer) {
if (!titleFont_ || !infoFont_)
return;
auto &app = Application::instance();
// 绘制标题
renderer.drawText(*titleFont_, "碰撞检测演示", Vec2(50.0f, 30.0f),
Color(1.0f, 1.0f, 1.0f, 1.0f));
// 绘制说明文字
renderer.drawText(*infoFont_, "蓝色方块旋转并检测碰撞", Vec2(50.0f, 80.0f),
Color(0.8f, 0.8f, 0.8f, 1.0f));
renderer.drawText(*infoFont_, "红色 = 检测到碰撞", Vec2(50.0f, 105.0f),
Color(1.0f, 0.5f, 0.5f, 1.0f));
// 绘制碰撞统计
std::stringstream ss;
ss << "碰撞数: " << collisionCount_;
renderer.drawText(*infoFont_, ss.str(), Vec2(50.0f, 150.0f),
Color(1.0f, 1.0f, 0.5f, 1.0f));
// 绘制 FPS
ss.str("");
ss << "FPS: " << app.fps();
renderer.drawText(*infoFont_, ss.str(), Vec2(50.0f, 175.0f),
Color(0.8f, 1.0f, 0.8f, 1.0f));
// 绘制操作提示
float screenHeight = static_cast<float>(app.getConfig().height);
renderer.drawText(*infoFont_, "按 + 键退出",
Vec2(50.0f, screenHeight - 50.0f),
Color(0.8f, 0.8f, 0.8f, 1.0f));
}
Ptr<CollisionBox> centerBox_;
std::vector<Ptr<CollisionBox>> boxes_;
float rotationAngle_ = 0.0f;
@ -229,6 +224,14 @@ private:
// 字体资源
Ptr<FontAtlas> titleFont_;
Ptr<FontAtlas> infoFont_;
// UI 文本组件
Ptr<Text> titleText_;
Ptr<Text> descText_;
Ptr<Text> collideHintText_;
Ptr<Text> collisionText_;
Ptr<Text> fpsText_;
Ptr<Text> exitHintText_;
};
// ============================================================================

View File

@ -20,6 +20,7 @@ target("collision_demo")
set_toolchains("switch")
set_targetdir("../../build/examples/collision_demo")
-- 构建后生成 NRO 文件
after_build(function (target)
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
local elf_file = target:targetfile()
@ -37,6 +38,17 @@ target("collision_demo")
else
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
end
print("Generated NRO: " .. nro_file)
end
end)
-- 打包时将 NRO 文件复制到 package 目录
after_package(function (target)
local nro_file = path.join(target:targetdir(), "collision_demo.nro")
local package_dir = target:packagedir()
if os.isfile(nro_file) and package_dir then
os.cp(nro_file, package_dir)
print("Copied NRO to package: " .. package_dir)
end
end)
@ -46,20 +58,39 @@ target("collision_demo")
set_targetdir("../../build/examples/collision_demo")
add_ldflags("-mwindows", {force = true})
-- 复制资源
-- 复制资源到输出目录
after_build(function (target)
local romfs = path.join(example_dir, "romfs")
if os.isdir(romfs) then
local target_dir = path.directory(target:targetfile())
local assets_dir = path.join(target_dir, "assets")
-- 创建 assets 目录
if not os.isdir(assets_dir) then
os.mkdir(assets_dir)
end
os.cp(path.join(romfs, "assets/*"), assets_dir)
-- 复制所有资源文件(包括子目录)
os.cp(path.join(romfs, "assets/**"), assets_dir)
print("Copied assets from " .. romfs .. " to " .. assets_dir)
else
print("Warning: romfs directory not found at " .. romfs)
end
end)
-- 打包时将资源复制到 package 目录
after_package(function (target)
local target_dir = path.directory(target:targetfile())
local assets_dir = path.join(target_dir, "assets")
local package_dir = target:packagedir()
if os.isdir(assets_dir) and package_dir then
local package_assets = path.join(package_dir, "assets")
if not os.isdir(package_assets) then
os.mkdir(package_assets)
end
os.cp(path.join(assets_dir, "**"), package_assets)
print("Copied assets to package: " .. package_assets)
end
end)
end
target_end()

View File

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

View File

@ -20,7 +20,7 @@ target("hello_world")
set_toolchains("switch")
set_targetdir("../../build/examples/hello_world")
-- 生成 NRO
-- 构建后生成 NRO 文件
after_build(function (target)
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
local elf_file = target:targetfile()
@ -38,6 +38,17 @@ target("hello_world")
else
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
end
print("Generated NRO: " .. nro_file)
end
end)
-- 打包时将 NRO 文件复制到 package 目录
after_package(function (target)
local nro_file = path.join(target:targetdir(), "hello_world.nro")
local package_dir = target:packagedir()
if os.isfile(nro_file) and package_dir then
os.cp(nro_file, package_dir)
print("Copied NRO to package: " .. package_dir)
end
end)
@ -47,20 +58,39 @@ target("hello_world")
set_targetdir("../../build/examples/hello_world")
add_ldflags("-mwindows", {force = true})
-- 复制资源
-- 复制资源到输出目录
after_build(function (target)
local romfs = path.join(example_dir, "romfs")
if os.isdir(romfs) then
local target_dir = path.directory(target:targetfile())
local assets_dir = path.join(target_dir, "assets")
-- 创建 assets 目录
if not os.isdir(assets_dir) then
os.mkdir(assets_dir)
end
os.cp(path.join(romfs, "assets/*"), assets_dir)
-- 复制所有资源文件(包括子目录)
os.cp(path.join(romfs, "assets/**"), assets_dir)
print("Copied assets from " .. romfs .. " to " .. assets_dir)
else
print("Warning: romfs directory not found at " .. romfs)
end
end)
-- 打包时将资源复制到 package 目录
after_package(function (target)
local target_dir = path.directory(target:targetfile())
local assets_dir = path.join(target_dir, "assets")
local package_dir = target:packagedir()
if os.isdir(assets_dir) and package_dir then
local package_assets = path.join(package_dir, "assets")
if not os.isdir(package_assets) then
os.mkdir(package_assets)
end
os.cp(path.join(assets_dir, "**"), package_assets)
print("Copied assets to package: " .. package_assets)
end
end)
end
target_end()

View File

@ -1,7 +1,6 @@
#include "PlayScene.h"
#include "audio_context.h"
#include "audio_controller.h"
#include "audio_manager.h"
#include "storage.h"
#include "StartScene.h"
#include "SuccessScene.h"
@ -93,11 +92,6 @@ PlayScene::PlayScene(int level) {
mapLayer_->setPosition(0.0f, 0.0f);
addChild(mapLayer_);
auto audioNode = AudioController::create();
audioNode->setName("AudioController");
addChild(audioNode);
setAudioController(audioNode);
setLevel(level);
}
@ -124,45 +118,43 @@ void PlayScene::onUpdate(float dt) {
auto& input = app.input();
// B 键返回主菜单
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_B)) {
if (input.isButtonPressed(extra2d::GamepadButton::B)) {
app.scenes().replaceScene(
extra2d::makePtr<StartScene>(), extra2d::TransitionType::Fade, 0.2f);
return;
}
// Y 键重开
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_Y)) {
if (input.isButtonPressed(extra2d::GamepadButton::Y)) {
setLevel(g_CurrentLevel);
return;
}
// X 键直接切换音效
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_X)) {
// X键直接切换音效
if (input.isButtonPressed(extra2d::GamepadButton::X)) {
g_SoundOpen = !g_SoundOpen;
if (auto audio = getAudioController()) {
audio->setEnabled(g_SoundOpen);
}
AudioManager::instance().setEnabled(g_SoundOpen);
updateSoundIcon();
return;
}
// A 键执行选中的菜单项
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_A)) {
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
executeMenuItem();
return;
}
// 方向键移动
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_UP)) {
if (input.isButtonPressed(extra2d::GamepadButton::DPadUp)) {
move(0, -1, 1);
flush();
} else if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_DOWN)) {
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadDown)) {
move(0, 1, 2);
flush();
} else if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_LEFT)) {
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadLeft)) {
move(-1, 0, 3);
flush();
} else if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_RIGHT)) {
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadRight)) {
move(1, 0, 4);
flush();
} else {
@ -189,9 +181,7 @@ void PlayScene::executeMenuItem() {
break;
case 1: // 切换音效
g_SoundOpen = !g_SoundOpen;
if (auto audio = getAudioController()) {
audio->setEnabled(g_SoundOpen);
}
AudioManager::instance().setEnabled(g_SoundOpen);
updateSoundIcon();
break;
}
@ -329,9 +319,7 @@ void PlayScene::move(int dx, int dy, int direct) {
g_Pushing = false;
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
map_.value[targetY][targetX].type = TYPE::Man;
if (auto audio = getAudioController()) {
audio->playManMove();
}
AudioManager::instance().playManMove();
} else if (map_.value[targetY][targetX].type == TYPE::Box) {
g_Pushing = true;
@ -370,9 +358,7 @@ void PlayScene::move(int dx, int dy, int direct) {
map_.value[targetY][targetX].type = TYPE::Man;
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
if (auto audio = getAudioController()) {
audio->playBoxMove();
}
AudioManager::instance().playBoxMove();
} else {
return;
}

View File

@ -1,7 +1,6 @@
#include "StartScene.h"
#include "audio_context.h"
#include "audio_controller.h"
#include "audio_manager.h"
#include "data.h"
#include "PlayScene.h"
#include <extra2d/extra2d.h>
@ -28,10 +27,6 @@ void StartScene::onEnter() {
setBackgroundColor(extra2d::Colors::Black);
if (getChildren().empty()) {
auto audioNode = AudioController::create();
audioNode->setName("audio_controller");
addChild(audioNode);
setAudioController(audioNode);
float screenW = static_cast<float>(app.getConfig().width);
float screenH = static_cast<float>(app.getConfig().height);
@ -138,9 +133,7 @@ void StartScene::onUpdate(float dt) {
// X键切换音效
if (input.isButtonPressed(extra2d::GamepadButton::X)) {
g_SoundOpen = !g_SoundOpen;
if (auto audio = getAudioController()) {
audio->setEnabled(g_SoundOpen);
}
AudioManager::instance().setEnabled(g_SoundOpen);
updateSoundIcon();
}
}

View File

@ -67,7 +67,7 @@ void SuccessScene::onUpdate(float dt) {
auto& input = app.input();
// A键确认返回主菜单
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_A)) {
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
auto& scenes = extra2d::Application::instance().scenes();
scenes.popScene(extra2d::TransitionType::Fade, 0.2f);
scenes.popScene(extra2d::TransitionType::Fade, 0.2f);

View File

@ -1,17 +0,0 @@
#include "audio_context.h"
#include "audio_controller.h"
namespace pushbox {
static extra2d::WeakPtr<AudioController> g_audioController;
void setAudioController(const extra2d::Ptr<AudioController>& controller) {
g_audioController = controller;
}
extra2d::Ptr<AudioController> getAudioController() {
return g_audioController.lock();
}
} // namespace pushbox

View File

@ -1,12 +0,0 @@
#pragma once
#include <extra2d/extra2d.h>
namespace pushbox {
class AudioController;
void setAudioController(const extra2d::Ptr<AudioController>& controller);
extra2d::Ptr<AudioController> getAudioController();
} // namespace pushbox

View File

@ -1,62 +0,0 @@
#include "audio_controller.h"
#include "storage.h"
namespace pushbox {
extra2d::Ptr<AudioController> AudioController::create() {
return extra2d::makePtr<AudioController>();
}
void AudioController::onEnter() {
Node::onEnter();
if (!loaded_) {
auto& resources = extra2d::Application::instance().resources();
background_ = resources.loadSound("pushbox_bg", "assets/audio/background.wav");
manMove_ = resources.loadSound("pushbox_manmove", "assets/audio/manmove.wav");
boxMove_ = resources.loadSound("pushbox_boxmove", "assets/audio/boxmove.wav");
if (background_) {
background_->setLooping(true);
background_->play();
}
loaded_ = true;
}
setEnabled(g_SoundOpen);
}
void AudioController::setEnabled(bool enabled) {
enabled_ = enabled;
g_SoundOpen = enabled;
saveSoundOpen(enabled);
if (!background_) {
return;
}
if (enabled_) {
background_->resume();
} else {
background_->pause();
}
}
void AudioController::playManMove() {
if (!enabled_ || !manMove_) {
return;
}
manMove_->play();
}
void AudioController::playBoxMove() {
if (!enabled_ || !boxMove_) {
return;
}
boxMove_->play();
}
} // namespace pushbox

View File

@ -1,29 +0,0 @@
#pragma once
#include "data.h"
#include <extra2d/extra2d.h>
namespace pushbox {
class AudioController : public extra2d::Node {
public:
static extra2d::Ptr<AudioController> create();
void onEnter() override;
void setEnabled(bool enabled);
bool isEnabled() const { return enabled_; }
void playManMove();
void playBoxMove();
private:
bool loaded_ = false;
bool enabled_ = true;
extra2d::Ptr<extra2d::Sound> background_;
extra2d::Ptr<extra2d::Sound> manMove_;
extra2d::Ptr<extra2d::Sound> boxMove_;
};
} // namespace pushbox

View File

@ -0,0 +1,112 @@
#include "audio_manager.h"
#include "storage.h"
namespace pushbox {
// ============================================================================
// 单例实现
// ============================================================================
AudioManager& AudioManager::instance() {
static AudioManager instance;
return instance;
}
// ============================================================================
// 初始化音频资源
// ============================================================================
void AudioManager::init() {
if (initialized_) {
return;
}
auto& resources = extra2d::Application::instance().resources();
// 加载音效资源
background_ = resources.loadSound("pushbox_bg", "assets/audio/background.wav");
manMove_ = resources.loadSound("pushbox_manmove", "assets/audio/manmove.wav");
boxMove_ = resources.loadSound("pushbox_boxmove", "assets/audio/boxmove.wav");
// 设置背景音乐循环播放
if (background_) {
background_->setLooping(true);
}
// 从存储中读取音效设置
enabled_ = g_SoundOpen;
initialized_ = true;
// 如果音效开启,播放背景音乐
if (enabled_ && background_) {
background_->play();
}
}
// ============================================================================
// 启用/禁用音效
// ============================================================================
void AudioManager::setEnabled(bool enabled) {
enabled_ = enabled;
g_SoundOpen = enabled;
saveSoundOpen(enabled);
if (!background_) {
return;
}
if (enabled_) {
background_->resume();
} else {
background_->pause();
}
}
// ============================================================================
// 播放角色移动音效
// ============================================================================
void AudioManager::playManMove() {
if (!enabled_ || !manMove_) {
return;
}
manMove_->play();
}
// ============================================================================
// 播放箱子移动音效
// ============================================================================
void AudioManager::playBoxMove() {
if (!enabled_ || !boxMove_) {
return;
}
boxMove_->play();
}
// ============================================================================
// 背景音乐控制
// ============================================================================
void AudioManager::playBackground() {
if (background_) {
background_->play();
}
}
void AudioManager::pauseBackground() {
if (background_) {
background_->pause();
}
}
void AudioManager::resumeBackground() {
if (background_) {
background_->resume();
}
}
void AudioManager::stopBackground() {
if (background_) {
background_->stop();
}
}
} // namespace pushbox

View File

@ -0,0 +1,47 @@
#pragma once
#include "data.h"
#include <extra2d/extra2d.h>
namespace pushbox {
// ============================================================================
// 全局音频管理器 - 单例模式,不依赖场景生命周期
// ============================================================================
class AudioManager {
public:
// 获取单例实例
static AudioManager& instance();
// 初始化音频资源
void init();
// 启用/禁用音效
void setEnabled(bool enabled);
bool isEnabled() const { return enabled_; }
// 播放音效
void playManMove();
void playBoxMove();
// 背景音乐控制
void playBackground();
void pauseBackground();
void resumeBackground();
void stopBackground();
private:
AudioManager() = default;
~AudioManager() = default;
AudioManager(const AudioManager&) = delete;
AudioManager& operator=(const AudioManager&) = delete;
bool initialized_ = false;
bool enabled_ = true;
extra2d::Ptr<extra2d::Sound> background_;
extra2d::Ptr<extra2d::Sound> manMove_;
extra2d::Ptr<extra2d::Sound> boxMove_;
};
} // namespace pushbox

View File

@ -2,6 +2,7 @@
#include "StartScene.h"
#include "data.h"
#include "storage.h"
#include "audio_manager.h"
using namespace extra2d;
@ -40,6 +41,9 @@ int main(int argc, char **argv)
}
pushbox::g_SoundOpen = pushbox::loadSoundOpen(true);
// 初始化全局音频管理器
pushbox::AudioManager::instance().init();
// 进入开始场景(主界面)
app.enterScene(makePtr<pushbox::StartScene>());

View File

@ -1,40 +0,0 @@
#include "menu_button.h"
#include <extra2d/extra2d.h>
namespace pushbox {
extra2d::Ptr<MenuButton> MenuButton::create(extra2d::Ptr<extra2d::FontAtlas> font,
const extra2d::String& text,
extra2d::Function<void()> onClick) {
auto btn = extra2d::makePtr<MenuButton>();
btn->setFont(font);
btn->setText(text);
btn->setPadding(extra2d::Vec2(0.0f, 0.0f));
btn->setBackgroundColor(extra2d::Colors::Transparent, extra2d::Colors::Transparent,
extra2d::Colors::Transparent);
btn->setBorder(extra2d::Colors::Transparent, 0.0f);
btn->setTextColor(extra2d::Colors::Black);
btn->onClick_ = std::move(onClick);
btn->setOnClick([wbtn = extra2d::WeakPtr<MenuButton>(btn)]() {
if (auto self = wbtn.lock()) {
if (self->enabled_ && self->onClick_) {
self->onClick_();
}
}
});
// 使用事件监听来处理悬停效果
// Note: Extra2D 的 Button 类可能有不同的悬停检测机制
// 这里简化处理,仅保留基本功能
return btn;
}
void MenuButton::setEnabled(bool enabled) {
enabled_ = enabled;
setTextColor(enabled ? extra2d::Colors::Black : extra2d::Colors::LightGray);
}
} // namespace pushbox

View File

@ -1,21 +0,0 @@
#pragma once
#include <extra2d/extra2d.h>
namespace pushbox {
class MenuButton : public extra2d::Button {
public:
static extra2d::Ptr<MenuButton> create(extra2d::Ptr<extra2d::FontAtlas> font,
const extra2d::String& text,
extra2d::Function<void()> onClick);
void setEnabled(bool enabled);
bool isEnabled() const { return enabled_; }
private:
bool enabled_ = true;
extra2d::Function<void()> onClick_;
};
} // namespace pushbox

View File

@ -20,6 +20,7 @@ target("push_box")
set_toolchains("switch")
set_targetdir("../../build/examples/push_box")
-- 构建后生成 NRO 文件
after_build(function (target)
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
local elf_file = target:targetfile()
@ -37,6 +38,17 @@ target("push_box")
else
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
end
print("Generated NRO: " .. nro_file)
end
end)
-- 打包时将 NRO 文件复制到 package 目录
after_package(function (target)
local nro_file = path.join(target:targetdir(), "push_box.nro")
local package_dir = target:packagedir()
if os.isfile(nro_file) and package_dir then
os.cp(nro_file, package_dir)
print("Copied NRO to package: " .. package_dir)
end
end)
@ -46,20 +58,39 @@ target("push_box")
set_targetdir("../../build/examples/push_box")
add_ldflags("-mwindows", {force = true})
-- 复制资源
-- 复制资源到输出目录
after_build(function (target)
local romfs = path.join(example_dir, "romfs")
if os.isdir(romfs) then
local target_dir = path.directory(target:targetfile())
local assets_dir = path.join(target_dir, "assets")
-- 创建 assets 目录
if not os.isdir(assets_dir) then
os.mkdir(assets_dir)
end
os.cp(path.join(romfs, "assets/*"), assets_dir)
-- 复制所有资源文件(包括子目录)
os.cp(path.join(romfs, "assets/**"), assets_dir)
print("Copied assets from " .. romfs .. " to " .. assets_dir)
else
print("Warning: romfs directory not found at " .. romfs)
end
end)
-- 打包时将资源复制到 package 目录
after_package(function (target)
local target_dir = path.directory(target:targetfile())
local assets_dir = path.join(target_dir, "assets")
local package_dir = target:packagedir()
if os.isdir(assets_dir) and package_dir then
local package_assets = path.join(package_dir, "assets")
if not os.isdir(package_assets) then
os.mkdir(package_assets)
end
os.cp(path.join(assets_dir, "**"), package_assets)
print("Copied assets to package: " .. package_assets)
end
end)
end
target_end()

View File

@ -178,30 +178,135 @@ public:
}
void onRender(RenderBackend &renderer) override {
Scene::onRender(renderer);
auto renderStart = std::chrono::high_resolution_clock::now();
// 节点渲染由Scene自动处理
Scene::onRender(renderer);
auto renderEnd = std::chrono::high_resolution_clock::now();
stats_.renderTime =
std::chrono::duration<double, std::milli>(renderEnd - renderStart)
.count();
// 绘制UI
drawUI(renderer);
// 更新UI文本内容
updateUI();
// 绘制图例方块Text组件会自动渲染
drawLegend(renderer);
}
private:
/**
* @brief
* @brief UI文本组件
*/
void loadFonts() {
auto &resources = Application::instance().resources();
titleFont_ = resources.loadFont("assets/font.ttf", 28, true);
infoFont_ = resources.loadFont("assets/font.ttf", 16, true);
// 创建标题文本
titleText_ = Text::create("引擎空间索引演示", titleFont_);
titleText_->setPosition(30.0f, 20.0f);
titleText_->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f));
addChild(titleText_);
float x = 30.0f;
float y = 60.0f;
float lineHeight = 22.0f;
// 创建统计信息文本
nodeCountText_ = Text::create("", infoFont_);
nodeCountText_->setPosition(x, y);
nodeCountText_->setTextColor(Color(0.9f, 0.9f, 0.9f, 1.0f));
addChild(nodeCountText_);
y += lineHeight;
strategyText_ = Text::create("", infoFont_);
strategyText_->setPosition(x, y);
strategyText_->setTextColor(Color(0.5f, 1.0f, 0.5f, 1.0f));
addChild(strategyText_);
y += lineHeight;
collisionText_ = Text::create("", infoFont_);
collisionText_->setPosition(x, y);
collisionText_->setTextColor(Color(1.0f, 0.5f, 0.5f, 1.0f));
addChild(collisionText_);
y += lineHeight;
updateTimeText_ = Text::create("", infoFont_);
updateTimeText_->setPosition(x, y);
updateTimeText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f));
addChild(updateTimeText_);
y += lineHeight;
collisionTimeText_ = Text::create("", infoFont_);
collisionTimeText_->setPosition(x, y);
collisionTimeText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f));
addChild(collisionTimeText_);
y += lineHeight;
renderTimeText_ = Text::create("", infoFont_);
renderTimeText_->setPosition(x, y);
renderTimeText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f));
addChild(renderTimeText_);
y += lineHeight;
fpsText_ = Text::create("", infoFont_);
fpsText_->setPosition(x, y);
fpsText_->setTextColor(Color(0.5f, 1.0f, 0.5f, 1.0f));
addChild(fpsText_);
y += lineHeight * 1.5f;
// 创建操作说明文本
helpTitleText_ = Text::create("操作说明:", infoFont_);
helpTitleText_->setPosition(x, y);
helpTitleText_->setTextColor(Color(1.0f, 1.0f, 0.5f, 1.0f));
addChild(helpTitleText_);
y += lineHeight;
helpAddText_ = Text::create("A键 - 添加100个节点", infoFont_);
helpAddText_->setPosition(x + 10, y);
helpAddText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f));
addChild(helpAddText_);
y += lineHeight;
helpRemoveText_ = Text::create("B键 - 移除100个节点", infoFont_);
helpRemoveText_->setPosition(x + 10, y);
helpRemoveText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f));
addChild(helpRemoveText_);
y += lineHeight;
helpToggleText_ = Text::create("X键 - 切换索引策略", infoFont_);
helpToggleText_->setPosition(x + 10, y);
helpToggleText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f));
addChild(helpToggleText_);
y += lineHeight;
helpExitText_ = Text::create("+键 - 退出程序", infoFont_);
helpExitText_->setPosition(x + 10, y);
helpExitText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f));
addChild(helpExitText_);
// 创建图例文本
float legendX = screenWidth_ - 200.0f;
float legendY = 20.0f;
legendTitleText_ = Text::create("图例:", infoFont_);
legendTitleText_->setPosition(legendX, legendY);
legendTitleText_->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f));
addChild(legendTitleText_);
legendY += 25.0f;
legendNormalText_ = Text::create("- 正常", infoFont_);
legendNormalText_->setPosition(legendX + 20.0f, legendY);
legendNormalText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f));
addChild(legendNormalText_);
legendY += 25.0f;
legendCollidingText_ = Text::create("- 碰撞中", infoFont_);
legendCollidingText_->setPosition(legendX + 20.0f, legendY);
legendCollidingText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f));
addChild(legendCollidingText_);
}
/**
@ -302,99 +407,39 @@ private:
}
/**
* @brief UI界面
* @brief UI文本内容
*/
void drawUI(RenderBackend &renderer) {
if (!titleFont_ || !infoFont_)
void updateUI() {
if (!nodeCountText_)
return;
auto &app = Application::instance();
// 绘制标题
renderer.drawText(*titleFont_, "引擎空间索引演示", Vec2(30.0f, 20.0f),
Color(1.0f, 1.0f, 1.0f, 1.0f));
// 使用 setFormat 格式化文本
nodeCountText_->setFormat("节点数量: %zu", stats_.nodeCount);
strategyText_->setFormat("索引策略: %s", stats_.strategyName);
collisionText_->setFormat("碰撞对数: %zu", stats_.collisionCount);
updateTimeText_->setFormat("更新时间: %.2f ms", stats_.updateTime);
collisionTimeText_->setFormat("碰撞检测: %.2f ms", stats_.collisionTime);
renderTimeText_->setFormat("渲染时间: %.2f ms", stats_.renderTime);
fpsText_->setFormat("FPS: %u", app.fps());
}
// 绘制性能统计
std::stringstream ss;
float x = 30.0f;
float y = 60.0f;
float lineHeight = 22.0f;
ss << "节点数量: " << stats_.nodeCount;
renderer.drawText(*infoFont_, ss.str(), Vec2(x, y),
Color(0.9f, 0.9f, 0.9f, 1.0f));
y += lineHeight;
ss.str("");
ss << "索引策略: " << stats_.strategyName;
renderer.drawText(*infoFont_, ss.str(), Vec2(x, y),
Color(0.5f, 1.0f, 0.5f, 1.0f));
y += lineHeight;
ss.str("");
ss << "碰撞对数: " << stats_.collisionCount;
renderer.drawText(*infoFont_, ss.str(), Vec2(x, y),
Color(1.0f, 0.5f, 0.5f, 1.0f));
y += lineHeight;
ss.str("");
ss << std::fixed << std::setprecision(2);
ss << "更新时间: " << stats_.updateTime << " ms";
renderer.drawText(*infoFont_, ss.str(), Vec2(x, y),
Color(0.8f, 0.8f, 0.8f, 1.0f));
y += lineHeight;
ss.str("");
ss << "碰撞检测: " << stats_.collisionTime << " ms";
renderer.drawText(*infoFont_, ss.str(), Vec2(x, y),
Color(0.8f, 0.8f, 0.8f, 1.0f));
y += lineHeight;
ss.str("");
ss << "渲染时间: " << stats_.renderTime << " ms";
renderer.drawText(*infoFont_, ss.str(), Vec2(x, y),
Color(0.8f, 0.8f, 0.8f, 1.0f));
y += lineHeight;
ss.str("");
ss << "FPS: " << app.fps();
renderer.drawText(*infoFont_, ss.str(), Vec2(x, y),
Color(0.5f, 1.0f, 0.5f, 1.0f));
y += lineHeight * 1.5f;
// 绘制操作说明
renderer.drawText(*infoFont_, "操作说明:", Vec2(x, y),
Color(1.0f, 1.0f, 0.5f, 1.0f));
y += lineHeight;
renderer.drawText(*infoFont_, "A键 - 添加100个节点", Vec2(x + 10, y),
Color(0.8f, 0.8f, 0.8f, 1.0f));
y += lineHeight;
renderer.drawText(*infoFont_, "B键 - 移除100个节点", Vec2(x + 10, y),
Color(0.8f, 0.8f, 0.8f, 1.0f));
y += lineHeight;
renderer.drawText(*infoFont_, "X键 - 切换索引策略", Vec2(x + 10, y),
Color(0.8f, 0.8f, 0.8f, 1.0f));
y += lineHeight;
renderer.drawText(*infoFont_, "+键 - 退出程序", Vec2(x + 10, y),
Color(0.8f, 0.8f, 0.8f, 1.0f));
// 绘制图例
/**
* @brief
*/
void drawLegend(RenderBackend &renderer) {
float legendX = screenWidth_ - 200.0f;
float legendY = 20.0f;
renderer.drawText(*infoFont_, "图例:", Vec2(legendX, legendY),
Color(1.0f, 1.0f, 1.0f, 1.0f));
legendY += 25.0f;
float legendY = 20.0f + 25.0f; // 在标题下方
// 绘制正常状态方块
renderer.fillRect(Rect(legendX, legendY, 15.0f, 15.0f),
Color(0.5f, 0.5f, 0.9f, 0.7f));
renderer.drawText(*infoFont_, "- 正常", Vec2(legendX + 20.0f, legendY),
Color(0.8f, 0.8f, 0.8f, 1.0f));
legendY += 25.0f;
// 绘制碰撞状态方块
renderer.fillRect(Rect(legendX, legendY, 15.0f, 15.0f),
Color(1.0f, 0.2f, 0.2f, 0.9f));
renderer.drawText(*infoFont_, "- 碰撞中", Vec2(legendX + 20.0f, legendY),
Color(0.8f, 0.8f, 0.8f, 1.0f));
}
std::vector<Ptr<PhysicsNode>> nodes_;
@ -404,6 +449,24 @@ private:
Ptr<FontAtlas> titleFont_;
Ptr<FontAtlas> infoFont_;
// UI 文本组件
Ptr<Text> titleText_;
Ptr<Text> nodeCountText_;
Ptr<Text> strategyText_;
Ptr<Text> collisionText_;
Ptr<Text> updateTimeText_;
Ptr<Text> collisionTimeText_;
Ptr<Text> renderTimeText_;
Ptr<Text> fpsText_;
Ptr<Text> helpTitleText_;
Ptr<Text> helpAddText_;
Ptr<Text> helpRemoveText_;
Ptr<Text> helpToggleText_;
Ptr<Text> helpExitText_;
Ptr<Text> legendTitleText_;
Ptr<Text> legendNormalText_;
Ptr<Text> legendCollidingText_;
};
// ============================================================================

View File

@ -20,6 +20,7 @@ target("spatial_index_demo")
set_toolchains("switch")
set_targetdir("../../build/examples/spatial_index_demo")
-- 构建后生成 NRO 文件
after_build(function (target)
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
local elf_file = target:targetfile()
@ -37,6 +38,17 @@ target("spatial_index_demo")
else
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
end
print("Generated NRO: " .. nro_file)
end
end)
-- 打包时将 NRO 文件复制到 package 目录
after_package(function (target)
local nro_file = path.join(target:targetdir(), "spatial_index_demo.nro")
local package_dir = target:packagedir()
if os.isfile(nro_file) and package_dir then
os.cp(nro_file, package_dir)
print("Copied NRO to package: " .. package_dir)
end
end)
@ -46,20 +58,39 @@ target("spatial_index_demo")
set_targetdir("../../build/examples/spatial_index_demo")
add_ldflags("-mwindows", {force = true})
-- 复制资源
-- 复制资源到输出目录
after_build(function (target)
local romfs = path.join(example_dir, "romfs")
if os.isdir(romfs) then
local target_dir = path.directory(target:targetfile())
local assets_dir = path.join(target_dir, "assets")
-- 创建 assets 目录
if not os.isdir(assets_dir) then
os.mkdir(assets_dir)
end
os.cp(path.join(romfs, "assets/*"), assets_dir)
-- 复制所有资源文件(包括子目录)
os.cp(path.join(romfs, "assets/**"), assets_dir)
print("Copied assets from " .. romfs .. " to " .. assets_dir)
else
print("Warning: romfs directory not found at " .. romfs)
end
end)
-- 打包时将资源复制到 package 目录
after_package(function (target)
local target_dir = path.directory(target:targetfile())
local assets_dir = path.join(target_dir, "assets")
local package_dir = target:packagedir()
if os.isdir(assets_dir) and package_dir then
local package_assets = path.join(package_dir, "assets")
if not os.isdir(package_assets) then
os.mkdir(package_assets)
end
os.cp(path.join(assets_dir, "**"), package_assets)
print("Copied assets to package: " .. package_assets)
end
end)
end
target_end()

View File

@ -1,146 +0,0 @@
#ifdef _SQ64
#ifdef _MSC_VER
typedef __int64 SQInteger;
typedef unsigned __int64 SQUnsignedInteger;
typedef unsigned __int64 SQHash; /*should be the same size of a pointer*/
#else
typedef long long SQInteger;
typedef unsigned long long SQUnsignedInteger;
typedef unsigned long long SQHash; /*should be the same size of a pointer*/
#endif
typedef int SQInt32;
typedef unsigned int SQUnsignedInteger32;
#else
typedef int SQInteger;
typedef int SQInt32; /*must be 32 bits(also on 64bits processors)*/
typedef unsigned int SQUnsignedInteger32; /*must be 32 bits(also on 64bits processors)*/
typedef unsigned int SQUnsignedInteger;
typedef unsigned int SQHash; /*should be the same size of a pointer*/
#endif
#ifdef SQUSEDOUBLE
typedef double SQFloat;
#else
typedef float SQFloat;
#endif
#if defined(SQUSEDOUBLE) && !defined(_SQ64) || !defined(SQUSEDOUBLE) && defined(_SQ64)
#ifdef _MSC_VER
typedef __int64 SQRawObjectVal; //must be 64bits
#else
typedef long long SQRawObjectVal; //must be 64bits
#endif
#define SQ_OBJECT_RAWINIT() { _unVal.raw = 0; }
#else
typedef SQUnsignedInteger SQRawObjectVal; //is 32 bits on 32 bits builds and 64 bits otherwise
#define SQ_OBJECT_RAWINIT()
#endif
#ifndef SQ_ALIGNMENT // SQ_ALIGNMENT shall be less than or equal to SQ_MALLOC alignments, and its value shall be power of 2.
#if defined(SQUSEDOUBLE) || defined(_SQ64)
#define SQ_ALIGNMENT 8
#else
#define SQ_ALIGNMENT 4
#endif
#endif
typedef void* SQUserPointer;
typedef SQUnsignedInteger SQBool;
typedef SQInteger SQRESULT;
#ifdef SQUNICODE
#include <wchar.h>
#include <wctype.h>
typedef wchar_t SQChar;
#define scstrcmp wcscmp
#ifdef _WIN32
#define scsprintf _snwprintf
#else
#define scsprintf swprintf
#endif
#define scstrlen wcslen
#define scstrtod wcstod
#ifdef _SQ64
#define scstrtol wcstoll
#else
#define scstrtol wcstol
#endif
#define scstrtoul wcstoul
#define scvsprintf vswprintf
#define scstrstr wcsstr
#define scprintf wprintf
#ifdef _WIN32
#define WCHAR_SIZE 2
#define WCHAR_SHIFT_MUL 1
#define MAX_CHAR 0xFFFF
#else
#define WCHAR_SIZE 4
#define WCHAR_SHIFT_MUL 2
#define MAX_CHAR 0xFFFFFFFF
#endif
#define _SC(a) L##a
#define scisspace iswspace
#define scisdigit iswdigit
#define scisprint iswprint
#define scisxdigit iswxdigit
#define scisalpha iswalpha
#define sciscntrl iswcntrl
#define scisalnum iswalnum
#define sq_rsl(l) ((l)<<WCHAR_SHIFT_MUL)
#else
typedef char SQChar;
#define _SC(a) a
#define scstrcmp strcmp
#ifdef _MSC_VER
#define scsprintf _snprintf
#else
#define scsprintf snprintf
#endif
#define scstrlen strlen
#define scstrtod strtod
#ifdef _SQ64
#ifdef _MSC_VER
#define scstrtol _strtoi64
#else
#define scstrtol strtoll
#endif
#else
#define scstrtol strtol
#endif
#define scstrtoul strtoul
#define scvsprintf vsnprintf
#define scstrstr strstr
#define scisspace isspace
#define scisdigit isdigit
#define scisprint isprint
#define scisxdigit isxdigit
#define sciscntrl iscntrl
#define scisalpha isalpha
#define scisalnum isalnum
#define scprintf printf
#define MAX_CHAR 0xFF
#define sq_rsl(l) (l)
#endif
#ifdef _SQ64
#define _PRINT_INT_PREC _SC("ll")
#define _PRINT_INT_FMT _SC("%lld")
#else
#define _PRINT_INT_FMT _SC("%d")
#endif

View File

@ -1,18 +0,0 @@
/* see copyright notice in squirrel.h */
#ifndef _SQSTD_AUXLIB_H_
#define _SQSTD_AUXLIB_H_
#ifdef __cplusplus
extern "C" {
#endif
SQUIRREL_API void sqstd_seterrorhandlers(HSQUIRRELVM v);
SQUIRREL_API void sqstd_printcallstack(HSQUIRRELVM v);
SQUIRREL_API SQRESULT sqstd_throwerrorf(HSQUIRRELVM v,const SQChar *err,...);
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /* _SQSTD_AUXLIB_H_ */

View File

@ -1,20 +0,0 @@
/* see copyright notice in squirrel.h */
#ifndef _SQSTDBLOB_H_
#define _SQSTDBLOB_H_
#ifdef __cplusplus
extern "C" {
#endif
SQUIRREL_API SQUserPointer sqstd_createblob(HSQUIRRELVM v, SQInteger size);
SQUIRREL_API SQRESULT sqstd_getblob(HSQUIRRELVM v,SQInteger idx,SQUserPointer *ptr);
SQUIRREL_API SQInteger sqstd_getblobsize(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sqstd_register_bloblib(HSQUIRRELVM v);
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*_SQSTDBLOB_H_*/

View File

@ -1,54 +0,0 @@
/* see copyright notice in squirrel.h */
#ifndef _SQSTDIO_H_
#define _SQSTDIO_H_
#ifdef __cplusplus
#define SQSTD_STREAM_TYPE_TAG 0x80000000
struct SQStream {
virtual ~SQStream() {}
virtual SQInteger Read(void *buffer, SQInteger size) = 0;
virtual SQInteger Write(void *buffer, SQInteger size) = 0;
virtual SQInteger Flush() = 0;
virtual SQInteger Tell() = 0;
virtual SQInteger Len() = 0;
virtual SQInteger Seek(SQInteger offset, SQInteger origin) = 0;
virtual bool IsValid() = 0;
virtual bool EOS() = 0;
};
extern "C" {
#endif
#define SQ_SEEK_CUR 0
#define SQ_SEEK_END 1
#define SQ_SEEK_SET 2
typedef void* SQFILE;
SQUIRREL_API SQFILE sqstd_fopen(const SQChar *,const SQChar *);
SQUIRREL_API SQInteger sqstd_fread(SQUserPointer, SQInteger, SQInteger, SQFILE);
SQUIRREL_API SQInteger sqstd_fwrite(const SQUserPointer, SQInteger, SQInteger, SQFILE);
SQUIRREL_API SQInteger sqstd_fseek(SQFILE , SQInteger , SQInteger);
SQUIRREL_API SQInteger sqstd_ftell(SQFILE);
SQUIRREL_API SQInteger sqstd_fflush(SQFILE);
SQUIRREL_API SQInteger sqstd_fclose(SQFILE);
SQUIRREL_API SQInteger sqstd_feof(SQFILE);
SQUIRREL_API SQRESULT sqstd_createfile(HSQUIRRELVM v, SQFILE file,SQBool own);
SQUIRREL_API SQRESULT sqstd_getfile(HSQUIRRELVM v, SQInteger idx, SQFILE *file);
//compiler helpers
SQUIRREL_API SQRESULT sqstd_loadfile(HSQUIRRELVM v,const SQChar *filename,SQBool printerror);
SQUIRREL_API SQRESULT sqstd_dofile(HSQUIRRELVM v,const SQChar *filename,SQBool retval,SQBool printerror);
SQUIRREL_API SQRESULT sqstd_writeclosuretofile(HSQUIRRELVM v,const SQChar *filename);
SQUIRREL_API SQRESULT sqstd_register_iolib(HSQUIRRELVM v);
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*_SQSTDIO_H_*/

View File

@ -1,15 +0,0 @@
/* see copyright notice in squirrel.h */
#ifndef _SQSTD_MATH_H_
#define _SQSTD_MATH_H_
#ifdef __cplusplus
extern "C" {
#endif
SQUIRREL_API SQRESULT sqstd_register_mathlib(HSQUIRRELVM v);
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*_SQSTD_MATH_H_*/

View File

@ -1,35 +0,0 @@
/* see copyright notice in squirrel.h */
#ifndef _SQSTD_STRING_H_
#define _SQSTD_STRING_H_
#ifdef __cplusplus
extern "C" {
#endif
typedef unsigned int SQRexBool;
typedef struct SQRex SQRex;
typedef struct {
const SQChar *begin;
SQInteger len;
} SQRexMatch;
SQUIRREL_API SQRex *sqstd_rex_compile(const SQChar *pattern,const SQChar **error);
SQUIRREL_API void sqstd_rex_free(SQRex *exp);
SQUIRREL_API SQBool sqstd_rex_match(SQRex* exp,const SQChar* text);
SQUIRREL_API SQBool sqstd_rex_search(SQRex* exp,const SQChar* text, const SQChar** out_begin, const SQChar** out_end);
SQUIRREL_API SQBool sqstd_rex_searchrange(SQRex* exp,const SQChar* text_begin,const SQChar* text_end,const SQChar** out_begin, const SQChar** out_end);
SQUIRREL_API SQInteger sqstd_rex_getsubexpcount(SQRex* exp);
SQUIRREL_API SQBool sqstd_rex_getsubexp(SQRex* exp, SQInteger n, SQRexMatch *subexp);
SQUIRREL_API SQRESULT sqstd_format(HSQUIRRELVM v,SQInteger nformatstringidx,SQInteger *outlen,SQChar **output);
SQUIRREL_API void sqstd_pushstringf(HSQUIRRELVM v,const SQChar *s,...);
SQUIRREL_API SQRESULT sqstd_register_stringlib(HSQUIRRELVM v);
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*_SQSTD_STRING_H_*/

View File

@ -1,15 +0,0 @@
/* see copyright notice in squirrel.h */
#ifndef _SQSTD_SYSTEMLIB_H_
#define _SQSTD_SYSTEMLIB_H_
#ifdef __cplusplus
extern "C" {
#endif
SQUIRREL_API SQInteger sqstd_register_systemlib(HSQUIRRELVM v);
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /* _SQSTD_SYSTEMLIB_H_ */

View File

@ -1,411 +0,0 @@
/*
Copyright (c) 2003-2022 Alberto Demichelis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef _SQUIRREL_H_
#define _SQUIRREL_H_
#ifdef _SQ_CONFIG_INCLUDE
#include _SQ_CONFIG_INCLUDE
#endif
#ifdef __cplusplus
extern "C" {
#endif
#ifndef SQUIRREL_API
#define SQUIRREL_API extern
#endif
#if (defined(_WIN64) || defined(_LP64))
#ifndef _SQ64
#define _SQ64
#endif
#endif
#define SQTrue (1)
#define SQFalse (0)
struct SQVM;
struct SQTable;
struct SQArray;
struct SQString;
struct SQClosure;
struct SQGenerator;
struct SQNativeClosure;
struct SQUserData;
struct SQFunctionProto;
struct SQRefCounted;
struct SQClass;
struct SQInstance;
struct SQDelegable;
struct SQOuter;
#ifdef _UNICODE
#define SQUNICODE
#endif
#include "sqconfig.h"
#define SQUIRREL_VERSION _SC("Squirrel 3.2 stable")
#define SQUIRREL_COPYRIGHT _SC("Copyright (C) 2003-2022 Alberto Demichelis")
#define SQUIRREL_AUTHOR _SC("Alberto Demichelis")
#define SQUIRREL_VERSION_NUMBER 320
#define SQ_VMSTATE_IDLE 0
#define SQ_VMSTATE_RUNNING 1
#define SQ_VMSTATE_SUSPENDED 2
#define SQUIRREL_EOB 0
#define SQ_BYTECODE_STREAM_TAG 0xFAFA
#define SQOBJECT_REF_COUNTED 0x08000000
#define SQOBJECT_NUMERIC 0x04000000
#define SQOBJECT_DELEGABLE 0x02000000
#define SQOBJECT_CANBEFALSE 0x01000000
#define SQ_MATCHTYPEMASKSTRING (-99999)
#define _RT_MASK 0x00FFFFFF
#define _RAW_TYPE(type) (type&_RT_MASK)
#define _RT_NULL 0x00000001
#define _RT_INTEGER 0x00000002
#define _RT_FLOAT 0x00000004
#define _RT_BOOL 0x00000008
#define _RT_STRING 0x00000010
#define _RT_TABLE 0x00000020
#define _RT_ARRAY 0x00000040
#define _RT_USERDATA 0x00000080
#define _RT_CLOSURE 0x00000100
#define _RT_NATIVECLOSURE 0x00000200
#define _RT_GENERATOR 0x00000400
#define _RT_USERPOINTER 0x00000800
#define _RT_THREAD 0x00001000
#define _RT_FUNCPROTO 0x00002000
#define _RT_CLASS 0x00004000
#define _RT_INSTANCE 0x00008000
#define _RT_WEAKREF 0x00010000
#define _RT_OUTER 0x00020000
typedef enum tagSQObjectType{
OT_NULL = (_RT_NULL|SQOBJECT_CANBEFALSE),
OT_INTEGER = (_RT_INTEGER|SQOBJECT_NUMERIC|SQOBJECT_CANBEFALSE),
OT_FLOAT = (_RT_FLOAT|SQOBJECT_NUMERIC|SQOBJECT_CANBEFALSE),
OT_BOOL = (_RT_BOOL|SQOBJECT_CANBEFALSE),
OT_STRING = (_RT_STRING|SQOBJECT_REF_COUNTED),
OT_TABLE = (_RT_TABLE|SQOBJECT_REF_COUNTED|SQOBJECT_DELEGABLE),
OT_ARRAY = (_RT_ARRAY|SQOBJECT_REF_COUNTED),
OT_USERDATA = (_RT_USERDATA|SQOBJECT_REF_COUNTED|SQOBJECT_DELEGABLE),
OT_CLOSURE = (_RT_CLOSURE|SQOBJECT_REF_COUNTED),
OT_NATIVECLOSURE = (_RT_NATIVECLOSURE|SQOBJECT_REF_COUNTED),
OT_GENERATOR = (_RT_GENERATOR|SQOBJECT_REF_COUNTED),
OT_USERPOINTER = _RT_USERPOINTER,
OT_THREAD = (_RT_THREAD|SQOBJECT_REF_COUNTED) ,
OT_FUNCPROTO = (_RT_FUNCPROTO|SQOBJECT_REF_COUNTED), //internal usage only
OT_CLASS = (_RT_CLASS|SQOBJECT_REF_COUNTED),
OT_INSTANCE = (_RT_INSTANCE|SQOBJECT_REF_COUNTED|SQOBJECT_DELEGABLE),
OT_WEAKREF = (_RT_WEAKREF|SQOBJECT_REF_COUNTED),
OT_OUTER = (_RT_OUTER|SQOBJECT_REF_COUNTED) //internal usage only
}SQObjectType;
#define ISREFCOUNTED(t) (t&SQOBJECT_REF_COUNTED)
typedef union tagSQObjectValue
{
struct SQTable *pTable;
struct SQArray *pArray;
struct SQClosure *pClosure;
struct SQOuter *pOuter;
struct SQGenerator *pGenerator;
struct SQNativeClosure *pNativeClosure;
struct SQString *pString;
struct SQUserData *pUserData;
SQInteger nInteger;
SQFloat fFloat;
SQUserPointer pUserPointer;
struct SQFunctionProto *pFunctionProto;
struct SQRefCounted *pRefCounted;
struct SQDelegable *pDelegable;
struct SQVM *pThread;
struct SQClass *pClass;
struct SQInstance *pInstance;
struct SQWeakRef *pWeakRef;
SQRawObjectVal raw;
}SQObjectValue;
typedef struct tagSQObject
{
SQObjectType _type;
SQObjectValue _unVal;
}SQObject;
typedef struct tagSQMemberHandle{
SQBool _static;
SQInteger _index;
}SQMemberHandle;
typedef struct tagSQStackInfos{
const SQChar* funcname;
const SQChar* source;
SQInteger line;
}SQStackInfos;
typedef struct SQVM* HSQUIRRELVM;
typedef SQObject HSQOBJECT;
typedef SQMemberHandle HSQMEMBERHANDLE;
typedef SQInteger (*SQFUNCTION)(HSQUIRRELVM);
typedef SQInteger (*SQRELEASEHOOK)(SQUserPointer,SQInteger size);
typedef void (*SQCOMPILERERROR)(HSQUIRRELVM,const SQChar * /*desc*/,const SQChar * /*source*/,SQInteger /*line*/,SQInteger /*column*/);
typedef void (*SQPRINTFUNCTION)(HSQUIRRELVM,const SQChar * ,...);
typedef void (*SQDEBUGHOOK)(HSQUIRRELVM /*v*/, SQInteger /*type*/, const SQChar * /*sourcename*/, SQInteger /*line*/, const SQChar * /*funcname*/);
typedef SQInteger (*SQWRITEFUNC)(SQUserPointer,SQUserPointer,SQInteger);
typedef SQInteger (*SQREADFUNC)(SQUserPointer,SQUserPointer,SQInteger);
typedef SQInteger (*SQLEXREADFUNC)(SQUserPointer);
typedef struct tagSQRegFunction{
const SQChar *name;
SQFUNCTION f;
SQInteger nparamscheck;
const SQChar *typemask;
}SQRegFunction;
typedef struct tagSQFunctionInfo {
SQUserPointer funcid;
const SQChar *name;
const SQChar *source;
SQInteger line;
}SQFunctionInfo;
/*vm*/
SQUIRREL_API HSQUIRRELVM sq_open(SQInteger initialstacksize);
SQUIRREL_API HSQUIRRELVM sq_newthread(HSQUIRRELVM friendvm, SQInteger initialstacksize);
SQUIRREL_API void sq_seterrorhandler(HSQUIRRELVM v);
SQUIRREL_API void sq_close(HSQUIRRELVM v);
SQUIRREL_API void sq_setforeignptr(HSQUIRRELVM v,SQUserPointer p);
SQUIRREL_API SQUserPointer sq_getforeignptr(HSQUIRRELVM v);
SQUIRREL_API void sq_setsharedforeignptr(HSQUIRRELVM v,SQUserPointer p);
SQUIRREL_API SQUserPointer sq_getsharedforeignptr(HSQUIRRELVM v);
SQUIRREL_API void sq_setvmreleasehook(HSQUIRRELVM v,SQRELEASEHOOK hook);
SQUIRREL_API SQRELEASEHOOK sq_getvmreleasehook(HSQUIRRELVM v);
SQUIRREL_API void sq_setsharedreleasehook(HSQUIRRELVM v,SQRELEASEHOOK hook);
SQUIRREL_API SQRELEASEHOOK sq_getsharedreleasehook(HSQUIRRELVM v);
SQUIRREL_API void sq_setprintfunc(HSQUIRRELVM v, SQPRINTFUNCTION printfunc,SQPRINTFUNCTION errfunc);
SQUIRREL_API SQPRINTFUNCTION sq_getprintfunc(HSQUIRRELVM v);
SQUIRREL_API SQPRINTFUNCTION sq_geterrorfunc(HSQUIRRELVM v);
SQUIRREL_API SQRESULT sq_suspendvm(HSQUIRRELVM v);
SQUIRREL_API SQRESULT sq_wakeupvm(HSQUIRRELVM v,SQBool resumedret,SQBool retval,SQBool raiseerror,SQBool throwerror);
SQUIRREL_API SQInteger sq_getvmstate(HSQUIRRELVM v);
SQUIRREL_API SQInteger sq_getversion();
/*compiler*/
SQUIRREL_API SQRESULT sq_compile(HSQUIRRELVM v,SQLEXREADFUNC read,SQUserPointer p,const SQChar *sourcename,SQBool raiseerror);
SQUIRREL_API SQRESULT sq_compilebuffer(HSQUIRRELVM v,const SQChar *s,SQInteger size,const SQChar *sourcename,SQBool raiseerror);
SQUIRREL_API void sq_enabledebuginfo(HSQUIRRELVM v, SQBool enable);
SQUIRREL_API void sq_notifyallexceptions(HSQUIRRELVM v, SQBool enable);
SQUIRREL_API void sq_setcompilererrorhandler(HSQUIRRELVM v,SQCOMPILERERROR f);
/*stack operations*/
SQUIRREL_API void sq_push(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API void sq_pop(HSQUIRRELVM v,SQInteger nelemstopop);
SQUIRREL_API void sq_poptop(HSQUIRRELVM v);
SQUIRREL_API void sq_remove(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQInteger sq_gettop(HSQUIRRELVM v);
SQUIRREL_API void sq_settop(HSQUIRRELVM v,SQInteger newtop);
SQUIRREL_API SQRESULT sq_reservestack(HSQUIRRELVM v,SQInteger nsize);
SQUIRREL_API SQInteger sq_cmp(HSQUIRRELVM v);
SQUIRREL_API void sq_move(HSQUIRRELVM dest,HSQUIRRELVM src,SQInteger idx);
/*object creation handling*/
SQUIRREL_API SQUserPointer sq_newuserdata(HSQUIRRELVM v,SQUnsignedInteger size);
SQUIRREL_API void sq_newtable(HSQUIRRELVM v);
SQUIRREL_API void sq_newtableex(HSQUIRRELVM v,SQInteger initialcapacity);
SQUIRREL_API void sq_newarray(HSQUIRRELVM v,SQInteger size);
SQUIRREL_API void sq_newclosure(HSQUIRRELVM v,SQFUNCTION func,SQUnsignedInteger nfreevars);
SQUIRREL_API SQRESULT sq_setparamscheck(HSQUIRRELVM v,SQInteger nparamscheck,const SQChar *typemask);
SQUIRREL_API SQRESULT sq_bindenv(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_setclosureroot(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_getclosureroot(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API void sq_pushstring(HSQUIRRELVM v,const SQChar *s,SQInteger len);
SQUIRREL_API void sq_pushfloat(HSQUIRRELVM v,SQFloat f);
SQUIRREL_API void sq_pushinteger(HSQUIRRELVM v,SQInteger n);
SQUIRREL_API void sq_pushbool(HSQUIRRELVM v,SQBool b);
SQUIRREL_API void sq_pushuserpointer(HSQUIRRELVM v,SQUserPointer p);
SQUIRREL_API void sq_pushnull(HSQUIRRELVM v);
SQUIRREL_API void sq_pushthread(HSQUIRRELVM v, HSQUIRRELVM thread);
SQUIRREL_API SQObjectType sq_gettype(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_typeof(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQInteger sq_getsize(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQHash sq_gethash(HSQUIRRELVM v, SQInteger idx);
SQUIRREL_API SQRESULT sq_getbase(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQBool sq_instanceof(HSQUIRRELVM v);
SQUIRREL_API SQRESULT sq_tostring(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API void sq_tobool(HSQUIRRELVM v, SQInteger idx, SQBool *b);
SQUIRREL_API SQRESULT sq_getstringandsize(HSQUIRRELVM v,SQInteger idx,const SQChar **c,SQInteger *size);
SQUIRREL_API SQRESULT sq_getstring(HSQUIRRELVM v,SQInteger idx,const SQChar **c);
SQUIRREL_API SQRESULT sq_getinteger(HSQUIRRELVM v,SQInteger idx,SQInteger *i);
SQUIRREL_API SQRESULT sq_getfloat(HSQUIRRELVM v,SQInteger idx,SQFloat *f);
SQUIRREL_API SQRESULT sq_getbool(HSQUIRRELVM v,SQInteger idx,SQBool *b);
SQUIRREL_API SQRESULT sq_getthread(HSQUIRRELVM v,SQInteger idx,HSQUIRRELVM *thread);
SQUIRREL_API SQRESULT sq_getuserpointer(HSQUIRRELVM v,SQInteger idx,SQUserPointer *p);
SQUIRREL_API SQRESULT sq_getuserdata(HSQUIRRELVM v,SQInteger idx,SQUserPointer *p,SQUserPointer *typetag);
SQUIRREL_API SQRESULT sq_settypetag(HSQUIRRELVM v,SQInteger idx,SQUserPointer typetag);
SQUIRREL_API SQRESULT sq_gettypetag(HSQUIRRELVM v,SQInteger idx,SQUserPointer *typetag);
SQUIRREL_API void sq_setreleasehook(HSQUIRRELVM v,SQInteger idx,SQRELEASEHOOK hook);
SQUIRREL_API SQRELEASEHOOK sq_getreleasehook(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQChar *sq_getscratchpad(HSQUIRRELVM v,SQInteger minsize);
SQUIRREL_API SQRESULT sq_getfunctioninfo(HSQUIRRELVM v,SQInteger level,SQFunctionInfo *fi);
SQUIRREL_API SQRESULT sq_getclosureinfo(HSQUIRRELVM v,SQInteger idx,SQInteger *nparams,SQInteger *nfreevars);
SQUIRREL_API SQRESULT sq_getclosurename(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_setnativeclosurename(HSQUIRRELVM v,SQInteger idx,const SQChar *name);
SQUIRREL_API SQRESULT sq_setinstanceup(HSQUIRRELVM v, SQInteger idx, SQUserPointer p);
SQUIRREL_API SQRESULT sq_getinstanceup(HSQUIRRELVM v, SQInteger idx, SQUserPointer *p,SQUserPointer typetag,SQBool throwerror);
SQUIRREL_API SQRESULT sq_setclassudsize(HSQUIRRELVM v, SQInteger idx, SQInteger udsize);
SQUIRREL_API SQRESULT sq_newclass(HSQUIRRELVM v,SQBool hasbase);
SQUIRREL_API SQRESULT sq_createinstance(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_setattributes(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_getattributes(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_getclass(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API void sq_weakref(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_getdefaultdelegate(HSQUIRRELVM v,SQObjectType t);
SQUIRREL_API SQRESULT sq_getmemberhandle(HSQUIRRELVM v,SQInteger idx,HSQMEMBERHANDLE *handle);
SQUIRREL_API SQRESULT sq_getbyhandle(HSQUIRRELVM v,SQInteger idx,const HSQMEMBERHANDLE *handle);
SQUIRREL_API SQRESULT sq_setbyhandle(HSQUIRRELVM v,SQInteger idx,const HSQMEMBERHANDLE *handle);
/*object manipulation*/
SQUIRREL_API void sq_pushroottable(HSQUIRRELVM v);
SQUIRREL_API void sq_pushregistrytable(HSQUIRRELVM v);
SQUIRREL_API void sq_pushconsttable(HSQUIRRELVM v);
SQUIRREL_API SQRESULT sq_setroottable(HSQUIRRELVM v);
SQUIRREL_API SQRESULT sq_setconsttable(HSQUIRRELVM v);
SQUIRREL_API SQRESULT sq_newslot(HSQUIRRELVM v, SQInteger idx, SQBool bstatic);
SQUIRREL_API SQRESULT sq_deleteslot(HSQUIRRELVM v,SQInteger idx,SQBool pushval);
SQUIRREL_API SQRESULT sq_set(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_get(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_rawget(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_rawset(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_rawdeleteslot(HSQUIRRELVM v,SQInteger idx,SQBool pushval);
SQUIRREL_API SQRESULT sq_newmember(HSQUIRRELVM v,SQInteger idx,SQBool bstatic);
SQUIRREL_API SQRESULT sq_rawnewmember(HSQUIRRELVM v,SQInteger idx,SQBool bstatic);
SQUIRREL_API SQRESULT sq_arrayappend(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_arraypop(HSQUIRRELVM v,SQInteger idx,SQBool pushval);
SQUIRREL_API SQRESULT sq_arrayresize(HSQUIRRELVM v,SQInteger idx,SQInteger newsize);
SQUIRREL_API SQRESULT sq_arrayreverse(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_arrayremove(HSQUIRRELVM v,SQInteger idx,SQInteger itemidx);
SQUIRREL_API SQRESULT sq_arrayinsert(HSQUIRRELVM v,SQInteger idx,SQInteger destpos);
SQUIRREL_API SQRESULT sq_setdelegate(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_getdelegate(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_clone(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_setfreevariable(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger nval);
SQUIRREL_API SQRESULT sq_next(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_getweakrefval(HSQUIRRELVM v,SQInteger idx);
SQUIRREL_API SQRESULT sq_clear(HSQUIRRELVM v,SQInteger idx);
/*calls*/
SQUIRREL_API SQRESULT sq_call(HSQUIRRELVM v,SQInteger params,SQBool retval,SQBool raiseerror);
SQUIRREL_API SQRESULT sq_resume(HSQUIRRELVM v,SQBool retval,SQBool raiseerror);
SQUIRREL_API const SQChar *sq_getlocal(HSQUIRRELVM v,SQUnsignedInteger level,SQUnsignedInteger idx);
SQUIRREL_API SQRESULT sq_getcallee(HSQUIRRELVM v);
SQUIRREL_API const SQChar *sq_getfreevariable(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger nval);
SQUIRREL_API SQRESULT sq_throwerror(HSQUIRRELVM v,const SQChar *err);
SQUIRREL_API SQRESULT sq_throwobject(HSQUIRRELVM v);
SQUIRREL_API void sq_reseterror(HSQUIRRELVM v);
SQUIRREL_API void sq_getlasterror(HSQUIRRELVM v);
SQUIRREL_API SQRESULT sq_tailcall(HSQUIRRELVM v, SQInteger nparams);
/*raw object handling*/
SQUIRREL_API SQRESULT sq_getstackobj(HSQUIRRELVM v,SQInteger idx,HSQOBJECT *po);
SQUIRREL_API void sq_pushobject(HSQUIRRELVM v,HSQOBJECT obj);
SQUIRREL_API void sq_addref(HSQUIRRELVM v,HSQOBJECT *po);
SQUIRREL_API SQBool sq_release(HSQUIRRELVM v,HSQOBJECT *po);
SQUIRREL_API SQUnsignedInteger sq_getrefcount(HSQUIRRELVM v,HSQOBJECT *po);
SQUIRREL_API void sq_resetobject(HSQOBJECT *po);
SQUIRREL_API const SQChar *sq_objtostring(const HSQOBJECT *o);
SQUIRREL_API SQBool sq_objtobool(const HSQOBJECT *o);
SQUIRREL_API SQInteger sq_objtointeger(const HSQOBJECT *o);
SQUIRREL_API SQFloat sq_objtofloat(const HSQOBJECT *o);
SQUIRREL_API SQUserPointer sq_objtouserpointer(const HSQOBJECT *o);
SQUIRREL_API SQRESULT sq_getobjtypetag(const HSQOBJECT *o,SQUserPointer * typetag);
SQUIRREL_API SQUnsignedInteger sq_getvmrefcount(HSQUIRRELVM v, const HSQOBJECT *po);
/*GC*/
SQUIRREL_API SQInteger sq_collectgarbage(HSQUIRRELVM v);
SQUIRREL_API SQRESULT sq_resurrectunreachable(HSQUIRRELVM v);
/*serialization*/
SQUIRREL_API SQRESULT sq_writeclosure(HSQUIRRELVM vm,SQWRITEFUNC writef,SQUserPointer up);
SQUIRREL_API SQRESULT sq_readclosure(HSQUIRRELVM vm,SQREADFUNC readf,SQUserPointer up);
/*mem allocation*/
SQUIRREL_API void *sq_malloc(SQUnsignedInteger size);
SQUIRREL_API void *sq_realloc(void* p,SQUnsignedInteger oldsize,SQUnsignedInteger newsize);
SQUIRREL_API void sq_free(void *p,SQUnsignedInteger size);
/*debug*/
SQUIRREL_API SQRESULT sq_stackinfos(HSQUIRRELVM v,SQInteger level,SQStackInfos *si);
SQUIRREL_API void sq_setdebughook(HSQUIRRELVM v);
SQUIRREL_API void sq_setnativedebughook(HSQUIRRELVM v,SQDEBUGHOOK hook);
/*UTILITY MACRO*/
#define sq_isnumeric(o) ((o)._type&SQOBJECT_NUMERIC)
#define sq_istable(o) ((o)._type==OT_TABLE)
#define sq_isarray(o) ((o)._type==OT_ARRAY)
#define sq_isfunction(o) ((o)._type==OT_FUNCPROTO)
#define sq_isclosure(o) ((o)._type==OT_CLOSURE)
#define sq_isgenerator(o) ((o)._type==OT_GENERATOR)
#define sq_isnativeclosure(o) ((o)._type==OT_NATIVECLOSURE)
#define sq_isstring(o) ((o)._type==OT_STRING)
#define sq_isinteger(o) ((o)._type==OT_INTEGER)
#define sq_isfloat(o) ((o)._type==OT_FLOAT)
#define sq_isuserpointer(o) ((o)._type==OT_USERPOINTER)
#define sq_isuserdata(o) ((o)._type==OT_USERDATA)
#define sq_isthread(o) ((o)._type==OT_THREAD)
#define sq_isnull(o) ((o)._type==OT_NULL)
#define sq_isclass(o) ((o)._type==OT_CLASS)
#define sq_isinstance(o) ((o)._type==OT_INSTANCE)
#define sq_isbool(o) ((o)._type==OT_BOOL)
#define sq_isweakref(o) ((o)._type==OT_WEAKREF)
#define sq_type(o) ((o)._type)
/* deprecated */
#define sq_createslot(v,n) sq_newslot(v,n,SQFalse)
#define SQ_OK (0)
#define SQ_ERROR (-1)
#define SQ_FAILED(res) (res<0)
#define SQ_SUCCEEDED(res) (res>=0)
#ifdef __GNUC__
# define SQ_UNUSED_ARG(x) x __attribute__((__unused__))
#else
# define SQ_UNUSED_ARG(x) x
#endif
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*_SQUIRREL_H_*/

View File

@ -1,151 +0,0 @@
/* see copyright notice in squirrel.h */
#include <squirrel.h>
#include <sqstdaux.h>
#include <stdio.h>
#include <assert.h>
#include <stdarg.h>
void sqstd_printcallstack(HSQUIRRELVM v)
{
SQPRINTFUNCTION pf = sq_geterrorfunc(v);
if(pf) {
SQStackInfos si;
SQInteger i;
SQFloat f;
const SQChar *s;
SQInteger level=1; //1 is to skip this function that is level 0
const SQChar *name=0;
SQInteger seq=0;
pf(v,_SC("\nCALLSTACK\n"));
while(SQ_SUCCEEDED(sq_stackinfos(v,level,&si)))
{
const SQChar *fn=_SC("unknown");
const SQChar *src=_SC("unknown");
if(si.funcname)fn=si.funcname;
if(si.source)src=si.source;
pf(v,_SC("*FUNCTION [%s()] %s line [%d]\n"),fn,src,si.line);
level++;
}
level=0;
pf(v,_SC("\nLOCALS\n"));
for(level=0;level<10;level++){
seq=0;
while((name = sq_getlocal(v,level,seq)))
{
seq++;
switch(sq_gettype(v,-1))
{
case OT_NULL:
pf(v,_SC("[%s] NULL\n"),name);
break;
case OT_INTEGER:
sq_getinteger(v,-1,&i);
pf(v,_SC("[%s] %d\n"),name,i);
break;
case OT_FLOAT:
sq_getfloat(v,-1,&f);
pf(v,_SC("[%s] %.14g\n"),name,f);
break;
case OT_USERPOINTER:
pf(v,_SC("[%s] USERPOINTER\n"),name);
break;
case OT_STRING:
sq_getstring(v,-1,&s);
pf(v,_SC("[%s] \"%s\"\n"),name,s);
break;
case OT_TABLE:
pf(v,_SC("[%s] TABLE\n"),name);
break;
case OT_ARRAY:
pf(v,_SC("[%s] ARRAY\n"),name);
break;
case OT_CLOSURE:
pf(v,_SC("[%s] CLOSURE\n"),name);
break;
case OT_NATIVECLOSURE:
pf(v,_SC("[%s] NATIVECLOSURE\n"),name);
break;
case OT_GENERATOR:
pf(v,_SC("[%s] GENERATOR\n"),name);
break;
case OT_USERDATA:
pf(v,_SC("[%s] USERDATA\n"),name);
break;
case OT_THREAD:
pf(v,_SC("[%s] THREAD\n"),name);
break;
case OT_CLASS:
pf(v,_SC("[%s] CLASS\n"),name);
break;
case OT_INSTANCE:
pf(v,_SC("[%s] INSTANCE\n"),name);
break;
case OT_WEAKREF:
pf(v,_SC("[%s] WEAKREF\n"),name);
break;
case OT_BOOL:{
SQBool bval;
sq_getbool(v,-1,&bval);
pf(v,_SC("[%s] %s\n"),name,bval == SQTrue ? _SC("true"):_SC("false"));
}
break;
default: assert(0); break;
}
sq_pop(v,1);
}
}
}
}
static SQInteger _sqstd_aux_printerror(HSQUIRRELVM v)
{
SQPRINTFUNCTION pf = sq_geterrorfunc(v);
if(pf) {
const SQChar *sErr = 0;
if(sq_gettop(v)>=1) {
if(SQ_SUCCEEDED(sq_getstring(v,2,&sErr))) {
pf(v,_SC("\nAN ERROR HAS OCCURRED [%s]\n"),sErr);
}
else{
pf(v,_SC("\nAN ERROR HAS OCCURRED [unknown]\n"));
}
sqstd_printcallstack(v);
}
}
return 0;
}
void _sqstd_compiler_error(HSQUIRRELVM v,const SQChar *sErr,const SQChar *sSource,SQInteger line,SQInteger column)
{
SQPRINTFUNCTION pf = sq_geterrorfunc(v);
if(pf) {
pf(v,_SC("%s line = (%d) column = (%d) : error %s\n"),sSource,line,column,sErr);
}
}
void sqstd_seterrorhandlers(HSQUIRRELVM v)
{
sq_setcompilererrorhandler(v,_sqstd_compiler_error);
sq_newclosure(v,_sqstd_aux_printerror,0);
sq_seterrorhandler(v);
}
SQRESULT sqstd_throwerrorf(HSQUIRRELVM v,const SQChar *err,...)
{
SQInteger n=256;
va_list args;
begin:
va_start(args,err);
SQChar *b=sq_getscratchpad(v,n);
SQInteger r=scvsprintf(b,n,err,args);
va_end(args);
if (r>=n) {
n=r+1;//required+null
goto begin;
} else if (r<0) {
return sq_throwerror(v,_SC("@failed to generate formatted error message"));
} else {
return sq_throwerror(v,b);
}
}

View File

@ -1,283 +0,0 @@
/* see copyright notice in squirrel.h */
#include <new>
#include <squirrel.h>
#include <sqstdio.h>
#include <string.h>
#include <sqstdblob.h>
#include "sqstdstream.h"
#include "sqstdblobimpl.h"
#define SQSTD_BLOB_TYPE_TAG ((SQUnsignedInteger)(SQSTD_STREAM_TYPE_TAG | 0x00000002))
//Blob
#define SETUP_BLOB(v) \
SQBlob *self = NULL; \
{ if(SQ_FAILED(sq_getinstanceup(v,1,(SQUserPointer*)&self,(SQUserPointer)SQSTD_BLOB_TYPE_TAG,SQFalse))) \
return sq_throwerror(v,_SC("invalid type tag")); } \
if(!self || !self->IsValid()) \
return sq_throwerror(v,_SC("the blob is invalid"));
static SQInteger _blob_resize(HSQUIRRELVM v)
{
SETUP_BLOB(v);
SQInteger size;
sq_getinteger(v,2,&size);
if(!self->Resize(size))
return sq_throwerror(v,_SC("resize failed"));
return 0;
}
static void __swap_dword(unsigned int *n)
{
*n=(unsigned int)(((*n&0xFF000000)>>24) |
((*n&0x00FF0000)>>8) |
((*n&0x0000FF00)<<8) |
((*n&0x000000FF)<<24));
}
static void __swap_word(unsigned short *n)
{
*n=(unsigned short)((*n>>8)&0x00FF)| ((*n<<8)&0xFF00);
}
static SQInteger _blob_swap4(HSQUIRRELVM v)
{
SETUP_BLOB(v);
SQInteger num=(self->Len()-(self->Len()%4))>>2;
unsigned int *t=(unsigned int *)self->GetBuf();
for(SQInteger i = 0; i < num; i++) {
__swap_dword(&t[i]);
}
return 0;
}
static SQInteger _blob_swap2(HSQUIRRELVM v)
{
SETUP_BLOB(v);
SQInteger num=(self->Len()-(self->Len()%2))>>1;
unsigned short *t = (unsigned short *)self->GetBuf();
for(SQInteger i = 0; i < num; i++) {
__swap_word(&t[i]);
}
return 0;
}
static SQInteger _blob__set(HSQUIRRELVM v)
{
SETUP_BLOB(v);
SQInteger idx,val;
sq_getinteger(v,2,&idx);
sq_getinteger(v,3,&val);
if(idx < 0 || idx >= self->Len())
return sq_throwerror(v,_SC("index out of range"));
((unsigned char *)self->GetBuf())[idx] = (unsigned char) val;
sq_push(v,3);
return 1;
}
static SQInteger _blob__get(HSQUIRRELVM v)
{
SETUP_BLOB(v);
SQInteger idx;
if ((sq_gettype(v, 2) & SQOBJECT_NUMERIC) == 0)
{
sq_pushnull(v);
return sq_throwobject(v);
}
sq_getinteger(v,2,&idx);
if(idx < 0 || idx >= self->Len())
return sq_throwerror(v,_SC("index out of range"));
sq_pushinteger(v,((unsigned char *)self->GetBuf())[idx]);
return 1;
}
static SQInteger _blob__nexti(HSQUIRRELVM v)
{
SETUP_BLOB(v);
if(sq_gettype(v,2) == OT_NULL) {
sq_pushinteger(v, 0);
return 1;
}
SQInteger idx;
if(SQ_SUCCEEDED(sq_getinteger(v, 2, &idx))) {
if(idx+1 < self->Len()) {
sq_pushinteger(v, idx+1);
return 1;
}
sq_pushnull(v);
return 1;
}
return sq_throwerror(v,_SC("internal error (_nexti) wrong argument type"));
}
static SQInteger _blob__typeof(HSQUIRRELVM v)
{
sq_pushstring(v,_SC("blob"),-1);
return 1;
}
static SQInteger _blob_releasehook(SQUserPointer p, SQInteger SQ_UNUSED_ARG(size))
{
SQBlob *self = (SQBlob*)p;
self->~SQBlob();
sq_free(self,sizeof(SQBlob));
return 1;
}
static SQInteger _blob_constructor(HSQUIRRELVM v)
{
SQInteger nparam = sq_gettop(v);
SQInteger size = 0;
if(nparam == 2) {
sq_getinteger(v, 2, &size);
}
if(size < 0) return sq_throwerror(v, _SC("cannot create blob with negative size"));
//SQBlob *b = new SQBlob(size);
SQBlob *b = new (sq_malloc(sizeof(SQBlob)))SQBlob(size);
if(SQ_FAILED(sq_setinstanceup(v,1,b))) {
b->~SQBlob();
sq_free(b,sizeof(SQBlob));
return sq_throwerror(v, _SC("cannot create blob"));
}
sq_setreleasehook(v,1,_blob_releasehook);
return 0;
}
static SQInteger _blob__cloned(HSQUIRRELVM v)
{
SQBlob *other = NULL;
{
if(SQ_FAILED(sq_getinstanceup(v,2,(SQUserPointer*)&other,(SQUserPointer)SQSTD_BLOB_TYPE_TAG,SQFalse)))
return SQ_ERROR;
}
//SQBlob *thisone = new SQBlob(other->Len());
SQBlob *thisone = new (sq_malloc(sizeof(SQBlob)))SQBlob(other->Len());
memcpy(thisone->GetBuf(),other->GetBuf(),thisone->Len());
if(SQ_FAILED(sq_setinstanceup(v,1,thisone))) {
thisone->~SQBlob();
sq_free(thisone,sizeof(SQBlob));
return sq_throwerror(v, _SC("cannot clone blob"));
}
sq_setreleasehook(v,1,_blob_releasehook);
return 0;
}
#define _DECL_BLOB_FUNC(name,nparams,typecheck) {_SC(#name),_blob_##name,nparams,typecheck}
static const SQRegFunction _blob_methods[] = {
_DECL_BLOB_FUNC(constructor,-1,_SC("xn")),
_DECL_BLOB_FUNC(resize,2,_SC("xn")),
_DECL_BLOB_FUNC(swap2,1,_SC("x")),
_DECL_BLOB_FUNC(swap4,1,_SC("x")),
_DECL_BLOB_FUNC(_set,3,_SC("xnn")),
_DECL_BLOB_FUNC(_get,2,_SC("x.")),
_DECL_BLOB_FUNC(_typeof,1,_SC("x")),
_DECL_BLOB_FUNC(_nexti,2,_SC("x")),
_DECL_BLOB_FUNC(_cloned,2,_SC("xx")),
{NULL,(SQFUNCTION)0,0,NULL}
};
//GLOBAL FUNCTIONS
static SQInteger _g_blob_casti2f(HSQUIRRELVM v)
{
SQInteger i;
sq_getinteger(v,2,&i);
sq_pushfloat(v,*((const SQFloat *)&i));
return 1;
}
static SQInteger _g_blob_castf2i(HSQUIRRELVM v)
{
SQFloat f;
sq_getfloat(v,2,&f);
sq_pushinteger(v,*((const SQInteger *)&f));
return 1;
}
static SQInteger _g_blob_swap2(HSQUIRRELVM v)
{
SQInteger i;
sq_getinteger(v,2,&i);
unsigned short s = (unsigned short)i;
sq_pushinteger(v, ((s << 8) | ((s >> 8) & 0x00FFu)) & 0xFFFFu);
return 1;
}
static SQInteger _g_blob_swap4(HSQUIRRELVM v)
{
SQInteger i;
sq_getinteger(v,2,&i);
unsigned int t4 = (unsigned int)i;
__swap_dword(&t4);
sq_pushinteger(v,(SQInteger)t4);
return 1;
}
static SQInteger _g_blob_swapfloat(HSQUIRRELVM v)
{
SQFloat f;
sq_getfloat(v,2,&f);
__swap_dword((unsigned int *)&f);
sq_pushfloat(v,f);
return 1;
}
#define _DECL_GLOBALBLOB_FUNC(name,nparams,typecheck) {_SC(#name),_g_blob_##name,nparams,typecheck}
static const SQRegFunction bloblib_funcs[]={
_DECL_GLOBALBLOB_FUNC(casti2f,2,_SC(".n")),
_DECL_GLOBALBLOB_FUNC(castf2i,2,_SC(".n")),
_DECL_GLOBALBLOB_FUNC(swap2,2,_SC(".n")),
_DECL_GLOBALBLOB_FUNC(swap4,2,_SC(".n")),
_DECL_GLOBALBLOB_FUNC(swapfloat,2,_SC(".n")),
{NULL,(SQFUNCTION)0,0,NULL}
};
SQRESULT sqstd_getblob(HSQUIRRELVM v,SQInteger idx,SQUserPointer *ptr)
{
SQBlob *blob;
if(SQ_FAILED(sq_getinstanceup(v,idx,(SQUserPointer *)&blob,(SQUserPointer)SQSTD_BLOB_TYPE_TAG,SQTrue)))
return -1;
*ptr = blob->GetBuf();
return SQ_OK;
}
SQInteger sqstd_getblobsize(HSQUIRRELVM v,SQInteger idx)
{
SQBlob *blob;
if(SQ_FAILED(sq_getinstanceup(v,idx,(SQUserPointer *)&blob,(SQUserPointer)SQSTD_BLOB_TYPE_TAG,SQTrue)))
return -1;
return blob->Len();
}
SQUserPointer sqstd_createblob(HSQUIRRELVM v, SQInteger size)
{
SQInteger top = sq_gettop(v);
sq_pushregistrytable(v);
sq_pushstring(v,_SC("std_blob"),-1);
if(SQ_SUCCEEDED(sq_get(v,-2))) {
sq_remove(v,-2); //removes the registry
sq_push(v,1); // push the this
sq_pushinteger(v,size); //size
SQBlob *blob = NULL;
if(SQ_SUCCEEDED(sq_call(v,2,SQTrue,SQFalse))
&& SQ_SUCCEEDED(sq_getinstanceup(v,-1,(SQUserPointer *)&blob,(SQUserPointer)SQSTD_BLOB_TYPE_TAG,SQTrue))) {
sq_remove(v,-2);
return blob->GetBuf();
}
}
sq_settop(v,top);
return NULL;
}
SQRESULT sqstd_register_bloblib(HSQUIRRELVM v)
{
return declare_stream(v,_SC("blob"),(SQUserPointer)SQSTD_BLOB_TYPE_TAG,_SC("std_blob"),_blob_methods,bloblib_funcs);
}

View File

@ -1,108 +0,0 @@
/* see copyright notice in squirrel.h */
#ifndef _SQSTD_BLOBIMPL_H_
#define _SQSTD_BLOBIMPL_H_
struct SQBlob : public SQStream
{
SQBlob(SQInteger size) {
_size = size;
_allocated = size;
_buf = (unsigned char *)sq_malloc(size);
memset(_buf, 0, _size);
_ptr = 0;
_owns = true;
}
virtual ~SQBlob() {
sq_free(_buf, _allocated);
}
SQInteger Write(void *buffer, SQInteger size) {
if(!CanAdvance(size)) {
GrowBufOf(_ptr + size - _size);
}
memcpy(&_buf[_ptr], buffer, size);
_ptr += size;
return size;
}
SQInteger Read(void *buffer,SQInteger size) {
SQInteger n = size;
if(!CanAdvance(size)) {
if((_size - _ptr) > 0)
n = _size - _ptr;
else return 0;
}
memcpy(buffer, &_buf[_ptr], n);
_ptr += n;
return n;
}
bool Resize(SQInteger n) {
if(!_owns) return false;
if(n != _allocated) {
unsigned char *newbuf = (unsigned char *)sq_malloc(n);
memset(newbuf,0,n);
if(_size > n)
memcpy(newbuf,_buf,n);
else
memcpy(newbuf,_buf,_size);
sq_free(_buf,_allocated);
_buf=newbuf;
_allocated = n;
if(_size > _allocated)
_size = _allocated;
if(_ptr > _allocated)
_ptr = _allocated;
}
return true;
}
bool GrowBufOf(SQInteger n)
{
bool ret = true;
if(_size + n > _allocated) {
if(_size + n > _size * 2)
ret = Resize(_size + n);
else
ret = Resize(_size * 2);
}
_size = _size + n;
return ret;
}
bool CanAdvance(SQInteger n) {
if(_ptr+n>_size)return false;
return true;
}
SQInteger Seek(SQInteger offset, SQInteger origin) {
switch(origin) {
case SQ_SEEK_SET:
if(offset > _size || offset < 0) return -1;
_ptr = offset;
break;
case SQ_SEEK_CUR:
if(_ptr + offset > _size || _ptr + offset < 0) return -1;
_ptr += offset;
break;
case SQ_SEEK_END:
if(_size + offset > _size || _size + offset < 0) return -1;
_ptr = _size + offset;
break;
default: return -1;
}
return 0;
}
bool IsValid() {
return _size == 0 || _buf?true:false;
}
bool EOS() {
return _ptr == _size;
}
SQInteger Flush() { return 0; }
SQInteger Tell() { return _ptr; }
SQInteger Len() { return _size; }
SQUserPointer GetBuf(){ return _buf; }
private:
SQInteger _size;
SQInteger _allocated;
SQInteger _ptr;
unsigned char *_buf;
bool _owns;
};
#endif //_SQSTD_BLOBIMPL_H_

View File

@ -1,489 +0,0 @@
/* see copyright notice in squirrel.h */
#include <new>
#include <stdio.h>
#include <squirrel.h>
#include <sqstdio.h>
#include "sqstdstream.h"
#define SQSTD_FILE_TYPE_TAG ((SQUnsignedInteger)(SQSTD_STREAM_TYPE_TAG | 0x00000001))
//basic API
SQFILE sqstd_fopen(const SQChar *filename ,const SQChar *mode)
{
#ifndef SQUNICODE
return (SQFILE)fopen(filename,mode);
#else
return (SQFILE)_wfopen(filename,mode);
#endif
}
SQInteger sqstd_fread(void* buffer, SQInteger size, SQInteger count, SQFILE file)
{
SQInteger ret = (SQInteger)fread(buffer,size,count,(FILE *)file);
return ret;
}
SQInteger sqstd_fwrite(const SQUserPointer buffer, SQInteger size, SQInteger count, SQFILE file)
{
return (SQInteger)fwrite(buffer,size,count,(FILE *)file);
}
SQInteger sqstd_fseek(SQFILE file, SQInteger offset, SQInteger origin)
{
SQInteger realorigin;
switch(origin) {
case SQ_SEEK_CUR: realorigin = SEEK_CUR; break;
case SQ_SEEK_END: realorigin = SEEK_END; break;
case SQ_SEEK_SET: realorigin = SEEK_SET; break;
default: return -1; //failed
}
return fseek((FILE *)file,(long)offset,(int)realorigin);
}
SQInteger sqstd_ftell(SQFILE file)
{
return ftell((FILE *)file);
}
SQInteger sqstd_fflush(SQFILE file)
{
return fflush((FILE *)file);
}
SQInteger sqstd_fclose(SQFILE file)
{
return fclose((FILE *)file);
}
SQInteger sqstd_feof(SQFILE file)
{
return feof((FILE *)file);
}
//File
struct SQFile : public SQStream {
SQFile() { _handle = NULL; _owns = false;}
SQFile(SQFILE file, bool owns) { _handle = file; _owns = owns;}
virtual ~SQFile() { Close(); }
bool Open(const SQChar *filename ,const SQChar *mode) {
Close();
if( (_handle = sqstd_fopen(filename,mode)) ) {
_owns = true;
return true;
}
return false;
}
void Close() {
if(_handle && _owns) {
sqstd_fclose(_handle);
_handle = NULL;
_owns = false;
}
}
SQInteger Read(void *buffer,SQInteger size) {
return sqstd_fread(buffer,1,size,_handle);
}
SQInteger Write(void *buffer,SQInteger size) {
return sqstd_fwrite(buffer,1,size,_handle);
}
SQInteger Flush() {
return sqstd_fflush(_handle);
}
SQInteger Tell() {
return sqstd_ftell(_handle);
}
SQInteger Len() {
SQInteger prevpos=Tell();
Seek(0,SQ_SEEK_END);
SQInteger size=Tell();
Seek(prevpos,SQ_SEEK_SET);
return size;
}
SQInteger Seek(SQInteger offset, SQInteger origin) {
return sqstd_fseek(_handle,offset,origin);
}
bool IsValid() { return _handle?true:false; }
bool EOS() { return Tell()==Len()?true:false;}
SQFILE GetHandle() {return _handle;}
private:
SQFILE _handle;
bool _owns;
};
static SQInteger _file__typeof(HSQUIRRELVM v)
{
sq_pushstring(v,_SC("file"),-1);
return 1;
}
static SQInteger _file_releasehook(SQUserPointer p, SQInteger SQ_UNUSED_ARG(size))
{
SQFile *self = (SQFile*)p;
self->~SQFile();
sq_free(self,sizeof(SQFile));
return 1;
}
static SQInteger _file_constructor(HSQUIRRELVM v)
{
const SQChar *filename,*mode;
bool owns = true;
SQFile *f;
SQFILE newf;
if(sq_gettype(v,2) == OT_STRING && sq_gettype(v,3) == OT_STRING) {
sq_getstring(v, 2, &filename);
sq_getstring(v, 3, &mode);
newf = sqstd_fopen(filename, mode);
if(!newf) return sq_throwerror(v, _SC("cannot open file"));
} else if(sq_gettype(v,2) == OT_USERPOINTER) {
owns = !(sq_gettype(v,3) == OT_NULL);
sq_getuserpointer(v,2,&newf);
} else {
return sq_throwerror(v,_SC("wrong parameter"));
}
f = new (sq_malloc(sizeof(SQFile)))SQFile(newf,owns);
if(SQ_FAILED(sq_setinstanceup(v,1,f))) {
f->~SQFile();
sq_free(f,sizeof(SQFile));
return sq_throwerror(v, _SC("cannot create blob with negative size"));
}
sq_setreleasehook(v,1,_file_releasehook);
return 0;
}
static SQInteger _file_close(HSQUIRRELVM v)
{
SQFile *self = NULL;
if(SQ_SUCCEEDED(sq_getinstanceup(v,1,(SQUserPointer*)&self,(SQUserPointer)SQSTD_FILE_TYPE_TAG, SQTrue))
&& self != NULL)
{
self->Close();
}
return 0;
}
//bindings
#define _DECL_FILE_FUNC(name,nparams,typecheck) {_SC(#name),_file_##name,nparams,typecheck}
static const SQRegFunction _file_methods[] = {
_DECL_FILE_FUNC(constructor,3,_SC("x")),
_DECL_FILE_FUNC(_typeof,1,_SC("x")),
_DECL_FILE_FUNC(close,1,_SC("x")),
{NULL,(SQFUNCTION)0,0,NULL}
};
SQRESULT sqstd_createfile(HSQUIRRELVM v, SQFILE file,SQBool own)
{
SQInteger top = sq_gettop(v);
sq_pushregistrytable(v);
sq_pushstring(v,_SC("std_file"),-1);
if(SQ_SUCCEEDED(sq_get(v,-2))) {
sq_remove(v,-2); //removes the registry
sq_pushroottable(v); // push the this
sq_pushuserpointer(v,file); //file
if(own){
sq_pushinteger(v,1); //true
}
else{
sq_pushnull(v); //false
}
if(SQ_SUCCEEDED( sq_call(v,3,SQTrue,SQFalse) )) {
sq_remove(v,-2);
return SQ_OK;
}
}
sq_settop(v,top);
return SQ_ERROR;
}
SQRESULT sqstd_getfile(HSQUIRRELVM v, SQInteger idx, SQFILE *file)
{
SQFile *fileobj = NULL;
if(SQ_SUCCEEDED(sq_getinstanceup(v,idx,(SQUserPointer*)&fileobj,(SQUserPointer)SQSTD_FILE_TYPE_TAG,SQFalse))) {
*file = fileobj->GetHandle();
return SQ_OK;
}
return sq_throwerror(v,_SC("not a file"));
}
#define IO_BUFFER_SIZE 2048
struct IOBuffer {
unsigned char buffer[IO_BUFFER_SIZE];
SQInteger size;
SQInteger ptr;
SQFILE file;
};
SQInteger _read_byte(IOBuffer *iobuffer)
{
if(iobuffer->ptr < iobuffer->size) {
SQInteger ret = iobuffer->buffer[iobuffer->ptr];
iobuffer->ptr++;
return ret;
}
else {
if( (iobuffer->size = sqstd_fread(iobuffer->buffer,1,IO_BUFFER_SIZE,iobuffer->file )) > 0 )
{
SQInteger ret = iobuffer->buffer[0];
iobuffer->ptr = 1;
return ret;
}
}
return 0;
}
SQInteger _read_two_bytes(IOBuffer *iobuffer)
{
if(iobuffer->ptr < iobuffer->size) {
if(iobuffer->size < 2) return 0;
SQInteger ret = *((const wchar_t*)&iobuffer->buffer[iobuffer->ptr]);
iobuffer->ptr += 2;
return ret;
}
else {
if( (iobuffer->size = sqstd_fread(iobuffer->buffer,1,IO_BUFFER_SIZE,iobuffer->file )) > 0 )
{
if(iobuffer->size < 2) return 0;
SQInteger ret = *((const wchar_t*)&iobuffer->buffer[0]);
iobuffer->ptr = 2;
return ret;
}
}
return 0;
}
static SQInteger _io_file_lexfeed_PLAIN(SQUserPointer iobuf)
{
IOBuffer *iobuffer = (IOBuffer *)iobuf;
return _read_byte(iobuffer);
}
#ifdef SQUNICODE
static SQInteger _io_file_lexfeed_UTF8(SQUserPointer iobuf)
{
IOBuffer *iobuffer = (IOBuffer *)iobuf;
#define READ(iobuf) \
if((inchar = (unsigned char)_read_byte(iobuf)) == 0) \
return 0;
static const SQInteger utf8_lengths[16] =
{
1,1,1,1,1,1,1,1, /* 0000 to 0111 : 1 byte (plain ASCII) */
0,0,0,0, /* 1000 to 1011 : not valid */
2,2, /* 1100, 1101 : 2 bytes */
3, /* 1110 : 3 bytes */
4 /* 1111 :4 bytes */
};
static const unsigned char byte_masks[5] = {0,0,0x1f,0x0f,0x07};
unsigned char inchar;
SQInteger c = 0;
READ(iobuffer);
c = inchar;
//
if(c >= 0x80) {
SQInteger tmp;
SQInteger codelen = utf8_lengths[c>>4];
if(codelen == 0)
return 0;
//"invalid UTF-8 stream";
tmp = c&byte_masks[codelen];
for(SQInteger n = 0; n < codelen-1; n++) {
tmp<<=6;
READ(iobuffer);
tmp |= inchar & 0x3F;
}
c = tmp;
}
return c;
}
#endif
static SQInteger _io_file_lexfeed_UCS2_LE(SQUserPointer iobuf)
{
SQInteger ret;
IOBuffer *iobuffer = (IOBuffer *)iobuf;
if( (ret = _read_two_bytes(iobuffer)) > 0 )
return ret;
return 0;
}
static SQInteger _io_file_lexfeed_UCS2_BE(SQUserPointer iobuf)
{
SQInteger c;
IOBuffer *iobuffer = (IOBuffer *)iobuf;
if( (c = _read_two_bytes(iobuffer)) > 0 ) {
c = ((c>>8)&0x00FF)| ((c<<8)&0xFF00);
return c;
}
return 0;
}
SQInteger file_read(SQUserPointer file,SQUserPointer buf,SQInteger size)
{
SQInteger ret;
if( ( ret = sqstd_fread(buf,1,size,(SQFILE)file ))!=0 )return ret;
return -1;
}
SQInteger file_write(SQUserPointer file,SQUserPointer p,SQInteger size)
{
return sqstd_fwrite(p,1,size,(SQFILE)file);
}
SQRESULT sqstd_loadfile(HSQUIRRELVM v,const SQChar *filename,SQBool printerror)
{
SQFILE file = sqstd_fopen(filename,_SC("rb"));
SQInteger ret;
unsigned short us;
unsigned char uc;
SQLEXREADFUNC func = _io_file_lexfeed_PLAIN;
if(file){
ret = sqstd_fread(&us,1,2,file);
if(ret != 2) {
//probably an empty file
us = 0;
}
if(us == SQ_BYTECODE_STREAM_TAG) { //BYTECODE
sqstd_fseek(file,0,SQ_SEEK_SET);
if(SQ_SUCCEEDED(sq_readclosure(v,file_read,file))) {
sqstd_fclose(file);
return SQ_OK;
}
}
else { //SCRIPT
switch(us)
{
//gotta swap the next 2 lines on BIG endian machines
case 0xFFFE: func = _io_file_lexfeed_UCS2_BE; break;//UTF-16 little endian;
case 0xFEFF: func = _io_file_lexfeed_UCS2_LE; break;//UTF-16 big endian;
case 0xBBEF:
if(sqstd_fread(&uc,1,sizeof(uc),file) == 0) {
sqstd_fclose(file);
return sq_throwerror(v,_SC("io error"));
}
if(uc != 0xBF) {
sqstd_fclose(file);
return sq_throwerror(v,_SC("Unrecognized encoding"));
}
#ifdef SQUNICODE
func = _io_file_lexfeed_UTF8;
#else
func = _io_file_lexfeed_PLAIN;
#endif
break;//UTF-8 ;
default: sqstd_fseek(file,0,SQ_SEEK_SET); break; // ascii
}
IOBuffer buffer;
buffer.ptr = 0;
buffer.size = 0;
buffer.file = file;
if(SQ_SUCCEEDED(sq_compile(v,func,&buffer,filename,printerror))){
sqstd_fclose(file);
return SQ_OK;
}
}
sqstd_fclose(file);
return SQ_ERROR;
}
return sq_throwerror(v,_SC("cannot open the file"));
}
SQRESULT sqstd_dofile(HSQUIRRELVM v,const SQChar *filename,SQBool retval,SQBool printerror)
{
//at least one entry must exist in order for us to push it as the environment
if(sq_gettop(v) == 0)
return sq_throwerror(v,_SC("environment table expected"));
if(SQ_SUCCEEDED(sqstd_loadfile(v,filename,printerror))) {
sq_push(v,-2);
if(SQ_SUCCEEDED(sq_call(v,1,retval,SQTrue))) {
sq_remove(v,retval?-2:-1); //removes the closure
return 1;
}
sq_pop(v,1); //removes the closure
}
return SQ_ERROR;
}
SQRESULT sqstd_writeclosuretofile(HSQUIRRELVM v,const SQChar *filename)
{
SQFILE file = sqstd_fopen(filename,_SC("wb+"));
if(!file) return sq_throwerror(v,_SC("cannot open the file"));
if(SQ_SUCCEEDED(sq_writeclosure(v,file_write,file))) {
sqstd_fclose(file);
return SQ_OK;
}
sqstd_fclose(file);
return SQ_ERROR; //forward the error
}
SQInteger _g_io_loadfile(HSQUIRRELVM v)
{
const SQChar *filename;
SQBool printerror = SQFalse;
sq_getstring(v,2,&filename);
if(sq_gettop(v) >= 3) {
sq_getbool(v,3,&printerror);
}
if(SQ_SUCCEEDED(sqstd_loadfile(v,filename,printerror)))
return 1;
return SQ_ERROR; //propagates the error
}
SQInteger _g_io_writeclosuretofile(HSQUIRRELVM v)
{
const SQChar *filename;
sq_getstring(v,2,&filename);
if(SQ_SUCCEEDED(sqstd_writeclosuretofile(v,filename)))
return 1;
return SQ_ERROR; //propagates the error
}
SQInteger _g_io_dofile(HSQUIRRELVM v)
{
const SQChar *filename;
SQBool printerror = SQFalse;
sq_getstring(v,2,&filename);
if(sq_gettop(v) >= 3) {
sq_getbool(v,3,&printerror);
}
sq_push(v,1); //repush the this
if(SQ_SUCCEEDED(sqstd_dofile(v,filename,SQTrue,printerror)))
return 1;
return SQ_ERROR; //propagates the error
}
#define _DECL_GLOBALIO_FUNC(name,nparams,typecheck) {_SC(#name),_g_io_##name,nparams,typecheck}
static const SQRegFunction iolib_funcs[]={
_DECL_GLOBALIO_FUNC(loadfile,-2,_SC(".sb")),
_DECL_GLOBALIO_FUNC(dofile,-2,_SC(".sb")),
_DECL_GLOBALIO_FUNC(writeclosuretofile,3,_SC(".sc")),
{NULL,(SQFUNCTION)0,0,NULL}
};
SQRESULT sqstd_register_iolib(HSQUIRRELVM v)
{
SQInteger top = sq_gettop(v);
//create delegate
declare_stream(v,_SC("file"),(SQUserPointer)SQSTD_FILE_TYPE_TAG,_SC("std_file"),_file_methods,iolib_funcs);
sq_pushstring(v,_SC("stdout"),-1);
sqstd_createfile(v,stdout,SQFalse);
sq_newslot(v,-3,SQFalse);
sq_pushstring(v,_SC("stdin"),-1);
sqstd_createfile(v,stdin,SQFalse);
sq_newslot(v,-3,SQFalse);
sq_pushstring(v,_SC("stderr"),-1);
sqstd_createfile(v,stderr,SQFalse);
sq_newslot(v,-3,SQFalse);
sq_settop(v,top);
return SQ_OK;
}

View File

@ -1,107 +0,0 @@
/* see copyright notice in squirrel.h */
#include <squirrel.h>
#include <math.h>
#include <stdlib.h>
#include <sqstdmath.h>
#define SINGLE_ARG_FUNC(_funcname) static SQInteger math_##_funcname(HSQUIRRELVM v){ \
SQFloat f; \
sq_getfloat(v,2,&f); \
sq_pushfloat(v,(SQFloat)_funcname(f)); \
return 1; \
}
#define TWO_ARGS_FUNC(_funcname) static SQInteger math_##_funcname(HSQUIRRELVM v){ \
SQFloat p1,p2; \
sq_getfloat(v,2,&p1); \
sq_getfloat(v,3,&p2); \
sq_pushfloat(v,(SQFloat)_funcname(p1,p2)); \
return 1; \
}
static SQInteger math_srand(HSQUIRRELVM v)
{
SQInteger i;
if(SQ_FAILED(sq_getinteger(v,2,&i)))
return sq_throwerror(v,_SC("invalid param"));
srand((unsigned int)i);
return 0;
}
static SQInteger math_rand(HSQUIRRELVM v)
{
sq_pushinteger(v,rand());
return 1;
}
static SQInteger math_abs(HSQUIRRELVM v)
{
SQInteger n;
sq_getinteger(v,2,&n);
sq_pushinteger(v,(SQInteger)abs((int)n));
return 1;
}
SINGLE_ARG_FUNC(sqrt)
SINGLE_ARG_FUNC(fabs)
SINGLE_ARG_FUNC(sin)
SINGLE_ARG_FUNC(cos)
SINGLE_ARG_FUNC(asin)
SINGLE_ARG_FUNC(acos)
SINGLE_ARG_FUNC(log)
SINGLE_ARG_FUNC(log10)
SINGLE_ARG_FUNC(tan)
SINGLE_ARG_FUNC(atan)
TWO_ARGS_FUNC(atan2)
TWO_ARGS_FUNC(pow)
SINGLE_ARG_FUNC(floor)
SINGLE_ARG_FUNC(ceil)
SINGLE_ARG_FUNC(exp)
#define _DECL_FUNC(name,nparams,tycheck) {_SC(#name),math_##name,nparams,tycheck}
static const SQRegFunction mathlib_funcs[] = {
_DECL_FUNC(sqrt,2,_SC(".n")),
_DECL_FUNC(sin,2,_SC(".n")),
_DECL_FUNC(cos,2,_SC(".n")),
_DECL_FUNC(asin,2,_SC(".n")),
_DECL_FUNC(acos,2,_SC(".n")),
_DECL_FUNC(log,2,_SC(".n")),
_DECL_FUNC(log10,2,_SC(".n")),
_DECL_FUNC(tan,2,_SC(".n")),
_DECL_FUNC(atan,2,_SC(".n")),
_DECL_FUNC(atan2,3,_SC(".nn")),
_DECL_FUNC(pow,3,_SC(".nn")),
_DECL_FUNC(floor,2,_SC(".n")),
_DECL_FUNC(ceil,2,_SC(".n")),
_DECL_FUNC(exp,2,_SC(".n")),
_DECL_FUNC(srand,2,_SC(".n")),
_DECL_FUNC(rand,1,NULL),
_DECL_FUNC(fabs,2,_SC(".n")),
_DECL_FUNC(abs,2,_SC(".n")),
{NULL,(SQFUNCTION)0,0,NULL}
};
#undef _DECL_FUNC
#ifndef M_PI
#define M_PI (3.14159265358979323846)
#endif
SQRESULT sqstd_register_mathlib(HSQUIRRELVM v)
{
SQInteger i=0;
while(mathlib_funcs[i].name!=0) {
sq_pushstring(v,mathlib_funcs[i].name,-1);
sq_newclosure(v,mathlib_funcs[i].f,0);
sq_setparamscheck(v,mathlib_funcs[i].nparamscheck,mathlib_funcs[i].typemask);
sq_setnativeclosurename(v,-1,mathlib_funcs[i].name);
sq_newslot(v,-3,SQFalse);
i++;
}
sq_pushstring(v,_SC("RAND_MAX"),-1);
sq_pushinteger(v,RAND_MAX);
sq_newslot(v,-3,SQFalse);
sq_pushstring(v,_SC("PI"),-1);
sq_pushfloat(v,(SQFloat)M_PI);
sq_newslot(v,-3,SQFalse);
return SQ_OK;
}

View File

@ -1,666 +0,0 @@
/* see copyright notice in squirrel.h */
#include <squirrel.h>
#include <string.h>
#include <ctype.h>
#include <setjmp.h>
#include <sqstdstring.h>
#ifdef _DEBUG
#include <stdio.h>
static const SQChar *g_nnames[] =
{
_SC("NONE"),_SC("OP_GREEDY"), _SC("OP_OR"),
_SC("OP_EXPR"),_SC("OP_NOCAPEXPR"),_SC("OP_DOT"), _SC("OP_CLASS"),
_SC("OP_CCLASS"),_SC("OP_NCLASS"),_SC("OP_RANGE"),_SC("OP_CHAR"),
_SC("OP_EOL"),_SC("OP_BOL"),_SC("OP_WB"),_SC("OP_MB")
};
#endif
#define OP_GREEDY (MAX_CHAR+1) // * + ? {n}
#define OP_OR (MAX_CHAR+2)
#define OP_EXPR (MAX_CHAR+3) //parentesis ()
#define OP_NOCAPEXPR (MAX_CHAR+4) //parentesis (?:)
#define OP_DOT (MAX_CHAR+5)
#define OP_CLASS (MAX_CHAR+6)
#define OP_CCLASS (MAX_CHAR+7)
#define OP_NCLASS (MAX_CHAR+8) //negates class the [^
#define OP_RANGE (MAX_CHAR+9)
#define OP_CHAR (MAX_CHAR+10)
#define OP_EOL (MAX_CHAR+11)
#define OP_BOL (MAX_CHAR+12)
#define OP_WB (MAX_CHAR+13)
#define OP_MB (MAX_CHAR+14) //match balanced
#define SQREX_SYMBOL_ANY_CHAR ('.')
#define SQREX_SYMBOL_GREEDY_ONE_OR_MORE ('+')
#define SQREX_SYMBOL_GREEDY_ZERO_OR_MORE ('*')
#define SQREX_SYMBOL_GREEDY_ZERO_OR_ONE ('?')
#define SQREX_SYMBOL_BRANCH ('|')
#define SQREX_SYMBOL_END_OF_STRING ('$')
#define SQREX_SYMBOL_BEGINNING_OF_STRING ('^')
#define SQREX_SYMBOL_ESCAPE_CHAR ('\\')
typedef int SQRexNodeType;
typedef struct tagSQRexNode{
SQRexNodeType type;
SQInteger left;
SQInteger right;
SQInteger next;
}SQRexNode;
struct SQRex{
const SQChar *_eol;
const SQChar *_bol;
const SQChar *_p;
SQInteger _first;
SQInteger _op;
SQRexNode *_nodes;
SQInteger _nallocated;
SQInteger _nsize;
SQInteger _nsubexpr;
SQRexMatch *_matches;
SQInteger _currsubexp;
void *_jmpbuf;
const SQChar **_error;
};
static SQInteger sqstd_rex_list(SQRex *exp);
static SQInteger sqstd_rex_newnode(SQRex *exp, SQRexNodeType type)
{
SQRexNode n;
n.type = type;
n.next = n.right = n.left = -1;
if(type == OP_EXPR)
n.right = exp->_nsubexpr++;
if(exp->_nallocated < (exp->_nsize + 1)) {
SQInteger oldsize = exp->_nallocated;
exp->_nallocated *= 2;
exp->_nodes = (SQRexNode *)sq_realloc(exp->_nodes, oldsize * sizeof(SQRexNode) ,exp->_nallocated * sizeof(SQRexNode));
}
exp->_nodes[exp->_nsize++] = n;
SQInteger newid = exp->_nsize - 1;
return (SQInteger)newid;
}
static void sqstd_rex_error(SQRex *exp,const SQChar *error)
{
if(exp->_error) *exp->_error = error;
longjmp(*((jmp_buf*)exp->_jmpbuf),-1);
}
static void sqstd_rex_expect(SQRex *exp, SQInteger n){
if((*exp->_p) != n)
sqstd_rex_error(exp, _SC("expected paren"));
exp->_p++;
}
static SQChar sqstd_rex_escapechar(SQRex *exp)
{
if(*exp->_p == SQREX_SYMBOL_ESCAPE_CHAR){
exp->_p++;
switch(*exp->_p) {
case 'v': exp->_p++; return '\v';
case 'n': exp->_p++; return '\n';
case 't': exp->_p++; return '\t';
case 'r': exp->_p++; return '\r';
case 'f': exp->_p++; return '\f';
default: return (*exp->_p++);
}
} else if(!scisprint(*exp->_p)) sqstd_rex_error(exp,_SC("letter expected"));
return (*exp->_p++);
}
static SQInteger sqstd_rex_charclass(SQRex *exp,SQInteger classid)
{
SQInteger n = sqstd_rex_newnode(exp,OP_CCLASS);
exp->_nodes[n].left = classid;
return n;
}
static SQInteger sqstd_rex_charnode(SQRex *exp,SQBool isclass)
{
SQChar t;
if(*exp->_p == SQREX_SYMBOL_ESCAPE_CHAR) {
exp->_p++;
switch(*exp->_p) {
case 'n': exp->_p++; return sqstd_rex_newnode(exp,'\n');
case 't': exp->_p++; return sqstd_rex_newnode(exp,'\t');
case 'r': exp->_p++; return sqstd_rex_newnode(exp,'\r');
case 'f': exp->_p++; return sqstd_rex_newnode(exp,'\f');
case 'v': exp->_p++; return sqstd_rex_newnode(exp,'\v');
case 'a': case 'A': case 'w': case 'W': case 's': case 'S':
case 'd': case 'D': case 'x': case 'X': case 'c': case 'C':
case 'p': case 'P': case 'l': case 'u':
{
t = *exp->_p; exp->_p++;
return sqstd_rex_charclass(exp,t);
}
case 'm':
{
SQChar cb, ce; //cb = character begin match ce = character end match
cb = *++exp->_p; //skip 'm'
ce = *++exp->_p;
exp->_p++; //points to the next char to be parsed
if ((!cb) || (!ce)) sqstd_rex_error(exp,_SC("balanced chars expected"));
if ( cb == ce ) sqstd_rex_error(exp,_SC("open/close char can't be the same"));
SQInteger node = sqstd_rex_newnode(exp,OP_MB);
exp->_nodes[node].left = cb;
exp->_nodes[node].right = ce;
return node;
}
case 0:
sqstd_rex_error(exp,_SC("letter expected for argument of escape sequence"));
break;
case 'b':
case 'B':
if(!isclass) {
SQInteger node = sqstd_rex_newnode(exp,OP_WB);
exp->_nodes[node].left = *exp->_p;
exp->_p++;
return node;
} //else default
default:
t = *exp->_p; exp->_p++;
return sqstd_rex_newnode(exp,t);
}
}
else if(!scisprint(*exp->_p)) {
sqstd_rex_error(exp,_SC("letter expected"));
}
t = *exp->_p; exp->_p++;
return sqstd_rex_newnode(exp,t);
}
static SQInteger sqstd_rex_class(SQRex *exp)
{
SQInteger ret = -1;
SQInteger first = -1,chain;
if(*exp->_p == SQREX_SYMBOL_BEGINNING_OF_STRING){
ret = sqstd_rex_newnode(exp,OP_NCLASS);
exp->_p++;
}else ret = sqstd_rex_newnode(exp,OP_CLASS);
if(*exp->_p == ']') sqstd_rex_error(exp,_SC("empty class"));
chain = ret;
while(*exp->_p != ']' && exp->_p != exp->_eol) {
if(*exp->_p == '-' && first != -1){
SQInteger r;
if(*exp->_p++ == ']') sqstd_rex_error(exp,_SC("unfinished range"));
r = sqstd_rex_newnode(exp,OP_RANGE);
if(exp->_nodes[first].type>*exp->_p) sqstd_rex_error(exp,_SC("invalid range"));
if(exp->_nodes[first].type == OP_CCLASS) sqstd_rex_error(exp,_SC("cannot use character classes in ranges"));
exp->_nodes[r].left = exp->_nodes[first].type;
SQInteger t = sqstd_rex_escapechar(exp);
exp->_nodes[r].right = t;
exp->_nodes[chain].next = r;
chain = r;
first = -1;
}
else{
if(first!=-1){
SQInteger c = first;
exp->_nodes[chain].next = c;
chain = c;
first = sqstd_rex_charnode(exp,SQTrue);
}
else{
first = sqstd_rex_charnode(exp,SQTrue);
}
}
}
if(first!=-1){
SQInteger c = first;
exp->_nodes[chain].next = c;
}
/* hack? */
exp->_nodes[ret].left = exp->_nodes[ret].next;
exp->_nodes[ret].next = -1;
return ret;
}
static SQInteger sqstd_rex_parsenumber(SQRex *exp)
{
SQInteger ret = *exp->_p-'0';
SQInteger positions = 10;
exp->_p++;
while(isdigit(*exp->_p)) {
ret = ret*10+(*exp->_p++-'0');
if(positions==1000000000) sqstd_rex_error(exp,_SC("overflow in numeric constant"));
positions *= 10;
};
return ret;
}
static SQInteger sqstd_rex_element(SQRex *exp)
{
SQInteger ret = -1;
switch(*exp->_p)
{
case '(': {
SQInteger expr;
exp->_p++;
if(*exp->_p =='?') {
exp->_p++;
sqstd_rex_expect(exp,':');
expr = sqstd_rex_newnode(exp,OP_NOCAPEXPR);
}
else
expr = sqstd_rex_newnode(exp,OP_EXPR);
SQInteger newn = sqstd_rex_list(exp);
exp->_nodes[expr].left = newn;
ret = expr;
sqstd_rex_expect(exp,')');
}
break;
case '[':
exp->_p++;
ret = sqstd_rex_class(exp);
sqstd_rex_expect(exp,']');
break;
case SQREX_SYMBOL_END_OF_STRING: exp->_p++; ret = sqstd_rex_newnode(exp,OP_EOL);break;
case SQREX_SYMBOL_ANY_CHAR: exp->_p++; ret = sqstd_rex_newnode(exp,OP_DOT);break;
default:
ret = sqstd_rex_charnode(exp,SQFalse);
break;
}
SQBool isgreedy = SQFalse;
unsigned short p0 = 0, p1 = 0;
switch(*exp->_p){
case SQREX_SYMBOL_GREEDY_ZERO_OR_MORE: p0 = 0; p1 = 0xFFFF; exp->_p++; isgreedy = SQTrue; break;
case SQREX_SYMBOL_GREEDY_ONE_OR_MORE: p0 = 1; p1 = 0xFFFF; exp->_p++; isgreedy = SQTrue; break;
case SQREX_SYMBOL_GREEDY_ZERO_OR_ONE: p0 = 0; p1 = 1; exp->_p++; isgreedy = SQTrue; break;
case '{':
exp->_p++;
if(!isdigit(*exp->_p)) sqstd_rex_error(exp,_SC("number expected"));
p0 = (unsigned short)sqstd_rex_parsenumber(exp);
/*******************************/
switch(*exp->_p) {
case '}':
p1 = p0; exp->_p++;
break;
case ',':
exp->_p++;
p1 = 0xFFFF;
if(isdigit(*exp->_p)){
p1 = (unsigned short)sqstd_rex_parsenumber(exp);
}
sqstd_rex_expect(exp,'}');
break;
default:
sqstd_rex_error(exp,_SC(", or } expected"));
}
/*******************************/
isgreedy = SQTrue;
break;
}
if(isgreedy) {
SQInteger nnode = sqstd_rex_newnode(exp,OP_GREEDY);
exp->_nodes[nnode].left = ret;
exp->_nodes[nnode].right = ((p0)<<16)|p1;
ret = nnode;
}
if((*exp->_p != SQREX_SYMBOL_BRANCH) && (*exp->_p != ')') && (*exp->_p != SQREX_SYMBOL_GREEDY_ZERO_OR_MORE) && (*exp->_p != SQREX_SYMBOL_GREEDY_ONE_OR_MORE) && (*exp->_p != '\0')) {
SQInteger nnode = sqstd_rex_element(exp);
exp->_nodes[ret].next = nnode;
}
return ret;
}
static SQInteger sqstd_rex_list(SQRex *exp)
{
SQInteger ret=-1,e;
if(*exp->_p == SQREX_SYMBOL_BEGINNING_OF_STRING) {
exp->_p++;
ret = sqstd_rex_newnode(exp,OP_BOL);
}
e = sqstd_rex_element(exp);
if(ret != -1) {
exp->_nodes[ret].next = e;
}
else ret = e;
if(*exp->_p == SQREX_SYMBOL_BRANCH) {
SQInteger temp,tright;
exp->_p++;
temp = sqstd_rex_newnode(exp,OP_OR);
exp->_nodes[temp].left = ret;
tright = sqstd_rex_list(exp);
exp->_nodes[temp].right = tright;
ret = temp;
}
return ret;
}
static SQBool sqstd_rex_matchcclass(SQInteger cclass,SQChar c)
{
switch(cclass) {
case 'a': return isalpha(c)?SQTrue:SQFalse;
case 'A': return !isalpha(c)?SQTrue:SQFalse;
case 'w': return (isalnum(c) || c == '_')?SQTrue:SQFalse;
case 'W': return (!isalnum(c) && c != '_')?SQTrue:SQFalse;
case 's': return isspace(c)?SQTrue:SQFalse;
case 'S': return !isspace(c)?SQTrue:SQFalse;
case 'd': return isdigit(c)?SQTrue:SQFalse;
case 'D': return !isdigit(c)?SQTrue:SQFalse;
case 'x': return isxdigit(c)?SQTrue:SQFalse;
case 'X': return !isxdigit(c)?SQTrue:SQFalse;
case 'c': return iscntrl(c)?SQTrue:SQFalse;
case 'C': return !iscntrl(c)?SQTrue:SQFalse;
case 'p': return ispunct(c)?SQTrue:SQFalse;
case 'P': return !ispunct(c)?SQTrue:SQFalse;
case 'l': return islower(c)?SQTrue:SQFalse;
case 'u': return isupper(c)?SQTrue:SQFalse;
}
return SQFalse; /*cannot happen*/
}
static SQBool sqstd_rex_matchclass(SQRex* exp,SQRexNode *node,SQChar c)
{
do {
switch(node->type) {
case OP_RANGE:
if(c >= node->left && c <= node->right) return SQTrue;
break;
case OP_CCLASS:
if(sqstd_rex_matchcclass(node->left,c)) return SQTrue;
break;
default:
if(c == node->type)return SQTrue;
}
} while((node->next != -1) && (node = &exp->_nodes[node->next]));
return SQFalse;
}
static const SQChar *sqstd_rex_matchnode(SQRex* exp,SQRexNode *node,const SQChar *str,SQRexNode *next)
{
SQRexNodeType type = node->type;
switch(type) {
case OP_GREEDY: {
//SQRexNode *greedystop = (node->next != -1) ? &exp->_nodes[node->next] : NULL;
SQRexNode *greedystop = NULL;
SQInteger p0 = (node->right >> 16)&0x0000FFFF, p1 = node->right&0x0000FFFF, nmaches = 0;
const SQChar *s=str, *good = str;
if(node->next != -1) {
greedystop = &exp->_nodes[node->next];
}
else {
greedystop = next;
}
while((nmaches == 0xFFFF || nmaches < p1)) {
const SQChar *stop;
if(!(s = sqstd_rex_matchnode(exp,&exp->_nodes[node->left],s,greedystop)))
break;
nmaches++;
good=s;
if(greedystop) {
//checks that 0 matches satisfy the expression(if so skips)
//if not would always stop(for instance if is a '?')
if(greedystop->type != OP_GREEDY ||
(greedystop->type == OP_GREEDY && ((greedystop->right >> 16)&0x0000FFFF) != 0))
{
SQRexNode *gnext = NULL;
if(greedystop->next != -1) {
gnext = &exp->_nodes[greedystop->next];
}else if(next && next->next != -1){
gnext = &exp->_nodes[next->next];
}
stop = sqstd_rex_matchnode(exp,greedystop,s,gnext);
if(stop) {
//if satisfied stop it
if(p0 == p1 && p0 == nmaches) break;
else if(nmaches >= p0 && p1 == 0xFFFF) break;
else if(nmaches >= p0 && nmaches <= p1) break;
}
}
}
if(s >= exp->_eol)
break;
}
if(p0 == p1 && p0 == nmaches) return good;
else if(nmaches >= p0 && p1 == 0xFFFF) return good;
else if(nmaches >= p0 && nmaches <= p1) return good;
return NULL;
}
case OP_OR: {
const SQChar *asd = str;
SQRexNode *temp=&exp->_nodes[node->left];
while( (asd = sqstd_rex_matchnode(exp,temp,asd,NULL)) ) {
if(temp->next != -1)
temp = &exp->_nodes[temp->next];
else
return asd;
}
asd = str;
temp = &exp->_nodes[node->right];
while( (asd = sqstd_rex_matchnode(exp,temp,asd,NULL)) ) {
if(temp->next != -1)
temp = &exp->_nodes[temp->next];
else
return asd;
}
return NULL;
break;
}
case OP_EXPR:
case OP_NOCAPEXPR:{
SQRexNode *n = &exp->_nodes[node->left];
const SQChar *cur = str;
SQInteger capture = -1;
if(node->type != OP_NOCAPEXPR && node->right == exp->_currsubexp) {
capture = exp->_currsubexp;
exp->_matches[capture].begin = cur;
exp->_currsubexp++;
}
SQInteger tempcap = exp->_currsubexp;
do {
SQRexNode *subnext = NULL;
if(n->next != -1) {
subnext = &exp->_nodes[n->next];
}else {
subnext = next;
}
if(!(cur = sqstd_rex_matchnode(exp,n,cur,subnext))) {
if(capture != -1){
exp->_matches[capture].begin = 0;
exp->_matches[capture].len = 0;
}
return NULL;
}
} while((n->next != -1) && (n = &exp->_nodes[n->next]));
exp->_currsubexp = tempcap;
if(capture != -1)
exp->_matches[capture].len = cur - exp->_matches[capture].begin;
return cur;
}
case OP_WB:
if((str == exp->_bol && !isspace(*str))
|| (str == exp->_eol && !isspace(*(str-1)))
|| (!isspace(*str) && isspace(*(str+1)))
|| (isspace(*str) && !isspace(*(str+1))) ) {
return (node->left == 'b')?str:NULL;
}
return (node->left == 'b')?NULL:str;
case OP_BOL:
if(str == exp->_bol) return str;
return NULL;
case OP_EOL:
if(str == exp->_eol) return str;
return NULL;
case OP_DOT:{
if (str == exp->_eol) return NULL;
str++;
}
return str;
case OP_NCLASS:
case OP_CLASS:
if (str == exp->_eol) return NULL;
if(sqstd_rex_matchclass(exp,&exp->_nodes[node->left],*str)?(type == OP_CLASS?SQTrue:SQFalse):(type == OP_NCLASS?SQTrue:SQFalse)) {
str++;
return str;
}
return NULL;
case OP_CCLASS:
if (str == exp->_eol) return NULL;
if(sqstd_rex_matchcclass(node->left,*str)) {
str++;
return str;
}
return NULL;
case OP_MB:
{
SQInteger cb = node->left; //char that opens a balanced expression
if(*str != cb) return NULL; // string doesnt start with open char
SQInteger ce = node->right; //char that closes a balanced expression
SQInteger cont = 1;
const SQChar *streol = exp->_eol;
while (++str < streol) {
if (*str == ce) {
if (--cont == 0) {
return ++str;
}
}
else if (*str == cb) cont++;
}
}
return NULL; // string ends out of balance
default: /* char */
if (str == exp->_eol) return NULL;
if(*str != node->type) return NULL;
str++;
return str;
}
return NULL;
}
/* public api */
SQRex *sqstd_rex_compile(const SQChar *pattern,const SQChar **error)
{
SQRex * volatile exp = (SQRex *)sq_malloc(sizeof(SQRex)); // "volatile" is needed for setjmp()
exp->_eol = exp->_bol = NULL;
exp->_p = pattern;
exp->_nallocated = (SQInteger)scstrlen(pattern) * sizeof(SQChar);
exp->_nodes = (SQRexNode *)sq_malloc(exp->_nallocated * sizeof(SQRexNode));
exp->_nsize = 0;
exp->_matches = 0;
exp->_nsubexpr = 0;
exp->_first = sqstd_rex_newnode(exp,OP_EXPR);
exp->_error = error;
exp->_jmpbuf = sq_malloc(sizeof(jmp_buf));
if(setjmp(*((jmp_buf*)exp->_jmpbuf)) == 0) {
SQInteger res = sqstd_rex_list(exp);
exp->_nodes[exp->_first].left = res;
if(*exp->_p!='\0')
sqstd_rex_error(exp,_SC("unexpected character"));
#ifdef _DEBUG
{
SQInteger nsize,i;
SQRexNode *t;
nsize = exp->_nsize;
t = &exp->_nodes[0];
scprintf(_SC("\n"));
for(i = 0;i < nsize; i++) {
if(exp->_nodes[i].type>MAX_CHAR)
scprintf(_SC("[%02d] %10s "), (SQInt32)i,g_nnames[exp->_nodes[i].type-MAX_CHAR]);
else
scprintf(_SC("[%02d] %10c "), (SQInt32)i,exp->_nodes[i].type);
scprintf(_SC("left %02d right %02d next %02d\n"), (SQInt32)exp->_nodes[i].left, (SQInt32)exp->_nodes[i].right, (SQInt32)exp->_nodes[i].next);
}
scprintf(_SC("\n"));
}
#endif
exp->_matches = (SQRexMatch *) sq_malloc(exp->_nsubexpr * sizeof(SQRexMatch));
memset(exp->_matches,0,exp->_nsubexpr * sizeof(SQRexMatch));
}
else{
sqstd_rex_free(exp);
return NULL;
}
return exp;
}
void sqstd_rex_free(SQRex *exp)
{
if(exp) {
if(exp->_nodes) sq_free(exp->_nodes,exp->_nallocated * sizeof(SQRexNode));
if(exp->_jmpbuf) sq_free(exp->_jmpbuf,sizeof(jmp_buf));
if(exp->_matches) sq_free(exp->_matches,exp->_nsubexpr * sizeof(SQRexMatch));
sq_free(exp,sizeof(SQRex));
}
}
SQBool sqstd_rex_match(SQRex* exp,const SQChar* text)
{
const SQChar* res = NULL;
exp->_bol = text;
exp->_eol = text + scstrlen(text);
exp->_currsubexp = 0;
res = sqstd_rex_matchnode(exp,exp->_nodes,text,NULL);
if(res == NULL || res != exp->_eol)
return SQFalse;
return SQTrue;
}
SQBool sqstd_rex_searchrange(SQRex* exp,const SQChar* text_begin,const SQChar* text_end,const SQChar** out_begin, const SQChar** out_end)
{
const SQChar *cur = NULL;
SQInteger node = exp->_first;
if(text_begin >= text_end) return SQFalse;
exp->_bol = text_begin;
exp->_eol = text_end;
do {
cur = text_begin;
while(node != -1) {
exp->_currsubexp = 0;
cur = sqstd_rex_matchnode(exp,&exp->_nodes[node],cur,NULL);
if(!cur)
break;
node = exp->_nodes[node].next;
}
text_begin++;
} while(cur == NULL && text_begin != text_end);
if(cur == NULL)
return SQFalse;
--text_begin;
if(out_begin) *out_begin = text_begin;
if(out_end) *out_end = cur;
return SQTrue;
}
SQBool sqstd_rex_search(SQRex* exp,const SQChar* text, const SQChar** out_begin, const SQChar** out_end)
{
return sqstd_rex_searchrange(exp,text,text + scstrlen(text),out_begin,out_end);
}
SQInteger sqstd_rex_getsubexpcount(SQRex* exp)
{
return exp->_nsubexpr;
}
SQBool sqstd_rex_getsubexp(SQRex* exp, SQInteger n, SQRexMatch *subexp)
{
if( n<0 || n >= exp->_nsubexpr) return SQFalse;
*subexp = exp->_matches[n];
return SQTrue;
}

View File

@ -1,336 +0,0 @@
/* see copyright notice in squirrel.h */
#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <squirrel.h>
#include <sqstdio.h>
#include <sqstdblob.h>
#include "sqstdstream.h"
#include "sqstdblobimpl.h"
#define SETUP_STREAM(v) \
SQStream *self = NULL; \
if(SQ_FAILED(sq_getinstanceup(v,1,(SQUserPointer*)&self,(SQUserPointer)((SQUnsignedInteger)SQSTD_STREAM_TYPE_TAG),SQFalse))) \
return sq_throwerror(v,_SC("invalid type tag")); \
if(!self || !self->IsValid()) \
return sq_throwerror(v,_SC("the stream is invalid"));
SQInteger _stream_readblob(HSQUIRRELVM v)
{
SETUP_STREAM(v);
SQUserPointer data,blobp;
SQInteger size,res;
sq_getinteger(v,2,&size);
if(size > self->Len()) {
size = self->Len();
}
data = sq_getscratchpad(v,size);
res = self->Read(data,size);
if(res <= 0)
return sq_throwerror(v,_SC("no data left to read"));
blobp = sqstd_createblob(v,res);
memcpy(blobp,data,res);
return 1;
}
#define SAFE_READN(ptr,len) { \
if(self->Read(ptr,len) != len) return sq_throwerror(v,_SC("io error")); \
}
SQInteger _stream_readn(HSQUIRRELVM v)
{
SETUP_STREAM(v);
SQInteger format;
sq_getinteger(v, 2, &format);
switch(format) {
case 'l': {
SQInteger i;
SAFE_READN(&i, sizeof(i));
sq_pushinteger(v, i);
}
break;
case 'i': {
SQInt32 i;
SAFE_READN(&i, sizeof(i));
sq_pushinteger(v, i);
}
break;
case 's': {
short s;
SAFE_READN(&s, sizeof(short));
sq_pushinteger(v, s);
}
break;
case 'w': {
unsigned short w;
SAFE_READN(&w, sizeof(unsigned short));
sq_pushinteger(v, w);
}
break;
case 'c': {
char c;
SAFE_READN(&c, sizeof(char));
sq_pushinteger(v, c);
}
break;
case 'b': {
unsigned char c;
SAFE_READN(&c, sizeof(unsigned char));
sq_pushinteger(v, c);
}
break;
case 'f': {
float f;
SAFE_READN(&f, sizeof(float));
sq_pushfloat(v, f);
}
break;
case 'd': {
double d;
SAFE_READN(&d, sizeof(double));
sq_pushfloat(v, (SQFloat)d);
}
break;
default:
return sq_throwerror(v, _SC("invalid format"));
}
return 1;
}
SQInteger _stream_writeblob(HSQUIRRELVM v)
{
SQUserPointer data;
SQInteger size;
SETUP_STREAM(v);
if(SQ_FAILED(sqstd_getblob(v,2,&data)))
return sq_throwerror(v,_SC("invalid parameter"));
size = sqstd_getblobsize(v,2);
if(self->Write(data,size) != size)
return sq_throwerror(v,_SC("io error"));
sq_pushinteger(v,size);
return 1;
}
SQInteger _stream_writen(HSQUIRRELVM v)
{
SETUP_STREAM(v);
SQInteger format, ti;
SQFloat tf;
sq_getinteger(v, 3, &format);
switch(format) {
case 'l': {
SQInteger i;
sq_getinteger(v, 2, &ti);
i = ti;
self->Write(&i, sizeof(SQInteger));
}
break;
case 'i': {
SQInt32 i;
sq_getinteger(v, 2, &ti);
i = (SQInt32)ti;
self->Write(&i, sizeof(SQInt32));
}
break;
case 's': {
short s;
sq_getinteger(v, 2, &ti);
s = (short)ti;
self->Write(&s, sizeof(short));
}
break;
case 'w': {
unsigned short w;
sq_getinteger(v, 2, &ti);
w = (unsigned short)ti;
self->Write(&w, sizeof(unsigned short));
}
break;
case 'c': {
char c;
sq_getinteger(v, 2, &ti);
c = (char)ti;
self->Write(&c, sizeof(char));
}
break;
case 'b': {
unsigned char b;
sq_getinteger(v, 2, &ti);
b = (unsigned char)ti;
self->Write(&b, sizeof(unsigned char));
}
break;
case 'f': {
float f;
sq_getfloat(v, 2, &tf);
f = (float)tf;
self->Write(&f, sizeof(float));
}
break;
case 'd': {
double d;
sq_getfloat(v, 2, &tf);
d = tf;
self->Write(&d, sizeof(double));
}
break;
default:
return sq_throwerror(v, _SC("invalid format"));
}
return 0;
}
SQInteger _stream_seek(HSQUIRRELVM v)
{
SETUP_STREAM(v);
SQInteger offset, origin = SQ_SEEK_SET;
sq_getinteger(v, 2, &offset);
if(sq_gettop(v) > 2) {
SQInteger t;
sq_getinteger(v, 3, &t);
switch(t) {
case 'b': origin = SQ_SEEK_SET; break;
case 'c': origin = SQ_SEEK_CUR; break;
case 'e': origin = SQ_SEEK_END; break;
default: return sq_throwerror(v,_SC("invalid origin"));
}
}
sq_pushinteger(v, self->Seek(offset, origin));
return 1;
}
SQInteger _stream_tell(HSQUIRRELVM v)
{
SETUP_STREAM(v);
sq_pushinteger(v, self->Tell());
return 1;
}
SQInteger _stream_len(HSQUIRRELVM v)
{
SETUP_STREAM(v);
sq_pushinteger(v, self->Len());
return 1;
}
SQInteger _stream_flush(HSQUIRRELVM v)
{
SETUP_STREAM(v);
if(!self->Flush())
sq_pushinteger(v, 1);
else
sq_pushnull(v);
return 1;
}
SQInteger _stream_eos(HSQUIRRELVM v)
{
SETUP_STREAM(v);
if(self->EOS())
sq_pushinteger(v, 1);
else
sq_pushnull(v);
return 1;
}
SQInteger _stream__cloned(HSQUIRRELVM v)
{
return sq_throwerror(v,_SC("this object cannot be cloned"));
}
static const SQRegFunction _stream_methods[] = {
_DECL_STREAM_FUNC(readblob,2,_SC("xn")),
_DECL_STREAM_FUNC(readn,2,_SC("xn")),
_DECL_STREAM_FUNC(writeblob,-2,_SC("xx")),
_DECL_STREAM_FUNC(writen,3,_SC("xnn")),
_DECL_STREAM_FUNC(seek,-2,_SC("xnn")),
_DECL_STREAM_FUNC(tell,1,_SC("x")),
_DECL_STREAM_FUNC(len,1,_SC("x")),
_DECL_STREAM_FUNC(eos,1,_SC("x")),
_DECL_STREAM_FUNC(flush,1,_SC("x")),
_DECL_STREAM_FUNC(_cloned,0,NULL),
{NULL,(SQFUNCTION)0,0,NULL}
};
void init_streamclass(HSQUIRRELVM v)
{
sq_pushregistrytable(v);
sq_pushstring(v,_SC("std_stream"),-1);
if(SQ_FAILED(sq_get(v,-2))) {
sq_pushstring(v,_SC("std_stream"),-1);
sq_newclass(v,SQFalse);
sq_settypetag(v,-1,(SQUserPointer)((SQUnsignedInteger)SQSTD_STREAM_TYPE_TAG));
SQInteger i = 0;
while(_stream_methods[i].name != 0) {
const SQRegFunction &f = _stream_methods[i];
sq_pushstring(v,f.name,-1);
sq_newclosure(v,f.f,0);
sq_setparamscheck(v,f.nparamscheck,f.typemask);
sq_newslot(v,-3,SQFalse);
i++;
}
sq_newslot(v,-3,SQFalse);
sq_pushroottable(v);
sq_pushstring(v,_SC("stream"),-1);
sq_pushstring(v,_SC("std_stream"),-1);
sq_get(v,-4);
sq_newslot(v,-3,SQFalse);
sq_pop(v,1);
}
else {
sq_pop(v,1); //result
}
sq_pop(v,1);
}
SQRESULT declare_stream(HSQUIRRELVM v,const SQChar* name,SQUserPointer typetag,const SQChar* reg_name,const SQRegFunction *methods,const SQRegFunction *globals)
{
if(sq_gettype(v,-1) != OT_TABLE)
return sq_throwerror(v,_SC("table expected"));
SQInteger top = sq_gettop(v);
//create delegate
init_streamclass(v);
sq_pushregistrytable(v);
sq_pushstring(v,reg_name,-1);
sq_pushstring(v,_SC("std_stream"),-1);
if(SQ_SUCCEEDED(sq_get(v,-3))) {
sq_newclass(v,SQTrue);
sq_settypetag(v,-1,typetag);
SQInteger i = 0;
while(methods[i].name != 0) {
const SQRegFunction &f = methods[i];
sq_pushstring(v,f.name,-1);
sq_newclosure(v,f.f,0);
sq_setparamscheck(v,f.nparamscheck,f.typemask);
sq_setnativeclosurename(v,-1,f.name);
sq_newslot(v,-3,SQFalse);
i++;
}
sq_newslot(v,-3,SQFalse);
sq_pop(v,1);
i = 0;
while(globals[i].name!=0)
{
const SQRegFunction &f = globals[i];
sq_pushstring(v,f.name,-1);
sq_newclosure(v,f.f,0);
sq_setparamscheck(v,f.nparamscheck,f.typemask);
sq_setnativeclosurename(v,-1,f.name);
sq_newslot(v,-3,SQFalse);
i++;
}
//register the class in the target table
sq_pushstring(v,name,-1);
sq_pushregistrytable(v);
sq_pushstring(v,reg_name,-1);
sq_get(v,-2);
sq_remove(v,-2);
sq_newslot(v,-3,SQFalse);
sq_settop(v,top);
return SQ_OK;
}
sq_settop(v,top);
return SQ_ERROR;
}

View File

@ -1,18 +0,0 @@
/* see copyright notice in squirrel.h */
#ifndef _SQSTD_STREAM_H_
#define _SQSTD_STREAM_H_
SQInteger _stream_readblob(HSQUIRRELVM v);
SQInteger _stream_readline(HSQUIRRELVM v);
SQInteger _stream_readn(HSQUIRRELVM v);
SQInteger _stream_writeblob(HSQUIRRELVM v);
SQInteger _stream_writen(HSQUIRRELVM v);
SQInteger _stream_seek(HSQUIRRELVM v);
SQInteger _stream_tell(HSQUIRRELVM v);
SQInteger _stream_len(HSQUIRRELVM v);
SQInteger _stream_eos(HSQUIRRELVM v);
SQInteger _stream_flush(HSQUIRRELVM v);
#define _DECL_STREAM_FUNC(name,nparams,typecheck) {_SC(#name),_stream_##name,nparams,typecheck}
SQRESULT declare_stream(HSQUIRRELVM v,const SQChar* name,SQUserPointer typetag,const SQChar* reg_name,const SQRegFunction *methods,const SQRegFunction *globals);
#endif /*_SQSTD_STREAM_H_*/

View File

@ -1,552 +0,0 @@
/* see copyright notice in squirrel.h */
#include <squirrel.h>
#include <sqstdstring.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <assert.h>
#include <stdarg.h>
#define MAX_FORMAT_LEN 20
#define MAX_WFORMAT_LEN 3
#define ADDITIONAL_FORMAT_SPACE (100*sizeof(SQChar))
static SQUserPointer rex_typetag = NULL;
static SQBool isfmtchr(SQChar ch)
{
switch(ch) {
case '-': case '+': case ' ': case '#': case '0': return SQTrue;
}
return SQFalse;
}
static SQInteger validate_format(HSQUIRRELVM v, SQChar *fmt, const SQChar *src, SQInteger n,SQInteger &width)
{
SQChar *dummy;
SQChar swidth[MAX_WFORMAT_LEN];
SQInteger wc = 0;
SQInteger start = n;
fmt[0] = '%';
while (isfmtchr(src[n])) n++;
while (scisdigit(src[n])) {
swidth[wc] = src[n];
n++;
wc++;
if(wc>=MAX_WFORMAT_LEN)
return sq_throwerror(v,_SC("width format too long"));
}
swidth[wc] = '\0';
if(wc > 0) {
width = scstrtol(swidth,&dummy,10);
}
else
width = 0;
if (src[n] == '.') {
n++;
wc = 0;
while (scisdigit(src[n])) {
swidth[wc] = src[n];
n++;
wc++;
if(wc>=MAX_WFORMAT_LEN)
return sq_throwerror(v,_SC("precision format too long"));
}
swidth[wc] = '\0';
if(wc > 0) {
width += scstrtol(swidth,&dummy,10);
}
}
if (n-start > MAX_FORMAT_LEN )
return sq_throwerror(v,_SC("format too long"));
memcpy(&fmt[1],&src[start],((n-start)+1)*sizeof(SQChar));
fmt[(n-start)+2] = '\0';
return n;
}
SQRESULT sqstd_format(HSQUIRRELVM v,SQInteger nformatstringidx,SQInteger *outlen,SQChar **output)
{
const SQChar *format;
SQChar *dest;
SQChar fmt[MAX_FORMAT_LEN];
const SQRESULT res = sq_getstring(v,nformatstringidx,&format);
if (SQ_FAILED(res)) {
return res; // propagate the error
}
SQInteger format_size = sq_getsize(v,nformatstringidx);
SQInteger allocated = (format_size+2)*sizeof(SQChar);
dest = sq_getscratchpad(v,allocated);
SQInteger n = 0,i = 0, nparam = nformatstringidx+1, w = 0;
//while(format[n] != '\0')
while(n < format_size)
{
if(format[n] != '%') {
assert(i < allocated);
dest[i++] = format[n];
n++;
}
else if(format[n+1] == '%') { //handles %%
dest[i++] = '%';
n += 2;
}
else {
n++;
if( nparam > sq_gettop(v) )
return sq_throwerror(v,_SC("not enough parameters for the given format string"));
n = validate_format(v,fmt,format,n,w);
if(n < 0) return -1;
SQInteger addlen = 0;
SQInteger valtype = 0;
const SQChar *ts = NULL;
SQInteger ti = 0;
SQFloat tf = 0;
switch(format[n]) {
case 's':
if(SQ_FAILED(sq_getstring(v,nparam,&ts)))
return sq_throwerror(v,_SC("string expected for the specified format"));
addlen = (sq_getsize(v,nparam)*sizeof(SQChar))+((w+1)*sizeof(SQChar));
valtype = 's';
break;
case 'i': case 'd': case 'o': case 'u': case 'x': case 'X':
#ifdef _SQ64
{
size_t flen = scstrlen(fmt);
SQInteger fpos = flen - 1;
SQChar f = fmt[fpos];
const SQChar *prec = (const SQChar *)_PRINT_INT_PREC;
while(*prec != _SC('\0')) {
fmt[fpos++] = *prec++;
}
fmt[fpos++] = f;
fmt[fpos++] = _SC('\0');
}
#endif
case 'c':
if(SQ_FAILED(sq_getinteger(v,nparam,&ti)))
return sq_throwerror(v,_SC("integer expected for the specified format"));
addlen = (ADDITIONAL_FORMAT_SPACE)+((w+1)*sizeof(SQChar));
valtype = 'i';
break;
case 'f': case 'g': case 'G': case 'e': case 'E':
if(SQ_FAILED(sq_getfloat(v,nparam,&tf)))
return sq_throwerror(v,_SC("float expected for the specified format"));
addlen = (ADDITIONAL_FORMAT_SPACE)+((w+1)*sizeof(SQChar));
valtype = 'f';
break;
default:
return sq_throwerror(v,_SC("invalid format"));
}
n++;
allocated += addlen + sizeof(SQChar);
dest = sq_getscratchpad(v,allocated);
switch(valtype) {
case 's': i += scsprintf(&dest[i],allocated,fmt,ts); break;
case 'i': i += scsprintf(&dest[i],allocated,fmt,ti); break;
case 'f': i += scsprintf(&dest[i],allocated,fmt,tf); break;
};
nparam ++;
}
}
*outlen = i;
dest[i] = '\0';
*output = dest;
return SQ_OK;
}
void sqstd_pushstringf(HSQUIRRELVM v,const SQChar *s,...)
{
SQInteger n=256;
va_list args;
begin:
va_start(args,s);
SQChar *b=sq_getscratchpad(v,n);
SQInteger r=scvsprintf(b,n,s,args);
va_end(args);
if (r>=n) {
n=r+1;//required+null
goto begin;
} else if (r<0) {
sq_pushnull(v);
} else {
sq_pushstring(v,b,r);
}
}
static SQInteger _string_printf(HSQUIRRELVM v)
{
SQChar *dest = NULL;
SQInteger length = 0;
if(SQ_FAILED(sqstd_format(v,2,&length,&dest)))
return -1;
SQPRINTFUNCTION printfunc = sq_getprintfunc(v);
if(printfunc) printfunc(v,_SC("%s"),dest);
return 0;
}
static SQInteger _string_format(HSQUIRRELVM v)
{
SQChar *dest = NULL;
SQInteger length = 0;
if(SQ_FAILED(sqstd_format(v,2,&length,&dest)))
return -1;
sq_pushstring(v,dest,length);
return 1;
}
static void __strip_l(const SQChar *str,const SQChar **start)
{
const SQChar *t = str;
while(((*t) != '\0') && scisspace(*t)){ t++; }
*start = t;
}
static void __strip_r(const SQChar *str,SQInteger len,const SQChar **end)
{
if(len == 0) {
*end = str;
return;
}
const SQChar *t = &str[len-1];
while(t >= str && scisspace(*t)) { t--; }
*end = t + 1;
}
static SQInteger _string_strip(HSQUIRRELVM v)
{
const SQChar *str,*start,*end;
sq_getstring(v,2,&str);
SQInteger len = sq_getsize(v,2);
__strip_l(str,&start);
__strip_r(str,len,&end);
sq_pushstring(v,start,end - start);
return 1;
}
static SQInteger _string_lstrip(HSQUIRRELVM v)
{
const SQChar *str,*start;
sq_getstring(v,2,&str);
__strip_l(str,&start);
sq_pushstring(v,start,-1);
return 1;
}
static SQInteger _string_rstrip(HSQUIRRELVM v)
{
const SQChar *str,*end;
sq_getstring(v,2,&str);
SQInteger len = sq_getsize(v,2);
__strip_r(str,len,&end);
sq_pushstring(v,str,end - str);
return 1;
}
static SQInteger _string_split(HSQUIRRELVM v)
{
const SQChar *str,*seps;
SQInteger sepsize;
SQBool skipempty = SQFalse;
sq_getstring(v,2,&str);
sq_getstringandsize(v,3,&seps,&sepsize);
if(sepsize == 0) return sq_throwerror(v,_SC("empty separators string"));
if(sq_gettop(v)>3) {
sq_getbool(v,4,&skipempty);
}
const SQChar *start = str;
const SQChar *end = str;
sq_newarray(v,0);
while(*end != '\0')
{
SQChar cur = *end;
for(SQInteger i = 0; i < sepsize; i++)
{
if(cur == seps[i])
{
if(!skipempty || (end != start)) {
sq_pushstring(v,start,end-start);
sq_arrayappend(v,-2);
}
start = end + 1;
break;
}
}
end++;
}
if(end != start)
{
sq_pushstring(v,start,end-start);
sq_arrayappend(v,-2);
}
return 1;
}
static SQInteger _string_escape(HSQUIRRELVM v)
{
const SQChar *str;
SQChar *dest,*resstr;
SQInteger size;
sq_getstring(v,2,&str);
size = sq_getsize(v,2);
if(size == 0) {
sq_push(v,2);
return 1;
}
#ifdef SQUNICODE
#if WCHAR_SIZE == 2
const SQChar *escpat = _SC("\\x%04x");
const SQInteger maxescsize = 6;
#else //WCHAR_SIZE == 4
const SQChar *escpat = _SC("\\x%08x");
const SQInteger maxescsize = 10;
#endif
#else
const SQChar *escpat = _SC("\\x%02x");
const SQInteger maxescsize = 4;
#endif
SQInteger destcharsize = (size * maxescsize); //assumes every char could be escaped
resstr = dest = (SQChar *)sq_getscratchpad(v,destcharsize * sizeof(SQChar));
SQChar c;
SQChar escch;
SQInteger escaped = 0;
for(int n = 0; n < size; n++){
c = *str++;
escch = 0;
if(scisprint(c) || c == 0) {
switch(c) {
case '\a': escch = 'a'; break;
case '\b': escch = 'b'; break;
case '\t': escch = 't'; break;
case '\n': escch = 'n'; break;
case '\v': escch = 'v'; break;
case '\f': escch = 'f'; break;
case '\r': escch = 'r'; break;
case '\\': escch = '\\'; break;
case '\"': escch = '\"'; break;
case '\'': escch = '\''; break;
case 0: escch = '0'; break;
}
if(escch) {
*dest++ = '\\';
*dest++ = escch;
escaped++;
}
else {
*dest++ = c;
}
}
else {
dest += scsprintf(dest, destcharsize, escpat, c);
escaped++;
}
}
if(escaped) {
sq_pushstring(v,resstr,dest - resstr);
}
else {
sq_push(v,2); //nothing escaped
}
return 1;
}
static SQInteger _string_startswith(HSQUIRRELVM v)
{
const SQChar *str,*cmp;
sq_getstring(v,2,&str);
sq_getstring(v,3,&cmp);
SQInteger len = sq_getsize(v,2);
SQInteger cmplen = sq_getsize(v,3);
SQBool ret = SQFalse;
if(cmplen <= len) {
ret = memcmp(str,cmp,sq_rsl(cmplen)) == 0 ? SQTrue : SQFalse;
}
sq_pushbool(v,ret);
return 1;
}
static SQInteger _string_endswith(HSQUIRRELVM v)
{
const SQChar *str,*cmp;
sq_getstring(v,2,&str);
sq_getstring(v,3,&cmp);
SQInteger len = sq_getsize(v,2);
SQInteger cmplen = sq_getsize(v,3);
SQBool ret = SQFalse;
if(cmplen <= len) {
ret = memcmp(&str[len - cmplen],cmp,sq_rsl(cmplen)) == 0 ? SQTrue : SQFalse;
}
sq_pushbool(v,ret);
return 1;
}
#define SETUP_REX(v) \
SQRex *self = NULL; \
if(SQ_FAILED(sq_getinstanceup(v,1,(SQUserPointer *)&self,rex_typetag,SQFalse))) { \
return sq_throwerror(v,_SC("invalid type tag")); \
}
static SQInteger _rexobj_releasehook(SQUserPointer p, SQInteger SQ_UNUSED_ARG(size))
{
SQRex *self = ((SQRex *)p);
sqstd_rex_free(self);
return 1;
}
static SQInteger _regexp_match(HSQUIRRELVM v)
{
SETUP_REX(v);
const SQChar *str;
sq_getstring(v,2,&str);
if(sqstd_rex_match(self,str) == SQTrue)
{
sq_pushbool(v,SQTrue);
return 1;
}
sq_pushbool(v,SQFalse);
return 1;
}
static void _addrexmatch(HSQUIRRELVM v,const SQChar *str,const SQChar *begin,const SQChar *end)
{
sq_newtable(v);
sq_pushstring(v,_SC("begin"),-1);
sq_pushinteger(v,begin - str);
sq_rawset(v,-3);
sq_pushstring(v,_SC("end"),-1);
sq_pushinteger(v,end - str);
sq_rawset(v,-3);
}
static SQInteger _regexp_search(HSQUIRRELVM v)
{
SETUP_REX(v);
const SQChar *str,*begin,*end;
SQInteger start = 0;
sq_getstring(v,2,&str);
if(sq_gettop(v) > 2) sq_getinteger(v,3,&start);
if(sqstd_rex_search(self,str+start,&begin,&end) == SQTrue) {
_addrexmatch(v,str,begin,end);
return 1;
}
return 0;
}
static SQInteger _regexp_capture(HSQUIRRELVM v)
{
SETUP_REX(v);
const SQChar *str,*begin,*end;
SQInteger start = 0;
sq_getstring(v,2,&str);
if(sq_gettop(v) > 2) sq_getinteger(v,3,&start);
if(sqstd_rex_search(self,str+start,&begin,&end) == SQTrue) {
SQInteger n = sqstd_rex_getsubexpcount(self);
SQRexMatch match;
sq_newarray(v,0);
for(SQInteger i = 0;i < n; i++) {
sqstd_rex_getsubexp(self,i,&match);
if(match.len > 0)
_addrexmatch(v,str,match.begin,match.begin+match.len);
else
_addrexmatch(v,str,str,str); //empty match
sq_arrayappend(v,-2);
}
return 1;
}
return 0;
}
static SQInteger _regexp_subexpcount(HSQUIRRELVM v)
{
SETUP_REX(v);
sq_pushinteger(v,sqstd_rex_getsubexpcount(self));
return 1;
}
static SQInteger _regexp_constructor(HSQUIRRELVM v)
{
SQRex *self = NULL;
if (SQ_FAILED(sq_getinstanceup(v, 1, (SQUserPointer *)&self, rex_typetag, SQFalse))) {
return sq_throwerror(v, _SC("invalid type tag"));
}
if (self != NULL) {
return sq_throwerror(v, _SC("invalid regexp object"));
}
const SQChar *error,*pattern;
sq_getstring(v,2,&pattern);
SQRex *rex = sqstd_rex_compile(pattern,&error);
if(!rex) return sq_throwerror(v,error);
sq_setinstanceup(v,1,rex);
sq_setreleasehook(v,1,_rexobj_releasehook);
return 0;
}
static SQInteger _regexp__typeof(HSQUIRRELVM v)
{
sq_pushstring(v,_SC("regexp"),-1);
return 1;
}
#define _DECL_REX_FUNC(name,nparams,pmask) {_SC(#name),_regexp_##name,nparams,pmask}
static const SQRegFunction rexobj_funcs[]={
_DECL_REX_FUNC(constructor,2,_SC(".s")),
_DECL_REX_FUNC(search,-2,_SC("xsn")),
_DECL_REX_FUNC(match,2,_SC("xs")),
_DECL_REX_FUNC(capture,-2,_SC("xsn")),
_DECL_REX_FUNC(subexpcount,1,_SC("x")),
_DECL_REX_FUNC(_typeof,1,_SC("x")),
{NULL,(SQFUNCTION)0,0,NULL}
};
#undef _DECL_REX_FUNC
#define _DECL_FUNC(name,nparams,pmask) {_SC(#name),_string_##name,nparams,pmask}
static const SQRegFunction stringlib_funcs[]={
_DECL_FUNC(format,-2,_SC(".s")),
_DECL_FUNC(printf,-2,_SC(".s")),
_DECL_FUNC(strip,2,_SC(".s")),
_DECL_FUNC(lstrip,2,_SC(".s")),
_DECL_FUNC(rstrip,2,_SC(".s")),
_DECL_FUNC(split,-3,_SC(".ssb")),
_DECL_FUNC(escape,2,_SC(".s")),
_DECL_FUNC(startswith,3,_SC(".ss")),
_DECL_FUNC(endswith,3,_SC(".ss")),
{NULL,(SQFUNCTION)0,0,NULL}
};
#undef _DECL_FUNC
SQInteger sqstd_register_stringlib(HSQUIRRELVM v)
{
sq_pushstring(v,_SC("regexp"),-1);
sq_newclass(v,SQFalse);
rex_typetag = (SQUserPointer)rexobj_funcs;
sq_settypetag(v, -1, rex_typetag);
SQInteger i = 0;
while(rexobj_funcs[i].name != 0) {
const SQRegFunction &f = rexobj_funcs[i];
sq_pushstring(v,f.name,-1);
sq_newclosure(v,f.f,0);
sq_setparamscheck(v,f.nparamscheck,f.typemask);
sq_setnativeclosurename(v,-1,f.name);
sq_newslot(v,-3,SQFalse);
i++;
}
sq_newslot(v,-3,SQFalse);
i = 0;
while(stringlib_funcs[i].name!=0)
{
sq_pushstring(v,stringlib_funcs[i].name,-1);
sq_newclosure(v,stringlib_funcs[i].f,0);
sq_setparamscheck(v,stringlib_funcs[i].nparamscheck,stringlib_funcs[i].typemask);
sq_setnativeclosurename(v,-1,stringlib_funcs[i].name);
sq_newslot(v,-3,SQFalse);
i++;
}
return 1;
}

View File

@ -1,154 +0,0 @@
/* see copyright notice in squirrel.h */
#include <squirrel.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <sqstdsystem.h>
#ifdef SQUNICODE
#include <wchar.h>
#define scgetenv _wgetenv
#define scsystem _wsystem
#define scasctime _wasctime
#define scremove _wremove
#define screname _wrename
#else
#define scgetenv getenv
#define scsystem system
#define scasctime asctime
#define scremove remove
#define screname rename
#endif
#ifdef IOS
#include <spawn.h>
extern char **environ;
#endif
static SQInteger _system_getenv(HSQUIRRELVM v)
{
const SQChar *s;
if(SQ_SUCCEEDED(sq_getstring(v,2,&s))){
sq_pushstring(v,scgetenv(s),-1);
return 1;
}
return 0;
}
static SQInteger _system_system(HSQUIRRELVM v)
{
const SQChar *s;
if(SQ_SUCCEEDED(sq_getstring(v,2,&s))){
#ifdef IOS
pid_t pid;
posix_spawn(&pid, s, NULL, NULL, NULL, environ);
sq_pushinteger(v, 0);
#else
sq_pushinteger(v,scsystem(s));
#endif
return 1;
}
return sq_throwerror(v,_SC("wrong param"));
}
static SQInteger _system_clock(HSQUIRRELVM v)
{
sq_pushfloat(v,((SQFloat)clock())/(SQFloat)CLOCKS_PER_SEC);
return 1;
}
static SQInteger _system_time(HSQUIRRELVM v)
{
SQInteger t = (SQInteger)time(NULL);
sq_pushinteger(v,t);
return 1;
}
static SQInteger _system_remove(HSQUIRRELVM v)
{
const SQChar *s;
sq_getstring(v,2,&s);
if(scremove(s)==-1)
return sq_throwerror(v,_SC("remove() failed"));
return 0;
}
static SQInteger _system_rename(HSQUIRRELVM v)
{
const SQChar *oldn,*newn;
sq_getstring(v,2,&oldn);
sq_getstring(v,3,&newn);
if(screname(oldn,newn)==-1)
return sq_throwerror(v,_SC("rename() failed"));
return 0;
}
static void _set_integer_slot(HSQUIRRELVM v,const SQChar *name,SQInteger val)
{
sq_pushstring(v,name,-1);
sq_pushinteger(v,val);
sq_rawset(v,-3);
}
static SQInteger _system_date(HSQUIRRELVM v)
{
time_t t;
SQInteger it;
SQInteger format = 'l';
if(sq_gettop(v) > 1) {
sq_getinteger(v,2,&it);
t = it;
if(sq_gettop(v) > 2) {
sq_getinteger(v,3,(SQInteger*)&format);
}
}
else {
time(&t);
}
tm *date;
if(format == 'u')
date = gmtime(&t);
else
date = localtime(&t);
if(!date)
return sq_throwerror(v,_SC("crt api failure"));
sq_newtable(v);
_set_integer_slot(v, _SC("sec"), date->tm_sec);
_set_integer_slot(v, _SC("min"), date->tm_min);
_set_integer_slot(v, _SC("hour"), date->tm_hour);
_set_integer_slot(v, _SC("day"), date->tm_mday);
_set_integer_slot(v, _SC("month"), date->tm_mon);
_set_integer_slot(v, _SC("year"), date->tm_year+1900);
_set_integer_slot(v, _SC("wday"), date->tm_wday);
_set_integer_slot(v, _SC("yday"), date->tm_yday);
return 1;
}
#define _DECL_FUNC(name,nparams,pmask) {_SC(#name),_system_##name,nparams,pmask}
static const SQRegFunction systemlib_funcs[]={
_DECL_FUNC(getenv,2,_SC(".s")),
_DECL_FUNC(system,2,_SC(".s")),
_DECL_FUNC(clock,0,NULL),
_DECL_FUNC(time,1,NULL),
_DECL_FUNC(date,-1,_SC(".nn")),
_DECL_FUNC(remove,2,_SC(".s")),
_DECL_FUNC(rename,3,_SC(".ss")),
{NULL,(SQFUNCTION)0,0,NULL}
};
#undef _DECL_FUNC
SQInteger sqstd_register_systemlib(HSQUIRRELVM v)
{
SQInteger i=0;
while(systemlib_funcs[i].name!=0)
{
sq_pushstring(v,systemlib_funcs[i].name,-1);
sq_newclosure(v,systemlib_funcs[i].f,0);
sq_setparamscheck(v,systemlib_funcs[i].nparamscheck,systemlib_funcs[i].typemask);
sq_setnativeclosurename(v,-1,systemlib_funcs[i].name);
sq_newslot(v,-3,SQFalse);
i++;
}
return 1;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,94 +0,0 @@
/* see copyright notice in squirrel.h */
#ifndef _SQARRAY_H_
#define _SQARRAY_H_
struct SQArray : public CHAINABLE_OBJ
{
private:
SQArray(SQSharedState *ss,SQInteger nsize){_values.resize(nsize); INIT_CHAIN();ADD_TO_CHAIN(&_ss(this)->_gc_chain,this);}
~SQArray()
{
REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain,this);
}
public:
static SQArray* Create(SQSharedState *ss,SQInteger nInitialSize){
SQArray *newarray=(SQArray*)SQ_MALLOC(sizeof(SQArray));
new (newarray) SQArray(ss,nInitialSize);
return newarray;
}
#ifndef NO_GARBAGE_COLLECTOR
void Mark(SQCollectable **chain);
SQObjectType GetType() {return OT_ARRAY;}
#endif
void Finalize(){
_values.resize(0);
}
bool Get(const SQInteger nidx,SQObjectPtr &val)
{
if(nidx>=0 && nidx<(SQInteger)_values.size()){
SQObjectPtr &o = _values[nidx];
val = _realval(o);
return true;
}
else return false;
}
bool Set(const SQInteger nidx,const SQObjectPtr &val)
{
if(nidx>=0 && nidx<(SQInteger)_values.size()){
_values[nidx]=val;
return true;
}
else return false;
}
SQInteger Next(const SQObjectPtr &refpos,SQObjectPtr &outkey,SQObjectPtr &outval)
{
SQUnsignedInteger idx=TranslateIndex(refpos);
while(idx<_values.size()){
//first found
outkey=(SQInteger)idx;
SQObjectPtr &o = _values[idx];
outval = _realval(o);
//return idx for the next iteration
return ++idx;
}
//nothing to iterate anymore
return -1;
}
SQArray *Clone(){SQArray *anew=Create(_opt_ss(this),0); anew->_values.copy(_values); return anew; }
SQInteger Size() const {return _values.size();}
void Resize(SQInteger size)
{
SQObjectPtr _null;
Resize(size,_null);
}
void Resize(SQInteger size,SQObjectPtr &fill) { _values.resize(size,fill); ShrinkIfNeeded(); }
void Reserve(SQInteger size) { _values.reserve(size); }
void Append(const SQObject &o){_values.push_back(o);}
void Extend(const SQArray *a);
SQObjectPtr &Top(){return _values.top();}
void Pop(){_values.pop_back(); ShrinkIfNeeded(); }
bool Insert(SQInteger idx,const SQObject &val){
if(idx < 0 || idx > (SQInteger)_values.size())
return false;
_values.insert(idx,val);
return true;
}
void ShrinkIfNeeded() {
if(_values.size() <= _values.capacity()>>2) //shrink the array
_values.shrinktofit();
}
bool Remove(SQInteger idx){
if(idx < 0 || idx >= (SQInteger)_values.size())
return false;
_values.remove(idx);
ShrinkIfNeeded();
return true;
}
void Release()
{
sq_delete(this,SQArray);
}
SQObjectPtrVec _values;
};
#endif //_SQARRAY_H_

File diff suppressed because it is too large Load Diff

View File

@ -1,213 +0,0 @@
/*
see copyright notice in squirrel.h
*/
#include "sqpcheader.h"
#include "sqvm.h"
#include "sqtable.h"
#include "sqclass.h"
#include "sqfuncproto.h"
#include "sqclosure.h"
SQClass::SQClass(SQSharedState *ss,SQClass *base)
{
_base = base;
_typetag = 0;
_hook = NULL;
_udsize = 0;
_locked = false;
_constructoridx = -1;
if(_base) {
_constructoridx = _base->_constructoridx;
_udsize = _base->_udsize;
_defaultvalues.copy(base->_defaultvalues);
_methods.copy(base->_methods);
_COPY_VECTOR(_metamethods,base->_metamethods,MT_LAST);
__ObjAddRef(_base);
}
_members = base?base->_members->Clone() : SQTable::Create(ss,0);
__ObjAddRef(_members);
INIT_CHAIN();
ADD_TO_CHAIN(&_sharedstate->_gc_chain, this);
}
void SQClass::Finalize() {
_attributes.Null();
_NULL_SQOBJECT_VECTOR(_defaultvalues,_defaultvalues.size());
_methods.resize(0);
_NULL_SQOBJECT_VECTOR(_metamethods,MT_LAST);
__ObjRelease(_members);
if(_base) {
__ObjRelease(_base);
}
}
SQClass::~SQClass()
{
REMOVE_FROM_CHAIN(&_sharedstate->_gc_chain, this);
Finalize();
}
bool SQClass::NewSlot(SQSharedState *ss,const SQObjectPtr &key,const SQObjectPtr &val,bool bstatic)
{
SQObjectPtr temp;
bool belongs_to_static_table = sq_type(val) == OT_CLOSURE || sq_type(val) == OT_NATIVECLOSURE || bstatic;
if(_locked && !belongs_to_static_table)
return false; //the class already has an instance so cannot be modified
if(_members->Get(key,temp) && _isfield(temp)) //overrides the default value
{
_defaultvalues[_member_idx(temp)].val = val;
return true;
}
if (_members->CountUsed() >= MEMBER_MAX_COUNT) {
return false;
}
if(belongs_to_static_table) {
SQInteger mmidx;
if((sq_type(val) == OT_CLOSURE || sq_type(val) == OT_NATIVECLOSURE) &&
(mmidx = ss->GetMetaMethodIdxByName(key)) != -1) {
_metamethods[mmidx] = val;
}
else {
SQObjectPtr theval = val;
if(_base && sq_type(val) == OT_CLOSURE) {
theval = _closure(val)->Clone();
_closure(theval)->_base = _base;
__ObjAddRef(_base); //ref for the closure
}
if(sq_type(temp) == OT_NULL) {
bool isconstructor;
SQVM::IsEqual(ss->_constructoridx, key, isconstructor);
if(isconstructor) {
_constructoridx = (SQInteger)_methods.size();
}
SQClassMember m;
m.val = theval;
_members->NewSlot(key,SQObjectPtr(_make_method_idx(_methods.size())));
_methods.push_back(m);
}
else {
_methods[_member_idx(temp)].val = theval;
}
}
return true;
}
SQClassMember m;
m.val = val;
_members->NewSlot(key,SQObjectPtr(_make_field_idx(_defaultvalues.size())));
_defaultvalues.push_back(m);
return true;
}
SQInstance *SQClass::CreateInstance()
{
if(!_locked) Lock();
return SQInstance::Create(_opt_ss(this),this);
}
SQInteger SQClass::Next(const SQObjectPtr &refpos, SQObjectPtr &outkey, SQObjectPtr &outval)
{
SQObjectPtr oval;
SQInteger idx = _members->Next(false,refpos,outkey,oval);
if(idx != -1) {
if(_ismethod(oval)) {
outval = _methods[_member_idx(oval)].val;
}
else {
SQObjectPtr &o = _defaultvalues[_member_idx(oval)].val;
outval = _realval(o);
}
}
return idx;
}
bool SQClass::SetAttributes(const SQObjectPtr &key,const SQObjectPtr &val)
{
SQObjectPtr idx;
if(_members->Get(key,idx)) {
if(_isfield(idx))
_defaultvalues[_member_idx(idx)].attrs = val;
else
_methods[_member_idx(idx)].attrs = val;
return true;
}
return false;
}
bool SQClass::GetAttributes(const SQObjectPtr &key,SQObjectPtr &outval)
{
SQObjectPtr idx;
if(_members->Get(key,idx)) {
outval = (_isfield(idx)?_defaultvalues[_member_idx(idx)].attrs:_methods[_member_idx(idx)].attrs);
return true;
}
return false;
}
///////////////////////////////////////////////////////////////////////
void SQInstance::Init(SQSharedState *ss)
{
_userpointer = NULL;
_hook = NULL;
__ObjAddRef(_class);
_delegate = _class->_members;
INIT_CHAIN();
ADD_TO_CHAIN(&_sharedstate->_gc_chain, this);
}
SQInstance::SQInstance(SQSharedState *ss, SQClass *c, SQInteger memsize)
{
_memsize = memsize;
_class = c;
SQUnsignedInteger nvalues = _class->_defaultvalues.size();
for(SQUnsignedInteger n = 0; n < nvalues; n++) {
new (&_values[n]) SQObjectPtr(_class->_defaultvalues[n].val);
}
Init(ss);
}
SQInstance::SQInstance(SQSharedState *ss, SQInstance *i, SQInteger memsize)
{
_memsize = memsize;
_class = i->_class;
SQUnsignedInteger nvalues = _class->_defaultvalues.size();
for(SQUnsignedInteger n = 0; n < nvalues; n++) {
new (&_values[n]) SQObjectPtr(i->_values[n]);
}
Init(ss);
}
void SQInstance::Finalize()
{
SQUnsignedInteger nvalues = _class->_defaultvalues.size();
__ObjRelease(_class);
_NULL_SQOBJECT_VECTOR(_values,nvalues);
}
SQInstance::~SQInstance()
{
REMOVE_FROM_CHAIN(&_sharedstate->_gc_chain, this);
if(_class){ Finalize(); } //if _class is null it was already finalized by the GC
}
bool SQInstance::GetMetaMethod(SQVM* SQ_UNUSED_ARG(v),SQMetaMethod mm,SQObjectPtr &res)
{
if(sq_type(_class->_metamethods[mm]) != OT_NULL) {
res = _class->_metamethods[mm];
return true;
}
return false;
}
bool SQInstance::InstanceOf(SQClass *trg)
{
SQClass *parent = _class;
while(parent != NULL) {
if(parent == trg)
return true;
parent = parent->_base;
}
return false;
}

View File

@ -1,163 +0,0 @@
/* see copyright notice in squirrel.h */
#ifndef _SQCLASS_H_
#define _SQCLASS_H_
struct SQInstance;
struct SQClassMember {
SQObjectPtr val;
SQObjectPtr attrs;
void Null() {
val.Null();
attrs.Null();
}
};
typedef sqvector<SQClassMember> SQClassMemberVec;
#define MEMBER_TYPE_METHOD 0x01000000
#define MEMBER_TYPE_FIELD 0x02000000
#define MEMBER_MAX_COUNT 0x00FFFFFF
#define _ismethod(o) (_integer(o)&MEMBER_TYPE_METHOD)
#define _isfield(o) (_integer(o)&MEMBER_TYPE_FIELD)
#define _make_method_idx(i) ((SQInteger)(MEMBER_TYPE_METHOD|i))
#define _make_field_idx(i) ((SQInteger)(MEMBER_TYPE_FIELD|i))
#define _member_type(o) (_integer(o)&0xFF000000)
#define _member_idx(o) (_integer(o)&0x00FFFFFF)
struct SQClass : public CHAINABLE_OBJ
{
SQClass(SQSharedState *ss,SQClass *base);
public:
static SQClass* Create(SQSharedState *ss,SQClass *base) {
SQClass *newclass = (SQClass *)SQ_MALLOC(sizeof(SQClass));
new (newclass) SQClass(ss, base);
return newclass;
}
~SQClass();
bool NewSlot(SQSharedState *ss, const SQObjectPtr &key,const SQObjectPtr &val,bool bstatic);
bool Get(const SQObjectPtr &key,SQObjectPtr &val) {
if(_members->Get(key,val)) {
if(_isfield(val)) {
SQObjectPtr &o = _defaultvalues[_member_idx(val)].val;
val = _realval(o);
}
else {
val = _methods[_member_idx(val)].val;
}
return true;
}
return false;
}
bool GetConstructor(SQObjectPtr &ctor)
{
if(_constructoridx != -1) {
ctor = _methods[_constructoridx].val;
return true;
}
return false;
}
bool SetAttributes(const SQObjectPtr &key,const SQObjectPtr &val);
bool GetAttributes(const SQObjectPtr &key,SQObjectPtr &outval);
void Lock() { _locked = true; if(_base) _base->Lock(); }
void Release() {
if (_hook) { _hook(_typetag,0);}
sq_delete(this, SQClass);
}
void Finalize();
#ifndef NO_GARBAGE_COLLECTOR
void Mark(SQCollectable ** );
SQObjectType GetType() {return OT_CLASS;}
#endif
SQInteger Next(const SQObjectPtr &refpos, SQObjectPtr &outkey, SQObjectPtr &outval);
SQInstance *CreateInstance();
SQTable *_members;
SQClass *_base;
SQClassMemberVec _defaultvalues;
SQClassMemberVec _methods;
SQObjectPtr _metamethods[MT_LAST];
SQObjectPtr _attributes;
SQUserPointer _typetag;
SQRELEASEHOOK _hook;
bool _locked;
SQInteger _constructoridx;
SQInteger _udsize;
};
#define calcinstancesize(_theclass_) \
(_theclass_->_udsize + sq_aligning(sizeof(SQInstance) + (sizeof(SQObjectPtr)*(_theclass_->_defaultvalues.size()>0?_theclass_->_defaultvalues.size()-1:0))))
struct SQInstance : public SQDelegable
{
void Init(SQSharedState *ss);
SQInstance(SQSharedState *ss, SQClass *c, SQInteger memsize);
SQInstance(SQSharedState *ss, SQInstance *c, SQInteger memsize);
public:
static SQInstance* Create(SQSharedState *ss,SQClass *theclass) {
SQInteger size = calcinstancesize(theclass);
SQInstance *newinst = (SQInstance *)SQ_MALLOC(size);
new (newinst) SQInstance(ss, theclass,size);
if(theclass->_udsize) {
newinst->_userpointer = ((unsigned char *)newinst) + (size - theclass->_udsize);
}
return newinst;
}
SQInstance *Clone(SQSharedState *ss)
{
SQInteger size = calcinstancesize(_class);
SQInstance *newinst = (SQInstance *)SQ_MALLOC(size);
new (newinst) SQInstance(ss, this,size);
if(_class->_udsize) {
newinst->_userpointer = ((unsigned char *)newinst) + (size - _class->_udsize);
}
return newinst;
}
~SQInstance();
bool Get(const SQObjectPtr &key,SQObjectPtr &val) {
if(_class->_members->Get(key,val)) {
if(_isfield(val)) {
SQObjectPtr &o = _values[_member_idx(val)];
val = _realval(o);
}
else {
val = _class->_methods[_member_idx(val)].val;
}
return true;
}
return false;
}
bool Set(const SQObjectPtr &key,const SQObjectPtr &val) {
SQObjectPtr idx;
if(_class->_members->Get(key,idx) && _isfield(idx)) {
_values[_member_idx(idx)] = val;
return true;
}
return false;
}
void Release() {
_uiRef++;
if (_hook) { _hook(_userpointer,0);}
_uiRef--;
if(_uiRef > 0) return;
SQInteger size = _memsize;
this->~SQInstance();
SQ_FREE(this, size);
}
void Finalize();
#ifndef NO_GARBAGE_COLLECTOR
void Mark(SQCollectable ** );
SQObjectType GetType() {return OT_INSTANCE;}
#endif
bool InstanceOf(SQClass *trg);
bool GetMetaMethod(SQVM *v,SQMetaMethod mm,SQObjectPtr &res);
SQClass *_class;
SQUserPointer _userpointer;
SQRELEASEHOOK _hook;
SQInteger _memsize;
SQObjectPtr _values[1];
};
#endif //_SQCLASS_H_

View File

@ -1,201 +0,0 @@
/* see copyright notice in squirrel.h */
#ifndef _SQCLOSURE_H_
#define _SQCLOSURE_H_
#define _CALC_CLOSURE_SIZE(func) (sizeof(SQClosure) + (func->_noutervalues*sizeof(SQObjectPtr)) + (func->_ndefaultparams*sizeof(SQObjectPtr)))
struct SQFunctionProto;
struct SQClass;
struct SQClosure : public CHAINABLE_OBJ
{
private:
SQClosure(SQSharedState *ss,SQFunctionProto *func){_function = func; __ObjAddRef(_function); _base = NULL; INIT_CHAIN();ADD_TO_CHAIN(&_ss(this)->_gc_chain,this); _env = NULL; _root=NULL;}
public:
static SQClosure *Create(SQSharedState *ss,SQFunctionProto *func,SQWeakRef *root){
SQInteger size = _CALC_CLOSURE_SIZE(func);
SQClosure *nc=(SQClosure*)SQ_MALLOC(size);
new (nc) SQClosure(ss,func);
nc->_outervalues = (SQObjectPtr *)(nc + 1);
nc->_defaultparams = &nc->_outervalues[func->_noutervalues];
nc->_root = root;
__ObjAddRef(nc->_root);
_CONSTRUCT_VECTOR(SQObjectPtr,func->_noutervalues,nc->_outervalues);
_CONSTRUCT_VECTOR(SQObjectPtr,func->_ndefaultparams,nc->_defaultparams);
return nc;
}
void Release(){
SQFunctionProto *f = _function;
SQInteger size = _CALC_CLOSURE_SIZE(f);
_DESTRUCT_VECTOR(SQObjectPtr,f->_noutervalues,_outervalues);
_DESTRUCT_VECTOR(SQObjectPtr,f->_ndefaultparams,_defaultparams);
__ObjRelease(_function);
this->~SQClosure();
sq_vm_free(this,size);
}
void SetRoot(SQWeakRef *r)
{
__ObjRelease(_root);
_root = r;
__ObjAddRef(_root);
}
SQClosure *Clone()
{
SQFunctionProto *f = _function;
SQClosure * ret = SQClosure::Create(_opt_ss(this),f,_root);
ret->_env = _env;
if(ret->_env) __ObjAddRef(ret->_env);
_COPY_VECTOR(ret->_outervalues,_outervalues,f->_noutervalues);
_COPY_VECTOR(ret->_defaultparams,_defaultparams,f->_ndefaultparams);
return ret;
}
~SQClosure();
bool Save(SQVM *v,SQUserPointer up,SQWRITEFUNC write);
static bool Load(SQVM *v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &ret);
#ifndef NO_GARBAGE_COLLECTOR
void Mark(SQCollectable **chain);
void Finalize(){
SQFunctionProto *f = _function;
_NULL_SQOBJECT_VECTOR(_outervalues,f->_noutervalues);
_NULL_SQOBJECT_VECTOR(_defaultparams,f->_ndefaultparams);
}
SQObjectType GetType() {return OT_CLOSURE;}
#endif
SQWeakRef *_env;
SQWeakRef *_root;
SQClass *_base;
SQFunctionProto *_function;
SQObjectPtr *_outervalues;
SQObjectPtr *_defaultparams;
};
//////////////////////////////////////////////
struct SQOuter : public CHAINABLE_OBJ
{
private:
SQOuter(SQSharedState *ss, SQObjectPtr *outer){_valptr = outer; _next = NULL; INIT_CHAIN(); ADD_TO_CHAIN(&_ss(this)->_gc_chain,this); }
public:
static SQOuter *Create(SQSharedState *ss, SQObjectPtr *outer)
{
SQOuter *nc = (SQOuter*)SQ_MALLOC(sizeof(SQOuter));
new (nc) SQOuter(ss, outer);
return nc;
}
~SQOuter() { REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain,this); }
void Release()
{
this->~SQOuter();
sq_vm_free(this,sizeof(SQOuter));
}
#ifndef NO_GARBAGE_COLLECTOR
void Mark(SQCollectable **chain);
void Finalize() { _value.Null(); }
SQObjectType GetType() {return OT_OUTER;}
#endif
SQObjectPtr *_valptr; /* pointer to value on stack, or _value below */
SQInteger _idx; /* idx in stack array, for relocation */
SQObjectPtr _value; /* value of outer after stack frame is closed */
SQOuter *_next; /* pointer to next outer when frame is open */
};
//////////////////////////////////////////////
struct SQGenerator : public CHAINABLE_OBJ
{
enum SQGeneratorState{eRunning,eSuspended,eDead};
private:
SQGenerator(SQSharedState *ss,SQClosure *closure){_closure=closure;_state=eRunning;_ci._generator=NULL;INIT_CHAIN();ADD_TO_CHAIN(&_ss(this)->_gc_chain,this);}
public:
static SQGenerator *Create(SQSharedState *ss,SQClosure *closure){
SQGenerator *nc=(SQGenerator*)SQ_MALLOC(sizeof(SQGenerator));
new (nc) SQGenerator(ss,closure);
return nc;
}
~SQGenerator()
{
REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain,this);
}
void Kill(){
_state=eDead;
_stack.resize(0);
_closure.Null();}
void Release(){
sq_delete(this,SQGenerator);
}
bool Yield(SQVM *v,SQInteger target);
bool Resume(SQVM *v,SQObjectPtr &dest);
#ifndef NO_GARBAGE_COLLECTOR
void Mark(SQCollectable **chain);
void Finalize(){_stack.resize(0);_closure.Null();}
SQObjectType GetType() {return OT_GENERATOR;}
#endif
SQObjectPtr _closure;
SQObjectPtrVec _stack;
SQVM::CallInfo _ci;
ExceptionsTraps _etraps;
SQGeneratorState _state;
};
#define _CALC_NATVIVECLOSURE_SIZE(noutervalues) (sizeof(SQNativeClosure) + (noutervalues*sizeof(SQObjectPtr)))
struct SQNativeClosure : public CHAINABLE_OBJ
{
private:
SQNativeClosure(SQSharedState *ss,SQFUNCTION func){_function=func;INIT_CHAIN();ADD_TO_CHAIN(&_ss(this)->_gc_chain,this); _env = NULL;}
public:
static SQNativeClosure *Create(SQSharedState *ss,SQFUNCTION func,SQInteger nouters)
{
SQInteger size = _CALC_NATVIVECLOSURE_SIZE(nouters);
SQNativeClosure *nc=(SQNativeClosure*)SQ_MALLOC(size);
new (nc) SQNativeClosure(ss,func);
nc->_outervalues = (SQObjectPtr *)(nc + 1);
nc->_noutervalues = nouters;
_CONSTRUCT_VECTOR(SQObjectPtr,nc->_noutervalues,nc->_outervalues);
return nc;
}
SQNativeClosure *Clone()
{
SQNativeClosure * ret = SQNativeClosure::Create(_opt_ss(this),_function,_noutervalues);
ret->_env = _env;
if(ret->_env) __ObjAddRef(ret->_env);
ret->_name = _name;
_COPY_VECTOR(ret->_outervalues,_outervalues,_noutervalues);
ret->_typecheck.copy(_typecheck);
ret->_nparamscheck = _nparamscheck;
return ret;
}
~SQNativeClosure()
{
__ObjRelease(_env);
REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain,this);
}
void Release(){
SQInteger size = _CALC_NATVIVECLOSURE_SIZE(_noutervalues);
_DESTRUCT_VECTOR(SQObjectPtr,_noutervalues,_outervalues);
this->~SQNativeClosure();
sq_free(this,size);
}
#ifndef NO_GARBAGE_COLLECTOR
void Mark(SQCollectable **chain);
void Finalize() { _NULL_SQOBJECT_VECTOR(_outervalues,_noutervalues); }
SQObjectType GetType() {return OT_NATIVECLOSURE;}
#endif
SQInteger _nparamscheck;
SQIntVec _typecheck;
SQObjectPtr *_outervalues;
SQUnsignedInteger _noutervalues;
SQWeakRef *_env;
SQFUNCTION _function;
SQObjectPtr _name;
};
#endif //_SQCLOSURE_H_

File diff suppressed because it is too large Load Diff

View File

@ -1,79 +0,0 @@
/* see copyright notice in squirrel.h */
#ifndef _SQCOMPILER_H_
#define _SQCOMPILER_H_
struct SQVM;
#define TK_IDENTIFIER 258
#define TK_STRING_LITERAL 259
#define TK_INTEGER 260
#define TK_FLOAT 261
#define TK_BASE 262
#define TK_DELETE 263
#define TK_EQ 264
#define TK_NE 265
#define TK_LE 266
#define TK_GE 267
#define TK_SWITCH 268
#define TK_ARROW 269
#define TK_AND 270
#define TK_OR 271
#define TK_IF 272
#define TK_ELSE 273
#define TK_WHILE 274
#define TK_BREAK 275
#define TK_FOR 276
#define TK_DO 277
#define TK_NULL 278
#define TK_FOREACH 279
#define TK_IN 280
#define TK_NEWSLOT 281
#define TK_MODULO 282
#define TK_LOCAL 283
#define TK_CLONE 284
#define TK_FUNCTION 285
#define TK_RETURN 286
#define TK_TYPEOF 287
#define TK_UMINUS 288
#define TK_PLUSEQ 289
#define TK_MINUSEQ 290
#define TK_CONTINUE 291
#define TK_YIELD 292
#define TK_TRY 293
#define TK_CATCH 294
#define TK_THROW 295
#define TK_SHIFTL 296
#define TK_SHIFTR 297
#define TK_RESUME 298
#define TK_DOUBLE_COLON 299
#define TK_CASE 300
#define TK_DEFAULT 301
#define TK_THIS 302
#define TK_PLUSPLUS 303
#define TK_MINUSMINUS 304
#define TK_3WAYSCMP 305
#define TK_USHIFTR 306
#define TK_CLASS 307
#define TK_EXTENDS 308
#define TK_CONSTRUCTOR 310
#define TK_INSTANCEOF 311
#define TK_VARPARAMS 312
#define TK___LINE__ 313
#define TK___FILE__ 314
#define TK_TRUE 315
#define TK_FALSE 316
#define TK_MULEQ 317
#define TK_DIVEQ 318
#define TK_MODEQ 319
#define TK_ATTR_OPEN 320
#define TK_ATTR_CLOSE 321
#define TK_STATIC 322
#define TK_ENUM 323
#define TK_CONST 324
#define TK_RAWCALL 325
typedef void(*CompilerErrorFunc)(void *ud, const SQChar *s);
bool Compile(SQVM *vm, SQLEXREADFUNC rg, SQUserPointer up, const SQChar *sourcename, SQObjectPtr &out, bool raiseerror, bool lineinfo);
#endif //_SQCOMPILER_H_

Some files were not shown because too many files have changed in this diff Show More