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::layout_types::FlowDirection;
use crate::ecs::ui::state::{UiBase, UiFocused, UiHover, UiPressed};
use crate::ecs::ui::types::Anchor;
use crate::ecs::ui::units::{Ab, Rl};
impl<'a> UiTreeBuilder<'a> {
pub fn add_button(&mut self, text: &str) -> freecs::Entity {
let theme = self.active_theme();
let font_size = theme.font_size;
let button_height = theme.button_height;
let corner_radius = theme.corner_radius;
let border_width = theme.border_width;
let border_color = theme.border_color;
let text_slot = self.world_mut().resources.text.cache.add_text(text);
let button_entity = self
.add_node()
.flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
.with_rect(corner_radius, border_width, border_color)
.with_theme_border_color(ThemeColor::Border)
.with_theme_color::<UiBase>(ThemeColor::Background)
.with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
.with_theme_color::<UiPressed>(ThemeColor::BackgroundActive)
.with_theme_effect_role(crate::ecs::ui::components::ThemeEffect::ButtonEffect)
.with_theme_shadow_role::<UiBase>(crate::ecs::ui::components::ThemeShadow::ButtonShadow)
.with_theme_shadow_role::<UiHover>(
crate::ecs::ui::components::ThemeShadow::ButtonShadowHover,
)
.with_theme_shadow_role::<UiPressed>(
crate::ecs::ui::components::ThemeShadow::ButtonShadowPressed,
)
.with_theme_shadow_role::<UiFocused>(crate::ecs::ui::components::ThemeShadow::FocusGlow)
.with_interaction()
.with_transition::<UiHover>(8.0, 6.0)
.with_transition::<UiPressed>(12.0, 8.0)
.with_transition::<UiFocused>(8.0, 6.0)
.with_state_offset::<UiHover>(Vec2::new(0.0, -2.0))
.with_state_offset::<UiPressed>(Vec2::new(0.0, 2.0))
.with_cursor_icon(winit::window::CursorIcon::Pointer)
.with_children(|tree| {
tree.add_node()
.with_text_slot(text_slot, font_size)
.with_theme_color::<UiBase>(ThemeColor::Text)
.entity();
})
.entity();
self.world_mut().ui.set_ui_button(
button_entity,
UiButtonData {
clicked: false,
text_slot: Some(text_slot),
},
);
if let Some(interaction) = self
.world_mut()
.ui
.get_ui_node_interaction_mut(button_entity)
{
interaction.accessible_role = Some(AccessibleRole::Button);
}
self.assign_tab_index(button_entity);
button_entity
}
pub fn add_button_colored(&mut self, text: &str, color: Vec4) -> freecs::Entity {
let theme = self.active_theme();
let font_size = theme.font_size;
let button_height = theme.button_height;
let corner_radius = theme.corner_radius;
let border_width = theme.border_width;
let border_color = theme.border_color;
let hover_color = Vec4::new(
(color.x + 0.1).min(1.0),
(color.y + 0.1).min(1.0),
(color.z + 0.1).min(1.0),
color.w,
);
let active_color = Vec4::new(
(color.x - 0.1).max(0.0),
(color.y - 0.1).max(0.0),
(color.z - 0.1).max(0.0),
color.w,
);
let text_slot = self.world_mut().resources.text.cache.add_text(text);
let button_entity = self
.add_node()
.flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
.with_rect(corner_radius, border_width, border_color)
.with_theme_border_color(ThemeColor::Border)
.color_raw::<UiBase>(color)
.color_raw::<UiHover>(hover_color)
.color_raw::<UiPressed>(active_color)
.with_theme_shadow_role::<UiBase>(crate::ecs::ui::components::ThemeShadow::ButtonShadow)
.with_theme_shadow_role::<UiHover>(
crate::ecs::ui::components::ThemeShadow::ButtonShadowHover,
)
.with_theme_shadow_role::<UiPressed>(
crate::ecs::ui::components::ThemeShadow::ButtonShadowPressed,
)
.with_theme_shadow_role::<UiFocused>(crate::ecs::ui::components::ThemeShadow::FocusGlow)
.with_interaction()
.with_transition::<UiHover>(8.0, 6.0)
.with_transition::<UiPressed>(12.0, 8.0)
.with_transition::<UiFocused>(8.0, 6.0)
.with_state_offset::<UiHover>(Vec2::new(0.0, -2.0))
.with_state_offset::<UiPressed>(Vec2::new(0.0, 2.0))
.with_cursor_icon(winit::window::CursorIcon::Pointer)
.with_children(|tree| {
tree.add_node()
.with_text_slot(text_slot, font_size)
.with_theme_color::<UiBase>(ThemeColor::Text)
.entity();
})
.entity();
self.world_mut().ui.set_ui_button(
button_entity,
UiButtonData {
clicked: false,
text_slot: Some(text_slot),
},
);
if let Some(interaction) = self
.world_mut()
.ui
.get_ui_node_interaction_mut(button_entity)
{
interaction.accessible_role = Some(AccessibleRole::Button);
}
self.assign_tab_index(button_entity);
button_entity
}
pub fn add_icon_button(
&mut self,
texture_index: u32,
icon_size: Vec2,
text: &str,
) -> freecs::Entity {
let theme = self.active_theme();
let font_size = theme.font_size;
let button_height = theme.button_height;
let corner_radius = theme.corner_radius;
let border_width = theme.border_width;
let border_color = theme.border_color;
let text_slot = self.world_mut().resources.text.cache.add_text(text);
let button_entity = self
.add_node()
.flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
.with_rect(corner_radius, border_width, border_color)
.with_theme_border_color(ThemeColor::Border)
.with_theme_color::<UiBase>(ThemeColor::Background)
.with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
.with_theme_color::<UiPressed>(ThemeColor::BackgroundActive)
.with_theme_effect_role(crate::ecs::ui::components::ThemeEffect::ButtonEffect)
.with_theme_shadow_role::<UiBase>(crate::ecs::ui::components::ThemeShadow::ButtonShadow)
.with_theme_shadow_role::<UiHover>(
crate::ecs::ui::components::ThemeShadow::ButtonShadowHover,
)
.with_theme_shadow_role::<UiPressed>(
crate::ecs::ui::components::ThemeShadow::ButtonShadowPressed,
)
.with_theme_shadow_role::<UiFocused>(crate::ecs::ui::components::ThemeShadow::FocusGlow)
.with_interaction()
.with_transition::<UiHover>(8.0, 6.0)
.with_transition::<UiPressed>(12.0, 8.0)
.with_transition::<UiFocused>(8.0, 6.0)
.with_state_offset::<UiHover>(Vec2::new(0.0, -2.0))
.with_state_offset::<UiPressed>(Vec2::new(0.0, 2.0))
.with_cursor_icon(winit::window::CursorIcon::Pointer)
.flow(FlowDirection::Horizontal, 8.0, 8.0)
.with_children(|tree| {
tree.add_node()
.flow_child(Ab(icon_size))
.with_image(texture_index)
.with_theme_color::<UiBase>(ThemeColor::Text)
.entity();
tree.add_node()
.flow_child(Ab(Vec2::new(0.0, button_height)))
.flex_grow(1.0)
.with_text_slot(text_slot, font_size)
.with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
.with_theme_color::<UiBase>(ThemeColor::Text)
.entity();
})
.entity();
self.world_mut().ui.set_ui_button(
button_entity,
UiButtonData {
clicked: false,
text_slot: Some(text_slot),
},
);
if let Some(interaction) = self
.world_mut()
.ui
.get_ui_node_interaction_mut(button_entity)
{
interaction.accessible_role = Some(AccessibleRole::Button);
}
self.assign_tab_index(button_entity);
button_entity
}
pub fn add_button_rich(&mut self, spans: &[TextSpan]) -> freecs::Entity {
let theme = self.active_theme();
let font_size = theme.font_size;
let button_height = theme.button_height;
let corner_radius = theme.corner_radius;
let border_width = theme.border_width;
let border_color = theme.border_color;
let text_color = theme.text_color;
let button_entity = self
.add_node()
.flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
.with_rect(corner_radius, border_width, border_color)
.with_theme_border_color(ThemeColor::Border)
.with_theme_color::<UiBase>(ThemeColor::Background)
.with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
.with_theme_color::<UiPressed>(ThemeColor::BackgroundActive)
.with_theme_effect_role(crate::ecs::ui::components::ThemeEffect::ButtonEffect)
.with_theme_shadow_role::<UiBase>(crate::ecs::ui::components::ThemeShadow::ButtonShadow)
.with_theme_shadow_role::<UiHover>(
crate::ecs::ui::components::ThemeShadow::ButtonShadowHover,
)
.with_theme_shadow_role::<UiPressed>(
crate::ecs::ui::components::ThemeShadow::ButtonShadowPressed,
)
.with_theme_shadow_role::<UiFocused>(crate::ecs::ui::components::ThemeShadow::FocusGlow)
.with_interaction()
.with_transition::<UiHover>(8.0, 6.0)
.with_transition::<UiPressed>(12.0, 8.0)
.with_transition::<UiFocused>(8.0, 6.0)
.with_state_offset::<UiHover>(Vec2::new(0.0, -2.0))
.with_state_offset::<UiPressed>(Vec2::new(0.0, 2.0))
.with_cursor_icon(winit::window::CursorIcon::Pointer)
.entity();
self.push_parent(button_entity);
let span_container = self
.add_node()
.flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
.flow(FlowDirection::Horizontal, 0.0, 8.0)
.flow_wrap()
.entity();
self.push_parent(span_container);
for span in spans {
self.build_text_span(span, font_size, text_color, Some(button_height), false);
}
self.pop_parent();
self.pop_parent();
self.world_mut().ui.set_ui_button(
button_entity,
UiButtonData {
clicked: false,
text_slot: None,
},
);
if let Some(interaction) = self
.world_mut()
.ui
.get_ui_node_interaction_mut(button_entity)
{
interaction.accessible_role = Some(AccessibleRole::Button);
}
self.assign_tab_index(button_entity);
button_entity
}
pub fn add_image_node(&mut self, texture_index: u32, size: Vec2) -> freecs::Entity {
self.add_node()
.flow_child(Ab(size))
.with_image(texture_index)
.color_raw::<UiBase>(Vec4::new(1.0, 1.0, 1.0, 1.0))
.entity()
}
pub fn add_spinner(&mut self) -> freecs::Entity {
let dot_count = 8;
let size = 28.0_f32;
let radius = 9.0_f32;
let dot_size = 5.0_f32;
let center = size * 0.5;
let entity = self
.add_node()
.flow_child(Ab(Vec2::new(size, size)))
.entity();
self.push_parent(entity);
let theme = self.active_theme();
let accent = theme.accent_color;
let mut dots = Vec::with_capacity(dot_count);
for index in 0..dot_count {
let angle = (index as f32 / dot_count as f32) * std::f32::consts::TAU
- std::f32::consts::FRAC_PI_2;
let x = center + angle.cos() * radius - dot_size * 0.5;
let y = center + angle.sin() * radius - dot_size * 0.5;
let dot = self
.add_node()
.window(
Ab(Vec2::new(x, y)),
Ab(Vec2::new(dot_size, dot_size)),
Anchor::TopLeft,
)
.with_rect(dot_size * 0.5, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
.color_raw::<UiBase>(accent)
.entity();
dots.push(dot);
}
self.pop_parent();
self.world_mut().ui.set_ui_spinner(
entity,
UiSpinnerData {
dots,
rotation_speed: 6.5,
phase: 0.0,
},
);
entity
}
pub fn add_selectable_label(&mut self, text: &str, group_id: Option<u32>) -> freecs::Entity {
let theme = self.active_theme();
let font_size = theme.font_size;
let corner_radius = theme.corner_radius;
let text_slot = self.world_mut().resources.text.cache.add_text(text);
let label_entity = self
.add_node()
.flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, font_size * 1.5)))
.with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
.color_raw::<UiBase>(Vec4::new(0.0, 0.0, 0.0, 0.0))
.with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
.with_theme_color::<UiPressed>(ThemeColor::BackgroundActive)
.with_interaction()
.with_transition::<UiHover>(10.0, 6.0)
.with_transition::<UiPressed>(14.0, 8.0)
.with_cursor_icon(winit::window::CursorIcon::Pointer)
.with_children(|tree| {
tree.add_node()
.boundary(Ab(Vec2::new(8.0, 0.0)), Rl(Vec2::new(100.0, 100.0)))
.with_text_slot(text_slot, font_size)
.with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
.with_theme_color::<UiBase>(ThemeColor::Text)
.entity();
})
.entity();
self.world_mut().ui.set_ui_selectable_label(
label_entity,
UiSelectableLabelData {
selected: false,
changed: false,
text_slot,
group_id,
},
);
if let Some(gid) = group_id {
self.world_mut()
.resources
.retained_ui
.groups
.selectable_labels
.entry(gid)
.or_default()
.push(label_entity);
}
label_entity
}
}