use crate::ecs::text::resources::FontAtlasData;
use crate::ecs::world::World;
pub(super) fn validate_widget_text(world: &mut World, entity: freecs::Entity, text: &str) {
let rules: Vec<crate::ecs::ui::components::ValidationRule> = world
.ui
.get_ui_node_interaction(entity)
.map(|interaction| interaction.validation_rules.clone())
.unwrap_or_default();
if rules.is_empty() {
return;
}
let mut error: Option<String> = None;
for rule in &rules {
if let Err(message) = rule.validate(text) {
error = Some(message);
break;
}
}
if let Some(interaction) = world.ui.get_ui_node_interaction_mut(entity) {
interaction.error_text = error.clone();
interaction.tooltip_text = error;
}
}
pub fn measure_text_width(atlas: &FontAtlasData, text: &str, font_size: f32) -> f32 {
let scale = font_size / atlas.font_size;
let mut width = 0.0f32;
let mut prev_char: Option<char> = None;
for character in text.chars() {
if let Some(glyph) = atlas.glyphs.get(&character) {
if let Some(previous) = prev_char
&& let Some(&kern) = atlas.kerning.get(&(previous, character))
{
width += kern * scale;
}
width += glyph.advance * scale;
}
prev_char = Some(character);
}
width
}
pub fn byte_index_at_x(atlas: &FontAtlasData, text: &str, font_size: f32, target_x: f32) -> usize {
let scale = font_size / atlas.font_size;
let mut cursor_x = 0.0f32;
let mut prev_char: Option<char> = None;
for (index, character) in text.chars().enumerate() {
if let Some(glyph) = atlas.glyphs.get(&character) {
if let Some(previous) = prev_char
&& let Some(&kern) = atlas.kerning.get(&(previous, character))
{
cursor_x += kern * scale;
}
let advance = glyph.advance * scale;
if target_x < cursor_x + advance * 0.5 {
return index;
}
cursor_x += advance;
}
prev_char = Some(character);
}
text.chars().count()
}
pub fn prev_word_boundary(text: &str, position: usize) -> usize {
let chars: Vec<char> = text.chars().collect();
if position == 0 {
return 0;
}
let mut index = position - 1;
while index > 0 && chars[index].is_whitespace() {
index -= 1;
}
while index > 0 && !chars[index - 1].is_whitespace() {
index -= 1;
}
index
}
pub fn next_word_boundary(text: &str, position: usize) -> usize {
let chars: Vec<char> = text.chars().collect();
let len = chars.len();
if position >= len {
return len;
}
let mut index = position;
while index < len && !chars[index].is_whitespace() {
index += 1;
}
while index < len && chars[index].is_whitespace() {
index += 1;
}
index
}
pub(super) fn line_col_from_char_position(text: &str, position: usize) -> (usize, usize) {
let mut line = 0;
let mut col = 0;
for (index, character) in text.chars().enumerate() {
if index == position {
return (line, col);
}
if character == '\n' {
line += 1;
col = 0;
} else {
col += 1;
}
}
(line, col)
}
pub(super) fn char_position_from_line_col(
text: &str,
target_line: usize,
target_col: usize,
) -> usize {
let mut line = 0;
let mut col = 0;
for (index, character) in text.chars().enumerate() {
if line == target_line && col == target_col {
return index;
}
if line > target_line {
return index.saturating_sub(1);
}
if character == '\n' {
if line == target_line {
return index;
}
line += 1;
col = 0;
} else {
col += 1;
}
}
text.chars().count()
}
pub(super) fn line_count(text: &str) -> usize {
text.chars().filter(|c| *c == '\n').count() + 1
}
pub(super) fn line_start_char_index(text: &str, target_line: usize) -> usize {
let mut line = 0;
for (index, character) in text.chars().enumerate() {
if line == target_line {
return index;
}
if character == '\n' {
line += 1;
}
}
text.chars().count()
}
pub(super) fn line_text(text: &str, target_line: usize) -> &str {
text.split('\n').nth(target_line).unwrap_or("")
}