149 Commits
ecs ... master

Author SHA1 Message Date
bfad8b571f 11 2025-10-14 09:53:36 +08:00
f8de8be68e 11 2025-10-14 09:51:27 +08:00
a5f2398a62 11 2025-10-13 19:12:14 +08:00
166784e422 注释提交 2025-07-02 18:35:44 +08:00
c725bd8b07 注释 2025-07-02 17:38:38 +08:00
0aea4d2f0a 修复行高不正确 2025-07-02 16:57:44 +08:00
80c0e46629 重构文本布局 2025-07-02 16:26:56 +08:00
2785c336b9 完成config方便获取值
TODO DPI缩放
2025-07-01 21:04:44 +08:00
b77c611752 TODO 配置文件类修改 2025-07-01 18:33:17 +08:00
c7bc99e041 注释整理 2025-07-01 16:41:34 +08:00
38bd206a25 整理代码 2025-07-01 15:58:42 +08:00
28e5d72f9b 光标功能完成 2025-07-01 15:52:05 +08:00
b0f9d92750 修复文本垂直方向对齐问题 2025-07-01 14:34:39 +08:00
b6979749f8 移除旧版本字符串布局函数 2025-07-01 11:44:10 +08:00
5d30162f78 光标第二版 2025-06-30 18:19:35 +08:00
7f3bc18305 添加获取字符位置函数,初版光标位置 2025-06-30 18:13:35 +08:00
0995ef09f2 移除font_layout中的cursor_pos 2025-06-30 17:38:53 +08:00
97cb9c3f18 修复文本y坐标不正确 2025-06-30 17:32:16 +08:00
95a5d7f60e TODO 文本布局部分重构完成 2025-06-30 17:28:24 +08:00
060a18cd68 TODO 重构文本布局 2025-06-30 14:55:33 +08:00
30de05f679 TODO: 重构文本布局部分 2025-06-26 16:01:13 +08:00
193b355e6e 实现键盘事件 2025-06-24 17:40:38 +08:00
3004bf1f00 修复文本编辑框换空行时cursor不正确问题 2025-06-23 18:16:31 +08:00
581e376c5c 更新git过滤规则 2025-06-23 18:07:33 +08:00
c5c60f479a 调整目录结构 2025-06-23 18:03:33 +08:00
c9b4e6dccd 优化代码可读性 2025-06-23 17:55:55 +08:00
3db251aac6 修复文本编辑框换行时大小不正确,优化布局更新性能 2025-06-23 17:40:21 +08:00
d5e25c8eaf 修复文本输入框输入文字后高度错误 2025-06-20 17:06:52 +08:00
0f609e1e9c 测试代码 2025-06-20 10:30:47 +08:00
7a281dee1f 删除shdc 2025-06-19 16:01:57 +08:00
c7d859a9b6 指定slang为行主序 2025-06-19 16:01:19 +08:00
0adc357d8a 修复D3D11着色器加载失败,修复pipeline创建警告 2025-06-19 15:29:53 +08:00
a501d409ff 实现批量编译着色器 2025-06-19 11:11:56 +08:00
d0aa773666 修复没有生成sg_shader_image_sampler_pair 2025-06-19 10:30:07 +08:00
d560c437fe 修复mirage_image.slang编译失败 2025-06-18 18:12:52 +08:00
3a9ce532d5 完善着色器绑定器 2025-06-18 17:37:03 +08:00
6cc65e12b2 重构80% 2025-06-18 16:33:11 +08:00
562f598a41 TODO 重构着色器绑定代码 2025-06-17 19:17:00 +08:00
28b23ffd0f TODO slang编译脚本 2025-06-17 10:17:29 +08:00
daiqingshuang
163b91b4f2 TODO 2025-05-14 18:21:58 +08:00
daiqingshuang
3d735f81df 优化字体布局管理:重构文本布局结构以支持光标位置跟踪 2025-05-14 16:01:15 +08:00
daiqingshuang
19fb1e889c 优化字体布局管理:重构文本追加布局函数以支持传入布局参数 2025-05-14 15:49:23 +08:00
daiqingshuang
a1f09f9f10 优化IME光标位置更新:使用作用域退出机制简化资源管理 2025-05-14 14:54:04 +08:00
daiqingshuang
188aa2d7b8 优化IME光标位置更新:重构相关函数以接受窗口句柄参数 2025-05-14 11:49:33 +08:00
daiqingshuang
05ccd31ca3 增强IME支持:添加光标位置设置和焦点事件处理 2025-05-14 11:19:19 +08:00
cbc52fb72d 重构:改进各种模块中的日志记录和错误处理 2025-05-13 16:58:24 +08:00
daiqingshuang
8d01ad91c4 ime位置跟踪 2025-05-13 15:35:04 +08:00
aee4ba33d1 ime文本输入 2025-05-12 20:50:24 +08:00
daiqingshuang
96189c8a1a IME支持 2025-05-12 18:26:37 +08:00
daiqingshuang
d6487aef83 将静态成员初始化改为内联以优化代码结构 2025-05-07 17:26:56 +08:00
daiqingshuang
081905523b 处理双击事件时先触发按下事件 2025-05-07 17:25:42 +08:00
daiqingshuang
607d0a43a1 将 C++ 标准更新到 26 并改进资源清理
在构建配置中将 C++ 标准提高到 26,并在应用程序关闭之前添加资源清理消息。为了 UI 代码的一致性,对格式进行了细微的调整。
2025-05-07 17:23:33 +08:00
0b5487f69d 性能优化,去除多余的无效标记 2025-04-22 22:50:05 +08:00
4418c383bc TextBlock性能优化,避免重复布局 2025-04-22 22:38:17 +08:00
9366415c8c 测试git钩子 2025-04-22 22:30:35 +08:00
1230abc898 自动包裹文本块 2025-04-22 22:25:53 +08:00
daiqingshuang
c695b6e4ed overlay控件 2025-04-22 14:33:40 +08:00
a88904a546 修复MinGW编译问题 2025-04-22 01:49:04 +08:00
d8551a96ca 优化文本布局算法 2025-04-22 01:45:02 +08:00
cc37be615c 优化文本布局 2025-04-22 00:49:59 +08:00
12b6d058df 消除警告,规范日志输出 2025-04-21 23:57:16 +08:00
546c40a18b 尝试添加CYGWIN支持 2025-04-21 23:44:56 +08:00
79704b894b 尝试支持CYGWIN 2025-04-21 23:31:17 +08:00
057967a5c9 消除警告 2025-04-21 23:29:13 +08:00
daiqingshuang
21a12b257f 按钮 2025-04-21 15:22:20 +08:00
daiqingshuang
28980f8c41 按钮颜色 2025-04-21 14:41:24 +08:00
daiqingshuang
542949e9f5 优化控件布局语法 2025-04-21 14:30:01 +08:00
900db6dd70 TODO 优化控件布局语法 2025-04-21 10:39:32 +08:00
c322a4b6dd 优化布局语法,消除部分警告 2025-04-11 18:23:24 +08:00
daiqingshuang
2c794509bc 布局语法优化 2025-04-11 13:42:57 +08:00
b5fc6a9a26 垂直水平容器布局 2025-04-10 21:02:26 +08:00
934dd653ee 修复msvc编译错误 2025-04-10 20:02:05 +08:00
daiqingshuang
a88ef151d7 修复水平对齐问题 2025-04-10 17:19:59 +08:00
daiqingshuang
57c65892db 分离config使其作为单独的模块 2025-04-10 14:24:31 +08:00
daiqingshuang
791fca9282 添加注释 2025-04-10 13:47:50 +08:00
daiqingshuang
bbfe5ab665 新的布局语法,修复部分编译警告 2025-04-10 13:34:15 +08:00
daiqingshuang
7f37e2098d 修复c++标准设置不正确,使用新的std::println来输出日志 2025-04-10 13:21:35 +08:00
84330d5069 颜色解析 2025-04-08 01:17:51 +08:00
d596ae4c09 修复c++标准设置不正确,新增utfcpp库 2025-04-08 00:52:07 +08:00
4d9ccc73c0 修复add_resource_file 2025-04-07 21:42:20 +08:00
daiqingshuang
2ebd7f9182 修复着色器编译失败 2025-04-07 17:59:35 +08:00
daiqingshuang
fe1ca8998c color从字符串初始化,在CMake中添加资源文件 2025-04-07 17:32:12 +08:00
1c3aee5ca1 TODO 风格系统 2025-04-07 09:44:56 +08:00
3d58348c12 优化可读性 2025-04-06 20:15:49 +08:00
0ac8367b43 修复文本缓存失效 2025-04-04 23:48:47 +08:00
e9909e1c77 修复非windows平台编译错误 2025-04-04 15:35:39 +08:00
bd26423cb6 使用GET_X_LPARAM和GET_Y_LPARAM宏 2025-04-04 15:21:15 +08:00
3999bccf6e 清理代码 2025-04-04 15:19:40 +08:00
fbf6e7e716 修复滚轮事件不是窗口内位置,新增slot可以覆盖控件的visibility 2025-04-04 15:19:25 +08:00
fb46da91d4 修复文本计算下降部分计算不正确 2025-04-04 14:05:15 +08:00
c79167eb93 注释 2025-04-04 14:02:49 +08:00
0c32af832d 修复文本布局总大小计算 2025-04-04 13:57:35 +08:00
759f482afe 修复内边距不正确 2025-04-04 13:44:38 +08:00
3a426b8b99 总大小计算修复 2025-04-04 13:37:13 +08:00
a0487a75c1 修复文本垂直大小计算 2025-04-04 13:10:13 +08:00
9282b9f214 使用字号而不是像素高度 2025-04-04 12:58:58 +08:00
09f56e6c57 水平布局 2025-04-04 12:24:14 +08:00
daiqingshuang
2b923c2bb5 修复链式调用 2025-04-02 17:08:26 +08:00
daiqingshuang
da19e27f5b 优化行高计算 2025-04-02 14:37:41 +08:00
daiqingshuang
e0c0648221 优化文本渲染效果 2025-04-02 13:25:34 +08:00
daiqingshuang
9be39399d9 子像素绘制 2025-04-02 12:50:17 +08:00
daiqingshuang
4d0dbbe6c5 修复stb_truetype大小与freetype不一致 2025-04-02 11:03:36 +08:00
daiqingshuang
73eebd5387 确保不同后端布局功能完好 2025-04-02 10:58:03 +08:00
daiqingshuang
aaae32bd26 修复编译错误 2025-04-02 10:35:11 +08:00
3e612e1274 优化字体显示效果 2025-04-02 00:48:43 +08:00
b7a27096b5 stb_truetype布局 2025-04-02 00:39:03 +08:00
d5465f1953 抽象字体接口 2025-04-02 00:07:53 +08:00
e966dadc70 新增图片加载接口,调整目录结构 2025-04-01 20:42:27 +08:00
4036ee2ded 修改为filesystem作为路径参数类型 2025-04-01 17:57:17 +08:00
7206c04a71 楼提交 2025-03-31 10:25:50 +08:00
673b0a1478 线框着色器和线段着色器 2025-03-30 23:28:36 +08:00
56917da0d1 文本布局 2025-03-28 18:27:34 +08:00
765617d538 子像素渲染 2025-03-28 15:51:33 +08:00
2a84cd6c4e 布局测试 2025-03-28 13:51:54 +08:00
976994e85b 添加svg表情支持 2025-03-28 13:45:15 +08:00
2f9e76e4aa 修复表情符号布局 2025-03-28 13:08:36 +08:00
f200fcba4a 彩色表情绘制第一版 2025-03-28 12:41:41 +08:00
727457b9f7 文本布局系统 2025-03-28 03:14:21 +08:00
a19317b2f2 文本绘制 2025-03-28 02:47:32 +08:00
2a99584120 代码整理 2025-03-27 20:31:09 +08:00
5aafef4cfb 字体布局 2025-03-27 18:36:32 +08:00
6129e71db7 新增图集类,DX11映射函数 2025-03-27 14:13:39 +08:00
627dba45ea 嵌套语法 2025-03-27 01:53:54 +08:00
7cf03b639b 1 2025-03-26 23:11:43 +08:00
de33e765d4 修复图片大小不正确 2025-03-26 22:33:41 +08:00
dc896d51f3 图片绘制 2025-03-26 22:17:11 +08:00
2c5ea1e7e9 图片类 2025-03-26 18:21:12 +08:00
ed6722c297 文件夹重命名 2025-03-26 18:04:29 +08:00
3f375ccfc2 重命名mirage_stb_image为mirage_stb_image_loader 2025-03-26 18:01:15 +08:00
a27c49af75 脏矩形优化,拆分mwindow实现 2025-03-26 17:59:15 +08:00
e44a9bda57 删除ECS模式 2025-03-26 17:25:55 +08:00
79363ce141 mirage_stb_image 2025-03-25 21:31:03 +08:00
2a2cac52d3 项目结构调整 2025-03-25 19:57:07 +08:00
daiqingshuang
7dbcc93d1a 图片读取 2025-03-25 18:37:06 +08:00
daiqingshuang
3333dacdce 头文件目录整理 2025-03-25 14:59:46 +08:00
daiqingshuang
aba7da227d 目录整理 2025-03-25 14:59:22 +08:00
daiqingshuang
5940a4747b 整理代码结构 2025-03-25 14:57:40 +08:00
daiqingshuang
81d391d5ba 调整目录结构 2025-03-25 14:51:46 +08:00
daiqingshuang
aa926e31fc 清理不需要的代码 2025-03-25 14:48:20 +08:00
daiqingshuang
2d026313ff 添加mapped_file, 删除hit_test_parameters,整理目录 2025-03-25 14:41:12 +08:00
daiqingshuang
ab9c7028ef 修改事件layout_changed发送次数为单次,删除无用函数 2025-03-25 14:22:52 +08:00
088d301b6c 整理代码 2025-03-24 21:52:09 +08:00
1a768f405c 滚轮事件和widget_local_pos缓存 2025-03-24 21:18:31 +08:00
19a23d4f94 修复布局更新后hover的控件没有刷新的问题,新增在windows平台下,使用操作系统提供的双击接口 2025-03-24 21:03:09 +08:00
daiqingshuang
0f014790c9 修复CPU占用过高,全局绘制脏标记 2025-03-24 14:03:02 +08:00
daiqingshuang
3bac5b548b 更新mustache仓库地址 2025-03-24 13:32:03 +08:00
daiqingshuang
db735b94ea 修复布局系统 2025-03-24 13:22:47 +08:00
43aa8ce56d todo 垂直布局不正确 2025-03-24 01:50:14 +08:00
17c14a7a14 修复控件树重建,修复hover状态不正确 2025-03-24 01:43:01 +08:00
209 changed files with 24380 additions and 8497 deletions

View File

@@ -1,4 +1,3 @@
# Generated from CLion C/C++ Code Style settings
---
Language: Cpp
BasedOnStyle: LLVM
@@ -8,14 +7,12 @@ AlignConsecutiveDeclarations: true
AlignOperands: true
AlignTrailingComments: true
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: false # 禁止 case 标签与语句在同一行
AllowShortIfStatementsOnASingleLine: false
AllowShortReturnStatementsOnASingleLine: WithoutReturnValue # **只允许无返回值的 return 语句在单行**
AlwaysBreakAfterDefinitionReturnType: false
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BraceWrapping:
BinPackParameters: false
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: false
@@ -33,14 +30,13 @@ BraceWrapping:
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBraces: Custom
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: AfterColon
BreakConstructorInitializersBeforeComma: false
ColumnLimit: 120
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ContinuationIndentWidth: 8
Cpp11BracedListStyle: false
IncludeCategories:
IncludeCategories:
- Regex: '^<.*'
Priority: 1
- Regex: '^".*'

20
.gitignore vendored
View File

@@ -1,16 +1,6 @@
/cmake-build-debug
/cmake-build-release
/.idea
/scripts/shader_paths.txt
/cache/shader_loader.h
#>fips
# this area is managed by fips, do not edit
.fips-*
fips-files/build/
fips-files/deploy/
*.pyc
.vscode/
.idea/
CMakeUserPresets.json
#<fips
/tools/shader_path.txt
/.vscode
/cmake-build-*
/cache
*.shader.h
*.pyc

6
.gitmodules vendored
View File

@@ -1,6 +0,0 @@
[submodule "third_party/msdfgen"]
path = third_party/msdfgen
url = https://github.com/Chlumsky/msdfgen.git
[submodule "third_party/mustache"]
path = third_party/mustache
url = https://github.com/kirillochnev/mustache.git

View File

@@ -4,20 +4,8 @@
cmake_minimum_required(VERSION 3.21)
project(mirage)
set(CMAKE_CXX_STANDARD 26)
if (MSVC)
# MSVC编译器设置C++标准
add_compile_options(/std:c++latest)
# 设置utf-8编码
add_compile_options(/utf-8)
endif ()
if (WIN32)
# 定义Windows版本宏
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_definitions(-DUNICODE -D_UNICODE)
endif ()
include(cmake/project_cpp_standard.cmake)
set_cpp_standard(23)
set(MIRAGE_USE_HDR OFF CACHE BOOL "Enable HDR format")
set(MIRAGE_HDR_FORMAT "SG_PIXELFORMAT_RGBA16F" CACHE STRING "Enable HDR format")
@@ -30,23 +18,22 @@ else ()
endif ()
add_definitions(-DMIRAGE_HDR_FORMAT=${MIRAGE_HDR_FORMAT} -DMIRAGE_PIXEL_FORMAT=${MIRAGE_PIXEL_FORMAT})
set(MSDFGEN_USE_SKIA OFF CACHE BOOL "Use Skia for MSDFGen" FORCE)
set(MSDFGEN_USE_VCPKG OFF CACHE BOOL "Use VCPKG for MSDFGen" FORCE)
set(MSDFGEN_USE_OPENMP ON CACHE BOOL "Use OpenMP for MSDFGen" FORCE)
set(MSDFGEN_BUILD_STANDALONE ON CACHE BOOL "Build MSDFGen standalone" FORCE)
set(MSDFGEN_BUILD_STANDALONE ON CACHE BOOL "Build MSDFGen standalone" FORCE)
set(MUSTACHE_BUILD_SHARED OFF CACHE BOOL "Build shared libraries?" FORCE)
# 配置输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# --- 设置项目根目录变量 ---
# **定义项目源代码根目录变量**:
# CMAKE_CURRENT_SOURCE_DIR 在根 CMakeLists.txt 中即为项目源代码的根目录
# 使用 PARENT_SCOPE 使该变量在调用此函数的 CMakeLists.txt 文件中也可用
set(MIRAGE_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR})
message(STATUS "mirage 项目根源目录 (MIRAGE_ROOT_DIR) 设置为: ${MIRAGE_ROOT_DIR}")
include(cmake/retrieve_files.cmake)
include(cmake/detect_os.cmake)
include(cmake/config_macos.cmake)
include(cmake/compile_shaders.cmake)
include(cmake/mingw_dll.cmake)
include(cmake/mirage_utils.cmake)
# 配置输出目录
configure_project_defaults()
# 如果是Debug模式, 添加宏定义
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
@@ -55,9 +42,26 @@ else ()
add_definitions(-DDEBUG=0)
endif ()
add_subdirectory(third_party/msdfgen)
add_subdirectory(third_party/mustache)
add_subdirectory(src)
# 按照依赖顺序添加子目录(从底层到上层)
# Layer 0: 基础设施
add_subdirectory(src/sokol)
# Layer 1: 核心功能层
add_subdirectory(src/mirage_config)
add_subdirectory(src/mirage_image)
add_subdirectory(src/mirage_core)
# Layer 2: 平台抽象层
add_subdirectory(src/mirage_platform)
# Layer 3: 渲染层
add_subdirectory(src/mirage_render)
# Layer 4: Widget层
add_subdirectory(src/mirage_widget)
# Layer 5: 应用程序框架
add_subdirectory(src/mirage_app)
set(BUILD_EXAMPLE FALSE CACHE BOOL "Build example")
if (BUILD_EXAMPLE)

View File

@@ -4,56 +4,107 @@ include(CMakeParseArguments)
find_package(Python3 REQUIRED)
# 存储脚本路径
set(SHADER_COMPILE_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/tools/compile_shaders.py")
set(SHADER_COMPILE_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/tools/main.py")
# 在cache目录下创建着色器目录.txt
set(SHADER_PATH_FILE ${CMAKE_CACHEFILE_DIR}/shader_paths.txt)
set(SHADER_PATH_FILE ${CMAKE_CURRENT_SOURCE_DIR}/cache/shader_paths.txt)
file(REMOVE ${SHADER_PATH_FILE})
file(WRITE ${SHADER_PATH_FILE} "")
# 添加着色器目录的函数
# 参数:
# path: 要添加的包含 .slang 着色器文件的目录路径
# 功能:
# 1. 将路径转换为适合目标系统的格式 (特别是处理 Cygwin)。
# 2. 将路径追加到 SHADER_PATH_FILE 指定的文件中。
# 3. 查找指定路径下的所有 .slang 文件。
# 4. 将找到的 .slang 文件添加为 CMake 配置的依赖项,
# 以便在这些文件更改时触发重新配置。
# 注意:
# - 需要在使用此函数前定义 CMAKE_BINARY_DIR 和 SHADER_PATH_FILE 变量。
# 例如: set(SHADER_PATH_FILE ${CMAKE_BINARY_DIR}/shader_paths.txt)
# - 假定 SHADER_PATH_FILE 的使用者期望接收适合其环境的路径格式
# (此处在 Cygwin 下转换为 Windows 格式)。
function(add_mirage_shader_directory path)
# 将路径写入shader_paths.txt
file(APPEND ${SHADER_PATH_FILE} "${path}\n")
# 检查 SHADER_PATH_FILE 变量是否已定义
if(NOT DEFINED SHADER_PATH_FILE)
message(FATAL_ERROR "**错误**: SHADER_PATH_FILE 变量未定义。请在使用 add_mirage_shader_directory 前设置此变量。")
endif()
# 观察目录文件是否修改, 触发compile_shaders目标重新编译
file(GLOB_RECURSE SHADER_FILES "${path}/*.slang")
# 获取绝对路径以确保一致性
get_filename_component(abs_path ${path} ABSOLUTE)
# 设置依赖关系当shader文件变化时重新编译
# 保存用于文件操作和写入文件的路径变量
set(path_to_write ${abs_path})
# 如果是cygwin环境, 需要转换路径为Windows格式
if (CYGWIN)
message(STATUS "检测到 Cygwin 环境,尝试使用 cygpath 转换路径: ${abs_path}")
# **关键点**: 使用 cygpath -w 将 Cygwin 路径转换为 Windows 路径
execute_process(
COMMAND cygpath -w ${abs_path}
OUTPUT_VARIABLE path_windows
OUTPUT_STRIP_TRAILING_WHITESPACE # 去除可能的尾随空格
RESULT_VARIABLE cygpath_result
ERROR_QUIET # 如果 cygpath 不存在或失败,则不显示错误,但检查 result
)
if(cygpath_result EQUAL 0 AND path_windows) # 检查 cygpath 是否成功执行并有输出
# 更新用于写入文件的路径
set(path_to_write ${path_windows})
message(STATUS "路径已成功转换为 Windows 格式: ${path_to_write}")
else()
# 如果 cygpath 失败或未找到,发出警告,并回退到原始路径
message(WARNING "无法使用 cygpath 转换路径 ${abs_path}。将使用原始路径。请确保 cygpath 在系统 PATH 中。")
# path_to_write 保持为 abs_path
endif()
endif()
# 将可能已转换的路径写入shader_paths.txt
# **关键点**: 追加路径到 ${SHADER_PATH_FILE}
file(APPEND ${SHADER_PATH_FILE} "${path_to_write}\n")
# 查找目录下的所有 .slang 文件,包括子目录
# **关键点**: 递归查找 ${abs_path} 目录下的 *.slang 文件
file(GLOB_RECURSE SHADER_FILES LIST_DIRECTORIES false "${abs_path}/*.slang")
# 设置依赖关系当shader文件变化时重新运行CMake配置
# **关键点**: 将着色器文件添加为 CMake 配置依赖项
foreach(SHADER_FILE ${SHADER_FILES})
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${SHADER_FILE})
get_filename_component(ABS_SHADER_FILE ${SHADER_FILE} ABSOLUTE)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${ABS_SHADER_FILE})
endforeach()
# **关键点**: 输出状态消息,告知用户已添加目录
message(STATUS "添加着色器源文件目录: ${path_to_write}")
endfunction()
set(SHADER_COMPILE_ARGS "")
set(SHADER_SHDC "")
if (WIN32)
list(APPEND SHADER_COMPILE_ARGS "--hlsl")
set(SHADER_SHDC ${MIRAGE_ROOT_DIR}/tools/win_mirage_shdc.exe)
if (WIN32 OR CYGWIN)
list(APPEND SHADER_COMPILE_ARGS "-t" "dxbc")
elseif (APPLE)
list(APPEND SHADER_COMPILE_ARGS "--metal")
set(SHADER_SHDC ${MIRAGE_ROOT_DIR}/tools/mac_mirage_shdc)
list(APPEND SHADER_COMPILE_ARGS "-t" "msl")
else()
list(APPEND SHADER_COMPILE_ARGS "--glsl")
set(SHADER_SHDC ${MIRAGE_ROOT_DIR}/tools/linux_mirage_shdc)
list(APPEND SHADER_COMPILE_ARGS "-t" "glsl")
endif()
message(STATUS "使用着色器编译器: ${SHADER_SHDC}")
# 如果是Debug模式, 添加--debug选项
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
list(APPEND SHADER_COMPILE_ARGS "--debug")
list(APPEND SHADER_COMPILE_ARGS "-d")
endif ()
# 添加编译目标, 调用tools/compile_shaders.py
add_custom_target(compile_shaders ALL
COMMAND ${Python3_EXECUTABLE} ${SHADER_COMPILE_SCRIPT}
--shdc ${SHADER_SHDC}
--shader_list ${SHADER_PATH_FILE}
-l ${SHADER_PATH_FILE}
${SHADER_COMPILE_ARGS}
COMMENT "编译着色器"
VERBATIM
)
# 定义一个,将编译着色器作为其他目标的依赖
# 定义一个函数,将编译着色器作为其他目标的依赖
function(add_shader_dependencies target)
add_dependencies(${target} compile_shaders)
endfunction()

View File

@@ -1,73 +1,131 @@
# DetectOS.cmake
# 定义一个函数,为指定的目标添加操作系统和架构相关的预处理器定义
function(add_os_definitions target)
# 初始化所有平台宏为 0
set(PLATFORMS MIRAGE_PLATFORM_WINDOWS MIRAGE_PLATFORM_MACOS MIRAGE_PLATFORM_LINUX MIRAGE_PLATFORM_FREEBSD MIRAGE_PLATFORM_IOS MIRAGE_PLATFORM_ANDROID)
# 检查 target 参数是否提供
if(NOT target)
message(FATAL_ERROR "函数 add_os_definitions 需要一个 target 参数。")
return()
endif()
# 检测操作系统并设置相应的宏为 1
if(WIN32)
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_WINDOWS=1)
message(STATUS "检测到 Windows 操作系统")
list(REMOVE_ITEM PLATFORMS MIRAGE_PLATFORM_WINDOWS)
elseif(APPLE AND NOT IOS)
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_MACOS=1)
message(STATUS "检测到 macOS 操作系统")
list(REMOVE_ITEM PLATFORMS MIRAGE_PLATFORM_MACOS)
elseif(UNIX)
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_LINUX=1)
message(STATUS "检测到 Linux 操作系统")
list(REMOVE_ITEM PLATFORMS MIRAGE_PLATFORM_LINUX)
elseif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_FREEBSD=1)
message(STATUS "检测到 FreeBSD 操作系统")
list(REMOVE_ITEM PLATFORMS MIRAGE_PLATFORM_FREEBSD)
else()
message(WARNING "检测到未知的 类Unix 操作系统")
endif()
# --- 阶段 1: 确定宏的值 ---
# 初始化所有平台、架构和特性宏的值为 0
set(mirage_def_windows 0)
set(mirage_def_macos 0)
set(mirage_def_linux 0)
set(mirage_def_freebsd 0)
set(mirage_def_ios 0)
set(mirage_def_android 0)
set(mirage_def_cygwin 0)
set(mirage_def_unix 0)
set(mirage_def_posix 0)
set(mirage_def_mobile 0)
set(mirage_def_arch_64bit 0)
set(mirage_def_arch_32bit 0)
# -- 操作系统检测与赋值 --
# 注意检测顺序:优先检测更具体的平台
if(CYGWIN)
# Cygwin 环境比较特殊,它在 Windows 上模拟 Unix
set(mirage_def_windows 1) # 基础是 Windows
set(mirage_def_cygwin 1) # 明确是 Cygwin
set(mirage_def_unix 1) # 提供 Unix API
set(mirage_def_posix 1) # 提供 POSIX API
message(STATUS "检测到 **Cygwin** 环境 (运行于 Windows)")
elseif(WIN32)
# 非 Cygwin 的 Windows 环境 (MSVC, MinGW, etc.)
set(mirage_def_windows 1)
message(STATUS "检测到 **Windows** 操作系统 (非 Cygwin)")
elseif(ANDROID)
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_ANDROID=1)
message(STATUS "检测到 Android 操作系统")
list(REMOVE_ITEM PLATFORMS MIRAGE_PLATFORM_ANDROID)
# Android 平台 (通常需要特定工具链设置 ANDROID 变量)
set(mirage_def_android 1)
set(mirage_def_unix 1) # Android NDK 基于 Unix
set(mirage_def_posix 1) # NDK 提供 POSIX API
set(mirage_def_mobile 1) # 移动平台
message(STATUS "检测到 **Android** 操作系统")
elseif(IOS)
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_IOS=1)
message(STATUS "检测到 iOS 操作系统")
list(REMOVE_ITEM PLATFORMS MIRAGE_PLATFORM_IOS)
# iOS 平台 (通常需要特定工具链设置 IOS 变量)
# 需要在 APPLE 之前判断,因为 iOS 下 APPLE 也为 TRUE
set(mirage_def_ios 1)
set(mirage_def_unix 1) # iOS (Darwin) 基于 Unix
set(mirage_def_posix 1) # 提供 POSIX API
set(mirage_def_mobile 1) # 移动平台
message(STATUS "检测到 **iOS** 操作系统")
elseif(APPLE)
# 此时排除了 iOS确定是 macOS
set(mirage_def_macos 1)
set(mirage_def_unix 1) # macOS (Darwin) 基于 Unix
set(mirage_def_posix 1) # 提供 POSIX API
message(STATUS "检测到 **macOS** 操作系统")
elseif(UNIX)
# 此时排除了 Apple, Android, Cygwin 的其他 Unix-like 系统
set(mirage_def_unix 1)
set(mirage_def_posix 1)
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
set(mirage_def_linux 1)
message(STATUS "检测到 **Linux** 操作系统")
elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
set(mirage_def_freebsd 1)
message(STATUS "检测到 **FreeBSD** 操作系统")
else()
message(WARNING "检测到未知的 类Unix 操作系统: ${CMAKE_SYSTEM_NAME}")
endif()
else()
message(WARNING "检测到未知的操作系统")
message(WARNING "检测到未知的操作系统: ${CMAKE_SYSTEM_NAME}")
endif()
foreach(PLATFORM ${PLATFORMS})
target_compile_definitions(${target} PUBLIC ${PLATFORM}=0)
endforeach()
# 检测并设置架构宏
# -- 架构检测与赋值 --
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_ARCH_64BIT=1 MIRAGE_PLATFORM_ARCH_32BIT=0)
message(STATUS "检测到 64-bit 架构")
set(mirage_def_arch_64bit 1)
set(mirage_def_arch_32bit 0) # 明确设置为 0
message(STATUS "检测到 **64-bit** 架构")
elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
set(mirage_def_arch_64bit 0) # 明确设置为 0
set(mirage_def_arch_32bit 1)
message(STATUS "检测到 **32-bit** 架构")
else()
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_ARCH_64BIT=0 MIRAGE_PLATFORM_ARCH_32BIT=1)
message(STATUS "检测到 32-bit 架构")
# 对于未知或未定义的指针大小,两者都保持 0
message(WARNING "无法明确检测到 32-bit 或 64-bit 架构 (CMAKE_SIZEOF_VOID_P = ${CMAKE_SIZEOF_VOID_P})。将两者都设置为 0。")
endif()
# 设置通用的 UNIX 宏
if(UNIX)
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_UNIX=1)
else()
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_UNIX=0)
# --- 阶段 2: 组装定义列表 ---
set(definitions_list "") # 初始化空列表
# 添加平台定义
list(APPEND definitions_list "MIRAGE_PLATFORM_WINDOWS=${mirage_def_windows}")
list(APPEND definitions_list "MIRAGE_PLATFORM_MACOS=${mirage_def_macos}")
list(APPEND definitions_list "MIRAGE_PLATFORM_LINUX=${mirage_def_linux}")
list(APPEND definitions_list "MIRAGE_PLATFORM_FREEBSD=${mirage_def_freebsd}")
list(APPEND definitions_list "MIRAGE_PLATFORM_IOS=${mirage_def_ios}")
list(APPEND definitions_list "MIRAGE_PLATFORM_ANDROID=${mirage_def_android}")
list(APPEND definitions_list "MIRAGE_PLATFORM_CYGWIN=${mirage_def_cygwin}")
# 添加架构定义
list(APPEND definitions_list "MIRAGE_PLATFORM_ARCH_64BIT=${mirage_def_arch_64bit}")
list(APPEND definitions_list "MIRAGE_PLATFORM_ARCH_32BIT=${mirage_def_arch_32bit}")
# 添加特性定义
list(APPEND definitions_list "MIRAGE_PLATFORM_UNIX=${mirage_def_unix}")
list(APPEND definitions_list "MIRAGE_PLATFORM_POSIX=${mirage_def_posix}")
list(APPEND definitions_list "MIRAGE_PLATFORM_IS_MOBILE=${mirage_def_mobile}")
# --- 阶段 3: 应用所有定义 ---
# **关键:使用一次调用将所有定义添加到目标**
if(definitions_list) # 确保列表非空
target_compile_definitions(${target} PUBLIC ${definitions_list})
endif()
# 设置通用的 POSIX 宏
if(UNIX OR APPLE)
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_POSIX=1)
else()
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_POSIX=0)
endif()
# 函数作用域结束时mirage_def_* 变量会自动销毁,无需显式 unset
# 设置IS_MOBILE宏
if(ANDROID OR IOS)
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_IS_MOBILE=1)
else()
target_compile_definitions(${target} PUBLIC MIRAGE_PLATFORM_IS_MOBILE=0)
endif()
endfunction()
# --- 使用示例 ---
# project(MyProject)
# add_executable(my_app main.c)
#
# # 调用函数为 my_app 添加平台定义
# add_os_definitions(my_app)
#
# # 你也可以为库调用
# # add_library(my_lib STATIC my_lib.c)
# # add_os_definitions(my_lib)

41
cmake/mingw_dll.cmake Normal file
View File

@@ -0,0 +1,41 @@
function(auto_copy_mingw_dll target)
if(MINGW)
# 获取MinGW目录
get_filename_component(MINGW_DIR ${CMAKE_CXX_COMPILER} PATH)
# 根据你的环境调整DLL列表
set(MINGW_DLLS
"libstdc++-6.dll"
# "libgcc_s_dw2-1.dll"
"libgcc_s_seh-1.dll"
"libwinpthread-1.dll"
"libbz2-1.dll"
"libbrotlidec.dll"
"libbrotlicommon.dll"
"libharfbuzz-0.dll"
"libpng16-16.dll"
"zlib1.dll"
"libglib-2.0-0.dll"
"libfreetype-6.dll"
"libgraphite2.dll"
"libintl-8.dll"
"libpcre2-8-0.dll"
"libiconv-2.dll"
)
foreach(DLL ${MINGW_DLLS})
# 检查文件是否存在
if(EXISTS "${MINGW_DIR}/${DLL}")
add_custom_command(TARGET ${target} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${MINGW_DIR}/${DLL}"
"$<TARGET_FILE_DIR:${target}>"
COMMENT "Copying ${DLL} to output directory"
VERBATIM)
else()
message(WARNING "DLL not found: ${MINGW_DIR}/${DLL}")
endif()
endforeach()
endif()
endfunction()

46
cmake/mirage_utils.cmake Normal file
View File

