color从字符串初始化,在CMake中添加资源文件

This commit is contained in:
daiqingshuang 2025-04-07 17:32:12 +08:00
parent 1c3aee5ca1
commit fe1ca8998c
11 changed files with 304 additions and 112 deletions

View File

@ -30,17 +30,22 @@ else ()
endif ()
add_definitions(-DMIRAGE_HDR_FORMAT=${MIRAGE_HDR_FORMAT} -DMIRAGE_PIXEL_FORMAT=${MIRAGE_PIXEL_FORMAT})
#
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)
set(MIRAGE_ROOT_DIR ${CMAKE_CURRENT_SOURCE_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()
# --- ---
# ****:
# 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}")
# Debug,
if (CMAKE_BUILD_TYPE STREQUAL "Debug")

44
cmake/mirage_utils.cmake Normal file
View File

@ -0,0 +1,44 @@
#
#
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
# ****:
# 对于单配置生成器 ( Makefiles, Ninja), <build>/bin/
# 对于多配置生成器 ( Visual Studio, Xcode), CMake
# ( <build>/bin/Debug/, <build>/bin/Release/)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin CACHE PATH "Directory for runtime executables")
message(STATUS "运行时输出目录设置为: ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
# **设置库文件输出路径 (共享库和静态库)**:
# <build>/lib/ <build>/lib/<Config>/
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib CACHE PATH "Directory for shared libraries")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib CACHE PATH "Directory for static libraries")
message(STATUS "库输出目录设置为: ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
message(STATUS "存档输出目录设置为: ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}")
# --- ---
#
# target
# :
# add_executable(my_app main.cpp)
# set_target_properties(my_app PROPERTIES
# RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/executables"
# )
# add_library(my_lib STATIC my_lib.cpp)
# set_target_properties(my_lib PROPERTIES
# ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/static_libs"
# )
endfunction()

View File

@ -155,4 +155,85 @@ function(retrieve_files path out_files)
#
set(${out_files} ${${out_files}} ${temp_files} PARENT_SCOPE)
endfunction()
#[=======================================================================[
用于添加资源文件并在编译后复制到目标文件 (可执行文件或库)
:
TARGET_NAME: 目标 (可执行文件或库)
RESOURCE_FILE_PATHS - 一个或多个要复制的资源文件的路径 (可以是相对或绝对路径)
#]=======================================================================]
function(add_resources TARGET_NAME)
# TARGET_NAME
set(RESOURCE_FILES ${ARGN})
# 检查目标是否存在 (可选,但推荐)
if(NOT TARGET ${TARGET_NAME})
message(FATAL_ERROR "目标 '${TARGET_NAME}' 不存在,无法添加资源。")
return()
endif()
# **** ()
# 对于多配置生成器 ( Visual Studio, Xcode)使 $<TARGET_FILE_DIR:TARGET_NAME>
# 对于单配置生成器 ( Makefiles, Ninja)使 CMAKE_RUNTIME_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:TARGET_NAME>
if(CMAKE_CONFIGURATION_TYPES)
set(DESTINATION_DIR "$<TARGET_FILE_DIR:${TARGET_NAME}>")
else()
if(CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(DESTINATION_DIR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
else()
# 使 $<TARGET_FILE_DIR:TARGET_NAME>
set(DESTINATION_DIR "$<TARGET_FILE_DIR:${TARGET_NAME}>")
endif()
endif()
# ****
foreach(RESOURCE_FILE ${RESOURCE_FILES})
set(ABS_RESOURCE_FILE "") # /
# ****
if(IS_ABSOLUTE "${RESOURCE_FILE}")
#
if(EXISTS "${RESOURCE_FILE}")
set(ABS_RESOURCE_FILE "${RESOURCE_FILE}")
else()
message(WARNING "资源文件未找到 (绝对路径,将跳过): ${RESOURCE_FILE}")
continue() #
endif()
else()
# CMakeLists.txt
set(TEMP_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_FILE}")
if(EXISTS "${TEMP_PATH}")
set(ABS_RESOURCE_FILE "${TEMP_PATH}")
else()
message(WARNING "资源文件未找到 (相对路径,将跳过): ${RESOURCE_FILE} (相对于: ${CMAKE_CURRENT_SOURCE_DIR})")
continue() #
endif()
endif()
# **** (copy_if_different )
if(IS_DIRECTORY "${ABS_RESOURCE_FILE}")
message(WARNING "提供的资源是一个目录,而非文件 (将跳过): ${ABS_RESOURCE_FILE}. 请使用其他方法复制目录。")
continue()
endif()
# ****
get_filename_component(RESOURCE_FILENAME "${ABS_RESOURCE_FILE}" NAME)
# ****
add_custom_command(
TARGET ${TARGET_NAME} #
POST_BUILD #
COMMAND ${CMAKE_COMMAND} -E copy_if_different # 使 cmake 内建命令复制文件 (仅在文件不同时复制)
"${ABS_RESOURCE_FILE}" # 源文件 (绝对路径)
"${DESTINATION_DIR}/${RESOURCE_FILENAME}" # **** ( + )
COMMENT " ${RESOURCE_FILENAME} ${TARGET_NAME} " #
VERBATIM #
)
# () 便 IDE
# IDE
# target_sources(${TARGET_NAME} PRIVATE "${ABS_RESOURCE_FILE}")
endforeach()
endfunction()

View File

@ -11,6 +11,10 @@
int main(int argc, char* argv[]) {
mirage_app::get().init();
auto c1 = linear_color::from_string("#FF0000");
auto c2 = linear_color::from_string("rgb(255, 0, 0)");
auto c3 = linear_color::from_string("rgba(255, 0, 0, 255)");
auto& manager = font_manager::instance();
manager.add_font(L"C:/Users/46944/AppData/Local/Microsoft/Windows/Fonts/MapleMono-NF-CN-Regular.ttf");
manager.add_font(L"C:/Windows/Fonts/msyh.ttc");

View File

@ -8,6 +8,7 @@
#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) {
@ -57,6 +58,9 @@ void mirage_app::init() {
mirage_scoped_duration_timer timer(duration);
last_time = get_current_time();
if (!mirage_style::get().load_config("default_style.toml")) {
fprintf(stderr, "mirage: 无法加载样式配置\n");
}
render_context = mirage_create_render_context();
render_context->init();
const sg_desc desc = {
@ -70,7 +74,7 @@ void mirage_app::init() {
render_context->end_init();
}
// 初始化用时
std::cout << "mirage: " << "Initialization took " << std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() << "ms" << std::endl;
fprintf(stderr, "mirage: 初始化耗时 %lld ms\n", std::chrono::duration_cast<std::chrono::milliseconds>(duration).count());
}

View File

@ -1,6 +1,7 @@
#pragma once
#include <unordered_map>
#include <string>
#include <cstdint>
/**
* name_t

View File

@ -3,108 +3,156 @@
#include <charconv>
#include <vector>
linear_color linear_color::from_string(const std::string& in_str) {
// **定义一个表示无效输入的默认返回值 (C++20 designated initializers)**
// Made static constexpr for potential compile-time use if needed elsewhere
static linear_color invalid_color = { 0.0f, 0.0f, 0.0f, 0.0f };
std::string str = in_str;
// **1. 去除前后空格 (No major C++23 change here, std::find_if is efficient)**
auto trim_func = [](unsigned char ch){ return !std::isspace(ch); };
str.erase(str.begin(), std::ranges::find_if(str, trim_func));
str.erase(std::find_if(str.rbegin(), str.rend(), trim_func).base(), str.end());
// **2. 检查格式前缀和后缀 (C++20 starts_with/ends_with)**
bool is_rgba = false;
std::string_view value_part_sv;
if (str.starts_with("rgb(") && str.ends_with(')')) {
is_rgba = false;
value_part_sv = std::string_view(str).substr(4, str.length() - 5); // Skip "rgb(" and ")"
} else if (str.starts_with("rgba(") && str.ends_with(')')) {
is_rgba = true;
value_part_sv = std::string_view(str).substr(5, str.length() - 6); // Skip "rgba(" and ")"
} else {
return invalid_color; // **无效格式前缀/后缀**
}
if (value_part_sv.empty() && (is_rgba ? 4 : 3) > 0) {
// Handle cases like "rgb()" or "rgba()" which are invalid if components are expected
return invalid_color; // **括号内为空**
}
// **3. 分割数值字符串 (Using stringstream, C++23 views could be an alternative but more complex here)**
std::vector<float> values;
std::stringstream ss;
ss << value_part_sv;
std::string segment;
while (std::getline(ss, segment, ',')) {
// **4. 处理每个分量**
// 去除分量前后的空格
segment.erase(segment.begin(), std::ranges::find_if(segment, trim_func));
segment.erase(std::find_if(segment.rbegin(), segment.rend(), trim_func).base(), segment.end());
if (segment.empty()) {
return invalid_color; // **空分量值**
}
const char* const first = segment.data();
const char* const last = first + segment.size();
float component_value = 0.0f;
// **C++23 std::string::contains to check for decimal point**
if (segment.contains('.')) {
// **处理浮点数 [0, 1]**
float val = 0.0f;
auto [ptr, ec] = std::from_chars(first, last, val);
// Check for errors: conversion failed OR not the entire segment was parsed
if (ec != std::errc() || ptr != last) {
return invalid_color; // **无效浮点数格式**
}
// Check range [0, 1] for floats
if (val < 0.0f || val > 1.0f) {
return invalid_color; // **浮点数值超出 [0, 1] 范围**
}
component_value = val;
} else {
// **处理整数 [0, 255]**
int int_val = 0;
auto [ptr, ec] = std::from_chars(first, last, int_val);
// Check for errors: conversion failed OR not the entire segment was parsed
if (ec != std::errc() || ptr != last) {
return invalid_color; // **无效整数格式**
}
// Check range [0, 255] for integers
if (int_val < 0 || int_val > 255) {
return invalid_color; // **整数值超出 [0, 255] 范围**
}
// **归一化: 将 uint8 范围的值映射到 [0, 1] float**
component_value = static_cast<float>(int_val) / 255.0f;
}
values.push_back(component_value);
}
// **5. 检查分量数量是否匹配**
if (is_rgba) {
if (values.size() != 4) {
return invalid_color; // **rgba 分量数量错误**
}
return { values[0], values[1], values[2], values[3] };
}
// rgb
if (values.size() != 3) {
return invalid_color; // **rgb 分量数量错误**
}
// **对于 rgbalpha 默认为 1.0f**
return { values[0], values[1], values[2], 1.f };
/**
* @brief
*
* @param hex (0-9, A-F, a-f)
* @return (0-15)-1
*/
int hex_char_to_int(char hex) {
if (hex >= '0' && hex <= '9')
return hex - '0';
if (hex >= 'A' && hex <= 'F')
return hex - 'A' + 10;
if (hex >= 'a' && hex <= 'f')
return hex - 'a' + 10;
return -1; // 无效字符
}
std::optional<linear_color> linear_color::from_string(const std::string& in_str) {
linear_color result{};
// 处理十六进制格式
if (!in_str.empty() && in_str[0] == '#') {
// **支持的十六进制格式:** #RGB, #RGBA, #RRGGBB, #RRGGBBAA
size_t length = in_str.length();
// 检查格式并解析
if (length == 4) { // #RGB 格式
// 检查所有字符是否都是有效的十六进制数字
for (int i = 1; i < 4; i++) {
if (!std::isxdigit(in_str[i]))
return std::nullopt;
}
// 对于 #RGB 格式每个字符需要重复R -> RR, G -> GG, B -> BB
int r = hex_char_to_int(in_str[1]);
int g = hex_char_to_int(in_str[2]);
int b = hex_char_to_int(in_str[3]);
// 将 0-15 的值扩展到 0-255 的范围
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;
}
if (length == 5) { // #RGBA 格式
// 检查所有字符是否都是有效的十六进制数字
for (int i = 1; i < 5; i++) {
if (!std::isxdigit(in_str[i]))
return std::nullopt;
}
int r = hex_char_to_int(in_str[1]);
int g = hex_char_to_int(in_str[2]);
int b = hex_char_to_int(in_str[3]);
int a = hex_char_to_int(in_str[4]);
// 将 0-15 的值扩展到 0-255 的范围
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;
}
if (length == 7) { // #RRGGBB 格式
// 检查所有字符是否都是有效的十六进制数字
for (int i = 1; i < 7; i++) {
if (!std::isxdigit(in_str[i]))
return std::nullopt;
}
int r = (hex_char_to_int(in_str[1]) << 4) + hex_char_to_int(in_str[2]);
int g = (hex_char_to_int(in_str[3]) << 4) + hex_char_to_int(in_str[4]);
int b = (hex_char_to_int(in_str[5]) << 4) + hex_char_to_int(in_str[6]);
result.r = r / 255.0f;
result.g = g / 255.0f;
result.b = b / 255.0f;
result.a = 1.0f; // 默认为完全不透明
return result;
}
if (length == 9) { // #RRGGBBAA 格式
// 检查所有字符是否都是有效的十六进制数字
for (int i = 1; i < 9; i++) {
if (!std::isxdigit(in_str[i]))
return std::nullopt;
}
int r = (hex_char_to_int(in_str[1]) << 4) + hex_char_to_int(in_str[2]);
int g = (hex_char_to_int(in_str[3]) << 4) + hex_char_to_int(in_str[4]);
int b = (hex_char_to_int(in_str[5]) << 4) + hex_char_to_int(in_str[6]);
int a = (hex_char_to_int(in_str[7]) << 4) + hex_char_to_int(in_str[8]);
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 格式
int r_int, g_int, b_int, a_int = 255; // Alpha 默认值为 255 (对应 1.0f)
int components_read = 0;
char closing_paren = '\0';
// 尝试匹配 rgba(r,g,b,a) 格式
components_read = std::sscanf(in_str.c_str(), "rgba(%d,%d,%d,%d %c",
&r_int, &g_int, &b_int, &a_int, &closing_paren);
if (components_read == 5 && closing_paren == ')') {
// 验证数值范围 [0, 255]
if (r_int < 0 || r_int > 255 || g_int < 0 || g_int > 255 ||
b_int < 0 || b_int > 255 || a_int < 0 || a_int > 255) {
return std::nullopt; // 数值超出范围
}
// 归一化到 [0.0, 1.0]
result.r = static_cast<float>(r_int) / 255.0f;
result.g = static_cast<float>(g_int) / 255.0f;
result.b = static_cast<float>(b_int) / 255.0f;
result.a = static_cast<float>(a_int) / 255.0f;
return result;
}
// 如果 RGBA 格式不匹配,尝试匹配 rgb(r,g,b) 格式
closing_paren = '\0'; // 重置
components_read = std::sscanf(in_str.c_str(), "rgb(%d,%d,%d %c",
&r_int, &g_int, &b_int, &closing_paren);
if (components_read == 4 && closing_paren == ')') {
// 验证数值范围 [0, 255]
if (r_int < 0 || r_int > 255 || g_int < 0 || g_int > 255 ||
b_int < 0 || b_int > 255) {
return std::nullopt; // 数值超出范围
}
// 归一化到 [0.0, 1.0]Alpha 使用默认值 1.0f
result.r = static_cast<float>(r_int) / 255.0f;
result.g = static_cast<float>(g_int) / 255.0f;
result.b = static_cast<float>(b_int) / 255.0f;
result.a = 1.0f;
return result;
}
// 所有格式都不匹配
return std::nullopt;
}

View File

@ -148,7 +148,7 @@ public:
* @note **/ linear_color{0.0f, 0.0f, 0.0f, 0.0f}**
* 使 [[nodiscard]]
*/
[[nodiscard]] static linear_color from_string(const std::string& in_str);
[[nodiscard]] static std::optional<linear_color> from_string(const std::string& in_str);
//-------------- 成员变量 --------------

View File

@ -8,3 +8,5 @@ add_subdirectory(third_party/tomlplusplus)
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_render tomlplusplus::tomlplusplus)
add_resources(${PROJECT_NAME} src/style/default_style.toml)

View File

@ -4,6 +4,7 @@ version = "0.0.1"
description = "mirage的默认样式"
author = "奶酪"
[button]
hover_color = "rgb(31, 31, 31)"
pressed_color = "rgb(31, 31, 31)"

View File

@ -6,5 +6,7 @@ bool mirage_style::load_config(const std::filesystem::path& in_filename) {
}
catch (const toml::parse_error& err) {
fprintf(stderr, "%s\n", err.what());
return false;
}
return true;
}