Compare commits
149 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bfad8b571f | |||
| f8de8be68e | |||
| a5f2398a62 | |||
| 166784e422 | |||
| c725bd8b07 | |||
| 0aea4d2f0a | |||
| 80c0e46629 | |||
| 2785c336b9 | |||
| b77c611752 | |||
| c7bc99e041 | |||
| 38bd206a25 | |||
| 28e5d72f9b | |||
| b0f9d92750 | |||
| b6979749f8 | |||
| 5d30162f78 | |||
| 7f3bc18305 | |||
| 0995ef09f2 | |||
| 97cb9c3f18 | |||
| 95a5d7f60e | |||
| 060a18cd68 | |||
| 30de05f679 | |||
| 193b355e6e | |||
| 3004bf1f00 | |||
| 581e376c5c | |||
| c5c60f479a | |||
| c9b4e6dccd | |||
| 3db251aac6 | |||
| d5e25c8eaf | |||
| 0f609e1e9c | |||
| 7a281dee1f | |||
| c7d859a9b6 | |||
| 0adc357d8a | |||
| a501d409ff | |||
| d0aa773666 | |||
| d560c437fe | |||
| 3a9ce532d5 | |||
| 6cc65e12b2 | |||
| 562f598a41 | |||
| 28b23ffd0f | |||
|
|
163b91b4f2 | ||
|
|
3d735f81df | ||
|
|
19fb1e889c | ||
|
|
a1f09f9f10 | ||
|
|
188aa2d7b8 | ||
|
|
05ccd31ca3 | ||
| cbc52fb72d | |||
|
|
8d01ad91c4 | ||
| aee4ba33d1 | |||
|
|
96189c8a1a | ||
|
|
d6487aef83 | ||
|
|
081905523b | ||
|
|
607d0a43a1 | ||
| 0b5487f69d | |||
| 4418c383bc | |||
| 9366415c8c | |||
| 1230abc898 | |||
|
|
c695b6e4ed | ||
| a88904a546 | |||
| d8551a96ca | |||
| cc37be615c | |||
| 12b6d058df | |||
| 546c40a18b | |||
| 79704b894b | |||
| 057967a5c9 | |||
|
|
21a12b257f | ||
|
|
28980f8c41 | ||
|
|
542949e9f5 | ||
| 900db6dd70 | |||
| c322a4b6dd | |||
|
|
2c794509bc | ||
| b5fc6a9a26 | |||
| 934dd653ee | |||
|
|
a88ef151d7 | ||
|
|
57c65892db | ||
|
|
791fca9282 | ||
|
|
bbfe5ab665 | ||
|
|
7f37e2098d | ||
| 84330d5069 | |||
| d596ae4c09 | |||
| 4d9ccc73c0 | |||
|
|
2ebd7f9182 | ||
|
|
fe1ca8998c | ||
| 1c3aee5ca1 | |||
| 3d58348c12 | |||
| 0ac8367b43 | |||
| e9909e1c77 | |||
| bd26423cb6 | |||
| 3999bccf6e | |||
| fbf6e7e716 | |||
| fb46da91d4 | |||
| c79167eb93 | |||
| 0c32af832d | |||
| 759f482afe | |||
| 3a426b8b99 | |||
| a0487a75c1 | |||
| 9282b9f214 | |||
| 09f56e6c57 | |||
|
|
2b923c2bb5 | ||
|
|
da19e27f5b | ||
|
|
e0c0648221 | ||
|
|
9be39399d9 | ||
|
|
4d0dbbe6c5 | ||
|
|
73eebd5387 | ||
|
|
aaae32bd26 | ||
| 3e612e1274 | |||
| b7a27096b5 | |||
| d5465f1953 | |||
| e966dadc70 | |||
| 4036ee2ded | |||
| 7206c04a71 | |||
| 673b0a1478 | |||
| 56917da0d1 | |||
| 765617d538 | |||
| 2a84cd6c4e | |||
| 976994e85b | |||
| 2f9e76e4aa | |||
| f200fcba4a | |||
| 727457b9f7 | |||
| a19317b2f2 | |||
| 2a99584120 | |||
| 5aafef4cfb | |||
| 6129e71db7 | |||
| 627dba45ea | |||
| 7cf03b639b | |||
| de33e765d4 | |||
| dc896d51f3 | |||
| 2c5ea1e7e9 | |||
| ed6722c297 | |||
| 3f375ccfc2 | |||
| a27c49af75 | |||
| e44a9bda57 | |||
| 79363ce141 | |||
| 2a2cac52d3 | |||
|
|
7dbcc93d1a | ||
|
|
3333dacdce | ||
|
|
aba7da227d | ||
|
|
5940a4747b | ||
|
|
81d391d5ba | ||
|
|
aa926e31fc | ||
|
|
2d026313ff | ||
|
|
ab9c7028ef | ||
| 088d301b6c | |||
| 1a768f405c | |||
| 19a23d4f94 | |||
|
|
0f014790c9 | ||
|
|
3bac5b548b | ||
|
|
db735b94ea | ||
| 43aa8ce56d | |||
| 17c14a7a14 |
@@ -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
20
.gitignore
vendored
@@ -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
6
.gitmodules
vendored
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
41
cmake/mingw_dll.cmake
Normal 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
46
cmake/mirage_utils.cmake
Normal 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()
|
||||
85
cmake/project_cpp_standard.cmake
Normal file
85
cmake/project_cpp_standard.cmake
Normal 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()
|
||||
@@ -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()
|
||||
|
||||
76
docs/mirage_cross_platform_refactor.md
Normal file
76
docs/mirage_cross_platform_refactor.md
Normal 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% 下降。
|
||||
- **发布节奏**:Beta(v0.1-beta,启用开关,内部 dogfood);RC(v0.2-rc,禁用旧路径,beta 用户反馈);GA(v1.0,移除遗留代码,全面文档更新)。
|
||||
|
||||
## 尚需确认或缺失的信息
|
||||
- 目标平台清单(Windows 之外是否包含 Linux/X11、Wayland、macOS、移动端)。
|
||||
- 新平台的渲染后端偏好(Vulkan、Metal、OpenGL)及与 sokol 的集成策略。
|
||||
- 输入法与键盘布局要求(多语言支持、组合键需求)。
|
||||
- 示例与生产环境的期望验证范围(是否需要 headless 测试)。
|
||||
- CI 资源可用性(是否具备多平台 runner)与发布时间表。
|
||||
- 性能或延迟红线指标,用于衡量重构后回归。
|
||||
@@ -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})
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 ()
|
||||
@@ -1,4 +0,0 @@
|
||||
//
|
||||
// Created by Administrator on 25-3-1.
|
||||
//
|
||||
#include "core/window/render_window.h"
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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_;
|
||||
};
|
||||
@@ -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(); }
|
||||
@@ -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; }
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_;
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
#include "geometry.h"
|
||||
@@ -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});
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
// }
|
||||
}
|
||||
8
src/mirage_app/CMakeLists.txt
Normal file
8
src/mirage_app/CMakeLists.txt
Normal 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)
|
||||
105
src/mirage_app/src/mirage.cpp
Normal file
105
src/mirage_app/src/mirage.cpp
Normal 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();
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
25
src/mirage_config/CMakeLists.txt
Normal file
25
src/mirage_config/CMakeLists.txt
Normal 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})
|
||||
4
src/mirage_config/src/config.cpp
Normal file
4
src/mirage_config/src/config.cpp
Normal file
@@ -0,0 +1,4 @@
|
||||
//
|
||||
// Created by 46944 on 25-7-1.
|
||||
//
|
||||
#include "config.h"
|
||||
510
src/mirage_config/src/config.h
Normal file
510
src/mirage_config/src/config.h
Normal 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;
|
||||
}
|
||||
};
|
||||
24
src/mirage_core/CMakeLists.txt
Normal file
24
src/mirage_core/CMakeLists.txt
Normal 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)
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
|
||||
@@ -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());
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
//-------------- 分割操作 --------------
|
||||
|
||||
/**
|
||||
@@ -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
@@ -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 平台原生事件类型
|
||||
366
src/mirage_core/src/misc/key_type/windows/key_type.cpp
Normal file
366
src/mirage_core/src/misc/key_type/windows/key_type.cpp
Normal 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;
|
||||
}
|
||||
|
||||
32
src/mirage_core/src/misc/log_util.h
Normal file
32
src/mirage_core/src/misc/log_util.h
Normal 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;
|
||||
}
|
||||
264
src/mirage_core/src/misc/lru_cache.h
Normal file
264
src/mirage_core/src/misc/lru_cache.h
Normal 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_ };
|
||||
}
|
||||
};
|
||||
40
src/mirage_core/src/misc/mapped_file/mapped_file.h
Normal file
40
src/mirage_core/src/misc/mapped_file/mapped_file.h
Normal 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();
|
||||
};
|
||||
110
src/mirage_core/src/misc/mapped_file/unix/mapped_file.cpp
Normal file
110
src/mirage_core/src/misc/mapped_file/unix/mapped_file.cpp
Normal 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>();
|
||||
}
|
||||
153
src/mirage_core/src/misc/mapped_file/windows/mapped_file.cpp
Normal file
153
src/mirage_core/src/misc/mapped_file/windows/mapped_file.cpp
Normal 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>();
|
||||
}
|
||||
@@ -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,y,后两个元素到z,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])) {
|
||||
}
|
||||
|
||||
//-------------- 辅助类型特征检测 --------------
|
||||
37
src/mirage_core/src/misc/name.h
Normal file
37
src/mirage_core/src/misc/name.h
Normal 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>;
|
||||
24
src/mirage_image/CMakeLists.txt
Normal file
24
src/mirage_image/CMakeLists.txt
Normal 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
246
src/mirage_image/color.cpp
Normal 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;
|
||||
}
|
||||
@@ -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 }; }
|
||||
192
src/mirage_image/image_accessor_factory.h
Normal file
192
src/mirage_image/image_accessor_factory.h
Normal 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
1970
src/mirage_image/pixel.h
Normal file
File diff suppressed because it is too large
Load Diff
20
src/mirage_platform/CMakeLists.txt
Normal file
20
src/mirage_platform/CMakeLists.txt
Normal 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})
|
||||
138
src/mirage_platform/include/mirage_platform/ime.h
Normal file
138
src/mirage_platform/include/mirage_platform/ime.h
Normal 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
|
||||
131
src/mirage_platform/include/mirage_platform/input.h
Normal file
131
src/mirage_platform/include/mirage_platform/input.h
Normal 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
|
||||
174
src/mirage_platform/include/mirage_platform/platform_types.h
Normal file
174
src/mirage_platform/include/mirage_platform/platform_types.h
Normal 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
|
||||
216
src/mirage_platform/include/mirage_platform/window.h
Normal file
216
src/mirage_platform/include/mirage_platform/window.h
Normal 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
|
||||
199
src/mirage_platform/src/windows/windows_ime.cpp
Normal file
199
src/mirage_platform/src/windows/windows_ime.cpp
Normal 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
|
||||
175
src/mirage_platform/src/windows/windows_input.cpp
Normal file
175
src/mirage_platform/src/windows/windows_input.cpp
Normal 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
|
||||
414
src/mirage_platform/src/windows/windows_window.cpp
Normal file
414
src/mirage_platform/src/windows/windows_window.cpp
Normal 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
|
||||
49
src/mirage_render/CMakeLists.txt
Normal file
49
src/mirage_render/CMakeLists.txt
Normal 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 ()
|
||||
10
src/mirage_render/font/freetype_font/CMakeLists.txt
Normal file
10
src/mirage_render/font/freetype_font/CMakeLists.txt
Normal 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)
|
||||
255
src/mirage_render/font/freetype_font/src/freetype_interface.cpp
Normal file
255
src/mirage_render/font/freetype_font/src/freetype_interface.cpp
Normal 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;
|
||||
}
|
||||
@@ -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_{};
|
||||
};
|
||||
10
src/mirage_render/font/stb_truetype_font/CMakeLists.txt
Normal file
10
src/mirage_render/font/stb_truetype_font/CMakeLists.txt
Normal 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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_{};
|
||||
};
|
||||
10
src/mirage_render/image/stb_image_loader/CMakeLists.txt
Normal file
10
src/mirage_render/image/stb_image_loader/CMakeLists.txt
Normal 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)
|
||||
123
src/mirage_render/image/stb_image_loader/stb_image_loader.cpp
Normal file
123
src/mirage_render/image/stb_image_loader/stb_image_loader.cpp
Normal 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();
|
||||
}
|
||||
35
src/mirage_render/image/stb_image_loader/stb_image_loader.h
Normal file
35
src/mirage_render/image/stb_image_loader/stb_image_loader.h
Normal 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_{};
|
||||
};
|
||||
38
src/mirage_render/src/font/atlas/bitmap_glyph_atlas.cpp
Normal file
38
src/mirage_render/src/font/atlas/bitmap_glyph_atlas.cpp
Normal 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;
|
||||
}
|
||||
33
src/mirage_render/src/font/atlas/bitmap_glyph_atlas.h
Normal file
33
src/mirage_render/src/font/atlas/bitmap_glyph_atlas.h
Normal 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;
|
||||
};
|
||||
39
src/mirage_render/src/font/atlas/color_emoji_atlas.cpp
Normal file
39
src/mirage_render/src/font/atlas/color_emoji_atlas.cpp
Normal 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;
|
||||
}
|
||||
33
src/mirage_render/src/font/atlas/color_emoji_atlas.h
Normal file
33
src/mirage_render/src/font/atlas/color_emoji_atlas.h
Normal 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;
|
||||
};
|
||||
9
src/mirage_render/src/font/atlas/font_atlas.cpp
Normal file
9
src/mirage_render/src/font/atlas/font_atlas.cpp
Normal 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) {}
|
||||
168
src/mirage_render/src/font/atlas/font_atlas.h
Normal file
168
src/mirage_render/src/font/atlas/font_atlas.h
Normal 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;
|
||||
};
|
||||
116
src/mirage_render/src/font/atlas/font_atlas_manager.cpp
Normal file
116
src/mirage_render/src/font/atlas/font_atlas_manager.cpp
Normal 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;
|
||||
}
|
||||
80
src/mirage_render/src/font/atlas/font_atlas_manager.h
Normal file
80
src/mirage_render/src/font/atlas/font_atlas_manager.h
Normal 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});
|
||||
};
|
||||
88
src/mirage_render/src/font/emoji_detector.h
Normal file
88
src/mirage_render/src/font/emoji_detector.h
Normal 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;
|
||||
}
|
||||
};
|
||||
244
src/mirage_render/src/font/font_layout.cpp
Normal file
244
src/mirage_render/src/font/font_layout.cpp
Normal 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
|
||||
}
|
||||
361
src/mirage_render/src/font/font_layout.h
Normal file
361
src/mirage_render/src/font/font_layout.h
Normal 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; ///< 覆盖的主字体
|
||||
};
|
||||
147
src/mirage_render/src/font/font_system.cpp
Normal file
147
src/mirage_render/src/font/font_system.cpp
Normal 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;
|
||||
}
|
||||
156
src/mirage_render/src/font/font_system.h
Normal file
156
src/mirage_render/src/font/font_system.h
Normal 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
|
||||
};
|
||||
49
src/mirage_render/src/font/font_type.h
Normal file
49
src/mirage_render/src/font/font_type.h
Normal 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 }; ///< 文本边距
|
||||
};
|
||||
71
src/mirage_render/src/font/font_utils.h
Normal file
71
src/mirage_render/src/font/font_utils.h
Normal 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; // 表不存在
|
||||
}
|
||||
}
|
||||
42
src/mirage_render/src/ime/ime.cpp
Normal file
42
src/mirage_render/src/ime/ime.cpp
Normal 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);
|
||||
}
|
||||
40
src/mirage_render/src/ime/ime.h
Normal file
40
src/mirage_render/src/ime/ime.h
Normal 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_;
|
||||
};
|
||||
30
src/mirage_render/src/ime/windows/windows_ime.cpp
Normal file
30
src/mirage_render/src/ime/windows/windows_ime.cpp
Normal 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;
|
||||
}
|
||||
10
src/mirage_render/src/interface/font_interface.cpp
Normal file
10
src/mirage_render/src/interface/font_interface.cpp
Normal 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();
|
||||
}
|
||||
|
||||
123
src/mirage_render/src/interface/font_interface.h
Normal file
123
src/mirage_render/src/interface/font_interface.h
Normal 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);
|
||||
31
src/mirage_render/src/interface/image_interface.h
Normal file
31
src/mirage_render/src/interface/image_interface.h
Normal 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());
|
||||
}
|
||||
38
src/mirage_render/src/platform_window/platform_window.cpp
Normal file
38
src/mirage_render/src/platform_window/platform_window.cpp
Normal 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
Reference in New Issue
Block a user