@@ -0,0 +1,46 @@
# 定义一个函数来配置项目的默认设置
# 这包括设置输出目录和项目根目录变量
function(configure_project_defaults)
# 检查是否在顶层 CMakeLists.txt 中调用 (可选但推荐)
# 确保 CMAKE_SOURCE_DIR 和 CMAKE_CURRENT_SOURCE_DIR 相同
if(NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
message(WARNING "configure_project_defaults() 应该在项目的根 CMakeLists.txt 中调用。")
# 如果您确实需要在子目录中设置不同的根目录,请调整此逻辑
endif()
# --- 配置输出目录 ---
# 使用 CMAKE_BINARY_DIR 作为基础构建目录
# ${CMAKE_BINARY_DIR} 指向您配置 CMake 时指定的构建目录
# 例如,在 CLion 中通常是 cmake-build-debug 或 cmake-build-release
# 如果手动运行 cmake ..,它就是您运行 cmake 命令的目录
get_filename_component(ABS_BIN_DIR ${CMAKE_BINARY_DIR} ABSOLUTE)
# **设置可执行文件输出路径**:
# 对于单配置生成器 (如 Makefiles, Ninja), 可执行文件将位于 <build>/bin/
# 对于多配置生成器 (如 Visual Studio, Xcode), CMake 通常会自动在此路径下附加配置名称
# (例如 <build>/bin/Debug/, <build>/bin/Release/)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${ABS_BIN_DIR}/bin CACHE PATH "Directory for runtime executables")
message(STATUS "运行时输出目录设置为: ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
# **设置库文件输出路径 (共享库和静态库)**:
# 规则同上,库文件将位于 <build>/lib/ 或 <build>/lib/<Config>/
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${ABS_BIN_DIR}/lib CACHE PATH "Directory for shared libraries")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${ABS_BIN_DIR}/lib CACHE PATH "Directory for static libraries")
message(STATUS "库输出目录设置为: ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
message(STATUS "存档输出目录设置为: ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}")
# --- 提示 ---
# 这种全局设置输出目录的方法对于中小型项目是常见的。
# 对于更复杂的项目或需要更细粒度控制的情况可以考虑为每个目标target单独设置输出目录属性
# 例如:
# add_executable(my_app main.cpp)
# set_target_properties(my_app PROPERTIES
# RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/executables"
# )
# add_library(my_lib STATIC my_lib.cpp)
# set_target_properties(my_lib PROPERTIES
# ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/static_libs"
# )
endfunction()

View File

@@ -0,0 +1,85 @@
# 函数:设置 C++ 标准及相关编译选项
# 参数 standard: 需要设置的 C++ 标准版本 (例如 11, 14, 17, 20, 23)
function(set_cpp_standard standard)
# --- 参数验证 ---
set(VALID_STANDARDS 11 14 17 20 23 26) # 定义支持的 C++ 标准列表
list(FIND VALID_STANDARDS ${standard} _standard_index) # 查找 standard 是否在列表中
if(_standard_index EQUAL -1) # 如果未找到
message(WARNING "**非标准 C++ 版本**: ${standard}。支持的版本有: ${VALID_STANDARDS}")
# 可选:可以设置为一个默认值或停止配置
# message(FATAL_ERROR "不支持的 C++ 标准: ${standard}")
# set(standard 17) # 或者设置为默认值,例如 C++17
# message(WARNING "已将 C++ 标准设置为默认值: ${standard}")
endif()
# --- 设置 C++ 标准 ---
# 指定需要的 C++ 标准,设置到父作用域,使其对调用者定义的 target 生效
set(CMAKE_CXX_STANDARD ${standard} PARENT_SCOPE)
# **强制要求此标准**,如果编译器不支持则配置时报错
set(CMAKE_CXX_STANDARD_REQUIRED ON PARENT_SCOPE)
# **禁用编译器特定的扩展**,使用更纯粹的标准 C++
set(CMAKE_CXX_EXTENSIONS OFF PARENT_SCOPE)
# --- 平台特定设置 ---
if(WIN32 OR CYGWIN)
# 为 Windows 定义 UNICODE 宏
add_definitions(-DUNICODE -D_UNICODE)
# 可选:添加 WIN32_LEAN_AND_MEAN 以减少 Windows 头文件包含,加快编译速度
# add_definitions(-DWIN32_LEAN_AND_MEAN)
message(STATUS "为 Windows 添加 UNICODE 定义")
endif()
# --- 编译器特定设置 ---
if(MSVC)
# **设置源代码和执行字符集为 UTF-8**
add_compile_options(/utf-8)
# **强制 MSVC 正确设置 __cplusplus 宏**,以便代码能准确判断 C++ 标准
add_compile_options(/Zc:__cplusplus)
# **设置警告级别为 W4** (较高警告级别)
add_compile_options(/W4)
# **禁用特定警告C4100 未使用的形参** (有时用于接口兼容性)
add_compile_options(/wd4100)
# 禁用特定警告C4996 使用了被标记为否决的函数或变量 (例如一些旧的 CRT 函数)
add_compile_options(/wd4996)
message(STATUS "为 MSVC 添加特定编译选项: /utf-8 /Zc:__cplusplus /W4 /wd4100 /wd4996")
endif()
# GCC/Clang 特定选项
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# **启用常用警告**
add_compile_options(-Wall -Wextra)
# **禁用未使用参数的警告** (与 MSVC 的 /wd4100 对应)
add_compile_options(-Wno-unused-parameter)
# **设置输入和执行字符集为 UTF-8** (对应 MSVC 的 /utf-8)
# 这有助于处理源代码中的 UTF-8 字符,并影响运行时字符编码,但控制台本身的显示需要环境配合
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -finput-charset=UTF-8 -fexec-charset=UTF-8")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -finput-charset=UTF-8 -fexec-charset=UTF-8")
# 根据 C++ 标准添加特定警告 (C++17 及以上)
if(${standard} GREATER 14)
# **启用阴影变量警告和非虚析构函数警告**
add_compile_options(-Wshadow -Wnon-virtual-dtor)
message(STATUS "为 GCC/Clang (C++${CMAKE_CXX_STANDARD}) 添加额外警告: -Wshadow -Wnon-virtual-dtor")
endif()
message(STATUS "为 GCC/Clang 添加特定编译选项: -Wall -Wextra -Wno-unused-parameter -finput-charset=UTF-8 -fexec-charset=UTF-8")
# 注意: 控制台/终端的 UTF-8 输出显示通常还需要操作系统环境的配合 (例如 Linux/macOS 终端本身设置Windows下 chcp 65001)
endif()
# MinGW 特定设置 (通常在 Windows 上使用 GCC 工具链)
if(MINGW)
message(STATUS "检测到 MinGW 编译器")
# 如果使用了 C++17 或更高版本, 可能需要链接 libstdc++exp 以支持 <filesystem> 等特性
if(${standard} GREATER 14)
message(STATUS "**为 MinGW C++${CMAKE_CXX_STANDARD} 添加 libstdc++fs 库依赖** (用于 <filesystem>)")
# 注意:较新版本的 MinGW 可能不再需要显式链接,或者需要链接的是 -lstdc++fs
# link_libraries() 会影响后续所有 target更推荐使用 target_link_libraries()
# 这里暂时保留 link_libraries(),但建议后续根据具体 target 调整
# 尝试链接 stdc++fs这在较新的 MinGW 中更常见用于 <filesystem>
# link_libraries(-lstdc++fs)
# 如果链接 -lstdc++fs 失败,可以尝试回退到 -lstdc++exp
link_libraries(-lstdc++exp)
endif()
endif()
message(STATUS "**C++ 标准已设置为: c++${standard}**")
endfunction()

View File

@@ -9,7 +9,7 @@ function(is_current_platform platform is_match)
set(matches FALSE)
if(platform STREQUAL "windows")
if(WIN32)
if(WIN32 OR CYGWIN)
set(matches TRUE)
endif()
elseif(platform STREQUAL "linux")
@@ -24,10 +24,15 @@ function(is_current_platform platform is_match)
if(IOS)
set(matches TRUE)
endif()
elseif (platform STREQUAL "android")
elseif(platform STREQUAL "android")
if(ANDROID)
set(matches TRUE)
endif()
# 添加对unix平台的支持
elseif(platform STREQUAL "unix")
if(UNIX)
set(matches TRUE)
endif()
elseif(platform STREQUAL "mobile")
if(ANDROID OR IOS)
set(matches TRUE)
@@ -85,7 +90,7 @@ function(retrieve_files_custom path extension out_files)
foreach(dir_name IN LISTS dir_components)
# 检查是否是平台相关目录
if(dir_name MATCHES "^(windows|linux|mac|ios|android|mobile|desktop)$")
if(dir_name MATCHES "^(windows|linux|mac|ios|android|unix|mobile|desktop)$")
set(found_platform_dir TRUE)
is_current_platform(${dir_name} platform_matches)
if(NOT platform_matches)
@@ -150,4 +155,162 @@ function(retrieve_files path out_files)
# 合并结果到输出变量
set(${out_files} ${${out_files}} ${temp_files} PARENT_SCOPE)
endfunction()
endfunction()
#[=======================================================================[
# 用于添加资源文件并在编译后复制到最终可执行文件所在目录
# 注意:此函数依赖于 CMAKE_RUNTIME_OUTPUT_DIRECTORY 或 EXECUTABLE_OUTPUT_PATH
# 变量的设置,以确定可执行文件的输出目录。请确保在项目中设置了其中之一。
#
# 参数:
# TARGET_NAME: (必需) - 关联的目标 (库或可执行文件) 的名称。
# 资源复制命令将在 TARGET_NAME 构建后执行。
# RESOURCE_FILES: (必需) - 一个或多个要复制的资源文件的路径列表 (相对或绝对)
# OUTPUT_SUBDIR: (可选) - 相对于可执行文件输出目录的子目录路径 (例如 "assets")
#
# 例子:
# # 确保设置了可执行文件输出目录 (通常在顶层 CMakeLists.txt)
# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
#
# # 添加库
# add_library(my_lib STATIC src/my_lib.cpp)
#
# # 添加资源到 my_lib但复制到最终可执行文件的输出目录下的 'config' 子目录
# add_resource_file(
# TARGET_NAME my_lib
# RESOURCE_FILES config/settings.json config/defaults.ini
# OUTPUT_SUBDIR config
# )
#
# # 添加可执行文件
# add_executable(my_app main.cpp)
# target_link_libraries(my_app PRIVATE my_lib)
#
# # 添加 my_app 的资源,复制到可执行文件输出目录的根目录
# add_resource_file(
# TARGET_NAME my_app
# RESOURCE_FILES assets/icon.png
# )
#]=======================================================================]
function(add_resource_file)
# 定义预期的参数
set(options "") # 无布尔选项
set(oneValueArgs TARGET_NAME OUTPUT_SUBDIR)
set(multiValueArgs RESOURCE_FILES)
# 解析传递给函数的参数
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# --- 参数验证 ---
if(NOT ARG_TARGET_NAME)
message(FATAL_ERROR "**add_resource_file**: **缺少必需参数** **TARGET_NAME**.")
endif()
if(NOT ARG_RESOURCE_FILES)
message(FATAL_ERROR "**add_resource_file**: **缺少必需参数** **RESOURCE_FILES**.")
endif()
if(NOT TARGET ${ARG_TARGET_NAME})
message(WARNING "**add_resource_file**: 目标 '${ARG_TARGET_NAME}' (尚)不存在。请确保在调用 add_executable/add_library('${ARG_TARGET_NAME}') 之后调用此函数。")
# 即使目标尚不存在仍然尝试配置命令。CMake通常能处理好依赖关系。
endif()
# --- 确定最终可执行文件的目标基础目录 ---
set(DESTINATION_BASE "")
if(DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY AND CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(DESTINATION_BASE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
elseif(DEFINED EXECUTABLE_OUTPUT_PATH AND EXECUTABLE_OUTPUT_PATH)
# EXECUTABLE_OUTPUT_PATH 是旧变量,但也检查一下
set(DESTINATION_BASE "${EXECUTABLE_OUTPUT_PATH}")
else()
# 如果是多配置生成器(如 Visual Studio, Xcode需要考虑配置类型
get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(is_multi_config)
# 对于多配置,没有单一的顶级运行时目录变量。
# 可以考虑使用 $<OUTPUT_DIRECTORY> 配合一个已知的可执行文件名,但这会使函数复杂化。
# 最好的做法是要求用户设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY_<CONFIG>
# 或者我们直接报错,强制用户设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY
message(FATAL_ERROR "**add_resource_file**: **无法确定可执行文件输出目录**。请在您的项目中设置 **CMAKE_RUNTIME_OUTPUT_DIRECTORY** 变量 (例如 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"\${CMAKE_BINARY_DIR}/bin\"))。对于多配置生成器,可能需要设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY_<CONFIG> 变量。")
else()
# 对于单配置生成器(如 Makefiles, Ninja可以默认到 CMAKE_BINARY_DIR
set(DESTINATION_BASE "${CMAKE_BINARY_DIR}")
message(WARNING "**add_resource_file**: **未设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY**。默认将资源复制到 CMAKE_BINARY_DIR ('${CMAKE_BINARY_DIR}')。强烈建议设置 CMAKE_RUNTIME_OUTPUT_DIRECTORY 以获得可预测的行为。")
endif()
# message(FATAL_ERROR "**add_resource_file**: **无法确定可执行文件输出目录**。请在您的项目中设置 **CMAKE_RUNTIME_OUTPUT_DIRECTORY** 变量 (例如 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"\${CMAKE_BINARY_DIR}/bin\"))。")
endif()
# 处理子目录
set(DESTINATION_DIR "${DESTINATION_BASE}") # 默认目标目录
if(ARG_OUTPUT_SUBDIR)
# 清理子目录路径字符串
string(STRIP "${ARG_OUTPUT_SUBDIR}" _subdir)
if(IS_ABSOLUTE "${_subdir}")
message(FATAL_ERROR "**add_resource_file**: **OUTPUT_SUBDIR** ('${ARG_OUTPUT_SUBDIR}') **必须是相对路径**。")
else()
# 移除可能存在的前导/后导斜杠,以便干净地拼接路径
string(REGEX REPLACE "^[/\\\\]+" "" _subdir "${_subdir}")
string(REGEX REPLACE "[/\\\\]+$" "" _subdir "${_subdir}")
if(_subdir) # 仅当子目录清理后非空时才追加
set(DESTINATION_DIR "${DESTINATION_BASE}/${_subdir}")
endif()
endif()
endif()
# --- 准备源文件路径 ---
set(ABS_RESOURCE_FILES "")
foreach(RESOURCE_FILE ${ARG_RESOURCE_FILES})
get_filename_component(RESOURCE_FILE_REALPATH "${RESOURCE_FILE}" REALPATH BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
# if(IS_ABSOLUTE "${RESOURCE_FILE}")
# # 如果已经是绝对路径,直接使用
# list(APPEND ABS_RESOURCE_FILES "${RESOURCE_FILE}")
# else()
# # 如果是相对路径,相对于当前源目录进行解析
# list(APPEND ABS_RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_FILE}")
# endif()
list(APPEND ABS_RESOURCE_FILES "${RESOURCE_FILE_REALPATH}")
# 检查文件是否存在 (在配置时发出警告,有助于早期发现错误)
# list(GET ABS_RESOURCE_FILES -1 _current_abs_file) # 获取刚才添加的绝对路径
if(NOT EXISTS "${RESOURCE_FILE_REALPATH}")
message(WARNING "**add_resource_file**: **资源文件** '${RESOURCE_FILE}' (解析为 '${RESOURCE_FILE_REALPATH}') **在配置时不存在**。")
endif()
endforeach()
# --- 添加自定义命令 ---
# 使用 add_custom_command 在目标构建完成后执行复制操作
if(ABS_RESOURCE_FILES) # 确保有文件需要复制
# 注意DESTINATION_DIR 可能包含特定于配置的路径(例如,如果 CMAKE_RUNTIME_OUTPUT_DIRECTORY
# 设置为 ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin
# add_custom_command 的 COMMAND 参数在构建时执行,此时这些变量/生成器表达式已解析。
add_custom_command(
TARGET ${ARG_TARGET_NAME}
POST_BUILD # 指定在目标构建之后执行
# 步骤 1: 确保目标目录存在 (copy_if_different 不会创建目录)
COMMAND ${CMAKE_COMMAND} -E make_directory "${DESTINATION_DIR}"
# 步骤 2: 复制文件
COMMAND ${CMAKE_COMMAND} -E copy_if_different # 使用CMake内置命令复制仅当文件不同时
${ABS_RESOURCE_FILES} # 要复制的源文件列表(绝对路径)
"${DESTINATION_DIR}" # 最终可执行文件所在的目标目录 (带引号以处理空格)
COMMENT "为 ${ARG_TARGET_NAME} 将资源复制到可执行文件目录: ${DESTINATION_DIR}..." # 构建时显示的注释
VERBATIM # 确保参数(尤其是路径和生成器表达式)被正确处理
)
else()
message(WARNING "**add_resource_file**: 没有有效的资源文件提供给目标 '${ARG_TARGET_NAME}'。")
endif()
# --- 可选: 将资源文件添加到 IDE 项目结构中 ---
if(ABS_RESOURCE_FILES)
set(_source_group_name "Resource Files") # 基础组名
if(ARG_OUTPUT_SUBDIR)
# 使用与目标目录结构匹配的组名
string(STRIP "${ARG_OUTPUT_SUBDIR}" _clean_subdir)
string(REPLACE "\\" "/" _clean_subdir "${_clean_subdir}") # 统一使用正斜杠
string(REGEX REPLACE "^[/]+" "" _clean_subdir "${_clean_subdir}")
string(REGEX REPLACE "[/]+$" "" _clean_subdir "${_clean_subdir}")
if(_clean_subdir)
set(_source_group_name "Resource Files/${_clean_subdir}")
endif()
endif()
# 使用 source_group 将文件添加到 IDE 的指定组下
source_group(${_source_group_name} FILES ${ABS_RESOURCE_FILES})
endif()
endfunction()

View File

@@ -0,0 +1,76 @@
# Mirage 跨平台重构方案
## 项目背景与现状
- **模块分层**: [CMakeLists.txt](CMakeLists.txt:45) 明确自底向上的依赖序列sokol → mirage_config → mirage_image → mirage_core → mirage_platform → mirage_render → mirage_widget → mirage_app符合 UI 框架 layered 设计。
- **平台窗口抽象**: [IWindow](src/mirage_platform/include/mirage_platform/window.h:21) 提供完整的跨平台接口,但当前仅有 [WindowsWindow](src/mirage_platform/src/windows/windows_window.cpp:19) 实现,且未暴露平台选择机制。
- **渲染层耦合**: [mwindow::impl](src/mirage_widget/src/window/mwindow_impl.cpp:40) 依赖 [platform_window](src/mirage_render/src/platform_window/platform_window.h:7) 及其 Windows 后端,绕过 mirage_platform导致 UI 层直接绑定 Windows/Direct3D。
- **输入与 IME**: [WindowsInputManager](src/mirage_platform/src/windows/windows_input.cpp:18) 与 [WindowsIMEManager](src/mirage_platform/src/windows/windows_ime.cpp:22) 未与 widget 层事件总线解耦,仍由 [windows_window_event_handler](src/mirage_render/src/platform_window/windows/windows_window_event.cpp:396) 直接触发。
- **事件循环**: [platform_window::poll_events](src/mirage_render/src/platform_window/windows/windows_window.cpp:189) 与 [mirage_platform::poll_events](src/mirage_platform/src/windows/windows_window.cpp:401) 重复实现,且全局窗口集合通过静态数组维护,缺乏生命周期管理。
- **渲染上下文**: [mirage_render_context::create_window_state](src/mirage_render/src/render/render_context.h:131) 仅返回 [windows_render_state](src/mirage_render/src/render/windows/windows_render_state.cpp:28),而 [windows_mirage_render_context::init](src/mirage_render/src/render/windows/windows_render_context.cpp:14) 硬编码 D3D11缺乏可插拔后端。
## 主要问题、风险和影响
| 问题 | 影响 | 相关位置 |
| --- | --- | --- |
| 平台抽象未贯通UI 仍依赖渲染层 Windows 实现 | 阻碍引入 macOS/Linux 后端,导致模块边界失效 | [mwindow::impl](src/mirage_widget/src/window/mwindow_impl.cpp:40)、[platform_window](src/mirage_render/src/platform_window/platform_window.h:7) |
| 事件与输入栈重复实现 | 维护成本高,行为不一致风险大 | [windows_window_event_handler](src/mirage_render/src/platform_window/windows/windows_window_event.cpp:396)、[mirage_platform::poll_events](src/mirage_platform/src/windows/windows_window.cpp:401) |
| 资源与生命周期缺乏统一管理 | 多窗口或重建交换链时易泄漏或崩溃 | [windows_render_state::rebuild_swapchain](src/mirage_render/src/render/windows/windows_render_state.cpp:164)、静态窗口集合 |
| 构建开关仅考虑 Windows | 其他平台编译链无法建立,限制 CI | [CMakeLists.txt](CMakeLists.txt:47)、[mirage_platform/CMakeLists.txt](src/mirage_platform/CMakeLists.txt:9) |
补充风险:当前 Direct3D 专用路径使得渲染接口与平台抽象紧耦合,一旦在 UI 层更换平台实现,需要同时改动渲染层,违背分层初衷。
## 重构目标与衡量标准
- **目标一:平台层统一抽象**
- 指标:`mirage_widget` 仅通过 `mirage_platform` 接口访问窗口与事件;编译期禁用直接包含 `platform_window`
- 验证CI 静态检查与 include 依赖审计,确保 Widget 模块无 `platform_window` 依赖。
- **目标二:输入与 IME 管线解耦**
- 指标:事件流经统一队列,`mirage_platform::poll_events` 成为唯一入口;至少一个 Widget 示例可接收键鼠与 IME 事件。
- 验证:自动化 UI smoke test记录事件分发链路。
- **目标三:渲染上下文可插拔**
- 指标:新增 `mirage_render_context` 工厂支持注入不同后端Windows D3D11、占位 Mock新增后端无需修改 Widget 层。
- 验证Mock 后端单元测试;示例应用在 Mock 模式下通过逻辑帧。
- **目标四:跨平台构建基础**
- 指标:提供最小 Linux/macOS stub窗口/输入空实现CI 完成配置与编译;新增 CMake 选项 `MIRAGE_PLATFORM=Windows|Stub`
- 验证CI 在 Stub 平台运行配置与构建流程。
## 实施路线与优先级
| 阶段 | 重点 | 依赖 | 预期产出 | 潜在阻碍 |
| --- | --- | --- | --- | --- |
| P0 架构冻结 | 梳理公共接口、定义禁用路径 | 需完成设计评审 | 迁移指南、include 审计报告 | 历史代码依赖 [platform_window](src/mirage_render/src/platform_window/platform_window.h:7) |
| P1 平台层整合 | 引入 `mirage_platform` 工厂,替换 Widget 中的窗口创建流程 | P0 | 平台抽象接口实现、统一事件泵 | 需要保持现有渲染上下文兼容 |
| P2 输入与 IME 重构 | 将事件派发由 `windows_window_event_handler` 转至 `mirage_platform` | P1 | 统一事件队列、IME 光标同步工具 | IME 组合态测试成本高 |
| P3 渲染上下文插件化 | 拆分 D3D11 实现、定义后端注册表 | P1 | D3D11 后端与 Stub 后端、配置选项 | 需同步更新 shader 初始化 |
| P4 验证与回归 | 构建测试矩阵、性能与回退脚本 | P0-P3 | CI job、性能基准、回滚流程 | GPU 驱动差异导致的不确定性 |
```mermaid
flowchart TD
UIWidgets[UI Widgets] --> PlatformAbstraction[Platform Abstraction]
PlatformAbstraction --> EventBus[Event Bus]
PlatformAbstraction --> RenderBridge[Render Bridge]
EventBus --> InputIME[Input IME Processors]
RenderBridge --> GPUBackend[GPU Backend]
InputIME --> UIWidgets
```
## 风险缓解、回滚、测试与发布计划
- **风险缓解**
- 维护双轨实现:在 P1-P3 间保持旧 `platform_window` 开关,允许按模块回退。
- 引入契约测试:为 `mirage_platform` 接口编写最小集成测试,确保多后端一致行为。
- 文档与培训:同步更新模块依赖图与代码示例,降低团队理解成本。
- **回滚策略**
- 按阶段封装开关,使用 CMake 选项 `MIRAGE_USE_LEGACY_PLATFORM` 快速恢复旧逻辑。
- 保留旧事件泵与渲染环境初始化代码至最终阶段后再清理,确保可编译回退。
- **测试与发布计划**
- **单元测试**:覆盖率目标 >85%;针对 `mirage_platform` 接口编写 mock-based 测试,包括 IWindow 生命周期方法、IInputManager 事件转换、IIMEManager 处理器注册/分发;渲染工厂测试验证后端注入与销毁无泄漏。
- **集成测试**:扩展 example/main.cpp 为测试套件模拟多窗口场景验证事件泵一致性IME 测试使用自动化脚本注入中文输入序列,检查 composition/clear 事件完整性;渲染集成测试覆盖 swapchain 重建与 texture 上传路径。
- **端到端测试**:在 CI 中运行 headless 模式Stub 平台)验证逻辑帧完整性;在 Windows runner 上对比前后版本的 UI 交互录像,量化输入延迟(目标 <16ms
- **性能与稳定性**:基准测试包括 1000 帧渲染循环(测量 FPS/内存峰值);负载测试模拟高频 IME 输入与鼠标事件洪水;稳定性检查运行 24h 无崩溃。
- **CI/CD 集成**:新增 GitHub Actions 多平台矩阵Windows/Stubs每个 PR 强制单元+集成测试通过;性能回归警报阈值设为 10% 下降。
- **发布节奏**Betav0.1-beta启用开关内部 dogfoodRCv0.2-rc禁用旧路径beta 用户反馈GAv1.0,移除遗留代码,全面文档更新)。
## 尚需确认或缺失的信息
- 目标平台清单Windows 之外是否包含 Linux/X11、Wayland、macOS、移动端
- 新平台的渲染后端偏好Vulkan、Metal、OpenGL及与 sokol 的集成策略。
- 输入法与键盘布局要求(多语言支持、组合键需求)。
- 示例与生产环境的期望验证范围(是否需要 headless 测试)。
- CI 资源可用性(是否具备多平台 runner与发布时间表。
- 性能或延迟红线指标,用于衡量重构后回归。

View File

@@ -4,7 +4,9 @@ set(CMAKE_CXX_STANDARD 23)
set(SRC_FILES "")
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
add_executable(${PROJECT_NAME} ${SRC_FILES})
target_link_libraries(${PROJECT_NAME} PRIVATE mirage_core)
target_link_libraries(${PROJECT_NAME} PRIVATE mirage_app)
if (MSVC)
target_link_options(${PROJECT_NAME} PRIVATE "/SUBSYSTEM:WINDOWS" "/ENTRY:mainCRTStartup")
endif()
auto_copy_mingw_dll(${PROJECT_NAME})

View File

@@ -1,33 +1,148 @@
//
// Created by Administrator on 25-2-26.
//
#include <iostream>
#include "mirage.h"
#include "core/window/mwindow.h"
#include "font/font_system.h"
#include "style/mirage_style.h"
#include "widget/compound_widget/mbutton.h"
#include "widget/leaf_widget/mtext_block.h"
#include "widget/panel_widget/mbox.h"
#include "widget/widget_new.h"
#include "window/mwindow.h"
#include "utf8.h"
#include "misc/log_util.h"
#include "widget/leaf_widget/meditable_text_box.h"
#include "widget/panel_widget/moverlay.h"
void test_color() {
const char* test_cases[] = {
"#FFF", // hex rgb
"#ff0000", // hex RRGGBB
"#00ff0080", // hex RRGGBBAA
"#1234", // hex RGBA
"rgb(255,0,0)", // rgb int
"rgba(0,255,0,128)", // rgba int
"rgba(0, 0, 255, 255)", // rgba int with spaces
"rgb(1.0, 0.0, 0.0)", // rgb float
"rgba(0.0, 1.0, 0.0, 0.5)", // rgba float
" rgb(0.0, 0.0, 1.0) ", // rgb float with spaces
"rgba(1, 0.5, 0, 1.0)", // rgba mixed - should parse as float
"invalid",
"#12345",
"rgb(300,0,0)",
"rgba(1.1, 0, 0, 1)",
"rgba(100,100,100)" // missing alpha
};
for (const char* test_str: test_cases) {
std::optional<linear_color> color = linear_color::from_string(test_str);
log_info("Parsing '{}': ", test_str);
if (color) {
log_info("Success -> r:{}, g:{}, b:{}, a:{}", color->r, color->g, color->b, color->a);
}
else { log_info("Failed to parse color string: {}", test_str); }
}
}
int main(int argc, char* argv[]) {
mirage_app app;
app.init();
mirage_app::get().init();
auto window = std::make_shared<mwindow>();
window->create_window(800, 600, L"Hello, World!");
font_manager::instance().load_default_font();
const auto name = mirage_style::get().name();
const auto version = mirage_style::get().version();
const auto author = mirage_style::get().author();
const auto description = mirage_style::get().description();
const auto license = mirage_style::get().license();
std::stringstream ss;
ss << "name: " << name << "\n";
ss << "version: " << version << "\n";
ss << "author: " << author << "\n";
ss << "description: " << description << "\n";
ss << "license: " << license;
// const char*转换为std::u32string
const auto& config_info_str = utf8::utf8to32(ss.str());
const auto& window = mwindow::create({ 800, 600 }, L"Hello, World!");
window->show();
window->set_content(
mnew(mv_box)
[
mslot(mv_box)
.horizontal_alignment(horizontal_alignment_t::left)
+ mnew(mbutton)
[
mslot(mbutton)
.margin({10})
.visibility(visibility_t::visible)[
mnew(mtext_block,
.text(config_info_str)
.font_size(15)
)
]
],
mirage_app::get_render_context()->setup_surface(window.get());
mslot(mv_box)
.horizontal_alignment(horizontal_alignment_t::right)
+ mnew(mbutton)
[
mslot(mbutton)
.margin({10})
.visibility(visibility_t::visible)
[
mnew(mtext_block,
.text(U"Hello, World! 你好,世界!\n换行测试1111测试测试测试测试,测试测试😀🐵🙏 😃🐵🙏")
// .text(U"😀🐵🙏😀🐵🙏")
.font_size(15)
.warp_text(true)
)
]
],
widget_manager::get().init_window(window);
mslot(mv_box)
.horizontal_alignment(horizontal_alignment_t::stretch)
+ mnew(meditable_text_box)
]
);
auto weak_border = widget_manager::get().new_widget<mborder>(window.get());
auto border = weak_border.lock();
auto button = widget_manager::get().new_widget<mbutton>(window.get());
border->set_content(button.lock())
.margin({5});
const auto& window2 = mwindow::create({800, 600}, L"Hello, World!");
window2->show();
window2->set_content(
mnew(moverlay)
[
mslot(moverlay)
.h_alignment(horizontal_alignment_t::center)
.v_alignment(vertical_alignment_t::center)
+ mnew(mbutton)
[
mslot(mbutton)
.margin({10})
[
mnew(mtext_block,
.text(U"测试测试")
.font_size(24))
]
],
window->set_content(border);
mslot(moverlay)
.h_alignment(horizontal_alignment_t::center)
.v_alignment(vertical_alignment_t::center)
+ mnew(mbutton)
[
mslot(mbutton)
.margin({10})
[
mnew(mtext_block,
.text(U"测试测试21111")
.font_size(15))
]
]
]
);
app.run();
mirage_app::get().run();
return 0;
}

View File

@@ -1,33 +0,0 @@
cmake_minimum_required(VERSION 3.15)
project(mirage_core LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 26)
if (MSVC)
# MSVC编译器设置C++标准
add_compile_options(/std:c++latest)
# 设置utf-8编码
add_compile_options(/utf-8)
endif ()
find_package(Freetype REQUIRED)
find_package(Eigen3 REQUIRED)
set(SRC_FILES)
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR} SRC_FILES)
add_library(${PROJECT_NAME} STATIC ${SRC_FILES})
target_link_libraries(${PROJECT_NAME} PUBLIC Freetype::Freetype Eigen3::Eigen mustache)
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
add_os_definitions(${PROJECT_NAME})
target_compile_definitions(${PROJECT_NAME} PUBLIC -DNOMINMAX)
# 添加编译shader的自定义命令
add_mirage_shader_directory(${CMAKE_CURRENT_SOURCE_DIR}/shaders)
add_shader_dependencies(${PROJECT_NAME})
if (WIN32)
target_compile_definitions(${PROJECT_NAME} PUBLIC -DSOKOL_D3D11)
target_link_libraries(${PROJECT_NAME} PUBLIC d3d11 dxgi)
elseif (UNIX)
target_compile_definitions(${PROJECT_NAME} PUBLIC -DSOKOL_GLCORE33)
endif ()

View File

@@ -1,4 +0,0 @@
//
// Created by Administrator on 25-3-1.
//
#include "core/window/render_window.h"

View File

@@ -1,8 +0,0 @@
//
// Created by Administrator on 25-3-1.
//
#include "core/window/render_window.h"
void* mirage_window::get_window_handle() const {
return glfwGetIOSurface(window);
}

View File

@@ -1,8 +0,0 @@
//
// Created by Administrator on 25-3-1.
//
#include "core/window/render_window.h"
void* mirage_window::get_window_handle() const {
return glfwGetX11Window(window);
}

View File

@@ -1,8 +0,0 @@
//
// Created by Administrator on 25-3-1.
//
#include "core/window/render_window.h"
void* mirage_window::get_window_handle() const {
return glfwGetCocoaWindow(window);
}

View File

@@ -1,69 +0,0 @@
#include "mwindow.h"
#include "widget_tree/widget_system.h"
geometry_t mwindow::get_window_geometry_in_screen() const {
const auto& local_to_screen = get_local_to_screen_transform();
return { get_window_frame_size().cast<float>(), local_to_screen, {} };
}
geometry_t mwindow::get_window_geometry_in_window() const {
const auto& local_to_window = get_local_to_window_transform();
return { get_window_frame_size().cast<float>(), local_to_window, local_to_window };
}
void mwindow::arrange_children(const geometry_t& in_allotted_geometry) {
if (content_widget_) {
auto child_geo = in_allotted_geometry.make_child({0, 0}, get_window_frame_size().cast<float>());
content_widget_->set_geometry(child_geo);
}
}
void mwindow::set_content(const std::shared_ptr<mwidget>& in_widget) {
content_widget_ = in_widget;
in_widget->set_parent(get_key());
invalidate(invalidate_reason::all);
}
void mwindow::on_resize(int width, int height) {
const Eigen::Vector2i size(width, height);
state_->swapchain.width = width;
state_->swapchain.height = height;
on_resize_delegate.broadcast(size);
transform2d identity;
geometry_t new_geometry(size.cast<float>(), identity, identity);
if (content_widget_) {
content_widget_->set_geometry(new_geometry);
}
invalidate(invalidate_reason::all);
}
void mwindow::rebuild_swapchain() {
state_->rebuild_swapchain(get_window_frame_size());
}
void mwindow::on_move(int x, int y) {
}
void mwindow::on_paint(mirage_paint_context& in_context) {
if (content_widget_)
content_widget_->on_paint(in_context);
}
void mwindow::handle_mouse_move(const Eigen::Vector2f& in_window_pos) {
on_mouse_move_delegate.broadcast(in_window_pos);
}
void mwindow::handle_mouse_button_down(const Eigen::Vector2f& in_window_pos, mouse_button in_button) {
on_mouse_button_down_delegate.broadcast(in_window_pos, in_button);
}
void mwindow::handle_mouse_button_up(const Eigen::Vector2f& in_window_pos, mouse_button in_button) {
on_mouse_button_up_delegate.broadcast(in_window_pos, in_button);
}
void mwindow::handle_mouse_leave() {
on_mouse_leave_delegate.broadcast();
}

View File

@@ -1,413 +0,0 @@
#pragma once
/**
* @file mwindow.h
* @brief 定义UI系统的窗口类
*
* 本文件定义了mirage_window类作为UI系统的窗口容器管理窗口的创建、
* 操作、渲染和内容布局。窗口本身也是一个UI组件可以包含其他组件。
*/
#include "windows/windows_render_context.h"
#include <Eigen/Eigen>
#include "geometry/dpi_helper.h"
#include "geometry/geometry.h"
#include "geometry/layout_transform.h"
#include "misc/delegates.h"
#include "misc/key_type/key_type.h"
#include "widget/mwidget.h"
/**
* @struct mirage_window_state
* @brief 存储窗口渲染状态的结构
*
* 维护与特定窗口关联的渲染资源和状态包括GPU缓冲区、交换链和渲染设置。
* 这是一个虚拟基类,不同平台可以提供特定实现。
*/
struct mirage_window_state {
/**
* @brief 虚析构函数
*
* 确保派生类可以正确清理资源。
*/
virtual ~mirage_window_state() {
clear();
}
/**
* @brief 清理资源
*/
void clear() { on_clear(); }
/** 顶点缓冲区 */
sg_buffer buffer{};
/** 窗口交换链 */
sg_swapchain swapchain{};
/** 渲染绑定 */
sg_bindings bindings{};
/** 渲染管线 */
sg_pipeline pipeline{};
/** 垂直同步标志 */
bool vsync = true;
/**
* @brief 清理资源
*
* 虚方法,由派生类实现以释放平台特定资源。
*/
virtual void on_clear() {}
/**
* @brief 呈现渲染内容
*
* 虚方法,由派生类实现以将渲染内容呈现到窗口。
*/
virtual void present() {}
/**
* @brief 重建交换链
* @param size 新的窗口大小
*
* 当窗口大小改变时重建交换链。虚方法,由派生类实现。
*/
virtual void rebuild_swapchain(const Eigen::Vector2i& size) {}
};
/**
* @class mwindow
* @brief UI系统的窗口类
*
* 表示一个可以包含UI组件的窗口。提供窗口创建、操作、样式设置和内容布局等功能。
* 作为一个组件容器窗口本身也是一个UI组件。
*/
class mwindow : public mwidget {
public:
virtual ~mwindow() override { close(); }
//-------------- 窗口创建和基本操作 --------------
/**
* @brief 创建窗口
* @param width 窗口宽度
* @param height 窗口高度
* @param title 窗口标题
* @return 是否成功创建窗口
*/
bool create_window(int width, int height, const wchar_t* title);
/**
* @brief 显示窗口
*/
void show();
/**
* @brief 隐藏窗口
*/
void hide();
/**
* @brief 关闭窗口
*/
void close();
/**
* @brief 最大化窗口
*/
void maximize();
/**
* @brief 最小化窗口
*/
void minimize();
/**
* @brief 是否可见
*/
bool is_visible() const;
//-------------- 窗口位置和大小 --------------
/**
* @brief 调整窗口大小
* @param width 新宽度
* @param height 新高度
*/
void resize(int width, int height);
/**
* @brief 调整窗口大小
* @param size 新大小向量
*/
void resize(const Eigen::Vector2i& size) { resize(size.x(), size.y()); }
/**
* @brief 移动窗口
* @param x 新的X坐标
* @param y 新的Y坐标
*/
void move(int x, int y);
/**
* @brief 移动窗口
* @param pos 新位置向量
*/
void move(const Eigen::Vector2i& pos) { move(pos.x(), pos.y()); }
//-------------- 窗口属性获取 --------------
/**
* @brief 获取窗口大小
* @return 窗口大小向量
*/
[[nodiscard]] Eigen::Vector2i get_window_size() const;
/**
* @brief 获取窗口位置
* @return 窗口位置向量
*/
[[nodiscard]] Eigen::Vector2i get_window_position() const;
/**
* @brief 获取窗口框架大小
* @return 窗口框架大小向量
*/
[[nodiscard]] Eigen::Vector2i get_window_frame_size() const;
/**
* @brief 获取DPI缩放系数
* @return DPI缩放系数
*/
[[nodiscard]] float get_window_dpi_scale() const;
/**
* @brief 获取窗口句柄
* @return 原生窗口句柄
*/
[[nodiscard]] void* get_window_handle() const;
//-------------- 窗口样式设置 --------------
/**
* @brief 设置窗口标题
* @param title 新标题
* @return 窗口对象引用,用于链式调用
*/
mwindow& set_title(const wchar_t* title);
/**
* @brief 设置是否有最小化按钮
* @param has_minimize_button 是否有最小化按钮
* @return 窗口对象引用,用于链式调用
*/
mwindow& set_has_minimize_button(bool has_minimize_button);
/**
* @brief 设置是否有最大化按钮
* @param has_maximize_button 是否有最大化按钮
* @return 窗口对象引用,用于链式调用
*/
mwindow& set_has_maximize_button(bool has_maximize_button);
/**
* @brief 设置是否有关闭按钮
* @param has_close_button 是否有关闭按钮
* @return 窗口对象引用,用于链式调用
*/
mwindow& set_has_close_button(bool has_close_button);
/**
* @brief 设置是否有边框
* @param has_border 是否有边框
* @return 窗口对象引用,用于链式调用
*/
mwindow& set_has_border(bool has_border);
/**
* @brief 设置是否有标题栏
* @param has_caption 是否有标题栏
* @return 窗口对象引用,用于链式调用
*/
mwindow& set_has_caption(bool has_caption);
/**
* @brief 设置是否有可调整大小的边框
* @param has_resizable_border 是否有可调整大小的边框
* @return 窗口对象引用,用于链式调用
*/
mwindow& set_has_resizable_border(bool has_resizable_border);
/**
* @brief 设置窗口是否总在最前
* @param is_topmost 是否总在最前
* @return 窗口对象引用,用于链式调用
*/
mwindow& set_topmost(bool is_topmost);
//-------------- 事件处理 --------------
/**
* @brief 处理鼠标移动事件
* @param in_window_pos 窗口位置
*/
void handle_mouse_move(const Eigen::Vector2f& in_window_pos);
/**
* @brief 处理鼠标按下事件
* @param in_window_pos 窗口位置
* @param in_button 按下的按钮
*/
void handle_mouse_button_down(const Eigen::Vector2f& in_window_pos, mouse_button in_button);
/**
* @brief 处理鼠标释放事件
* @param in_window_pos 窗口位置
* @param in_button 释放的按钮
*/
void handle_mouse_button_up(const Eigen::Vector2f& in_window_pos, mouse_button in_button);
/**
* @brief 处理光标移出窗口事件
*/
void handle_mouse_leave();
/**
* @brief 轮询所有窗口的事件
* @return 如果有活动窗口则返回true
*
* 静态方法,处理所有窗口的事件队列。
*/
static bool poll_events();
/**
* @brief 获取所有窗口的列表
* @return 窗口指针列表
*
* 静态方法,返回所有已创建窗口的列表。
*/
static const std::vector<mwindow*>& get_windows();
/**
* @brief 处理窗口大小改变事件
* @param width 新宽度
* @param height 新高度
*/
void on_resize(int width, int height);
/**
* @brief 重建交换链
*
* 当窗口大小改变时重建渲染交换链。
*/
void rebuild_swapchain();
/**
* @brief 处理窗口移动事件
* @param x 新的X坐标
* @param y 新的Y坐标
*/
void on_move(int x, int y);
//-------------- 渲染和状态 --------------
/**
* @brief 设置窗口渲染状态
* @param in_state 新的渲染状态
*/
void set_state(std::unique_ptr<mirage_window_state>&& in_state) { state_ = std::move(in_state); }
/**
* @brief 获取窗口渲染状态
* @return 渲染状态常量引用
*/
[[nodiscard]] auto& get_state() const { return *state_; }
//-------------- 坐标变换 --------------
/**
* @brief 获取从本地坐标到屏幕坐标的变换
* @return 变换矩阵
*/
[[nodiscard]] auto get_local_to_screen_transform() const {
return transform2d(get_window_position().cast<float>(), 0, { dpi_helper::get_global_scale() * get_window_dpi_scale(), dpi_helper::get_global_scale() * get_window_dpi_scale() });
}
/**
* @brief 获取从本地坐标到窗口坐标的变换
* @return 变换矩阵
*/
[[nodiscard]] auto get_local_to_window_transform() const {
return transform2d({0, 0}, 0, { dpi_helper::get_global_scale() * get_window_dpi_scale(), dpi_helper::get_global_scale() * get_window_dpi_scale() });
}
/**
* @brief 获取窗口在屏幕中的几何信息
* @return 窗口几何信息
*/
[[nodiscard]] geometry_t get_window_geometry_in_screen() const;
/**
* @brief 获取窗口在窗口中的几何信息
* @return 窗口几何信息
*/
[[nodiscard]] geometry_t get_window_geometry_in_window() const;
//-------------- UI组件覆盖方法 --------------
/**
* @brief 计算窗口的期望大小
* @param in_layout_scale_multiplier 布局缩放系数
* @return 窗口的期望大小
*
* 覆盖基类方法,窗口的期望大小就是其框架大小。
*/
virtual Eigen::Vector2f compute_desired_size(float in_layout_scale_multiplier) const override { return get_window_frame_size().cast<float>(); }
/**
* @brief 排列子组件
* @param in_allotted_geometry 分配的几何区域
*
* 覆盖基类方法,安排窗口内容的布局。
*/
virtual void arrange_children(const geometry_t& in_allotted_geometry) override;
/**
* @brief 设置窗口内容
* @param in_widget 要设置为内容的组件
*
* 设置窗口的主要内容组件。
*/
void set_content(const std::shared_ptr<mwidget>& in_widget);
/**
* @brief 获取窗口内容
* @return 窗口内容组件
*/
[[nodiscard]] auto get_content() const { return content_widget_; }
/**
* @brief 绘制窗口及其内容
* @param in_context 绘制上下文
*
* 覆盖基类方法,处理窗口及其内容的绘制。
*/
virtual void on_paint(mirage_paint_context& in_context) override;
multicast_delegate<const Eigen::Vector2i&> on_resize_delegate;
multicast_delegate<mwindow*> on_close_delegate;
multicast_delegate<const Eigen::Vector2f&> on_mouse_move_delegate;
multicast_delegate<const Eigen::Vector2f&, mouse_button> on_mouse_button_down_delegate;
multicast_delegate<const Eigen::Vector2f&, mouse_button> on_mouse_button_up_delegate;
multicast_delegate<> on_mouse_leave_delegate;
private:
/** 原生窗口句柄 */
void* window_handle_{};
/** 窗口渲染状态 */
std::unique_ptr<mirage_window_state> state_;
/** 窗口内容组件 */
std::shared_ptr<mwidget> content_widget_;
};

View File

@@ -1,241 +0,0 @@
//
// Created by Administrator on 25-3-1.
//
#include "windows_render_context.h"
#include <iostream>
#include <ranges>
#include "misc/angle_literals.h"
#include "core/window/mwindow.h"
#include <windows.h>
#include "windows_window_state.h"
bool windows_mirage_render_context::init() {
try {
// 定义支持的特性级别(从高到低排序)
D3D_FEATURE_LEVEL feature_levels[] = {
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0
};
// 设置设备创建标志
// UINT device_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_SINGLETHREADED; // BGRA支持和单线程模式
UINT device_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; // BGRA支持和单线程模式
#if DEBUG
device_flags |= D3D11_CREATE_DEVICE_DEBUG; // 在Debug模式下启用调试层
#endif
// 定义要尝试的驱动类型数组
constexpr D3D_DRIVER_TYPE driver_types[] = {
D3D_DRIVER_TYPE_HARDWARE, // 首选硬件加速
D3D_DRIVER_TYPE_WARP, // 其次是WARP软件渲染器
D3D_DRIVER_TYPE_REFERENCE // 最后是参考软件渲染器
};
// 尝试按优先级创建设备
HRESULT hr = E_FAIL;
D3D_DRIVER_TYPE used_driver_type = D3D_DRIVER_TYPE_UNKNOWN;
SetProcessDPIAware();
for (const auto& driver_type : driver_types) {
hr = D3D11CreateDevice(
nullptr, // 使用默认适配器
driver_type, // 驱动类型
nullptr, // 软件栅格化模块句柄(仅用于软件设备)
device_flags, // 设备创建标志
feature_levels, // 特性级别数组
ARRAYSIZE(feature_levels), // 特性级别数量
D3D11_SDK_VERSION, // SDK版本
&device, // 输出设备
&feature_level, // 输出获取的特性级别
&device_context // 输出设备上下文
);
if (SUCCEEDED(hr)) {
used_driver_type = driver_type;
break;
}
}
// 检查是否成功创建设备
if (FAILED(hr)) {
std::cerr << "mirage: " << "Failed to create D3D11 device with any driver type. HRESULT: 0x"
<< std::hex << hr << std::dec << std::endl;
cleanup();
return false;
}
// **获取DXGI设备以访问适配器**
IDXGIDevice* dxgi_device = nullptr;
hr = device->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(&dxgi_device));
if (FAILED(hr)) {
std::cerr << "mirage: Failed to get DXGI device. HRESULT: 0x" << std::hex << hr << std::dec << std::endl;
cleanup();
return false;
}
// **获取适配器**
IDXGIAdapter* dxgi_adapter = nullptr;
hr = dxgi_device->GetAdapter(&dxgi_adapter);
dxgi_device->Release();
if (FAILED(hr)) {
std::cerr << "mirage: Failed to get DXGI adapter. HRESULT: 0x" << std::hex << hr << std::dec << std::endl;
cleanup();
return false;
}
// **获取适配器描述信息**
DXGI_ADAPTER_DESC adapter_desc;
hr = dxgi_adapter->GetDesc(&adapter_desc);
if (FAILED(hr)) {
std::wcerr << L"mirage: Failed to get DXGI Adapter description" << std::endl;
}
// **尝试获取最新的DXGI工厂版本 - 从最新的Factory6开始尝试**
// Windows 10 Fall Creators Update (1709)或更高版本支持
hr = dxgi_adapter->GetParent(__uuidof(IDXGIFactory6), reinterpret_cast<void**>(&dxgi_factory));
if (SUCCEEDED(hr)) {
std::cout << "mirage: Using IDXGIFactory6" << std::endl;
} else {
// 尝试Factory5 - Windows 10 Anniversary Update (1607)或更高版本
hr = dxgi_adapter->GetParent(__uuidof(IDXGIFactory5), reinterpret_cast<void**>(&dxgi_factory));
if (SUCCEEDED(hr)) {
std::cout << "mirage: Using IDXGIFactory5" << std::endl;
} else {
// 尝试Factory4 - Windows 10初始版本
hr = dxgi_adapter->GetParent(__uuidof(IDXGIFactory4), reinterpret_cast<void**>(&dxgi_factory));
if (SUCCEEDED(hr)) {
std::cout << "mirage: Using IDXGIFactory4" << std::endl;
} else {
// 尝试Factory3 - Windows 8.1
hr = dxgi_adapter->GetParent(__uuidof(IDXGIFactory3), reinterpret_cast<void**>(&dxgi_factory));
if (SUCCEEDED(hr)) {
std::cout << "mirage: Using IDXGIFactory3" << std::endl;
} else {
// 尝试Factory2 - Windows 8支持FLIP模式交换链
hr = dxgi_adapter->GetParent(__uuidof(IDXGIFactory2), reinterpret_cast<void**>(&dxgi_factory));
if (SUCCEEDED(hr)) {
std::cout << "mirage: Using IDXGIFactory2" << std::endl;
} else {
// 回退到基本Factory1 - Windows 7
hr = dxgi_adapter->GetParent(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(&dxgi_factory));
if (SUCCEEDED(hr)) {
std::cout << "mirage: Using IDXGIFactory1" << std::endl;
} else {
// 最后尝试原始Factory
hr = dxgi_adapter->GetParent(__uuidof(IDXGIFactory), reinterpret_cast<void**>(&dxgi_factory));
if (SUCCEEDED(hr)) {
std::cout << "mirage: Using IDXGIFactory" << std::endl;
} else {
std::cerr << "mirage: Failed to get any DXGI Factory" << std::endl;
dxgi_adapter->Release();
cleanup();
return false;
}
}
}
}
}
}
}
// **检查是否支持撕裂(tearing)功能**
BOOL allow_tearing = FALSE;
// IDXGIFactory5及以上版本支持CheckFeatureSupport
IDXGIFactory5* factory5 = nullptr;
if (SUCCEEDED(dxgi_factory->QueryInterface(__uuidof(IDXGIFactory5), (void**)&factory5))) {
if (SUCCEEDED(factory5->CheckFeatureSupport(
DXGI_FEATURE_PRESENT_ALLOW_TEARING,
&allow_tearing,
sizeof(allow_tearing)))) {
std::cout << "mirage: Tearing support: " << (allow_tearing ? "Yes" : "No") << std::endl;
}
factory5->Release();
}
tearing_supported = allow_tearing == TRUE;
dxgi_adapter->Release();
// 输出初始化成功信息
std::cout << "mirage: D3D11 device created successfully" << std::endl;
std::wcout << L"mirage: Using adapter: " << adapter_desc.Description << std::endl;
// 输出驱动类型信息
auto driver_type_str = "Unknown";
switch (used_driver_type) {
case D3D_DRIVER_TYPE_HARDWARE: driver_type_str = "Hardware";
break;
case D3D_DRIVER_TYPE_WARP: driver_type_str = "WARP";
break;
case D3D_DRIVER_TYPE_REFERENCE: driver_type_str = "Reference";
break;
default: ;
}
std::cout << "mirage: Using driver type: " << driver_type_str << std::endl;
// 输出特性级别信息
auto feature_level_str = "Unknown";
switch (feature_level) {
case D3D_FEATURE_LEVEL_11_1: feature_level_str = "11.1";
break;
case D3D_FEATURE_LEVEL_11_0: feature_level_str = "11.0";
break;
case D3D_FEATURE_LEVEL_10_1: feature_level_str = "10.1";
break;
case D3D_FEATURE_LEVEL_10_0: feature_level_str = "10.0";
break;
default: ;
}
std::cout << "mirage: Using feature level: " << feature_level_str << std::endl;
return true;
} catch (const std::exception& e) {
std::cerr << "mirage: Exception during D3D11 initialization: " << e.what() << std::endl;
cleanup();
return false;
}
}
// 资源清理函数
void windows_mirage_render_context::cleanup() {
mirage_render_context::cleanup();
// 安全释放COM接口
sg_shutdown();
if (device_context) {
device_context->Release();
device_context = nullptr;
}
if (device) {
device->Release();
device = nullptr;
}
if (dxgi_factory) {
dxgi_factory->Release();
dxgi_factory = nullptr;
}
}
sg_environment windows_mirage_render_context::get_environment() {
return {
.d3d11 = {
.device = device,
.device_context = device_context
}
};
}
bool windows_mirage_render_context::setup_surface(mwindow* in_window) {
auto state = std::make_unique<windows_window_state>();
if (!state->init(device, dxgi_factory, in_window)) { return false; }
state->set_tearing_support(tearing_supported);
in_window->set_state(std::move(state));
return true;
}
mirage_render_context* mirage_create_render_context() { return new windows_mirage_render_context(); }

