use nalgebra_glm::{Vec2, Vec4};
use crate::ecs::text::components::{TextAlignment, VerticalAlignment};
use crate::ecs::ui::builder::UiTreeBuilder;
use crate::ecs::ui::components::*;
use crate::ecs::ui::state::{UiBase, UiFocused};
use crate::ecs::ui::types::Anchor;
use crate::ecs::ui::units::{Ab, Rl};
impl<'a> UiTreeBuilder<'a> {
pub fn add_text_input(&mut self, placeholder: &str) -> freecs::Entity {
self.add_text_input_inner(placeholder, "")
}
pub fn add_text_input_with_value(
&mut self,
placeholder: &str,
initial_text: &str,
) -> freecs::Entity {
self.add_text_input_inner(placeholder, initial_text)
}
pub fn add_text_input_max_length(
&mut self,
placeholder: &str,
max_length: usize,
) -> freecs::Entity {
let entity = self.add_text_input_inner(placeholder, "");
if let Some(UiWidgetState::TextInput(data)) =
self.world_mut().ui.get_ui_widget_state_mut(entity)
{
data.max_length = Some(max_length);
}
entity
}
fn add_text_input_inner(&mut self, placeholder: &str, initial_text: &str) -> freecs::Entity {
let theme = self
.world_mut()
.resources
.retained_ui
.theme_state
.active_theme();
let font_size = theme.font_size;
let accent_color = theme.accent_color;
let corner_radius = theme.corner_radius;
let border_color = theme.border_color;
let input_height = theme.button_height;
let text_slot = self.world_mut().resources.text_cache.add_text(initial_text);
let placeholder_text = placeholder.to_string();
let has_placeholder = !placeholder_text.is_empty();
let mut cursor_entity = freecs::Entity::default();
let mut selection_entity = freecs::Entity::default();
let mut placeholder_entity_out = None;
let input_entity = self
.add_node()
.flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, input_height)))
.with_rect(corner_radius, 1.0, border_color)
.with_theme_border_color(ThemeColor::Border)
.with_theme_color::<UiBase>(ThemeColor::InputBackground)
.with_theme_color::<UiFocused>(ThemeColor::InputBackgroundFocused)
.with_interaction()
.with_transition::<UiFocused>(8.0, 6.0)
.with_cursor_icon(winit::window::CursorIcon::Text)
.with_clip()
.with_children(|tree| {
selection_entity = tree
.add_node()
.window(
Ab(Vec2::new(8.0, 4.0)),
Ab(Vec2::new(0.0, input_height - 8.0)),
Anchor::TopLeft,
)
.with_rect(2.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
.with_color::<UiBase>(Vec4::new(
accent_color.x,
accent_color.y,
accent_color.z,
0.3,
))
.with_visible(false)
.without_pointer_events()
.done();
tree.add_node()
.window(
Ab(Vec2::new(8.0, 0.0)),
Ab(Vec2::new(0.0, input_height)) + Rl(Vec2::new(100.0, 0.0)),
Anchor::TopLeft,
)
.with_text_slot(text_slot, font_size)
.with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
.with_theme_color::<UiBase>(ThemeColor::Text)
.without_pointer_events()
.done();
if has_placeholder {
let placeholder_slot = tree
.world_mut()
.resources
.text_cache
.add_text(&placeholder_text);
placeholder_entity_out = Some(
tree.add_node()
.window(
Ab(Vec2::new(8.0, 0.0)),
Ab(Vec2::new(0.0, input_height)) + Rl(Vec2::new(100.0, 0.0)),
Anchor::TopLeft,
)
.with_text_slot(placeholder_slot, font_size)
.with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
.with_theme_color::<UiBase>(ThemeColor::TextDisabled)
.without_pointer_events()
.done(),
);
}
cursor_entity = tree
.add_node()
.window(
Ab(Vec2::new(8.0, 4.0)),
Ab(Vec2::new(2.0, input_height - 8.0)),
Anchor::TopLeft,
)
.with_rect(1.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
.with_theme_color::<UiBase>(ThemeColor::Text)
.with_visible(false)
.without_pointer_events()
.done();
})
.done();
self.world_mut().ui.set_ui_widget_state(
input_entity,
UiWidgetState::TextInput(UiTextInputData {
text: initial_text.to_string(),
cursor_position: initial_text.chars().count(),
selection_start: None,
changed: false,
text_slot,
cursor_entity,
selection_entity,
scroll_offset: 0.0,
cursor_blink_timer: 0.0,
placeholder_entity: placeholder_entity_out,
undo_stack: UndoStack::new(100),
input_mask: crate::ecs::ui::components::InputMask::None,
max_length: None,
}),
);
if let Some(interaction) = self
.world_mut()
.ui
.get_ui_node_interaction_mut(input_entity)
{
interaction.accessible_role = Some(AccessibleRole::TextInput);
}
if !initial_text.is_empty()
&& let Some(ph_entity) = placeholder_entity_out
&& let Some(node) = self.world_mut().ui.get_ui_layout_node_mut(ph_entity)
{
node.visible = false;
}
self.assign_tab_index(input_entity);
input_entity
}
pub fn add_text_area(&mut self, placeholder: &str, rows: usize) -> freecs::Entity {
let theme = self
.world_mut()
.resources
.retained_ui
.theme_state
.active_theme();
let font_size = theme.font_size;
let accent_color = theme.accent_color;
let corner_radius = theme.corner_radius;
let border_color = theme.border_color;
let line_height = font_size * 1.4;
let area_height = line_height * rows as f32 + 16.0;
let text_slot = self.world_mut().resources.text_cache.add_text("");
let placeholder_text = placeholder.to_string();
let has_placeholder = !placeholder_text.is_empty();
let mut cursor_entity = freecs::Entity::default();
let mut selection_pool = Vec::new();
let mut placeholder_entity_out = None;
let input_entity = self
.add_node()
.flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, area_height)))
.with_rect(corner_radius, 1.0, border_color)
.with_theme_border_color(ThemeColor::Border)
.with_theme_color::<UiBase>(ThemeColor::InputBackground)
.with_theme_color::<UiFocused>(ThemeColor::InputBackgroundFocused)
.with_interaction()
.with_transition::<UiFocused>(8.0, 6.0)
.with_cursor_icon(winit::window::CursorIcon::Text)
.with_clip()
.with_children(|tree| {
for _ in 0..rows + 1 {
let sel = tree
.add_node()
.window(
Ab(Vec2::new(8.0, 0.0)),
Ab(Vec2::new(0.0, line_height)),
Anchor::TopLeft,
)
.with_rect(2.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
.with_color::<UiBase>(Vec4::new(
accent_color.x,
accent_color.y,
accent_color.z,
0.3,
))
.with_visible(false)
.without_pointer_events()
.done();
selection_pool.push(sel);
}
tree.add_node()
.window(
Ab(Vec2::new(8.0, 8.0)),
Rl(Vec2::new(100.0, 100.0)) + Ab(Vec2::new(-16.0, 0.0)),
Anchor::TopLeft,
)
.with_text_slot(text_slot, font_size)
.with_text_alignment(TextAlignment::Left, VerticalAlignment::Top)
.with_theme_color::<UiBase>(ThemeColor::Text)
.without_pointer_events()
.done();
if has_placeholder {
let placeholder_slot = tree
.world_mut()
.resources
.text_cache
.add_text(&placeholder_text);
placeholder_entity_out = Some(
tree.add_node()
.window(
Ab(Vec2::new(8.0, 8.0)),
Rl(Vec2::new(100.0, 100.0)) + Ab(Vec2::new(-16.0, 0.0)),
Anchor::TopLeft,
)
.with_text_slot(placeholder_slot, font_size)
.with_text_alignment(TextAlignment::Left, VerticalAlignment::Top)
.with_theme_color::<UiBase>(ThemeColor::TextDisabled)
.without_pointer_events()
.done(),
);
}
cursor_entity = tree
.add_node()
.window(
Ab(Vec2::new(8.0, 8.0)),
Ab(Vec2::new(2.0, line_height)),
Anchor::TopLeft,
)
.with_rect(1.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
.with_theme_color::<UiBase>(ThemeColor::Text)
.with_visible(false)
.without_pointer_events()
.done();
})
.done();
self.world_mut().ui.set_ui_widget_state(
input_entity,
UiWidgetState::TextArea(UiTextAreaData {
text: String::new(),
cursor_position: 0,
selection_start: None,
changed: false,
text_slot,
cursor_entity,
selection_pool,
scroll_offset_y: 0.0,
cursor_blink_timer: 0.0,
placeholder_entity: placeholder_entity_out,
line_height,
visible_rows: rows,
syntax_language: None,
undo_stack: UndoStack::new(100),
max_length: None,
}),
);
if let Some(interaction) = self
.world_mut()
.ui
.get_ui_node_interaction_mut(input_entity)
{
interaction.accessible_role = Some(AccessibleRole::TextArea);
}
self.assign_tab_index(input_entity);
input_entity
}
pub fn add_text_area_with_value(
&mut self,
placeholder: &str,
rows: usize,
initial_text: &str,
) -> freecs::Entity {
let entity = self.add_text_area(placeholder, rows);
self.world_mut()
.ui_text_area_set_value(entity, initial_text);
entity
}
#[cfg(feature = "syntax_highlighting")]
pub fn add_text_area_with_syntax(
&mut self,
placeholder: &str,
rows: usize,
language: &str,
) -> freecs::Entity {
let entity = self.add_text_area(placeholder, rows);
if let Some(UiWidgetState::TextArea(data)) =
self.world_mut().ui.get_ui_widget_state_mut(entity)
{
data.syntax_language = Some(language.to_string());
}
entity
}
#[cfg(feature = "syntax_highlighting")]
pub fn add_text_area_with_syntax_and_value(
&mut self,
placeholder: &str,
rows: usize,
language: &str,
initial_text: &str,
) -> freecs::Entity {
let entity = self.add_text_area_with_syntax(placeholder, rows, language);
self.world_mut()
.ui_text_area_set_value(entity, initial_text);
entity
}
pub fn add_rich_text_editor(&mut self, placeholder: &str, rows: usize) -> freecs::Entity {
let theme = self
.world_mut()
.resources
.retained_ui
.theme_state
.active_theme();
let font_size = theme.font_size;
let accent_color = theme.accent_color;
let corner_radius = theme.corner_radius;
let border_color = theme.border_color;
let line_height = font_size * 1.4;
let area_height = line_height * rows as f32 + 16.0;
let text_slot = self.world_mut().resources.text_cache.add_text("");
let placeholder_text = placeholder.to_string();
let has_placeholder = !placeholder_text.is_empty();
let mut cursor_entity = freecs::Entity::default();
let mut selection_pool = Vec::new();
let mut placeholder_entity_out = None;
let input_entity = self
.add_node()
.flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, area_height)))
.with_rect(corner_radius, 1.0, border_color)
.with_theme_border_color(ThemeColor::Border)
.with_theme_color::<UiBase>(ThemeColor::InputBackground)
.with_theme_color::<UiFocused>(ThemeColor::InputBackgroundFocused)
.with_interaction()
.with_transition::<UiFocused>(8.0, 6.0)
.with_cursor_icon(winit::window::CursorIcon::Text)
.with_clip()
.with_children(|tree| {
for _ in 0..rows + 1 {
let sel = tree
.add_node()
.window(
Ab(Vec2::new(8.0, 0.0)),
Ab(Vec2::new(0.0, line_height)),
Anchor::TopLeft,
)
.with_rect(2.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
.with_color::<UiBase>(Vec4::new(
accent_color.x,
accent_color.y,
accent_color.z,
0.3,
))
.with_visible(false)
.without_pointer_events()
.done();
selection_pool.push(sel);
}
tree.add_node()
.window(
Ab(Vec2::new(8.0, 8.0)),
Rl(Vec2::new(100.0, 100.0)) + Ab(Vec2::new(-16.0, 0.0)),
Anchor::TopLeft,
)
.with_text_slot(text_slot, font_size)
.with_text_alignment(TextAlignment::Left, VerticalAlignment::Top)
.with_theme_color::<UiBase>(ThemeColor::Text)
.without_pointer_events()
.done();
if has_placeholder {
let placeholder_slot = tree
.world_mut()
.resources
.text_cache
.add_text(&placeholder_text);
placeholder_entity_out = Some(
tree.add_node()
.window(
Ab(Vec2::new(8.0, 8.0)),
Rl(Vec2::new(100.0, 100.0)) + Ab(Vec2::new(-16.0, 0.0)),
Anchor::TopLeft,
)
.with_text_slot(placeholder_slot, font_size)
.with_text_alignment(TextAlignment::Left, VerticalAlignment::Top)
.with_theme_color::<UiBase>(ThemeColor::TextDisabled)
.without_pointer_events()
.done(),
);
}
cursor_entity = tree
.add_node()
.window(
Ab(Vec2::new(8.0, 8.0)),
Ab(Vec2::new(2.0, line_height)),
Anchor::TopLeft,
)
.with_rect(1.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
.with_theme_color::<UiBase>(ThemeColor::Text)
.with_visible(false)
.without_pointer_events()
.done();
})
.done();
self.world_mut().ui.set_ui_widget_state(
input_entity,
UiWidgetState::RichTextEditor(UiRichTextEditorData {
text: String::new(),
char_styles: Vec::new(),
current_style: CharStyle::default(),
cursor_position: 0,
selection_start: None,
changed: false,
text_slot,
cursor_entity,
selection_pool,
scroll_offset_y: 0.0,
cursor_blink_timer: 0.0,
line_height,
visible_rows: rows,
placeholder_entity: placeholder_entity_out,
undo_stack: UndoStack::new(100),
}),
);
if let Some(interaction) = self
.world_mut()
.ui
.get_ui_node_interaction_mut(input_entity)
{
interaction.accessible_role = Some(AccessibleRole::TextArea);
}
self.assign_tab_index(input_entity);
input_entity
}
pub fn add_rich_text_editor_with_value(
&mut self,
placeholder: &str,
rows: usize,
initial_text: &str,
) -> freecs::Entity {
let entity = self.add_rich_text_editor(placeholder, rows);
self.world_mut()
.ui_rich_text_editor_set_value(entity, initial_text);
entity
}
}