Compare commits
10 Commits
23647b6458
...
db35d8762c
| Author | SHA1 | Date |
|---|---|---|
|
|
db35d8762c | |
|
|
39a0ab7124 | |
|
|
6975f69d64 | |
|
|
52d82763e1 | |
|
|
c6a5557d89 | |
|
|
1f10ce999c | |
|
|
2751b27d90 | |
|
|
d0314447ee | |
|
|
8d086a97e8 | |
|
|
b4036cd8dd |
|
|
@ -14,7 +14,7 @@
|
||||||
/x64/
|
/x64/
|
||||||
/x86/
|
/x86/
|
||||||
/.build/
|
/.build/
|
||||||
|
/.trae/
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
# xmake 构建系统
|
# xmake 构建系统
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -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` 中添加平台配置
|
|
||||||
|
|
@ -1,228 +1,39 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// String 类 - 跨平台字符串,内部统一使用 UTF-8 存储
|
// 字符串编码转换工具函数
|
||||||
|
// 统一使用 std::string (UTF-8) 作为项目标准字符串类型
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
class String {
|
|
||||||
public:
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 构造函数
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
String() = default;
|
|
||||||
|
|
||||||
String(const char* utf8) : data_(utf8 ? utf8 : "") {}
|
// UTF-8 ↔ UTF-16 转换
|
||||||
String(const std::string& utf8) : data_(utf8) {}
|
std::u16string utf8ToUtf16(const std::string& utf8);
|
||||||
explicit String(const wchar_t* wide);
|
std::string utf16ToUtf8(const std::u16string& utf16);
|
||||||
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-32 转换
|
||||||
// 静态工厂方法
|
std::u32string utf8ToUtf32(const std::string& utf8);
|
||||||
// ------------------------------------------------------------------------
|
std::string utf32ToUtf8(const std::u32string& utf32);
|
||||||
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);
|
|
||||||
|
|
||||||
/// 从 GBK/GB2312 编码构造(Windows 中文系统常用)
|
// UTF-8 ↔ Wide String 转换
|
||||||
static String fromGBK(const char* gbk);
|
std::wstring utf8ToWide(const std::string& utf8);
|
||||||
static String fromGBK(const std::string& gbk);
|
std::string wideToUtf8(const std::wstring& wide);
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// UTF-8 ↔ GBK/GB2312 转换(Windows 中文系统常用)
|
||||||
// 编码转换
|
std::string utf8ToGbk(const std::string& utf8);
|
||||||
// ------------------------------------------------------------------------
|
std::string gbkToUtf8(const std::string& gbk);
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 内联实现
|
// 内联实现
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
inline String::String(const wchar_t* wide) {
|
inline std::u16string utf8ToUtf16(const std::string& utf8) {
|
||||||
if (wide) {
|
if (utf8.empty()) return std::u16string();
|
||||||
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();
|
|
||||||
|
|
||||||
// UTF-8 → UTF-32 → UTF-16 (with surrogate pairs)
|
// UTF-8 → UTF-32 → UTF-16 (with surrogate pairs)
|
||||||
std::u32string u32 = toUtf32();
|
std::u32string u32 = utf8ToUtf32(utf8);
|
||||||
std::u16string result;
|
std::u16string result;
|
||||||
result.reserve(u32.size());
|
result.reserve(u32.size());
|
||||||
|
|
||||||
|
|
@ -240,8 +51,8 @@ inline std::u16string String::toUtf16() const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline String String::fromUtf16(const std::u16string& utf16) {
|
inline std::string utf16ToUtf8(const std::u16string& utf16) {
|
||||||
if (utf16.empty()) return String();
|
if (utf16.empty()) return std::string();
|
||||||
|
|
||||||
// UTF-16 → UTF-32 → UTF-8
|
// UTF-16 → UTF-32 → UTF-8
|
||||||
std::u32string u32;
|
std::u32string u32;
|
||||||
|
|
@ -266,19 +77,15 @@ inline String String::fromUtf16(const std::u16string& utf16) {
|
||||||
u32.push_back(ch);
|
u32.push_back(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
return fromUtf32(u32);
|
return utf32ToUtf8(u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline String String::fromUtf16(const char16_t* utf16) {
|
inline std::u32string utf8ToUtf32(const std::string& utf8) {
|
||||||
return utf16 ? fromUtf16(std::u16string(utf16)) : String();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::u32string String::toUtf32() const {
|
|
||||||
std::u32string result;
|
std::u32string result;
|
||||||
result.reserve(length());
|
result.reserve(utf8.size());
|
||||||
|
|
||||||
const char* ptr = data_.c_str();
|
const char* ptr = utf8.c_str();
|
||||||
const char* end = ptr + data_.size();
|
const char* end = ptr + utf8.size();
|
||||||
|
|
||||||
while (ptr < end) {
|
while (ptr < end) {
|
||||||
char32_t ch = 0;
|
char32_t ch = 0;
|
||||||
|
|
@ -318,7 +125,7 @@ inline std::u32string String::toUtf32() const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline String String::fromUtf32(const std::u32string& utf32) {
|
inline std::string utf32ToUtf8(const std::u32string& utf32) {
|
||||||
std::string result;
|
std::string result;
|
||||||
|
|
||||||
for (char32_t ch : utf32) {
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline String String::replaceAll(const String& from, const String& to) const {
|
inline std::wstring utf8ToWide(const std::string& utf8) {
|
||||||
if (from.empty()) return *this;
|
if (utf8.empty()) return std::wstring();
|
||||||
|
|
||||||
std::string result = data_;
|
if constexpr (sizeof(wchar_t) == 4) {
|
||||||
size_t pos = 0;
|
// wchar_t is 32-bit (Linux/Switch): same as UTF-32
|
||||||
|
std::u32string u32 = utf8ToUtf32(utf8);
|
||||||
while ((pos = result.find(from.data_, pos)) != std::string::npos) {
|
return std::wstring(u32.begin(), u32.end());
|
||||||
result.replace(pos, from.data_.size(), to.data_);
|
} else {
|
||||||
pos += to.data_.size();
|
// 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 std::string wideToUtf8(const std::wstring& wide) {
|
||||||
inline String String::operator+(const String& other) const {
|
if (wide.empty()) return std::string();
|
||||||
String result(*this);
|
|
||||||
result.append(other);
|
if constexpr (sizeof(wchar_t) == 4) {
|
||||||
return result;
|
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) {
|
// GBK/GB2312 转换(Windows 平台实现)
|
||||||
return append(other);
|
// 注意: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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化字符串
|
inline std::string gbkToUtf8(const std::string& gbk) {
|
||||||
#include <cstdio>
|
return gbkToUtf8Impl(gbk);
|
||||||
|
}
|
||||||
template<typename... Args>
|
#else
|
||||||
String String::format(const char* fmt, Args&&... args) {
|
// 非 Windows 平台,GBK 转换使用 iconv 或返回原字符串
|
||||||
int size = std::snprintf(nullptr, 0, fmt, std::forward<Args>(args)...);
|
inline std::string utf8ToGbk(const std::string& utf8) {
|
||||||
if (size <= 0) return String();
|
// TODO: 使用 iconv 实现
|
||||||
|
return utf8;
|
||||||
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) {
|
||||||
inline String operator+(const char* lhs, const String& rhs) {
|
// TODO: 使用 iconv 实现
|
||||||
return String(lhs) + rhs;
|
return gbk;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,15 @@
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 字符串别名 - 统一使用 std::string (UTF-8)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
using String = std::string;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// 智能指针别名
|
// 智能指针别名
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@
|
||||||
#include <extra2d/scene/node.h>
|
#include <extra2d/scene/node.h>
|
||||||
#include <extra2d/scene/scene.h>
|
#include <extra2d/scene/scene.h>
|
||||||
#include <extra2d/scene/sprite.h>
|
#include <extra2d/scene/sprite.h>
|
||||||
#include <extra2d/scene/text.h>
|
|
||||||
#include <extra2d/scene/shape_node.h>
|
#include <extra2d/scene/shape_node.h>
|
||||||
#include <extra2d/scene/scene_manager.h>
|
#include <extra2d/scene/scene_manager.h>
|
||||||
#include <extra2d/scene/transition.h>
|
#include <extra2d/scene/transition.h>
|
||||||
|
|
@ -53,6 +52,12 @@
|
||||||
// UI
|
// UI
|
||||||
#include <extra2d/ui/widget.h>
|
#include <extra2d/ui/widget.h>
|
||||||
#include <extra2d/ui/button.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
|
// Action
|
||||||
#include <extra2d/action/action.h>
|
#include <extra2d/action/action.h>
|
||||||
|
|
@ -92,10 +97,6 @@
|
||||||
// Application
|
// Application
|
||||||
#include <extra2d/app/application.h>
|
#include <extra2d/app/application.h>
|
||||||
|
|
||||||
// Script
|
|
||||||
#include <extra2d/script/script_engine.h>
|
|
||||||
#include <extra2d/script/script_node.h>
|
|
||||||
|
|
||||||
#ifdef __SWITCH__
|
#ifdef __SWITCH__
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
#include <extra2d/core/color.h>
|
||||||
#include <extra2d/core/math_types.h>
|
#include <extra2d/core/math_types.h>
|
||||||
#include <extra2d/core/string.h>
|
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
#include <extra2d/core/color.h>
|
||||||
#include <extra2d/core/math_types.h>
|
#include <extra2d/core/math_types.h>
|
||||||
#include <extra2d/core/string.h>
|
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
#include <glm/mat4x4.hpp>
|
#include <glm/mat4x4.hpp>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/script/sq_binding.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
namespace sq {
|
|
||||||
|
|
||||||
void registerActionBindings(HSQUIRRELVM vm);
|
|
||||||
|
|
||||||
} // namespace sq
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/script/sq_binding.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
namespace sq {
|
|
||||||
|
|
||||||
void registerAnimationBindings(HSQUIRRELVM vm);
|
|
||||||
|
|
||||||
} // namespace sq
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/script/sq_binding.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
namespace sq {
|
|
||||||
|
|
||||||
void registerAudioBindings(HSQUIRRELVM vm);
|
|
||||||
|
|
||||||
} // namespace sq
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
#include <extra2d/graphics/font.h>
|
#include <extra2d/graphics/font.h>
|
||||||
#include <extra2d/graphics/texture.h>
|
#include <extra2d/graphics/texture.h>
|
||||||
#include <extra2d/platform/window.h>
|
#include <extra2d/platform/window.h>
|
||||||
|
|
@ -21,52 +22,118 @@ enum class ImageScaleMode {
|
||||||
class Button : public Widget {
|
class Button : public Widget {
|
||||||
public:
|
public:
|
||||||
Button();
|
Button();
|
||||||
|
explicit Button(const String &text);
|
||||||
~Button() override = default;
|
~Button() override = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 静态创建方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
static Ptr<Button> create();
|
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);
|
void setText(const String &text);
|
||||||
const String &getText() const { return text_; }
|
const String &getText() const { return text_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 字体
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
void setFont(Ptr<FontAtlas> font);
|
void setFont(Ptr<FontAtlas> font);
|
||||||
Ptr<FontAtlas> getFont() const { return font_; }
|
Ptr<FontAtlas> getFont() const { return font_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 内边距
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
void setPadding(const Vec2 &padding);
|
void setPadding(const Vec2 &padding);
|
||||||
Vec2 getPadding() const { return padding_; }
|
Vec2 getPadding() const { return padding_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 文字颜色
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
void setTextColor(const Color &color);
|
void setTextColor(const Color &color);
|
||||||
Color getTextColor() const { return textColor_; }
|
Color getTextColor() const { return textColor_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
// 纯色背景设置
|
// 纯色背景设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
void setBackgroundColor(const Color &normal, const Color &hover,
|
void setBackgroundColor(const Color &normal, const Color &hover,
|
||||||
const Color &pressed);
|
const Color &pressed);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 边框设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
void setBorder(const Color &color, float width);
|
void setBorder(const Color &color, float width);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
// 图片背景设置
|
// 图片背景设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
void setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover = nullptr,
|
void setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover = nullptr,
|
||||||
Ptr<Texture> pressed = nullptr);
|
Ptr<Texture> pressed = nullptr);
|
||||||
void setBackgroundImageScaleMode(ImageScaleMode mode);
|
void setBackgroundImageScaleMode(ImageScaleMode mode);
|
||||||
void setCustomSize(const Vec2 &size);
|
void setCustomSize(const Vec2 &size);
|
||||||
void setCustomSize(float width, float height);
|
void setCustomSize(float width, float height);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
// 圆角矩形设置
|
// 圆角矩形设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
void setCornerRadius(float radius);
|
void setCornerRadius(float radius);
|
||||||
float getCornerRadius() const { return cornerRadius_; }
|
float getCornerRadius() const { return cornerRadius_; }
|
||||||
void setRoundedCornersEnabled(bool enabled);
|
void setRoundedCornersEnabled(bool enabled);
|
||||||
bool isRoundedCornersEnabled() const { return roundedCornersEnabled_; }
|
bool isRoundedCornersEnabled() const { return roundedCornersEnabled_; }
|
||||||
|
|
||||||
// 鼠标光标设置(悬停时显示的光标形状)
|
// ------------------------------------------------------------------------
|
||||||
|
// 鼠标光标设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
void setHoverCursor(CursorShape cursor);
|
void setHoverCursor(CursorShape cursor);
|
||||||
CursorShape getHoverCursor() const { return hoverCursor_; }
|
CursorShape getHoverCursor() const { return hoverCursor_; }
|
||||||
|
|
||||||
// Alpha遮罩点击检测(用于不规则形状按钮)
|
// ------------------------------------------------------------------------
|
||||||
|
// Alpha遮罩点击检测
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
void setUseAlphaMaskForHitTest(bool enabled);
|
void setUseAlphaMaskForHitTest(bool enabled);
|
||||||
bool isUseAlphaMaskForHitTest() const { return useAlphaMaskForHitTest_; }
|
bool isUseAlphaMaskForHitTest() const { return useAlphaMaskForHitTest_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 点击回调
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
void setOnClick(Function<void()> callback);
|
void setOnClick(Function<void()> callback);
|
||||||
|
|
||||||
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void onDraw(RenderBackend &renderer) override;
|
void onDrawWidget(RenderBackend &renderer) override;
|
||||||
void drawBackgroundImage(RenderBackend &renderer, const Rect &rect);
|
void drawBackgroundImage(RenderBackend &renderer, const Rect &rect);
|
||||||
void drawRoundedRect(RenderBackend &renderer, const Rect &rect,
|
void drawRoundedRect(RenderBackend &renderer, const Rect &rect,
|
||||||
const Color &color, float radius);
|
const Color &color, float radius);
|
||||||
|
|
@ -103,15 +170,15 @@ private:
|
||||||
float borderWidth_ = 1.0f;
|
float borderWidth_ = 1.0f;
|
||||||
|
|
||||||
// 圆角矩形
|
// 圆角矩形
|
||||||
float cornerRadius_ = 8.0f; // 圆角半径
|
float cornerRadius_ = 8.0f;
|
||||||
bool roundedCornersEnabled_ = false; // 默认关闭
|
bool roundedCornersEnabled_ = false;
|
||||||
|
|
||||||
// 鼠标光标
|
// 鼠标光标
|
||||||
CursorShape hoverCursor_ = CursorShape::Hand; // 悬停时默认显示手型光标
|
CursorShape hoverCursor_ = CursorShape::Hand;
|
||||||
bool cursorChanged_ = false; // 标记是否已改变光标
|
bool cursorChanged_ = false;
|
||||||
|
|
||||||
// Alpha遮罩点击检测(用于不规则形状按钮)
|
// Alpha遮罩点击检测
|
||||||
bool useAlphaMaskForHitTest_ = false; // 默认关闭
|
bool useAlphaMaskForHitTest_ = false;
|
||||||
|
|
||||||
bool hovered_ = false;
|
bool hovered_ = false;
|
||||||
bool pressed_ = false;
|
bool pressed_ = false;
|
||||||
|
|
@ -151,7 +218,7 @@ public:
|
||||||
void setOnStateChange(Function<void(bool)> callback);
|
void setOnStateChange(Function<void(bool)> callback);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void onDraw(RenderBackend &renderer) override;
|
void onDrawWidget(RenderBackend &renderer) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// 状态图片
|
// 状态图片
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -1,9 +1,34 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/platform/input.h>
|
||||||
#include <extra2d/scene/node.h>
|
#include <extra2d/scene/node.h>
|
||||||
|
|
||||||
namespace extra2d {
|
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 {
|
class Widget : public Node {
|
||||||
public:
|
public:
|
||||||
Widget();
|
Widget();
|
||||||
|
|
@ -15,8 +40,72 @@ public:
|
||||||
|
|
||||||
Rect getBoundingBox() const override;
|
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:
|
private:
|
||||||
Size size_ = Size::Zero();
|
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
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,218 +1,48 @@
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <extra2d/core/string.h>
|
#include <extra2d/core/string.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
// ============================================================================
|
std::string utf8ToGbkImpl(const std::string& utf8) {
|
||||||
// GBK/GB2312 到 UTF-8 转换表
|
if (utf8.empty()) return std::string();
|
||||||
// 使用简化的转换表,覆盖常用中文字符
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// GBK 编码范围:
|
// UTF-8 → Wide → GBK
|
||||||
// - 单字节:0x00-0x7F (ASCII)
|
int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, nullptr, 0);
|
||||||
// - 双字节:0x81-0xFE 0x40-0xFE
|
if (wideLen <= 0) return std::string();
|
||||||
|
|
||||||
// 将 GBK 双字节解码为 Unicode 码点
|
std::wstring wide(wideLen - 1, 0);
|
||||||
// 这是简化的实现,使用 GBK 到 Unicode 的映射公式
|
MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, &wide[0], wideLen);
|
||||||
static uint32_t gbkToUnicode(uint16_t gbkCode) {
|
|
||||||
// GBK 编码转 Unicode 的简化算法
|
|
||||||
// 对于常见汉字,使用近似转换
|
|
||||||
|
|
||||||
uint8_t high = (gbkCode >> 8) & 0xFF;
|
int gbkLen = WideCharToMultiByte(CP_ACP, 0, wide.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
||||||
uint8_t low = gbkCode & 0xFF;
|
if (gbkLen <= 0) return std::string();
|
||||||
|
|
||||||
// ASCII 范围
|
std::string gbk(gbkLen - 1, 0);
|
||||||
if (high < 0x80) {
|
WideCharToMultiByte(CP_ACP, 0, wide.c_str(), -1, &gbk[0], gbkLen, nullptr, nullptr);
|
||||||
return high;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GBK 双字节范围
|
return 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将 Unicode 码点编码为 UTF-8
|
std::string gbkToUtf8Impl(const std::string& gbk) {
|
||||||
static std::string unicodeToUtf8(uint32_t codepoint) {
|
if (gbk.empty()) return std::string();
|
||||||
std::string result;
|
|
||||||
|
|
||||||
if (codepoint <= 0x7F) {
|
// GBK → Wide → UTF-8
|
||||||
// 1-byte
|
int wideLen = MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), -1, nullptr, 0);
|
||||||
result.push_back(static_cast<char>(codepoint));
|
if (wideLen <= 0) return std::string();
|
||||||
} 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)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
std::wstring wide(wideLen - 1, 0);
|
||||||
}
|
MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), -1, &wide[0], wideLen);
|
||||||
|
|
||||||
// 将 UTF-8 解码为 Unicode 码点
|
int utf8Len = WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
||||||
// 返回解码后的码点和消耗的 UTF-8 字节数
|
if (utf8Len <= 0) return std::string();
|
||||||
static std::pair<uint32_t, size_t> utf8ToUnicode(const char *utf8) {
|
|
||||||
unsigned char byte = static_cast<unsigned char>(*utf8);
|
|
||||||
|
|
||||||
if ((byte & 0x80) == 0) {
|
std::string utf8(utf8Len - 1, 0);
|
||||||
// 1-byte
|
WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, &utf8[0], utf8Len, nullptr, nullptr);
|
||||||
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};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalid UTF-8
|
return utf8;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
||||||
|
#endif // _WIN32
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
||||||
|
#include <extra2d/core/string.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <fstream>
|
||||||
#define STB_TRUETYPE_IMPLEMENTATION
|
#define STB_TRUETYPE_IMPLEMENTATION
|
||||||
#include <stb/stb_truetype.h>
|
#include <stb/stb_truetype.h>
|
||||||
#define STB_RECT_PACK_IMPLEMENTATION
|
#define STB_RECT_PACK_IMPLEMENTATION
|
||||||
#include <algorithm>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
#include <fstream>
|
|
||||||
#include <stb/stb_rect_pack.h>
|
#include <stb/stb_rect_pack.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -74,7 +75,7 @@ Vec2 GLFontAtlas::measureText(const String &text) {
|
||||||
float height = getAscent() - getDescent();
|
float height = getAscent() - getDescent();
|
||||||
float currentWidth = 0.0f;
|
float currentWidth = 0.0f;
|
||||||
|
|
||||||
for (char32_t codepoint : text.toUtf32()) {
|
for (char32_t codepoint : utf8ToUtf32(text)) {
|
||||||
if (codepoint == '\n') {
|
if (codepoint == '\n') {
|
||||||
width = std::max(width, currentWidth);
|
width = std::max(width, currentWidth);
|
||||||
currentWidth = 0.0f;
|
currentWidth = 0.0f;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <extra2d/core/string.h>
|
||||||
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
#include <extra2d/graphics/opengl/gl_renderer.h>
|
||||||
#include <extra2d/graphics/opengl/gl_texture.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 cursorY = y;
|
||||||
float baselineY = cursorY + font.getAscent();
|
float baselineY = cursorY + font.getAscent();
|
||||||
|
|
||||||
for (char32_t codepoint : text.toUtf32()) {
|
for (char32_t codepoint : utf8ToUtf32(text)) {
|
||||||
if (codepoint == '\n') {
|
if (codepoint == '\n') {
|
||||||
cursorX = x;
|
cursorX = x;
|
||||||
cursorY += font.getLineHeight();
|
cursorY += font.getLineHeight();
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -3,18 +3,22 @@
|
||||||
#include <extra2d/app/application.h>
|
#include <extra2d/app/application.h>
|
||||||
#include <extra2d/graphics/render_backend.h>
|
#include <extra2d/graphics/render_backend.h>
|
||||||
#include <extra2d/ui/button.h>
|
#include <extra2d/ui/button.h>
|
||||||
|
#include <extra2d/core/string.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
/**
|
// ============================================================================
|
||||||
* @brief 构造函数,初始化按钮状态并设置事件监听器
|
// Button 实现
|
||||||
*/
|
// ============================================================================
|
||||||
|
|
||||||
Button::Button() {
|
Button::Button() {
|
||||||
|
// 按钮默认锚点为左上角,这样setPosition(0, 0)会在左上角显示
|
||||||
|
setAnchor(0.0f, 0.0f);
|
||||||
setSpatialIndexed(false);
|
setSpatialIndexed(false);
|
||||||
|
|
||||||
auto &dispatcher = getEventDispatcher();
|
auto &dispatcher = getEventDispatcher();
|
||||||
dispatcher.addListener(EventType::UIHoverEnter, [this](Event &) {
|
dispatcher.addListener(EventType::UIHoverEnter, [this](Event &) {
|
||||||
hovered_ = true;
|
hovered_ = true;
|
||||||
// 鼠标进入按钮区域,改变光标为手型
|
|
||||||
auto &app = Application::instance();
|
auto &app = Application::instance();
|
||||||
app.window().setCursor(hoverCursor_);
|
app.window().setCursor(hoverCursor_);
|
||||||
cursorChanged_ = true;
|
cursorChanged_ = true;
|
||||||
|
|
@ -22,7 +26,6 @@ Button::Button() {
|
||||||
dispatcher.addListener(EventType::UIHoverExit, [this](Event &) {
|
dispatcher.addListener(EventType::UIHoverExit, [this](Event &) {
|
||||||
hovered_ = false;
|
hovered_ = false;
|
||||||
pressed_ = false;
|
pressed_ = false;
|
||||||
// 鼠标离开按钮区域,恢复默认光标
|
|
||||||
if (cursorChanged_) {
|
if (cursorChanged_) {
|
||||||
auto &app = Application::instance();
|
auto &app = Application::instance();
|
||||||
app.window().resetCursor();
|
app.window().resetCursor();
|
||||||
|
|
@ -39,16 +42,137 @@ Button::Button() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
Button::Button(const String &text) : Button() {
|
||||||
* @brief 创建按钮实例的工厂方法
|
text_ = text;
|
||||||
* @return 按钮智能指针
|
}
|
||||||
*/
|
|
||||||
Ptr<Button> Button::create() { return makePtr<Button>(); }
|
|
||||||
|
|
||||||
/**
|
// ------------------------------------------------------------------------
|
||||||
* @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) {
|
void Button::setText(const String &text) {
|
||||||
text_ = text;
|
text_ = text;
|
||||||
if (font_ && getSize().empty()) {
|
if (font_ && getSize().empty()) {
|
||||||
|
|
@ -57,10 +181,6 @@ void Button::setText(const String &text) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置字体,并根据当前文字自动调整按钮尺寸
|
|
||||||
* @param font 字体图集
|
|
||||||
*/
|
|
||||||
void Button::setFont(Ptr<FontAtlas> font) {
|
void Button::setFont(Ptr<FontAtlas> font) {
|
||||||
font_ = font;
|
font_ = font;
|
||||||
if (font_ && getSize().empty() && !text_.empty()) {
|
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) {
|
void Button::setPadding(const Vec2 &padding) {
|
||||||
padding_ = padding;
|
padding_ = padding;
|
||||||
if (font_ && getSize().empty() && !text_.empty()) {
|
if (font_ && getSize().empty() && !text_.empty()) {
|
||||||
|
|
@ -81,18 +197,10 @@ void Button::setPadding(const Vec2 &padding) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void Button::setTextColor(const Color &color) {
|
||||||
* @brief 设置文字颜色
|
textColor_ = color;
|
||||||
* @param color 颜色值
|
}
|
||||||
*/
|
|
||||||
void Button::setTextColor(const Color &color) { textColor_ = color; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置纯色背景的颜色状态
|
|
||||||
* @param normal 正常状态颜色
|
|
||||||
* @param hover 悬停状态颜色
|
|
||||||
* @param pressed 按下状态颜色
|
|
||||||
*/
|
|
||||||
void Button::setBackgroundColor(const Color &normal, const Color &hover,
|
void Button::setBackgroundColor(const Color &normal, const Color &hover,
|
||||||
const Color &pressed) {
|
const Color &pressed) {
|
||||||
bgNormal_ = normal;
|
bgNormal_ = normal;
|
||||||
|
|
@ -100,60 +208,31 @@ void Button::setBackgroundColor(const Color &normal, const Color &hover,
|
||||||
bgPressed_ = pressed;
|
bgPressed_ = pressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置边框样式
|
|
||||||
* @param color 边框颜色
|
|
||||||
* @param width 边框宽度
|
|
||||||
*/
|
|
||||||
void Button::setBorder(const Color &color, float width) {
|
void Button::setBorder(const Color &color, float width) {
|
||||||
borderColor_ = color;
|
borderColor_ = color;
|
||||||
borderWidth_ = width;
|
borderWidth_ = width;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置圆角半径
|
|
||||||
* @param radius 圆角半径(像素)
|
|
||||||
*/
|
|
||||||
void Button::setCornerRadius(float radius) {
|
void Button::setCornerRadius(float radius) {
|
||||||
cornerRadius_ = std::max(0.0f, radius);
|
cornerRadius_ = std::max(0.0f, radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否启用圆角矩形
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void Button::setRoundedCornersEnabled(bool enabled) {
|
void Button::setRoundedCornersEnabled(bool enabled) {
|
||||||
roundedCornersEnabled_ = enabled;
|
roundedCornersEnabled_ = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否使用Alpha遮罩进行点击检测(用于不规则形状按钮)
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void Button::setUseAlphaMaskForHitTest(bool enabled) {
|
void Button::setUseAlphaMaskForHitTest(bool enabled) {
|
||||||
useAlphaMaskForHitTest_ = enabled;
|
useAlphaMaskForHitTest_ = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置点击回调函数
|
|
||||||
* @param callback 回调函数
|
|
||||||
*/
|
|
||||||
void Button::setOnClick(Function<void()> callback) {
|
void Button::setOnClick(Function<void()> callback) {
|
||||||
onClick_ = std::move(callback);
|
onClick_ = std::move(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void Button::setHoverCursor(CursorShape cursor) {
|
||||||
* @brief 设置悬停时的鼠标光标形状
|
hoverCursor_ = cursor;
|
||||||
* @param cursor 光标形状枚举
|
}
|
||||||
*/
|
|
||||||
void Button::setHoverCursor(CursorShape cursor) { hoverCursor_ = cursor; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置背景图片,支持三种状态
|
|
||||||
* @param normal 正常状态图片
|
|
||||||
* @param hover 悬停状态图片(可选)
|
|
||||||
* @param pressed 按下状态图片(可选)
|
|
||||||
*/
|
|
||||||
void Button::setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover,
|
void Button::setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover,
|
||||||
Ptr<Texture> pressed) {
|
Ptr<Texture> pressed) {
|
||||||
imgNormal_ = normal;
|
imgNormal_ = normal;
|
||||||
|
|
@ -161,20 +240,14 @@ void Button::setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover,
|
||||||
imgPressed_ = pressed ? pressed : (hover ? hover : normal);
|
imgPressed_ = pressed ? pressed : (hover ? hover : normal);
|
||||||
useImageBackground_ = (normal != nullptr);
|
useImageBackground_ = (normal != nullptr);
|
||||||
|
|
||||||
// 如果使用原图大小模式,设置按钮大小为图片大小
|
|
||||||
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original && normal) {
|
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original && normal) {
|
||||||
setSize(static_cast<float>(normal->getWidth()),
|
setSize(static_cast<float>(normal->getWidth()),
|
||||||
static_cast<float>(normal->getHeight()));
|
static_cast<float>(normal->getHeight()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置图片缩放模式
|
|
||||||
* @param mode 缩放模式枚举值
|
|
||||||
*/
|
|
||||||
void Button::setBackgroundImageScaleMode(ImageScaleMode mode) {
|
void Button::setBackgroundImageScaleMode(ImageScaleMode mode) {
|
||||||
scaleMode_ = mode;
|
scaleMode_ = mode;
|
||||||
// 如果切换到原图大小模式,更新按钮大小
|
|
||||||
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original &&
|
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original &&
|
||||||
imgNormal_) {
|
imgNormal_) {
|
||||||
setSize(static_cast<float>(imgNormal_->getWidth()),
|
setSize(static_cast<float>(imgNormal_->getWidth()),
|
||||||
|
|
@ -182,27 +255,32 @@ void Button::setBackgroundImageScaleMode(ImageScaleMode mode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void Button::setCustomSize(const Vec2 &size) {
|
||||||
* @brief 设置自定义尺寸
|
setSize(size.x, size.y);
|
||||||
* @param size 尺寸向量
|
}
|
||||||
*/
|
|
||||||
void Button::setCustomSize(const Vec2 &size) { setSize(size.x, size.y); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置自定义尺寸
|
|
||||||
* @param width 宽度
|
|
||||||
* @param height 高度
|
|
||||||
*/
|
|
||||||
void Button::setCustomSize(float width, float height) {
|
void Button::setCustomSize(float width, float height) {
|
||||||
setSize(width, height);
|
setSize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
Rect Button::getBoundingBox() const {
|
||||||
* @brief 根据缩放模式计算图片绘制尺寸
|
auto pos = getRenderPosition();
|
||||||
* @param buttonSize 按钮尺寸
|
auto anchor = getAnchor();
|
||||||
* @param imageSize 图片原始尺寸
|
auto scale = getScale();
|
||||||
* @return 计算后的绘制尺寸
|
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) {
|
Vec2 Button::calculateImageSize(const Vec2 &buttonSize, const Vec2 &imageSize) {
|
||||||
switch (scaleMode_) {
|
switch (scaleMode_) {
|
||||||
case ImageScaleMode::Original:
|
case ImageScaleMode::Original:
|
||||||
|
|
@ -228,11 +306,6 @@ Vec2 Button::calculateImageSize(const Vec2 &buttonSize, const Vec2 &imageSize) {
|
||||||
return imageSize;
|
return imageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制背景图片,根据当前状态选择对应的图片
|
|
||||||
* @param renderer 渲染后端
|
|
||||||
* @param rect 按钮矩形区域
|
|
||||||
*/
|
|
||||||
void Button::drawBackgroundImage(RenderBackend &renderer, const Rect &rect) {
|
void Button::drawBackgroundImage(RenderBackend &renderer, const Rect &rect) {
|
||||||
Texture *texture = nullptr;
|
Texture *texture = nullptr;
|
||||||
if (pressed_ && imgPressed_) {
|
if (pressed_ && imgPressed_) {
|
||||||
|
|
@ -251,57 +324,37 @@ void Button::drawBackgroundImage(RenderBackend &renderer, const Rect &rect) {
|
||||||
Vec2 buttonSize(rect.size.width, rect.size.height);
|
Vec2 buttonSize(rect.size.width, rect.size.height);
|
||||||
Vec2 drawSize = calculateImageSize(buttonSize, imageSize);
|
Vec2 drawSize = calculateImageSize(buttonSize, imageSize);
|
||||||
|
|
||||||
// 计算绘制位置(居中)
|
|
||||||
Vec2 drawPos(rect.origin.x + (rect.size.width - drawSize.x) * 0.5f,
|
Vec2 drawPos(rect.origin.x + (rect.size.width - drawSize.x) * 0.5f,
|
||||||
rect.origin.y + (rect.size.height - drawSize.y) * 0.5f);
|
rect.origin.y + (rect.size.height - drawSize.y) * 0.5f);
|
||||||
|
|
||||||
Rect destRect(drawPos.x, drawPos.y, drawSize.x, drawSize.y);
|
Rect destRect(drawPos.x, drawPos.y, drawSize.x, drawSize.y);
|
||||||
|
|
||||||
// 绘制图片(使用Alpha混合)
|
|
||||||
renderer.drawSprite(*texture, destRect, Rect(0, 0, imageSize.x, imageSize.y),
|
renderer.drawSprite(*texture, destRect, Rect(0, 0, imageSize.x, imageSize.y),
|
||||||
Colors::White, 0.0f, Vec2::Zero());
|
Colors::White, 0.0f, Vec2::Zero());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制圆角矩形边框
|
|
||||||
* @param renderer 渲染后端
|
|
||||||
* @param rect 矩形区域
|
|
||||||
* @param color 颜色
|
|
||||||
* @param radius 圆角半径
|
|
||||||
*/
|
|
||||||
void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
|
void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
|
||||||
const Color &color, float radius) {
|
const Color &color, float radius) {
|
||||||
// 限制圆角半径不超过矩形尺寸的一半
|
|
||||||
float maxRadius = std::min(rect.size.width, rect.size.height) * 0.5f;
|
float maxRadius = std::min(rect.size.width, rect.size.height) * 0.5f;
|
||||||
radius = std::min(radius, maxRadius);
|
radius = std::min(radius, maxRadius);
|
||||||
|
|
||||||
if (radius <= 0.0f) {
|
if (radius <= 0.0f) {
|
||||||
// 圆角为0,使用普通矩形
|
|
||||||
renderer.drawRect(rect, color, borderWidth_);
|
renderer.drawRect(rect, color, borderWidth_);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int segments = 8; // 每个圆角的线段数
|
const int segments = 8;
|
||||||
float x = rect.origin.x;
|
float x = rect.origin.x;
|
||||||
float y = rect.origin.y;
|
float y = rect.origin.y;
|
||||||
float w = rect.size.width;
|
float w = rect.size.width;
|
||||||
float h = rect.size.height;
|
float h = rect.size.height;
|
||||||
float r = radius;
|
float r = radius;
|
||||||
|
|
||||||
// 绘制四条直线边
|
|
||||||
// 上边
|
|
||||||
renderer.drawLine(Vec2(x + r, y), Vec2(x + w - r, y), color, borderWidth_);
|
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, 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++) {
|
for (int i = 0; i < segments; i++) {
|
||||||
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f;
|
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f;
|
||||||
float angle2 = 3.14159f * 0.5f * (float)(i + 1) / 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));
|
Vec2 p2(x + r + r * cosf(angle2), y + r + r * sinf(angle2));
|
||||||
renderer.drawLine(p1, p2, color, borderWidth_);
|
renderer.drawLine(p1, p2, color, borderWidth_);
|
||||||
}
|
}
|
||||||
// 右上角
|
|
||||||
for (int i = 0; i < segments; i++) {
|
for (int i = 0; i < segments; i++) {
|
||||||
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f * 1.5f;
|
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f * 1.5f;
|
||||||
float angle2 =
|
float angle2 = 3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 1.5f;
|
||||||
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 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));
|
Vec2 p2(x + w - r + r * cosf(angle2), y + r + r * sinf(angle2));
|
||||||
renderer.drawLine(p1, p2, color, borderWidth_);
|
renderer.drawLine(p1, p2, color, borderWidth_);
|
||||||
}
|
}
|
||||||
// 右下角
|
|
||||||
for (int i = 0; i < segments; i++) {
|
for (int i = 0; i < segments; i++) {
|
||||||
float angle1 = 3.14159f * 0.5f * (float)i / segments;
|
float angle1 = 3.14159f * 0.5f * (float)i / segments;
|
||||||
float angle2 = 3.14159f * 0.5f * (float)(i + 1) / 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));
|
Vec2 p2(x + w - r + r * cosf(angle2), y + h - r + r * sinf(angle2));
|
||||||
renderer.drawLine(p1, p2, color, borderWidth_);
|
renderer.drawLine(p1, p2, color, borderWidth_);
|
||||||
}
|
}
|
||||||
// 左下角
|
|
||||||
for (int i = 0; i < segments; i++) {
|
for (int i = 0; i < segments; i++) {
|
||||||
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f * 0.5f;
|
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f * 0.5f;
|
||||||
float angle2 =
|
float angle2 = 3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 0.5f;
|
||||||
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 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));
|
Vec2 p2(x + r + r * cosf(angle2), y + h - r + r * sinf(angle2));
|
||||||
renderer.drawLine(p1, p2, color, borderWidth_);
|
renderer.drawLine(p1, p2, color, borderWidth_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 填充圆角矩形
|
|
||||||
* @param renderer 渲染后端
|
|
||||||
* @param rect 矩形区域
|
|
||||||
* @param color 颜色
|
|
||||||
* @param radius 圆角半径
|
|
||||||
*/
|
|
||||||
void Button::fillRoundedRect(RenderBackend &renderer, const Rect &rect,
|
void Button::fillRoundedRect(RenderBackend &renderer, const Rect &rect,
|
||||||
const Color &color, float radius) {
|
const Color &color, float radius) {
|
||||||
// 限制圆角半径不超过矩形尺寸的一半
|
|
||||||
float maxRadius = std::min(rect.size.width, rect.size.height) * 0.5f;
|
float maxRadius = std::min(rect.size.width, rect.size.height) * 0.5f;
|
||||||
radius = std::min(radius, maxRadius);
|
radius = std::min(radius, maxRadius);
|
||||||
|
|
||||||
if (radius <= 0.0f) {
|
if (radius <= 0.0f) {
|
||||||
// 圆角为0,使用普通矩形填充
|
|
||||||
renderer.fillRect(rect, color);
|
renderer.fillRect(rect, color);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int segments = 8; // 每个圆角的线段数
|
const int segments = 8;
|
||||||
float x = rect.origin.x;
|
float x = rect.origin.x;
|
||||||
float y = rect.origin.y;
|
float y = rect.origin.y;
|
||||||
float w = rect.size.width;
|
float w = rect.size.width;
|
||||||
float h = rect.size.height;
|
float h = rect.size.height;
|
||||||
float r = radius;
|
float r = radius;
|
||||||
|
|
||||||
// 构建圆角矩形的顶点(使用三角形扇形填充)
|
|
||||||
std::vector<Vec2> vertices;
|
std::vector<Vec2> vertices;
|
||||||
|
|
||||||
// 中心矩形区域
|
vertices.push_back(Vec2(x + r, y + 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 + r)); // 右上内角
|
vertices.push_back(Vec2(x + w - r, y + h - 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 + h - r)); // 左下内角
|
|
||||||
|
|
||||||
renderer.fillPolygon(vertices, color);
|
renderer.fillPolygon(vertices, color);
|
||||||
|
|
||||||
// 填充四个侧边矩形
|
|
||||||
// 上边
|
|
||||||
renderer.fillRect(Rect(x + r, y, w - 2 * r, r), 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 + r, y + h - r, w - 2 * r, r), color);
|
||||||
// 左边
|
|
||||||
renderer.fillRect(Rect(x, y + r, r, h - 2 * 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);
|
renderer.fillRect(Rect(x + w - r, y + r, r, h - 2 * r), color);
|
||||||
|
|
||||||
// 填充四个圆角(使用扇形)
|
|
||||||
// 左上角
|
|
||||||
vertices.clear();
|
vertices.clear();
|
||||||
vertices.push_back(Vec2(x + r, y + r));
|
vertices.push_back(Vec2(x + r, y + r));
|
||||||
for (int i = 0; i <= segments; i++) {
|
for (int i = 0; i <= segments; i++) {
|
||||||
|
|
@ -394,7 +424,6 @@ void Button::fillRoundedRect(RenderBackend &renderer, const Rect &rect,
|
||||||
}
|
}
|
||||||
renderer.fillPolygon(vertices, color);
|
renderer.fillPolygon(vertices, color);
|
||||||
|
|
||||||
// 右上角
|
|
||||||
vertices.clear();
|
vertices.clear();
|
||||||
vertices.push_back(Vec2(x + w - r, y + r));
|
vertices.push_back(Vec2(x + w - r, y + r));
|
||||||
for (int i = 0; i <= segments; i++) {
|
for (int i = 0; i <= segments; i++) {
|
||||||
|
|
@ -404,7 +433,6 @@ void Button::fillRoundedRect(RenderBackend &renderer, const Rect &rect,
|
||||||
}
|
}
|
||||||
renderer.fillPolygon(vertices, color);
|
renderer.fillPolygon(vertices, color);
|
||||||
|
|
||||||
// 右下角
|
|
||||||
vertices.clear();
|
vertices.clear();
|
||||||
vertices.push_back(Vec2(x + w - r, y + h - r));
|
vertices.push_back(Vec2(x + w - r, y + h - r));
|
||||||
for (int i = 0; i <= segments; i++) {
|
for (int i = 0; i <= segments; i++) {
|
||||||
|
|
@ -414,7 +442,6 @@ void Button::fillRoundedRect(RenderBackend &renderer, const Rect &rect,
|
||||||
}
|
}
|
||||||
renderer.fillPolygon(vertices, color);
|
renderer.fillPolygon(vertices, color);
|
||||||
|
|
||||||
// 左下角
|
|
||||||
vertices.clear();
|
vertices.clear();
|
||||||
vertices.push_back(Vec2(x + r, y + h - r));
|
vertices.push_back(Vec2(x + r, y + h - r));
|
||||||
for (int i = 0; i <= segments; i++) {
|
for (int i = 0; i <= segments; i++) {
|
||||||
|
|
@ -425,30 +452,15 @@ void Button::fillRoundedRect(RenderBackend &renderer, const Rect &rect,
|
||||||
renderer.fillPolygon(vertices, color);
|
renderer.fillPolygon(vertices, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void Button::onDrawWidget(RenderBackend &renderer) {
|
||||||
* @brief 按钮绘制主函数,按正确顺序渲染所有视觉元素
|
|
||||||
*
|
|
||||||
* 渲染顺序:
|
|
||||||
* 1. 背景(图片或纯色)- 最底层
|
|
||||||
* 2. 边框 - 中间层
|
|
||||||
* 3. 文字 - 最顶层
|
|
||||||
*
|
|
||||||
* 注意:此方法在场景渲染的精灵批次中被调用。
|
|
||||||
* 由于 fillRect 和 drawRect 使用形状渲染管线(与精灵批次不同),
|
|
||||||
* 需要在调用它们之前结束精灵批次,然后在 drawText 之前重新开始。
|
|
||||||
*/
|
|
||||||
void Button::onDraw(RenderBackend &renderer) {
|
|
||||||
Rect rect = getBoundingBox();
|
Rect rect = getBoundingBox();
|
||||||
if (rect.empty()) {
|
if (rect.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 第1层:绘制背景(图片或纯色)==========
|
|
||||||
if (useImageBackground_) {
|
if (useImageBackground_) {
|
||||||
// 图片背景使用精灵批次绘制
|
|
||||||
drawBackgroundImage(renderer, rect);
|
drawBackgroundImage(renderer, rect);
|
||||||
} else {
|
} else {
|
||||||
// 纯色背景使用 fillRect 或 fillRoundedRect 绘制
|
|
||||||
renderer.endSpriteBatch();
|
renderer.endSpriteBatch();
|
||||||
|
|
||||||
Color bg = bgNormal_;
|
Color bg = bgNormal_;
|
||||||
|
|
@ -467,7 +479,6 @@ void Button::onDraw(RenderBackend &renderer) {
|
||||||
renderer.beginSpriteBatch();
|
renderer.beginSpriteBatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 第2层:绘制边框 ==========
|
|
||||||
renderer.endSpriteBatch();
|
renderer.endSpriteBatch();
|
||||||
|
|
||||||
if (borderWidth_ > 0.0f) {
|
if (borderWidth_ > 0.0f) {
|
||||||
|
|
@ -480,7 +491,6 @@ void Button::onDraw(RenderBackend &renderer) {
|
||||||
|
|
||||||
renderer.beginSpriteBatch();
|
renderer.beginSpriteBatch();
|
||||||
|
|
||||||
// ========== 第3层:绘制文字 ==========
|
|
||||||
if (font_ && !text_.empty()) {
|
if (font_ && !text_.empty()) {
|
||||||
Vec2 textSize = font_->measureText(text_);
|
Vec2 textSize = font_->measureText(text_);
|
||||||
|
|
||||||
|
|
@ -506,30 +516,14 @@ void Button::onDraw(RenderBackend &renderer) {
|
||||||
// ToggleImageButton 实现
|
// ToggleImageButton 实现
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 切换按钮构造函数
|
|
||||||
*/
|
|
||||||
ToggleImageButton::ToggleImageButton() {
|
ToggleImageButton::ToggleImageButton() {
|
||||||
setOnClick([this]() { toggle(); });
|
setOnClick([this]() { toggle(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建切换按钮实例的工厂方法
|
|
||||||
* @return 切换按钮智能指针
|
|
||||||
*/
|
|
||||||
Ptr<ToggleImageButton> ToggleImageButton::create() {
|
Ptr<ToggleImageButton> ToggleImageButton::create() {
|
||||||
return makePtr<ToggleImageButton>();
|
return makePtr<ToggleImageButton>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置两种状态的图片
|
|
||||||
* @param stateOffNormal 关闭状态-正常
|
|
||||||
* @param stateOnNormal 开启状态-正常
|
|
||||||
* @param stateOffHover 关闭状态-悬停(可选)
|
|
||||||
* @param stateOnHover 开启状态-悬停(可选)
|
|
||||||
* @param stateOffPressed 关闭状态-按下(可选)
|
|
||||||
* @param stateOnPressed 开启状态-按下(可选)
|
|
||||||
*/
|
|
||||||
void ToggleImageButton::setStateImages(Ptr<Texture> stateOffNormal,
|
void ToggleImageButton::setStateImages(Ptr<Texture> stateOffNormal,
|
||||||
Ptr<Texture> stateOnNormal,
|
Ptr<Texture> stateOnNormal,
|
||||||
Ptr<Texture> stateOffHover,
|
Ptr<Texture> stateOffHover,
|
||||||
|
|
@ -549,10 +543,6 @@ void ToggleImageButton::setStateImages(Ptr<Texture> stateOffNormal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置开关状态
|
|
||||||
* @param on 是否开启
|
|
||||||
*/
|
|
||||||
void ToggleImageButton::setOn(bool on) {
|
void ToggleImageButton::setOn(bool on) {
|
||||||
if (isOn_ != on) {
|
if (isOn_ != on) {
|
||||||
isOn_ = on;
|
isOn_ = on;
|
||||||
|
|
@ -562,24 +552,14 @@ void ToggleImageButton::setOn(bool on) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void ToggleImageButton::toggle() {
|
||||||
* @brief 切换当前状态
|
setOn(!isOn_);
|
||||||
*/
|
}
|
||||||
void ToggleImageButton::toggle() { setOn(!isOn_); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置状态改变回调
|
|
||||||
* @param callback 回调函数
|
|
||||||
*/
|
|
||||||
void ToggleImageButton::setOnStateChange(Function<void(bool)> callback) {
|
void ToggleImageButton::setOnStateChange(Function<void(bool)> callback) {
|
||||||
onStateChange_ = std::move(callback);
|
onStateChange_ = std::move(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置两种状态的文字
|
|
||||||
* @param textOff 关闭状态文字
|
|
||||||
* @param textOn 开启状态文字
|
|
||||||
*/
|
|
||||||
void ToggleImageButton::setStateText(const String &textOff,
|
void ToggleImageButton::setStateText(const String &textOff,
|
||||||
const String &textOn) {
|
const String &textOn) {
|
||||||
textOff_ = textOff;
|
textOff_ = textOff;
|
||||||
|
|
@ -587,11 +567,6 @@ void ToggleImageButton::setStateText(const String &textOff,
|
||||||
useStateText_ = true;
|
useStateText_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置两种状态的文字颜色
|
|
||||||
* @param colorOff 关闭状态颜色
|
|
||||||
* @param colorOn 开启状态颜色
|
|
||||||
*/
|
|
||||||
void ToggleImageButton::setStateTextColor(const Color &colorOff,
|
void ToggleImageButton::setStateTextColor(const Color &colorOff,
|
||||||
const Color &colorOn) {
|
const Color &colorOn) {
|
||||||
textColorOff_ = colorOff;
|
textColorOff_ = colorOff;
|
||||||
|
|
@ -599,21 +574,12 @@ void ToggleImageButton::setStateTextColor(const Color &colorOff,
|
||||||
useStateTextColor_ = true;
|
useStateTextColor_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void ToggleImageButton::onDrawWidget(RenderBackend &renderer) {
|
||||||
* @brief 切换按钮绘制主函数
|
|
||||||
*
|
|
||||||
* 渲染顺序:
|
|
||||||
* 1. 状态图片背景 - 最底层
|
|
||||||
* 2. 边框 - 中间层
|
|
||||||
* 3. 状态文字 - 最顶层
|
|
||||||
*/
|
|
||||||
void ToggleImageButton::onDraw(RenderBackend &renderer) {
|
|
||||||
Rect rect = getBoundingBox();
|
Rect rect = getBoundingBox();
|
||||||
if (rect.empty()) {
|
if (rect.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 第1层:根据当前状态和交互状态选择并绘制图片 ==========
|
|
||||||
Ptr<Texture> texture = nullptr;
|
Ptr<Texture> texture = nullptr;
|
||||||
|
|
||||||
if (isOn_) {
|
if (isOn_) {
|
||||||
|
|
@ -649,7 +615,6 @@ void ToggleImageButton::onDraw(RenderBackend &renderer) {
|
||||||
0.0f, Vec2::Zero());
|
0.0f, Vec2::Zero());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 第2层:绘制边框 ==========
|
|
||||||
renderer.endSpriteBatch();
|
renderer.endSpriteBatch();
|
||||||
|
|
||||||
float borderWidth = 1.0f;
|
float borderWidth = 1.0f;
|
||||||
|
|
@ -665,7 +630,6 @@ void ToggleImageButton::onDraw(RenderBackend &renderer) {
|
||||||
|
|
||||||
renderer.beginSpriteBatch();
|
renderer.beginSpriteBatch();
|
||||||
|
|
||||||
// ========== 第3层:绘制状态文字 ==========
|
|
||||||
auto font = getFont();
|
auto font = getFont();
|
||||||
if (font) {
|
if (font) {
|
||||||
String textToDraw;
|
String textToDraw;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <extra2d/graphics/camera.h>
|
||||||
|
#include <extra2d/scene/scene.h>
|
||||||
#include <extra2d/ui/widget.h>
|
#include <extra2d/ui/widget.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
Widget::Widget() { setSpatialIndexed(false); }
|
Widget::Widget() {
|
||||||
|
setSpatialIndexed(false);
|
||||||
|
}
|
||||||
|
|
||||||
void Widget::setSize(const Size &size) {
|
void Widget::setSize(const Size &size) {
|
||||||
size_ = size;
|
size_ = size;
|
||||||
|
|
@ -19,7 +23,7 @@ Rect Widget::getBoundingBox() const {
|
||||||
return Rect();
|
return Rect();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pos = getPosition();
|
auto pos = getRenderPosition();
|
||||||
auto anchor = getAnchor();
|
auto anchor = getAnchor();
|
||||||
auto scale = getScale();
|
auto scale = getScale();
|
||||||
|
|
||||||
|
|
@ -35,4 +39,103 @@ Rect Widget::getBoundingBox() const {
|
||||||
return Rect(l, t, std::abs(w), std::abs(h));
|
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
|
} // namespace extra2d
|
||||||
|
|
|
||||||
404
README.md
404
README.md
|
|
@ -25,7 +25,7 @@
|
||||||
<i>高性能、易用、原生支持 Switch 平台</i>
|
<i>高性能、易用、原生支持 Switch 平台</i>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[📖 构建指南](./SWITCH_BUILD_GUIDE.md) | [🚀 快速开始](#快速开始) | [📦 项目结构](#项目结构) | [💬 问题反馈](https://github.com/ChestnutYueyue/extra2d/issues)
|
[📖 构建指南](./docs/Extra2D%20构建系统文档.md) | [🚀 快速开始](#快速开始) | [📦 示例程序](#示例程序) | [📚 API 教程](./docs/API_Tutorial/01_Quick_Start.md)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -40,68 +40,12 @@
|
||||||
### ✨ 核心特性
|
### ✨ 核心特性
|
||||||
|
|
||||||
- **🎯 Switch 原生支持**:专为 Nintendo Switch 硬件优化,支持掌机/主机双模式
|
- **🎯 Switch 原生支持**:专为 Nintendo Switch 硬件优化,支持掌机/主机双模式
|
||||||
- **🎬 高级动画系统**:支持骨骼动画、精灵动画、补间动画,提供 ALS 动画格式支持
|
- **🎬 高级动画系统**:支持骨骼动画、精灵动画、补间动画
|
||||||
- **📜 脚本系统**:集成 Squirrel 脚本引擎,支持热更新和快速迭代开发
|
- **🎵 音频系统**:基于 SDL2_mixer 的高质量音频播放,支持 BGM 和音效
|
||||||
- **🎵 音频系统**:基于 SDL2 的高质量音频播放,支持 BGM 和音效
|
- **🎨 渲染系统**:基于 OpenGL ES 的 2D 渲染,支持自定义着色器
|
||||||
- **🎨 特效系统**:粒子系统、后处理效果、自定义着色器支持
|
|
||||||
- **💾 数据持久化**:游戏存档、配置文件的便捷读写
|
- **💾 数据持久化**:游戏存档、配置文件的便捷读写
|
||||||
|
- **🔧 空间索引**:内置四叉树和空间哈希碰撞检测系统
|
||||||
---
|
- **🖱️ UI 系统**:完整的 UI 控件支持(按钮、文本、滑块等)
|
||||||
|
|
||||||
## 🗺️ 架构概览
|
|
||||||
|
|
||||||
```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
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -116,319 +60,24 @@ mindmap
|
||||||
| 构建工具 | xmake |
|
| 构建工具 | xmake |
|
||||||
| 目标平台 | Nintendo Switch / Windows (MinGW) |
|
| 目标平台 | Nintendo Switch / Windows (MinGW) |
|
||||||
|
|
||||||
### 安装 devkitPro
|
### 安装 xmake
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Windows (以管理员身份运行 PowerShell)
|
# Windows (PowerShell)
|
||||||
Invoke-WebRequest -Uri "https://github.com/devkitPro/pacman/releases/latest/download/devkitpro-pacman.amd64.exe" -OutFile "devkitpro-pacman.exe"
|
Invoke-Expression (Invoke-WebRequest 'https://xmake.io/psget.text' -UseBasicParsing).Content
|
||||||
.\devkitpro-pacman.exe
|
|
||||||
|
|
||||||
# 安装 Switch 开发工具链
|
# macOS
|
||||||
pacman -S switch-dev switch-portlibs
|
brew install xmake
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
sudo add-apt-repository ppa:xmake-io/xmake
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install xmake
|
||||||
```
|
```
|
||||||
|
|
||||||
### 构建项目
|
## 📚 文档
|
||||||
|
|
||||||
#### Switch 平台
|
- [📖 API 教程](./docs/API_Tutorial/01_Quick_Start.md) - 完整的 API 使用教程
|
||||||
|
|
||||||
```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 使用教程
|
|
||||||
- [01. 快速开始](./docs/API_Tutorial/01_Quick_Start.md)
|
- [01. 快速开始](./docs/API_Tutorial/01_Quick_Start.md)
|
||||||
- [02. 场景系统](./docs/API_Tutorial/02_Scene_System.md)
|
- [02. 场景系统](./docs/API_Tutorial/02_Scene_System.md)
|
||||||
- [03. 节点系统](./docs/API_Tutorial/03_Node_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)
|
- [07. UI 系统](./docs/API_Tutorial/07_UI_System.md)
|
||||||
- [08. 音频系统](./docs/API_Tutorial/08_Audio_System.md)
|
- [08. 音频系统](./docs/API_Tutorial/08_Audio_System.md)
|
||||||
- [🔧 构建系统文档](./docs/Extra2D%20构建系统文档.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+ |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,113 @@
|
||||||
# Extra2D API 教程 - 01. 快速开始
|
# 01. 快速开始
|
||||||
|
|
||||||
## 简介
|
本教程将带你快速上手 Extra2D 引擎,通过一个简单的 Hello World 示例了解引擎的基本使用方法。
|
||||||
|
|
||||||
Extra2D 是一个跨平台的 2D 游戏引擎,支持 Windows (MinGW) 和 Nintendo Switch 平台。
|
## 示例代码
|
||||||
|
|
||||||
## 最小示例
|
完整示例位于 `examples/hello_world/main.cpp`:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include <extra2d/extra2d.h>
|
#include <extra2d/extra2d.h>
|
||||||
|
|
||||||
using namespace extra2d;
|
using namespace extra2d;
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
// ============================================================================
|
||||||
// 1. 初始化日志系统
|
// Hello World 场景
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Hello World 场景类
|
||||||
|
* 显示简单的 "Hello World" 文字
|
||||||
|
*/
|
||||||
|
class HelloWorldScene : public Scene {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 场景进入时调用
|
||||||
|
*/
|
||||||
|
void onEnter() override {
|
||||||
|
E2D_LOG_INFO("HelloWorldScene::onEnter - 进入场景");
|
||||||
|
|
||||||
|
// 设置背景颜色为深蓝色
|
||||||
|
setBackgroundColor(Color(0.1f, 0.1f, 0.3f, 1.0f));
|
||||||
|
|
||||||
|
// 加载字体(支持多种字体后备)
|
||||||
|
auto &resources = Application::instance().resources();
|
||||||
|
font_ = resources.loadFont("assets/font.ttf", 48, true);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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::init();
|
||||||
Logger::setLevel(LogLevel::Debug);
|
Logger::setLevel(LogLevel::Debug);
|
||||||
|
|
||||||
// 2. 获取应用实例
|
// 获取应用实例
|
||||||
auto &app = Application::instance();
|
auto &app = Application::instance();
|
||||||
|
|
||||||
// 3. 配置应用
|
// 配置应用
|
||||||
AppConfig config;
|
AppConfig config;
|
||||||
config.title = "My Game";
|
config.title = "Easy2D - Hello World";
|
||||||
config.width = 1280;
|
config.width = 1280;
|
||||||
config.height = 720;
|
config.height = 720;
|
||||||
config.vsync = true;
|
config.vsync = true;
|
||||||
config.fpsLimit = 60;
|
config.fpsLimit = 60;
|
||||||
|
|
||||||
// 4. 初始化应用
|
// 初始化应用
|
||||||
if (!app.init(config)) {
|
if (!app.init(config)) {
|
||||||
E2D_LOG_ERROR("应用初始化失败!");
|
E2D_LOG_ERROR("应用初始化失败!");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 进入场景
|
// 进入 Hello World 场景
|
||||||
app.enterScene(makePtr<MyScene>());
|
app.enterScene(makePtr<HelloWorldScene>());
|
||||||
|
|
||||||
// 6. 运行应用
|
// 运行应用
|
||||||
app.run();
|
app.run();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -45,24 +116,100 @@ int main(int argc, char **argv) {
|
||||||
|
|
||||||
## 核心概念
|
## 核心概念
|
||||||
|
|
||||||
### 应用生命周期
|
### 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)
|
- [02. 场景系统](./02_Scene_System.md) - 深入了解场景管理
|
||||||
- [03. 节点系统](03_Node_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)
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,17 @@
|
||||||
# Extra2D API 教程 - 02. 场景系统
|
# 02. 场景系统
|
||||||
|
|
||||||
|
Extra2D 的场景系统提供了游戏内容的分层管理和切换功能。本教程将详细介绍场景的生命周期、切换和过渡效果。
|
||||||
|
|
||||||
|
## 完整示例
|
||||||
|
|
||||||
|
参考 `examples/push_box/` 中的实现:
|
||||||
|
|
||||||
|
- `StartScene.h/cpp` - 开始菜单场景
|
||||||
|
- `PlayScene.h/cpp` - 游戏主场景
|
||||||
|
- `SuccessScene.h/cpp` - 通关场景
|
||||||
|
|
||||||
## 场景基础
|
## 场景基础
|
||||||
|
|
||||||
场景(Scene)是游戏的基本组织单位,负责管理节点和渲染。
|
|
||||||
|
|
||||||
### 创建场景
|
### 创建场景
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
|
|
@ -11,71 +19,54 @@
|
||||||
|
|
||||||
using namespace extra2d;
|
using namespace extra2d;
|
||||||
|
|
||||||
class MyScene : public Scene {
|
class GameScene : public Scene {
|
||||||
public:
|
public:
|
||||||
// 场景进入时调用
|
|
||||||
void onEnter() override {
|
void onEnter() override {
|
||||||
// 必须先调用父类的 onEnter()
|
// 必须先调用父类方法
|
||||||
Scene::onEnter();
|
Scene::onEnter();
|
||||||
|
|
||||||
// 设置背景颜色
|
// 设置背景色
|
||||||
setBackgroundColor(Color(0.1f, 0.1f, 0.3f, 1.0f));
|
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 {
|
void onExit() override {
|
||||||
// 清理资源
|
// 清理资源
|
||||||
|
removeAllChildren();
|
||||||
|
|
||||||
Scene::onExit();
|
Scene::onExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onUpdate(float dt) override {
|
||||||
|
Scene::onUpdate(dt);
|
||||||
|
|
||||||
|
// 游戏逻辑更新
|
||||||
|
}
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### 重要提示
|
### 场景切换
|
||||||
|
|
||||||
**必须调用 `Scene::onEnter()`**:
|
|
||||||
```cpp
|
|
||||||
void onEnter() override {
|
|
||||||
Scene::onEnter(); // 必须调用!
|
|
||||||
// 你的初始化代码
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
如果不调用,会导致:
|
|
||||||
- `running_` 状态未设置
|
|
||||||
- 子节点无法正确注册到空间索引
|
|
||||||
- 碰撞检测失效
|
|
||||||
|
|
||||||
## 场景管理
|
|
||||||
|
|
||||||
### 进入场景
|
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// 进入新场景
|
// 进入场景(无过渡)
|
||||||
app.enterScene(makePtr<MyScene>());
|
app.enterScene(makePtr<GameScene>());
|
||||||
|
|
||||||
// 替换当前场景(带过渡效果)
|
// 进入场景(有过渡效果)
|
||||||
app.scenes().replaceScene(
|
app.enterScene(makePtr<GameScene>(), TransitionType::Fade, 0.5f);
|
||||||
makePtr<PlayScene>(),
|
|
||||||
TransitionType::Fade, // 淡入淡出
|
// 替换当前场景
|
||||||
0.25f // 过渡时间(秒)
|
app.scenes().replaceScene(makePtr<NewScene>());
|
||||||
);
|
|
||||||
|
// 推入场景(保留当前场景)
|
||||||
|
app.scenes().pushScene(makePtr<NewScene>());
|
||||||
|
|
||||||
|
// 弹出场景(返回上一个场景)
|
||||||
|
app.scenes().popScene();
|
||||||
```
|
```
|
||||||
|
|
||||||
### 场景过渡类型
|
### 过渡效果类型
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
enum class TransitionType {
|
enum class TransitionType {
|
||||||
|
|
@ -88,84 +79,184 @@ enum class TransitionType {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## 场景配置
|
## 场景管理器
|
||||||
|
|
||||||
### 视口设置
|
通过 `app.scenes()` 访问场景管理器:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
void onEnter() override {
|
auto& scenes = app.scenes();
|
||||||
Scene::onEnter();
|
|
||||||
|
|
||||||
// 设置视口大小(影响坐标系)
|
// 获取当前场景
|
||||||
setViewportSize(1280.0f, 720.0f);
|
auto current = scenes.currentScene();
|
||||||
|
|
||||||
// 设置背景颜色
|
// 获取场景栈深度
|
||||||
setBackgroundColor(Colors::Black);
|
size_t depth = scenes.stackDepth();
|
||||||
|
|
||||||
// 启用/禁用空间索引
|
// 清空场景栈
|
||||||
setSpatialIndexingEnabled(true);
|
scenes.clearStack();
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 空间索引
|
## 场景生命周期
|
||||||
|
|
||||||
```cpp
|
```
|
||||||
// 获取空间管理器
|
创建场景 (makePtr<Scene>)
|
||||||
auto &spatialManager = getSpatialManager();
|
↓
|
||||||
|
进入场景 (enterScene)
|
||||||
// 切换空间索引策略
|
↓
|
||||||
spatialManager.setStrategy(SpatialStrategy::QuadTree); // 四叉树
|
onEnter() - 初始化资源
|
||||||
spatialManager.setStrategy(SpatialStrategy::SpatialHash); // 空间哈希
|
↓
|
||||||
|
主循环
|
||||||
// 查询所有碰撞
|
├── onUpdate(dt) - 每帧更新
|
||||||
auto collisions = queryCollisions();
|
└── onRender(renderer) - 每帧渲染
|
||||||
|
↓
|
||||||
|
退出场景
|
||||||
|
↓
|
||||||
|
onExit() - 清理资源
|
||||||
|
↓
|
||||||
|
场景销毁
|
||||||
```
|
```
|
||||||
|
|
||||||
## 完整示例
|
## 推箱子示例场景结构
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ StartScene │
|
||||||
|
│ ┌─────────────────────────────┐ │
|
||||||
|
│ │ 开始菜单界面 │ │
|
||||||
|
│ │ - 新游戏 │ │
|
||||||
|
│ │ - 继续游戏 │ │
|
||||||
|
│ │ - 退出 │ │
|
||||||
|
│ └─────────────────────────────┘ │
|
||||||
|
└──────────────┬──────────────────────┘
|
||||||
|
│ 选择"新游戏"
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ PlayScene │
|
||||||
|
│ ┌─────────────────────────────┐ │
|
||||||
|
│ │ 游戏主界面 │ │
|
||||||
|
│ │ - 地图渲染 │ │
|
||||||
|
│ │ - 玩家控制 │ │
|
||||||
|
│ │ - 关卡信息 │ │
|
||||||
|
│ └─────────────────────────────┘ │
|
||||||
|
└──────────────┬──────────────────────┘
|
||||||
|
│ 通关
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ SuccessScene │
|
||||||
|
│ ┌─────────────────────────────┐ │
|
||||||
|
│ │ 通关界面 │ │
|
||||||
|
│ │ - 显示成绩 │ │
|
||||||
|
│ │ - 下一关/返回菜单 │ │
|
||||||
|
│ └─────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 代码示例:菜单场景
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
class GameScene : public Scene {
|
class MenuScene : public Scene {
|
||||||
public:
|
public:
|
||||||
void onEnter() override {
|
void onEnter() override {
|
||||||
Scene::onEnter();
|
Scene::onEnter();
|
||||||
|
|
||||||
// 设置视口和背景
|
auto& app = Application::instance();
|
||||||
setViewportSize(1280.0f, 720.0f);
|
auto& resources = app.resources();
|
||||||
setBackgroundColor(Color(0.1f, 0.2f, 0.3f, 1.0f));
|
|
||||||
|
|
||||||
// 启用空间索引
|
// 加载背景
|
||||||
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 {
|
void onUpdate(float dt) override {
|
||||||
Scene::onUpdate(dt);
|
Scene::onUpdate(dt);
|
||||||
|
|
||||||
// 检查退出按键
|
auto& input = Application::instance().input();
|
||||||
auto &input = Application::instance().input();
|
|
||||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_START)) {
|
// 方向键导航
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void createMenuButtons() {
|
||||||
|
float centerX = 640.0f;
|
||||||
|
float startY = 300.0f;
|
||||||
|
float spacing = 50.0f;
|
||||||
|
|
||||||
|
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 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();
|
Application::instance().quit();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onRender(RenderBackend &renderer) override {
|
Ptr<FontAtlas> font_;
|
||||||
Scene::onRender(renderer);
|
std::vector<Ptr<Button>> buttons_;
|
||||||
|
int selectedIndex_ = 0;
|
||||||
// 绘制 FPS
|
int menuCount_ = 3;
|
||||||
auto &app = Application::instance();
|
|
||||||
std::string fpsText = "FPS: " + std::to_string(app.fps());
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
void onExit() override {
|
|
||||||
E2D_LOG_INFO("游戏场景退出");
|
|
||||||
Scene::onExit();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **始终在 onEnter 中调用 Scene::onEnter()** - 确保场景正确初始化
|
||||||
|
2. **在 onExit 中清理资源** - 避免内存泄漏
|
||||||
|
3. **使用过渡效果** - 提升用户体验
|
||||||
|
4. **分离场景逻辑** - 每个场景负责自己的功能
|
||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
- [03. 节点系统](03_Node_System.md)
|
- [03. 节点系统](./03_Node_System.md) - 学习节点和精灵的使用
|
||||||
- [04. 资源管理](04_Resource_Management.md)
|
- [04. 资源管理](./04_Resource_Management.md) - 深入了解资源加载
|
||||||
|
|
|
||||||
|
|
@ -1,219 +1,353 @@
|
||||||
# Extra2D API 教程 - 03. 节点系统
|
# 03. 节点系统
|
||||||
|
|
||||||
## 节点基础
|
Extra2D 的节点系统是构建游戏对象的基础。所有可见的游戏元素都是节点的子类。
|
||||||
|
|
||||||
节点(Node)是游戏对象的基本单位,可以包含子节点,形成树形结构。
|
## 核心节点类型
|
||||||
|
|
||||||
### 创建节点
|
```
|
||||||
|
Node (基类)
|
||||||
```cpp
|
├── Sprite (精灵)
|
||||||
#include <extra2d/extra2d.h>
|
├── Text (文本)
|
||||||
|
├── Button (按钮)
|
||||||
using namespace extra2d;
|
├── Widget (UI控件基类)
|
||||||
|
│ ├── Label (标签)
|
||||||
class MyNode : public Node {
|
│ ├── CheckBox (复选框)
|
||||||
public:
|
│ ├── RadioButton (单选按钮)
|
||||||
MyNode() {
|
│ ├── Slider (滑块)
|
||||||
// 设置位置
|
│ └── ProgressBar (进度条)
|
||||||
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);
|
|
||||||
// 自定义渲染
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 节点层级
|
## 基础节点操作
|
||||||
|
|
||||||
### 添加子节点
|
### 创建和添加节点
|
||||||
|
|
||||||
```cpp
|
```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>();
|
auto text = Text::create("Hello World", font);
|
||||||
|
text->setPosition(Vec2(400, 300));
|
||||||
// 添加到场景
|
text->setTextColor(Color(1, 1, 1, 1));
|
||||||
addChild(child);
|
addChild(text);
|
||||||
|
|
||||||
// 在指定位置添加
|
|
||||||
addChild(child, 0); // z-order = 0
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 移除子节点
|
### 节点属性
|
||||||
|
|
||||||
```cpp
|
```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
|
```cpp
|
||||||
// 获取子节点数量
|
// 使用链式调用快速配置节点
|
||||||
size_t count = getChildren().size();
|
auto text = Text::create("标题", font)
|
||||||
|
->withPosition(640.0f, 100.0f)
|
||||||
// 通过名称查找
|
->withAnchor(0.5f, 0.5f)
|
||||||
auto node = getChildByName("myNode");
|
->withTextColor(Color(1.0f, 1.0f, 0.0f, 1.0f))
|
||||||
|
->withCoordinateSpace(CoordinateSpace::Screen);
|
||||||
// 遍历子节点
|
addChild(text);
|
||||||
for (auto &child : getChildren()) {
|
|
||||||
// 处理子节点
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 空间索引
|
## 精灵(Sprite)
|
||||||
|
|
||||||
### 启用空间索引
|
|
||||||
|
|
||||||
```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)) {
|
|
||||||
// 边界框相交
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 精灵节点
|
|
||||||
|
|
||||||
### 创建精灵
|
### 创建精灵
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// 加载纹理
|
auto& resources = Application::instance().resources();
|
||||||
auto texture = resources.loadTexture("assets/player.png");
|
|
||||||
|
|
||||||
// 创建精灵
|
// 从纹理创建
|
||||||
|
auto texture = resources.loadTexture("assets/player.png");
|
||||||
auto sprite = Sprite::create(texture);
|
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);
|
auto newTexture = resources.loadTexture("assets/player2.png");
|
||||||
|
sprite->setTexture(newTexture);
|
||||||
// 添加到场景
|
|
||||||
addChild(sprite);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 精灵动画
|
### 精灵动画
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// 创建动画
|
// 创建帧动画
|
||||||
auto animation = Animation::create("walk", 0.1f);
|
auto anim = AnimatedSprite::createFromGrid(
|
||||||
animation->addFrame(resources.loadTexture("assets/walk1.png"));
|
"player.png", // 纹理
|
||||||
animation->addFrame(resources.loadTexture("assets/walk2.png"));
|
32, 32, // 单帧宽高
|
||||||
animation->addFrame(resources.loadTexture("assets/walk3.png"));
|
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
|
```cpp
|
||||||
class Player : public Node {
|
class Player : public Node {
|
||||||
public:
|
public:
|
||||||
Player() {
|
static Ptr<Player> create(Ptr<Texture> texture) {
|
||||||
setSpatialIndexed(true);
|
auto player = makePtr<Player>();
|
||||||
|
if (player->init(texture)) {
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// 加载精灵
|
bool init(Ptr<Texture> texture) {
|
||||||
auto &resources = Application::instance().resources();
|
|
||||||
auto texture = resources.loadTexture("assets/player.png");
|
|
||||||
sprite_ = Sprite::create(texture);
|
sprite_ = Sprite::create(texture);
|
||||||
sprite_->setAnchor(0.5f, 0.5f);
|
|
||||||
addChild(sprite_);
|
addChild(sprite_);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUpdate(float dt) override {
|
void update(float dt) override {
|
||||||
Node::onUpdate(dt);
|
// 更新逻辑
|
||||||
|
velocity_.y += gravity_ * dt;
|
||||||
// 移动
|
setPosition(getPosition() + velocity_ * 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rect getBoundingBox() const override {
|
void jump() {
|
||||||
Vec2 pos = getPosition();
|
velocity_.y = jumpForce_;
|
||||||
return Rect(pos.x - 25.0f, pos.y - 25.0f, 50.0f, 50.0f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ptr<Sprite> sprite_;
|
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)
|
- [04. 资源管理](./04_Resource_Management.md) - 深入了解资源加载
|
||||||
- [05. 输入处理](05_Input_Handling.md)
|
- [05. 输入处理](./05_Input_Handling.md) - 学习输入处理
|
||||||
|
- [06. 碰撞检测](./06_Collision_Detection.md) - 学习碰撞检测系统
|
||||||
|
|
|
||||||
|
|
@ -1,148 +1,167 @@
|
||||||
# Extra2D API 教程 - 04. 资源管理
|
# 04. 资源管理
|
||||||
|
|
||||||
|
Extra2D 提供了统一的资源管理系统,用于加载和管理游戏中的各种资源。
|
||||||
|
|
||||||
## 资源管理器
|
## 资源管理器
|
||||||
|
|
||||||
Extra2D 使用资源管理器来统一加载和管理资源。
|
通过 `Application::instance().resources()` 访问资源管理器:
|
||||||
|
|
||||||
### 获取资源管理器
|
|
||||||
|
|
||||||
```cpp
|
```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
|
```cpp
|
||||||
// 加载纹理
|
// 加载纹理
|
||||||
auto texture = resources.loadTexture("assets/player.png");
|
auto texture = resources.loadTexture("assets/images/player.png");
|
||||||
|
|
||||||
if (!texture) {
|
if (texture) {
|
||||||
E2D_LOG_ERROR("纹理加载失败!");
|
// 创建精灵
|
||||||
|
auto sprite = Sprite::create(texture);
|
||||||
|
addChild(sprite);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 创建精灵
|
### 纹理缓存
|
||||||
|
|
||||||
|
资源管理器会自动缓存已加载的纹理,多次加载同一文件会返回缓存的实例:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
auto sprite = Sprite::create(texture);
|
// 第一次加载 - 从文件读取
|
||||||
sprite->setPosition(Vec2(640.0f, 360.0f));
|
auto tex1 = resources.loadTexture("assets/image.png");
|
||||||
addChild(sprite);
|
|
||||||
|
// 第二次加载 - 返回缓存
|
||||||
|
auto tex2 = resources.loadTexture("assets/image.png");
|
||||||
|
|
||||||
|
// tex1 和 tex2 指向同一个纹理对象
|
||||||
```
|
```
|
||||||
|
|
||||||
## 音效资源
|
## 字体加载
|
||||||
|
|
||||||
### 加载音效
|
### 基本用法
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// 加载音效
|
// 加载字体(指定字号)
|
||||||
auto sound = resources.loadSound("assets/jump.wav");
|
auto font24 = resources.loadFont("assets/font.ttf", 24, true);
|
||||||
|
|
||||||
// 播放音效
|
// 创建文本
|
||||||
sound->play();
|
auto text = Text::create("Hello World", font24);
|
||||||
|
addChild(text);
|
||||||
// 循环播放
|
|
||||||
sound->play(true);
|
|
||||||
|
|
||||||
// 停止播放
|
|
||||||
sound->stop();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 资源路径解析
|
### 字体后备
|
||||||
|
|
||||||
Extra2D 的资源管理器支持多平台路径解析:
|
支持设置后备字体,当主字体缺少某些字符时自动使用后备字体:
|
||||||
|
|
||||||
### 路径优先级
|
|
||||||
|
|
||||||
1. **原始路径**: `assets/font.ttf`
|
|
||||||
2. **romfs 路径**: `romfs:/assets/font.ttf` (Switch)
|
|
||||||
3. **sdmc 路径**: `sdmc:/assets/font.ttf` (Switch SD卡)
|
|
||||||
4. **可执行文件相对路径** (Windows)
|
|
||||||
|
|
||||||
### 使用示例
|
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// 所有平台使用相同的路径
|
// 加载主字体和后备字体
|
||||||
auto font = resources.loadFont("assets/font.ttf", 48, true);
|
auto mainFont = resources.loadFont("assets/main.ttf", 24, true);
|
||||||
auto texture = resources.loadTexture("assets/images/player.png");
|
auto fallbackFont = resources.loadFont("assets/fallback.ttf", 24, true);
|
||||||
auto sound = resources.loadSound("assets/audio/jump.wav");
|
|
||||||
|
// 设置后备字体
|
||||||
|
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
|
```cpp
|
||||||
class GameScene : public Scene {
|
void StartScene::onEnter() {
|
||||||
public:
|
|
||||||
void onEnter() override {
|
|
||||||
Scene::onEnter();
|
Scene::onEnter();
|
||||||
|
|
||||||
auto &resources = Application::instance().resources();
|
auto& app = Application::instance();
|
||||||
|
auto& resources = app.resources();
|
||||||
|
|
||||||
|
// 加载背景纹理
|
||||||
|
auto bgTex = resources.loadTexture("assets/images/start.jpg");
|
||||||
|
if (bgTex) {
|
||||||
|
auto background = Sprite::create(bgTex);
|
||||||
|
background->setAnchor(0.0f, 0.0f);
|
||||||
|
addChild(background);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载音效图标纹理
|
||||||
|
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_);
|
||||||
|
}
|
||||||
|
|
||||||
// 加载字体
|
// 加载字体
|
||||||
titleFont_ = resources.loadFont("assets/font.ttf", 60, true);
|
font_ = resources.loadFont("assets/font.ttf", 28, 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_);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Ptr<FontAtlas> titleFont_;
|
|
||||||
Ptr<FontAtlas> infoFont_;
|
|
||||||
Ptr<Texture> playerTexture_;
|
|
||||||
Ptr<Texture> enemyTexture_;
|
|
||||||
Ptr<Sprite> player_;
|
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **预加载资源** - 在场景 `onEnter()` 中加载所需资源
|
||||||
|
2. **检查资源有效性** - 始终检查加载结果是否为 nullptr
|
||||||
|
3. **复用资源** - 多次使用同一资源时保存指针,避免重复加载
|
||||||
|
4. **合理设置字号** - 字体加载时会生成对应字号的图集
|
||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
- [05. 输入处理](05_Input_Handling.md)
|
- [05. 输入处理](./05_Input_Handling.md) - 学习输入处理
|
||||||
- [06. 碰撞检测](06_Collision_Detection.md)
|
- [06. 碰撞检测](./06_Collision_Detection.md) - 学习碰撞检测系统
|
||||||
|
|
|
||||||
|
|
@ -1,216 +1,189 @@
|
||||||
# Extra2D API 教程 - 05. 输入处理
|
# 05. 输入处理
|
||||||
|
|
||||||
## 输入系统
|
Extra2D 提供了统一的输入处理系统,支持手柄、键盘等多种输入设备。
|
||||||
|
|
||||||
Extra2D 提供统一的输入处理接口,支持键盘和游戏手柄。
|
## 输入管理器
|
||||||
|
|
||||||
### 获取输入管理器
|
通过 `Application::instance().input()` 访问输入管理器:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
auto &input = Application::instance().input();
|
auto& input = Application::instance().input();
|
||||||
```
|
```
|
||||||
|
|
||||||
## 游戏手柄输入
|
## 按键检测
|
||||||
|
|
||||||
Extra2D 提供了 `GamepadButton` 和 `GamepadAxis` 命名空间来映射 SDL 按键。
|
### 检测方法
|
||||||
|
|
||||||
### 检测按键按下
|
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
void onUpdate(float dt) override {
|
// 按键是否按下(持续触发)
|
||||||
auto &input = Application::instance().input();
|
if (input.isButtonDown(GamepadButton::A)) {
|
||||||
|
// 每帧都会触发,只要按键保持按下
|
||||||
|
}
|
||||||
|
|
||||||
// 检测按键按下(每帧只触发一次)
|
// 按键是否刚按下(单次触发)
|
||||||
if (input.isButtonPressed(GamepadButton::A)) {
|
if (input.isButtonPressed(GamepadButton::A)) {
|
||||||
// A 键被按下
|
// 只在按下瞬间触发一次
|
||||||
jump();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (input.isButtonPressed(GamepadButton::B)) {
|
// 按键是否刚释放
|
||||||
// B 键被按下
|
if (input.isButtonReleased(GamepadButton::A)) {
|
||||||
attack();
|
// 只在释放瞬间触发一次
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 检测按键按住
|
### 常用按键
|
||||||
|
|
||||||
```cpp
|
| 按键 | 说明 | Switch 对应 |
|
||||||
void onUpdate(float dt) override {
|
|------|------|------------|
|
||||||
auto &input = Application::instance().input();
|
| `GamepadButton::A` | A 键 | A 键 |
|
||||||
|
| `GamepadButton::B` | B 键 | B 键 |
|
||||||
// 检测按键按住(每帧都触发)
|
| `GamepadButton::X` | X 键 | X 键 |
|
||||||
if (input.isButtonDown(GamepadButton::DPadLeft)) {
|
| `GamepadButton::Y` | Y 键 | Y 键 |
|
||||||
// 左方向键按住
|
| `GamepadButton::Start` | 开始键 | + 键 |
|
||||||
moveLeft();
|
| `GamepadButton::Select` | 选择键 | - 键 |
|
||||||
}
|
| `GamepadButton::DPadUp` | 方向上 | 方向键上 |
|
||||||
|
| `GamepadButton::DPadDown` | 方向下 | 方向键下 |
|
||||||
if (input.isButtonDown(GamepadButton::DPadRight)) {
|
| `GamepadButton::DPadLeft` | 方向左 | 方向键左 |
|
||||||
// 右方向键按住
|
| `GamepadButton::DPadRight` | 方向右 | 方向键右 |
|
||||||
moveRight();
|
| `GamepadButton::LeftStick` | 左摇杆按下 | L3 |
|
||||||
}
|
| `GamepadButton::RightStick` | 右摇杆按下 | R3 |
|
||||||
}
|
| `GamepadButton::LeftShoulder` | 左肩键 | L |
|
||||||
```
|
| `GamepadButton::RightShoulder` | 右肩键 | R |
|
||||||
|
|
||||||
### 按键映射表
|
|
||||||
|
|
||||||
| 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 |
|
|
||||||
|
|
||||||
## 摇杆输入
|
## 摇杆输入
|
||||||
|
|
||||||
### 获取摇杆值
|
### 获取摇杆值
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
void onUpdate(float dt) override {
|
// 获取左摇杆位置(范围 -1.0 到 1.0)
|
||||||
auto &input = Application::instance().input();
|
Vec2 leftStick = input.getLeftStick();
|
||||||
|
|
||||||
// 左摇杆(范围 -1.0 到 1.0)
|
// 获取右摇杆位置
|
||||||
float leftX = input.getAxis(GamepadAxis::LeftX);
|
Vec2 rightStick = input.getRightStick();
|
||||||
float leftY = input.getAxis(GamepadAxis::LeftY);
|
|
||||||
|
|
||||||
// 右摇杆
|
// 应用摇杆输入
|
||||||
float rightX = input.getAxis(GamepadAxis::RightX);
|
float speed = 200.0f;
|
||||||
float rightY = input.getAxis(GamepadAxis::RightY);
|
player->setPosition(player->getPosition() + leftStick * speed * dt);
|
||||||
|
|
||||||
// 使用摇杆值移动
|
|
||||||
if (std::abs(leftX) > 0.1f || std::abs(leftY) > 0.1f) {
|
|
||||||
Vec2 velocity(leftX * speed, leftY * speed);
|
|
||||||
player->setPosition(player->getPosition() + velocity * dt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 摇杆轴映射表
|
### 摇杆死区
|
||||||
|
|
||||||
| Extra2D 枚举 | 说明 |
|
|
||||||
|-------------|------|
|
|
||||||
| `GamepadAxis::LeftX` | 左摇杆 X 轴 |
|
|
||||||
| `GamepadAxis::LeftY` | 左摇杆 Y 轴 |
|
|
||||||
| `GamepadAxis::RightX` | 右摇杆 X 轴 |
|
|
||||||
| `GamepadAxis::RightY` | 右摇杆 Y 轴 |
|
|
||||||
| `GamepadAxis::LeftTrigger` | 左扳机 (LT/L2) |
|
|
||||||
| `GamepadAxis::RightTrigger` | 右扳机 (RT/R2) |
|
|
||||||
|
|
||||||
## 键盘输入
|
|
||||||
|
|
||||||
### 检测键盘按键
|
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
void onUpdate(float dt) override {
|
// 设置摇杆死区(默认 0.15)
|
||||||
auto &input = Application::instance().input();
|
input.setStickDeadZone(0.2f);
|
||||||
|
|
||||||
// 检测按键按下
|
|
||||||
if (input.isKeyPressed(SDLK_SPACE)) {
|
|
||||||
jump();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检测按键按住
|
|
||||||
if (input.isKeyDown(SDLK_LEFT)) {
|
|
||||||
moveLeft();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.isKeyDown(SDLK_RIGHT)) {
|
|
||||||
moveRight();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 完整示例
|
## 完整示例
|
||||||
|
|
||||||
```cpp
|
### 菜单导航
|
||||||
class Player : public Node {
|
|
||||||
public:
|
|
||||||
void onUpdate(float dt) override {
|
|
||||||
Node::onUpdate(dt);
|
|
||||||
|
|
||||||
auto &input = Application::instance().input();
|
参考 `examples/push_box/StartScene.cpp`:
|
||||||
Vec2 velocity(0.0f, 0.0f);
|
|
||||||
|
```cpp
|
||||||
|
void StartScene::onUpdate(float dt) {
|
||||||
|
Scene::onUpdate(dt);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)) {
|
if (input.isButtonDown(GamepadButton::DPadLeft)) {
|
||||||
velocity.x = -speed_;
|
moveDir.x -= 1;
|
||||||
} else if (input.isButtonDown(GamepadButton::DPadRight)) {
|
}
|
||||||
velocity.x = speed_;
|
if (input.isButtonDown(GamepadButton::DPadRight)) {
|
||||||
|
moveDir.x += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.isButtonDown(GamepadButton::DPadUp)) {
|
if (input.isButtonDown(GamepadButton::DPadUp)) {
|
||||||
velocity.y = -speed_;
|
moveDir.y -= 1;
|
||||||
} else if (input.isButtonDown(GamepadButton::DPadDown)) {
|
}
|
||||||
velocity.y = speed_;
|
if (input.isButtonDown(GamepadButton::DPadDown)) {
|
||||||
|
moveDir.y += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 摇杆移动(如果方向键没有按下)
|
// 摇杆移动
|
||||||
if (velocity.x == 0.0f && velocity.y == 0.0f) {
|
Vec2 stick = input.getLeftStick();
|
||||||
float axisX = input.getAxis(GamepadAxis::LeftX);
|
if (stick.length() > 0.1f) {
|
||||||
float axisY = input.getAxis(GamepadAxis::LeftY);
|
moveDir = stick;
|
||||||
|
|
||||||
if (std::abs(axisX) > 0.1f) {
|
|
||||||
velocity.x = axisX * speed_;
|
|
||||||
}
|
|
||||||
if (std::abs(axisY) > 0.1f) {
|
|
||||||
velocity.y = axisY * speed_;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用移动
|
// 应用移动
|
||||||
Vec2 pos = getPosition();
|
if (moveDir.length() > 0) {
|
||||||
pos = pos + velocity * dt;
|
moveDir.normalize();
|
||||||
setPosition(pos);
|
setPosition(getPosition() + moveDir * speed_ * dt);
|
||||||
|
}
|
||||||
|
|
||||||
// 动作键
|
// 跳跃
|
||||||
if (input.isButtonPressed(GamepadButton::A)) {
|
if (input.isButtonPressed(GamepadButton::A)) {
|
||||||
jump();
|
jump();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (input.isButtonPressed(GamepadButton::B)) {
|
|
||||||
attack();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 退出游戏
|
|
||||||
if (input.isButtonPressed(GamepadButton::Start)) {
|
|
||||||
Application::instance().quit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
float speed_ = 200.0f;
|
|
||||||
|
|
||||||
void jump() {
|
|
||||||
// 跳跃逻辑
|
|
||||||
}
|
|
||||||
|
|
||||||
void attack() {
|
|
||||||
// 攻击逻辑
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 输入映射
|
||||||
|
|
||||||
|
### 自定义按键映射
|
||||||
|
|
||||||
|
```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)
|
- [06. 碰撞检测](./06_Collision_Detection.md) - 学习碰撞检测系统
|
||||||
- [07. UI 系统](07_UI_System.md)
|
- [07. UI 系统](./07_UI_System.md) - 学习 UI 控件使用
|
||||||
|
|
|
||||||
|
|
@ -1,81 +1,90 @@
|
||||||
# Extra2D API 教程 - 06. 碰撞检测
|
# 06. 碰撞检测
|
||||||
|
|
||||||
## 空间索引系统
|
Extra2D 提供了基于空间索引的高效碰撞检测系统,支持四叉树和空间哈希两种策略。
|
||||||
|
|
||||||
Extra2D 内置了空间索引系统,用于高效地进行碰撞检测。
|
## 完整示例
|
||||||
|
|
||||||
### 启用空间索引
|
参考示例代码:
|
||||||
|
- `examples/collision_demo/main.cpp` - 基础碰撞检测演示
|
||||||
|
- `examples/spatial_index_demo/main.cpp` - 空间索引性能演示
|
||||||
|
|
||||||
|
## 启用碰撞检测
|
||||||
|
|
||||||
|
### 1. 创建可碰撞节点
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
void onEnter() override {
|
class CollidableBox : public Node {
|
||||||
Scene::onEnter();
|
|
||||||
|
|
||||||
// 启用空间索引
|
|
||||||
setSpatialIndexingEnabled(true);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 碰撞节点
|
|
||||||
|
|
||||||
### 创建可碰撞节点
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
class PhysicsNode : public Node {
|
|
||||||
public:
|
public:
|
||||||
PhysicsNode(float size, const Color &color)
|
CollidableBox(float width, float height, const Color& color)
|
||||||
: size_(size), color_(color), isColliding_(false) {
|
: width_(width), height_(height), color_(color), isColliding_(false) {
|
||||||
// 启用空间索引(关键!)
|
// 启用空间索引 - 这是关键!
|
||||||
setSpatialIndexed(true);
|
setSpatialIndexed(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 必须实现 getBoundingBox()
|
// 必须实现 getBoundingBox 方法
|
||||||
Rect getBoundingBox() const override {
|
Rect getBoundingBox() const override {
|
||||||
Vec2 pos = getPosition();
|
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 setColliding(bool colliding) { isColliding_ = colliding; }
|
||||||
|
|
||||||
void onRender(RenderBackend &renderer) override {
|
void onRender(RenderBackend& renderer) override {
|
||||||
Vec2 pos = getPosition();
|
Vec2 pos = getPosition();
|
||||||
|
|
||||||
// 碰撞时变红色
|
// 碰撞时变红色
|
||||||
Color fillColor = isColliding_ ? Color(1.0f, 0.2f, 0.2f, 0.9f) : color_;
|
Color fillColor = isColliding_ ? Color(1.0f, 0.2f, 0.2f, 0.8f) : color_;
|
||||||
renderer.fillRect(Rect(pos.x - size_ / 2, pos.y - size_ / 2, size_, size_),
|
renderer.fillRect(
|
||||||
fillColor);
|
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:
|
private:
|
||||||
float size_;
|
float width_, height_;
|
||||||
Color color_;
|
Color color_;
|
||||||
bool isColliding_;
|
bool isColliding_;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## 碰撞检测
|
### 2. 执行碰撞检测
|
||||||
|
|
||||||
### 查询所有碰撞
|
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
void performCollisionDetection() {
|
class GameScene : public Scene {
|
||||||
|
public:
|
||||||
|
void onUpdate(float dt) override {
|
||||||
|
Scene::onUpdate(dt);
|
||||||
|
|
||||||
// 清除之前的碰撞状态
|
// 清除之前的碰撞状态
|
||||||
for (auto &node : nodes_) {
|
for (auto& box : boxes_) {
|
||||||
node->setColliding(false);
|
box->setColliding(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询所有碰撞(使用空间索引)
|
// 使用场景的空间索引查询所有碰撞
|
||||||
auto collisions = queryCollisions();
|
auto collisions = queryCollisions();
|
||||||
|
|
||||||
// 标记碰撞的节点
|
// 处理碰撞
|
||||||
for (const auto &[nodeA, nodeB] : collisions) {
|
for (const auto& [nodeA, nodeB] : collisions) {
|
||||||
if (auto boxA = dynamic_cast<PhysicsNode *>(nodeA)) {
|
if (auto boxA = dynamic_cast<CollidableBox*>(nodeA)) {
|
||||||
boxA->setColliding(true);
|
boxA->setColliding(true);
|
||||||
}
|
}
|
||||||
if (auto boxB = dynamic_cast<PhysicsNode *>(nodeB)) {
|
if (auto boxB = dynamic_cast<CollidableBox*>(nodeB)) {
|
||||||
boxB->setColliding(true);
|
boxB->setColliding(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Ptr<CollidableBox>> boxes_;
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## 空间索引策略
|
## 空间索引策略
|
||||||
|
|
@ -83,141 +92,105 @@ void performCollisionDetection() {
|
||||||
### 切换策略
|
### 切换策略
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
void onEnter() override {
|
// 获取空间管理器
|
||||||
Scene::onEnter();
|
auto& spatialManager = getSpatialManager();
|
||||||
|
|
||||||
// 启用空间索引
|
// 切换到四叉树
|
||||||
setSpatialIndexingEnabled(true);
|
spatialManager.setStrategy(SpatialStrategy::QuadTree);
|
||||||
|
|
||||||
// 设置空间索引策略
|
// 切换到空间哈希
|
||||||
auto &spatialManager = getSpatialManager();
|
spatialManager.setStrategy(SpatialStrategy::SpatialHash);
|
||||||
spatialManager.setStrategy(SpatialStrategy::QuadTree); // 四叉树
|
|
||||||
// 或
|
|
||||||
spatialManager.setStrategy(SpatialStrategy::SpatialHash); // 空间哈希
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换策略
|
// 获取当前策略名称
|
||||||
void toggleStrategy() {
|
const char* name = spatialManager.getStrategyName();
|
||||||
auto &spatialManager = getSpatialManager();
|
|
||||||
SpatialStrategy current = spatialManager.getCurrentStrategy();
|
|
||||||
|
|
||||||
if (current == SpatialStrategy::QuadTree) {
|
|
||||||
spatialManager.setStrategy(SpatialStrategy::SpatialHash);
|
|
||||||
} else {
|
|
||||||
spatialManager.setStrategy(SpatialStrategy::QuadTree);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 策略对比
|
### 策略对比
|
||||||
|
|
||||||
| 策略 | 适用场景 | 特点 |
|
| 策略 | 适用场景 | 特点 |
|
||||||
|------|----------|------|
|
|------|---------|------|
|
||||||
| QuadTree | 节点分布不均匀 | 适合稀疏场景 |
|
| QuadTree | 节点分布不均匀 | 分层划分,适合稀疏分布 |
|
||||||
| SpatialHash | 节点分布均匀 | 适合密集场景 |
|
| SpatialHash | 节点分布均匀 | 均匀网格,适合密集分布 |
|
||||||
|
|
||||||
## 完整示例
|
## 性能演示
|
||||||
|
|
||||||
|
`examples/spatial_index_demo/main.cpp` 展示了空间索引的性能优势:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
class CollisionScene : public Scene {
|
class SpatialIndexDemoScene : public Scene {
|
||||||
public:
|
public:
|
||||||
void onEnter() override {
|
void onEnter() override {
|
||||||
Scene::onEnter();
|
Scene::onEnter();
|
||||||
|
|
||||||
// 启用空间索引
|
// 创建100个碰撞节点
|
||||||
setSpatialIndexingEnabled(true);
|
|
||||||
|
|
||||||
// 创建碰撞节点
|
|
||||||
createNodes(100);
|
createNodes(100);
|
||||||
|
|
||||||
|
E2D_LOG_INFO("创建了 {} 个碰撞节点", nodes_.size());
|
||||||
|
E2D_LOG_INFO("空间索引已启用: {}", isSpatialIndexingEnabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUpdate(float dt) override {
|
void onUpdate(float dt) override {
|
||||||
Scene::onUpdate(dt);
|
Scene::onUpdate(dt);
|
||||||
|
|
||||||
// 更新节点位置
|
// 更新所有节点位置
|
||||||
for (auto &node : nodes_) {
|
for (auto& node : nodes_) {
|
||||||
node->update(dt);
|
node->update(dt, screenWidth_, screenHeight_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行碰撞检测
|
// 使用空间索引进行碰撞检测
|
||||||
performCollisionDetection();
|
performCollisionDetection();
|
||||||
|
|
||||||
|
// 按 X 键切换索引策略
|
||||||
|
auto& input = Application::instance().input();
|
||||||
|
if (input.isButtonPressed(GamepadButton::X)) {
|
||||||
|
toggleSpatialStrategy();
|
||||||
}
|
}
|
||||||
|
|
||||||
void onRender(RenderBackend &renderer) override {
|
|
||||||
Scene::onRender(renderer);
|
|
||||||
|
|
||||||
// 绘制碰撞统计
|
|
||||||
std::string text = "Collisions: " + std::to_string(collisionCount_);
|
|
||||||
// ...
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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() {
|
void performCollisionDetection() {
|
||||||
// 清除碰撞状态
|
// 清除之前的碰撞状态
|
||||||
for (auto &node : nodes_) {
|
for (auto& node : nodes_) {
|
||||||
node->setColliding(false);
|
node->setColliding(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询碰撞
|
// 使用引擎自带的空间索引进行碰撞检测
|
||||||
auto collisions = queryCollisions();
|
auto collisions = queryCollisions();
|
||||||
collisionCount_ = collisions.size();
|
|
||||||
|
|
||||||
// 标记碰撞节点
|
// 标记碰撞的节点
|
||||||
for (const auto &[nodeA, nodeB] : collisions) {
|
for (const auto& [nodeA, nodeB] : collisions) {
|
||||||
if (auto boxA = dynamic_cast<PhysicsNode *>(nodeA)) {
|
if (auto boxA = dynamic_cast<PhysicsNode*>(nodeA)) {
|
||||||
boxA->setColliding(true);
|
boxA->setColliding(true);
|
||||||
}
|
}
|
||||||
if (auto boxB = dynamic_cast<PhysicsNode *>(nodeB)) {
|
if (auto boxB = dynamic_cast<PhysicsNode*>(nodeB)) {
|
||||||
boxB->setColliding(true);
|
boxB->setColliding(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## 注意事项
|
void toggleSpatialStrategy() {
|
||||||
|
auto& spatialManager = getSpatialManager();
|
||||||
|
SpatialStrategy currentStrategy = spatialManager.getCurrentStrategy();
|
||||||
|
|
||||||
### 必须调用 Scene::onEnter()
|
if (currentStrategy == SpatialStrategy::QuadTree) {
|
||||||
|
spatialManager.setStrategy(SpatialStrategy::SpatialHash);
|
||||||
```cpp
|
E2D_LOG_INFO("切换到空间哈希策略");
|
||||||
void onEnter() override {
|
} else {
|
||||||
Scene::onEnter(); // 必须调用!
|
spatialManager.setStrategy(SpatialStrategy::QuadTree);
|
||||||
|
E2D_LOG_INFO("切换到四叉树策略");
|
||||||
// 否则子节点无法注册到空间索引
|
|
||||||
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_);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 关键要点
|
||||||
|
|
||||||
|
1. **必须调用 `setSpatialIndexed(true)`** - 启用节点的空间索引
|
||||||
|
2. **必须实现 `getBoundingBox()`** - 返回准确的边界框
|
||||||
|
3. **在 `onEnter()` 中调用 `Scene::onEnter()`** - 确保节点正确注册到空间索引
|
||||||
|
4. **使用 `queryCollisions()`** - 自动利用空间索引优化检测
|
||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
- [07. UI 系统](07_UI_System.md)
|
- [07. UI 系统](./07_UI_System.md) - 学习 UI 控件使用
|
||||||
- [08. 音频系统](08_Audio_System.md)
|
- [08. 音频系统](./08_Audio_System.md) - 学习音频播放
|
||||||
|
|
|
||||||
|
|
@ -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
|
```cpp
|
||||||
// 创建按钮
|
auto& resources = Application::instance().resources();
|
||||||
|
auto font = resources.loadFont("assets/font.ttf", 24);
|
||||||
|
|
||||||
|
// 方式1:简单创建
|
||||||
auto button = Button::create();
|
auto button = Button::create();
|
||||||
|
button->setText("点击我");
|
||||||
|
button->setFont(font);
|
||||||
|
button->setPosition(Vec2(400, 300));
|
||||||
|
|
||||||
// 设置位置
|
// 方式2:链式调用创建
|
||||||
button->setPosition(Vec2(640.0f, 360.0f));
|
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);
|
addChild(button);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 设置按钮文本
|
### 按钮属性设置
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// 加载字体
|
// 文本和字体
|
||||||
auto font = resources.loadFont("assets/font.ttf", 28, true);
|
button->setText("新文本");
|
||||||
|
|
||||||
// 设置按钮字体和文本
|
|
||||||
button->setFont(font);
|
button->setFont(font);
|
||||||
button->setText("点击我");
|
button->setTextColor(Colors::White);
|
||||||
button->setTextColor(Colors::Black);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 设置按钮样式
|
// 尺寸和内边距
|
||||||
|
button->setCustomSize(200.0f, 60.0f);
|
||||||
|
button->setPadding(Vec2(10.0f, 5.0f));
|
||||||
|
|
||||||
```cpp
|
// 背景颜色(正常、悬停、按下三种状态)
|
||||||
// 设置背景颜色(普通、悬停、按下)
|
|
||||||
button->setBackgroundColor(
|
button->setBackgroundColor(
|
||||||
Colors::White, // 普通状态
|
Colors::Blue, // 正常状态
|
||||||
Colors::LightGray, // 悬停状态
|
Colors::Green, // 悬停状态
|
||||||
Colors::Gray // 按下状态
|
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
|
```cpp
|
||||||
// 创建透明按钮(仅文本可点击)
|
enum class ImageScaleMode {
|
||||||
auto button = Button::create();
|
Original, // 使用原图大小
|
||||||
button->setFont(font);
|
Stretch, // 拉伸填充
|
||||||
button->setText("菜单项");
|
ScaleFit, // 等比缩放,保持完整显示
|
||||||
button->setTextColor(Colors::Black);
|
ScaleFill // 等比缩放,填充整个区域(可能裁剪)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
// 透明背景
|
### 透明按钮(菜单项)
|
||||||
button->setBackgroundColor(
|
|
||||||
Colors::Transparent,
|
```cpp
|
||||||
Colors::Transparent,
|
// 创建纯文本按钮(透明背景,用于菜单)
|
||||||
Colors::Transparent
|
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->setPadding(Vec2(0.0f, 0.0f));
|
||||||
button->setBorder(Colors::Transparent, 0.0f);
|
menuBtn->setCustomSize(200.0f, 40.0f);
|
||||||
|
menuBtn->setAnchor(0.5f, 0.5f); // 中心锚点
|
||||||
// 无内边距
|
menuBtn->setPosition(centerX, centerY);
|
||||||
button->setPadding(Vec2(0.0f, 0.0f));
|
addChild(menuBtn);
|
||||||
```
|
```
|
||||||
|
|
||||||
## 菜单系统
|
## 文本(Text)
|
||||||
|
|
||||||
### 创建菜单
|
### 创建文本
|
||||||
|
|
||||||
```cpp
|
```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:
|
public:
|
||||||
void onEnter() override {
|
void onEnter() override {
|
||||||
Scene::onEnter();
|
Scene::onEnter();
|
||||||
|
|
||||||
auto &resources = Application::instance().resources();
|
auto& resources = Application::instance().resources();
|
||||||
font_ = resources.loadFont("assets/font.ttf", 28, true);
|
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);
|
auto soundLabel = Label::create("音效", font_);
|
||||||
createMenuButton("继续游戏", centerX, startY + 50.0f, 1);
|
soundLabel->setPosition(Vec2(200, 200));
|
||||||
createMenuButton("退出", centerX, startY + 100.0f, 2);
|
addChild(soundLabel);
|
||||||
|
|
||||||
menuCount_ = 3;
|
soundCheck_ = CheckBox::create();
|
||||||
selectedIndex_ = 0;
|
soundCheck_->setPosition(Vec2(350, 200));
|
||||||
updateMenuColors();
|
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)) {
|
auto difficultyLabel = Label::create("难度", font_);
|
||||||
selectedIndex_ = (selectedIndex_ - 1 + menuCount_) % menuCount_;
|
difficultyLabel->setPosition(Vec2(200, 360));
|
||||||
updateMenuColors();
|
addChild(difficultyLabel);
|
||||||
} else if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_DOWN)) {
|
|
||||||
selectedIndex_ = (selectedIndex_ + 1) % menuCount_;
|
|
||||||
updateMenuColors();
|
|
||||||
}
|
|
||||||
|
|
||||||
// A键确认
|
auto easyRadio = RadioButton::create("简单");
|
||||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_A)) {
|
easyRadio->setPosition(Vec2(350, 360));
|
||||||
executeMenuItem();
|
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:
|
private:
|
||||||
Ptr<FontAtlas> font_;
|
Ptr<FontAtlas> font_;
|
||||||
std::vector<Ptr<Button>> buttons_;
|
Ptr<CheckBox> soundCheck_;
|
||||||
int selectedIndex_ = 0;
|
Ptr<Slider> volumeSlider_;
|
||||||
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();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## 绘制文字
|
## 最佳实践
|
||||||
|
|
||||||
### 基本文字绘制
|
1. **使用屏幕空间** - UI 控件通常使用 `CoordinateSpace::Screen` 固定在屏幕上
|
||||||
|
2. **使用链式调用** - 创建控件时优先使用链式调用,代码更简洁
|
||||||
```cpp
|
3. **设置合适的锚点** - 使用锚点(0.5, 0.5)让控件中心对齐,方便布局
|
||||||
void onRender(RenderBackend &renderer) override {
|
4. **复用字体资源** - 避免重复加载相同字体
|
||||||
Scene::onRender(renderer);
|
5. **使用回调函数** - 使用 `setOnClick`、`setOnValueChange` 等回调响应用户操作
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
- [08. 音频系统](08_Audio_System.md)
|
- [08. 音频系统](./08_Audio_System.md) - 学习音频播放
|
||||||
|
|
|
||||||
|
|
@ -1,323 +1,279 @@
|
||||||
# Extra2D API 教程 - 08. 音频系统
|
# 08. 音频系统
|
||||||
|
|
||||||
## 音频系统概述
|
Extra2D 提供了基于 SDL2_mixer 的音频播放系统,支持音效播放。
|
||||||
|
|
||||||
Extra2D 使用 SDL2_mixer 作为音频后端,支持 WAV、MP3、OGG 等格式。
|
## 音频引擎
|
||||||
|
|
||||||
## 音效播放
|
通过 `Application::instance().audio()` 访问音频引擎:
|
||||||
|
|
||||||
### 加载和播放音效
|
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// 获取资源管理器
|
auto& audio = Application::instance().audio();
|
||||||
auto &resources = Application::instance().resources();
|
```
|
||||||
|
|
||||||
|
## 播放音效
|
||||||
|
|
||||||
|
### 基本用法
|
||||||
|
|
||||||
|
```cpp
|
||||||
// 加载音效
|
// 加载音效
|
||||||
auto jumpSound = resources.loadSound("assets/jump.wav");
|
auto sound = audio.loadSound("assets/audio/jump.wav");
|
||||||
auto attackSound = resources::loadSound("assets/attack.ogg");
|
|
||||||
|
|
||||||
// 播放音效(一次)
|
// 播放音效
|
||||||
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
|
```cpp
|
||||||
class AudioController : public Node {
|
// 设置音调(当前实现不支持)
|
||||||
public:
|
sound->setPitch(1.0f);
|
||||||
static Ptr<AudioController> create() {
|
|
||||||
return makePtr<AudioController>();
|
|
||||||
}
|
|
||||||
|
|
||||||
void onEnter() override {
|
// 获取/设置播放位置(当前实现不支持)
|
||||||
Node::onEnter();
|
float cursor = sound->getCursor();
|
||||||
|
sound->setCursor(0.0f);
|
||||||
|
|
||||||
auto &resources = Application::instance().resources();
|
// 获取音频时长(当前实现不支持)
|
||||||
|
float duration = sound->getDuration();
|
||||||
// 加载音效
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 在场景中使用
|
## 全局音量控制
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
class GameScene : public Scene {
|
// 设置主音量
|
||||||
public:
|
audio.setMasterVolume(0.8f);
|
||||||
void onEnter() override {
|
|
||||||
Scene::onEnter();
|
|
||||||
|
|
||||||
// 创建音频控制器
|
// 获取主音量
|
||||||
auto audio = AudioController::create();
|
float volume = audio.getMasterVolume();
|
||||||
audio->setName("audio_controller");
|
```
|
||||||
addChild(audio);
|
|
||||||
setAudioController(audio);
|
|
||||||
}
|
|
||||||
|
|
||||||
void playJumpSound() {
|
## 全局播放控制
|
||||||
if (auto audio = getAudioController()) {
|
|
||||||
audio->playSound("jump");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void playAttackSound() {
|
```cpp
|
||||||
if (auto audio = getAudioController()) {
|
// 暂停所有音效
|
||||||
audio->playSound("attack");
|
audio.pauseAll();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void toggleSound() {
|
// 恢复所有音效
|
||||||
if (auto audio = getAudioController()) {
|
audio.resumeAll();
|
||||||
audio->setEnabled(!audio->isEnabled());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
```cpp
|
||||||
// AudioController.h
|
// audio_manager.h
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/extra2d.h>
|
#include <extra2d/extra2d.h>
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace game {
|
namespace pushbox {
|
||||||
|
|
||||||
class AudioController : public extra2d::Node {
|
class AudioManager {
|
||||||
public:
|
public:
|
||||||
static extra2d::Ptr<AudioController> create();
|
static AudioManager& instance() {
|
||||||
|
static AudioManager instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
void onEnter() override;
|
void init();
|
||||||
void playSound(const std::string &name);
|
|
||||||
void setEnabled(bool enabled);
|
void setEnabled(bool enabled);
|
||||||
bool isEnabled() const { return enabled_; }
|
void playMoveSound();
|
||||||
|
void playBoxMoveSound();
|
||||||
|
void playWinSound();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string, extra2d::Ptr<extra2d::Sound>> sounds_;
|
AudioManager() = default;
|
||||||
|
|
||||||
bool enabled_ = true;
|
bool enabled_ = true;
|
||||||
|
extra2d::Ptr<extra2d::Sound> moveSound_;
|
||||||
|
extra2d::Ptr<extra2d::Sound> boxMoveSound_;
|
||||||
|
extra2d::Ptr<extra2d::Sound> winSound_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace game
|
} // namespace pushbox
|
||||||
|
|
||||||
// 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 音频格式支持
|
|
||||||
|
|
||||||
| 格式 | 支持 |
|
|
||||||
|------|------|
|
|
||||||
| WAV | ✓ |
|
|
||||||
| MP3 | ✓ |
|
|
||||||
| OGG | ✓ |
|
|
||||||
| FLAC | ✓ |
|
|
||||||
| MOD | ✓ |
|
|
||||||
|
|
||||||
## 音量控制
|
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// 设置音效音量 (0-128)
|
// audio_manager.cpp
|
||||||
Mix_Volume(-1, MIX_MAX_VOLUME / 2); // 所有音效
|
#include "audio_manager.h"
|
||||||
Mix_Volume(channel, volume); // 指定通道
|
|
||||||
|
|
||||||
// 设置音乐音量 (0-128)
|
namespace pushbox {
|
||||||
Mix_VolumeMusic(volume);
|
|
||||||
|
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()` 中加载所有需要的音效
|
1. **使用单例管理器** - 集中管理音频资源
|
||||||
2. **使用音频控制器**: 统一管理音效,方便控制开关
|
2. **预加载常用音效** - 在初始化时加载
|
||||||
3. **音效开关**: 提供用户选项控制音效开关
|
3. **提供开关选项** - 让用户控制音效
|
||||||
4. **资源释放**: 音效资源会自动管理,无需手动释放
|
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
|
1. [快速开始](./01_Quick_Start.md) - 引擎基础
|
||||||
// 加载
|
2. [场景系统](./02_Scene_System.md) - 场景管理
|
||||||
auto sound = resources.loadSound("assets/sound.wav");
|
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)
|
|
||||||
|
|
|
||||||
|
|
@ -1,327 +1,218 @@
|
||||||
# Extra2D 构建系统文档
|
# Extra2D 构建系统文档
|
||||||
|
|
||||||
## 概述
|
本文档详细介绍 Extra2D 引擎的构建系统配置和使用方法。
|
||||||
|
|
||||||
Extra2D 使用 **Xmake** 作为构建系统,支持 **MinGW (Windows)** 和 **Nintendo Switch** 两个平台。
|
## 构建工具
|
||||||
|
|
||||||
## 项目结构
|
Extra2D 使用 [xmake](https://xmake.io/) 作为构建工具,支持多平台构建。
|
||||||
|
|
||||||
```
|
### 安装 xmake
|
||||||
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
|
```bash
|
||||||
# 默认配置 (MinGW)
|
# Windows (使用 PowerShell)
|
||||||
xmake f -c
|
Invoke-Expression (Invoke-WebRequest 'https://xmake.io/psget.text' -UseBasicParsing).Content
|
||||||
|
|
||||||
# 指定平台 (使用 -p 参数)
|
# macOS
|
||||||
xmake f -c -p mingw
|
brew install xmake
|
||||||
xmake f -c -p switch
|
|
||||||
|
|
||||||
# 指定 MinGW 路径(如果不在默认位置)
|
# Linux
|
||||||
xmake f -c -p mingw --mingw=C:\mingw
|
sudo add-apt-repository ppa:xmake-io/xmake
|
||||||
|
sudo apt update
|
||||||
# 禁用示例
|
sudo apt install xmake
|
||||||
xmake f --examples=n
|
|
||||||
|
|
||||||
# 启用调试日志
|
|
||||||
xmake f --debug_logs=y
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 安装依赖 (MinGW)
|
## 平台支持
|
||||||
|
|
||||||
|
| 平台 | 目标 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| Windows | `mingw` | MinGW-w64 工具链 |
|
||||||
|
| Nintendo Switch | `switch` | devkitPro 工具链 |
|
||||||
|
|
||||||
|
## 构建配置
|
||||||
|
|
||||||
|
### Windows (MinGW)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# 配置构建
|
||||||
|
xmake f -p mingw --mode=release
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
xmake require -y
|
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
|
```bash
|
||||||
# 构建所有目标
|
# 构建所有目标
|
||||||
xmake
|
xmake
|
||||||
|
|
||||||
# 构建特定目标
|
# 构建特定目标
|
||||||
xmake -r extra2d
|
xmake -t extra2d # 引擎库
|
||||||
xmake -r push_box
|
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/
|
Extra2D/
|
||||||
├── examples/
|
├── xmake.lua # 主构建配置
|
||||||
│ ├── hello_world/
|
├── xmake/
|
||||||
│ │ ├── hello_world.exe # MinGW
|
│ ├── engine.lua # 引擎构建规则
|
||||||
│ │ ├── hello_world.nro # Switch
|
│ └── toolchains/ # 工具链定义
|
||||||
│ │ └── assets/ # 资源文件
|
├── Extra2D/ # 引擎源码
|
||||||
│ ├── push_box/
|
│ ├── include/ # 头文件
|
||||||
│ ├── collision_demo/
|
│ └── src/ # 源文件
|
||||||
│ └── spatial_index_demo/
|
└── examples/ # 示例程序
|
||||||
└── ...
|
├── hello_world/
|
||||||
|
├── push_box/
|
||||||
|
├── collision_demo/
|
||||||
|
└── spatial_index_demo/
|
||||||
```
|
```
|
||||||
|
|
||||||
## 关键设计决策
|
## 添加新示例
|
||||||
|
|
||||||
### 1. 平台检测
|
创建新的示例程序:
|
||||||
- 使用 `is_plat()` 而不是手动检测,确保与主项目一致
|
|
||||||
- 示例脚本继承主项目的平台配置
|
|
||||||
|
|
||||||
### 2. 资源处理
|
1. 在 `examples/` 下创建目录
|
||||||
- **Switch**: 使用 romfs 嵌入 NRO 文件
|
2. 添加 `main.cpp` 和 `xmake.lua`
|
||||||
- **MinGW**: 构建后复制到输出目录
|
|
||||||
|
|
||||||
### 3. 依赖管理
|
### 示例 xmake.lua
|
||||||
- **MinGW**: 使用 Xmake 包管理器 (`add_requires`)
|
|
||||||
- **Switch**: 使用 devkitPro 提供的库
|
|
||||||
|
|
||||||
### 4. 工具链隔离
|
```lua
|
||||||
- Switch 工具链定义在单独文件中
|
-- examples/my_demo/xmake.lua
|
||||||
- 通过 `set_toolchains("switch")` 切换
|
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
|
```bash
|
||||||
xmake repo -u
|
# 强制重新安装依赖
|
||||||
xmake require -y
|
xmake require -f -y
|
||||||
|
|
||||||
|
# 清理构建缓存
|
||||||
|
xmake clean
|
||||||
|
xmake f -c
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Switch 工具链找不到
|
### Switch 构建失败
|
||||||
- 确保 DEVKITPRO 环境变量设置正确
|
|
||||||
- 默认路径: `C:/devkitPro`
|
|
||||||
|
|
||||||
### 3. 平台配置不匹配
|
确保已安装 devkitPro:
|
||||||
- 使用 `xmake show` 查看当前配置
|
|
||||||
- 使用 `xmake f -c` 重新配置
|
|
||||||
|
|
||||||
### 4. MinGW 路径问题
|
|
||||||
如果 MinGW 安装在非默认位置,使用 `--mingw` 参数指定:
|
|
||||||
```bash
|
```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")`
|
|
||||||
|
|
||||||
### 添加新平台
|
```lua
|
||||||
1. 在 `xmake/toolchains/` 下创建工具链定义
|
-- 添加编译选项
|
||||||
2. 在 `xmake.lua` 中添加平台检测逻辑
|
add_cxxflags("-O3", "-ffast-math")
|
||||||
3. 在 `xmake/engine.lua` 中添加平台配置
|
|
||||||
|
-- 添加宏定义
|
||||||
|
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/)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <extra2d/extra2d.h>
|
#include <extra2d/extra2d.h>
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
|
|
||||||
using namespace extra2d;
|
using namespace extra2d;
|
||||||
|
|
||||||
|
|
@ -73,7 +71,7 @@ public:
|
||||||
centerBox_->setPosition(Vec2(centerX, centerY));
|
centerBox_->setPosition(Vec2(centerX, centerY));
|
||||||
addChild(centerBox_);
|
addChild(centerBox_);
|
||||||
|
|
||||||
// 加载字体
|
// 加载字体并创建UI
|
||||||
loadFonts();
|
loadFonts();
|
||||||
|
|
||||||
E2D_LOG_INFO("创建了 {} 个碰撞框", boxes_.size() + 1);
|
E2D_LOG_INFO("创建了 {} 个碰撞框", boxes_.size() + 1);
|
||||||
|
|
@ -102,6 +100,9 @@ public:
|
||||||
// 执行碰撞检测
|
// 执行碰撞检测
|
||||||
performCollisionDetection();
|
performCollisionDetection();
|
||||||
|
|
||||||
|
// 更新UI文本
|
||||||
|
updateUI();
|
||||||
|
|
||||||
// 检查退出按键
|
// 检查退出按键
|
||||||
auto &input = Application::instance().input();
|
auto &input = Application::instance().input();
|
||||||
if (input.isButtonPressed(GamepadButton::Start)) {
|
if (input.isButtonPressed(GamepadButton::Start)) {
|
||||||
|
|
@ -110,28 +111,60 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onRender(RenderBackend &renderer) override {
|
|
||||||
Scene::onRender(renderer);
|
|
||||||
|
|
||||||
// 绘制说明文字
|
|
||||||
drawUI(renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* @brief 加载字体资源
|
* @brief 加载字体资源并创建UI文本
|
||||||
*/
|
*/
|
||||||
void loadFonts() {
|
void loadFonts() {
|
||||||
auto &resources = Application::instance().resources();
|
auto &resources = Application::instance().resources();
|
||||||
titleFont_ = resources.loadFont("assets/font.ttf", 60, true);
|
titleFont_ = resources.loadFont("assets/font.ttf", 60, true);
|
||||||
infoFont_ = resources.loadFont("assets/font.ttf", 28, true);
|
infoFont_ = resources.loadFont("assets/font.ttf", 28, true);
|
||||||
|
|
||||||
if (!titleFont_) {
|
// 创建标题文本
|
||||||
E2D_LOG_WARN("无法加载标题字体");
|
titleText_ = Text::create("碰撞检测演示", titleFont_);
|
||||||
}
|
titleText_->setPosition(50.0f, 30.0f);
|
||||||
if (!infoFont_) {
|
titleText_->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f));
|
||||||
E2D_LOG_WARN("无法加载信息字体");
|
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_;
|
Ptr<CollisionBox> centerBox_;
|
||||||
std::vector<Ptr<CollisionBox>> boxes_;
|
std::vector<Ptr<CollisionBox>> boxes_;
|
||||||
float rotationAngle_ = 0.0f;
|
float rotationAngle_ = 0.0f;
|
||||||
|
|
@ -229,6 +224,14 @@ private:
|
||||||
// 字体资源
|
// 字体资源
|
||||||
Ptr<FontAtlas> titleFont_;
|
Ptr<FontAtlas> titleFont_;
|
||||||
Ptr<FontAtlas> infoFont_;
|
Ptr<FontAtlas> infoFont_;
|
||||||
|
|
||||||
|
// UI 文本组件
|
||||||
|
Ptr<Text> titleText_;
|
||||||
|
Ptr<Text> descText_;
|
||||||
|
Ptr<Text> collideHintText_;
|
||||||
|
Ptr<Text> collisionText_;
|
||||||
|
Ptr<Text> fpsText_;
|
||||||
|
Ptr<Text> exitHintText_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ target("collision_demo")
|
||||||
set_toolchains("switch")
|
set_toolchains("switch")
|
||||||
set_targetdir("../../build/examples/collision_demo")
|
set_targetdir("../../build/examples/collision_demo")
|
||||||
|
|
||||||
|
-- 构建后生成 NRO 文件
|
||||||
after_build(function (target)
|
after_build(function (target)
|
||||||
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
||||||
local elf_file = target:targetfile()
|
local elf_file = target:targetfile()
|
||||||
|
|
@ -37,6 +38,17 @@ target("collision_demo")
|
||||||
else
|
else
|
||||||
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
|
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
|
||||||
end
|
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
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
@ -46,20 +58,39 @@ target("collision_demo")
|
||||||
set_targetdir("../../build/examples/collision_demo")
|
set_targetdir("../../build/examples/collision_demo")
|
||||||
add_ldflags("-mwindows", {force = true})
|
add_ldflags("-mwindows", {force = true})
|
||||||
|
|
||||||
-- 复制资源
|
-- 复制资源到输出目录
|
||||||
after_build(function (target)
|
after_build(function (target)
|
||||||
local romfs = path.join(example_dir, "romfs")
|
local romfs = path.join(example_dir, "romfs")
|
||||||
if os.isdir(romfs) then
|
if os.isdir(romfs) then
|
||||||
local target_dir = path.directory(target:targetfile())
|
local target_dir = path.directory(target:targetfile())
|
||||||
local assets_dir = path.join(target_dir, "assets")
|
local assets_dir = path.join(target_dir, "assets")
|
||||||
|
|
||||||
|
-- 创建 assets 目录
|
||||||
if not os.isdir(assets_dir) then
|
if not os.isdir(assets_dir) then
|
||||||
os.mkdir(assets_dir)
|
os.mkdir(assets_dir)
|
||||||
end
|
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)
|
print("Copied assets from " .. romfs .. " to " .. assets_dir)
|
||||||
else
|
else
|
||||||
print("Warning: romfs directory not found at " .. romfs)
|
print("Warning: romfs directory not found at " .. romfs)
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
target_end()
|
target_end()
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,40 @@ public:
|
||||||
|
|
||||||
if (!font_) {
|
if (!font_) {
|
||||||
E2D_LOG_ERROR("字体加载失败,文字渲染将不可用!");
|
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:
|
private:
|
||||||
Ptr<FontAtlas> font_; // 字体图集
|
Ptr<FontAtlas> font_; // 字体图集
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ target("hello_world")
|
||||||
set_toolchains("switch")
|
set_toolchains("switch")
|
||||||
set_targetdir("../../build/examples/hello_world")
|
set_targetdir("../../build/examples/hello_world")
|
||||||
|
|
||||||
-- 生成 NRO
|
-- 构建后生成 NRO 文件
|
||||||
after_build(function (target)
|
after_build(function (target)
|
||||||
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
||||||
local elf_file = target:targetfile()
|
local elf_file = target:targetfile()
|
||||||
|
|
@ -38,6 +38,17 @@ target("hello_world")
|
||||||
else
|
else
|
||||||
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
|
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
|
||||||
end
|
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
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
@ -47,20 +58,39 @@ target("hello_world")
|
||||||
set_targetdir("../../build/examples/hello_world")
|
set_targetdir("../../build/examples/hello_world")
|
||||||
add_ldflags("-mwindows", {force = true})
|
add_ldflags("-mwindows", {force = true})
|
||||||
|
|
||||||
-- 复制资源
|
-- 复制资源到输出目录
|
||||||
after_build(function (target)
|
after_build(function (target)
|
||||||
local romfs = path.join(example_dir, "romfs")
|
local romfs = path.join(example_dir, "romfs")
|
||||||
if os.isdir(romfs) then
|
if os.isdir(romfs) then
|
||||||
local target_dir = path.directory(target:targetfile())
|
local target_dir = path.directory(target:targetfile())
|
||||||
local assets_dir = path.join(target_dir, "assets")
|
local assets_dir = path.join(target_dir, "assets")
|
||||||
|
|
||||||
|
-- 创建 assets 目录
|
||||||
if not os.isdir(assets_dir) then
|
if not os.isdir(assets_dir) then
|
||||||
os.mkdir(assets_dir)
|
os.mkdir(assets_dir)
|
||||||
end
|
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)
|
print("Copied assets from " .. romfs .. " to " .. assets_dir)
|
||||||
else
|
else
|
||||||
print("Warning: romfs directory not found at " .. romfs)
|
print("Warning: romfs directory not found at " .. romfs)
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
target_end()
|
target_end()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#include "PlayScene.h"
|
#include "PlayScene.h"
|
||||||
|
|
||||||
#include "audio_context.h"
|
#include "audio_manager.h"
|
||||||
#include "audio_controller.h"
|
|
||||||
#include "storage.h"
|
#include "storage.h"
|
||||||
#include "StartScene.h"
|
#include "StartScene.h"
|
||||||
#include "SuccessScene.h"
|
#include "SuccessScene.h"
|
||||||
|
|
@ -93,11 +92,6 @@ PlayScene::PlayScene(int level) {
|
||||||
mapLayer_->setPosition(0.0f, 0.0f);
|
mapLayer_->setPosition(0.0f, 0.0f);
|
||||||
addChild(mapLayer_);
|
addChild(mapLayer_);
|
||||||
|
|
||||||
auto audioNode = AudioController::create();
|
|
||||||
audioNode->setName("AudioController");
|
|
||||||
addChild(audioNode);
|
|
||||||
setAudioController(audioNode);
|
|
||||||
|
|
||||||
setLevel(level);
|
setLevel(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,45 +118,43 @@ void PlayScene::onUpdate(float dt) {
|
||||||
auto& input = app.input();
|
auto& input = app.input();
|
||||||
|
|
||||||
// B 键返回主菜单
|
// B 键返回主菜单
|
||||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_B)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::B)) {
|
||||||
app.scenes().replaceScene(
|
app.scenes().replaceScene(
|
||||||
extra2d::makePtr<StartScene>(), extra2d::TransitionType::Fade, 0.2f);
|
extra2d::makePtr<StartScene>(), extra2d::TransitionType::Fade, 0.2f);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Y 键重开
|
// Y 键重开
|
||||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_Y)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::Y)) {
|
||||||
setLevel(g_CurrentLevel);
|
setLevel(g_CurrentLevel);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// X 键直接切换音效
|
// X键直接切换音效
|
||||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_X)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::X)) {
|
||||||
g_SoundOpen = !g_SoundOpen;
|
g_SoundOpen = !g_SoundOpen;
|
||||||
if (auto audio = getAudioController()) {
|
AudioManager::instance().setEnabled(g_SoundOpen);
|
||||||
audio->setEnabled(g_SoundOpen);
|
|
||||||
}
|
|
||||||
updateSoundIcon();
|
updateSoundIcon();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A 键执行选中的菜单项
|
// A 键执行选中的菜单项
|
||||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_A)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
|
||||||
executeMenuItem();
|
executeMenuItem();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 方向键移动
|
// 方向键移动
|
||||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_UP)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::DPadUp)) {
|
||||||
move(0, -1, 1);
|
move(0, -1, 1);
|
||||||
flush();
|
flush();
|
||||||
} else if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_DOWN)) {
|
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadDown)) {
|
||||||
move(0, 1, 2);
|
move(0, 1, 2);
|
||||||
flush();
|
flush();
|
||||||
} else if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_LEFT)) {
|
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadLeft)) {
|
||||||
move(-1, 0, 3);
|
move(-1, 0, 3);
|
||||||
flush();
|
flush();
|
||||||
} else if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_RIGHT)) {
|
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadRight)) {
|
||||||
move(1, 0, 4);
|
move(1, 0, 4);
|
||||||
flush();
|
flush();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -189,9 +181,7 @@ void PlayScene::executeMenuItem() {
|
||||||
break;
|
break;
|
||||||
case 1: // 切换音效
|
case 1: // 切换音效
|
||||||
g_SoundOpen = !g_SoundOpen;
|
g_SoundOpen = !g_SoundOpen;
|
||||||
if (auto audio = getAudioController()) {
|
AudioManager::instance().setEnabled(g_SoundOpen);
|
||||||
audio->setEnabled(g_SoundOpen);
|
|
||||||
}
|
|
||||||
updateSoundIcon();
|
updateSoundIcon();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -329,9 +319,7 @@ void PlayScene::move(int dx, int dy, int direct) {
|
||||||
g_Pushing = false;
|
g_Pushing = false;
|
||||||
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
|
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
|
||||||
map_.value[targetY][targetX].type = TYPE::Man;
|
map_.value[targetY][targetX].type = TYPE::Man;
|
||||||
if (auto audio = getAudioController()) {
|
AudioManager::instance().playManMove();
|
||||||
audio->playManMove();
|
|
||||||
}
|
|
||||||
} else if (map_.value[targetY][targetX].type == TYPE::Box) {
|
} else if (map_.value[targetY][targetX].type == TYPE::Box) {
|
||||||
g_Pushing = true;
|
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[targetY][targetX].type = TYPE::Man;
|
||||||
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
|
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
|
||||||
|
|
||||||
if (auto audio = getAudioController()) {
|
AudioManager::instance().playBoxMove();
|
||||||
audio->playBoxMove();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#include "StartScene.h"
|
#include "StartScene.h"
|
||||||
|
|
||||||
#include "audio_context.h"
|
#include "audio_manager.h"
|
||||||
#include "audio_controller.h"
|
|
||||||
#include "data.h"
|
#include "data.h"
|
||||||
#include "PlayScene.h"
|
#include "PlayScene.h"
|
||||||
#include <extra2d/extra2d.h>
|
#include <extra2d/extra2d.h>
|
||||||
|
|
@ -28,10 +27,6 @@ void StartScene::onEnter() {
|
||||||
setBackgroundColor(extra2d::Colors::Black);
|
setBackgroundColor(extra2d::Colors::Black);
|
||||||
|
|
||||||
if (getChildren().empty()) {
|
if (getChildren().empty()) {
|
||||||
auto audioNode = AudioController::create();
|
|
||||||
audioNode->setName("audio_controller");
|
|
||||||
addChild(audioNode);
|
|
||||||
setAudioController(audioNode);
|
|
||||||
|
|
||||||
float screenW = static_cast<float>(app.getConfig().width);
|
float screenW = static_cast<float>(app.getConfig().width);
|
||||||
float screenH = static_cast<float>(app.getConfig().height);
|
float screenH = static_cast<float>(app.getConfig().height);
|
||||||
|
|
@ -138,9 +133,7 @@ void StartScene::onUpdate(float dt) {
|
||||||
// X键切换音效
|
// X键切换音效
|
||||||
if (input.isButtonPressed(extra2d::GamepadButton::X)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::X)) {
|
||||||
g_SoundOpen = !g_SoundOpen;
|
g_SoundOpen = !g_SoundOpen;
|
||||||
if (auto audio = getAudioController()) {
|
AudioManager::instance().setEnabled(g_SoundOpen);
|
||||||
audio->setEnabled(g_SoundOpen);
|
|
||||||
}
|
|
||||||
updateSoundIcon();
|
updateSoundIcon();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ void SuccessScene::onUpdate(float dt) {
|
||||||
auto& input = app.input();
|
auto& input = app.input();
|
||||||
|
|
||||||
// A键确认返回主菜单
|
// A键确认返回主菜单
|
||||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_A)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
|
||||||
auto& scenes = extra2d::Application::instance().scenes();
|
auto& scenes = extra2d::Application::instance().scenes();
|
||||||
scenes.popScene(extra2d::TransitionType::Fade, 0.2f);
|
scenes.popScene(extra2d::TransitionType::Fade, 0.2f);
|
||||||
scenes.popScene(extra2d::TransitionType::Fade, 0.2f);
|
scenes.popScene(extra2d::TransitionType::Fade, 0.2f);
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#include "StartScene.h"
|
#include "StartScene.h"
|
||||||
#include "data.h"
|
#include "data.h"
|
||||||
#include "storage.h"
|
#include "storage.h"
|
||||||
|
#include "audio_manager.h"
|
||||||
|
|
||||||
using namespace extra2d;
|
using namespace extra2d;
|
||||||
|
|
||||||
|
|
@ -40,6 +41,9 @@ int main(int argc, char **argv)
|
||||||
}
|
}
|
||||||
pushbox::g_SoundOpen = pushbox::loadSoundOpen(true);
|
pushbox::g_SoundOpen = pushbox::loadSoundOpen(true);
|
||||||
|
|
||||||
|
// 初始化全局音频管理器
|
||||||
|
pushbox::AudioManager::instance().init();
|
||||||
|
|
||||||
// 进入开始场景(主界面)
|
// 进入开始场景(主界面)
|
||||||
app.enterScene(makePtr<pushbox::StartScene>());
|
app.enterScene(makePtr<pushbox::StartScene>());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -20,6 +20,7 @@ target("push_box")
|
||||||
set_toolchains("switch")
|
set_toolchains("switch")
|
||||||
set_targetdir("../../build/examples/push_box")
|
set_targetdir("../../build/examples/push_box")
|
||||||
|
|
||||||
|
-- 构建后生成 NRO 文件
|
||||||
after_build(function (target)
|
after_build(function (target)
|
||||||
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
||||||
local elf_file = target:targetfile()
|
local elf_file = target:targetfile()
|
||||||
|
|
@ -37,6 +38,17 @@ target("push_box")
|
||||||
else
|
else
|
||||||
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
|
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
|
||||||
end
|
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
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
@ -46,20 +58,39 @@ target("push_box")
|
||||||
set_targetdir("../../build/examples/push_box")
|
set_targetdir("../../build/examples/push_box")
|
||||||
add_ldflags("-mwindows", {force = true})
|
add_ldflags("-mwindows", {force = true})
|
||||||
|
|
||||||
-- 复制资源
|
-- 复制资源到输出目录
|
||||||
after_build(function (target)
|
after_build(function (target)
|
||||||
local romfs = path.join(example_dir, "romfs")
|
local romfs = path.join(example_dir, "romfs")
|
||||||
if os.isdir(romfs) then
|
if os.isdir(romfs) then
|
||||||
local target_dir = path.directory(target:targetfile())
|
local target_dir = path.directory(target:targetfile())
|
||||||
local assets_dir = path.join(target_dir, "assets")
|
local assets_dir = path.join(target_dir, "assets")
|
||||||
|
|
||||||
|
-- 创建 assets 目录
|
||||||
if not os.isdir(assets_dir) then
|
if not os.isdir(assets_dir) then
|
||||||
os.mkdir(assets_dir)
|
os.mkdir(assets_dir)
|
||||||
end
|
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)
|
print("Copied assets from " .. romfs .. " to " .. assets_dir)
|
||||||
else
|
else
|
||||||
print("Warning: romfs directory not found at " .. romfs)
|
print("Warning: romfs directory not found at " .. romfs)
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
target_end()
|
target_end()
|
||||||
|
|
|
||||||
|
|
@ -178,30 +178,135 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void onRender(RenderBackend &renderer) override {
|
void onRender(RenderBackend &renderer) override {
|
||||||
Scene::onRender(renderer);
|
|
||||||
|
|
||||||
auto renderStart = std::chrono::high_resolution_clock::now();
|
auto renderStart = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
// 节点渲染由Scene自动处理
|
Scene::onRender(renderer);
|
||||||
|
|
||||||
auto renderEnd = std::chrono::high_resolution_clock::now();
|
auto renderEnd = std::chrono::high_resolution_clock::now();
|
||||||
stats_.renderTime =
|
stats_.renderTime =
|
||||||
std::chrono::duration<double, std::milli>(renderEnd - renderStart)
|
std::chrono::duration<double, std::milli>(renderEnd - renderStart)
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
// 绘制UI
|
// 更新UI文本内容
|
||||||
drawUI(renderer);
|
updateUI();
|
||||||
|
|
||||||
|
// 绘制图例方块(Text组件会自动渲染)
|
||||||
|
drawLegend(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* @brief 加载字体资源
|
* @brief 加载字体资源并创建UI文本组件
|
||||||
*/
|
*/
|
||||||
void loadFonts() {
|
void loadFonts() {
|
||||||
auto &resources = Application::instance().resources();
|
auto &resources = Application::instance().resources();
|
||||||
|
|
||||||
titleFont_ = resources.loadFont("assets/font.ttf", 28, true);
|
titleFont_ = resources.loadFont("assets/font.ttf", 28, true);
|
||||||
infoFont_ = resources.loadFont("assets/font.ttf", 16, 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) {
|
void updateUI() {
|
||||||
if (!titleFont_ || !infoFont_)
|
if (!nodeCountText_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto &app = Application::instance();
|
auto &app = Application::instance();
|
||||||
|
|
||||||
// 绘制标题
|
// 使用 setFormat 格式化文本
|
||||||
renderer.drawText(*titleFont_, "引擎空间索引演示", Vec2(30.0f, 20.0f),
|
nodeCountText_->setFormat("节点数量: %zu", stats_.nodeCount);
|
||||||
Color(1.0f, 1.0f, 1.0f, 1.0f));
|
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;
|
* @brief 绘制图例方块
|
||||||
float x = 30.0f;
|
*/
|
||||||
float y = 60.0f;
|
void drawLegend(RenderBackend &renderer) {
|
||||||
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));
|
|
||||||
|
|
||||||
// 绘制图例
|
|
||||||
float legendX = screenWidth_ - 200.0f;
|
float legendX = screenWidth_ - 200.0f;
|
||||||
float legendY = 20.0f;
|
float legendY = 20.0f + 25.0f; // 在标题下方
|
||||||
renderer.drawText(*infoFont_, "图例:", Vec2(legendX, legendY),
|
|
||||||
Color(1.0f, 1.0f, 1.0f, 1.0f));
|
|
||||||
legendY += 25.0f;
|
|
||||||
|
|
||||||
|
// 绘制正常状态方块
|
||||||
renderer.fillRect(Rect(legendX, legendY, 15.0f, 15.0f),
|
renderer.fillRect(Rect(legendX, legendY, 15.0f, 15.0f),
|
||||||
Color(0.5f, 0.5f, 0.9f, 0.7f));
|
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;
|
legendY += 25.0f;
|
||||||
|
|
||||||
|
// 绘制碰撞状态方块
|
||||||
renderer.fillRect(Rect(legendX, legendY, 15.0f, 15.0f),
|
renderer.fillRect(Rect(legendX, legendY, 15.0f, 15.0f),
|
||||||
Color(1.0f, 0.2f, 0.2f, 0.9f));
|
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_;
|
std::vector<Ptr<PhysicsNode>> nodes_;
|
||||||
|
|
@ -404,6 +449,24 @@ private:
|
||||||
|
|
||||||
Ptr<FontAtlas> titleFont_;
|
Ptr<FontAtlas> titleFont_;
|
||||||
Ptr<FontAtlas> infoFont_;
|
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_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ target("spatial_index_demo")
|
||||||
set_toolchains("switch")
|
set_toolchains("switch")
|
||||||
set_targetdir("../../build/examples/spatial_index_demo")
|
set_targetdir("../../build/examples/spatial_index_demo")
|
||||||
|
|
||||||
|
-- 构建后生成 NRO 文件
|
||||||
after_build(function (target)
|
after_build(function (target)
|
||||||
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
||||||
local elf_file = target:targetfile()
|
local elf_file = target:targetfile()
|
||||||
|
|
@ -37,6 +38,17 @@ target("spatial_index_demo")
|
||||||
else
|
else
|
||||||
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
|
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
|
||||||
end
|
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
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
@ -46,20 +58,39 @@ target("spatial_index_demo")
|
||||||
set_targetdir("../../build/examples/spatial_index_demo")
|
set_targetdir("../../build/examples/spatial_index_demo")
|
||||||
add_ldflags("-mwindows", {force = true})
|
add_ldflags("-mwindows", {force = true})
|
||||||
|
|
||||||
-- 复制资源
|
-- 复制资源到输出目录
|
||||||
after_build(function (target)
|
after_build(function (target)
|
||||||
local romfs = path.join(example_dir, "romfs")
|
local romfs = path.join(example_dir, "romfs")
|
||||||
if os.isdir(romfs) then
|
if os.isdir(romfs) then
|
||||||
local target_dir = path.directory(target:targetfile())
|
local target_dir = path.directory(target:targetfile())
|
||||||
local assets_dir = path.join(target_dir, "assets")
|
local assets_dir = path.join(target_dir, "assets")
|
||||||
|
|
||||||
|
-- 创建 assets 目录
|
||||||
if not os.isdir(assets_dir) then
|
if not os.isdir(assets_dir) then
|
||||||
os.mkdir(assets_dir)
|
os.mkdir(assets_dir)
|
||||||
end
|
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)
|
print("Copied assets from " .. romfs .. " to " .. assets_dir)
|
||||||
else
|
else
|
||||||
print("Warning: romfs directory not found at " .. romfs)
|
print("Warning: romfs directory not found at " .. romfs)
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
target_end()
|
target_end()
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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_ */
|
|
||||||
|
|
@ -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_*/
|
|
||||||
|
|
||||||
|
|
@ -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_*/
|
|
||||||
|
|
||||||
|
|
@ -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_*/
|
|
||||||
|
|
@ -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_*/
|
|
||||||
|
|
@ -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_ */
|
|
||||||
|
|
@ -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_*/
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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_
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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_*/
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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_
|
|
||||||
|
|
@ -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
|
|
@ -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
Loading…
Reference in New Issue