View File

@@ -1,247 +0,0 @@
//
// Created by Administrator on 25-3-1.
//
#include <iostream>
#include "core/window/mwindow.h"
#include <Windows.h>
#define WINDOW_HANDLE static_cast<HWND>(window_handle_)
std::vector<mwindow*> windows;
mwindow* get_window_from_hwnd(const HWND hwnd) {
for (const auto& window: windows) {
if (window->get_window_handle() == hwnd) {
return window;
}
}
return nullptr;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
bool processed = false;
if (uMsg == WM_CLOSE) {
std::erase_if(windows, [hwnd](mwindow* window) {
if (window->get_window_handle() == hwnd) {
window->close();
return true;
}
return false;
});
processed = true;
}
if (uMsg == WM_DESTROY) {
PostQuitMessage(0);
processed = true;
}
if (uMsg == WM_MOVE) {
if (const auto window = get_window_from_hwnd(hwnd)) { window->on_move(LOWORD(lParam), HIWORD(lParam)); }
processed = true;
}
// if (uMsg == WM_PAINT) {
// if (const auto window = get_window_from_hwnd(hwnd)) { window->paint(); }
// return 0;
// }
if (uMsg == WM_SIZE) {
if (const auto window = get_window_from_hwnd(hwnd)) {
window->on_resize(LOWORD(lParam), HIWORD(lParam));
if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED) { window->rebuild_swapchain(); }
}
processed = true;
}
if (uMsg == WM_MOUSEMOVE) {
if (const auto window = get_window_from_hwnd(hwnd)) {
const int x = LOWORD(lParam);
const int y = HIWORD(lParam);
const Eigen::Vector2f pos(static_cast<float>(x), static_cast<float>(y));
window->handle_mouse_move(pos);
}
processed = true;
}
if (uMsg >= WM_LBUTTONDOWN && uMsg <= WM_MBUTTONDBLCLK) {
if (const auto window = get_window_from_hwnd(hwnd)) {
const int x = LOWORD(lParam);
const int y = HIWORD(lParam);
const Eigen::Vector2f pos(static_cast<float>(x), static_cast<float>(y));
const auto action = platform_event_to_mouse_action(uMsg, wParam);
const auto button = platform_event_to_mouse_button(uMsg, wParam);
if (action == mouse_action::press)
window->handle_mouse_button_down(pos, button);
if (action == mouse_action::release)
window->handle_mouse_button_up(pos, button);
}
processed = true;
}
if (uMsg == WM_MOUSELEAVE) {
if (const auto window = get_window_from_hwnd(hwnd)) {
window->handle_mouse_leave();
}
processed = true;
}
if (processed) { return 0; }
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
bool mwindow::create_window(int width, int height, const wchar_t* title) {
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = GetModuleHandle(nullptr);
wc.lpszClassName = L"mirage_window_class";
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
RegisterClass(&wc);
RECT rect = { 0, 0, width, height };
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);
window_handle_ = (void*)CreateWindowW(
L"mirage_window_class", title,
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
rect.right - rect.left, rect.bottom - rect.top,
NULL, NULL, GetModuleHandleW(NULL), NULL
);
if (!window_handle_) {
std::cerr << "Failed to create window" << std::endl;
return false;
}
windows.push_back(this);
return true;
}
void mwindow::show() { ShowWindow(WINDOW_HANDLE, SW_SHOW); }
void mwindow::hide() { ShowWindow(WINDOW_HANDLE, SW_HIDE); }
void mwindow::close() {
if (!window_handle_) { return; }
on_close_delegate.broadcast(this);
DestroyWindow(WINDOW_HANDLE);
window_handle_ = nullptr;
}
void mwindow::maximize() { ShowWindow(WINDOW_HANDLE, SW_MAXIMIZE); }
void mwindow::minimize() { ShowWindow(WINDOW_HANDLE, SW_MINIMIZE); }
bool mwindow::is_visible() const {
// 判断最小化和隐藏状态
return IsWindowVisible(WINDOW_HANDLE) && !IsIconic(WINDOW_HANDLE);
}
void mwindow::move(int x, int y) {
RECT rect;
GetWindowRect(WINDOW_HANDLE, &rect);
MoveWindow(WINDOW_HANDLE, x, y, rect.right - rect.left, rect.bottom - rect.top, TRUE);
on_move(x, y);
}
void mwindow::resize(int width, int height) {
RECT rect;
GetWindowRect(WINDOW_HANDLE, &rect);
MoveWindow(WINDOW_HANDLE, rect.left, rect.top, width, height, TRUE);
}
Eigen::Vector2i mwindow::get_window_size() const {
// 获取窗口大小, 包括边框和标题栏
RECT rect;
if (GetWindowRect(WINDOW_HANDLE, &rect)) {
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
return Eigen::Vector2i(width, height);
}
return Eigen::Vector2i(0, 0);
}
Eigen::Vector2i mwindow::get_window_frame_size() const {
// 获取窗口大小, 不包括边框和标题栏 (客户区大小)
RECT rect;
if (GetClientRect(WINDOW_HANDLE, &rect)) {
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
return Eigen::Vector2i(width, height);
}
return Eigen::Vector2i(0, 0);
}
float mwindow::get_window_dpi_scale() const {
return 1.f;
}
Eigen::Vector2i mwindow::get_window_position() const {
RECT rect;
if (!GetWindowRect(WINDOW_HANDLE, &rect)) { return {}; }
return { rect.left, rect.top };
}
void* mwindow::get_window_handle() const { return window_handle_; }
mwindow& mwindow::set_title(const wchar_t* title) {
SetWindowText(WINDOW_HANDLE, title);
return *this;
}
mwindow& mwindow::set_has_minimize_button(bool has_minimize_button) {
auto style = GetWindowLong(WINDOW_HANDLE, GWL_STYLE);
if (has_minimize_button) { style |= WS_MINIMIZEBOX; } else { style &= ~WS_MINIMIZEBOX; }
SetWindowLong(WINDOW_HANDLE, GWL_STYLE, style);
return *this;
}
mwindow& mwindow::set_has_maximize_button(bool has_maximize_button) {
auto style = GetWindowLong(WINDOW_HANDLE, GWL_STYLE);
if (has_maximize_button) { style |= WS_MAXIMIZEBOX; } else { style &= ~WS_MAXIMIZEBOX; }
SetWindowLong(WINDOW_HANDLE, GWL_STYLE, style);
return *this;
}
mwindow& mwindow::set_has_close_button(bool has_close_button) {
auto style = GetWindowLong(WINDOW_HANDLE, GWL_STYLE);
if (has_close_button) { style |= WS_SYSMENU; } else { style &= ~WS_SYSMENU; }
SetWindowLong(WINDOW_HANDLE, GWL_STYLE, style);
return *this;
}
mwindow& mwindow::set_has_border(bool has_border) {
auto style = GetWindowLong(WINDOW_HANDLE, GWL_STYLE);
if (has_border) { style |= WS_BORDER; } else { style &= ~WS_BORDER; }
SetWindowLong(WINDOW_HANDLE, GWL_STYLE, style);
return *this;
}
mwindow& mwindow::set_has_caption(bool has_caption) {
auto style = GetWindowLong(WINDOW_HANDLE, GWL_STYLE);
if (has_caption) { style |= WS_CAPTION; } else { style &= ~WS_CAPTION; }
SetWindowLong(WINDOW_HANDLE, GWL_STYLE, style);
return *this;
}
mwindow& mwindow::set_has_resizable_border(bool has_resizable_border) {
auto style = GetWindowLong(WINDOW_HANDLE, GWL_STYLE);
if (has_resizable_border) { style |= WS_THICKFRAME; } else { style &= ~WS_THICKFRAME; }
SetWindowLong(WINDOW_HANDLE, GWL_STYLE, style);
return *this;
}
mwindow& mwindow::set_topmost(bool is_topmost) {
auto style = GetWindowLong(WINDOW_HANDLE, GWL_EXSTYLE);
if (is_topmost) { style |= WS_EX_TOPMOST; } else { style &= ~WS_EX_TOPMOST; }
SetWindowLong(WINDOW_HANDLE, GWL_EXSTYLE, style);
return *this;
}
bool mwindow::poll_events() {
MSG msg;
bool has_messages = false;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
has_messages = true;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return has_messages;
}
const std::vector<mwindow*>& mwindow::get_windows() { return windows; }

View File

@@ -1,11 +0,0 @@
#include "arranged_children.h"
#include "widget/mwidget.h"
void arranged_children::add_widget(const arranged_widget& in_widget_geometry) {
add_widget(in_widget_geometry.get_widget()->get_visibility(), in_widget_geometry);
}
void arranged_children::insert_widget(const arranged_widget& in_widget_geometry, size_t in_index) {
insert_widget(in_widget_geometry.get_widget()->get_visibility(), in_widget_geometry, in_index);
}

View File

@@ -1,200 +0,0 @@
#pragma once
/**
* @file arranged_children.h
* @brief 定义UI组件布局后的几何结构和组织
*
* 本文件定义了arranged_widget和arranged_children类用于管理
* 已排列的UI组件及其几何关系。这些类在布局系统中用于存储和操作
* 组件的位置和大小。
*/
#include <utility>
#include "geometry.h"
#include "misc/mirage_type.h"
class mwidget;
/**
* @class arranged_widget
* @brief 表示已排列好的单个组件及其几何信息
*
* arranged_widget将一个UI组件与其对应的几何信息关联起来
* 用于在UI布局系统中跟踪组件的位置和大小。
*/
class arranged_widget {
public:
/**
* @brief 构造函数
* @param in_geometry 组件的几何信息
* @param in_widget 组件的共享指针
*/
arranged_widget(geometry_t in_geometry, std::shared_ptr<mwidget> in_widget) : geometry_(std::move(in_geometry)),
widget_(std::move(in_widget)) {
}
/**
* @brief 获取几何信息引用
* @return 几何信息的引用
*/
[[nodiscard]] auto& get_geometry() { return geometry_; }
/**
* @brief 获取几何信息常量引用
* @return 几何信息的常量引用
*/
[[nodiscard]] const auto& get_geometry() const { return geometry_; }
/**
* @brief 获取组件常量引用
* @return 组件共享指针的常量引用
*/
[[nodiscard]] const auto& get_widget() const { return widget_; }
/**
* @brief 设置新的几何信息
* @param in_geometry 新的几何信息
*/
void set_geometry(const geometry_t& in_geometry) { geometry_ = in_geometry; }
/**
* @brief 相等比较操作符
* @param in_other 要比较的另一个arranged_widget
* @return 如果组件指针相同则返回true
*/
[[nodiscard]]
auto operator==(const arranged_widget& in_other) const {
return widget_ == in_other.widget_;
}
private:
/** 组件的几何信息 */
geometry_t geometry_;
/** 组件的共享指针 */
std::shared_ptr<mwidget> widget_;
};
/**
* @class arranged_children
* @brief 管理一组已排列的子组件
*
* arranged_children用于存储和管理一组已排列的子组件提供了添加、插入和
* 过滤子组件的功能。它基于可见性属性对子组件进行筛选。
*/
class arranged_children {
public:
/**
* @brief 构造函数
* @param in_visibility_filter 可见性过滤器
*
* 创建一个具有指定可见性过滤条件的arranged_children实例。
*/
explicit arranged_children(visibility_t in_visibility_filter = visibility_t::any_visible) : visibility_filter_(in_visibility_filter) {}
//-------------- 可见性过滤 --------------
/**
* @brief 检查是否接受指定的可见性
* @param in_visibility 要检查的可见性
* @return 如果该可见性级别被接受则返回true
*/
[[nodiscard]] auto accepts(visibility_t in_visibility) const {
return has_any_flag(in_visibility, visibility_filter_);
}
/**
* @brief 设置可见性过滤器
* @param in_visibility_filter 新的可见性过滤级别
*/
void set_filter(visibility_t in_visibility_filter) {
visibility_filter_ = in_visibility_filter;
}
/**
* @brief 添加标志到筛选器
* @param in_flag 要添加的可见性标志
*/
void add_filter_flag(visibility_t in_flag) {
visibility_filter_ |= in_flag;
}
/**
* @brief 移除筛选器中的标志
* @param in_flag 要移除的可见性标志
*/
void remove_filter_flag(visibility_t in_flag) {
visibility_filter_ &= ~in_flag;
}
//-------------- 子项操作 --------------
/**
* @brief 反转子组件的顺序
*/
void reverse() {
std::ranges::reverse(children_);
}
/**
* @brief 添加带有可见性覆盖的组件
* @param in_visibility_override 可见性覆盖值
* @param in_widget_geometry 要添加的已排列组件
*
* 根据可见性过滤规则添加组件,只有当可见性满足条件时才会添加。
*/
void add_widget(visibility_t in_visibility_override, const arranged_widget& in_widget_geometry) {
if (accepts(in_visibility_override))
children_.push_back(in_widget_geometry);
}
/**
* @brief 在指定位置插入带有可见性覆盖的组件
* @param in_visibility_override 可见性覆盖值
* @param in_widget_geometry 要插入的已排列组件
* @param in_index 插入位置的索引
*
* 根据可见性过滤规则在指定位置插入组件,只有当可见性满足条件时才会插入。
*/
void insert_widget(visibility_t in_visibility_override, const arranged_widget& in_widget_geometry, size_t in_index) {
if (accepts(in_visibility_override))
children_.insert(children_.begin() + in_index, in_widget_geometry);
}
/**
* @brief 添加组件
* @param in_widget_geometry 要添加的已排列组件
*
* 使用组件自身的可见性进行添加。
*/
void add_widget(const arranged_widget& in_widget_geometry);
/**
* @brief 在指定位置插入组件
* @param in_widget_geometry 要插入的已排列组件
* @param in_index 插入位置的索引
*
* 使用组件自身的可见性在指定位置进行插入。
*/
void insert_widget(const arranged_widget& in_widget_geometry, size_t in_index);
//-------------- 访问器 --------------
/**
* @brief 获取子组件列表的常量引用
* @return 子组件列表的常量引用
*/
[[nodiscard]] const auto& get_children() const { return children_; }
/**
* @brief 获取子组件列表的引用
* @return 子组件列表的引用
*/
[[nodiscard]] auto& get_children() { return children_; }
private:
/** 可见性过滤级别 */
visibility_t visibility_filter_;
/** 子组件列表 */
std::vector<arranged_widget> children_;
};

View File

@@ -1 +0,0 @@
#include "geometry.h"

View File

@@ -1,39 +0,0 @@
#pragma once
#include <Eigen/Eigen>
#include <vector>
#include "layout_transform.h"
#include "rect.h"
class geometry_transformer {
public:
template<typename T>
static auto transform_points(const transform2d& in_transform, const std::vector<Eigen::Vector2<T>>& in_points) {
std::vector<Eigen::Vector2<T>> result;
result.reserve(in_points.size());
for (const auto& point : in_points) {
result.push_back(in_transform.transform_point(point));
}
return result;
}
template<typename T>
static auto transform_rect(const transform2d& in_transform, const rect_t<T>& in_rect) {
// 变换矩形的四个顶点
auto top_left = in_transform.transform_point(in_rect.top_left());
auto top_right = in_transform.transform_point(in_rect.top_right());
auto bottom_left = in_transform.transform_point(in_rect.bottom_left());
auto bottom_right = in_transform.transform_point(in_rect.bottom_right());
// 如果只有平移和缩放(没有旋转),则可以直接计算包围盒
if (std::abs(in_transform.get_matrix()(0, 1)) < 1e-6f &&
std::abs(in_transform.get_matrix()(1, 0)) < 1e-6f
) {
auto min = top_left.cwiseMin(top_right).cwiseMin(bottom_left).cwiseMin(bottom_right);
auto max = top_left.cwiseMax(top_right).cwiseMax(bottom_left).cwiseMax(bottom_right);
return rect_t<T>(min, max - min);
}
return rect_t<T>::bounding_box({top_left, top_right, bottom_left, bottom_right});
}
};

View File

@@ -1,68 +0,0 @@
#define SOKOL_IMPL
#include "mirage.h"
#include <iostream>
#include <thread>
#include "core/window/mwindow.h"
#include "misc/mirage_scoped_duration_timer.h"
void mirage_log(const char* tag, uint32_t log_level, uint32_t log_item_id, const char* message_or_null,
uint32_t line_nr, const char* filename_or_null, void* user_data) {
if (log_level == 0) // painc
std::cerr << "sg: " << message_or_null << std::endl;
else if (log_level == 1) // error
std::cerr << "sg: " << message_or_null << std::endl;
else if (log_level == 2) // warning
std::cout << "sg: " << message_or_null << std::endl;
else if (log_level == 3) // info
std::clog << "sg: " << message_or_null << std::endl;
}
void mirage_app::init() {
duration_type duration;
{
mirage_scoped_duration_timer timer(duration);
last_time = get_current_time();
render_context = mirage_create_render_context();
render_context->init();
const sg_desc desc = {
.logger = {
.func = mirage_log,
.user_data = nullptr
},
.environment = render_context->get_environment(),
};
sg_setup(desc);
render_context->end_init();
}
// 初始化用时
std::cout << "mirage: " << "Initialization took " << std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() << "ms" << std::endl;
}
void mirage_app::run() {
// std::thread render_thread(&mirage_app::render_thread, this);
while (!mwindow::get_windows().empty()) {
delta_time = get_current_time() - last_time;
mwindow::poll_events();
widget_manager::get().update();
last_time = get_current_time();
// std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::this_thread::yield();
}
running = false;
render_context->cleanup();
delete render_context;
}
void mirage_app::render_thread() {
// while (running) {
// std::scoped_lock lock(render_mutex);
// }
}

View File

@@ -0,0 +1,8 @@
project(mirage_app)
set(SRC_FILES)
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
add_library(${PROJECT_NAME} STATIC ${SRC_FILES})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(${PROJECT_NAME} PUBLIC mirage_core mirage_widget mirage_render)

View File

@@ -0,0 +1,105 @@
#include "mirage.h"
#include <iostream>
#include <thread>
#include "font/font_system.h"
#include "misc/log_util.h"
#include "window/mwindow.h"
#include "misc/mirage_scoped_duration_timer.h"
#include "platform_window/platform_window.h"
#include "style/mirage_style.h"
void mirage_log(const char* tag, uint32_t log_level, uint32_t log_item_id, const char* message_or_null,
uint32_t line_nr, const char* filename_or_null, void* user_data) {
/* 处理NULL指针情况 */
const char* tag_str = tag ? tag : "(null)";
const char* msg_str = message_or_null ? message_or_null : "(no message)";
const char* file_str = filename_or_null ? filename_or_null : "(unknown)";
/* 根据log_level确定日志级别字符串和输出流 */
const char* level_str;
std::ostream* output_stream;
switch (log_level) {
case 0:
level_str = "PANIC";
output_stream = &std::cerr;
break;
case 1:
level_str = "ERROR";
output_stream = &std::cerr;
break;
case 2:
level_str = "WARNING";
output_stream = &std::cerr;
break;
case 3:
level_str = "INFO";
output_stream = &std::cout;
break;
default:
level_str = "UNKNOWN";
output_stream = &std::cerr; // 默认使用stderr
break;
}
/* 打印格式化的日志消息到相应的流 */
std::println(*output_stream, "sokol_gfx: [{}][{}][ID:{}][{}:{}] {}", tag_str, level_str, log_item_id, file_str, line_nr, msg_str);
/* 在此实现中未使用user_data */
(void) user_data;
}
void mirage_app::init() {
duration_type duration;
{
mirage_scoped_duration_timer timer(duration);
last_time = get_current_time();
config_options_t o{};
o.enable_hot_reload = true;
if (!mirage_style::get().load_config("default_style.toml", o)) {
log_error("无法加载样式配置");
}
render_context = mirage_create_render_context();
render_context->init();
sg_desc desc = {};
desc.logger = {
.func = mirage_log,
.user_data = nullptr
};
desc.environment = render_context->get_environment();
sg_setup(desc);
render_context->end_init();
}
// 初始化用时
log_info("初始化耗时 {} ms", std::chrono::duration_cast<std::chrono::milliseconds>(duration).count());
}
void mirage_app::run() {
while (!platform_window::get_windows().empty()) {
delta_time = get_current_time() - last_time;
platform_window::poll_events();
for (const auto& window: mwindow::get_all()) {
if (const auto shared_window = window.lock()) {
shared_window->update(delta_time);
}
}
last_time = get_current_time();
// TODO 不应该在任何时候都睡眠1ms, 而是应该在没有事件时睡眠
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// 清理资源
log_info("清理资源");
running = false;
texture_sampler_builder::clear();
render_context->cleanup();
delete render_context;
font_manager::instance().destroy();
}

View File

@@ -1,7 +1,6 @@
#pragma once
#include <mutex>
#include "core/render_context.h"
#include "misc/mirage_type.h"
#include "render/render_context.h"
class mirage_render_context;
@@ -10,15 +9,25 @@ public:
void init();
void run();
static mirage_app& get() {
static mirage_app instance;
return instance;
}
[[nodiscard]] bool is_running() const {
return running;
}
[[nodiscard]] duration_type get_delta_time() const {
return delta_time;
}
[[nodiscard]] static mirage_render_context* get_render_context() {
return render_context;
}
private:
void render_thread();
inline static mirage_render_context* render_context{};
time_type last_time = {};
duration_type delta_time = {};
std::atomic_bool running = true;
// std::mutex render_mutex;
};

View File

@@ -0,0 +1,25 @@
project(mirage_config)
# 检索源文件(自动平台过滤)
set(SRC_FILES)
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/include SRC_FILES)
# 创建库
add_library(${PROJECT_NAME} STATIC ${SRC_FILES})
# 设置包含目录
target_include_directories(${PROJECT_NAME}
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
)
find_package(tomlplusplus CONFIG REQUIRED)
# 链接依赖
target_link_libraries(${PROJECT_NAME} PUBLIC
tomlplusplus
)
# 添加平台宏
add_os_definitions(${PROJECT_NAME})

View File

@@ -0,0 +1,4 @@
//
// Created by 46944 on 25-7-1.
//
#include "config.h"

View File

