refactor(ui): 重构UI组件并移除Squirrel脚本支持
重构UI组件系统,将Text组件从scene模块移动到ui模块,并新增CheckBox、RadioButton、Slider等UI控件 移除Squirrel脚本引擎及相关绑定代码,简化项目结构 调整.gitignore文件,添加.trae目录忽略 优化String类实现,使用Windows API进行GBK/UTF-8转换 更新构建配置,移除Squirrel相关依赖
This commit is contained in:
parent
8d086a97e8
commit
d0314447ee
|
|
@ -14,7 +14,7 @@
|
||||||
/x64/
|
/x64/
|
||||||
/x86/
|
/x86/
|
||||||
/.build/
|
/.build/
|
||||||
|
/.trae/
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
# xmake 构建系统
|
# xmake 构建系统
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -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,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>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
#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);
|
||||||
|
|
||||||
|
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 onDraw(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,149 @@
|
||||||
|
#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);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 文本内容
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
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 onDraw(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,218 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/font.h>
|
||||||
|
#include <extra2d/ui/widget.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 进度条组件 - 用于显示进度/百分比
|
||||||
|
// 适用于血条、能量条、加载进度、经验条等游戏场景
|
||||||
|
// ============================================================================
|
||||||
|
class ProgressBar : public Widget {
|
||||||
|
public:
|
||||||
|
ProgressBar();
|
||||||
|
~ProgressBar() override = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 静态创建方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static Ptr<ProgressBar> create();
|
||||||
|
static Ptr<ProgressBar> create(float min, float max, float value);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 数值范围
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setRange(float min, float max);
|
||||||
|
float getMin() const { return min_; }
|
||||||
|
float getMax() const { return max_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 当前值
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setValue(float value);
|
||||||
|
float getValue() const { return value_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 获取百分比 (0.0 - 1.0)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
float getPercent() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 进度条方向
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
enum class Direction { LeftToRight, RightToLeft, BottomToTop, TopToBottom };
|
||||||
|
|
||||||
|
void setDirection(Direction dir);
|
||||||
|
Direction getDirection() const { return direction_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 颜色设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setBackgroundColor(const Color &color);
|
||||||
|
Color getBackgroundColor() const { return bgColor_; }
|
||||||
|
|
||||||
|
void setFillColor(const Color &color);
|
||||||
|
Color getFillColor() const { return fillColor_; }
|
||||||
|
|
||||||
|
// 渐变填充色(从 fillColor_ 到 fillColorEnd_)
|
||||||
|
void setGradientFillEnabled(bool enabled);
|
||||||
|
bool isGradientFillEnabled() const { return gradientEnabled_; }
|
||||||
|
|
||||||
|
void setFillColorEnd(const Color &color);
|
||||||
|
Color getFillColorEnd() const { return fillColorEnd_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 分段颜色(根据百分比自动切换颜色)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setSegmentedColorsEnabled(bool enabled);
|
||||||
|
bool isSegmentedColorsEnabled() const { return segmentedColorsEnabled_; }
|
||||||
|
|
||||||
|
// 设置分段阈值和颜色,例如:>70%绿色, >30%黄色, 其他红色
|
||||||
|
void addColorSegment(float percentThreshold, const Color &color);
|
||||||
|
void clearColorSegments();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 圆角设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setCornerRadius(float radius);
|
||||||
|
float getCornerRadius() const { return cornerRadius_; }
|
||||||
|
|
||||||
|
void setRoundedCornersEnabled(bool enabled);
|
||||||
|
bool isRoundedCornersEnabled() const { return roundedCornersEnabled_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 边框设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setBorderEnabled(bool enabled);
|
||||||
|
bool isBorderEnabled() const { return borderEnabled_; }
|
||||||
|
|
||||||
|
void setBorderColor(const Color &color);
|
||||||
|
Color getBorderColor() const { return borderColor_; }
|
||||||
|
|
||||||
|
void setBorderWidth(float width);
|
||||||
|
float getBorderWidth() const { return borderWidth_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 内边距(填充与边框的距离)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setPadding(float padding);
|
||||||
|
float getPadding() const { return padding_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 文本显示
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setTextEnabled(bool enabled);
|
||||||
|
bool isTextEnabled() const { return textEnabled_; }
|
||||||
|
|
||||||
|
void setFont(Ptr<FontAtlas> font);
|
||||||
|
Ptr<FontAtlas> getFont() const { return font_; }
|
||||||
|
|
||||||
|
void setTextColor(const Color &color);
|
||||||
|
Color getTextColor() const { return textColor_; }
|
||||||
|
|
||||||
|
// 文本格式:"{value}/{max}", "{percent}%", "{value:.1f}" 等
|
||||||
|
void setTextFormat(const 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 onDraw(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,96 @@
|
||||||
|
#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);
|
||||||
|
|
||||||
|
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 onDraw(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,125 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/font.h>
|
||||||
|
#include <extra2d/ui/widget.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 滑动条组件
|
||||||
|
// ============================================================================
|
||||||
|
class Slider : public Widget {
|
||||||
|
public:
|
||||||
|
Slider();
|
||||||
|
~Slider() override = default;
|
||||||
|
|
||||||
|
static Ptr<Slider> create();
|
||||||
|
static Ptr<Slider> create(float min, float max, float value);
|
||||||
|
|
||||||
|
void setRange(float min, float max);
|
||||||
|
float getMin() const { return min_; }
|
||||||
|
float getMax() const { return max_; }
|
||||||
|
|
||||||
|
void setValue(float value);
|
||||||
|
float getValue() const { return value_; }
|
||||||
|
|
||||||
|
void setStep(float step);
|
||||||
|
float getStep() const { return step_; }
|
||||||
|
|
||||||
|
void setVertical(bool vertical);
|
||||||
|
bool isVertical() const { return vertical_; }
|
||||||
|
|
||||||
|
void setTrackSize(float size);
|
||||||
|
float getTrackSize() const { return trackSize_; }
|
||||||
|
|
||||||
|
void setThumbSize(float size);
|
||||||
|
float getThumbSize() const { return thumbSize_; }
|
||||||
|
|
||||||
|
void setTrackColor(const Color &color);
|
||||||
|
Color getTrackColor() const { return trackColor_; }
|
||||||
|
|
||||||
|
void setFillColor(const Color &color);
|
||||||
|
Color getFillColor() const { return fillColor_; }
|
||||||
|
|
||||||
|
void setThumbColor(const Color &color);
|
||||||
|
Color getThumbColor() const { return thumbColor_; }
|
||||||
|
|
||||||
|
void setThumbHoverColor(const Color &color);
|
||||||
|
Color getThumbHoverColor() const { return thumbHoverColor_; }
|
||||||
|
|
||||||
|
void setThumbPressedColor(const Color &color);
|
||||||
|
Color getThumbPressedColor() const { return thumbPressedColor_; }
|
||||||
|
|
||||||
|
void setShowThumb(bool show);
|
||||||
|
bool isShowThumb() const { return showThumb_; }
|
||||||
|
|
||||||
|
void setShowFill(bool show);
|
||||||
|
bool isShowFill() const { return showFill_; }
|
||||||
|
|
||||||
|
void setTextEnabled(bool enabled);
|
||||||
|
bool isTextEnabled() const { return textEnabled_; }
|
||||||
|
|
||||||
|
void setFont(Ptr<FontAtlas> font);
|
||||||
|
Ptr<FontAtlas> getFont() const { return font_; }
|
||||||
|
|
||||||
|
void setTextColor(const Color &color);
|
||||||
|
Color getTextColor() const { return textColor_; }
|
||||||
|
|
||||||
|
void setTextFormat(const 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 onDraw(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
|
||||||
|
|
@ -1,21 +1,28 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
#include <extra2d/core/color.h>
|
||||||
#include <extra2d/core/string.h>
|
#include <extra2d/core/types.h>
|
||||||
#include <extra2d/graphics/font.h>
|
#include <extra2d/graphics/font.h>
|
||||||
#include <extra2d/scene/node.h>
|
#include <extra2d/ui/widget.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 文字节点
|
// 文本组件 - 继承自 Widget 的 UI 组件
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
class Text : public Node {
|
class Text : public Widget {
|
||||||
public:
|
public:
|
||||||
Text();
|
Text();
|
||||||
explicit Text(const String &text);
|
explicit Text(const String &text);
|
||||||
~Text() override = default;
|
~Text() override = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 静态创建方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static Ptr<Text> create();
|
||||||
|
static Ptr<Text> create(const String &text);
|
||||||
|
static Ptr<Text> create(const String &text, Ptr<FontAtlas> font);
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 文字内容
|
// 文字内容
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
@ -45,25 +52,24 @@ public:
|
||||||
void setAlignment(Alignment align);
|
void setAlignment(Alignment align);
|
||||||
Alignment getAlignment() const { return alignment_; }
|
Alignment getAlignment() const { return alignment_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 垂直对齐方式
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
enum class VerticalAlignment { Top, Middle, Bottom };
|
||||||
|
|
||||||
|
void setVerticalAlignment(VerticalAlignment align);
|
||||||
|
VerticalAlignment getVerticalAlignment() const { return verticalAlignment_; }
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 尺寸计算
|
// 尺寸计算
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
Vec2 getTextSize() const;
|
Vec2 getTextSize() const;
|
||||||
float getLineHeight() 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;
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void onDraw(RenderBackend &renderer) override;
|
void onDraw(RenderBackend &renderer) override;
|
||||||
void generateRenderCommand(std::vector<RenderCommand> &commands,
|
|
||||||
int zOrder) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
String text_;
|
String text_;
|
||||||
|
|
@ -71,11 +77,13 @@ private:
|
||||||
Color color_ = Colors::White;
|
Color color_ = Colors::White;
|
||||||
int fontSize_ = 16;
|
int fontSize_ = 16;
|
||||||
Alignment alignment_ = Alignment::Left;
|
Alignment alignment_ = Alignment::Left;
|
||||||
|
VerticalAlignment verticalAlignment_ = VerticalAlignment::Top;
|
||||||
|
|
||||||
mutable Vec2 cachedSize_ = Vec2::Zero();
|
mutable Vec2 cachedSize_ = Vec2::Zero();
|
||||||
mutable bool sizeDirty_ = true;
|
mutable bool sizeDirty_ = true;
|
||||||
|
|
||||||
void updateCache() const;
|
void updateCache() const;
|
||||||
|
Vec2 calculateDrawPosition() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
@ -1,9 +1,25 @@
|
||||||
#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; // 修饰键状态
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Widget 基类 - UI 组件的基础
|
||||||
|
// ============================================================================
|
||||||
class Widget : public Node {
|
class Widget : public Node {
|
||||||
public:
|
public:
|
||||||
Widget();
|
Widget();
|
||||||
|
|
@ -15,8 +31,38 @@ public:
|
||||||
|
|
||||||
Rect getBoundingBox() const override;
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 鼠标事件处理(子类可重写)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Size size_ = Size::Zero();
|
Size size_ = Size::Zero();
|
||||||
|
bool enabled_ = true;
|
||||||
|
bool focused_ = false;
|
||||||
|
bool hovered_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // 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,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,6 +3,7 @@
|
||||||
#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 {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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::onDraw(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,313 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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::onDraw(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,373 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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::onDraw(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,185 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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::onDraw(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,337 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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::onDraw(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
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
#include <extra2d/ui/text.h>
|
||||||
#include <extra2d/graphics/render_backend.h>
|
#include <extra2d/graphics/render_backend.h>
|
||||||
#include <extra2d/graphics/render_command.h>
|
#include <extra2d/core/string.h>
|
||||||
#include <extra2d/scene/text.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -40,6 +40,11 @@ void Text::setAlignment(Alignment align) {
|
||||||
updateSpatialIndex();
|
updateSpatialIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Text::setVerticalAlignment(VerticalAlignment align) {
|
||||||
|
verticalAlignment_ = align;
|
||||||
|
updateSpatialIndex();
|
||||||
|
}
|
||||||
|
|
||||||
Vec2 Text::getTextSize() const {
|
Vec2 Text::getTextSize() const {
|
||||||
updateCache();
|
updateCache();
|
||||||
return cachedSize_;
|
return cachedSize_;
|
||||||
|
|
@ -61,6 +66,44 @@ void Text::updateCache() const {
|
||||||
sizeDirty_ = false;
|
sizeDirty_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vec2 Text::calculateDrawPosition() const {
|
||||||
|
Vec2 pos = getPosition();
|
||||||
|
Vec2 textSize = getTextSize();
|
||||||
|
Size widgetSize = getSize();
|
||||||
|
|
||||||
|
// 如果设置了控件大小,使用控件大小作为对齐参考
|
||||||
|
float refWidth = widgetSize.empty() ? textSize.x : widgetSize.width;
|
||||||
|
float refHeight = widgetSize.empty() ? textSize.y : widgetSize.height;
|
||||||
|
|
||||||
|
// 水平对齐
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 垂直对齐
|
||||||
|
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() { return makePtr<Text>(); }
|
||||||
|
|
||||||
Ptr<Text> Text::create(const String &text) { return makePtr<Text>(text); }
|
Ptr<Text> Text::create(const String &text) { return makePtr<Text>(text); }
|
||||||
|
|
@ -82,16 +125,7 @@ Rect Text::getBoundingBox() const {
|
||||||
return Rect();
|
return Rect();
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec2 pos = getPosition();
|
Vec2 pos = calculateDrawPosition();
|
||||||
|
|
||||||
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);
|
return Rect(pos.x, pos.y, size.x, size.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,46 +134,8 @@ void Text::onDraw(RenderBackend &renderer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec2 pos = getPosition();
|
Vec2 pos = calculateDrawPosition();
|
||||||
|
|
||||||
// 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_);
|
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
|
} // namespace extra2d
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
|
|
||||||
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;
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,9 @@ function define_extra2d_engine()
|
||||||
-- 引擎源文件
|
-- 引擎源文件
|
||||||
add_files("Extra2D/src/**.cpp")
|
add_files("Extra2D/src/**.cpp")
|
||||||
add_files("Extra2D/src/glad/glad.c")
|
add_files("Extra2D/src/glad/glad.c")
|
||||||
add_files("squirrel/squirrel/*.cpp")
|
|
||||||
add_files("squirrel/sqstdlib/*.cpp")
|
|
||||||
|
|
||||||
-- 头文件路径
|
-- 头文件路径
|
||||||
add_includedirs("Extra2D/include", {public = true})
|
add_includedirs("Extra2D/include", {public = true})
|
||||||
add_includedirs("squirrel/include", {public = true})
|
|
||||||
add_includedirs("Extra2D/include/extra2d/platform", {public = true})
|
add_includedirs("Extra2D/include/extra2d/platform", {public = true})
|
||||||
|
|
||||||
-- 平台配置
|
-- 平台配置
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue