注释
This commit is contained in:
@@ -1,163 +1,244 @@
|
||||
#include "font_layout.h"
|
||||
|
||||
#include "emoji_detector.h"
|
||||
#include "font_system.h"
|
||||
#include "interface/font_interface.h"
|
||||
#include "misc/log_util.h"
|
||||
|
||||
font_face_ptr find_renderable_glyph(const font_face_ptr& in_primary_font, std::vector<text_layout_t::glyph_position_t>& v, auto& g, uint32_t& glyph_index) {
|
||||
auto& font_mgr = font_manager::instance();
|
||||
font_face_ptr font;
|
||||
// 尝试找到一个可渲染的字形
|
||||
do {
|
||||
if (g == v.end()) {
|
||||
// 如果没有更多字形,跳出循环
|
||||
font = nullptr;
|
||||
break;
|
||||
}
|
||||
// 找一个能够渲染当前字形的字体
|
||||
font = font_mgr.get_font_for_code_point(in_primary_font, g->codepoint, &glyph_index);
|
||||
if (!font) {
|
||||
log_warn("无法找到用于渲染 U+{:04X} 的字体", (uint32_t)g->codepoint);
|
||||
// 移除当前字形并继续处理下一个
|
||||
g = v.erase(g);
|
||||
}
|
||||
}
|
||||
while (!font);
|
||||
if (!font)
|
||||
glyph_index = 0; // 如果没有找到字体,确保 glyph_index 被重置
|
||||
// ==================== text_layout_t::line_t 成员函数实现 ====================
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
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);
|
||||
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();
|
||||
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 向当前行添加一个字形
|
||||
*
|
||||
* 该函数负责:
|
||||
* 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; // 如果没有最大宽度限制或没有字形,直接返回
|
||||
}
|
||||
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& font_mgr = font_manager::instance();
|
||||
|
||||
auto g_metrics = using_font->shape_glyph(glyph_index);
|
||||
// 查找能渲染该字符的字体
|
||||
uint32_t glyph_index = 0;
|
||||
auto using_font = font_mgr.get_font_for_code_point(in_primary_font, codepoint, &glyph_index);
|
||||
|
||||
if (codepoint == ' ') {
|
||||
// 空格的宽度直接使用字形的宽度
|
||||
return line_width + g_metrics.advance.x() > max_width;
|
||||
}
|
||||
return line_width + g_metrics.rect.size().x() > max_width;
|
||||
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();
|
||||
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.");
|
||||
}
|
||||
// 获取主字体(优先使用覆盖字体)
|
||||
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* current_line = &get_last_line();
|
||||
|
||||
auto end_line = [&]() {
|
||||
current_line = &next_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();
|
||||
}
|
||||
// 遍历字符串中的每个字符
|
||||
for (const auto c : str) {
|
||||
// 处理换行符
|
||||
if (c == U'\n') {
|
||||
end_line();
|
||||
continue;
|
||||
}
|
||||
|
||||
current_line->add_glyph(primary_font, c, font_size);
|
||||
}
|
||||
// 检查是否需要自动换行
|
||||
if (current_line->should_next_line(primary_font, max_width, c)) {
|
||||
end_line();
|
||||
}
|
||||
|
||||
// 如果最后一行没有内容,设置其高度为默认空行高度
|
||||
if (current_line->empty()) {
|
||||
current_line->line_height = get_empty_line_height();
|
||||
}
|
||||
// 将字符添加到当前行
|
||||
current_line->add_glyph(primary_font, c, font_size);
|
||||
}
|
||||
|
||||
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();
|
||||
// 如果最后一行没有内容,设置其高度为默认空行高度
|
||||
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;
|
||||
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;
|
||||
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
|
||||
// 获取当前使用的字体
|
||||
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
|
||||
}
|
||||
|
||||
@@ -5,158 +5,357 @@
|
||||
#include "texture/atlas/texture_atlas_types.h"
|
||||
|
||||
namespace text_layout_impl {
|
||||
struct glyph_info_t {
|
||||
font_face_ptr font;
|
||||
uint32_t glyph_index = 0;
|
||||
glyph_shaped_t metrics;
|
||||
std::shared_ptr<atlas_region_t> region;
|
||||
bool is_emoji = false;
|
||||
bool valid = false;
|
||||
};
|
||||
/**
|
||||
* @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 {
|
||||
text_layout_t() {
|
||||
lines.emplace_back(); // 初始化第一行
|
||||
}
|
||||
struct glyph_position_t {
|
||||
bool is_emoji; // 是否为表情符号
|
||||
char32_t codepoint;
|
||||
Eigen::Vector2f position; // 屏幕位置
|
||||
Eigen::Vector2f size; // 实际占用的尺寸
|
||||
std::shared_ptr<atlas_region_t> region; // 纹理图集区域
|
||||
};
|
||||
/**
|
||||
* @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; ///< 字形在纹理图集中的区域
|
||||
};
|
||||
|
||||
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 文本行信息
|
||||
* 管理单行文本的布局信息和字形列表
|
||||
*/
|
||||
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; ///< 该行包含的所有字形
|
||||
|
||||
[[nodiscard]] auto get_line_height() const {
|
||||
return line_height;
|
||||
}
|
||||
[[nodiscard]] auto get_line_width() const {
|
||||
return line_width;
|
||||
}
|
||||
[[nodiscard]] auto get_line_size() const {
|
||||
return Eigen::Vector2f(line_width, get_line_height());
|
||||
}
|
||||
void add_glyph(const font_face_ptr& in_primary_font, char32_t codepoint, float font_size);
|
||||
// ==================== 行属性获取函数 ====================
|
||||
|
||||
bool should_next_line(const font_face_ptr& in_primary_font, float max_width, char32_t codepoint) const;
|
||||
/**
|
||||
* @brief 获取行高
|
||||
* @return 当前行的高度(像素)
|
||||
*/
|
||||
[[nodiscard]] auto get_line_height() const {
|
||||
return line_height;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return glyphs.empty();
|
||||
}
|
||||
/**
|
||||
* @brief 获取行宽
|
||||
* @return 当前行的宽度(像素)
|
||||
*/
|
||||
[[nodiscard]] auto get_line_width() const {
|
||||
return line_width;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_glyph_pos(size_t index, bool use_line_pos = true) const -> Eigen::Vector2f {
|
||||
if (glyphs.empty()) {
|
||||
return { 0, position_y };
|
||||
}
|
||||
/**
|
||||
* @brief 获取行的尺寸
|
||||
* @return 包含宽度和高度的二维向量
|
||||
*/
|
||||
[[nodiscard]] auto get_line_size() const {
|
||||
return Eigen::Vector2f(line_width, get_line_height());
|
||||
}
|
||||
|
||||
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();
|
||||
/**
|
||||
* @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);
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
/**
|
||||
* @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;
|
||||
|
||||
[[nodiscard]] size_t get_glyph_count() const {
|
||||
return glyphs.size();
|
||||
}
|
||||
[[nodiscard]] bool has_content() const {
|
||||
return !glyphs.empty();
|
||||
}
|
||||
};
|
||||
// ==================== 状态查询函数 ====================
|
||||
|
||||
line_t* get_line(size_t index) {
|
||||
if (index < lines.size()) {
|
||||
return &lines[index];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
[[nodiscard]] const line_t* get_line(size_t index) const {
|
||||
if (index < lines.size()) {
|
||||
return &lines[index];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
/**
|
||||
* @brief 检查行是否为空
|
||||
* @return true 如果行内没有字形,false 否则
|
||||
*/
|
||||
[[nodiscard]] bool empty() const {
|
||||
return glyphs.empty();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto size() const {
|
||||
return lines.size();
|
||||
}
|
||||
[[nodiscard]] auto empty() const {
|
||||
return lines.size() == 0 || (lines.size() == 1 && lines[0].empty());
|
||||
}
|
||||
auto& get_last_line() {
|
||||
return lines.back();
|
||||
}
|
||||
[[nodiscard]] const auto& get_last_line() const {
|
||||
return lines.back();
|
||||
}
|
||||
/**
|
||||
* @brief 检查行是否有内容
|
||||
* @return true 如果行内有字形,false 否则
|
||||
*/
|
||||
[[nodiscard]] bool has_content() const {
|
||||
return !glyphs.empty();
|
||||
}
|
||||
|
||||
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 获取行内字形数量
|
||||
* @return 字形数量
|
||||
*/
|
||||
[[nodiscard]] size_t get_glyph_count() const {
|
||||
return glyphs.size();
|
||||
}
|
||||
|
||||
void push_str(const std::u32string& str);
|
||||
void clear() {
|
||||
lines.clear();
|
||||
lines.emplace_back(); // 重新初始化第一行
|
||||
total_size.setZero();
|
||||
}
|
||||
/**
|
||||
* @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 };
|
||||
}
|
||||
};
|
||||
|
||||
[[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);
|
||||
}
|
||||
[[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;
|
||||
}
|
||||
// ==================== 构造函数 ====================
|
||||
|
||||
void set_font_size(float size);
|
||||
void set_primary_font(const font_face_ptr& font);
|
||||
void set_line_spacing(float spacing) {
|
||||
line_spacing = spacing;
|
||||
}
|
||||
void set_max_width(float width) {
|
||||
max_width = width;
|
||||
}
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
* 初始化文本布局并创建第一行
|
||||
*/
|
||||
text_layout_t() {
|
||||
lines.emplace_back(); // 初始化第一行
|
||||
}
|
||||
|
||||
[[nodiscard]] float get_line_spacing() const {
|
||||
return line_spacing;
|
||||
}
|
||||
// ==================== 行访问函数 ====================
|
||||
|
||||
[[nodiscard]] float get_font_size() const {
|
||||
return font_size;
|
||||
}
|
||||
float get_empty_line_height() const;
|
||||
/**
|
||||
* @brief 获取指定索引的行(可修改版本)
|
||||
* @param index 行索引
|
||||
* @return 指向行的指针,如果索引无效则返回nullptr
|
||||
*/
|
||||
line_t* get_line(size_t index) {
|
||||
if (index < lines.size()) {
|
||||
return &lines[index];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto begin() { return lines.begin(); }
|
||||
auto begin() const { return lines.begin(); }
|
||||
auto end() { return lines.end(); }
|
||||
auto end() const { return lines.end(); }
|
||||
/**
|
||||
* @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 }; ///< 文本总尺寸(宽度和高度)
|
||||
|
||||
Eigen::Vector2f total_size{ 0, 0 }; // 文本总尺寸
|
||||
private:
|
||||
float max_width = 0.f;
|
||||
float line_spacing = 1.2f; // 行间距
|
||||
float font_size = 24.f; // 字体大小
|
||||
// ==================== 私有成员变量 ====================
|
||||
|
||||
std::vector<line_t> lines; // 文本行列表
|
||||
font_face_ptr override_primary_font; // 主字体
|
||||
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; ///< 覆盖的主字体
|
||||
};
|
||||
|
||||
@@ -2,13 +2,27 @@
|
||||
#include "font/font_system.h"
|
||||
#include "window/mwindow.h"
|
||||
|
||||
// ========== 生命周期函数 ==========
|
||||
|
||||
/**
|
||||
* @brief 初始化可编辑文本框组件
|
||||
*
|
||||
* 设置组件为可见状态,并启用焦点功能
|
||||
*/
|
||||
void meditable_text_box::init() {
|
||||
mleaf_widget<editable_text_box_args>::init();
|
||||
set_visibility(visibility_t::visible);
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置组件参数
|
||||
*
|
||||
* 从参数中提取文本、字体、字体大小等信息,并初始化布局
|
||||
* @param in_args 组件参数,包含文本、字体、字体大小、行间距、是否换行等设置
|
||||
*/
|
||||
void meditable_text_box::setup_widget(const editable_text_box_args& in_args) {
|
||||
// 提取参数
|
||||
text_ = in_args.text();
|
||||
font_ = in_args.font();
|
||||
font_size_ = in_args.font_size();
|
||||
@@ -16,14 +30,25 @@ void meditable_text_box::setup_widget(const editable_text_box_args& in_args) {
|
||||
warp_text_ = in_args.warp_text();
|
||||
text_changed_ = true; // 初始化时需要更新布局
|
||||
|
||||
// 设置布局参数
|
||||
layout_.set_primary_font(font_);
|
||||
layout_.set_font_size(font_size_);
|
||||
layout_.set_line_spacing(line_spacing_);
|
||||
|
||||
// 更新布局
|
||||
update_layout(geometry_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 每帧更新函数
|
||||
*
|
||||
* 处理光标闪烁动画,当组件获得焦点时,光标会以一定频率闪烁
|
||||
* @param in_delta 距离上一帧的时间间隔
|
||||
*/
|
||||
void meditable_text_box::on_tick(const duration_type& in_delta) {
|
||||
mleaf_widget<editable_text_box_args>::on_tick(in_delta);
|
||||
|
||||
// 只有在获得焦点时才更新光标闪烁
|
||||
if (is_focus()) {
|
||||
const std::chrono::duration<float> s(in_delta);
|
||||
cursor_alpha_ = cursor_alpha_ + s.count() * 2.f;
|
||||
@@ -34,60 +59,141 @@ void meditable_text_box::on_tick(const duration_type& in_delta) {
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 渲染函数 ==========
|
||||
|
||||
/**
|
||||
* @brief 绘制组件
|
||||
*
|
||||
* 绘制背景、文本内容和光标
|
||||
* @param in_context 绘制上下文,提供绘制接口和几何信息
|
||||
*/
|
||||
void meditable_text_box::on_paint(mirage_paint_context& in_context) {
|
||||
const auto round = get_round();
|
||||
// 绘制底
|
||||
|
||||
// 绘制圆角矩形背景
|
||||
in_context.drawer().make_rounded_rect(
|
||||
{0, 0},
|
||||
in_context.geo().get_local_size(),
|
||||
in_context.geo(),
|
||||
draw_effect::none,
|
||||
{ { 0.1f, 0.1f, 0.1f, 1.f } },
|
||||
{ { 0.1f, 0.1f, 0.1f, 1.f } }, // 深灰色背景
|
||||
{ round }
|
||||
);
|
||||
|
||||
// 绘制文本
|
||||
// 绘制文本内容
|
||||
in_context.drawer().make_text(
|
||||
layout_,
|
||||
{ round, round },
|
||||
{ round, round }, // 考虑圆角的内边距
|
||||
in_context.geo().get_local_size(),
|
||||
in_context.geo()
|
||||
);
|
||||
|
||||
// 绘制光标(仅在获得焦点时)
|
||||
if (is_focus()) {
|
||||
const auto& last_line = layout_.get_last_line();
|
||||
const float line_height = last_line.get_line_height();
|
||||
|
||||
const auto& cursor_pos = layout_.get_glyph_pos(cursor_y_, cursor_x_, true);
|
||||
|
||||
// 绘制光标
|
||||
// 绘制白色闪烁光标
|
||||
in_context.drawer().make_rounded_rect(
|
||||
cursor_pos + Eigen::Vector2f(round, round),
|
||||
Eigen::Vector2f(2, line_height),
|
||||
Eigen::Vector2f(2, line_height), // 光标宽度为2像素
|
||||
in_context.geo(),
|
||||
draw_effect::none,
|
||||
{ { 1.f, 1.f, 1.f, cursor_alpha_ } },
|
||||
{ { 1.f, 1.f, 1.f, cursor_alpha_ } }, // 白色,透明度用于闪烁效果
|
||||
{0}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 布局函数 ==========
|
||||
|
||||
/**
|
||||
* @brief 计算组件的期望尺寸
|
||||
*
|
||||
* 根据文本内容和是否换行来计算组件需要的尺寸
|
||||
* @param in_layout_scale_multiplier 布局缩放倍数
|
||||
* @return 期望的尺寸,包含圆角边距
|
||||
*/
|
||||
auto meditable_text_box::compute_desired_size(float in_layout_scale_multiplier) const -> Eigen::Vector2f {
|
||||
return no_warp_size_
|
||||
.cwiseMax(layout_.total_size)
|
||||
.cwiseMax(Eigen::Vector2f(0, layout_.get_empty_line_height()))
|
||||
+ Eigen::Vector2f(get_round() * 2, get_round() * 2);
|
||||
.cwiseMax(layout_.total_size) // 取不换行尺寸和实际布局尺寸的最大值
|
||||
.cwiseMax(Eigen::Vector2f(0, layout_.get_empty_line_height())) // 确保至少有一行高度
|
||||
+ Eigen::Vector2f(get_round() * 2, get_round() * 2); // 加上圆角边距
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 排列子组件
|
||||
*
|
||||
* 当组件大小发生变化时,重新计算文本布局
|
||||
* @param in_allotted_geometry 分配给组件的几何区域
|
||||
*/
|
||||
void meditable_text_box::arrange_children(const geometry_t& in_allotted_geometry) {
|
||||
mleaf_widget<editable_text_box_args>::arrange_children(in_allotted_geometry);
|
||||
update_layout(in_allotted_geometry, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新文本布局
|
||||
*
|
||||
* 根据当前文本内容和组件尺寸重新计算文本布局
|
||||
* @param in_geometry 组件的几何信息
|
||||
* @param in_layout_validate 是否触发布局验证
|
||||
*/
|
||||
void meditable_text_box::update_layout(const geometry_t& in_geometry, bool in_layout_validate) {
|
||||
if (!text_changed_)
|
||||
return;
|
||||
|
||||
text_changed_ = false;
|
||||
|
||||
// 处理自动换行
|
||||
float max_width = 0;
|
||||
if (warp_text_) {
|
||||
const auto current_width = in_geometry.get_local_size().x() - get_round() * 2;
|
||||
if (current_width == layout_.total_size.x()) {
|
||||
return;
|
||||
}
|
||||
max_width = current_width;
|
||||
invalidate(invalidate_reason::layout);
|
||||
}
|
||||
|
||||
// 更新布局
|
||||
layout_.set_font_size(font_size_ * get_dpi_scale());
|
||||
layout_.clear();
|
||||
layout_.push_str(temp_text_);
|
||||
|
||||
// 计算不换行时的尺寸
|
||||
text_layout_t temp_layout{};
|
||||
temp_layout.set_font_size(font_size_ * get_dpi_scale());
|
||||
temp_layout.set_primary_font(font_);
|
||||
temp_layout.set_line_spacing(line_spacing_);
|
||||
temp_layout.set_max_width(max_width);
|
||||
temp_layout.push_str(temp_text_);
|
||||
no_warp_size_ = temp_layout.total_size;
|
||||
|
||||
if (in_layout_validate) {
|
||||
invalidate(invalidate_reason::layout);
|
||||
}
|
||||
|
||||
// 更新输入法位置
|
||||
if (is_focus())
|
||||
setup_ime_pos();
|
||||
}
|
||||
|
||||
// ========== 鼠标输入处理 ==========
|
||||
|
||||
/**
|
||||
* @brief 处理鼠标按下事件
|
||||
*
|
||||
* 根据鼠标点击位置设置光标位置
|
||||
* @param in_position 鼠标点击的位置(相对于组件)
|
||||
* @param in_button 鼠标按键
|
||||
* @return 命中测试结果,表示事件已处理
|
||||
*/
|
||||
hit_test_handle meditable_text_box::on_mouse_button_down(const Eigen::Vector2f& in_position, mouse_button in_button) {
|
||||
set_focus();
|
||||
|
||||
// 将鼠标位置转换为文本坐标
|
||||
// 将鼠标位置转换为文本坐标(减去圆角边距)
|
||||
const Eigen::Vector2f& text_pos = in_position - Eigen::Vector2f(get_round(), get_round());
|
||||
|
||||
// 找到最近的字符位置
|
||||
@@ -109,20 +215,23 @@ hit_test_handle meditable_text_box::on_mouse_button_down(const Eigen::Vector2f&
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果点击位置在行的左侧,则直接返回
|
||||
// 如果点击位置在行的左侧,则直接返回行开头
|
||||
if (text_pos.x() <= 0)
|
||||
break;
|
||||
|
||||
float prev_x = 0; // 上一个字符的x位置, 因为字符之间可能有间距,所以需要记录上一个字符的位置
|
||||
// 遍历字符找到最近的位置
|
||||
float prev_x = 0; // 上一个字符的x位置
|
||||
for (int32_t j = 0; j < line->get_glyph_count(); ++j) {
|
||||
const auto glyph = layout_.get_glyph(i, j);
|
||||
if (!glyph)
|
||||
continue;
|
||||
const auto g_width = glyph->size.x();
|
||||
|
||||
const auto g_width = glyph->size.x();
|
||||
const auto g_height = glyph->size.y();
|
||||
|
||||
// 计算字符的边界框
|
||||
const auto x_left = prev_x;
|
||||
const auto x_right = glyph->position.x() + g_width * 0.5f; // 使用字符宽度的一半作为范围
|
||||
const auto x_right = glyph->position.x() + g_width * 0.5f; // 使用字符宽度的一半作为判断边界
|
||||
const auto y_top = line->position_y;
|
||||
const auto y_bottom = glyph->position.y() + g_height;
|
||||
prev_x = x_right; // 更新上一个字符位置
|
||||
@@ -131,6 +240,7 @@ hit_test_handle meditable_text_box::on_mouse_button_down(const Eigen::Vector2f&
|
||||
Eigen::Vector2f(x_left, y_top),
|
||||
Eigen::Vector2f(x_right, y_bottom)
|
||||
);
|
||||
|
||||
// 检查鼠标位置是否在字符范围内
|
||||
if (box.contains(text_pos)) {
|
||||
char_index = j;
|
||||
@@ -142,6 +252,7 @@ hit_test_handle meditable_text_box::on_mouse_button_down(const Eigen::Vector2f&
|
||||
}
|
||||
}
|
||||
|
||||
// 更新光标位置
|
||||
cursor_y_ = line_index;
|
||||
cursor_x_ = char_index;
|
||||
validate_cursor_position();
|
||||
@@ -149,6 +260,15 @@ hit_test_handle meditable_text_box::on_mouse_button_down(const Eigen::Vector2f&
|
||||
return hit_test_handle::handled();
|
||||
}
|
||||
|
||||
// ========== 键盘输入处理 ==========
|
||||
|
||||
/**
|
||||
* @brief 处理键盘按下事件
|
||||
*
|
||||
* 处理方向键、Home/End键、删除键等键盘输入
|
||||
* @param in_key 按键代码
|
||||
* @param in_action 按键动作
|
||||
*/
|
||||
void meditable_text_box::on_key_down(key_code in_key, key_action in_action) {
|
||||
// 调用基类处理
|
||||
mleaf_widget<editable_text_box_args>::on_key_down(in_key, in_action);
|
||||
@@ -164,11 +284,12 @@ void meditable_text_box::on_key_down(key_code in_key, key_action in_action) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前行的字符数,处理空行情况
|
||||
// 获取当前行的字符数
|
||||
const auto current_line_length = current_line->get_glyph_count();
|
||||
|
||||
switch (in_key) {
|
||||
case key_code::left: {
|
||||
// 左方向键:向左移动光标
|
||||
if (cursor_x_ > 0) {
|
||||
// 在当前行内向左移动
|
||||
cursor_x_--;
|
||||
@@ -183,6 +304,7 @@ void meditable_text_box::on_key_down(key_code in_key, key_action in_action) {
|
||||
}
|
||||
|
||||
case key_code::right: {
|
||||
// 右方向键:向右移动光标
|
||||
if (cursor_x_ < current_line_length) {
|
||||
// 在当前行内向右移动
|
||||
cursor_x_++;
|
||||
@@ -195,49 +317,51 @@ void meditable_text_box::on_key_down(key_code in_key, key_action in_action) {
|
||||
}
|
||||
|
||||
case key_code::up: {
|
||||
// 上方向键:向上移动一行
|
||||
if (cursor_y_ > 0) {
|
||||
cursor_y_--;
|
||||
if (const auto prev_line = layout_.get_line(cursor_y_)) {
|
||||
// 保持x位置,但不超过新行的长度
|
||||
const auto prev_line_length = prev_line->get_glyph_count();
|
||||
cursor_x_ = std::min(cursor_x_, prev_line_length);
|
||||
cursor_x_ = std::min(cursor_x_, prev_line_length);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case key_code::down: {
|
||||
// 下方向键:向下移动一行
|
||||
if (cursor_y_ < layout_.size() - 1) {
|
||||
cursor_y_++;
|
||||
if (const auto next_line = layout_.get_line(cursor_y_)) {
|
||||
// 保持x位置,但不超过新行的长度
|
||||
const auto next_line_length = next_line->get_glyph_count();
|
||||
cursor_x_ = std::min(cursor_x_, next_line_length);
|
||||
cursor_x_ = std::min(cursor_x_, next_line_length);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case key_code::home: {
|
||||
// 移动到行首
|
||||
// Home键:移动到行首
|
||||
cursor_x_ = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case key_code::end: {
|
||||
// 移动到行尾
|
||||
// End键:移动到行尾
|
||||
cursor_x_ = current_line_length;
|
||||
break;
|
||||
}
|
||||
|
||||
case key_code::backspace: {
|
||||
// 删除光标前的字符
|
||||
// 退格键:删除光标前的字符
|
||||
delete_char_before_cursor();
|
||||
break;
|
||||
}
|
||||
|
||||
case key_code::delete_key: {
|
||||
// 删除光标后的字符
|
||||
// Delete键:删除光标后的字符
|
||||
delete_char_at_cursor();
|
||||
break;
|
||||
}
|
||||
@@ -256,6 +380,14 @@ void meditable_text_box::on_key_down(key_code in_key, key_action in_action) {
|
||||
}
|
||||
}
|
||||
|
||||
// ========== IME输入法处理 ==========
|
||||
|
||||
/**
|
||||
* @brief 处理输入法字符输入
|
||||
*
|
||||
* 在光标位置插入字符,并更新光标位置
|
||||
* @param c 输入的Unicode字符
|
||||
*/
|
||||
void meditable_text_box::process_ime_char(char32_t c) {
|
||||
// 在光标位置插入字符
|
||||
const auto insert_pos = get_text_index_from_cursor();
|
||||
@@ -278,11 +410,17 @@ void meditable_text_box::process_ime_char(char32_t c) {
|
||||
validate_cursor_position();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置输入法组合文本
|
||||
*
|
||||
* 显示输入法正在组合的文本(如中文输入时的拼音)
|
||||
* @param text 组合中的文本
|
||||
*/
|
||||
void meditable_text_box::set_ime_composition(const std::u32string& text) {
|
||||
text_changed_ = true;
|
||||
|
||||
const auto insert_pos = get_text_index_from_cursor();
|
||||
// 更新组合文本长度
|
||||
// 更新组合文本
|
||||
temp_text_ = text_;
|
||||
temp_text_.insert(insert_pos, text);
|
||||
|
||||
@@ -290,6 +428,11 @@ void meditable_text_box::set_ime_composition(const std::u32string& text) {
|
||||
update_layout(geometry_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 结束输入法组合
|
||||
*
|
||||
* 清除组合状态,恢复正常显示
|
||||
*/
|
||||
void meditable_text_box::end_ime_composition() {
|
||||
text_changed_ = true;
|
||||
// 清除组合状态
|
||||
@@ -297,59 +440,51 @@ void meditable_text_box::end_ime_composition() {
|
||||
update_layout(geometry_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置输入法位置
|
||||
*
|
||||
* 将光标位置转换为窗口坐标,通知输入法在正确位置显示候选框
|
||||
*/
|
||||
void meditable_text_box::setup_ime_pos() const {
|
||||
// 获取光标在文本布局中的位置
|
||||
const auto& in_local_pos = layout_.get_glyph_pos(cursor_y_, cursor_x_, true);
|
||||
// 转换为窗口坐标(加上组件位置和圆角边距)
|
||||
const auto& in_window_pos = geometry_.get_window_position() + in_local_pos + Eigen::Vector2f(get_round(), get_round());
|
||||
const auto& in_pos = in_window_pos.cast<int32_t>();
|
||||
|
||||
// 设置输入法位置
|
||||
ime::get().set_cursor_pos(in_pos);
|
||||
ime::get().update_cursor_pos(get_window()->get_platform_handle());
|
||||
}
|
||||
|
||||
// ========== 焦点处理 ==========
|
||||
|
||||
/**
|
||||
* @brief 获得焦点时的处理
|
||||
*
|
||||
* 注册输入法处理器,并设置输入法位置
|
||||
*/
|
||||
void meditable_text_box::on_got_focus() {
|
||||
ime::get().register_processor(cast());
|
||||
setup_ime_pos();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 失去焦点时的处理
|
||||
*
|
||||
* 注销输入法处理器
|
||||
*/
|
||||
void meditable_text_box::on_lost_focus() {
|
||||
ime::get().unregister_processor(cast());
|
||||
}
|
||||
|
||||
void meditable_text_box::update_layout(const geometry_t& in_geometry, bool in_layout_validate) {
|
||||
if (!text_changed_)
|
||||
return;
|
||||
|
||||
text_changed_ = false;
|
||||
|
||||
float max_width = 0;
|
||||
if (warp_text_) {
|
||||
const auto current_width = in_geometry.get_local_size().x() - get_round() * 2;
|
||||
if (current_width == layout_.total_size.x()) {
|
||||
return;
|
||||
}
|
||||
max_width = current_width;
|
||||
invalidate(invalidate_reason::layout);
|
||||
}
|
||||
|
||||
layout_.set_font_size(font_size_ * get_dpi_scale());
|
||||
layout_.clear();
|
||||
layout_.push_str(temp_text_);
|
||||
|
||||
text_layout_t temp_layout{};
|
||||
temp_layout.set_font_size(font_size_ * get_dpi_scale());
|
||||
temp_layout.set_primary_font(font_);
|
||||
temp_layout.set_line_spacing(line_spacing_);
|
||||
temp_layout.set_max_width(max_width);
|
||||
temp_layout.push_str(temp_text_);
|
||||
no_warp_size_ = temp_layout.total_size;
|
||||
|
||||
if (in_layout_validate) {
|
||||
invalidate(invalidate_reason::layout);
|
||||
}
|
||||
|
||||
if (is_focus())
|
||||
setup_ime_pos();
|
||||
}
|
||||
|
||||
void meditable_text_box::setup_ime_pos() const {
|
||||
const auto& in_local_pos = layout_.get_glyph_pos(cursor_y_, cursor_x_, true);
|
||||
const auto& in_window_pos = geometry_.get_window_position() + in_local_pos + Eigen::Vector2f(get_round(), get_round());
|
||||
const auto& in_pos = in_window_pos.cast<int32_t>();
|
||||
ime::get().set_cursor_pos(in_pos);
|
||||
ime::get().update_cursor_pos(get_window()->get_platform_handle());
|
||||
}
|
||||
// ========== 私有辅助函数 ==========
|
||||
|
||||
/**
|
||||
* @brief 验证光标位置的有效性
|
||||
*
|
||||
* 确保光标的行号和列号都在有效范围内
|
||||
*/
|
||||
void meditable_text_box::validate_cursor_position() {
|
||||
// 确保y坐标在有效范围内
|
||||
cursor_y_ = std::clamp(cursor_y_, 0zu, std::max(0zu, layout_.size() - 1));
|
||||
@@ -357,12 +492,18 @@ void meditable_text_box::validate_cursor_position() {
|
||||
// 确保x坐标在有效范围内
|
||||
if (const auto line = layout_.get_line(cursor_y_)) {
|
||||
const auto line_length = line->get_glyph_count();
|
||||
cursor_x_ = std::clamp(cursor_x_, 0zu, line_length);
|
||||
cursor_x_ = std::clamp(cursor_x_, 0zu, line_length);
|
||||
} else {
|
||||
cursor_x_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据光标位置获取文本索引
|
||||
*
|
||||
* 将二维的光标位置(行号+列号)转换为一维的文本字符索引
|
||||
* @return 光标位置对应的文本字符索引
|
||||
*/
|
||||
size_t meditable_text_box::get_text_index_from_cursor() const {
|
||||
size_t index = 0;
|
||||
|
||||
@@ -383,17 +524,24 @@ size_t meditable_text_box::get_text_index_from_cursor() const {
|
||||
return std::min(index, text_.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 删除光标前的字符
|
||||
*
|
||||
* 实现退格键功能,删除光标前一个字符并更新光标位置
|
||||
*/
|
||||
void meditable_text_box::delete_char_before_cursor() {
|
||||
if (text_.empty()) return;
|
||||
|
||||
const auto delete_pos = get_text_index_from_cursor();
|
||||
|
||||
if (delete_pos > 0) {
|
||||
// 删除字符
|
||||
text_.erase(delete_pos - 1, 1);
|
||||
temp_text_ = text_;
|
||||
|
||||
// 更新光标位置
|
||||
if (cursor_x_ > 0) {
|
||||
// 在当前行内向左移动
|
||||
cursor_x_--;
|
||||
} else if (cursor_y_ > 0) {
|
||||
// 需要移到上一行末尾
|
||||
@@ -408,16 +556,22 @@ void meditable_text_box::delete_char_before_cursor() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 删除光标处的字符
|
||||
*
|
||||
* 实现Delete键功能,删除光标后一个字符
|
||||
*/
|
||||
void meditable_text_box::delete_char_at_cursor() {
|
||||
if (text_.empty()) return;
|
||||
|
||||
const auto delete_pos = get_text_index_from_cursor();
|
||||
|
||||
if (delete_pos < text_.size()) {
|
||||
// 删除字符
|
||||
text_.erase(delete_pos, 1);
|
||||
temp_text_ = text_;
|
||||
|
||||
text_changed_ = true;
|
||||
update_layout(geometry_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,59 +5,178 @@
|
||||
|
||||
class font_face_interface;
|
||||
|
||||
/**
|
||||
* @brief 可编辑文本框的参数结构体
|
||||
*/
|
||||
struct editable_text_box_args {
|
||||
WARG(std::u32string, text, {})
|
||||
WARG(font_face_ptr, font, {})
|
||||
WARG(float, font_size, 24.f)
|
||||
WARG(float, line_spacing, 1.2f)
|
||||
WARG(bool, warp_text, false)
|
||||
WARG(std::u32string, text, {}) // 初始文本内容
|
||||
WARG(font_face_ptr, font, {}) // 字体
|
||||
WARG(float, font_size, 24.f) // 字体大小
|
||||
WARG(float, line_spacing, 1.2f) // 行间距倍数
|
||||
WARG(bool, warp_text, false) // 是否自动换行
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 可编辑文本框组件
|
||||
*
|
||||
* 继承自 mleaf_widget 提供基础组件功能
|
||||
* 继承自 ime_processor 提供输入法处理能力
|
||||
*/
|
||||
class meditable_text_box : public mleaf_widget<editable_text_box_args>, public ime_processor {
|
||||
public:
|
||||
virtual void init() override;
|
||||
virtual void setup_widget(const editable_text_box_args& in_args) override;
|
||||
virtual void on_tick(const duration_type& in_delta) override;
|
||||
virtual void on_paint(mirage_paint_context& in_context) override;
|
||||
virtual auto compute_desired_size(float in_layout_scale_multiplier) const -> Eigen::Vector2f override;
|
||||
virtual void arrange_children(const geometry_t& in_allotted_geometry) override;
|
||||
// ========== 生命周期函数 ==========
|
||||
/**
|
||||
* @brief 初始化组件
|
||||
*/
|
||||
virtual void init() override;
|
||||
|
||||
virtual hit_test_handle on_mouse_button_down(const Eigen::Vector2f& in_position, mouse_button in_button) override;
|
||||
virtual void on_key_down(key_code in_key, key_action in_action) override;
|
||||
/**
|
||||
* @brief 设置组件参数
|
||||
* @param in_args 组件参数
|
||||
*/
|
||||
virtual void setup_widget(const editable_text_box_args& in_args) override;
|
||||
|
||||
const auto& get_text() const { return text_; }
|
||||
/**
|
||||
* @brief 每帧更新
|
||||
* @param in_delta 时间间隔
|
||||
*/
|
||||
virtual void on_tick(const duration_type& in_delta) override;
|
||||
|
||||
virtual void process_ime_char(char32_t c) override;
|
||||
virtual void set_ime_composition(const std::u32string& text) override;
|
||||
virtual void end_ime_composition() override;
|
||||
// ========== 渲染相关函数 ==========
|
||||
/**
|
||||
* @brief 绘制组件
|
||||
* @param in_context 绘制上下文
|
||||
*/
|
||||
virtual void on_paint(mirage_paint_context& in_context) override;
|
||||
|
||||
virtual void on_got_focus() override;
|
||||
virtual void on_lost_focus() override;
|
||||
// ========== 布局相关函数 ==========
|
||||
/**
|
||||
* @brief 计算期望尺寸
|
||||
* @param in_layout_scale_multiplier 布局缩放倍数
|
||||
* @return 期望的尺寸
|
||||
*/
|
||||
virtual auto compute_desired_size(float in_layout_scale_multiplier) const -> Eigen::Vector2f override;
|
||||
|
||||
/**
|
||||
* @brief 排列子组件
|
||||
* @param in_allotted_geometry 分配的几何区域
|
||||
*/
|
||||
virtual void arrange_children(const geometry_t& in_allotted_geometry) override;
|
||||
|
||||
/**
|
||||
* @brief 更新文本布局
|
||||
* @param in_geometry 几何区域
|
||||
* @param in_layout_validate 是否验证布局
|
||||
*/
|
||||
void update_layout(const geometry_t& in_geometry, bool in_layout_validate = true);
|
||||
|
||||
// ========== 输入处理函数 ==========
|
||||
/**
|
||||
* @brief 处理鼠标按下事件
|
||||
* @param in_position 鼠标位置
|
||||
* @param in_button 鼠标按键
|
||||
* @return 命中测试句柄
|
||||
*/
|
||||
virtual hit_test_handle on_mouse_button_down(const Eigen::Vector2f& in_position, mouse_button in_button) override;
|
||||
|
||||
/**
|
||||
* @brief 处理键盘按下事件
|
||||
* @param in_key 按键代码
|
||||
* @param in_action 按键动作
|
||||
*/
|
||||
virtual void on_key_down(key_code in_key, key_action in_action) override;
|
||||
|
||||
// ========== IME输入法处理函数 ==========
|
||||
/**
|
||||
* @brief 处理输入法字符输入
|
||||
* @param c 输入的字符
|
||||
*/
|
||||
virtual void process_ime_char(char32_t c) override;
|
||||
|
||||
/**
|
||||
* @brief 设置输入法组合文本
|
||||
* @param text 组合文本
|
||||
*/
|
||||
virtual void set_ime_composition(const std::u32string& text) override;
|
||||
|
||||
/**
|
||||
* @brief 结束输入法组合
|
||||
*/
|
||||
virtual void end_ime_composition() override;
|
||||
|
||||
/**
|
||||
* @brief 设置输入法位置
|
||||
*/
|
||||
void setup_ime_pos() const;
|
||||
|
||||
// ========== 焦点处理函数 ==========
|
||||
/**
|
||||
* @brief 获得焦点时的处理
|
||||
*/
|
||||
virtual void on_got_focus() override;
|
||||
|
||||
/**
|
||||
* @brief 失去焦点时的处理
|
||||
*/
|
||||
virtual void on_lost_focus() override;
|
||||
|
||||
// ========== 公共接口函数 ==========
|
||||
/**
|
||||
* @brief 获取文本内容
|
||||
* @return 当前文本内容的常量引用
|
||||
*/
|
||||
const auto& get_text() const { return text_; }
|
||||
|
||||
void update_layout(const geometry_t& in_geometry, bool in_layout_validate = true);
|
||||
void setup_ime_pos() const;
|
||||
private:
|
||||
static float get_round() {
|
||||
return mirage_style::get().get_or(5.f, "editable_text_box", "round");
|
||||
}
|
||||
void validate_cursor_position();
|
||||
size_t get_text_index_from_cursor() const;
|
||||
void delete_char_before_cursor();
|
||||
void delete_char_at_cursor();
|
||||
// ========== 私有辅助函数 ==========
|
||||
/**
|
||||
* @brief 获取圆角半径
|
||||
* @return 圆角半径值,默认为5.0f
|
||||
*/
|
||||
static float get_round() {
|
||||
return mirage_style::get().get_or(5.f, "editable_text_box", "round");
|
||||
}
|
||||
|
||||
bool warp_text_ = false;
|
||||
bool text_changed_ = false;
|
||||
float font_size_ = 0.f;
|
||||
float line_spacing_ = 1.f;
|
||||
float cursor_alpha_ = 0.f;
|
||||
Eigen::Vector2f no_warp_size_{};
|
||||
/**
|
||||
* @brief 验证光标位置的有效性
|
||||
* 确保光标位置不超出文本范围
|
||||
*/
|
||||
void validate_cursor_position();
|
||||
|
||||
// 光标位置 值是字符索引
|
||||
size_t cursor_x_ = 0; // 在哪个字符的左边
|
||||
size_t cursor_y_ = 0; // 行索引
|
||||
/**
|
||||
* @brief 根据光标位置获取文本索引
|
||||
* @return 光标所在位置对应的文本字符索引
|
||||
*/
|
||||
size_t get_text_index_from_cursor() const;
|
||||
|
||||
text_layout_t layout_{};
|
||||
std::u32string text_{}; // 最终显示的文本
|
||||
std::u32string temp_text_{}; // 当前输入的文本
|
||||
font_face_ptr font_;
|
||||
};
|
||||
/**
|
||||
* @brief 删除光标前的字符
|
||||
*/
|
||||
void delete_char_before_cursor();
|
||||
|
||||
/**
|
||||
* @brief 删除光标处的字符
|
||||
*/
|
||||
void delete_char_at_cursor();
|
||||
|
||||
// ========== 成员变量 ==========
|
||||
// 文本显示相关
|
||||
bool warp_text_ = false; // 是否自动换行
|
||||
bool text_changed_ = false; // 文本是否已改变
|
||||
float font_size_ = 0.f; // 字体大小
|
||||
float line_spacing_ = 1.f; // 行间距倍数
|
||||
font_face_ptr font_; // 字体对象
|
||||
|
||||
// 光标相关
|
||||
float cursor_alpha_ = 0.f; // 光标透明度(用于闪烁效果)
|
||||
size_t cursor_x_ = 0; // 光标X位置(字符索引,表示在哪个字符的左边)
|
||||
size_t cursor_y_ = 0; // 光标Y位置(行索引)
|
||||
|
||||
// 布局相关
|
||||
Eigen::Vector2f no_warp_size_{}; // 不换行时的尺寸
|
||||
text_layout_t layout_{}; // 文本布局信息
|
||||
|
||||
// 文本内容
|
||||
std::u32string text_{}; // 最终显示的文本
|
||||
std::u32string temp_text_{}; // 当前输入的临时文本(用于IME组合)
|
||||
};
|
||||
Reference in New Issue
Block a user