@@ -0,0 +1,510 @@
/**
* @file config.h
* @brief 定义一个线程安全的、支持热重载的 TOML 配置文件管理器。
*
* 该文件包含一个通用的 C++ 头文件,实现了一个单例模式的配置管理器 `config_t`。
* 它能够解析 TOML 格式的配置文件,提供线程安全的数据读取接口,
* 并能监控配置文件的变化以实现自动热重载。
*/
#pragma once
// --- 标准库与第三方库依赖 (Standard Library & Third-party Dependencies) ---
#include <filesystem> // 用于处理文件路径
#include <print> // C++23: 用于格式化输出 (替代 iostream)
#include <string_view> // 用于高效、非拥有式的字符串视图
#include <expected> // C++23: 用于返回成功值或错误信息
#include <toml++/toml.h> // TOML 解析库 toml++
#include <shared_mutex> // 用于实现读写锁,允许多读单写
#include <atomic> // 用于原子操作,保证多线程下的数据完整性
#include <thread> // 用于创建和管理线程 (jthread 提供了自动 join 和停止令牌)
#include <chrono> // 用于处理时间相关的操作 (如重载间隔)
#include <functional> // 用于存储和调用回调函数 (std::function)
#include <iostream> // 用于向标准错误流输出日志
#include <stop_token> // C++20: 用于优雅地停止线程
// --- 辅助工具 (Utilities) ---
namespace config_convert {
/**
* @brief 概念 (Concept): 检查一个类型是否类似于字符串。
*
* 这个概念用于模板元编程,判断类型 U 是否是 `const char*` 或 `char[]`。
* 这在模板函数中处理字符串字面量时非常有用。
* @tparam U 待检查的类型。
*/
template<typename U>
concept StringLike = std::is_same_v<std::decay_t<U>, const char*> ||
(std::is_array_v<std::remove_reference_t<U>> &&
std::is_same_v<std::remove_extent_t<std::remove_reference_t<U>>, char>);
}
// --- 配置选项 (Configuration Options) ---
/**
* @struct config_options_t
* @brief `config_t` 类的配置选项结构体。
*
* 用于在加载配置文件时,定义管理器的行为,如是否启用热重载。
*/
struct config_options_t {
bool enable_hot_reload = false; // 是否启用热重载功能
std::chrono::milliseconds reload_interval{ 1000 }; // 热重载时检查文件变化的间隔 (默认1秒)
bool notify_on_reload = true; // 热重载成功后是否通知所有回调函数
};
// --- 核心配置管理器 (Core Configuration Manager) ---
/**
* @class config_t
* @brief 一个线程安全的、支持热重载的 TOML 配置管理器 (单例模式)。
*
* @tparam T 用于实现 CRTP (Curiously Recurring Template Pattern),确保每个派生类都是一个独立的单例。
*
* 设计思路:
* 1. **单例模式**: 通过 `get()`静态方法提供全局唯一的访问点。
* 2. **线程安全**: 使用 `std::shared_mutex` (读写锁) 保护内部数据。读取操作使用共享锁,写入/重载操作使用独占锁,以提高并发性能。
* 3. **热重载**: 使用 `std::jthread` 在后台监控配置文件的修改时间。一旦检测到变化,会自动重新加载配置。
* 4. **错误处理**: 使用 `std::expected` 返回操作结果,清晰地分离成功路径和错误路径。
* 5. **回调机制**: 允许用户注册回调函数,以便在配置更新时收到通知。
* 6. **易用性**: 提供了 `get`, `get_or`, `get_as` 等多种便捷的 API 来获取配置项。
*/
template<typename T>
class config_t {
public:
// --- 类型别名 (Type Aliases) ---
using callback_t = std::function<void(const toml::table&)>; // 回调函数类型,参数为重载后的配置表
using clock_t = std::chrono::steady_clock; // 用于计时的时钟类型
// --- 构造与析构 (Construction & Destruction) ---
/**
* @brief 获取配置管理器的单例实例。
* @returns {T&} 对单例实例的引用。
* @example auto& my_config = MyConfigManager::get();
*/
[[nodiscard]] static auto& get() {
static T instance;
return instance;
}
/**
* @brief 析构函数,确保在对象销毁时停止文件监控线程。
*/
~config_t() { stop_watching(); }
// --- 核心功能 (Core Functionality) ---
/**
* @brief 从文件加载配置。
*
* @param {const std::filesystem::path&} in_filename - TOML 配置文件的路径。
* @param {const config_options_t&} options - 配置管理器的行为选项。
* @returns {std::expected<void, std::string>} 如果成功,返回空 `expected`;如果失败,返回包含错误信息的 `unexpected`。
* @example
* if (auto result = config.load_config("settings.toml", {.enable_hot_reload = true}); !result) {
* std::cerr << "Error: " << result.error() << std::endl;
* }
*/
[[nodiscard]] auto load_config(const std::filesystem::path& in_filename,
const config_options_t& options = {}) -> std::expected<void, std::string> {
std::unique_lock lock(mutex_); // 获取独占锁,因为要修改内部状态
auto result = load_config_impl(in_filename);
if (!result) { return result; } // 如果加载失败,直接返回错误
config_path_ = in_filename;
options_ = options;
// 如果启用了热重载,则启动监控线程
if (options.enable_hot_reload) { start_watching(); }
return {}; // 返回成功
}
/**
* @brief 手动触发一次配置文件的重新加载。
* @returns {std::expected<void, std::string>} 成功或失败的 `expected` 对象。
*/
[[nodiscard]] auto reload() -> std::expected<void, std::string> {
std::unique_lock lock(mutex_); // 获取独占锁
if (config_path_.empty()) {
return std::unexpected("错误:尚未加载任何配置文件 (Error: No config file loaded)");
}
auto old_table = std::move(tbl_); // 备份旧配置,以便重载失败时恢复
auto result = load_config_impl(config_path_);
if (!result) {
tbl_ = std::move(old_table); // 重载失败,恢复旧配置
return result;
}
// 如果配置了通知,则在重载成功后调用所有回调函数
if (options_.notify_on_reload) { notify_callbacks(); }
return {}; // 返回成功
}
/**
* @brief 将当前内存中的配置保存到文件。
* @param {const std::filesystem::path&} in_filename - 目标文件路径。
* @returns {std::expected<void, std::string>} 成功或失败的 `expected` 对象。
*/
[[nodiscard]] auto save_config(const std::filesystem::path& in_filename) const -> std::expected<void, std::string> {
std::shared_lock lock(mutex_); // 获取共享锁,因为只是读取 tbl_
try {
std::ofstream file(in_filename);
if (!file) { return std::unexpected(std::format("无法打开文件: {}", in_filename.string())); }
file << tbl_;
return {};
}
catch (const std::exception& e) {
return std::unexpected(std::format("保存失败 {}: {}", in_filename.string(), e.what()));
}
}
// --- 回调管理 (Callback Management) ---
/**
* @brief 注册一个回调函数,当配置重载时被调用。
* @param {callback_t} callback - 要注册的回调函数。
* @returns {size_t} 回调函数的唯一 ID可用于之后注销。
*/
size_t register_callback(callback_t callback) {
std::unique_lock lock(mutex_);
size_t id = next_callback_id_++;
callbacks_[id] = std::move(callback);
return id;
}
/**
* @brief 根据 ID 注销一个已注册的回调函数。
* @param {size_t} id - `register_callback` 返回的 ID。
*/
void unregister_callback(size_t id) {
std::unique_lock lock(mutex_);
callbacks_.erase(id);
}
// --- 线程安全的数据访问 API (Thread-Safe Data Access API) ---
/**
* @brief 获取一个配置节点视图。
* @tparam Ks - 一个或多个可以转换为 `std::string_view` 的键名。
* @param {const Ks&...} keys - 访问节点的路径,例如 "database", "host"。
* @returns {std::optional<toml::node_view<const toml::node>>} 如果找到节点,返回节点视图;否则返回 `std::nullopt`。
* @example auto host = config.get("database", "host");
*/
template<typename... Ks> requires (std::convertible_to<Ks, std::string_view> && ...)
[[nodiscard]] auto get(const Ks&... keys) const noexcept -> std::optional<toml::node_view<const toml::node>> {
std::shared_lock lock(mutex_); // 获取共享锁,允许多个线程同时读取
try {
// at_path 会抛出异常,所以需要 try-catch
if (auto node = tbl_.at_path(make_path(keys...))) {
return node;
}
return std::nullopt;
}
catch (...) {
return std::nullopt; // 发生任何异常都视为未找到
}
}
/**
* @brief 获取一个配置项的值,如果不存在或类型不匹配,则返回默认值。
* @tparam T_Value - 期望获取的值的类型。
* @tparam Ks - 键名类型。
* @param {const T_Value&} default_value - 如果找不到或类型不匹配,返回此默认值。
* @param {const Ks&...} keys - 访问节点的路径。
* @returns 值或默认值。
* @example int port = config.get_or(5432, "database", "port");
*/
template<typename T_Value, typename... Ks>
requires (std::convertible_to<Ks, std::string_view> && ...)
[[nodiscard]] auto get_or(const T_Value& default_value, const Ks&... keys) const {
std::shared_lock lock(mutex_);
if (auto node = get_unlocked(keys...)) {
// 特殊处理字符串类型,因为 TOML 库返回的是 std::string
if constexpr (config_convert::StringLike<T_Value>) {
if (auto value = node->template value<std::string>()) { return *value; }
return std::string(default_value); // 保证返回 std::string
}
else {
// 其他类型直接获取
if (auto value = node->template value<T_Value>()) { return *value; }
return default_value;
}
}
// 未找到节点,返回默认值
if constexpr (config_convert::StringLike<T_Value>) { return std::string(default_value); }
else { return default_value; }
}
/**
* @brief 获取指定类型的配置项值。
* @tparam T_Value - 期望转换的目标类型。
* @tparam Ks - 键名类型。
* @param {const Ks&...} keys - 访问节点的路径。
* @returns {std::optional<...>} 如果找到且类型转换成功,返回包含值的 optional否则返回 `std::nullopt`。
* @example auto port = config.get_as<int>("database", "port");
*/
template<typename T_Value, typename... Ks>
requires (std::convertible_to<Ks, std::string_view> && ...)
[[nodiscard]] auto get_as(const Ks&... keys) const
-> std::optional<std::conditional_t<config_convert::StringLike<T_Value>, std::string, T_Value>> {
std::shared_lock lock(mutex_);
if (auto node = get_unlocked(keys...)) {
if constexpr (config_convert::StringLike<T_Value>) {
return node->template value<std::string>();
}
else {
return node->template value<T_Value>();
}
}
return std::nullopt;
}
/**
* @brief 获取字符串配置项的便捷方法 (重载版本)。
* @param {const std::string&} default_value - 默认值。
* @param {const Ks&...} keys - 访问节点的路径。
* @returns {std::string} 结果字符串或默认值。
*/
template<typename... Ks> requires (std::convertible_to<Ks, std::string_view> && ...)
[[nodiscard]] auto get_string(const std::string& default_value, const Ks&... keys) const noexcept -> std::string {
return get_or<std::string>(default_value, keys...);
}
/**
* @brief 获取字符串配置项的便捷方法 (重载版本,接受字符串字面量)。
* @param {const char*} default_value - 默认值。
* @param {const Ks&...} keys - 访问节点的路径。
* @returns {std::string} 结果字符串或默认值。
*/
template<typename... Ks> requires (std::convertible_to<Ks, std::string_view> && ...)
[[nodiscard]] auto get_string(const char* default_value, const Ks&... keys) const noexcept -> std::string {
return get_or<const char*>(default_value, keys...);
}
/**
* @brief 检查指定的键是否存在。
* @param {const Ks&...} keys - 访问节点的路径。
* @returns {bool} 如果存在则返回 true否则返回 false。
*/
template<typename... Ks> requires (std::convertible_to<Ks, std::string_view> && ...)
[[nodiscard]] bool contains(const Ks&... keys) const noexcept {
std::shared_lock lock(mutex_);
return get_unlocked(keys...).has_value();
}
/**
* @brief 获取当前配置的完整快照 (深拷贝)。
* @returns {toml::table} 一个当前配置的副本。
*/
[[nodiscard]] toml::table snapshot() const {
std::shared_lock lock(mutex_);
return tbl_;
}
/**
* @brief 在一个读锁保护的范围内执行批量读取操作。
* @param {Func&&} func - 一个可调用对象 (如 lambda),它接收 `const toml::table&` 作为参数。
* @returns func 的返回值。
* @example
* auto [host, port] = config.with_config([](const auto& tbl) {
* return std::make_tuple(tbl["db"]["host"].value_or(""), tbl["db"]["port"].value_or(0));
* });
*/
template<typename Func>
auto with_config(Func&& func) const {
std::shared_lock lock(mutex_);
return func(tbl_);
}
// --- 状态查询与控制 (Status & Control) ---
/**
* @brief 获取配置文件的最后修改时间。
* @returns {std::filesystem::file_time_type} 文件时间戳。
*/
[[nodiscard]] auto last_modified() const noexcept {
std::shared_lock lock(mutex_);
return last_modified_;
}
/**
* @brief 停止热重载监控。
*/
void stop_hot_reload() {
stop_watching();
}
/**
* @brief 启动热重载监控。
* 只有在已加载配置文件且 `enable_hot_reload` 选项为 true 时才会启动。
*/
void start_hot_reload() {
if (!config_path_.empty() && options_.enable_hot_reload) {
start_watching();
}
}
protected:
// --- 内部成员变量 (Internal Member Variables) ---
mutable std::shared_mutex mutex_; // 读写锁,保护所有成员变量
toml::table tbl_; // 存储解析后的 TOML 数据
std::filesystem::path config_path_; // 配置文件路径
config_options_t options_; // 管理器选项
std::filesystem::file_time_type last_modified_; // 文件最后修改时间
// 回调管理
std::unordered_map<size_t, callback_t> callbacks_; // 存储回调函数
size_t next_callback_id_ = 0; // 用于生成唯一的回调 ID
// 监控线程
std::jthread watch_thread_; // 监控文件的线程 (jthread 自动管理生命周期)
std::atomic<bool> watching_{ false }; // 监控状态标志
private:
// --- 内部实现方法 (Internal Implementation) ---
/**
* @brief 加载配置文件的内部实现 (不加锁)。
* @param {const std::filesystem::path&} in_filename - 文件路径。
* @returns {std::expected<void, std::string>} 操作结果。
*/
[[nodiscard]] auto load_config_impl(const std::filesystem::path& in_filename) -> std::expected<void, std::string> {
try {
if (!std::filesystem::exists(in_filename)) {
return std::unexpected(std::format("文件未找到: {}", in_filename.string()));
}
tbl_ = toml::parse_file(in_filename.string());
last_modified_ = std::filesystem::last_write_time(in_filename);
return {};
}
catch (const toml::parse_error& err) {
std::println(std::cerr, "配置解析错误: {}", err.what());
return std::unexpected(std::format("解析失败 {}: {}", in_filename.string(), err.what()));
} catch (const std::exception& e) {
return std::unexpected(std::format("加载错误 {}: {}", in_filename.string(), e.what()));
}
}
/**
* @brief 获取配置节点的内部实现 (不加锁)。
* @param {const Ks&...} keys - 访问路径。
* @returns {std::optional<toml::node_view<const toml::node>>} 节点视图或 `std::nullopt`。
*/
template<typename... Ks>
[[nodiscard]] auto get_unlocked(
const Ks&... keys) const noexcept -> std::optional<toml::node_view<const toml::node>> {
try {
auto node = tbl_.at_path(make_path(keys...));
if (node) { return node; }
return std::nullopt;
}
catch (...) { return std::nullopt; }
}
/**
* @brief 通知所有已注册的回调函数配置已更新。
*/
void notify_callbacks() {
for (const auto& [id, callback]: callbacks_) {
try {
callback(tbl_);
}
catch (const std::exception& e) {
// 打印错误信息,但不中断对其他回调的通知
std::println(std::cerr, "回调 {} 执行异常: {}", id, e.what());
}
}
}
/**
* @brief 启动文件监控线程。
* 如果已有线程在运行,会先停止旧的再启动新的。
*/
void start_watching() {
stop_watching(); // 确保只有一个监控线程在运行
watching_ = true;
watch_thread_ = std::jthread([this](std::stop_token stop_token) { watch_file(stop_token); });
}
/**
* @brief 停止文件监控线程。
* 通过请求停止令牌并等待线程结束来确保安全退出。
*/
void stop_watching() {
watching_ = false;
if (watch_thread_.joinable()) {
watch_thread_.request_stop(); // 请求线程停止
watch_thread_.join(); // 等待线程执行完毕
}
}
/**
* @brief 文件监控循环,在 `watch_thread_` 中运行。
* @param {std::stop_token} stop_token - 用于检查是否收到了停止请求。
*/
void watch_file(std::stop_token stop_token) {
while (!stop_token.stop_requested() && watching_) {
try {
if (std::filesystem::exists(config_path_)) {
auto current_modified = std::filesystem::last_write_time(config_path_);
// 检查文件的最后修改时间是否发生变化
if (current_modified != last_modified_) {
std::println("检测到配置文件变更,正在重新加载...");
// 短暂等待,防止因文件尚未完全写入而导致的读取问题
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 调用 reload 方法进行重载
if (auto result = reload(); !result) {
std::println(std::cerr, "重载配置文件失败: {}", result.error());
}
else { std::println("配置文件重载成功。"); }
}
}
}
catch (const std::exception& e) {
std::println(std::cerr, "监控配置文件时出错: {}", e.what());
}
// 使用可被中断的休眠方式,以实现快速响应 `stop_requested`
std::unique_lock lock(mutex_);
std::condition_variable_any cv;
// 等待指定的时间间隔,或者直到被 stop_token 中断
cv.wait_for(lock, stop_token, options_.reload_interval, [] { return false; });
}
}
/**
* @brief 将多个键名连接成 toml++ 需要的点分隔路径字符串。
* @param {const Ks&...} keys - 键名序列。
* @returns {std::string} 例如 "database.user.name"。
*/
template<typename... Ks>
static std::string make_path(const Ks&... keys) {
std::string path;
auto append = [&path](std::string_view key) {
if (!path.empty()) { path += '.'; }
path += key;
};
// 使用折叠表达式 (fold expression) 简洁地处理所有参数
(append(keys), ...);
return path;
}
};

View File

@@ -0,0 +1,24 @@
cmake_minimum_required(VERSION 3.15)
project(mirage_core LANGUAGES C CXX)
find_package(Eigen3 REQUIRED)
find_package(utfcpp REQUIRED)
set(SRC_FILES)
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/include SRC_FILES)
add_library(${PROJECT_NAME} STATIC ${SRC_FILES})
# 设置公共头文件包含目录
target_include_directories(${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include # 新增的公共接口目录
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src # 内部实现目录
)
target_link_libraries(${PROJECT_NAME} PUBLIC Eigen3::Eigen mirage_image utf8cpp)
add_os_definitions(${PROJECT_NAME})
target_compile_definitions(${PROJECT_NAME} PUBLIC -DNOMINMAX)

View File

@@ -5,12 +5,24 @@
class dpi_helper {
public:
static void set_global_scale(float in_scale) {
static void global_scale(float in_scale) {
global_scale_ = in_scale;
}
static auto get_global_scale() {
static auto global_scale() {
return global_scale_;
}
static auto dpi_x() {
return dpi_x_;
}
static auto dpi_y() {
return dpi_y_;
}
static void dpi_x(uint32_t in_dpi_x) {
dpi_x_ = in_dpi_x;
}
static void dpi_y(uint32_t in_dpi_y) {
dpi_y_ = in_dpi_y;
}
static auto physical_to_logical(float in_physical) {
return in_physical / global_scale_;
@@ -47,4 +59,6 @@ public:
}
private:
inline static float global_scale_ = 1.f;
inline static uint32_t dpi_x_ = 96;
inline static uint32_t dpi_y_ = 96;
};

View File

@@ -148,7 +148,7 @@ public:
[[nodiscard]] auto get_local_rect() const { return rect_t({0, 0}, size_); }
/**
* @brief
* @brief
* @return
*/
[[nodiscard]] auto get_window_position() const

View File

@@ -138,7 +138,7 @@ public:
*/
template<typename T>
auto inverse_transform_point(const Eigen::Vector2<T>& in_point) const {
auto inverse = matrix_.inverse();
const auto& inverse = matrix_.inverse();
Eigen::Vector3<T> homogeneous_point(in_point.x(), in_point.y(), 1);
auto transformed = inverse.cast<T>() * homogeneous_point;
return Eigen::Vector2<T>(
@@ -174,7 +174,7 @@ public:
*/
template<typename T>
auto inverse_transform_vector(const Eigen::Vector2<T>& in_vector) const {
auto inverse = matrix_.inverse();
const auto& inverse = matrix_.inverse();
Eigen::Vector3<T> homogeneous_vector(in_vector.x(), in_vector.y(), 0);
auto transformed = inverse.cast<T>() * homogeneous_vector;
return Eigen::Vector2<T>(transformed.x(), transformed.y());

View File

@@ -163,6 +163,18 @@ public:
*/
const auto& size() const { return size_; }
/**
* @brief
* @return
*/
auto& position() { return position_; }
/**
* @brief
* @return
*/
auto& size() { return size_; }
/**
* @brief
* @return
@@ -624,45 +636,6 @@ public:
return rect_t<U>({ x, y }, { width, height });
}
/**
* @brief
* @tparam U
* @param in_h_align
* @param in_v_align
* @param in_text_size
* @return
*/
template<typename U>
auto get_text_rect(horizontal_text_alignment_t in_h_align, vertical_text_alignment_t in_v_align, const Eigen::Vector2<U>& in_text_size) const {
rect_t<U> text_rect{{0, 0}, in_text_size};
switch (in_h_align) {
case horizontal_text_alignment_t::left:
text_rect.position_.x() = left();
break;
case horizontal_text_alignment_t::center:
text_rect.position_.x() = left() + (width() - text_rect.width()) / 2;
break;
case horizontal_text_alignment_t::right:
text_rect.position_.x() = right() - text_rect.width();
break;
}
switch (in_v_align) {
case vertical_text_alignment_t::top:
text_rect.position_.y() = top();
break;
case vertical_text_alignment_t::center:
text_rect.position_.y() = top() + (height() - text_rect.height()) / 2;
break;
case vertical_text_alignment_t::bottom:
text_rect.position_.y() = bottom() - text_rect.height();
break;
}
return text_rect;
}
//-------------- 分割操作 --------------
/**

View File

@@ -14,7 +14,7 @@
*
* : auto angle = 45.5_deg;
*/
consteval auto operator"" _deg(const long double in_degree) {
consteval auto operator""_deg(const long double in_degree) {
return in_degree * 3.1415926 / 180.0;
}
@@ -25,6 +25,6 @@ consteval auto operator"" _deg(const long double in_degree) {
*
* : auto angle = 90_deg;
*/
consteval auto operator"" _deg(const unsigned long long in_degree) {
return in_degree * 3.1415926 / 180.0;
consteval auto operator""_deg(const unsigned long long in_degree) {
return static_cast<double>(in_degree) * 3.1415926 / 180.0;
}

File diff suppressed because it is too large Load Diff

View File

@@ -68,7 +68,7 @@ enum class key_code : uint16_t {
p = 0x29,
left_bracket = 0x2A, // [
right_bracket = 0x2B, // ]
backslash = 0x2C, // \
backslash = 0x2C, // 左斜杠
caps_lock = 0x2D,
a = 0x2E,
@@ -161,6 +161,20 @@ enum class key_code : uint16_t {
max_key_value = 0xFF
};
/**
* @enum key_action
* @brief
*
*
*/
enum class key_action {
none, ///< 无动作
press, ///< 按下
release, ///< 释放
repeat, ///< 重复按下(长按)
double_press ///< 双击
};
/**
* @enum mouse_button
* @brief
@@ -270,6 +284,12 @@ struct wheel_event {
*/
key_code platform_key_to_key_code(int32_t native_key); // 在平台特定代码中实现
/**
*
*
*/
key_action platform_event_to_key_action(uint32_t native_event, uintptr_t native_param); // 在平台特定代码中实现
/**
* @brief mouse_button
* @param native_event

View File

@@ -0,0 +1,366 @@
#include "misc/key_type/key_type.h"
#include <unordered_map>
#include <Windows.h>
key_code platform_key_to_key_code(int32_t native_key) {
switch (native_key) {
// 字母键 (A-Z)
case 'A':
return key_code::a;
case 'B':
return key_code::b;
case 'C':
return key_code::c;
case 'D':
return key_code::d;
case 'E':
return key_code::e;
case 'F':
return key_code::f;
case 'G':
return key_code::g;
case 'H':
return key_code::h;
case 'I':
return key_code::i;
case 'J':
return key_code::j;
case 'K':
return key_code::k;
case 'L':
return key_code::l;
case 'M':
return key_code::m;
case 'N':
return key_code::n;
case 'O':
return key_code::o;
case 'P':
return key_code::p;
case 'Q':
return key_code::q;
case 'R':
return key_code::r;
case 'S':
return key_code::s;
case 'T':
return key_code::t;
case 'U':
return key_code::u;
case 'V':
return key_code::v;
case 'W':
return key_code::w;
case 'X':
return key_code::x;
case 'Y':
return key_code::y;
case 'Z':
return key_code::z;
// 数字键 (0-9)
case '0':
return key_code::num_0;
case '1':
return key_code::num_1;
case '2':
return key_code::num_2;
case '3':
return key_code::num_3;
case '4':
return key_code::num_4;
case '5':
return key_code::num_5;
case '6':
return key_code::num_6;
case '7':
return key_code::num_7;
case '8':
return key_code::num_8;
case '9':
return key_code::num_9;
// 功能键 (F1-F12)
case VK_F1:
return key_code::f1;
case VK_F2:
return key_code::f2;
case VK_F3:
return key_code::f3;
case VK_F4:
return key_code::f4;
case VK_F5:
return key_code::f5;
case VK_F6:
return key_code::f6;
case VK_F7:
return key_code::f7;
case VK_F8:
return key_code::f8;
case VK_F9:
return key_code::f9;
case VK_F10:
return key_code::f10;
case VK_F11:
return key_code::f11;
case VK_F12:
return key_code::f12;
// 特殊键
case VK_ESCAPE:
return key_code::escape;
case VK_TAB:
return key_code::tab;
case VK_CAPITAL:
return key_code::caps_lock;
case VK_SHIFT:
return key_code::left_shift;
case VK_LSHIFT:
return key_code::left_shift;
case VK_RSHIFT:
return key_code::right_shift;
case VK_CONTROL:
return key_code::left_control;
case VK_LCONTROL:
return key_code::left_control;
case VK_RCONTROL:
return key_code::right_control;
case VK_MENU:
return key_code::left_alt;
case VK_LMENU:
return key_code::left_alt;
case VK_RMENU:
return key_code::right_alt;
case VK_LWIN:
return key_code::left_meta;
case VK_RWIN:
return key_code::right_meta;
case VK_APPS:
return key_code::context_menu;
case VK_SPACE:
return key_code::space;
case VK_RETURN:
return key_code::enter;
case VK_BACK:
return key_code::backspace;
case VK_DELETE:
return key_code::delete_key;
// 导航键
case VK_INSERT:
return key_code::insert;
case VK_HOME:
return key_code::home;
case VK_END:
return key_code::end;
case VK_PRIOR:
return key_code::page_up;
case VK_NEXT:
return key_code::page_down;
case VK_UP:
return key_code::up;
case VK_DOWN:
return key_code::down;
case VK_LEFT:
return key_code::left;
case VK_RIGHT:
return key_code::right;
// 符号键
case VK_OEM_3:
return key_code::back_quote; // `
case VK_OEM_MINUS:
return key_code::minus; // -
case VK_OEM_PLUS:
return key_code::equals; // =
case VK_OEM_4:
return key_code::left_bracket; // [
case VK_OEM_6:
return key_code::right_bracket; // ]
case VK_OEM_5:
return key_code::backslash; // 左斜杠
case VK_OEM_1:
return key_code::semicolon; // ;
case VK_OEM_7:
return key_code::apostrophe; // '
case VK_OEM_COMMA:
return key_code::comma; // ,
case VK_OEM_PERIOD:
return key_code::period; // .
case VK_OEM_2:
return key_code::slash; // /
// 小键盘
case VK_NUMLOCK:
return key_code::num_lock;
case VK_NUMPAD0:
return key_code::numpad_0;
case VK_NUMPAD1:
return key_code::numpad_1;
case VK_NUMPAD2:
return key_code::numpad_2;
case VK_NUMPAD3:
return key_code::numpad_3;
case VK_NUMPAD4:
return key_code::numpad_4;
case VK_NUMPAD5:
return key_code::numpad_5;
case VK_NUMPAD6:
return key_code::numpad_6;
case VK_NUMPAD7:
return key_code::numpad_7;
case VK_NUMPAD8:
return key_code::numpad_8;
case VK_NUMPAD9:
return key_code::numpad_9;
case VK_MULTIPLY:
return key_code::numpad_multiply;
case VK_ADD:
return key_code::numpad_add;
case VK_SUBTRACT:
return key_code::numpad_subtract;
case VK_DECIMAL:
return key_code::numpad_decimal;
case VK_DIVIDE:
return key_code::numpad_divide;
// 系统/媒体键
case VK_SNAPSHOT:
return key_code::print_screen;
case VK_SCROLL:
return key_code::scroll_lock;
case VK_PAUSE:
return key_code::pause;
case VK_VOLUME_MUTE:
return key_code::volume_mute;
case VK_VOLUME_DOWN:
return key_code::volume_down;
case VK_VOLUME_UP:
return key_code::volume_up;
case VK_MEDIA_PLAY_PAUSE:
return key_code::media_play;
case VK_MEDIA_STOP:
return key_code::media_stop;
case VK_MEDIA_PREV_TRACK:
return key_code::media_prev;
case VK_MEDIA_NEXT_TRACK:
return key_code::media_next;
// 未识别的键
default:
return key_code::unknown;
}
}
key_action platform_event_to_key_action(uint32_t native_event, uintptr_t native_param) {
switch (native_event) {
case WM_KEYDOWN:
if (native_param & 0x40000000) { // 如果是重复按下
return key_action::repeat;
}
return key_action::press;
case WM_KEYUP:
return key_action::release;
case WM_SYSKEYDOWN:
if (native_param & 0x40000000) { // 如果是重复按下
return key_action::repeat;
}
return key_action::press;
case WM_SYSKEYUP:
return key_action::release;
case WM_CHAR:
return key_action::press; // 字符输入事件
default:
return key_action::none;
}
}
mouse_button platform_event_to_mouse_button(uint32_t native_event, uintptr_t native_param) {
switch (native_event) {
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_LBUTTONDBLCLK: return mouse_button::left;
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
case WM_RBUTTONDBLCLK: return mouse_button::right;
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
case WM_MBUTTONDBLCLK: return mouse_button::middle;
case WM_XBUTTONDOWN:
case WM_XBUTTONUP:
case WM_XBUTTONDBLCLK:
// 高位字节指示X按钮
return HIWORD(native_param) == XBUTTON1 ? mouse_button::x1 : mouse_button::x2;
default: ;
}
return mouse_button::none;
}
mouse_action platform_event_to_mouse_action(uint32_t native_event, uintptr_t native_param) {
switch (native_event) {
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_XBUTTONDOWN: return mouse_action::press;
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
case WM_XBUTTONUP: return mouse_action::release;
case WM_LBUTTONDBLCLK:
case WM_RBUTTONDBLCLK:
case WM_MBUTTONDBLCLK:
case WM_XBUTTONDBLCLK: return mouse_action::dbl_click;
case WM_MOUSEMOVE: return mouse_action::move;
default: ;
}
return mouse_action::none;
}
wheel_event platform_event_to_wheel_event(uint32_t native_event, uintptr_t native_param1, intptr_t native_param2) {
wheel_event result;
// 获取当前修饰键状态
if (GetKeyState(VK_CONTROL) & 0x8000) {
result.modifiers |= keyboard_modifier::ctrl;
}
if (GetKeyState(VK_SHIFT) & 0x8000) {
result.modifiers |= keyboard_modifier::shift;
}
if (GetKeyState(VK_MENU) & 0x8000) {
result.modifiers |= keyboard_modifier::alt;
}
if ((GetKeyState(VK_LWIN) & 0x8000) || (GetKeyState(VK_RWIN) & 0x8000)) {
result.modifiers |= keyboard_modifier::meta;
}
switch (native_event) {
case WM_MOUSEWHEEL:
result.type = wheel_type::vertical;
result.delta_y = static_cast<float>(GET_WHEEL_DELTA_WPARAM(native_param1)) / WHEEL_DELTA;
break;
case WM_MOUSEHWHEEL:
result.type = wheel_type::horizontal;
result.delta_x = static_cast<float>(GET_WHEEL_DELTA_WPARAM(native_param1)) / WHEEL_DELTA;
break;
default:
break;
}
return result;
}

View File

@@ -0,0 +1,32 @@
//
// Created by 46944 on 25-5-13.
//
#pragma once
#include <iostream>
#include <chrono>
// info
template<class... Types>
void log_info(const std::format_string<Types...> in_fmt, Types&&... in_args) {
// 获取当前时间
std::print(std::cout, "mirage info[{}]: ", std::chrono::system_clock::now());
std::println(std::cout, in_fmt, std::forward<Types>(in_args)...);
std::cout << std::flush;
}
// warn
template<class... Types>
void log_warn(const std::format_string<Types...> in_fmt, Types&&... in_args) {
// 获取当前时间
std::print(std::cout, "mirage warn[{}]: ", std::chrono::system_clock::now());
std::println(std::cout, in_fmt, std::forward<Types>(in_args)...);
std::cout << std::flush;
}
template<class... Types>
void log_error(const std::format_string<Types...> in_fmt, Types&&... in_args) {
// 获取当前时间
std::print(std::cerr, "mirage error[{}]: ", std::chrono::system_clock::now());
std::println(std::cerr, in_fmt, std::forward<Types>(in_args)...);
std::cerr << std::flush;
}

View File

@@ -0,0 +1,264 @@
#pragma once
#include <concepts>
#include <iterator>
#include <list>
#include <optional>
#include <shared_mutex>
#include <stdexcept>
#include <unordered_map>
#include <utility>
namespace lru_concept {
template<typename Key>
concept Hashable = requires(Key key) {
{ std::hash<Key>{}(key) } -> std::convertible_to<std::size_t>;
};
template<typename Value>
concept Movable = std::movable<Value> && !std::is_const_v<Value> && !std::is_reference_v<Value>;
}
template<lru_concept::Hashable Key, lru_concept::Movable Value>
requires std::movable<Value>
class lru_cache {
using key_value_pair_t = std::pair<Key, Value>;
using list_iterator = typename std::list<key_value_pair_t>::iterator;
std::size_t capacity_;
mutable std::list<key_value_pair_t> cache_list_;
std::unordered_map<Key, list_iterator> cache_map_;
mutable std::shared_mutex mutex_;
public:
explicit lru_cache(std::size_t capacity) : capacity_(capacity) {
if (capacity_ == 0) {
throw std::invalid_argument("Cache capacity must be greater than 0");
}
}
// 复制构造和赋值
lru_cache(const lru_cache& other) {
std::shared_lock lock(other.mutex_);
capacity_ = other.capacity_;
cache_list_ = other.cache_list_;
// 重建 map因为迭代器在复制后会失效
for (auto it = cache_list_.begin(); it != cache_list_.end(); ++it) {
cache_map_[it->first] = it;
}
}
lru_cache& operator=(const lru_cache& other) {
if (this != &other) {
std::unique_lock lock1(mutex_, std::defer_lock);
std::shared_lock lock2(other.mutex_, std::defer_lock);
std::lock(lock1, lock2);
capacity_ = other.capacity_;
cache_list_ = other.cache_list_;
cache_map_.clear();
for (auto it = cache_list_.begin(); it != cache_list_.end(); ++it) {
cache_map_[it->first] = it;
}
}
return *this;
}
// 移动构造和赋值
lru_cache(lru_cache&& other) noexcept : capacity_(0) {
std::unique_lock lock(other.mutex_);
capacity_ = std::exchange(other.capacity_, 0);
cache_list_ = std::move(other.cache_list_);
cache_map_ = std::move(other.cache_map_);
}
lru_cache& operator=(lru_cache&& other) noexcept {
if (this != &other) {
std::unique_lock lock1(mutex_, std::defer_lock);
std::unique_lock lock2(other.mutex_, std::defer_lock);
std::lock(lock1, lock2);
capacity_ = std::exchange(other.capacity_, 0);
cache_list_ = std::move(other.cache_list_);
cache_map_ = std::move(other.cache_map_);
}
return *this;
}
[[nodiscard]] std::optional<Value> get(const Key& key) const {
// 使用单次加锁,避免 TOCTOU 问题
std::unique_lock lock(mutex_);
auto it = cache_map_.find(key);
if (it == cache_map_.end()) {
return std::nullopt;
}
// 将访问的节点移到链表头部
cache_list_.splice(cache_list_.begin(), cache_list_, it->second);
return it->second->second;
}
// 插入或更新缓存
void put(const Key& key, Value value) {
std::unique_lock lock(mutex_);
auto it = cache_map_.find(key);
if (it != cache_map_.end()) {
// 键已存在,更新值并移至最前面
it->second->second = std::move(value);
cache_list_.splice(cache_list_.begin(), cache_list_, it->second);
return;
}
// 键不存在,插入新的键值对
if (cache_list_.size() >= capacity_) {
// 缓存已满,删除最近最少使用的项目
auto last = std::prev(cache_list_.end());
cache_map_.erase(last->first);
cache_list_.pop_back();
}
cache_list_.emplace_front(key, std::move(value));
cache_map_.emplace(key, cache_list_.begin());
}
// 删除指定key
bool remove(const Key& key) {
std::unique_lock lock(mutex_);
auto it = cache_map_.find(key);
if (it == cache_map_.end()) { // 修复了语法错误
return false;
}
cache_list_.erase(it->second);
cache_map_.erase(it);
return true;
}
// 清空缓存
void clear() {
std::unique_lock lock(mutex_);
cache_list_.clear();
cache_map_.clear();
}
// 获取当前缓存大小
[[nodiscard]] std::size_t size() const {
std::shared_lock lock(mutex_);
return cache_list_.size();
}
// 获取缓存容量
[[nodiscard]] std::size_t capacity() const noexcept { return capacity_; }
// 检查缓存是否为空
[[nodiscard]] bool empty() const {
std::shared_lock lock(mutex_);
return cache_list_.empty();
}
// 检查是否包含指定key
[[nodiscard]] bool contains(const Key& key) const {
std::shared_lock lock(mutex_);
return cache_map_.contains(key); // C++20 feature
}
// 提供线程安全的迭代器包装
class const_iterator {
const lru_cache* cache_;
list_iterator iter_;
std::shared_lock<std::shared_mutex> lock_;
public:
using iterator_category = std::forward_iterator_tag;
using value_type = key_value_pair_t;
using difference_type = std::ptrdiff_t;
using pointer = const key_value_pair_t*;
using reference = const key_value_pair_t&;
const_iterator(const lru_cache* cache, list_iterator iter) : cache_(cache), iter_(iter), lock_(cache->mutex_) {}
reference operator*() const { return *iter_; }
pointer operator->() const { return &(*iter_); }
const_iterator& operator++() {
++iter_;
return *this;
}
const_iterator operator++(int) {
const_iterator tmp = *this;
++(*this);
return tmp;
}
bool operator==(const const_iterator& other) const { return iter_ == other.iter_; }
bool operator!=(const const_iterator& other) const { return !(*this == other); }
};
// 线程安全的迭代器
const_iterator begin() const { return const_iterator(this, cache_list_.begin()); }
const_iterator end() const { return const_iterator(this, cache_list_.end()); }
// 调整缓存容量
void resize(std::size_t new_capacity) {
if (new_capacity == 0) { // 修复了语法错误
throw std::invalid_argument("Cache capacity must be greater than 0");
}
std::unique_lock lock(mutex_);
while (cache_list_.size() > new_capacity) {
auto last = std::prev(cache_list_.end());
cache_map_.erase(last->first);
cache_list_.pop_back();
}
capacity_ = new_capacity;
}
// 批量操作支持
template<typename InputIt>
void put_range(InputIt first, InputIt last) {
std::unique_lock lock(mutex_);
for (auto it = first; it != last; ++it) {
// 内部实现,避免重复加锁
auto map_it = cache_map_.find(it->first);
if (map_it != cache_map_.end()) {
map_it->second->second = it->second;
cache_list_.splice(cache_list_.begin(), cache_list_, map_it->second);
}
else {
if (cache_list_.size() >= capacity_) {
auto last_elem = std::prev(cache_list_.end());
cache_map_.erase(last_elem->first);
cache_list_.pop_back();
}
cache_list_.emplace_front(it->first, it->second);
cache_map_.emplace(it->first, cache_list_.begin());
}
}
}
// 获取缓存统计信息
struct cache_stats {
std::size_t size;
std::size_t capacity;
double fill_ratio;
};
[[nodiscard]] cache_stats get_stats() const {
std::shared_lock lock(mutex_);
std::size_t current_size = cache_list_.size();
return { .size = current_size,
.capacity = capacity_,
.fill_ratio = static_cast<double>(current_size) / capacity_ };
}
};

View File

@@ -0,0 +1,40 @@
#pragma once
#include <string>
#include <cstddef>
#include <memory>
#include <filesystem>
#include <span>
class mapped_file {
public:
mapped_file() = default;
virtual ~mapped_file() = default;
// 禁用拷贝
mapped_file(const mapped_file&) = delete;
mapped_file& operator=(const mapped_file&) = delete;
virtual bool map_file(const std::filesystem::path& filename) = 0;
virtual void unmap() = 0;
[[nodiscard]] virtual const void* get_data() const = 0;
[[nodiscard]] virtual void* get_data() = 0;
[[nodiscard]] virtual size_t get_size() const = 0;
[[nodiscard]] virtual bool is_mapped() const = 0;
[[nodiscard]] std::span<std::byte const> get_span() const {
return std::span(static_cast<std::byte const*>(get_data()), get_size());
}
[[nodiscard]] std::span<std::byte> get_span() {
return std::span(static_cast<std::byte*>(get_data()), get_size());
}
[[nodiscard]] uint8_t* get_u8() {
return static_cast<uint8_t*>(get_data());
}
[[nodiscard]] const uint8_t* get_u8() const {
return static_cast<const uint8_t*>(get_data());
}
static std::unique_ptr<mapped_file> create();
};

View File

@@ -0,0 +1,110 @@
#include "misc/mapped_file/mapped_file.h"
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
class mapped_file_unix : public mapped_file {
public:
mapped_file_unix();
~mapped_file_unix();
// 禁用拷贝
mapped_file_unix(const mapped_file_unix&) = delete;
mapped_file_unix& operator=(const mapped_file_unix&) = delete;
// 允许移动
mapped_file_unix(mapped_file_unix&& other) noexcept;
mapped_file_unix& operator=(mapped_file_unix&& other) noexcept;
bool map_file(const std::filesystem::path& filename);
void unmap();
[[nodiscard]] const void* get_data() const { return data; }
[[nodiscard]] void* get_data() { return data; }
[[nodiscard]] size_t get_size() const { return size; }
[[nodiscard]] bool is_mapped() const { return data != nullptr; }
private:
void cleanup();
void* data;
size_t size;
int fd;
};
mapped_file_unix::mapped_file_unix() : data(nullptr), size(0), fd(-1) {}
mapped_file_unix::~mapped_file_unix() {
cleanup();
}
mapped_file_unix::mapped_file_unix(mapped_file_unix&& other) noexcept
: data(other.data), size(other.size), fd(other.fd) {
other.data = nullptr;
other.size = 0;
other.fd = -1;
}
mapped_file_unix& mapped_file_unix::operator=(mapped_file_unix&& other) noexcept {
if (this != &other) {
cleanup();
data = other.data;
size = other.size;
fd = other.fd;
other.data = nullptr;
other.size = 0;
other.fd = -1;
}
return *this;
}
bool mapped_file_unix::map_file(const std::filesystem::path& filename) {
cleanup();
const std::string& utf8_filename = filename.string();
fd = open(utf8_filename.c_str(), O_RDONLY);
if (fd == -1) {
// 无法打开文件
throw std::runtime_error("Failed to open file");
}
struct stat sb;
if (fstat(fd, &sb) == -1) {
cleanup();
// 无法获取文件大小
throw std::runtime_error("Failed to get file size");
}
size = static_cast<size_t>(sb.st_size);
data = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (data == MAP_FAILED) {
cleanup();
// 无法映射文件
throw std::runtime_error("Failed to map file");
}
return true;
}
void mapped_file_unix::cleanup() {
if (data) {
munmap(data, size);
data = nullptr;
}
if (fd != -1) {
close(fd);
fd = -1;
}
size = 0;
}
void mapped_file_unix::unmap() {
cleanup();
}
std::unique_ptr<mapped_file> mapped_file::create() {
return std::make_unique<mapped_file_unix>();
}

View File

@@ -0,0 +1,153 @@
#include "misc/mapped_file/mapped_file.h"
#include <stdexcept>
#include <windows.h>
#include "utf8.h"
class mapped_file_win : public mapped_file {
public:
mapped_file_win();
~mapped_file_win() override;
// 禁用拷贝
mapped_file_win(const mapped_file_win&) = delete;
mapped_file_win& operator=(const mapped_file_win&) = delete;
// 允许移动
mapped_file_win(mapped_file_win&& other) noexcept;
mapped_file_win& operator=(mapped_file_win&& other) noexcept;
bool map_file(const std::filesystem::path& filename) override;
void unmap() override;
[[nodiscard]] const void* get_data() const override { return data_; }
[[nodiscard]] void* get_data() override { return data_; }
[[nodiscard]] size_t get_size() const override { return size_; }
[[nodiscard]] bool is_mapped() const override { return data_ != nullptr; }
private:
void cleanup();
void* data_;
size_t size_;
HANDLE file_handle_;
HANDLE mapping_handle_;
};
mapped_file_win::mapped_file_win() :
mapped_file(),
data_(nullptr),
size_(0),
file_handle_(INVALID_HANDLE_VALUE),
mapping_handle_(nullptr) {}
mapped_file_win::~mapped_file_win() {
cleanup();
}
mapped_file_win::mapped_file_win(mapped_file_win&& other) noexcept
: data_(other.data_), size_(other.size_), file_handle_(other.file_handle_), mapping_handle_(other.mapping_handle_) {
other.data_ = nullptr;
other.size_ = 0;
other.file_handle_ = INVALID_HANDLE_VALUE;
other.mapping_handle_ = nullptr;
}
mapped_file_win& mapped_file_win::operator=(mapped_file_win&& other) noexcept {
if (this == &other)
return *this;
cleanup();
data_ = other.data_;
size_ = other.size_;
file_handle_ = other.file_handle_;
mapping_handle_ = other.mapping_handle_;
other.data_ = nullptr;
other.size_ = 0;
other.file_handle_ = INVALID_HANDLE_VALUE;
other.mapping_handle_ = nullptr;
return *this;
}
bool mapped_file_win::map_file(const std::filesystem::path& filename) {
cleanup();
file_handle_ = CreateFileW(
filename.generic_wstring().c_str(),
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr
);
if (file_handle_ == INVALID_HANDLE_VALUE) {
// 无法打开文件
throw std::runtime_error("Failed to open file");
}
LARGE_INTEGER file_size;
if (!GetFileSizeEx(file_handle_, &file_size)) {
cleanup();
// 无法获取文件大小
throw std::runtime_error("Failed to get file size");
}
size_ = static_cast<size_t>(file_size.QuadPart);
mapping_handle_ = CreateFileMappingW(
file_handle_,
nullptr,
PAGE_READONLY,
0,
0,
nullptr
);
if (mapping_handle_ == nullptr) {
cleanup();
// 无法创建文件映射
throw std::runtime_error("Failed to create file mapping");
}
data_ = MapViewOfFile(
mapping_handle_,
FILE_MAP_READ,
0,
0,
0
);
if (data_ == nullptr) {
cleanup();
// 无法映射文件视图
throw std::runtime_error("Failed to map view of file");
}
return true;
}
void mapped_file_win::cleanup() {
if (data_) {
UnmapViewOfFile(data_);
data_ = nullptr;
}
if (mapping_handle_) {
CloseHandle(mapping_handle_);
mapping_handle_ = nullptr;
}
if (file_handle_ != INVALID_HANDLE_VALUE) {
CloseHandle(file_handle_);
file_handle_ = INVALID_HANDLE_VALUE;
}
size_ = 0;
}
void mapped_file_win::unmap() {
cleanup();
}
std::unique_ptr<mapped_file> mapped_file::create() {
return std::make_unique<mapped_file_win>();
}

View File

@@ -1,6 +1,6 @@
#pragma once
/**
* @file render_elements.h
* @file mirage_type.h
* @brief UI相关的基础类型
*
* UI系统中用于渲染和布局的基础类型
@@ -57,26 +57,6 @@ enum class vertical_alignment_t {
stretch ///< 拉伸填充
};
/**
* @enum horizontal_text_alignment_t
* @brief
*/
enum class horizontal_text_alignment_t {
left, ///< 文本左对齐
center, ///< 文本居中对齐
right ///< 文本右对齐
};
/**
* @enum vertical_text_alignment_t
* @brief
*/
enum class vertical_text_alignment_t {
top, ///< 文本顶部对齐
center, ///< 文本居中对齐
bottom ///< 文本底部对齐
};
/**
* @enum visibility_t
* @brief
@@ -90,10 +70,11 @@ enum class visibility_t : uint32_t {
hit_test_invisible = 1 << 4, ///< 整个组件树不可点击
// 常用组合
all = 0xFFFFFFFF, ///< 所有可见性标志
any_visible = visible | self_hit_test_invisible | hit_test_invisible, ///< 任何可见状态
any_invisible = collapsed | hidden, ///< 任何不可见状态
any_hit_testable = visible, ///< 任何可命中测试状态
all = 0xFFFFFFFF, ///< 所有可见性标志
any_visible = visible | self_hit_test_invisible | hit_test_invisible, ///< 任何可见状态
any_invisible = collapsed | hidden, ///< 任何不可见状态
any_hit_testable = visible, ///< 任何可命中测试状态
any_layout = any_visible | hidden, ///< 任何布局状态
};
// 为visibility枚举启用位标志功能
DEFINE_ENUM_FLAGS(visibility_t)
@@ -132,7 +113,7 @@ enum class flow_direction_preference_t {
/**
* @brief
*/
inline static auto g_flow_direction = flow_direction_t::left_to_right;
// inline static auto g_flow_direction = flow_direction_t::left_to_right;
//-------------- 几何和渲染结构体 --------------
@@ -184,6 +165,31 @@ struct rect_quad : std::array<T, 4> {
}
};
//-------------- 特化类型 --------------
/** 表示矩形四个角颜色的类型 */
using rect_color = rect_quad<linear_color>;
/** 表示矩形四个角UV坐标的类型 */
using rect_uv = rect_quad<Eigen::Vector2f>;
/** 表示矩形四个角圆角半径的类型 */
using rect_round = rect_quad<float>;
/**
* @brief rect_uv提供默认构造函数特化
*
* (0,0)(1,1)UV矩形
*/
template<>
inline rect_quad<Eigen::Vector2f>::rect_quad() : std::array<Eigen::Vector2f, 4>{
Eigen::Vector2f{ 0, 0 },
Eigen::Vector2f{ 1, 0 },
Eigen::Vector2f{ 0, 1 },
Eigen::Vector2f{ 1, 1 }
} {
}
/**
* @struct mirage_vertex_param_t
* @brief
@@ -298,16 +304,16 @@ struct mirage_vertex_param_t {
/**
* @brief - x,yz,w
* @param a x和y分量
* @param b z和w分量
* @param in_a x和y分量
* @param in_b z和w分量
*/
template<typename A, typename B,
typename = decltype(std::declval<A>()[0]),
typename = decltype(std::declval<B>()[0])>
mirage_vertex_param_t(const A& a, const B& b) : x(static_cast<float>(a[0])),
y(static_cast<float>(a[1])),
z(static_cast<float>(b[0])),
w(static_cast<float>(b[1])) {
mirage_vertex_param_t(const A& in_a, const B& in_b) : x(static_cast<float>(in_a[0])),
y(static_cast<float>(in_a[1])),
z(static_cast<float>(in_b[0])),
w(static_cast<float>(in_b[1])) {
}
//-------------- 辅助类型特征检测 --------------

View File

@@ -0,0 +1,37 @@
#pragma once
#include <unordered_map>
#include <string>
#include <cstdint>
/**
* name_t
* @brief 一种用于存储字符串的类,使用哈希值作为唯一标识符
* @note 应该尽量少的构造 name_t 对象, 因为每次构造都会在 name_map 中插入一个新的元素并 hash 字符串
*/
template<typename StringType>
struct name_t {
public:
explicit name_t(const StringType& in_str) {
hash_value_ = hash(in_str);
name_map[hash_value_] = in_str;
}
[[nodiscard]] auto to_string() const {
return name_map[hash_value_];
}
private:
static uint64_t hash(const StringType& str) {
uint64_t hash = 5381;
for (const auto c : str) {
hash = (hash << 5) + hash + c; // hash * 33 + c
}
return hash;
}
private:
uint64_t hash_value_;
inline static std::unordered_map<uint64_t, StringType> name_map;
};
using u8name_t = name_t<std::string>;
using u16name_t = name_t<std::u16string>;
using u32name_t = name_t<std::u32string>;

View File

@@ -0,0 +1,24 @@
project(mirage_image)
# 检索源文件(自动平台过滤)
set(SRC_FILES)
# 过渡期同时支持旧的根目录布局和新的 src/include 布局
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR} SRC_FILES)
# 创建库
add_library(${PROJECT_NAME} STATIC ${SRC_FILES})
# 设置包含目录
target_include_directories(${PROJECT_NAME}
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
)
# 链接依赖
target_link_libraries(${PROJECT_NAME} PUBLIC
mirage_core
sokol
)
# 添加平台宏
add_os_definitions(${PROJECT_NAME})

246
src/mirage_image/color.cpp Normal file
View File

@@ -0,0 +1,246 @@
#include "color.h"
#include <algorithm>
#include <array>
#include <vector>
// --- 辅助函数 ---
// 将单个十六进制字符转换为整数 (0-15)
int hex_char_to_int(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'a' && c <= 'f') {
return c - 'a' + 10;
}
if (c >= 'A' && c <= 'F') {
return c - 'A' + 10;
}
return -1;
}
// 去除字符串首尾空格
std::string trim_whitespace(const std::string& str) {
size_t first = str.find_first_not_of(" \t\n\r\f\v");
if (std::string::npos == first) {
return str; // String contains only whitespace
}
size_t last = str.find_last_not_of(" \t\n\r\f\v");
return str.substr(first, (last - first + 1));
}
// 按分隔符分割字符串
std::vector<std::string> split_string(const std::string& str, char delimiter) {
std::vector<std::string> tokens;
std::string token;
std::istringstream tokenStream(str);
while (std::getline(tokenStream, token, delimiter)) {
tokens.push_back(token);
}
return tokens;
}
// --- 十六进制格式解析辅助函数 (保持不变) ---
std::optional<linear_color> parse_hex_color(const std::string& in_str) {
const auto& hex_part = in_str.substr(1);
const size_t length = hex_part.length();
// 检查所有字符是否为有效十六进制数字 (使用 cctype::isxdigit)
for (const char c: hex_part) {
if (!::isxdigit(static_cast<unsigned char>(c))) { return std::nullopt; }
}
linear_color result{};
// #RGB
if (length == 3) {
int r = hex_char_to_int(hex_part[0]);
int g = hex_char_to_int(hex_part[1]);
int b = hex_char_to_int(hex_part[2]);
if (r == -1 || g == -1 || b == -1)
return std::nullopt;
result.r = (r * 16 + r) / 255.0f;
result.g = (g * 16 + g) / 255.0f;
result.b = (b * 16 + b) / 255.0f;
result.a = 1.0f;
return result;
}
// #RGBA
if (length == 4) {
int r = hex_char_to_int(hex_part[0]);
int g = hex_char_to_int(hex_part[1]);
int b = hex_char_to_int(hex_part[2]);
int a = hex_char_to_int(hex_part[3]);
if (r == -1 || g == -1 || b == -1 || a == -1)
return std::nullopt;
result.r = (r * 16 + r) / 255.0f;
result.g = (g * 16 + g) / 255.0f;
result.b = (b * 16 + b) / 255.0f;
result.a = (a * 16 + a) / 255.0f;
return result;
}
// #RRGGBB
if (length == 6) {
int r = (hex_char_to_int(hex_part[0]) << 4) + hex_char_to_int(hex_part[1]);
int g = (hex_char_to_int(hex_part[2]) << 4) + hex_char_to_int(hex_part[3]);
int b = (hex_char_to_int(hex_part[4]) << 4) + hex_char_to_int(hex_part[5]);
if (r < 0 || g < 0 || b < 0)
return std::nullopt; // Check hex_char_to_int results implicitly
result.r = r / 255.0f;
result.g = g / 255.0f;
result.b = b / 255.0f;
result.a = 1.0f;
return result;
}
// #RRGGBBAA
if (length == 8) {
int r = (hex_char_to_int(hex_part[0]) << 4) + hex_char_to_int(hex_part[1]);
int g = (hex_char_to_int(hex_part[2]) << 4) + hex_char_to_int(hex_part[3]);
int b = (hex_char_to_int(hex_part[4]) << 4) + hex_char_to_int(hex_part[5]);
int a = (hex_char_to_int(hex_part[6]) << 4) + hex_char_to_int(hex_part[7]);
if (r < 0 || g < 0 || b < 0 || a < 0)
return std::nullopt; // Check hex_char_to_int results implicitly
result.r = r / 255.0f;
result.g = g / 255.0f;
result.b = b / 255.0f;
result.a = a / 255.0f;
return result;
}
return std::nullopt;
}
// --- RGB/RGBA 格式解析辅助函数 (重写为手动解析) ---
std::optional<linear_color> parse_rgb_rgba_color(const std::string& in_str) {
std::string lower_str = in_str;
// 转换为小写以便检查 "rgb(" 和 "rgba("
std::ranges::transform(lower_str, lower_str.begin(), [](const char c) {
return static_cast<char>(tolower(c));
});
const auto open_paren = lower_str.find('(');
const auto close_paren = lower_str.find(')');
if (open_paren == std::string::npos || close_paren == std::string::npos || close_paren <= open_paren) {
return std::nullopt; // 括号不匹配或顺序错误
}
const auto& prefix = lower_str.substr(0, open_paren);
const bool is_rgba = prefix == "rgba";
const bool is_rgb = prefix == "rgb";
if (!is_rgb && !is_rgba) {
return std::nullopt; // 前缀不是 rgb 或 rgba
}
// **提取括号内的内容**
const auto& content = in_str.substr(open_paren + 1, close_paren - open_paren - 1);
// **按逗号分割**
const auto& components_str = split_string(content, ',');
const auto expected_components = is_rgba ? 4uz : 3uz;
if (components_str.size() != expected_components) {
return std::nullopt; // 组件数量不正确
}
std::array<float, 4> components;
auto index = 0uz;
bool uses_float = false;
bool uses_int = false;
for (const std::string& comp_str_raw : components_str) {
// **去除每个组件的首尾空格**
const auto& comp_str = trim_whitespace(comp_str_raw);
if (comp_str.empty()) {
return std::nullopt; // 空组件
}
try {
// **检查是否包含小数点,以此判断是浮点数还是整数**
if (comp_str.find('.') != std::string::npos || comp_str.find('e') != std::string::npos || comp_str.find('E') != std::string::npos) {
// **尝试解析为浮点数**
const auto val_f = std::stof(comp_str);
components[index] = val_f;
uses_float = true;
} else {
// **尝试解析为整数**
const auto val_i = std::stoi(comp_str);
components[index] = static_cast<float>(val_i);
uses_int = true;
}
index++;
} catch (const std::invalid_argument&) {
return std::nullopt; // 无法解析为数字
} catch (const std::out_of_range&) {
return std::nullopt; // 数字超出范围 (stoi/stof)
}
}
// **不允许混合整数和浮点数格式**
if (uses_float && uses_int) {
// 一个特例:允许整数 0 或 1 出现在浮点数格式中,视为 0.0f 或 1.0f
// 但这里为了简化,我们严格区分,要么全是类整数,要么全是类浮点数
// 如果需要支持混合,需要更复杂的逻辑判断每个分量
return std::nullopt;
}
linear_color result{};
if (uses_float) {
if (index != expected_components)
return std::nullopt; // 应该不会发生,但作为保险
result.r = components[0];
result.g = components[1];
result.b = components[2];
result.a = is_rgba ? components[3] : 1.0f;
return result;
}
if (uses_int) {
if (index != expected_components)
return std::nullopt; // 应该不会发生
result.r = components[0] / 255.0f;
result.g = components[1] / 255.0f;
result.b = components[2] / 255.0f;
result.a = is_rgba ? components[3] / 255.0f : 1.0f;
return result;
}
// **如果既没有解析出浮点数也没有解析出整数 (例如空字符串),则失败**
return std::nullopt;
}
// --- linear_color 静态成员函数实现 ---
std::optional<linear_color> linear_color::from_string(const std::string& in_str) {
if (in_str.empty()) { return std::nullopt; }
// **去除首尾空格**
std::string trimmed_str = trim_whitespace(in_str);
if (trimmed_str.empty()) { return std::nullopt; }
// **委托给十六进制解析器**
if (trimmed_str[0] == '#') {
// **支持的十六进制格式:** #RGB, #RGBA, #RRGGBB, #RRGGBBAA
return parse_hex_color(trimmed_str);
}
std::string lower_trimmed_str = trimmed_str;
std::ranges::transform(lower_trimmed_str, lower_trimmed_str.begin(), [](const char c) {
return static_cast<char>(tolower(c));
});
// **委托给 RGB/RGBA 解析器**
if (lower_trimmed_str.rfind("rgb", 0) == 0) {
// 检查是否以 "rgb" 或 "rgba" 开头
// **支持的 RGB/RGBA 格式 (包括整数和浮点数,以及内部空格):**
// - rgb(r,g,b)
// - rgba(r,g,b,a)
return parse_rgb_rgba_color(trimmed_str); // 传递原始带大小写的字符串给解析器
}
// **未知格式**
return std::nullopt;
}

View File

@@ -8,6 +8,7 @@
*/
#include <complex>
#include <optional>
/**
* @class linear_color
@@ -51,12 +52,12 @@ public:
* sRGB颜色空间的值转换到线性颜色空间
*/
static linear_color from_srgb(float in_r, float in_g, float in_b, float in_a = 1.0f) {
return linear_color(
in_r <= 0.04045f ? in_r / 12.92f : std::pow((in_r + 0.055f) / 1.055f, 2.4f),
in_g <= 0.04045f ? in_g / 12.92f : std::pow((in_g + 0.055f) / 1.055f, 2.4f),
in_b <= 0.04045f ? in_b / 12.92f : std::pow((in_b + 0.055f) / 1.055f, 2.4f),
in_a
);
return {
in_r <= 0.04045f ? in_r / 12.92f : std::pow((in_r + 0.055f) / 1.055f, 2.4f),
in_g <= 0.04045f ? in_g / 12.92f : std::pow((in_g + 0.055f) / 1.055f, 2.4f),
in_b <= 0.04045f ? in_b / 12.92f : std::pow((in_b + 0.055f) / 1.055f, 2.4f),
in_a
};
}
/**
@@ -135,6 +136,21 @@ public:
return r == in_color.r && g == in_color.g && b == in_color.b && a == in_color.a;
}
/**
* @brief (C++23 Style adaptation)
* : "rgb(r, g, b)" "rgba(r, g, b, a)"
* - ****: '.', [0, 1]
* - ****: :
* - [0, 1] 使 0.0f 1.0f ()
* - ** (1, 255] uint8_t [0, 1] (value / 255.0f)**
* - < 0 > 255
* @param in_str
* @return linear_color
* @note **/ linear_color{0.0f, 0.0f, 0.0f, 0.0f}**
* 使 [[nodiscard]]
*/
[[nodiscard]] static std::optional<linear_color> from_string(const std::string& in_str);
//-------------- 成员变量 --------------
/** 红色分量,范围 [0,1] */
@@ -152,7 +168,7 @@ public:
//-------------- 静态常量 --------------
/** 白色: (1,1,1,1) */
static linear_color white() { return {1.0f, 1.0f, 1.0f, 1.0f }; }
static linear_color white() { return { 1.0f, 1.0f, 1.0f, 1.0f }; }
/** 黑色: (0,0,0,1) */
static linear_color black() { return { 0.0f, 0.0f, 0.0f, 1.0f }; }

View File

@@ -0,0 +1,192 @@
#pragma once
#include <tuple>
#include "pixel.h"
#include "sokol_gfx.h"
/**
* @brief 像素格式工厂
*
* 提供从sg_pixel_format到对应像素类型的转换功能
*/
struct image_accessor_factory {
/**
* @brief 获取与sg_pixel_format对应的像素类型信息
*
* @param format 像素格式
* @return std::tuple<const char*, size_t, int> 类型名称、元素大小和通道数
*/
static std::tuple<const char*, size_t, int> get_pixel_type_info(const sg_pixel_format& format) {
switch (format) {
// 8位单通道格式
case SG_PIXELFORMAT_R8:
return {"uint8_t", sizeof(uint8_t), 1};
case SG_PIXELFORMAT_R8SN:
return {"int8_t", sizeof(int8_t), 1};
case SG_PIXELFORMAT_R8UI:
return {"uint8_t", sizeof(uint8_t), 1};
case SG_PIXELFORMAT_R8SI:
return {"int8_t", sizeof(int8_t), 1};
// 16位单通道和8位双通道格式
case SG_PIXELFORMAT_R16:
return {"uint16_t", sizeof(uint16_t), 1};
case SG_PIXELFORMAT_R16SN:
return {"int16_t", sizeof(int16_t), 1};
case SG_PIXELFORMAT_R16UI:
return {"uint16_t", sizeof(uint16_t), 1};
case SG_PIXELFORMAT_R16SI:
return {"int16_t", sizeof(int16_t), 1};
case SG_PIXELFORMAT_R16F:
return {"half", sizeof(Eigen::half), 1};
case SG_PIXELFORMAT_RG8:
return {"uint8_t", sizeof(uint8_t), 2};
case SG_PIXELFORMAT_RG8SN:
return {"int8_t", sizeof(int8_t), 2};
case SG_PIXELFORMAT_RG8UI:
return {"uint8_t", sizeof(uint8_t), 2};
case SG_PIXELFORMAT_RG8SI:
return {"int8_t", sizeof(int8_t), 2};
// 32位单通道和16位双通道和8位四通道格式
case SG_PIXELFORMAT_R32UI:
return {"uint32_t", sizeof(uint32_t), 1};
case SG_PIXELFORMAT_R32SI:
return {"int32_t", sizeof(int32_t), 1};
case SG_PIXELFORMAT_R32F:
return {"float", sizeof(float), 1};
case SG_PIXELFORMAT_RG16:
return {"uint16_t", sizeof(uint16_t), 2};
case SG_PIXELFORMAT_RG16SN:
return {"int16_t", sizeof(int16_t), 2};
case SG_PIXELFORMAT_RG16UI:
return {"uint16_t", sizeof(uint16_t), 2};
case SG_PIXELFORMAT_RG16SI:
return {"int16_t", sizeof(int16_t), 2};
case SG_PIXELFORMAT_RG16F:
return {"half", sizeof(Eigen::half), 2};
case SG_PIXELFORMAT_RGBA8:
case SG_PIXELFORMAT_SRGB8A8:
case SG_PIXELFORMAT_BGRA8:
return {"uint8_t", sizeof(uint8_t), 4};
case SG_PIXELFORMAT_RGBA8SN:
return {"int8_t", sizeof(int8_t), 4};
case SG_PIXELFORMAT_RGBA8UI:
return {"uint8_t", sizeof(uint8_t), 4};
case SG_PIXELFORMAT_RGBA8SI:
return {"int8_t", sizeof(int8_t), 4};
// 64位双通道和32位四通道格式
case SG_PIXELFORMAT_RG32UI:
return {"uint32_t", sizeof(uint32_t), 2};
case SG_PIXELFORMAT_RG32SI:
return {"int32_t", sizeof(int32_t), 2};
case SG_PIXELFORMAT_RG32F:
return {"float", sizeof(float), 2};
case SG_PIXELFORMAT_RGBA16:
return {"uint16_t", sizeof(uint16_t), 4};
case SG_PIXELFORMAT_RGBA16SN:
return {"int16_t", sizeof(int16_t), 4};
case SG_PIXELFORMAT_RGBA16UI:
return {"uint16_t", sizeof(uint16_t), 4};
case SG_PIXELFORMAT_RGBA16SI:
return {"int16_t", sizeof(int16_t), 4};
case SG_PIXELFORMAT_RGBA16F:
return {"half", sizeof(Eigen::half), 4};
// 128位四通道格式
case SG_PIXELFORMAT_RGBA32UI:
return {"uint32_t", sizeof(uint32_t), 4};
case SG_PIXELFORMAT_RGBA32SI:
return {"int32_t", sizeof(int32_t), 4};
case SG_PIXELFORMAT_RGBA32F:
return {"float", sizeof(float), 4};
// 特殊格式和压缩格式不直接支持
default:
return {"unknown", 0, 0};
}
}
/**
* @brief 判断是否可以直接支持该像素格式
*
* @param format 像素格式
* @return bool 如果格式可以用pixel<T,N>表示则返回true
*/
static bool is_supported(const sg_pixel_format format) {
auto [type_name, element_size, channels] = get_pixel_type_info(format);
return element_size > 0 && channels > 0;
}
/**
* @brief 创建新的图像访问器
*
* 根据像素格式创建适当类型的图像访问器
*
* @param format 像素格式
* @param data 图像数据指针
* @param width 图像宽度
* @param height 图像高度
* @return void* 创建的图像访问器指针(需要根据格式自行转换为正确类型)
*/
// static image_accessor_interface* create_image_accessor(const sg_pixel_format format, void* data, int width, int height) {
// // 根据格式创建对应类型的图像访问器
// switch (format) {
// case SG_PIXELFORMAT_R8:
// return new image_accessor<pixel<uint8_t, 1>>(data, width, height);
// case SG_PIXELFORMAT_R8SN:
// return new image_accessor<pixel<int8_t, 1>>(data, width, height);
// case SG_PIXELFORMAT_RG8:
// return new image_accessor<pixel<uint8_t, 2>>(data, width, height);
// case SG_PIXELFORMAT_RG8SN:
// return new image_accessor<pixel<int8_t, 2>>(data, width, height);
// case SG_PIXELFORMAT_RGBA8:
// case SG_PIXELFORMAT_SRGB8A8:
// return new image_accessor<pixel<uint8_t, 4>>(data, width, height);
// case SG_PIXELFORMAT_RGBA8SN:
// return new image_accessor<pixel<int8_t, 4>>(data, width, height);
// case SG_PIXELFORMAT_BGRA8:
// return new image_accessor<pixel<uint8_t, 4>>(data, width, height); // 需要单独处理RGB顺序
//
// case SG_PIXELFORMAT_R16:
// return new image_accessor<pixel<uint16_t, 1>>(data, width, height);
// case SG_PIXELFORMAT_R16SN:
// return new image_accessor<pixel<int16_t, 1>>(data, width, height);
// case SG_PIXELFORMAT_R16F:
// return new image_accessor<pixel<Eigen::half, 1>>(data, width, height);
// case SG_PIXELFORMAT_RG16:
// return new image_accessor<pixel<uint16_t, 2>>(data, width, height);
// case SG_PIXELFORMAT_RG16SN:
// return new image_accessor<pixel<int16_t, 2>>(data, width, height);
// case SG_PIXELFORMAT_RG16F:
// return new image_accessor<pixel<Eigen::half, 2>>(data, width, height);
// case SG_PIXELFORMAT_RGBA16:
// return new image_accessor<pixel<uint16_t, 4>>(data, width, height);
// case SG_PIXELFORMAT_RGBA16SN:
// return new image_accessor<pixel<int16_t, 4>>(data, width, height);
// case SG_PIXELFORMAT_RGBA16F:
// return new image_accessor<pixel<Eigen::half, 4>>(data, width, height);
//
// case SG_PIXELFORMAT_R32F:
// return new image_accessor<pixel<float, 1>>(data, width, height);
// case SG_PIXELFORMAT_RG32F:
// return new image_accessor<pixel<float, 2>>(data, width, height);
// case SG_PIXELFORMAT_RGBA32F:
// return new image_accessor<pixel<float, 4>>(data, width, height);
//
// default:
// return nullptr;
// }
// }
/**
* @brief 删除图像访问器
*
* @param accessor 图像访问器指针
*/
// static void destroy_image_accessor(image_accessor_interface* accessor) {
// delete accessor;
// }
};

1970
src/mirage_image/pixel.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.15)
project(mirage_platform LANGUAGES C CXX)
set(SRC_FILES)
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/include SRC_FILES)
add_library(${PROJECT_NAME} STATIC ${SRC_FILES})
# 设置公共头文件包含目录
target_include_directories(${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include # 公共接口目录
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src # 内部实现目录
)
target_link_libraries(${PROJECT_NAME} PUBLIC sokol mirage_core)
add_os_definitions(${PROJECT_NAME})

View File

@@ -0,0 +1,138 @@
#pragma once
/**
* @file ime.h
* @brief 输入法编辑器(IME)抽象接口
*
* 定义跨平台的IME支持接口,用于处理多语言文本输入
*/
#include "platform_types.h"
#include <string>
#include <memory>
#include <Eigen/Eigen>
namespace mirage::platform {
/**
* @brief IME组合文本信息
*/
struct IMEComposition {
std::u32string text; // 组合中的文本
int cursor_position; // 光标位置
int selection_start; // 选择起始位置
int selection_length; // 选择长度
};
/**
* @brief IME处理器接口
*
* 实现此接口以接收IME事件
*/
class IIMEProcessor {
public:
virtual ~IIMEProcessor() = default;
/**
* @brief 处理IME字符输入
* @param character 输入的字符
*/
virtual void on_ime_char(char32_t character) = 0;
/**
* @brief 处理IME组合文本更新
* @param composition 组合文本信息
*/
virtual void on_ime_composition(const IMEComposition& composition) = 0;
/**
* @brief 处理IME组合结束
*/
virtual void on_ime_composition_end() = 0;
};
/**
* @brief IME管理器接口
*/
class IIMEManager {
public:
virtual ~IIMEManager() = default;
//-------------- IME生命周期 --------------
/**
* @brief 启用IME
* @param window_handle 窗口句柄
*/
virtual void enable(void* window_handle) = 0;
/**
* @brief 禁用IME
*/
virtual void disable() = 0;
/**
* @brief 检查IME是否启用
* @return 启用返回true,否则返回false
*/
virtual bool is_enabled() const = 0;
//-------------- 光标位置 --------------
/**
* @brief 设置IME候选窗口位置
* @param position 位置(窗口坐标)
*/
virtual void set_cursor_position(const Eigen::Vector2i& position) = 0;
/**
* @brief 获取IME候选窗口位置
* @return 位置(窗口坐标)
*/
virtual Eigen::Vector2i get_cursor_position() const = 0;
//-------------- 处理器管理 --------------
/**
* @brief 注册IME处理器
* @param processor 处理器的弱引用
*/
virtual void register_processor(std::weak_ptr<IIMEProcessor> processor) = 0;
/**
* @brief 注销IME处理器
* @param processor 处理器的弱引用
*/
virtual void unregister_processor(std::weak_ptr<IIMEProcessor> processor) = 0;
/**
* @brief 检查是否有注册的处理器
* @return 有处理器返回true,否则返回false
*/
virtual bool has_processors() const = 0;
//-------------- 事件处理 --------------
/**
* @brief 处理平台IME事件
* @param event 事件数据
*
* 由平台层调用,分发IME事件给注册的处理器
*/
virtual void process_event(const Event& event) = 0;
};
/**
* @brief 创建IME管理器
* @return IME管理器的唯一指针
*/
std::unique_ptr<IIMEManager> create_ime_manager();
/**
* @brief 获取全局IME管理器实例
* @return IME管理器引用
*
* 单例模式,整个应用共享一个IME管理器
*/
IIMEManager& get_ime_manager();
} // namespace mirage::platform

View File

@@ -0,0 +1,131 @@
#pragma once
/**
* @file input.h
* @brief 输入系统抽象接口
*
* 定义跨平台的输入管理接口,包括鼠标和键盘状态查询
*/
#include "platform_types.h"
#include <Eigen/Eigen>
namespace mirage::platform {
/**
* @brief 输入管理器接口
*
* 提供键盘和鼠标状态的查询功能
*/
class IInputManager {
public:
virtual ~IInputManager() = default;
//-------------- 键盘状态查询 --------------
/**
* @brief 检查按键是否被按下
* @param key 键码
* @return 按下返回true,否则返回false
*/
virtual bool is_key_pressed(KeyCode key) const = 0;
/**
* @brief 检查按键是否刚被按下(本帧按下,上一帧未按下)
* @param key 键码
* @return 刚按下返回true,否则返回false
*/
virtual bool is_key_just_pressed(KeyCode key) const = 0;
/**
* @brief 检查按键是否刚被释放(本帧释放,上一帧按下)
* @param key 键码
* @return 刚释放返回true,否则返回false
*/
virtual bool is_key_just_released(KeyCode key) const = 0;
//-------------- 鼠标状态查询 --------------
/**
* @brief 检查鼠标按钮是否被按下
* @param button 鼠标按钮
* @return 按下返回true,否则返回false
*/
virtual bool is_mouse_button_pressed(MouseButton button) const = 0;
/**
* @brief 检查鼠标按钮是否刚被按下
* @param button 鼠标按钮
* @return 刚按下返回true,否则返回false
*/
virtual bool is_mouse_button_just_pressed(MouseButton button) const = 0;
/**
* @brief 检查鼠标按钮是否刚被释放
* @param button 鼠标按钮
* @return 刚释放返回true,否则返回false
*/
virtual bool is_mouse_button_just_released(MouseButton button) const = 0;
/**
* @brief 获取鼠标位置(窗口坐标)
* @return 鼠标位置
*/
virtual Eigen::Vector2f get_mouse_position() const = 0;
/**
* @brief 获取鼠标位置变化量(本帧与上一帧的差值)
* @return 鼠标位置变化量
*/
virtual Eigen::Vector2f get_mouse_delta() const = 0;
/**
* @brief 获取鼠标滚轮变化量
* @return 滚轮变化量
*/
virtual WheelEvent get_mouse_wheel_delta() const = 0;
//-------------- 修饰键状态 --------------
/**
* @brief 检查Shift键是否被按下
* @return 按下返回true,否则返回false
*/
virtual bool is_shift_pressed() const = 0;
/**
* @brief 检查Ctrl键是否被按下
* @return 按下返回true,否则返回false
*/
virtual bool is_control_pressed() const = 0;
/**
* @brief 检查Alt键是否被按下
* @return 按下返回true,否则返回false
*/
virtual bool is_alt_pressed() const = 0;
//-------------- 状态更新 --------------
/**
* @brief 更新输入状态
*
* 在每帧开始时调用,更新输入状态和"just pressed/released"状态
*/
virtual void update() = 0;
/**
* @brief 处理输入事件
* @param event 事件数据
*
* 根据事件更新内部状态
*/
virtual void process_event(const Event& event) = 0;
};
/**
* @brief 创建输入管理器
* @return 输入管理器的唯一指针
*/
std::unique_ptr<IInputManager> create_input_manager();
} // namespace mirage::platform

View File

@@ -0,0 +1,174 @@
#pragma once
/**
* @file platform_types.h
* @brief 平台层基础类型定义
*
* 定义跨平台使用的基本类型和枚举
*/
#include <cstdint>
#include <Eigen/Eigen>
namespace mirage::platform {
/**
* @brief 鼠标按键枚举
*/
enum class MouseButton {
Left = 0,
Right = 1,
Middle = 2,
Extra1 = 3,
Extra2 = 4
};
/**
* @brief 键盘按键代码
*
* 使用虚拟键码,映射到各平台的键盘按键
*/
enum class KeyCode {
Unknown = 0,
// 字母键
A = 'A', B = 'B', C = 'C', D = 'D', E = 'E', F = 'F', G = 'G',
H = 'H', I = 'I', J = 'J', K = 'K', L = 'L', M = 'M', N = 'N',
O = 'O', P = 'P', Q = 'Q', R = 'R', S = 'S', T = 'T', U = 'U',
V = 'V', W = 'W', X = 'X', Y = 'Y', Z = 'Z',
// 数字键
Num0 = '0', Num1 = '1', Num2 = '2', Num3 = '3', Num4 = '4',
Num5 = '5', Num6 = '6', Num7 = '7', Num8 = '8', Num9 = '9',
// 功能键
F1 = 0x70, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
// 控制键
Escape = 0x1B,
Tab = 0x09,
CapsLock = 0x14,
Shift = 0x10,
Control = 0x11,
Alt = 0x12,
Space = 0x20,
Enter = 0x0D,
Backspace = 0x08,
// 方向键
Left = 0x25,
Up = 0x26,
Right = 0x27,
Down = 0x28,
// 编辑键
Insert = 0x2D,
Delete = 0x2E,
Home = 0x24,
End = 0x23,
PageUp = 0x21,
PageDown = 0x22
};
/**
* @brief 键盘事件类型
*/
enum class KeyAction {
Press, // 按下
Release, // 释放
Repeat // 重复(长按)
};
/**
* @brief 鼠标滚轮事件
*/
struct WheelEvent {
float delta_x; // 水平滚动
float delta_y; // 垂直滚动
};
/**
* @brief 窗口事件类型
*/
enum class EventType {
None = 0,
// 鼠标事件
MouseMove,
MouseButtonDown,
MouseButtonUp,
MouseButtonDoubleClick,
MouseWheel,
MouseEnter,
MouseLeave,
// 键盘事件
KeyDown,
KeyUp,
KeyChar, // 字符输入
// 窗口事件
WindowClose,
WindowResize,
WindowMove,
WindowFocus,
WindowLostFocus,
WindowMinimize,
WindowMaximize,
WindowRestore,
// IME事件
IMEComposition,
IMECompositionEnd,
IMEChar
};
/**
* @brief 事件数据
*/
struct Event {
EventType type = EventType::None;
// 鼠标事件数据
struct MouseData {
Eigen::Vector2f position;
MouseButton button;
WheelEvent wheel;
} mouse;
// 键盘事件数据
struct KeyData {
KeyCode code;
KeyAction action;
char32_t character; // 对于 KeyChar 事件
} key;
// 窗口事件数据
struct WindowData {
Eigen::Vector2i size;
Eigen::Vector2i position;
} window;
// IME事件数据
struct IMEData {
std::u32string composition;
} ime;
};
/**
* @brief 窗口创建描述符
*/
struct WindowDesc {
const wchar_t* title = L"Mirage Window";
int width = 800;
int height = 600;
bool resizable = true;
bool decorated = true; // 是否有标题栏和边框
bool topmost = false; // 是否总在最前
// 窗口样式标志
bool has_minimize_button = true;
bool has_maximize_button = true;
bool has_close_button = true;
};
} // namespace mirage::platform

View File

@@ -0,0 +1,216 @@
#pragma once
/**
* @file window.h
* @brief 平台无关的窗口抽象接口
*
* 定义跨平台的窗口管理接口
*/
#include "platform_types.h"
#include <memory>
#include <functional>
#include <Eigen/Eigen>
namespace mirage::platform {
/**
* @brief 平台无关的窗口接口
*
* 封装了各平台的窗口管理功能,提供统一的API
*/
class IWindow {
public:
virtual ~IWindow() = default;
//-------------- 窗口生命周期 --------------
/**
* @brief 显示窗口
*/
virtual void show() = 0;
/**
* @brief 隐藏窗口
*/
virtual void hide() = 0;
/**
* @brief 关闭窗口
*/
virtual void close() = 0;
/**
* @brief 最大化窗口
*/
virtual void maximize() = 0;
/**
* @brief 最小化窗口
*/
virtual void minimize() = 0;
/**
* @brief 恢复窗口(从最小化/最大化状态)
*/
virtual void restore() = 0;
//-------------- 窗口状态查询 --------------
/**
* @brief 检查窗口是否可见
* @return 可见返回true,否则返回false
*/
virtual bool is_visible() const = 0;
/**
* @brief 检查窗口是否最小化
* @return 最小化返回true,否则返回false
*/
virtual bool is_minimized() const = 0;
/**
* @brief 检查窗口是否最大化
* @return 最大化返回true,否则返回false
*/
virtual bool is_maximized() const = 0;
/**
* @brief 检查窗口是否获得焦点
* @return 获得焦点返回true,否则返回false
*/
virtual bool is_focused() const = 0;
//-------------- 窗口属性访问 --------------
/**
* @brief 获取窗口大小(包含边框和标题栏)
* @return 窗口大小
*/
virtual Eigen::Vector2i get_size() const = 0;
/**
* @brief 获取客户区大小(不包含边框和标题栏)
* @return 客户区大小
*/
virtual Eigen::Vector2i get_client_size() const = 0;
/**
* @brief 获取窗口位置(屏幕坐标)
* @return 窗口位置
*/
virtual Eigen::Vector2i get_position() const = 0;
/**
* @brief 获取DPI缩放比例
* @return DPI缩放比例(1.0表示100%)
*/
virtual float get_dpi_scale() const = 0;
/**
* @brief 获取原生窗口句柄
* @return 平台相关的窗口句柄指针
*
* 用于需要直接访问平台API的情况(如渲染层创建图形上下文)
*/
virtual void* get_native_handle() const = 0;
//-------------- 窗口属性设置 --------------
/**
* @brief 设置窗口大小
* @param size 新的窗口大小
*/
virtual void set_size(const Eigen::Vector2i& size) = 0;
/**
* @brief 设置窗口位置
* @param position 新的窗口位置(屏幕坐标)
*/
virtual void set_position(const Eigen::Vector2i& position) = 0;
/**
* @brief 设置窗口标题
* @param title 新的窗口标题
*/
virtual void set_title(const wchar_t* title) = 0;
//-------------- 窗口样式设置 --------------
/**
* @brief 设置是否有最小化按钮
* @param enabled 是否启用
*/
virtual void set_minimize_button(bool enabled) = 0;
/**
* @brief 设置是否有最大化按钮
* @param enabled 是否启用
*/
virtual void set_maximize_button(bool enabled) = 0;
/**
* @brief 设置是否有关闭按钮
* @param enabled 是否启用
*/
virtual void set_close_button(bool enabled) = 0;
/**
* @brief 设置是否可调整大小
* @param resizable 是否可调整大小
*/
virtual void set_resizable(bool resizable) = 0;
/**
* @brief 设置是否总在最前
* @param topmost 是否总在最前
*/
virtual void set_topmost(bool topmost) = 0;
//-------------- 事件处理 --------------
/**
* @brief 轮询窗口事件
* @param event 输出事件数据
* @return 如果有事件则返回true,否则返回false
*
* 每次调用返回一个事件,需要循环调用直到返回false
*/
virtual bool poll_event(Event& event) = 0;
/**
* @brief 请求重绘窗口
*
* 标记窗口需要重绘,在下一个事件循环中会触发绘制
*/
virtual void request_redraw() = 0;
//-------------- 事件回调 --------------
/**
* @brief 设置事件回调函数
* @param callback 事件回调函数
*
* 当窗口产生事件时会调用此回调
*/
using EventCallback = std::function<void(const Event&)>;
virtual void set_event_callback(EventCallback callback) = 0;
};
/**
* @brief 创建平台窗口
* @param desc 窗口创建描述符
* @return 窗口接口的唯一指针
*
* 根据当前平台创建相应的窗口实现
*/
std::unique_ptr<IWindow> create_window(const WindowDesc& desc);
/**
* @brief 轮询所有窗口的事件
* @return 如果有活动窗口则返回true
*
* 静态函数,处理系统级别的事件消息
*/
bool poll_events();
} // namespace mirage::platform

View File

@@ -0,0 +1,199 @@
/**
* @file windows_ime.cpp
* @brief Windows 平台 IME (输入法编辑器) 实现
*/
#include "mirage_platform/ime.h"
#include "mirage_platform/platform_types.h"
#include <windows.h>
#include <imm.h>
#include <vector>
#include <memory>
#include <mutex>
#pragma comment(lib, "imm32.lib")
namespace mirage::platform {
/**
* @brief Windows IME 管理器实现类
*/
class WindowsIMEManager : public IIMEManager {
public:
WindowsIMEManager() = default;
~WindowsIMEManager() override = default;
// IME 生命周期
void enable(void* window_handle) override;
void disable() override;
bool is_enabled() const override;
// 光标位置
void set_cursor_position(const Eigen::Vector2i& position) override;
Eigen::Vector2i get_cursor_position() const override;
// 处理器管理
void register_processor(std::weak_ptr<IIMEProcessor> processor) override;
void unregister_processor(std::weak_ptr<IIMEProcessor> processor) override;
bool has_processors() const override;
// 事件处理
void process_event(const Event& event) override;
private:
HWND hwnd_ = nullptr;
Eigen::Vector2i cursor_position_{0, 0};
std::vector<std::weak_ptr<IIMEProcessor>> processors_;
mutable std::mutex processors_mutex_;
bool is_enabled_ = false;
// 辅助方法
void cleanup_dead_processors();
};
// 全局 IME 管理器实例
static std::unique_ptr<WindowsIMEManager> g_ime_manager;
static std::mutex g_ime_manager_mutex;
// WindowsIMEManager 实现
void WindowsIMEManager::enable(void* window_handle) {
hwnd_ = static_cast<HWND>(window_handle);
if (hwnd_) {
HIMC imc = ImmGetContext(hwnd_);
if (imc) {
ImmSetOpenStatus(imc, TRUE);
ImmReleaseContext(hwnd_, imc);
}
is_enabled_ = true;
}
}
void WindowsIMEManager::disable() {
if (hwnd_) {
HIMC imc = ImmGetContext(hwnd_);
if (imc) {
ImmSetOpenStatus(imc, FALSE);
ImmReleaseContext(hwnd_, imc);
}
is_enabled_ = false;
}
}
bool WindowsIMEManager::is_enabled() const {
return is_enabled_;
}
void WindowsIMEManager::set_cursor_position(const Eigen::Vector2i& position) {
cursor_position_ = position;
if (hwnd_) {
HIMC imc = ImmGetContext(hwnd_);
if (imc) {
COMPOSITIONFORM cf;
cf.dwStyle = CFS_POINT;
cf.ptCurrentPos.x = position.x();
cf.ptCurrentPos.y = position.y();
ImmSetCompositionWindow(imc, &cf);
ImmReleaseContext(hwnd_, imc);
}
}
}
Eigen::Vector2i WindowsIMEManager::get_cursor_position() const {
return cursor_position_;
}
void WindowsIMEManager::register_processor(std::weak_ptr<IIMEProcessor> processor) {
std::lock_guard<std::mutex> lock(processors_mutex_);
processors_.push_back(processor);
cleanup_dead_processors();
}
void WindowsIMEManager::unregister_processor(std::weak_ptr<IIMEProcessor> processor) {
std::lock_guard<std::mutex> lock(processors_mutex_);
// 由于 weak_ptr 无法直接比较,这里仅做清理
cleanup_dead_processors();
}
bool WindowsIMEManager::has_processors() const {
std::lock_guard<std::mutex> lock(processors_mutex_);
for (const auto& proc : processors_) {
if (!proc.expired()) {
return true;
}
}
return false;
}
void WindowsIMEManager::process_event(const Event& event) {
std::lock_guard<std::mutex> lock(processors_mutex_);
cleanup_dead_processors();
switch (event.type) {
case EventType::IMEChar: {
for (auto& proc : processors_) {
if (auto sp = proc.lock()) {
sp->on_ime_char(event.ime.composition[0]);
}
}
break;
}
case EventType::IMEComposition: {
IMEComposition comp;
comp.text = event.ime.composition;
comp.cursor_position = 0;
comp.selection_start = 0;
comp.selection_length = 0;
for (auto& proc : processors_) {
if (auto sp = proc.lock()) {
sp->on_ime_composition(comp);
}
}
break;
}
case EventType::IMECompositionEnd: {
for (auto& proc : processors_) {
if (auto sp = proc.lock()) {
sp->on_ime_composition_end();
}
}
break;
}
default:
break;
}
}
void WindowsIMEManager::cleanup_dead_processors() {
auto it = processors_.begin();
while (it != processors_.end()) {
if (it->expired()) {
it = processors_.erase(it);
} else {
++it;
}
}
}
// 全局函数实现
std::unique_ptr<IIMEManager> create_ime_manager() {
return std::make_unique<WindowsIMEManager>();
}
IIMEManager& get_ime_manager() {
std::lock_guard<std::mutex> lock(g_ime_manager_mutex);
if (!g_ime_manager) {
g_ime_manager = std::make_unique<WindowsIMEManager>();
}
return *g_ime_manager;
}
} // namespace mirage::platform

View File

@@ -0,0 +1,175 @@
/**
* @file windows_input.cpp
* @brief Windows平台输入管理器实现
*/
#include "mirage_platform/input.h"
#include "mirage_platform/platform_types.h"
#include <windows.h>
#include <unordered_map>
#include <unordered_set>
namespace mirage::platform {
/**
* @brief Windows输入管理器实现类
*/
class WindowsInputManager : public IInputManager {
public:
WindowsInputManager() = default;
~WindowsInputManager() override = default;
// 键盘状态查询
bool is_key_pressed(KeyCode key) const override;
bool is_key_just_pressed(KeyCode key) const override;
bool is_key_just_released(KeyCode key) const override;
// 鼠标状态查询
bool is_mouse_button_pressed(MouseButton button) const override;
bool is_mouse_button_just_pressed(MouseButton button) const override;
bool is_mouse_button_just_released(MouseButton button) const override;
Eigen::Vector2f get_mouse_position() const override;
Eigen::Vector2f get_mouse_delta() const override;
WheelEvent get_mouse_wheel_delta() const override;
// 修饰键状态
bool is_shift_pressed() const override;
bool is_control_pressed() const override;
bool is_alt_pressed() const override;
// 状态更新
void update() override;
void process_event(const Event& event) override;
private:
// 键盘状态
std::unordered_set<KeyCode> current_keys_;
std::unordered_set<KeyCode> previous_keys_;
// 鼠标状态
std::unordered_set<MouseButton> current_buttons_;
std::unordered_set<MouseButton> previous_buttons_;
Eigen::Vector2f current_mouse_position_{0.0f, 0.0f};
Eigen::Vector2f previous_mouse_position_{0.0f, 0.0f};
WheelEvent wheel_delta_{0.0f, 0.0f};
// 辅助方法
static int mouse_button_to_index(MouseButton button);
};
// WindowsInputManager 实现
bool WindowsInputManager::is_key_pressed(KeyCode key) const {
return current_keys_.find(key) != current_keys_.end();
}
bool WindowsInputManager::is_key_just_pressed(KeyCode key) const {
return (current_keys_.find(key) != current_keys_.end()) &&
(previous_keys_.find(key) == previous_keys_.end());
}
bool WindowsInputManager::is_key_just_released(KeyCode key) const {
return (current_keys_.find(key) == current_keys_.end()) &&
(previous_keys_.find(key) != previous_keys_.end());
}
bool WindowsInputManager::is_mouse_button_pressed(MouseButton button) const {
return current_buttons_.find(button) != current_buttons_.end();
}
bool WindowsInputManager::is_mouse_button_just_pressed(MouseButton button) const {
return (current_buttons_.find(button) != current_buttons_.end()) &&
(previous_buttons_.find(button) == previous_buttons_.end());
}
bool WindowsInputManager::is_mouse_button_just_released(MouseButton button) const {
return (current_buttons_.find(button) == current_buttons_.end()) &&
(previous_buttons_.find(button) != previous_buttons_.end());
}
Eigen::Vector2f WindowsInputManager::get_mouse_position() const {
return current_mouse_position_;
}
Eigen::Vector2f WindowsInputManager::get_mouse_delta() const {
return current_mouse_position_ - previous_mouse_position_;
}
WheelEvent WindowsInputManager::get_mouse_wheel_delta() const {
return wheel_delta_;
}
bool WindowsInputManager::is_shift_pressed() const {
return is_key_pressed(KeyCode::Shift);
}
bool WindowsInputManager::is_control_pressed() const {
return is_key_pressed(KeyCode::Control);
}
bool WindowsInputManager::is_alt_pressed() const {
return is_key_pressed(KeyCode::Alt);
}
void WindowsInputManager::update() {
// 保存上一帧的状态
previous_keys_ = current_keys_;
previous_buttons_ = current_buttons_;
previous_mouse_position_ = current_mouse_position_;
// 重置滚轮增量
wheel_delta_.delta_x = 0.0f;
wheel_delta_.delta_y = 0.0f;
}
void WindowsInputManager::process_event(const Event& event) {
switch (event.type) {
case EventType::KeyDown:
current_keys_.insert(event.key.code);
break;
case EventType::KeyUp:
current_keys_.erase(event.key.code);
break;
case EventType::MouseButtonDown:
current_buttons_.insert(event.mouse.button);
break;
case EventType::MouseButtonUp:
current_buttons_.erase(event.mouse.button);
break;
case EventType::MouseMove:
current_mouse_position_ = event.mouse.position;
break;
case EventType::MouseWheel:
wheel_delta_.delta_x += event.mouse.wheel.delta_x;
wheel_delta_.delta_y += event.mouse.wheel.delta_y;
break;
default:
break;
}
}
int WindowsInputManager::mouse_button_to_index(MouseButton button) {
switch (button) {
case MouseButton::Left: return 0;
case MouseButton::Right: return 1;
case MouseButton::Middle: return 2;
case MouseButton::Extra1: return 3;
case MouseButton::Extra2: return 4;
default: return -1;
}
}
// 工厂函数实现
std::unique_ptr<IInputManager> create_input_manager() {
return std::make_unique<WindowsInputManager>();
}
} // namespace mirage::platform

View File

@@ -0,0 +1,414 @@
/**
* @file windows_window.cpp
* @brief Windows平台窗口实现
*/
#include "mirage_platform/window.h"
#include "mirage_platform/platform_types.h"
#include <windows.h>
#include <windowsx.h>
#include <queue>
#include <unordered_map>
namespace mirage::platform {
/**
* @brief Windows窗口实现类
*/
class WindowsWindow : public IWindow {
public:
explicit WindowsWindow(const WindowDesc& desc);
~WindowsWindow() override;
// 窗口生命周期
void show() override;
void hide() override;
void close() override;
void maximize() override;
void minimize() override;
void restore() override;
// 窗口状态查询
bool is_visible() const override;
bool is_minimized() const override;
bool is_maximized() const override;
bool is_focused() const override;
// 窗口属性访问
Eigen::Vector2i get_size() const override;
Eigen::Vector2i get_client_size() const override;
Eigen::Vector2i get_position() const override;
float get_dpi_scale() const override;
void* get_native_handle() const override;
// 窗口属性设置
void set_size(const Eigen::Vector2i& size) override;
void set_position(const Eigen::Vector2i& position) override;
void set_title(const wchar_t* title) override;
// 窗口样式设置
void set_minimize_button(bool enabled) override;
void set_maximize_button(bool enabled) override;
void set_close_button(bool enabled) override;
void set_resizable(bool resizable) override;
void set_topmost(bool topmost) override;
// 事件处理
bool poll_event(Event& event) override;
void request_redraw() override;
void set_event_callback(EventCallback callback) override;
// 内部方法
void push_event(const Event& event);
LRESULT handle_message(UINT message, WPARAM wParam, LPARAM lParam);
private:
HWND hwnd_ = nullptr;
EventCallback event_callback_;
std::queue<Event> event_queue_;
bool is_closed_ = false;
// 辅助方法
void update_window_style();
DWORD get_window_style() const;
DWORD get_window_ex_style() const;
};
// 全局窗口过程函数
static std::unordered_map<HWND, WindowsWindow*> g_window_map;
LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
auto it = g_window_map.find(hwnd);
if (it != g_window_map.end()) {
return it->second->handle_message(message, wParam, lParam);
}
return DefWindowProcW(hwnd, message, wParam, lParam);
}
// WindowsWindow 实现
WindowsWindow::WindowsWindow(const WindowDesc& desc) {
// 注册窗口类
static bool class_registered = false;
if (!class_registered) {
WNDCLASSW wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = GetModuleHandleW(nullptr);
wc.lpszClassName = L"MirageWindowClass";
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS;
wc.hCursor = LoadCursorW(nullptr, IDC_ARROW);
wc.hIcon = LoadIconW(nullptr, IDI_APPLICATION);
wc.hbrBackground = nullptr;
if (!RegisterClassW(&wc)) {
// 处理注册失败
return;
}
class_registered = true;
}
// 计算窗口大小(包含边框和标题栏)
RECT rect = { 0, 0, desc.width, desc.height };
DWORD style = WS_OVERLAPPEDWINDOW;
AdjustWindowRect(&rect, style, FALSE);
// 创建窗口
hwnd_ = CreateWindowExW(
0,
L"MirageWindowClass",
desc.title,
style,
CW_USEDEFAULT, CW_USEDEFAULT,
rect.right - rect.left,
rect.bottom - rect.top,
nullptr,
nullptr,
GetModuleHandleW(nullptr),
nullptr
);
if (!hwnd_) {
// 处理创建失败
return;
}
// 注册窗口
g_window_map[hwnd_] = this;
// 应用窗口样式
update_window_style();
}
WindowsWindow::~WindowsWindow() {
if (hwnd_) {
g_window_map.erase(hwnd_);
DestroyWindow(hwnd_);
hwnd_ = nullptr;
}
}
void WindowsWindow::show() {
if (hwnd_) {
ShowWindow(hwnd_, SW_SHOW);
}
}
void WindowsWindow::hide() {
if (hwnd_) {
ShowWindow(hwnd_, SW_HIDE);
}
}
void WindowsWindow::close() {
if (hwnd_ && !is_closed_) {
Event event;
event.type = EventType::WindowClose;
push_event(event);
is_closed_ = true;
}
}
void WindowsWindow::maximize() {
if (hwnd_) {
ShowWindow(hwnd_, SW_MAXIMIZE);
}
}
void WindowsWindow::minimize() {
if (hwnd_) {
ShowWindow(hwnd_, SW_MINIMIZE);
}
}
void WindowsWindow::restore() {
if (hwnd_) {
ShowWindow(hwnd_, SW_RESTORE);
}
}
bool WindowsWindow::is_visible() const {
return hwnd_ && IsWindowVisible(hwnd_) && !IsIconic(hwnd_);
}
bool WindowsWindow::is_minimized() const {
return hwnd_ && IsIconic(hwnd_);
}
bool WindowsWindow::is_maximized() const {
return hwnd_ && IsZoomed(hwnd_);
}
bool WindowsWindow::is_focused() const {
return hwnd_ && GetForegroundWindow() == hwnd_;
}
Eigen::Vector2i WindowsWindow::get_size() const {
if (!hwnd_) return { 0, 0 };
RECT rect;
GetWindowRect(hwnd_, &rect);
return { rect.right - rect.left, rect.bottom - rect.top };
}
Eigen::Vector2i WindowsWindow::get_client_size() const {
if (!hwnd_) return { 0, 0 };
RECT rect;
GetClientRect(hwnd_, &rect);
return { rect.right - rect.left, rect.bottom - rect.top };
}
Eigen::Vector2i WindowsWindow::get_position() const {
if (!hwnd_) return { 0, 0 };
RECT rect;
GetWindowRect(hwnd_, &rect);
return { rect.left, rect.top };
}
float WindowsWindow::get_dpi_scale() const {
if (!hwnd_) return 1.0f;
HDC hdc = GetDC(hwnd_);
if (!hdc) return 1.0f;
int dpi = GetDeviceCaps(hdc, LOGPIXELSX);
ReleaseDC(hwnd_, hdc);
return static_cast<float>(dpi) / 96.0f;
}
void* WindowsWindow::get_native_handle() const {
return hwnd_;
}
void WindowsWindow::set_size(const Eigen::Vector2i& size) {
if (!hwnd_) return;
RECT rect;
GetWindowRect(hwnd_, &rect);
MoveWindow(hwnd_, rect.left, rect.top, size.x(), size.y(), TRUE);
}
void WindowsWindow::set_position(const Eigen::Vector2i& position) {
if (!hwnd_) return;
RECT rect;
GetWindowRect(hwnd_, &rect);
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
MoveWindow(hwnd_, position.x(), position.y(), width, height, TRUE);
}
void WindowsWindow::set_title(const wchar_t* title) {
if (hwnd_) {
SetWindowTextW(hwnd_, title);
}
}
void WindowsWindow::set_minimize_button(bool enabled) {
if (!hwnd_) return;
LONG style = GetWindowLongW(hwnd_, GWL_STYLE);
if (enabled) {
style |= WS_MINIMIZEBOX;
} else {
style &= ~WS_MINIMIZEBOX;
}
SetWindowLongW(hwnd_, GWL_STYLE, style);
update_window_style();
}
void WindowsWindow::set_maximize_button(bool enabled) {
if (!hwnd_) return;
LONG style = GetWindowLongW(hwnd_, GWL_STYLE);
if (enabled) {
style |= WS_MAXIMIZEBOX;
} else {
style &= ~WS_MAXIMIZEBOX;
}
SetWindowLongW(hwnd_, GWL_STYLE, style);
update_window_style();
}
void WindowsWindow::set_close_button(bool enabled) {
if (!hwnd_) return;
LONG style = GetWindowLongW(hwnd_, GWL_STYLE);
if (enabled) {
style |= WS_SYSMENU;
} else {
style &= ~WS_SYSMENU;
}
SetWindowLongW(hwnd_, GWL_STYLE, style);
update_window_style();
}
void WindowsWindow::set_resizable(bool resizable) {
if (!hwnd_) return;
LONG style = GetWindowLongW(hwnd_, GWL_STYLE);
if (resizable) {
style |= WS_THICKFRAME;
} else {
style &= ~WS_THICKFRAME;
}
SetWindowLongW(hwnd_, GWL_STYLE, style);
update_window_style();
}
void WindowsWindow::set_topmost(bool topmost) {
if (!hwnd_) return;
SetWindowPos(hwnd_,
topmost ? HWND_TOPMOST : HWND_NOTOPMOST,
0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE);
}
bool WindowsWindow::poll_event(Event& event) {
if (event_queue_.empty()) {
return false;
}
event = event_queue_.front();
event_queue_.pop();
return true;
}
void WindowsWindow::request_redraw() {
if (hwnd_) {
InvalidateRect(hwnd_, nullptr, FALSE);
}
}
void WindowsWindow::set_event_callback(EventCallback callback) {
event_callback_ = std::move(callback);
}
void WindowsWindow::push_event(const Event& event) {
event_queue_.push(event);
if (event_callback_) {
event_callback_(event);
}
}
LRESULT WindowsWindow::handle_message(UINT message, WPARAM wParam, LPARAM lParam) {
Event event;
switch (message) {
case WM_CLOSE:
event.type = EventType::WindowClose;
push_event(event);
return 0;
case WM_SIZE: {
event.type = EventType::WindowResize;
event.window.size = { LOWORD(lParam), HIWORD(lParam) };
push_event(event);
return 0;
}
case WM_MOVE: {
event.type = EventType::WindowMove;
event.window.position = { LOWORD(lParam), HIWORD(lParam) };
push_event(event);
return 0;
}
// TODO: 添加更多消息处理
default:
return DefWindowProcW(hwnd_, message, wParam, lParam);
}
}
void WindowsWindow::update_window_style() {
if (hwnd_) {
SetWindowPos(hwnd_, nullptr, 0, 0, 0, 0,
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
}
}
// 工厂函数实现
std::unique_ptr<IWindow> create_window(const WindowDesc& desc) {
return std::make_unique<WindowsWindow>(desc);
}
bool poll_events() {
MSG msg;
bool has_messages = false;
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
has_messages = true;
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return has_messages;
}
} // namespace mirage::platform

View File

@@ -0,0 +1,49 @@
cmake_minimum_required(VERSION 3.15)
project(mirage_render LANGUAGES C CXX)
set(SOURCE_FILES)
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/src SOURCE_FILES)
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/include SOURCE_FILES)
add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES})
# 设置公共头文件包含目录
target_include_directories(${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include # 公共接口目录
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src # 内部实现目录
)
target_link_libraries(${PROJECT_NAME} PUBLIC mirage_core sokol mirage_image)
# 设置图像加载器
set(MIRAGE_IMAGE_LOAD_BACKEND "STB_IMAGE" CACHE STRING "选择图像加载器")
set_property(CACHE MIRAGE_IMAGE_LOAD_BACKEND PROPERTY STRINGS "STB_IMAGE" "CUSTOM")
if (MIRAGE_IMAGE_LOAD_BACKEND STREQUAL "STB_IMAGE")
add_subdirectory(image/stb_image_loader)
target_link_libraries(${PROJECT_NAME} PUBLIC stb_image_loader)
target_compile_definitions(${PROJECT_NAME} PUBLIC HAS_STB_IMAGE)
endif ()
# 设置字体后端
set(MIRAGE_FONT_BACKEND "STB_TRUETYPE" CACHE STRING "选择字体后端")
set_property(CACHE MIRAGE_FONT_BACKEND PROPERTY STRINGS "STB_TRUETYPE" "FREETYPE" "CUSTOM")
if (MIRAGE_FONT_BACKEND STREQUAL "STB_TRUETYPE")
add_subdirectory(font/stb_truetype_font)
target_link_libraries(${PROJECT_NAME} PUBLIC stb_truetype_font)
target_compile_definitions(${PROJECT_NAME} PUBLIC HAS_STB_TRUETYPE)
elseif (MIRAGE_FONT_BACKEND STREQUAL "FREETYPE")
add_subdirectory(font/freetype_font)
target_link_libraries(${PROJECT_NAME} PUBLIC freetype_font)
target_compile_definitions(${PROJECT_NAME} PUBLIC HAS_FREETYPE)
endif ()
# 添加编译shader的自定义命令
add_mirage_shader_directory(${CMAKE_CURRENT_SOURCE_DIR}/src/shaders)
add_shader_dependencies(${PROJECT_NAME})
if (WIN32)
target_link_libraries(${PROJECT_NAME} PUBLIC imm32)
endif ()

View File

@@ -0,0 +1,10 @@
project(freetype_font)
find_package(freetype CONFIG REQUIRED)
find_package(harfbuzz CONFIG REQUIRED)
set(SRC_FILES)
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_FILES)
add_library(${PROJECT_NAME} STATIC ${SRC_FILES})
target_link_libraries(${PROJECT_NAME} PUBLIC freetype harfbuzz mirage_render)
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)

View File

@@ -0,0 +1,255 @@
//
// Created by 46944 on 25-4-1.
//
#include "freetype_interface.h"
#include "pixel.h"
#include "freetype/ftadvanc.h"
#include "freetype/ftcolor.h"
#include "freetype/ftglyph.h"
#include "freetype/ftlcdfil.h"
#include "geometry/dpi_helper.h"
#include "interface/image_interface.h"
FT_Library library_;
void freetype_bitmap_deleter(image_heap_t* in_data) {
if (in_data == nullptr) {
return;
}
if (in_data->data != nullptr) {
const auto data = (uint8_t*)in_data->data;
delete[] data;
in_data->data = nullptr;
}
delete in_data;
}
std::string freetype_interface::get_font_family() const {
if (face_ == nullptr) {
return "unknown font";
}
return face_->family_name ? face_->family_name : "unknown font";
}
std::string freetype_interface::get_font_style() const {
if (face_ == nullptr) {
return "unknown font";
}
return face_->style_name ? face_->style_name : "unknown font";
}
bool freetype_interface::supports_color_emoji() const {
return FT_HAS_COLOR(face_) || FT_HAS_SBIX(face_) || FT_HAS_SVG(face_);
}
font_v_metrics_t freetype_interface::get_metrics_impl() const {
font_v_metrics_t metrics{};
// 使用当前字体大小下的度量信息
metrics.ascent = face_->size->metrics.ascender >> 6;
metrics.descent = -face_->size->metrics.descender >> 6; // FreeType中descent为负值
metrics.line_height = face_->size->metrics.height >> 6;
return metrics;
}
std::shared_ptr<image_heap_t> freetype_interface::get_glyph_image(int32_t in_glyph_id) const {
if (in_glyph_id == 0) {
return nullptr;
}
// 加载字形
FT_Load_Glyph(face_, in_glyph_id, FT_LOAD_RENDER);
if (face_->glyph->format != FT_GLYPH_FORMAT_BITMAP) {
return nullptr;
}
// FT_Render_Glyph(face_->glyph, FT_RENDER_MODE_LCD);
auto& bitmap = face_->glyph->bitmap;
// 创建位图
// 这里使用了一个自定义的删除器来释放位图数据
std::shared_ptr<image_heap_t> image(new image_heap_t(), freetype_bitmap_deleter);
image->width = bitmap.width;
image->height = bitmap.rows;
image->pixel_format = SG_PIXELFORMAT_R8;
image->data = new uint8_t[image->width * image->height];
std::memcpy(image->data, face_->glyph->bitmap.buffer, image->width * image->height);
return image;
}
std::shared_ptr<image_heap_t> freetype_interface::get_emoji_image(int32_t in_glyph_id) const {
if (in_glyph_id == 0) {
return nullptr;
}
// 加载字形
FT_Load_Glyph(face_, in_glyph_id, FT_LOAD_RENDER | FT_LOAD_COLOR | FT_LOAD_FORCE_AUTOHINT);
if (face_->glyph->format != FT_GLYPH_FORMAT_BITMAP) {
return nullptr;
}
FT_Bitmap& bitmap = face_->glyph->bitmap;
// 创建位图
// 这里使用了一个自定义的删除器来释放位图数据
std::shared_ptr<image_heap_t> image(new image_heap_t(), freetype_bitmap_deleter);
image->width = bitmap.width;
image->height = bitmap.rows;
image->pixel_format = SG_PIXELFORMAT_BGRA8;
image->data = new uint8_t[image->width * image->height * 4];
// 初始化为透明
clear_bitmap(image.get());
attach_bitmap(bitmap, image.get());
return image;
}
uint32_t freetype_interface::find_glyph_index(uint32_t in_unicode_codepoint) const {
return FT_Get_Char_Index(face_, in_unicode_codepoint);
}
float freetype_interface::get_kerning(uint32_t in_first_glyph_id, uint32_t in_second_glyph_id) const {
if (!FT_HAS_KERNING(face_)) {
return 0.0f;
}
FT_Vector kerning;
if (FT_Get_Kerning(face_, in_first_glyph_id, in_second_glyph_id, FT_KERNING_DEFAULT, &kerning) != 0) {
return 0.0f;
}
return kerning.x >> 6;
}
glyph_shaped_t freetype_interface::make_shape_glyph(uint32_t in_glyph_id) const {
glyph_shaped_t out{};
// 加载字形 - 使用 FT_LOAD_DEFAULT 而非 FT_LOAD_NO_BITMAP
if (FT_Load_Glyph(face_, in_glyph_id, FT_LOAD_DEFAULT | FT_LOAD_FORCE_AUTOHINT) != 0) {
return out;
}
// 获取字形度量
FT_GlyphSlot slot = face_->glyph;
// 设置基本信息
out.glyph_index = in_glyph_id;
// 计算字形的前进量 (26.6 固定小数点格式转为浮点数)
out.advance.x() = slot->advance.x >> 6;
out.advance.y() = slot->metrics.vertAdvance >> 6;
// 字形偏移量
out.offset.x() = static_cast<float>(slot->bitmap_left);
out.offset.y() = static_cast<float>(-slot->bitmap_top);
// 水平基线
out.hori_bearing.x() = static_cast<float>(slot->metrics.horiBearingX) / 64.0f;
out.hori_bearing.y() = static_cast<float>(slot->metrics.horiBearingY) / -64.0f;
// 垂直基线
out.vert_bearing.x() = static_cast<float>(slot->metrics.vertBearingX) / 64.0f;
out.vert_bearing.y() = static_cast<float>(slot->metrics.vertBearingY) / 64.0f;
// 字形矩形 - 直接使用位图尺寸
if (slot->format == FT_GLYPH_FORMAT_BITMAP) {
// 位图字形
out.rect.set_position({ 0, 0 });
out.rect.set_size({
static_cast<float>(slot->bitmap.width),
static_cast<float>(slot->bitmap.rows)
});
} else {
// 非位图字形 - 使用轮廓边界盒
FT_Glyph glyph;
if (FT_Get_Glyph(slot, &glyph) != 0) {
return out;
}
FT_BBox bbox;
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox);
out.rect.set_position({ bbox.xMin, bbox.yMin });
out.rect.set_size({
static_cast<float>(bbox.xMax - bbox.xMin),
static_cast<float>(bbox.yMax - bbox.yMin)
});
FT_Done_Glyph(glyph);
}
return out;
}
bool freetype_interface::on_load() {
return FT_New_Memory_Face(library_, font_data_->get_u8(), font_data_->get_size(), 0, &face_) == 0;
}
void freetype_interface::on_set_font_size(float in_size) {
font_face_interface::on_set_font_size(in_size);
const FT_F26Dot6 font_size = static_cast<FT_F26Dot6>(in_size * 64);
FT_Set_Char_Size(face_, 0, font_size, dpi_helper::dpi_x(), dpi_helper::dpi_y());
}
void freetype_interface::clear_bitmap(image_heap_t* in_image) {
memset(in_image->data, 0, in_image->width * in_image->height * 4);
}
void freetype_interface::attach_bitmap(const FT_Bitmap& in_bitmap, image_heap_t* in_image) {
// 将FreeType位图数据复制到新分配的内存中
switch (in_bitmap.pixel_mode) {
case FT_PIXEL_MODE_BGRA:
// 彩色emoji (BGRA -> RGBA)
for (unsigned int y = 0; y < in_bitmap.rows; y++) {
for (unsigned int x = 0; x < in_bitmap.width; x++) {
unsigned char* src = in_bitmap.buffer + (y * in_bitmap.pitch + x * 4);
unsigned char* dst = (uint8_t*)in_image->data + (y * in_image->width + x) * 4;
// BGRA -> RGBA
dst[0] += src[2]; // R <- B
dst[1] += src[1]; // G <- G
dst[2] += src[0]; // B <- R
dst[3] += src[3]; // A <- A
}
}
break;
case FT_PIXEL_MODE_GRAY:
// 灰度图转RGBA
for (unsigned int y = 0; y < in_bitmap.rows; y++) {
for (unsigned int x = 0; x < in_bitmap.width; x++) {
unsigned char gray = in_bitmap.buffer[y * in_bitmap.pitch + x];
unsigned char* dst = (uint8_t*) in_image->data + (y * in_image->width + x) * 4;
dst[0] = gray; // R
dst[1] = gray; // G
dst[2] = gray; // B
dst[3] = gray; // A
}
}
break;
default:
break;
}
}
bool init_font_system() {
if (FT_Init_FreeType(&library_)) {
return false;
}
FT_Library_SetLcdFilter(library_, FT_LCD_FILTER_DEFAULT);
return true;
}
void destroy_font_system() {
FT_Done_FreeType(library_);
}
font_face_ptr create_font_face(const std::filesystem::path& in_path) {
auto font_face = std::make_shared<freetype_interface>();
if (!font_face->load(in_path)) {
return nullptr;
}
return font_face;
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "freetype/freetype.h"
#include "interface/font_interface.h"
class freetype_interface : public font_face_interface {
public:
[[nodiscard]] std::string get_font_family() const override;
[[nodiscard]] std::string get_font_style() const override;
[[nodiscard]] bool supports_color_emoji() const override;
[[nodiscard]] font_v_metrics_t get_metrics_impl() const override;
[[nodiscard]] std::shared_ptr<image_heap_t> get_glyph_image(int32_t in_glyph_id) const override;
[[nodiscard]] std::shared_ptr<image_heap_t> get_emoji_image(int32_t in_glyph_id) const override;
[[nodiscard]] uint32_t find_glyph_index(uint32_t in_unicode_codepoint) const override;
[[nodiscard]] float get_kerning(uint32_t in_first_glyph_id, uint32_t in_second_glyph_id) const override;
[[nodiscard]] glyph_shaped_t make_shape_glyph(uint32_t in_glyph_id) const override;
protected:
bool on_load() override;
void on_set_font_size(float in_size) override;
// 附加位图
static void clear_bitmap(image_heap_t* in_image);
static void attach_bitmap(const FT_Bitmap& in_bitmap, image_heap_t* in_image);
protected:
FT_Face face_{};
};

View File

@@ -0,0 +1,10 @@
project(stb_truetype_font)
set(SRC_FILES)
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR} SRC_FILES)
find_package(stb CONFIG REQUIRED)
add_library(${PROJECT_NAME} STATIC ${SRC_FILES})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC mirage_render stb)

View File

@@ -0,0 +1,195 @@
#include "stb_truetype_interface.h"
#define STB_TRUETYPE_IMPLEMENTATION
#include "stb_truetype.h"
#include "font/font_utils.h"
#include "geometry/dpi_helper.h"
#include "interface/image_interface.h"
void stb_truetype_deleter(image_heap_t* in_data) {
if (in_data == nullptr) {
return;
}
if (in_data->data != nullptr) {
stbtt_FreeBitmap((uint8_t*)in_data->data, nullptr);
}
delete in_data;
}
std::string stb_font_face_t::get_font_family() const {
const auto* data = static_cast<const uint8_t*>(font_data_->get_data());
const int font_offset = stbtt_GetFontOffsetForIndex(data, 0);
if (font_offset < 0) {
return "unknown font";
}
uint32_t name_offset = font::find_table_offset(data, font_offset, "name");
if (name_offset == 0) {
return "unknown font";
}
const uint8_t* name_table = data + name_offset;
const uint16_t count = font::read_u16(name_table + 2);
const uint16_t string_offset = font::read_u16(name_table + 4);
for (uint16_t i = 0; i < count; ++i) {
const uint8_t* record = name_table + 6 + 12 * i;
const uint16_t name_id = font::read_u16(record + 6);
if (name_id == 1) {
const uint16_t length = font::read_u16(record + 8);
const uint16_t offset = font::read_u16(record + 10);
if (length > 0) {
const uint8_t* string_data = name_table + string_offset + offset;
std::string name(reinterpret_cast<const char*>(string_data), length);
return name;
}
}
}
return "unknown font";
}
std::string stb_font_face_t::get_font_style() const {
const auto* data = static_cast<const uint8_t*>(font_data_->get_data());
const int font_offset = stbtt_GetFontOffsetForIndex(data, 0);
if (font_offset < 0) {
return "unknown font";
}
uint32_t name_offset = font::find_table_offset(data, font_offset, "name");
if (name_offset == 0) {
return "unknown font";
}
const uint8_t* name_table = data + name_offset;
const uint16_t count = font::read_u16(name_table + 2);
const uint16_t string_offset = font::read_u16(name_table + 4);
for (uint16_t i = 0; i < count; ++i) {
const uint8_t* record = name_table + 6 + 12 * i;
const uint16_t name_id = font::read_u16(record + 6);
if (name_id == 2) {
const uint16_t length = font::read_u16(record + 8);
const uint16_t offset = font::read_u16(record + 10);
if (length > 0) {
const uint8_t* string_data = name_table + string_offset + offset;
std::string name(reinterpret_cast<const char*>(string_data), length);
return name;
}
}
}
return "unknown font";
}
bool stb_font_face_t::supports_color_emoji() const {
// stb_truetype不支持彩色表情符号
return false;
}
font_v_metrics_t stb_font_face_t::get_metrics_impl() const {
int32_t ascent, descent, line_gap;
// 尝试使用OS2表中的度量这通常更准确
if (!stbtt_GetFontVMetricsOS2(&font_info_, &ascent, &descent, &line_gap)) {
// 如果失败使用默认的VMetrics
stbtt_GetFontVMetrics(&font_info_, &ascent, &descent, &line_gap);
}
line_gap = static_cast<int32_t>(line_gap * scale_ + 0.5f);
font_v_metrics_t metrics{};
metrics.ascent = ascent * scale_ + 0.5f;
metrics.descent = descent * scale_ - 0.5f;
metrics.line_height = metrics.ascent - metrics.descent + line_gap;
return metrics;
}
std::shared_ptr<image_heap_t> stb_font_face_t::get_glyph_image(int32_t in_glyph_id) const {
if (in_glyph_id == 0) {
return nullptr;
}
int32_t width, height, xoff, yoff;
// 获取字形图像
const auto bitmap = stbtt_GetGlyphBitmapSubpixel(&font_info_, scale_, scale_, 0.33f, 0, in_glyph_id, &width, &height, &xoff, &yoff);
// 创建位图
std::shared_ptr<image_heap_t> image(new image_heap_t(), stb_truetype_deleter);
image->width = width;
image->height = height;
image->pixel_format = SG_PIXELFORMAT_R8;
image->data = bitmap;
return image;
}
std::shared_ptr<image_heap_t> stb_font_face_t::get_emoji_image(int32_t in_glyph_id) const {
return nullptr;
}
bool stb_font_face_t::has_glyph_index(uint32_t in_glyph_index) const {
if (in_glyph_index == 0) {
return false; // 0通常表示无效的字形索引
}
// 检查字形索引是否存在
return stbtt_IsGlyphEmpty(&font_info_, in_glyph_index) == 0;
}
uint32_t stb_font_face_t::find_glyph_index(uint32_t in_unicode_codepoint) const {
return stbtt_FindGlyphIndex(&font_info_, in_unicode_codepoint);
}
float stb_font_face_t::get_kerning(uint32_t in_first_glyph_id, uint32_t in_second_glyph_id) const {
return (float)stbtt_GetGlyphKernAdvance(&font_info_, in_first_glyph_id, in_second_glyph_id) * scale_;
}
glyph_shaped_t stb_font_face_t::make_shape_glyph(uint32_t in_glyph_id) const {
int32_t advance, lsb;
stbtt_GetGlyphHMetrics(&font_info_, in_glyph_id, &advance, &lsb);
// 获取字形的边界框
int32_t x0, y0, x1, y1;
stbtt_GetGlyphBitmapBoxSubpixel(&font_info_, in_glyph_id, scale_, scale_, 0.33f, 0, &x0, &y0, &x1, &y1);
glyph_shaped_t out{};
out.glyph_index = in_glyph_id;
out.offset.x() = (float)lsb * scale_;
out.offset.y() = (float)y0;
out.advance.x() = (float)advance * scale_;
out.advance.y() = 0.0f;
out.rect.set_position({ x0, y0 });
out.rect.set_size({ x1 - x0, y1 - y0 });
out.hori_bearing.x() = (float)lsb * scale_;
out.hori_bearing.y() = (float)y0;
out.vert_bearing.x() = 0.0f;
out.vert_bearing.y() = (float)y0;
return out;
}
bool stb_font_face_t::on_load() {
const auto offset = stbtt_GetFontOffsetForIndex(font_data_->get_u8(), 0);
return stbtt_InitFont(&font_info_, font_data_->get_u8(), offset) != 0;
}
void stb_font_face_t::on_set_font_size(float in_size) {
font_face_interface::on_set_font_size(in_size);
float desired_pixel_height = static_cast<float>(in_size * dpi_helper::dpi_y() / 72.0);
desired_pixel_height = std::floorf(desired_pixel_height);
scale_ = get_scale_for_pixel_height(desired_pixel_height);
}
bool init_font_system() {
// stb_truetype不需要初始化
return true;
}
void destroy_font_system() {
// stb_truetype不需要销毁
}
font_face_ptr create_font_face(const std::filesystem::path& in_path) {
auto font_face = std::make_shared<stb_font_face_t>();
if (!font_face->load(in_path)) {
return nullptr;
}
return font_face;
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include "stb_truetype.h"
#include "interface/font_interface.h"
class stb_font_face_t : public font_face_interface {
public:
[[nodiscard]] std::string get_font_family() const override;
[[nodiscard]] std::string get_font_style() const override;
[[nodiscard]] bool supports_color_emoji() const override;
[[nodiscard]] font_v_metrics_t get_metrics_impl() const override;
[[nodiscard]] std::shared_ptr<image_heap_t> get_glyph_image(int32_t in_glyph_id) const override;
[[nodiscard]] std::shared_ptr<image_heap_t> get_emoji_image(int32_t in_glyph_id) const override;
virtual bool has_glyph_index(uint32_t in_glyph_index) const override;
[[nodiscard]] uint32_t find_glyph_index(uint32_t in_unicode_codepoint) const override;
[[nodiscard]] float get_kerning(uint32_t in_first_glyph_id, uint32_t in_second_glyph_id) const override;
[[nodiscard]] glyph_shaped_t make_shape_glyph(uint32_t in_glyph_id) const override;
[[nodiscard]] float get_scale_for_pixel_height(float font_size) const {
return stbtt_ScaleForMappingEmToPixels(&font_info_, font_size);
}
protected:
bool on_load() override;
void on_set_font_size(float in_size) override;
protected:
stbtt_fontinfo font_info_{};
float scale_{};
};

View File

@@ -0,0 +1,10 @@
project(stb_image_loader)
set(SRC_FILES)
retrieve_files(${CMAKE_CURRENT_SOURCE_DIR} SRC_FILES)
find_package(stb CONFIG REQUIRED)
add_library(${PROJECT_NAME} STATIC ${SRC_FILES})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC mirage_render stb)

View File

@@ -0,0 +1,123 @@
#include "stb_image_loader.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include "interface/image_interface.h"
sg_pixel_format stb_image_info::get_pixel_format() const {
// 根据通道数和是否HDR选择合适的像素格式
if (is_hdr) {
switch (channels) {
case 1:
return SG_PIXELFORMAT_R32F;
case 2:
return SG_PIXELFORMAT_RG32F;
case 3:
case 4:
return SG_PIXELFORMAT_RGBA32F;
default:
return SG_PIXELFORMAT_RGBA32F;
}
}
// 16位图像
if (is_16_bit) {
switch (channels) {
case 1:
return SG_PIXELFORMAT_R16;
case 2:
return SG_PIXELFORMAT_RG16;
case 3:
case 4:
return SG_PIXELFORMAT_RGBA16;
default:
return SG_PIXELFORMAT_RGBA16;
}
}
// 8位图像
switch (channels) {
case 1:
return SG_PIXELFORMAT_R8;
case 2:
return SG_PIXELFORMAT_RG8;
case 3:
case 4:
return SG_PIXELFORMAT_RGBA8;
default:
return SG_PIXELFORMAT_RGBA8;
}
}
void stb_image_loader::init(void* in_data, int32_t in_size) {
data_ = static_cast<stbi_uc*>(in_data);
size_ = in_size;
}
stb_image_info stb_image_loader::get_info() const {
stb_image_info info{};
stbi_info_from_memory(data_, size_, &info.width, &info.height, &info.channels);
info.is_hdr = is_hdr();
info.is_16_bit = is_16_bit();
return info;
}
bool stb_image_loader::is_hdr() const {
return stbi_is_hdr_from_memory(data_, size_);
}
bool stb_image_loader::is_16_bit() const {
return stbi_is_16_bit_from_memory(data_, size_);
}
float* stb_image_loader::load_hdr() const {
int width, height, channels;
return stbi_loadf_from_memory(data_, size_, &width, &height, &channels, 4);
}
uint16_t* stb_image_loader::load_16_bit() const {
int width, height, channels;
return stbi_load_16_from_memory(data_, size_, &width, &height, &channels, 4);
}
uint8_t* stb_image_loader::load_8_bit() const {
int width, height, channels;
return stbi_load_from_memory(data_, size_, &width, &height, &channels, 4);
}
struct stb_heap_image {
stb_image_info info;
void* data;
~stb_heap_image() {
if (data)
stbi_image_free(data);
}
};
void delete_stb_image(image_heap_t* in_data) {
if (in_data->data)
stbi_image_free(in_data->data);
delete in_data;
}
std::shared_ptr<image_heap_t> stb_image_loader::load() const {
std::shared_ptr<image_heap_t> heap(new image_heap_t, delete_stb_image);
const auto& info = get_info();
heap->width = info.width;
heap->height = info.height;
heap->pixel_format = info.get_pixel_format();
if (is_hdr())
heap->data = load_hdr();
else if (is_16_bit())
heap->data = load_16_bit();
else
heap->data = load_8_bit();
return heap;
}
std::shared_ptr<image_heap_t> load_image(const std::span<std::byte>& in_raw_data) {
stb_image_loader loader;
loader.init(in_raw_data.data(), static_cast<int32_t>(in_raw_data.size()));
return loader.load();
}

View File

@@ -0,0 +1,35 @@
#pragma once
#include <cstdint>
#include <memory>
#include "sokol_gfx.h"
#include "stb_image.h"
struct image_heap_t;
struct stb_image_info {
int32_t width;
int32_t height;
int32_t channels;
bool is_hdr;
bool is_16_bit;
[[nodiscard]] sg_pixel_format get_pixel_format() const;
};
class stb_image_loader {
public:
void init(void* in_data, int32_t in_size);
[[nodiscard]] stb_image_info get_info() const;
[[nodiscard]] bool is_hdr() const;
[[nodiscard]] bool is_16_bit() const;
[[nodiscard]] float* load_hdr() const;
[[nodiscard]] uint16_t* load_16_bit() const;
[[nodiscard]] uint8_t* load_8_bit() const;
[[nodiscard]] std::shared_ptr<image_heap_t> load() const;
private:
stbi_uc* data_{};
int32_t size_{};
};

View File

@@ -0,0 +1,38 @@
#include "bitmap_glyph_atlas.h"
#include "interface/image_interface.h"
glyph_atlas_result_t bitmap_glyph_atlas::create_glyph(const char_key_t& in_key) {
const auto& glyph_index = in_key.glyph_index;
const auto& font = in_key.font_face;
const auto& font_size = in_key.font_size;
glyph_atlas_result_t result{};
font->set_font_size(font_size);
// 获取字形的位图数据
auto bitmap = font->get_glyph_image(glyph_index);
if (!bitmap) {
result.reason = glyph_atlas_reason_t::glyph_not_found;
return result;
}
// 从图集分配空间添加2像素的padding防止纹理采样时的边缘混合问题
const auto& region = atlas_->allocate_region(bitmap->get_size(), { 2, 2 });
if (!region) {
result.reason = glyph_atlas_reason_t::atlas_full;
return result;
}
// 更新图集,将位图数据上传到纹理
atlas_->update_region(bitmap->data, bitmap->get_data_size(), region->rect);
// 缓存结果以便未来复用
add_to_cache(in_key, region);
result.reason = glyph_atlas_reason_t::success;
result.region = region;
return result;
}

View File

@@ -0,0 +1,33 @@
#pragma once
#include "font_atlas.h"
#include "interface/font_interface.h"
/**
* @class bitmap_glyph_atlas
* @brief 位图字形图集管理器
*
* 管理普通字形的位图纹理图集,使用单通道(R8)格式存储字形位图。
* 支持从字体中提取字形位图并将其添加到图集中。
*/
class bitmap_glyph_atlas : public atlas_base {
public:
/**
* @brief 初始化位图字形图集
*
* @param in_atlas_size 图集的像素尺寸
* @return 初始化是否成功
*/
bool initialize(const Eigen::Vector2i& in_atlas_size) override {
return initialize_atlas(in_atlas_size, SG_PIXELFORMAT_R8);
}
/**
* @brief 获取或创建字形的纹理区域
*
* 尝试从缓存中获取字形的纹理区域,如果不存在则从字体中提取位图并添加到图集中。
*
* @param in_key 字形的键,包含字形索引、字体和字体大小
* @return 操作结果,包含成功/失败状态和区域信息
*/
glyph_atlas_result_t create_glyph(const char_key_t& in_key) override;
};

View File

@@ -0,0 +1,39 @@
#include "color_emoji_atlas.h"
#include "interface/image_interface.h"
glyph_atlas_result_t color_emoji_atlas::create_glyph(const char_key_t& in_key) {
const auto& glyph_index = in_key.glyph_index;
const auto& font = in_key.font_face;
const auto& font_size = in_key.font_size;
glyph_atlas_result_t result{};
if (!font->supports_color_emoji())
return result;
font->set_font_size(font_size);
// 获取彩色表情位图
auto bitmap = font->get_emoji_image(glyph_index);
if (!bitmap) {
result.reason = glyph_atlas_reason_t::glyph_not_found;
return result;
}
// 从图集分配空间添加2像素的padding防止纹理采样时的边缘混合问题
const auto& region = atlas_->allocate_region(bitmap->get_size(), { 2, 2 });
if (!region) {
result.reason = glyph_atlas_reason_t::atlas_full;
return result;
}
// 更新图集,将位图数据上传到纹理
atlas_->update_region(bitmap->data, bitmap->get_data_size(), region->rect);
// 缓存结果以便未来复用
add_to_cache(in_key, region);
result.reason = glyph_atlas_reason_t::success;
result.region = region;
return result;
}

View File

@@ -0,0 +1,33 @@
#pragma once
#include "font_atlas.h"
#include "interface/font_interface.h"
/**
* @class color_emoji_atlas
* @brief 彩色表情符号图集管理器
*
* 管理彩色表情符号的纹理图集,支持从字体中提取彩色表情符号的位图,
* 并将其存储在RGBA格式的纹理图集中。
*/
class color_emoji_atlas : public atlas_base {
public:
/**
* @brief 初始化表情符号图集
*
* @param in_atlas_size 图集的像素尺寸
* @return 初始化是否成功
*/
bool initialize(const Eigen::Vector2i& in_atlas_size) override {
return initialize_atlas(in_atlas_size, SG_PIXELFORMAT_RGBA8);
}
/**
* @brief 获取或创建字形的纹理区域
*
* 尝试从缓存中获取字形的纹理区域,如果不存在则从字体中提取位图并添加到图集中。
*
* @param in_key 字形的键,包含字形索引、字体和字体大小
* @return 操作结果,包含成功/失败状态和区域信息
*/
glyph_atlas_result_t create_glyph(const char_key_t& in_key) override;
};

View File

@@ -0,0 +1,9 @@
#include "font_atlas.h"
#include "interface/font_interface.h"
char_key_t::char_key_t(uint32_t in_glyph_index, const font_face_ptr& in_font_face,
float in_font_size):
glyph_index(in_glyph_index),
font_face(in_font_face),
font_size(in_font_size) {}

View File

@@ -0,0 +1,168 @@
#pragma once
#include <memory>
#include <unordered_map>
#include "interface/font_interface.h"
#include "texture/atlas/texture2d_atlas.h"
class font_face_interface;
/**
* @enum glyph_atlas_reason_t
* @brief 图集操作结果原因枚举
*
* 用于标识字形图集操作的结果状态,便于调用者了解操作失败的具体原因。
*/
enum class glyph_atlas_reason_t {
success, ///< 操作成功完成
glyph_not_found, ///< 未找到请求的字形
atlas_full, ///< 图集空间已满,无法分配更多区域
unknown ///< 未知原因导致的失败
};
/**
* @struct glyph_atlas_result_t
* @brief 图集操作结果结构体
*
* 封装了字形图集操作的完整结果信息,包括操作状态和分配的区域。
*/
struct glyph_atlas_result_t {
glyph_atlas_reason_t reason = glyph_atlas_reason_t::unknown; ///< 操作结果原因
std::shared_ptr<atlas_region_t> region; ///< 分配的区域信息(仅当操作成功时有效)
};
struct char_key_t {
uint32_t glyph_index;
font_face_ptr font_face;
float font_size;
char_key_t(uint32_t in_glyph_index, const font_face_ptr& in_font_face, float in_font_size);
bool operator==(const char_key_t& other) const {
// Implement comparison logic. Might involve comparing font pointers
// or font identifiers, glyph_id, and font_size.
// Be careful comparing floats directly.
return glyph_index == other.glyph_index &&
font_face == other.font_face && // Simple pointer comparison, might need deeper check
std::abs(font_size - other.font_size) < 1e-6; // Epsilon comparison for float
}
};
template<>
struct std::hash<char_key_t> {
size_t operator()(const char_key_t& in_key) const {
constexpr std::hash<uint32_t> hash_fn;
size_t hash_key = hash_fn(in_key.glyph_index);
hash_key ^= reinterpret_cast<uint64_t>(in_key.font_face.get());
hash_key ^= std::hash<float>{}(in_key.font_size);
return hash_key;
}
};
/**
* @class atlas_base
* @brief 字形图集管理基类
*
* 提供字形图集管理的通用功能,包括图集初始化、区域分配和缓存管理。
* 派生类需要实现具体的字形渲染和图集更新逻辑。
*/
class atlas_base {
protected:
std::shared_ptr<texture2d_atlas> atlas_; ///< 底层纹理图集对象
std::unordered_map<char_key_t, std::shared_ptr<atlas_region_t>> cached_regions_; ///< 已缓存的区域映射表
/**
* @brief 初始化底层纹理图集
*
* @param in_atlas_size 图集的像素尺寸
* @param in_pixel_format 图集的像素格式
* @return 初始化是否成功
*/
bool initialize_atlas(const Eigen::Vector2i& in_atlas_size, sg_pixel_format in_pixel_format) {
atlas_ = std::make_shared<texture2d_atlas>(
in_atlas_size,
in_pixel_format,
allocation_strategy_t::bin_packing // 使用二叉树装箱算法策略
);
return atlas_ != nullptr;
}
/**
* @brief 将区域信息添加到缓存
*
* @param in_cache_key 缓存键
* @param in_region 要缓存的区域
*/
void add_to_cache(const char_key_t& in_cache_key, const std::shared_ptr<atlas_region_t>& in_region) {
cached_regions_[in_cache_key] = in_region;
}
public:
/**
* @brief 虚析构函数
*
* 确保正确释放派生类资源
*/
virtual ~atlas_base() = default;
/**
* @brief 初始化图集
*
* @param in_atlas_size 图集的像素尺寸
* @return 初始化是否成功
*/
virtual bool initialize(const Eigen::Vector2i& in_atlas_size) = 0;
/**
* @brief 获取图集底层纹理
*
* @return 图集纹理对象
*/
[[nodiscard]] auto get_texture() const {
return atlas_->get_texture();
}
/**
* @brief 获取已缓存区域的数量
*
* @return 缓存的区域数量
*/
[[nodiscard]] size_t get_cached_regions_count() const {
return cached_regions_.size();
}
/**
* @brief 清除所有缓存的区域
*/
void clear_cache() {
cached_regions_.clear();
}
/**
* @brief 从缓存中查找字形区域
*
* @param in_cache_key 缓存键
* @return 查找结果,包含成功/失败状态和区域信息
*/
glyph_atlas_result_t find_in_cache(const char_key_t& in_cache_key) {
glyph_atlas_result_t result{};
auto it = cached_regions_.find(in_cache_key);
if (it != cached_regions_.end()) {
result.reason = glyph_atlas_reason_t::success;
result.region = it->second;
}
return result;
}
/**
* @brief 获取或创建字形的纹理区域
*
* 尝试从缓存中获取字形的纹理区域,如果不存在则从字体中提取位图并添加到图集中。
*
* @param in_key 字形的键,包含字形索引、字体和字体大小
* @return 操作结果,包含成功/失败状态和区域信息
*/
virtual glyph_atlas_result_t create_glyph(const char_key_t& in_key) = 0;
};

View File

@@ -0,0 +1,116 @@
#include "font_atlas_manager.h"
/**
* @brief 在指定的图集列表中查找或创建字形的核心逻辑。
*
* @tparam AtlasType 图集对象的类型 (例如 glyph_atlas_t)。
* @param key 要查找或创建的字形的键。
* @param atlases 要搜索和/或添加到的图集列表 (通过引用传递)。
* @param current_atlas_index 当前活动图集的索引 (通过引用传递,可能会被更新)。
* @param create_new_atlas_func 一个函数对象,用于在需要时创建新的图集。
* @return 如果成功找到或创建了字形,则返回包含图集区域的 std::optional否则返回 std::nullopt。
*/
template <typename AtlasType>
std::shared_ptr<atlas_region_t> find_or_create_in_atlases(
const char_key_t& key,
std::vector<AtlasType>& atlases,
size_t& current_atlas_index,
const std::function<void(const Eigen::Vector2i&)>& create_new_atlas_func)
{
glyph_atlas_result_t result{};
result.reason = glyph_atlas_reason_t::unknown; // 默认为失败
// **1. 尝试在现有图集中查找**
for (auto& atlas: atlases) {
glyph_atlas_result_t find_result = atlas.find_in_cache(key);
if (find_result.reason == glyph_atlas_reason_t::success) {
return find_result.region; // 找到了!
}
}
// **2. 尝试在当前图集中创建**
if (!atlases.empty() && current_atlas_index < atlases.size()) {
result = atlases[current_atlas_index].create_glyph(key);
if (result.reason == glyph_atlas_reason_t::success) {
return result.region; // 在当前图集中创建成功!
}
}
else {
// 如果没有图集或当前索引无效,则模拟图集已满以触发创建流程
result.reason = glyph_atlas_reason_t::atlas_full;
}
// **3. 如果当前图集已满 (或不存在), 创建一个新图集并尝试创建**
if (result.reason == glyph_atlas_reason_t::atlas_full) {
const Eigen::Vector2i new_atlas_size = { 2048, 2048 }; // 示例大小
// 调用传入的函数来创建特定类型的新图集
create_new_atlas_func(new_atlas_size);
// 检查新图集是否真的被创建了
if (atlases.empty()) {
// 错误处理:图集创建失败?
// 可以添加日志记录
return nullptr; // 无法继续
}
// 更新当前图集索引为新创建的图集 (最后一个)
current_atlas_index = atlases.size() - 1;
// 尝试在新图集中创建字形 (直接访问最后一个元素)
result = atlases.back().create_glyph(key);
if (result.reason == glyph_atlas_reason_t::success) {
return result.region; // 在新图集中创建成功!
}
}
// **4. 如果所有尝试都失败,返回 nullopt**
return nullptr;
}
std::shared_ptr<atlas_region_t> font_atlas_manager::get_or_create_glyph(
const uint32_t in_glyph_id,
const font_face_ptr& in_font,
const float in_font_size,
const bool is_emoji) {
const char_key_t key(in_glyph_id, in_font, in_font_size);
if (is_emoji) {
// **处理表情符号**
// 使用 lambda 传递特定的新图集创建函数
auto create_new_atlas_lambda = [this](const Eigen::Vector2i& size) {
this->create_new_emoji_atlas(size);
};
return find_or_create_in_atlases(
key,
emoji_atlases_, // 表情图集列表
current_emoji_atlas_, // 当前表情图集索引
create_new_atlas_lambda // 创建新表情图集的函数
);
}
// **处理普通字形**
// 使用 lambda 传递特定的新图集创建函数
auto create_new_atlas_lambda = [this](const Eigen::Vector2i& size) {
this->create_new_glyph_atlas(size);
};
return find_or_create_in_atlases(
key,
glyph_atlases_, // 普通字形图集列表
current_glyph_atlas_, // 当前普通字形图集索引
create_new_atlas_lambda // 创建新普通字形图集的函数
);
}
bitmap_glyph_atlas& font_atlas_manager::create_new_glyph_atlas(const Eigen::Vector2i& atlas_size) {
auto& atlas = glyph_atlases_.emplace_back();
atlas.initialize(atlas_size);
return atlas;
}
color_emoji_atlas& font_atlas_manager::create_new_emoji_atlas(const Eigen::Vector2i& atlas_size) {
auto& atlas = emoji_atlases_.emplace_back();
atlas.initialize(atlas_size);
return atlas;
}

View File

@@ -0,0 +1,80 @@
#pragma once
#include <vector>
#include <optional>
#include "bitmap_glyph_atlas.h"
#include "color_emoji_atlas.h"
/**
* @class font_atlas_manager
* @brief 图集管理器 - 负责管理字形和表情符号的纹理图集
*
* 集中处理所有字形和表情符号的图集分配、创建和访问,
* 使字体管理系统与底层纹理管理解耦。
*/
class font_atlas_manager {
public:
/**
* @brief 默认构造函数
*/
font_atlas_manager() = default;
/**
* @brief 获取或创建字形在图集中的区域
*
* @param in_glyph_id 字形ID
* @param in_font 字体引用
* @param in_font_size 字体大小
* @param is_emoji 是否为表情符号
* @return 字形在图集中的区域失败返回nullopt
*/
std::shared_ptr<atlas_region_t> get_or_create_glyph(
uint32_t in_glyph_id,
const font_face_ptr& in_font,
float in_font_size,
bool is_emoji);
/**
* @brief 获取所有字形图集
* @return 字形图集列表的常量引用
*/
[[nodiscard]] const auto& get_glyph_atlases() const {
return glyph_atlases_;
}
/**
* @brief 获取所有表情图集
* @return 表情图集列表的常量引用
*/
[[nodiscard]] const auto& get_emoji_atlases() const {
return emoji_atlases_;
}
/**
* @brief 清除所有图集
*/
void clear() {
glyph_atlases_.clear();
emoji_atlases_.clear();
}
private:
std::vector<bitmap_glyph_atlas> glyph_atlases_; ///< 普通字形图集列表
std::vector<color_emoji_atlas> emoji_atlases_; ///< 表情符号图集列表
size_t current_glyph_atlas_ = 0; ///< 当前字形图集索引
size_t current_emoji_atlas_ = 0; ///< 当前表情图集索引
/**
* @brief 创建新的字形图集
* @param atlas_size 图集大小
* @return 新创建的图集引用
*/
bitmap_glyph_atlas& create_new_glyph_atlas(const Eigen::Vector2i& atlas_size = {1024, 1024});
/**
* @brief 创建新的表情图集
* @param atlas_size 图集大小
* @return 新创建的图集引用
*/
color_emoji_atlas& create_new_emoji_atlas(const Eigen::Vector2i& atlas_size = {1024, 1024});
};

View File

@@ -0,0 +1,88 @@
#pragma once
// emoji_detector.h - 表情符号检测
#include <cstdint>
#include <string>
#include <vector>
/**
* @class emoji_detector
* @brief 处理表情符号检测和序列识别
*/
class emoji_detector {
public:
/**
* @brief 检查码点是否为表情符号
* @param in_code_point Unicode码点
* @return 是否是表情符号
*/
static bool is_emoji(uint32_t in_code_point) {
// 常见表情符号范围检查
if ((in_code_point >= 0x1F300 && in_code_point <= 0x1F64F) || // 表情符号
(in_code_point >= 0x1F680 && in_code_point <= 0x1F6FF) || // 交通与地图
(in_code_point >= 0x1F700 && in_code_point <= 0x1F77F) || // 符号补充
(in_code_point >= 0x1F780 && in_code_point <= 0x1F7FF) || // 几何形状
(in_code_point >= 0x1F900 && in_code_point <= 0x1F9FF) || // 补充符号与象形文字
(in_code_point >= 0x2600 && in_code_point <= 0x26FF)) { // 杂项符号
return true;
}
return false;
}
/**
* @brief 检查码点是否为表情序列的组件
* @param in_code_point Unicode码点
* @return 是否是表情序列组件
*/
static bool is_emoji_sequence_component(uint32_t in_code_point) {
// 表情变体选择器
if (in_code_point == 0xFE0F || in_code_point == 0xFE0E) { return true; }
// 肤色修饰符
if (in_code_point >= 0x1F3FB && in_code_point <= 0x1F3FF) { return true; }
// 零宽连接符
if (in_code_point == 0x200D) { return true; }
return false;
}
/**
* @brief 提取表情序列
* @param in_text Unicode文本
* @param in_start_pos 开始位置
* @param in_out_length 输出序列长度
* @return 表情序列
*/
static std::vector<uint32_t> extract_emoji_sequence(const std::u32string& in_text, size_t in_start_pos,
size_t& in_out_length) {
std::vector<uint32_t> sequence;
if (in_start_pos >= in_text.length() || !is_emoji(in_text[in_start_pos])) {
in_out_length = 0;
return sequence;
}
// 添加基础表情符号
sequence.push_back(in_text[in_start_pos]);
in_out_length = 1;
// 寻找完整序列
size_t pos = in_start_pos + 1;
while (pos < in_text.length()) {
const uint32_t cp = in_text[pos];
// 检查是否是序列组件或跟随零宽连接符的表情
if (is_emoji_sequence_component(cp) || (pos > 0 && in_text[pos - 1] == 0x200D && is_emoji(cp))) {
sequence.push_back(cp);
in_out_length++;
pos++;
}
else { break; }
}
return sequence;
}
};

View File

@@ -0,0 +1,244 @@
#include "font_layout.h"
#include "emoji_detector.h"
#include "font_system.h"
#include "interface/font_interface.h"
#include "misc/log_util.h"
// ==================== text_layout_t::line_t 成员函数实现 ====================
/**
* @brief 向当前行添加一个字形
*
* 该函数负责:
* 1. 查找能够渲染指定字符的字体
* 2. 获取字形的度量信息(大小、基线偏移等)
* 3. 计算字形间距kerning
* 4. 设置字形在行内的位置
* 5. 更新行的宽度和高度
*
* @param in_primary_font 主字体
* @param codepoint 要添加的Unicode字符码点
* @param font_size 字体大小(像素)
*/
void text_layout_t::line_t::add_glyph(const font_face_ptr& in_primary_font,
char32_t codepoint,
float font_size) {
auto& font_mgr = font_manager::instance();
// 获取当前字符的字形索引和对应字体
uint32_t prev_glyph_index = 0;
uint32_t glyph_index = 0;
auto using_font = font_mgr.get_font_for_code_point(in_primary_font, codepoint, &glyph_index);
// 处理字形间距kerning
if (!glyphs.empty()) {
const auto prev_char = glyphs.back().codepoint;
const auto prev_using_font = font_mgr.get_font_for_code_point(in_primary_font, prev_char, &prev_glyph_index);
if (prev_using_font != using_font)
prev_glyph_index = 0; // 如果字体不一致无法计算kerning重置为0
}
// 检查是否找到可用字体
if (!using_font) {
log_warn("无法找到用于渲染 U+{:04X} 的字体", (uint32_t)codepoint);
return;
}
// 设置字体大小并创建新的字形位置信息
using_font->set_font_size(font_size);
auto& g = glyphs.emplace_back();
g.codepoint = codepoint;
// 获取字体度量信息并更新行高
const auto& metrics = using_font->get_metrics();
line_height = std::max(line_height, metrics.line_height);
// 检测是否为表情符号并获取或创建字形纹理
g.is_emoji = emoji_detector::is_emoji(codepoint);
g.region = font_mgr.get_or_create_glyph_by_index(glyph_index, using_font,
in_primary_font->get_font_size(), g.is_emoji);
// 获取字形的形状信息
auto g_metrics = using_font->shape_glyph(glyph_index);
// 设置字形尺寸(空格特殊处理)
if (g.codepoint == ' ') {
g.size.x() = g_metrics.advance.x(); // 空格使用advance宽度
g.size.y() = line_height; // 空格高度使用行高
} else {
g.size.x() = g_metrics.rect.size().x();
g.size.y() = g_metrics.rect.size().y();
}
// 计算字形间距
float kerning = 0.0f;
if (prev_glyph_index != 0) {
kerning = using_font->get_kerning(prev_glyph_index, glyph_index);
}
// 计算字形位置
const float size_y_diff = g_metrics.rect.size().y() - static_cast<float>(g.region->rect.size().y());
const float baseline_y = position_y + line_height + metrics.descent;
// 设置字形的水平和垂直位置
g.position.x() = line_width + kerning + g_metrics.offset.x();
g.position.y() = baseline_y + g_metrics.offset.y() + size_y_diff;
// 更新行宽
line_width += kerning + g_metrics.advance.x();
}
/**
* @brief 判断添加新字符后是否需要换行
*
* 预先计算如果添加指定字符,当前行的宽度是否会超过最大宽度限制。
* 用于自动换行功能。
*
* @param in_primary_font 主字体
* @param max_width 最大宽度限制(像素)
* @param codepoint 要添加的字符码点
* @return true 如果添加该字符后需要换行false 否则
*/
bool text_layout_t::line_t::should_next_line(const font_face_ptr& in_primary_font,
float max_width,
char32_t codepoint) const {
// 无宽度限制或空行不需要换行
if (max_width <= 0 || glyphs.empty()) {
return false;
}
auto& font_mgr = font_manager::instance();
// 查找能渲染该字符的字体
uint32_t glyph_index = 0;
auto using_font = font_mgr.get_font_for_code_point(in_primary_font, codepoint, &glyph_index);
if (!using_font) {
log_warn("无法找到用于渲染 U+{:04X} 的字体", (uint32_t)codepoint);
return false; // 如果没有找到字体,不能换行
}
// 获取字形度量信息
auto g_metrics = using_font->shape_glyph(glyph_index);
// 计算添加该字符后的行宽
if (codepoint == ' ') {
// 空格的宽度直接使用advance宽度
return line_width + g_metrics.advance.x() > max_width;
}
// 其他字符使用实际渲染宽度
return line_width + g_metrics.rect.size().x() > max_width;
}
// ==================== text_layout_t 成员函数实现 ====================
/**
* @brief 将Unicode字符串添加到文本布局中
*
* 该函数负责:
* 1. 处理换行符
* 2. 自动换行(当超过最大宽度时)
* 3. 将字符添加到相应的行
* 4. 更新布局的总尺寸
*
* @param str 要添加的Unicode字符串
* @throws std::runtime_error 如果没有可用的主字体
*/
void text_layout_t::push_str(const std::u32string& str) {
auto& font_mgr = font_manager::instance();
// 获取主字体(优先使用覆盖字体)
const auto& primary_font = override_primary_font ? override_primary_font : font_mgr.get_primary_font();
if (!primary_font) {
throw std::runtime_error("No primary font available for text layout.");
}
// 从最后一行开始添加
auto* current_line = &get_last_line();
// 换行的辅助函数
auto end_line = [&]() {
current_line = &next_line();
};
// 遍历字符串中的每个字符
for (const auto c : str) {
// 处理换行符
if (c == U'\n') {
end_line();
continue;
}
// 检查是否需要自动换行
if (current_line->should_next_line(primary_font, max_width, c)) {
end_line();
}
// 将字符添加到当前行
current_line->add_glyph(primary_font, c, font_size);
}
// 如果最后一行没有内容,设置其高度为默认空行高度
if (current_line->empty()) {
current_line->line_height = get_empty_line_height();
}
// 更新布局的总宽度(取所有行中的最大宽度)
for (const auto& line : lines) {
total_size.x() = std::max(total_size.x(), line.line_width);
}
// 更新布局的总高度
total_size.y() = get_last_line().position_y + get_last_line().get_line_height();
}
// ==================== 属性设置函数 ====================
/**
* @brief 设置字体大小
*
* 如果新的字体大小与当前大小相同,则不进行任何操作。
* 注意:更改字体大小后需要重新布局文本才能生效。
*
* @param size 新的字体大小(像素)
*/
void text_layout_t::set_font_size(float size) {
if (font_size == size) {
return; // 如果字体大小没有变化,直接返回
}
font_size = size;
}
/**
* @brief 设置主字体
*
* 设置一个覆盖的主字体,将优先于系统默认主字体使用。
*
* @param font 新的主字体可以为nullptr表示使用系统默认
*/
void text_layout_t::set_primary_font(const font_face_ptr& font) {
override_primary_font = font;
}
// ==================== 属性获取函数 ====================
/**
* @brief 获取空行的高度
*
* 空行高度基于当前字体的行高度量,不包含行间距。
* 主要用于处理空行或只有换行符的行。
*
* @return 空行高度像素如果没有可用字体则返回0
*/
float text_layout_t::get_empty_line_height() const {
// 获取当前使用的字体
const auto font = override_primary_font ? override_primary_font : font_manager::instance().get_primary_font();
if (!font) {
return 0.0f; // 如果没有主字体返回0
}
// 设置字体大小并获取行高
font->set_font_size(font_size);
return font->get_metrics().line_height; // 空行高度不需要line_spacing
}

View File

@@ -0,0 +1,361 @@
#pragma once
#include <Eigen/Eigen>
#include "interface/font_interface.h"
#include "texture/atlas/texture_atlas_types.h"
namespace text_layout_impl {
/**
* @brief 字形信息结构体
* 存储单个字形的渲染信息,包括字体、索引、度量和纹理区域
*/
struct glyph_info_t {
bool is_emoji = false; ///< 是否为表情符号
uint32_t glyph_index = 0; ///< 字形在字体中的索引
font_face_ptr font; ///< 字形所属的字体
std::shared_ptr<atlas_region_t> region; ///< 字形在纹理图集中的区域
glyph_shaped_t metrics; ///< 字形的度量信息(宽高、基线等)
};
}
/**
* @brief 文本布局类
* 负责文本的排版和布局,支持多行文本、自动换行等功能
*/
struct text_layout_t {
/**
* @brief 字形位置信息
* 存储单个字形在布局中的位置和渲染信息
*/
struct glyph_position_t {
bool is_emoji; ///< 是否为表情符号
char32_t codepoint; ///< Unicode码点
Eigen::Vector2f position; ///< 字形在屏幕上的位置(左上角)
Eigen::Vector2f size; ///< 字形实际占用的尺寸
std::shared_ptr<atlas_region_t> region; ///< 字形在纹理图集中的区域
};
/**
* @brief 文本行信息
* 管理单行文本的布局信息和字形列表
*/
struct line_t {
float line_height = 0.0f; ///< 行高(像素)
float position_y = 0.0f; ///< 行的Y轴位置基线位置
float line_width = 0.0f; ///< 行的总宽度(像素)
std::vector<glyph_position_t> glyphs; ///< 该行包含的所有字形
// ==================== 行属性获取函数 ====================
/**
* @brief 获取行高
* @return 当前行的高度(像素)
*/
[[nodiscard]] auto get_line_height() const {
return line_height;
}
/**
* @brief 获取行宽
* @return 当前行的宽度(像素)
*/
[[nodiscard]] auto get_line_width() const {
return line_width;
}
/**
* @brief 获取行的尺寸
* @return 包含宽度和高度的二维向量
*/
[[nodiscard]] auto get_line_size() const {
return Eigen::Vector2f(line_width, get_line_height());
}
// ==================== 字形管理函数 ====================
/**
* @brief 向当前行添加字形
* @param in_primary_font 使用的字体
* @param codepoint 要添加的字符的Unicode码点
* @param font_size 字体大小
*/
void add_glyph(const font_face_ptr& in_primary_font, char32_t codepoint, float font_size);
/**
* @brief 判断是否应该换行
* @param in_primary_font 使用的字体
* @param max_width 最大宽度限制
* @param codepoint 下一个要添加的字符
* @return true 如果需要换行false 否则
*/
bool should_next_line(const font_face_ptr& in_primary_font, float max_width, char32_t codepoint) const;
// ==================== 状态查询函数 ====================
/**
* @brief 检查行是否为空
* @return true 如果行内没有字形false 否则
*/
[[nodiscard]] bool empty() const {
return glyphs.empty();
}
/**
* @brief 检查行是否有内容
* @return true 如果行内有字形false 否则
*/
[[nodiscard]] bool has_content() const {
return !glyphs.empty();
}
/**
* @brief 获取行内字形数量
* @return 字形数量
*/
[[nodiscard]] size_t get_glyph_count() const {
return glyphs.size();
}
/**
* @brief 获取指定索引处字形的位置
* @param index 字形索引,如果超出范围则返回行末位置
* @param use_line_pos true使用行的Y位置false使用字形自身的Y位置
* @return 字形位置的二维向量
*/
[[nodiscard]] auto get_glyph_pos(size_t index, bool use_line_pos = true) const -> Eigen::Vector2f {
if (glyphs.empty()) {
return { 0, position_y };
}
const bool at_end = index >= glyphs.size();
const auto& glyph = glyphs[at_end ? glyphs.size() - 1 : index];
float x = at_end ? glyph.position.x() + glyph.size.x() : glyph.position.x();
float y = use_line_pos ? position_y : glyph.position.y();
return { x, y };
}
};
// ==================== 构造函数 ====================
/**
* @brief 默认构造函数
* 初始化文本布局并创建第一行
*/
text_layout_t() {
lines.emplace_back(); // 初始化第一行
}
// ==================== 行访问函数 ====================
/**
* @brief 获取指定索引的行(可修改版本)
* @param index 行索引
* @return 指向行的指针如果索引无效则返回nullptr
*/
line_t* get_line(size_t index) {
if (index < lines.size()) {
return &lines[index];
}
return nullptr;
}
/**
* @brief 获取指定索引的行(只读版本)
* @param index 行索引
* @return 指向行的常量指针如果索引无效则返回nullptr
*/
[[nodiscard]] const line_t* get_line(size_t index) const {
if (index < lines.size()) {
return &lines[index];
}
return nullptr;
}
/**
* @brief 获取最后一行(可修改版本)
* @return 最后一行的引用
*/
auto& get_last_line() {
return lines.back();
}
/**
* @brief 获取最后一行(只读版本)
* @return 最后一行的常量引用
*/
[[nodiscard]] const auto& get_last_line() const {
return lines.back();
}
// ==================== 布局管理函数 ====================
/**
* @brief 创建新行
* 根据当前行高和行间距计算新行位置
* @return 新创建行的引用
*/
auto& next_line() {
const auto& last_line = get_last_line();
const auto new_y = last_line.position_y + last_line.get_line_height() * line_spacing;
auto& new_line = lines.emplace_back();
new_line.position_y = new_y;
new_line.line_height = get_empty_line_height();
return new_line;
}
/**
* @brief 添加Unicode字符串到布局
* @param str 要添加的Unicode字符串
*/
void push_str(const std::u32string& str);
/**
* @brief 清空布局
* 移除所有行并重新初始化第一行
*/
void clear() {
lines.clear();
lines.emplace_back(); // 重新初始化第一行
total_size.setZero();
}
// ==================== 字形查询函数 ====================
/**
* @brief 获取指定行和字形索引的位置
* @param line_index 行索引(会被限制在有效范围内)
* @param glyph_index 字形索引
* @param use_line_pos true使用行的Y位置false使用字形自身的Y位置
* @return 字形位置的二维向量
*/
[[nodiscard]] auto get_glyph_pos(size_t line_index, size_t glyph_index, bool use_line_pos = true) const {
line_index = std::clamp(line_index, 0zu, lines.size() - 1);
const auto& line = lines[line_index];
return line.get_glyph_pos(glyph_index, use_line_pos);
}
/**
* @brief 获取指定位置的字形信息
* @param line_index 行索引(会被限制在有效范围内)
* @param glyph_index 字形索引
* @return 指向字形信息的常量指针如果索引无效则返回nullptr
*/
[[nodiscard]] auto get_glyph(size_t line_index, size_t glyph_index) const -> const glyph_position_t* {
line_index = std::clamp(line_index, 0zu, lines.size() - 1);
const auto& line = lines[line_index];
if (glyph_index < line.get_glyph_count()) {
return &line.glyphs[glyph_index];
}
return nullptr;
}
// ==================== 属性设置函数 ====================
/**
* @brief 设置字体大小
* @param size 新的字体大小(像素)
*/
void set_font_size(float size);
/**
* @brief 设置主字体
* @param font 新的主字体
*/
void set_primary_font(const font_face_ptr& font);
/**
* @brief 设置行间距
* @param spacing 行间距倍数例如1.2表示1.2倍行高)
*/
void set_line_spacing(float spacing) {
line_spacing = spacing;
}
/**
* @brief 设置最大宽度
* @param width 最大宽度(像素),用于自动换行
*/
void set_max_width(float width) {
max_width = width;
}
// ==================== 属性获取函数 ====================
/**
* @brief 获取行间距
* @return 当前行间距倍数
*/
[[nodiscard]] float get_line_spacing() const {
return line_spacing;
}
/**
* @brief 获取字体大小
* @return 当前字体大小(像素)
*/
[[nodiscard]] float get_font_size() const {
return font_size;
}
/**
* @brief 获取空行高度
* @return 空行的默认高度(像素)
*/
float get_empty_line_height() const;
// ==================== 状态查询函数 ====================
/**
* @brief 获取行数
* @return 当前布局中的行数
*/
[[nodiscard]] auto size() const {
return lines.size();
}
/**
* @brief 检查布局是否为空
* @return true 如果没有行或只有一个空行false 否则
*/
[[nodiscard]] auto empty() const {
return lines.size() == 0 || (lines.size() == 1 && lines[0].empty());
}
// ==================== 迭代器支持 ====================
/**
* @brief 获取行列表的起始迭代器
* @return 指向第一行的迭代器
*/
auto begin() { return lines.begin(); }
/**
* @brief 获取行列表的起始常量迭代器
* @return 指向第一行的常量迭代器
*/
auto begin() const { return lines.begin(); }
/**
* @brief 获取行列表的结束迭代器
* @return 指向最后一行之后的迭代器
*/
auto end() { return lines.end(); }
/**
* @brief 获取行列表的结束常量迭代器
* @return 指向最后一行之后的常量迭代器
*/
auto end() const { return lines.end(); }
// ==================== 公共成员变量 ====================
Eigen::Vector2f total_size{ 0, 0 }; ///< 文本总尺寸(宽度和高度)
private:
// ==================== 私有成员变量 ====================
float max_width = 0.f; ///< 最大宽度限制0表示无限制
float line_spacing = 1.2f; ///< 行间距倍数
float font_size = 24.f; ///< 字体大小(像素)
std::vector<line_t> lines; ///< 文本行列表
font_face_ptr override_primary_font; ///< 覆盖的主字体
};

View File

@@ -0,0 +1,147 @@
#include "font_system.h"
#include <ranges>
#include "emoji_detector.h"
void font_manager::destroy() {
fonts_.clear();
emoji_font_ids_.clear();
atlas_manager_.clear();
primary_font_id_ = -1;
next_font_id_ = 0;
destroy_font_system();
}
void font_manager::load_default_font() {
#if MIRAGE_PLATFORM_WINDOWS
add_font(L"C:/Windows/Fonts/msyh.ttc");
add_font(L"C:/Windows/Fonts/seguiemj.ttf");
#else
static_assert(false, "暂时不支持当前平台的默认字体加载");
#endif
}
int font_manager::add_font(const std::filesystem::path& in_font_path, const std::string& in_font_type) {
const auto font = create_font_face(in_font_path);
if (!font) {
return -1;
}
const auto font_id = next_font_id_++;
fonts_[font_id] = font;
// 如果支持彩色表情或明确指定为表情字体,添加到表情字体列表
if (font->supports_color_emoji() || in_font_type == "emoji") {
emoji_font_ids_.push_back(font);
}
// 如果是第一个添加的字体,自动设为主字体
if (primary_font_id_ == -1) {
primary_font_id_ = font_id;
}
return font_id;
}
font_face_ptr font_manager::get_font_for_code_point(
const font_face_ptr& in_custom_primary_font, const uint32_t in_code_point,
uint32_t* out_glyph_index) {
// **辅助 Lambda检查字体并更新字形索引**
auto check_font = [&](const font_face_ptr& font) -> font_face_ptr {
if (!font)
return nullptr; // 跳过空指针
// **调用字体接口查找字形索引 (Assuming 0 means 'not found')**
const uint32_t glyph_id = font->find_glyph_index(in_code_point);
if (glyph_id > 0) {
// **如果找到字形,设置输出参数并返回字体**
if (out_glyph_index) {
*out_glyph_index = glyph_id;
}
return font; // 找到字形
}
return nullptr; // 在此字体中未找到字形
};
// **1. 尝试自定义主字体 (如果提供了)**
if (auto found_font = check_font(in_custom_primary_font)) {
return found_font; // 在主字体中找到,直接返回
}
// 确定字符类型
const bool is_emoji = emoji_detector::is_emoji(in_code_point);
// **2. 定义字体过滤器**
// 此过滤器排除了空指针字体和已检查过的主字体。
// 注意: 更复杂的过滤器可以基于 is_emoji 和字体元数据 (例如 is_emoji_font())
// 来优先选择特定类型的字体(例如,为表情符号优先选择表情字体)。
auto font_filter = [&](const font_face_ptr& font) {
// **确保字体有效且不是我们已经检查过的主字体**
if (is_emoji) {
return std::ranges::contains(emoji_font_ids_, font);
}
return font != nullptr && font != in_custom_primary_font;
};
// **3. 遍历字体管理器中所有其他适用的字体**
for (const auto& font : std::views::values(fonts_) | std::views::filter(font_filter)) {
// **检查当前遍历到的字体**
if (auto found_font = check_font(font)) {
return found_font; // 在其他受管字体中找到,返回
}
}
// **4. 最终回退逻辑**
// 如果代码执行到这里,表示在主字体(如果提供)和所有其他过滤后的字体中都未找到该字形。
// (可选) 在这里可以尝试一个全局默认回退字体:
// if (m_global_fallback_font) {
// if (auto found_font = check_font(m_global_fallback_font)) {
// return found_font;
// }
// }
// **未能找到任何包含该字形的字体**
// **将输出字形索引设置为 0 (表示未找到)**
if (out_glyph_index) {
*out_glyph_index = 0;
}
// **返回 nullptr表示没有找到合适的字体**
return nullptr;
}
/**
* @brief 获取或创建字形在图集中的区域
*/
std::shared_ptr<atlas_region_t> font_manager::get_or_create_glyph_by_index(
uint32_t in_glyph_id,
const font_face_ptr& in_font,
float in_font_size,
bool is_emoji) {
// 验证字体有效性
if (!in_font) {
return nullptr;
}
return atlas_manager_.get_or_create_glyph(in_glyph_id, in_font, in_font_size, is_emoji);
}
font_manager::font_manager() {
// 初始化字体系统
if (!init_font_system()) {
throw std::runtime_error("无法初始化字体系统");
}
}
std::map<font_face_ptr, font_v_metrics_t> font_manager::cache_font_metrics(float font_size) {
std::map<font_face_ptr, font_v_metrics_t> out;
for (const auto& font: fonts_ | std::views::values) {
if (!font)
continue;
font->set_font_size(font_size);
out[font] = font->get_metrics();
}
return out;
}

View File

@@ -0,0 +1,156 @@
#pragma once
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <filesystem>
#include "atlas/font_atlas.h"
#include "atlas/font_atlas_manager.h"
#include "font_type.h"
#include "interface/font_interface.h"
/**
* @class font_manager
* @brief 字体管理系统
*
* 管理多种字体、提供字体回退机制,并处理文本布局和字形图集管理。
* 实现为单例模式,确保全局只有一个字体管理实例。
*/
class font_manager {
public:
/**
* @brief 获取单例实例
*
* @return font_manager& 字体管理器的单例引用
*/
static font_manager& instance() {
static font_manager instance;
return instance;
}
/**
* @brief 销毁所有资源
*
* 清理所有字体和图集资源,重置字体管理器状态。
*/
void destroy();
/**
* @brief 加载操作系统默认字体
*/
void load_default_font();
/**
* @brief 添加字体
*
* 从文件加载字体并添加到字体管理系统中。
* 如果字体支持彩色表情符号或指定为表情符号字体,将自动添加到表情符号字体列表。
*
* @param in_font_path 字体文件路径
* @param in_font_type 字体类型(regular, bold, italic, emoji等)
* @return 添加成功返回字体ID失败返回-1
*/
int add_font(const std::filesystem::path& in_font_path, const std::string& in_font_type = "regular");
/**
* @brief 设置主字体
*
* @param in_font_id 要设为主字体的字体ID
* @return 设置是否成功
*/
bool set_primary_font(int in_font_id) {
if (fonts_.contains(in_font_id)) {
primary_font_id_ = in_font_id;
return true;
}
return false;
}
/**
* @brief 获取指定码点的最佳字体
*
* 实现字体回退机制,按以下顺序尝试查找支持指定码点的字体:
* 1. 主字体
* 2. 如果是表情符号,尝试表情符号字体
* 3. 所有其他字体
* 4. 回退到主字体
*
* @param in_code_point Unicode码点
* @return 最适合渲染该码点的字体
*/
font_face_ptr get_font_for_code_point(uint32_t in_code_point,
uint32_t* out_glyph_index = nullptr) {
auto primary = get_primary_font();
return get_font_for_code_point(primary, in_code_point, out_glyph_index);
}
font_face_ptr get_font_for_code_point(
const font_face_ptr& in_custom_primary_font, uint32_t in_code_point,
uint32_t* out_glyph_index = nullptr);
/**
* @brief 获取主字体
* @return 主字体
*/
font_face_ptr get_primary_font() {
if (primary_font_id_ != -1 && fonts_.contains(primary_font_id_)) {
return fonts_[primary_font_id_];
}
return nullptr;
}
/**
* @brief 检查是否有彩色表情字体
* @return 是否支持彩色表情
*/
[[nodiscard]] bool has_color_emoji_font() const {
return !emoji_font_ids_.empty();
}
/**
* @brief 获取或创建字形
*
* 从图集中获取字形纹理区域,如果不存在则创建。
*
* @param in_glyph_id 字形ID
* @param in_font 字体
* @param in_font_size 字体大小
* @param is_emoji 是否为表情符号
* @return 字形在图集中的区域
*/
std::shared_ptr<atlas_region_t> get_or_create_glyph_by_index(
uint32_t in_glyph_id,
const font_face_ptr& in_font,
float in_font_size,
bool is_emoji);
/**
* @brief 获取所有字形图集
* @return 字形图集列表的常量引用
*/
[[nodiscard]] const auto& get_glyph_atlases() const {
return atlas_manager_.get_glyph_atlases();
}
/**
* @brief 获取所有表情图集
* @return 表情图集列表的常量引用
*/
[[nodiscard]] const auto& get_emoji_atlases() const {
return atlas_manager_.get_emoji_atlases();
}
std::map<font_face_ptr, font_v_metrics_t> cache_font_metrics(float font_size);
private:
font_manager();
std::unordered_map<int32_t, font_face_ptr> fonts_; ///< 字体ID到字体对象的映射
font_atlas_manager atlas_manager_; ///< 图集管理器
std::vector<font_face_ptr> emoji_font_ids_; ///< 表情符号字体ID列表
int32_t primary_font_id_ = -1; ///< 主字体ID
int32_t next_font_id_ = 0; ///< 下一个可用的字体ID
};

View File

@@ -0,0 +1,49 @@
#pragma once
#include <vector>
#include "geometry/rect.h"
#include "interface/font_interface.h"
#include "misc/mirage_type.h"
#include "texture/atlas/texture_atlas_types.h"
class font_face_interface;
class texture2d;
/**
* @enum horizontal_text_alignment_t
* @brief 文本水平对齐方式
*/
enum class horizontal_text_alignment_t {
left, ///< 文本左对齐
center, ///< 文本居中对齐
right ///< 文本右对齐
};
/**
* @enum vertical_text_alignment_t
* @brief 文本垂直对齐方式
*/
enum class vertical_text_alignment_t {
top, ///< 文本顶部对齐
center, ///< 文本居中对齐
bottom ///< 文本底部对齐
};
/**
* @struct text_style_t
* @brief 文本样式
*/
struct text_style_t {
linear_color text_color = { 1, 1, 1, 1 }; ///< 文本颜色
float font_size = 16.0f; ///< 字体大小
float emoji_size = 24.0f; ///< 表情大小
float smoothing = 0.1f; ///< MTSDF平滑值
float character_spacing = 0.0f; ///< 字符间距
float line_spacing = 0.0f; ///< 行间距
float paragraph_spacing = 8.0f; ///< 段落间距
horizontal_text_alignment_t h_align = horizontal_text_alignment_t::left; ///< 水平对齐
vertical_text_alignment_t v_align = vertical_text_alignment_t::top; ///< 垂直对齐
flow_direction_t text_direction = flow_direction_t::left_to_right; ///< 文本方向
bool word_wrap = true; ///< 自动换行
margin_t text_margin = { 0, 0, 0, 0 }; ///< 文本边距
};

View File

@@ -0,0 +1,71 @@
#pragma once
#include <cstdint>
namespace font {
/**
* @brief 读取无符号8位整数
* @param p 数据指针
* @return 8位无符号整数
*/
static uint8_t read_u8(const uint8_t* p) { return p[0]; }
/**
* @brief 读取有符号8位整数
* @param p 数据指针
* @return 8位有符号整数
*/
static int8_t read_i8(const uint8_t* p) { return static_cast<int8_t>(p[0]); }
/**
* @brief 读取无符号短整型2字节大端序
* @param p 数据指针
* @return 16位无符号整数
*/
static uint16_t read_u16(const uint8_t* p) { return static_cast<uint16_t>(p[0] << 8 | p[1]); }
/**
* @brief 读取有符号短整型2字节大端序
* @param p 数据指针
* @return 16位有符号整数
*/
static int16_t read_i16(const uint8_t* p) { return static_cast<int16_t>(p[0] << 8 | p[1]); }
/**
* @brief 读取无符号整型4字节大端序
* @param p 数据指针
* @return 32位无符号整数
*/
static uint32_t read_u32(const uint8_t* p) {
return static_cast<uint32_t>(p[0]) << 24 | static_cast<uint32_t>(p[1]) << 16 | static_cast<uint32_t>(p[2]) << 8
| static_cast<uint32_t>(p[3]);
}
/**
* @brief 检查标签是否匹配
* @param tag_data 标签数据
* @param tag 标签字符串
* @return 是否匹配
*/
static bool check_tag(const uint8_t* tag_data, const char* tag) {
return tag_data[0] == tag[0] && tag_data[1] == tag[1] && tag_data[2] == tag[2] && tag_data[3] == tag[3];
}
/**
* @brief 在字体数据中查找特定表的偏移量
* @param data 字体数据
* @param font_start 字体起始偏移
* @param tag 表标签
* @return 表偏移量不存在则返回0
*/
static uint32_t find_table_offset(const uint8_t* data, uint32_t font_start, const char* tag) {
const auto num_tables = read_u16(data + font_start + 4);
const uint32_t table_dir = font_start + 12;
for (int i = 0; i < num_tables; ++i) {
const uint32_t loc = table_dir + 16 * i;
if (check_tag(data + loc, tag)) { return read_u32(data + loc + 8); }
}
return 0; // 表不存在
}
}

View File

@@ -0,0 +1,42 @@
#include "ime.h"
#include "misc/log_util.h"
void ime::process_char(char32_t c) {
for (const auto& processor: ime_processors_) {
if (const auto p = processor.lock()) {
p->process_ime_char(c);
}
}
}
void ime::process_text(const std::u32string& text) {
for (const auto& processor: ime_processors_) {
if (const auto p = processor.lock()) {
p->set_ime_composition(text);
}
}
}
void ime::process_composition_clear() {
for (const auto& processor: ime_processors_) {
if (const auto p = processor.lock()) {
p->end_ime_composition();
}
}
}
void ime::set_cursor_pos(const Eigen::Vector2i& in_pos) {
cursor_pos_ = in_pos;
}
void ime::register_processor(const std::weak_ptr<ime_processor>& processor) {
ime_processors_.push_back(processor);
}
void ime::unregister_processor(std::weak_ptr<ime_processor> processor) {
auto pred = [&processor](const std::weak_ptr<ime_processor>& p) {
return p.lock() == processor.lock();
};
std::erase_if(ime_processors_, pred);
}

View File

@@ -0,0 +1,40 @@
#pragma once
#include <algorithm>
#include <Eigen/Eigen>
#include "misc/delegates.h"
class platform_window;
class ime_processor {
public:
virtual ~ime_processor() = default;
virtual void process_ime_char(char32_t c) = 0;
virtual void set_ime_composition(const std::u32string& text) = 0;
virtual void end_ime_composition() = 0;
};
class ime {
public:
static auto& get() {
static ime instance;
return instance;
}
void process_char(char32_t c);
void process_text(const std::u32string& text);
void process_composition_clear();
void set_cursor_pos(const Eigen::Vector2i& in_pos);
bool update_cursor_pos(void* in_window_native);
[[nodiscard]] auto has_processors() const -> bool { return !ime_processors_.empty(); }
void register_processor(const std::weak_ptr<ime_processor>& processor);
void unregister_processor(std::weak_ptr<ime_processor> processor);
private:
ime() = default;
std::vector<std::weak_ptr<ime_processor>> ime_processors_;
Eigen::Vector2i cursor_pos_;
};

View File

@@ -0,0 +1,30 @@
#include "ime/ime.h"
#include <windows.h>
#include "misc/log_util.h"
#include "misc/scope_exit.h"
#include "platform_window/platform_window.h"
bool ime::update_cursor_pos(void* in_window_native) {
const auto hwnd = (HWND)in_window_native;
HIMC himc = ImmGetContext(hwnd);
if (!himc) {
log_error("IME: failed to get IME context");
return false;
}
ON_SCOPE_EXIT{
ImmReleaseContext(hwnd, himc);
};
// 将客户区坐标转换为屏幕坐标
POINT point = { cursor_pos_.x(), cursor_pos_.y() };
// 设置组合窗口位置(跟随光标)
COMPOSITIONFORM cf;
cf.dwStyle = CFS_POINT;
cf.ptCurrentPos = point;
ImmSetCompositionWindow(himc, &cf);
return true;
}

View File

@@ -0,0 +1,10 @@
#include "font_interface.h"
bool font_face_interface::load(const std::filesystem::path& in_path) {
font_data_ = mapped_file::create();
if (!font_data_->map_file(in_path)) {
return false;
}
return on_load();
}

View File

@@ -0,0 +1,123 @@
#pragma once
#include <memory>
#include <filesystem>
#include <string>
#include "geometry/rect.h"
#include "misc/lru_cache.h"
#include "misc/mapped_file/mapped_file.h"
struct image_heap_t;
/**
* @struct font_v_metrics_t
* @brief 字体垂直度量
*/
struct font_v_metrics_t {
float ascent; // 上升高度
float descent; // 下降高度
float line_height; // 行高
[[nodiscard]] float line_gap() const { return line_height - (ascent - descent); }
[[nodiscard]] float baseline() const { return line_height + descent; }
};
struct glyph_shaped_t {
int32_t glyph_index; // Unicode码点
Eigen::Vector2f offset; // 相对位置偏移
Eigen::Vector2f advance; // 前进值
Eigen::Vector2f hori_bearing; // 水平基线
Eigen::Vector2f vert_bearing; // 垂直基线
rect_t<> rect; // 字形矩形区域
};
struct glyph_key {
uint32_t glyph_id;
float font_size;
bool operator==(const glyph_key& other) const { return glyph_id == other.glyph_id && font_size == other.font_size; }
};
// 为 glyph_key 提供 std::hash 特化
template<>
struct std::hash<glyph_key> {
std::size_t operator()(const glyph_key& key) const noexcept {
// 组合哈希值的常用方法
const std::size_t h1 = std::hash<uint32_t>{}(key.glyph_id);
const std::size_t h2 = std::hash<float>{}(key.font_size);
// 使用魔法数字组合哈希值,避免简单的异或
return h1 ^ (h2 << 1); // 或者使用 boost::hash_combine 的方法
// return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
}
};
class font_face_interface {
public:
virtual ~font_face_interface() = default;
bool load(const std::filesystem::path& in_path);
void set_font_size(float in_size) {
if (font_size_ == in_size)
return;
font_size_ = in_size;
on_set_font_size(in_size);
}
[[nodiscard]] float get_font_size() const { return font_size_; }
[[nodiscard]] virtual std::string get_font_family() const = 0;
[[nodiscard]] virtual std::string get_font_style() const = 0;
[[nodiscard]] uint64_t get_key() const { return reinterpret_cast<uint64_t>(this); }
[[nodiscard]] virtual bool supports_color_emoji() const = 0;
[[nodiscard]] virtual auto get_metrics() const -> font_v_metrics_t {
if (const auto cached = font_metrics_cache_.get(font_size_)) {
return *cached;
}
const auto& metrics = get_metrics_impl();
font_metrics_cache_.put(font_size_, metrics);
return metrics;
}
[[nodiscard]] virtual font_v_metrics_t get_metrics_impl() const = 0;
[[nodiscard]] virtual std::shared_ptr<image_heap_t> get_glyph_image(int32_t in_glyph_id) const = 0;
[[nodiscard]] virtual std::shared_ptr<image_heap_t> get_emoji_image(int32_t in_glyph_id) const = 0;
[[nodiscard]] virtual bool has_glyph_index(uint32_t in_glyph_index) const = 0;
[[nodiscard]] virtual uint32_t find_glyph_index(uint32_t in_unicode_codepoint) const = 0;
[[nodiscard]] virtual bool has_glyph(uint32_t in_unicode_codepoint) const { return find_glyph_index(in_unicode_codepoint) > 0; }
[[nodiscard]] virtual float get_kerning(uint32_t in_first_glyph_id, uint32_t in_second_glyph_id) const = 0;
[[nodiscard]] glyph_shaped_t shape_glyph(uint32_t in_glyph_id) const {
if (in_glyph_id == 0) {
return {};
}
const glyph_key key{ in_glyph_id, font_size_ };
if (const auto cached = glyph_cache_.get(key)) {
return *cached;
}
auto shaped = make_shape_glyph(in_glyph_id);
glyph_cache_.put(key, shaped);
return shaped;
}
protected:
[[nodiscard]] virtual glyph_shaped_t make_shape_glyph(uint32_t in_glyph_id) const = 0;
virtual bool on_load() = 0;
virtual void on_set_font_size(float in_size) {}
protected:
std::shared_ptr<mapped_file> font_data_; // 字体文件映射数据
mutable lru_cache<glyph_key, glyph_shaped_t> glyph_cache_{ 512 }; // 字形缓存
mutable lru_cache<float, font_v_metrics_t> font_metrics_cache_{ 8 }; // 字体度量缓存
private:
float font_size_ = 16.0f; // 字体大小
};
using font_face_ptr = std::shared_ptr<font_face_interface>;
bool init_font_system();
void destroy_font_system();
font_face_ptr create_font_face(const std::filesystem::path& in_path);

View File

@@ -0,0 +1,31 @@
#pragma once
#include <cstdint>
#include <span>
#include <filesystem>
#include <Eigen/Eigen>
#include "sokol_gfx.h"
#include "misc/mapped_file/mapped_file.h"
struct image_heap_t {
uint32_t width; // 图像宽度
uint32_t height; // 图像高度
sg_pixel_format pixel_format; // 像素格式
void* data; // 图像数据指针
[[nodiscard]] Eigen::Vector2i get_size() const {
return { static_cast<int32_t>(width), static_cast<int32_t>(height) };
}
[[nodiscard]] size_t get_data_size() const {
const auto& pixel_info = sg_query_pixelformat(pixel_format);
return width * height * pixel_info.bytes_per_pixel;
}
};
std::shared_ptr<image_heap_t> load_image(const std::span<std::byte>& in_raw_data);
inline std::shared_ptr<image_heap_t> load_image(const std::filesystem::path& in_file) {
auto mapped = mapped_file::create();
if (!mapped->map_file(in_file))
return nullptr;
return load_image(mapped->get_span());
}

View File

@@ -0,0 +1,38 @@
#include "platform_window.h"
#include "geometry/geometry.h"
void platform_window::on_resize(int width, int height) {
const Eigen::Vector2i size(width, height);
on_resize_delegate.broadcast(size);
}
void platform_window::on_move(const int x, const int y) {
on_move_delegate.broadcast(Eigen::Vector2i(x, y));
}
void platform_window::handle_mouse_move(const Eigen::Vector2f& in_window_pos) {
on_mouse_move_delegate.broadcast(in_window_pos);
}
void platform_window::handle_mouse_button_down(const Eigen::Vector2f& in_window_pos, mouse_button in_button) {
on_mouse_button_down_delegate.broadcast(in_window_pos, in_button);
}
void platform_window::handle_mouse_button_up(const Eigen::Vector2f& in_window_pos, mouse_button in_button) {
on_mouse_button_up_delegate.broadcast(in_window_pos, in_button);
}
void platform_window::handle_mouse_dbl_click(const Eigen::Vector2f& in_window_pos, mouse_button button) {
on_mouse_button_dbl_delegate.broadcast(in_window_pos, button);
}
void platform_window::handle_mouse_wheel(const Eigen::Vector2f& in_window_pos, wheel_event delta) {
on_mouse_wheel_delegate.broadcast(in_window_pos, delta);
}
void platform_window::handle_mouse_leave() { on_mouse_leave_delegate.broadcast(); }
void platform_window::handle_key_down(key_code key, key_action action) { on_key_down_delegate.broadcast(key, action); }
void platform_window::handle_key_up(key_code key, key_action action) { on_key_up_delegate.broadcast(key, action); }

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