use nalgebra_glm::{Vec2, Vec4};
use crate::ecs::primitives::EasingFunction;
use crate::ecs::text::components::{TextAlignment, VerticalAlignment};
use crate::ecs::ui::components::{
AutoSizeMode, DragPayload, StateTransition, ThemeColor, UiAnimationPhase, UiAnimationType,
UiDepthMode, UiDragSource, UiDropTarget, UiLayoutNode, UiLayoutRoot, UiNodeAnimation,
UiNodeColor, UiNodeContent, UiNodeInteraction, UiStateWeights, UiThemeBinding,
};
use crate::ecs::ui::layout_types::{
BoundaryLayout, FlowAlignment, FlowDirection, FlowLayout, GridLayout, ScalingMode, SolidLayout,
UiLayoutType, WindowLayout,
};
use crate::ecs::ui::state::UiStateTrait;
use crate::ecs::ui::theme::UiTheme;
use crate::ecs::ui::types::Anchor;
use crate::ecs::ui::units::UiValue;
use crate::ecs::world::World;
use crate::render::wgpu::passes::geometry::UiLayer;
use crate::prelude::*;
pub struct UiTreeBuilder<'a> {
world: &'a mut World,
root: freecs::Entity,
current_parent: freecs::Entity,
parent_stack: Vec<freecs::Entity>,
next_tab_index: i32,
}
impl<'a> UiTreeBuilder<'a> {
pub fn new(world: &'a mut World) -> Self {
let root = world.spawn();
world.core.add_components(root, crate::ecs::world::PARENT);
world
.ui
.add_components(root, crate::ecs::world::UI_LAYOUT_ROOT);
if let Some(root_comp) = world.ui.get_ui_layout_root_mut(root) {
*root_comp = UiLayoutRoot::default();
}
ui_mark_children_dirty(world);
Self {
world,
root,
current_parent: root,
parent_stack: Vec::new(),
next_tab_index: 0,
}
}
pub fn from_parent(world: &'a mut World, parent_entity: freecs::Entity) -> Self {
ui_mark_children_dirty(world);
Self {
world,
root: parent_entity,
current_parent: parent_entity,
parent_stack: Vec::new(),
next_tab_index: 0,
}
}
pub fn finish_subtree(self) {}
pub fn assign_tab_index(&mut self, entity: freecs::Entity) {
if let Some(interaction) = self.world.ui.get_ui_node_interaction_mut(entity) {
interaction.tab_index = Some(self.next_tab_index);
}
self.next_tab_index += 1;
}
pub fn with_absolute_scale(self, scale: f32) -> Self {
if let Some(root) = self.world.ui.get_ui_layout_root_mut(self.root) {
root.absolute_scale = scale;
}
self
}
pub fn with_default_font_size(self, size: f32) -> Self {
if let Some(root) = self.world.ui.get_ui_layout_root_mut(self.root) {
root.default_font_size = size;
}
self
}
pub fn root_entity(&self) -> freecs::Entity {
self.root
}
pub fn current_parent(&self) -> freecs::Entity {
self.current_parent
}
pub fn world_mut(&mut self) -> &mut World {
self.world
}
pub fn active_theme(&self) -> &UiTheme {
self.world.resources.retained_ui.theme_state.active_theme()
}
pub fn add_node(&mut self) -> UiNodeBuilder<'_, 'a> {
let entity = self.world.spawn();
self.world
.core
.add_components(entity, crate::ecs::world::PARENT);
self.world.ui.add_components(
entity,
crate::ecs::world::UI_LAYOUT_NODE
| crate::ecs::world::UI_NODE_COLOR
| crate::ecs::world::UI_NODE_CONTENT
| crate::ecs::world::UI_STATE_WEIGHTS,
);
if let Some(parent) = self.world.core.get_parent_mut(entity) {
*parent = crate::ecs::transform::components::Parent(Some(self.current_parent));
}
if let Some(node) = self.world.ui.get_ui_layout_node_mut(entity) {
*node = UiLayoutNode::default();
}
if let Some(color) = self.world.ui.get_ui_node_color_mut(entity) {
*color = UiNodeColor::default();
}
if let Some(content) = self.world.ui.get_ui_node_content_mut(entity) {
*content = UiNodeContent::default();
}
if let Some(weights) = self.world.ui.get_ui_state_weights_mut(entity) {
*weights = UiStateWeights::default();
}
self.world.resources.transform_state.children_cache_valid = false;
ui_mark_layout_dirty(self.world);
UiNodeBuilder { tree: self, entity }
}
pub fn push_parent(&mut self, entity: freecs::Entity) {
debug_assert!(
entity != self.current_parent && !self.parent_stack.contains(&entity),
"push_parent cycle detected: entity {:?} is already in the parent chain",
entity
);
self.parent_stack.push(self.current_parent);
self.current_parent = entity;
}
pub fn pop_parent(&mut self) {
if let Some(parent) = self.parent_stack.pop() {
self.current_parent = parent;
}
}
pub fn within_content(&mut self, entity: freecs::Entity, f: impl FnOnce(&mut UiTreeBuilder)) {
let content = ui_widget_content(self.world, entity).unwrap_or(entity);
self.push_parent(content);
f(self);
self.pop_parent();
}
pub fn in_parent<R>(&mut self, entity: freecs::Entity, f: impl FnOnce(&mut Self) -> R) -> R {
self.push_parent(entity);
let result = f(self);
self.pop_parent();
result
}
pub fn label_row<R>(
&mut self,
label: &str,
label_width: f32,
body: impl FnOnce(&mut Self) -> R,
) -> R {
use crate::ecs::ui::layout_types::FlowAlignment;
let row = self
.add_node()
.size(
crate::ecs::ui::units::Length::Pct(100.0),
crate::ecs::ui::units::Length::Px(32.0),
)
.flow_horizontal()
.gap(8.0)
.align_cross(FlowAlignment::Center)
.entity();
self.in_parent(row, |tree| {
tree.add_node()
.size(
crate::ecs::ui::units::Length::Px(label_width),
crate::ecs::ui::units::Length::Px(18.0),
)
.label(label, 14.0, ThemeColor::Text)
.entity();
body(tree)
})
}
pub fn children_of<'b>(&'b mut self, entity: freecs::Entity) -> ParentScope<'b, 'a> {
ParentScope::push(self, entity)
}
pub fn children_of_widget<'b>(&'b mut self, entity: freecs::Entity) -> ParentScope<'b, 'a> {
let content = ui_widget_content(self.world, entity).unwrap_or(entity);
ParentScope::push(self, content)
}
pub fn finish(self) -> freecs::Entity {
#[cfg(debug_assertions)]
self.validate_layout();
self.root
}
#[cfg(debug_assertions)]
fn validate_layout(&self) {
let children: Vec<freecs::Entity> = self
.world
.resources
.transform_state
.children_cache
.get(&self.root)
.map(|v| v.to_vec())
.unwrap_or_default();
let root_has_flow = self
.world
.ui
.get_ui_layout_node(self.root)
.is_some_and(|n| n.flow_layout.is_some());
for child in &children {
if let Some(node) = self.world.ui.get_ui_layout_node(*child)
&& !root_has_flow
&& let Some(ref child_size) = node.flow_child_size
{
let has_relative = child_size.relative.is_some()
|| child_size.relative_width.is_some()
|| child_size.relative_height.is_some();
if has_relative {
eprintln!(
"UI layout warning: entity {:?} has flow_child_size with relative units but parent {:?} has no flow layout",
child, self.root
);
}
}
}
}
}
pub struct UiNodeBuilder<'b, 'a> {
tree: &'b mut UiTreeBuilder<'a>,
entity: freecs::Entity,
}
impl<'b, 'a> UiNodeBuilder<'b, 'a> {
pub fn entity(self) -> freecs::Entity {
self.entity
}
pub fn boundary(
self,
position_1: impl Into<UiValue<Vec2>>,
position_2: impl Into<UiValue<Vec2>>,
) -> Self {
let layout = UiLayoutType::Boundary(BoundaryLayout {
position_1: position_1.into(),
position_2: position_2.into(),
});
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.base_layout = Some(layout);
}
self
}
pub fn window(
self,
position: impl Into<UiValue<Vec2>>,
size: impl Into<UiValue<Vec2>>,
anchor: Anchor,
) -> Self {
let layout = UiLayoutType::Window(WindowLayout {
position: position.into(),
size: size.into(),
anchor,
});
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.base_layout = Some(layout);
}
self
}
pub fn solid(
self,
size: impl Into<UiValue<Vec2>>,
scaling: ScalingMode,
alignment: Vec2,
) -> Self {
let layout = UiLayoutType::Solid(SolidLayout {
size: size.into(),
scaling,
alignment,
});
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.base_layout = Some(layout);
}
self
}
pub fn flow(self, direction: FlowDirection, padding: f32, spacing: f32) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.flow_layout = Some(FlowLayout {
direction,
padding,
spacing,
alignment: FlowAlignment::Start,
cross_alignment: FlowAlignment::Start,
wrap: false,
});
}
self
}
pub fn flow_with_alignment(
self,
direction: FlowDirection,
padding: f32,
spacing: f32,
alignment: FlowAlignment,
cross_alignment: FlowAlignment,
) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.flow_layout = Some(FlowLayout {
direction,
padding,
spacing,
alignment,
cross_alignment,
wrap: false,
});
}
self
}
pub fn flow_wrap(self) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity)
&& let Some(flow) = &mut node.flow_layout
{
flow.wrap = true;
}
self
}
pub fn grid(
self,
columns: usize,
row_height: f32,
padding: f32,
column_spacing: f32,
row_spacing: f32,
) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.grid_layout = Some(GridLayout {
columns,
row_height,
padding,
column_spacing,
row_spacing,
min_column_width: None,
});
}
self
}
pub fn auto_grid(self, min_column_width: f32, row_height: f32, spacing: f32) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.grid_layout = Some(GridLayout {
columns: 1,
row_height,
padding: spacing,
column_spacing: spacing,
row_spacing: spacing,
min_column_width: Some(min_column_width),
});
}
self
}
pub fn flow_child(self, size: impl Into<UiValue<Vec2>>) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.flow_child_size = Some(size.into());
}
self
}
pub fn size_px(self, width: f32, height: f32) -> Self {
self.flow_child(crate::ecs::ui::units::Ab(nalgebra_glm::vec2(width, height)))
}
pub fn fill_width(self) -> Self {
self.flow_child(crate::ecs::ui::units::Rl(nalgebra_glm::vec2(100.0, 0.0)))
}
pub fn fill_height(self) -> Self {
self.flow_child(crate::ecs::ui::units::Rl(nalgebra_glm::vec2(0.0, 100.0)))
}
pub fn fill(self) -> Self {
self.flow_child(crate::ecs::ui::units::Rl(nalgebra_glm::vec2(100.0, 100.0)))
}
pub fn auto_height(self) -> Self {
self.auto_size(AutoSizeMode::Height)
}
pub fn auto_width(self) -> Self {
self.auto_size(AutoSizeMode::Width)
}
pub fn size(
self,
width: crate::ecs::ui::units::Length,
height: crate::ecs::ui::units::Length,
) -> Self {
use crate::ecs::ui::units::UiAxis;
let mut value = UiValue::<Vec2>::default();
if !width.is_auto() {
value = value + width.into_axis(UiAxis::Horizontal);
}
if !height.is_auto() {
value = value + height.into_axis(UiAxis::Vertical);
}
let auto = match (width.is_auto(), height.is_auto()) {
(true, true) => Some(AutoSizeMode::Both),
(true, false) => Some(AutoSizeMode::Width),
(false, true) => Some(AutoSizeMode::Height),
(false, false) => None,
};
let this = self.flow_child(value);
match auto {
Some(mode) => this.auto_size(mode),
None => this,
}
}
pub fn flow_vertical(self) -> Self {
self.flow(FlowDirection::Vertical, 0.0, 0.0)
}
pub fn flow_horizontal(self) -> Self {
self.flow(FlowDirection::Horizontal, 0.0, 0.0)
}
pub fn padding(self, padding: f32) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity)
&& let Some(flow) = node.flow_layout.as_mut()
{
flow.padding = padding;
}
self
}
pub fn gap(self, spacing: f32) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity)
&& let Some(flow) = node.flow_layout.as_mut()
{
flow.spacing = spacing;
}
self
}
pub fn align_main(self, alignment: FlowAlignment) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity)
&& let Some(flow) = node.flow_layout.as_mut()
{
flow.alignment = alignment;
}
self
}
pub fn align_cross(self, alignment: FlowAlignment) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity)
&& let Some(flow) = node.flow_layout.as_mut()
{
flow.cross_alignment = alignment;
}
self
}
pub fn fg(self, role: ThemeColor) -> Self {
self.with_theme_color::<crate::ecs::ui::state::UiBase>(role)
}
pub fn bg(self, role: ThemeColor) -> Self {
self.with_theme_color::<crate::ecs::ui::state::UiBase>(role)
}
pub fn fg_hover(self, role: ThemeColor) -> Self {
self.with_theme_color::<crate::ecs::ui::state::UiHover>(role)
}
pub fn fg_pressed(self, role: ThemeColor) -> Self {
self.with_theme_color::<crate::ecs::ui::state::UiPressed>(role)
}
pub fn fg_focused(self, role: ThemeColor) -> Self {
self.with_theme_color::<crate::ecs::ui::state::UiFocused>(role)
}
pub fn label(self, text: &str, size: f32, role: ThemeColor) -> Self {
self.with_text(text, size).text_left().fg(role)
}
pub fn rect(self, corner_radius: f32) -> Self {
self.with_rect(corner_radius, 0.0, nalgebra_glm::vec4(0.0, 0.0, 0.0, 0.0))
}
pub fn window_at(
self,
position: impl Into<UiValue<Vec2>>,
size: impl Into<UiValue<Vec2>>,
) -> Self {
self.window(position, size, crate::ecs::ui::types::Anchor::TopLeft)
}
pub fn card(self) -> Self {
let theme = self
.tree
.world
.resources
.retained_ui
.theme_state
.active_theme();
let radius = theme.corner_radius;
let border_color = theme.border_color;
self.with_rect(radius, 1.0, border_color)
.with_theme_border_color(ThemeColor::Border)
.with_theme_color::<crate::ecs::ui::state::UiBase>(ThemeColor::Panel)
.with_theme_effect_role(crate::ecs::ui::components::ThemeEffect::PanelEffect)
}
pub fn named(self, name: &str) -> Self {
self.tree
.world
.resources
.retained_ui
.accessibility
.named_entities
.insert(name.to_string(), self.entity);
self
}
pub fn flex_grow(self, weight: f32) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.flex_grow = Some(weight);
}
self
}
pub fn flex_shrink(self, factor: f32) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.flex_shrink = Some(factor);
}
self
}
pub fn with_min_size(self, min: Vec2) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.min_size = Some(min);
}
self
}
pub fn with_max_size(self, max: Vec2) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.max_size = Some(max);
}
self
}
pub fn with_z_index(self, z: i32) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.z_index = Some(z);
}
self
}
pub fn auto_size(self, mode: AutoSizeMode) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.auto_size = mode;
}
self
}
pub fn auto_size_padding(self, padding: Vec2) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.auto_size_padding = padding;
}
self
}
pub fn responsive_size_at(
self,
breakpoint: crate::ecs::ui::resources::UiBreakpoint,
size: crate::ecs::ui::units::UiValue<Vec2>,
) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
let responsive = node
.responsive
.get_or_insert_with(crate::ecs::ui::components::UiResponsive::default);
responsive.override_mut(breakpoint).size = Some(size);
}
self
}
pub fn responsive_position_1_at(
self,
breakpoint: crate::ecs::ui::resources::UiBreakpoint,
position: crate::ecs::ui::units::UiValue<Vec2>,
) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
let responsive = node
.responsive
.get_or_insert_with(crate::ecs::ui::components::UiResponsive::default);
responsive.override_mut(breakpoint).position_1 = Some(position);
}
self
}
pub fn responsive_position_2_at(
self,
breakpoint: crate::ecs::ui::resources::UiBreakpoint,
position: crate::ecs::ui::units::UiValue<Vec2>,
) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
let responsive = node
.responsive
.get_or_insert_with(crate::ecs::ui::components::UiResponsive::default);
responsive.override_mut(breakpoint).position_2 = Some(position);
}
self
}
pub fn responsive_visible_at(
self,
breakpoint: crate::ecs::ui::resources::UiBreakpoint,
visible: bool,
) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
let responsive = node
.responsive
.get_or_insert_with(crate::ecs::ui::components::UiResponsive::default);
responsive.override_mut(breakpoint).visible = Some(visible);
}
self
}
pub fn with_rect(self, corner_radius: f32, border_width: f32, border_color: Vec4) -> Self {
if let Some(content) = self.tree.world.ui.get_ui_node_content_mut(self.entity) {
*content = UiNodeContent::Rect {
corner_radius,
border_width,
border_color,
};
}
self
}
pub fn with_shadow(
self,
color: Vec4,
offset: nalgebra_glm::Vec2,
blur: f32,
spread: f32,
) -> Self {
self.tree
.world
.ui
.add_components(self.entity, crate::ecs::world::UI_NODE_SHADOW);
if let Some(shadow) = self.tree.world.ui.get_ui_node_shadow_mut(self.entity) {
*shadow = crate::ecs::ui::components::UiNodeShadow {
color,
offset,
blur,
spread,
};
}
self
}
pub fn with_text(self, text: &str, font_size: f32) -> Self {
let text_slot = self.tree.world.resources.text.cache.add_text(text);
if let Some(content) = self.tree.world.ui.get_ui_node_content_mut(self.entity) {
*content = UiNodeContent::Text {
text_slot,
font_index: 0,
font_size_override: Some(font_size),
outline_color: Vec4::new(0.0, 0.0, 0.0, 0.0),
outline_width: 0.0,
alignment: TextAlignment::Center,
vertical_alignment: VerticalAlignment::Middle,
overflow: crate::ecs::ui::components::TextOverflow::default(),
monospace_width: None,
font_kind: crate::ecs::text::resources::font_engine::FontKind::Default,
};
}
self
}
pub fn with_text_slot(self, text_slot: usize, font_size: f32) -> Self {
if let Some(content) = self.tree.world.ui.get_ui_node_content_mut(self.entity) {
*content = UiNodeContent::Text {
text_slot,
font_index: 0,
font_size_override: Some(font_size),
outline_color: Vec4::new(0.0, 0.0, 0.0, 0.0),
outline_width: 0.0,
alignment: TextAlignment::Center,
vertical_alignment: VerticalAlignment::Middle,
overflow: crate::ecs::ui::components::TextOverflow::default(),
monospace_width: None,
font_kind: crate::ecs::text::resources::font_engine::FontKind::Default,
};
}
self
}
pub fn with_monospace_width(self, width: f32) -> Self {
if let Some(content) = self.tree.world.ui.get_ui_node_content_mut(self.entity)
&& let UiNodeContent::Text {
monospace_width, ..
} = content
{
*monospace_width = Some(width);
}
self
}
pub fn with_font_index(self, index: usize) -> Self {
if let Some(content) = self.tree.world.ui.get_ui_node_content_mut(self.entity)
&& let UiNodeContent::Text { font_index, .. } = content
{
*font_index = index;
}
self
}
pub fn with_icon(self, icon: crate::ecs::ui::icons::IconGlyph, font_size: f32) -> Self {
let mut buf = [0u8; 4];
let icon_str: &str = icon.codepoint.encode_utf8(&mut buf);
let text_slot = self.tree.world.resources.text.cache.add_text(icon_str);
if let Some(content) = self.tree.world.ui.get_ui_node_content_mut(self.entity) {
*content = UiNodeContent::Text {
text_slot,
font_index: 0,
font_size_override: Some(font_size),
outline_color: Vec4::new(0.0, 0.0, 0.0, 0.0),
outline_width: 0.0,
alignment: TextAlignment::Center,
vertical_alignment: VerticalAlignment::Middle,
overflow: crate::ecs::ui::components::TextOverflow::default(),
monospace_width: None,
font_kind: icon.font_kind,
};
}
self
}
pub fn text_left(self) -> Self {
self.with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
}
pub fn text_center(self) -> Self {
self.with_text_alignment(TextAlignment::Center, VerticalAlignment::Middle)
}
pub fn text_right(self) -> Self {
self.with_text_alignment(TextAlignment::Right, VerticalAlignment::Middle)
}
pub fn absolute_fill(self, inset: f32) -> Self {
self.boundary(
crate::ecs::ui::units::Ab(nalgebra_glm::vec2(inset, inset)),
crate::ecs::ui::units::Ab(nalgebra_glm::vec2(-inset, -inset))
+ crate::ecs::ui::units::Rl(nalgebra_glm::vec2(100.0, 100.0)),
)
}
pub fn with_text_alignment(
self,
text_alignment: TextAlignment,
text_vertical_alignment: VerticalAlignment,
) -> Self {
if let Some(content) = self.tree.world.ui.get_ui_node_content_mut(self.entity)
&& let UiNodeContent::Text {
alignment,
vertical_alignment,
..
} = content
{
*alignment = text_alignment;
*vertical_alignment = text_vertical_alignment;
}
self
}
pub fn with_text_outline(self, color: Vec4, width: f32) -> Self {
if let Some(content) = self.tree.world.ui.get_ui_node_content_mut(self.entity)
&& let UiNodeContent::Text {
outline_color,
outline_width,
..
} = content
{
*outline_color = color;
*outline_width = width;
}
self
}
pub fn with_text_overflow(self, mode: crate::ecs::ui::components::TextOverflow) -> Self {
if let Some(content) = self.tree.world.ui.get_ui_node_content_mut(self.entity)
&& let UiNodeContent::Text { overflow, .. } = content
{
*overflow = mode;
}
self
}
pub fn with_text_wrap(self) -> Self {
self.with_text_overflow(crate::ecs::ui::components::TextOverflow::Wrap)
}
pub fn with_image(self, texture_index: u32) -> Self {
if let Some(content) = self.tree.world.ui.get_ui_node_content_mut(self.entity) {
*content = UiNodeContent::Image {
texture_index,
uv_min: Vec2::new(0.0, 0.0),
uv_max: Vec2::new(1.0, 1.0),
};
}
self
}
pub fn with_image_uv(self, texture_index: u32, uv_min: Vec2, uv_max: Vec2) -> Self {
if let Some(content) = self.tree.world.ui.get_ui_node_content_mut(self.entity) {
*content = UiNodeContent::Image {
texture_index,
uv_min,
uv_max,
};
}
self
}
pub fn color_raw<S: UiStateTrait>(self, color: Vec4) -> Self {
if let Some(node_color) = self.tree.world.ui.get_ui_node_color_mut(self.entity) {
node_color.colors[S::INDEX] = Some(color);
}
if let Some(binding) = self.tree.world.ui.get_ui_theme_binding_mut(self.entity) {
binding.color_roles[S::INDEX] = None;
}
self
}
pub fn with_theme_color<S: UiStateTrait>(self, role: ThemeColor) -> Self {
let color = {
let theme = self
.tree
.world
.resources
.retained_ui
.theme_state
.active_theme();
role.resolve(theme)
};
if let Some(node_color) = self.tree.world.ui.get_ui_node_color_mut(self.entity) {
node_color.colors[S::INDEX] = Some(color);
}
if self
.tree
.world
.ui
.get_ui_theme_binding(self.entity)
.is_none()
{
self.tree
.world
.ui
.set_ui_theme_binding(self.entity, UiThemeBinding::default());
}
if let Some(binding) = self.tree.world.ui.get_ui_theme_binding_mut(self.entity) {
binding.color_roles[S::INDEX] = Some(role);
}
self
}
pub fn with_theme_shadow_role<S: UiStateTrait>(
self,
role: crate::ecs::ui::components::ThemeShadow,
) -> Self {
if self
.tree
.world
.ui
.get_ui_theme_binding(self.entity)
.is_none()
{
self.tree
.world
.ui
.set_ui_theme_binding(self.entity, UiThemeBinding::default());
}
if let Some(binding) = self.tree.world.ui.get_ui_theme_binding_mut(self.entity) {
binding.shadow_roles[S::INDEX] = Some(role);
}
self.tree.world.resources.retained_ui.theme_state.generation += 1;
self
}
pub fn with_theme_effect_role(self, role: crate::ecs::ui::components::ThemeEffect) -> Self {
if self
.tree
.world
.ui
.get_ui_theme_binding(self.entity)
.is_none()
{
self.tree
.world
.ui
.set_ui_theme_binding(self.entity, UiThemeBinding::default());
}
if let Some(binding) = self.tree.world.ui.get_ui_theme_binding_mut(self.entity) {
binding.effect_role = Some(role);
}
self.tree.world.resources.retained_ui.theme_state.generation += 1;
self
}
pub fn with_effect(self, effect: crate::ecs::ui::theme::RectEffect) -> Self {
let (kind, params) = effect.encode();
self.tree
.world
.ui
.add_components(self.entity, crate::ecs::world::UI_NODE_EFFECT);
if let Some(comp) = self.tree.world.ui.get_ui_node_effect_mut(self.entity) {
comp.kind = kind;
comp.params = params;
}
self
}
pub fn with_theme_border_color(self, role: ThemeColor) -> Self {
let color = {
let theme = self
.tree
.world
.resources
.retained_ui
.theme_state
.active_theme();
role.resolve(theme)
};
if let Some(content) = self.tree.world.ui.get_ui_node_content_mut(self.entity)
&& let UiNodeContent::Rect { border_color, .. } = content
{
*border_color = color;
}
if self
.tree
.world
.ui
.get_ui_theme_binding(self.entity)
.is_none()
{
self.tree
.world
.ui
.set_ui_theme_binding(self.entity, UiThemeBinding::default());
}
if let Some(binding) = self.tree.world.ui.get_ui_theme_binding_mut(self.entity) {
binding.border_color_role = Some(role);
}
self
}
pub fn with_interaction(self) -> Self {
self.tree
.world
.ui
.set_ui_node_interaction(self.entity, UiNodeInteraction::default());
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.pointer_events = true;
}
self
}
pub fn with_pointer_events(self) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.pointer_events = true;
}
self
}
pub fn with_test_id(self, id: &str) -> Self {
let this = if self
.tree
.world
.ui
.get_ui_node_interaction(self.entity)
.is_none()
{
self.with_interaction()
} else {
self
};
if let Some(interaction) = this.tree.world.ui.get_ui_node_interaction_mut(this.entity) {
interaction.test_id = Some(id.to_string());
}
this.tree
.world
.resources
.retained_ui
.accessibility
.test_id_map
.insert(id.to_string(), this.entity);
this
}
pub fn with_cursor_icon(self, icon: winit::window::CursorIcon) -> Self {
if let Some(interaction) = self.tree.world.ui.get_ui_node_interaction_mut(self.entity) {
interaction.cursor_icon = Some(icon);
}
self
}
pub fn with_tooltip(self, text: &str) -> Self {
let this = if self
.tree
.world
.ui
.get_ui_node_interaction(self.entity)
.is_none()
{
self.with_interaction()
} else {
self
};
if let Some(interaction) = this.tree.world.ui.get_ui_node_interaction_mut(this.entity) {
interaction.tooltip_text = Some(text.to_string());
}
this
}
pub fn with_tooltip_entity(self, tooltip_entity: freecs::Entity) -> Self {
let this = if self
.tree
.world
.ui
.get_ui_node_interaction(self.entity)
.is_none()
{
self.with_interaction()
} else {
self
};
if let Some(interaction) = this.tree.world.ui.get_ui_node_interaction_mut(this.entity) {
interaction.tooltip_entity = Some(tooltip_entity);
}
this
}
pub fn with_tab_index(self, index: i32) -> Self {
if let Some(interaction) = self.tree.world.ui.get_ui_node_interaction_mut(self.entity) {
interaction.tab_index = Some(index);
}
self
}
pub fn with_transition<S: UiStateTrait>(self, enter_speed: f32, exit_speed: f32) -> Self {
if let Some(weights) = self.tree.world.ui.get_ui_state_weights_mut(self.entity) {
let mut transition = StateTransition::for_state::<S>();
transition.enter_speed = enter_speed;
transition.exit_speed = exit_speed;
weights.transitions[S::INDEX] = Some(transition);
}
self
}
pub fn with_eased_transition<S: UiStateTrait>(
self,
enter_speed: f32,
exit_speed: f32,
easing: EasingFunction,
) -> Self {
if let Some(weights) = self.tree.world.ui.get_ui_state_weights_mut(self.entity) {
weights.transitions[S::INDEX] = Some(StateTransition {
enter_speed,
exit_speed,
easing,
spring: None,
});
}
self
}
pub fn with_spring_transition<S: UiStateTrait>(
self,
spring: crate::ecs::ui::components::Spring,
) -> Self {
if let Some(weights) = self.tree.world.ui.get_ui_state_weights_mut(self.entity) {
let mut transition = StateTransition::for_state::<S>();
transition.spring = Some(spring);
weights.transitions[S::INDEX] = Some(transition);
}
self
}
pub fn with_state_offset<S: UiStateTrait>(self, offset: nalgebra_glm::Vec2) -> Self {
self.tree
.world
.ui
.add_components(self.entity, crate::ecs::world::UI_NODE_TRANSFORM_STATES);
if let Some(states) = self
.tree
.world
.ui
.get_ui_node_transform_states_mut(self.entity)
{
states.offsets[S::INDEX] = Some(offset);
}
self
}
pub fn with_state_scale<S: UiStateTrait>(self, scale: f32) -> Self {
self.tree
.world
.ui
.add_components(self.entity, crate::ecs::world::UI_NODE_TRANSFORM_STATES);
if let Some(states) = self
.tree
.world
.ui
.get_ui_node_transform_states_mut(self.entity)
{
states.scales[S::INDEX] = Some(scale);
}
self
}
pub fn with_state_radius<S: UiStateTrait>(self, radius: f32) -> Self {
self.tree
.world
.ui
.add_components(self.entity, crate::ecs::world::UI_NODE_TRANSFORM_STATES);
if let Some(states) = self
.tree
.world
.ui
.get_ui_node_transform_states_mut(self.entity)
{
states.radii[S::INDEX] = Some(radius);
}
self
}
pub fn with_depth(self, depth: UiDepthMode) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.depth = depth;
}
self
}
pub fn with_font_size(self, size: f32) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.font_size = Some(size);
}
self
}
pub fn with_clip(self) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.clip_content = true;
}
self
}
pub fn with_visible(self, visible: bool) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.visible = visible;
}
self
}
pub fn without_pointer_events(self) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.pointer_events = false;
}
self
}
pub fn with_intro(self, animation: UiAnimationType, duration: f32) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
let anim = node.animation.get_or_insert(UiNodeAnimation {
intro: None,
outro: None,
duration,
progress: 0.0,
phase: UiAnimationPhase::Idle,
});
anim.intro = Some(animation);
anim.duration = duration;
if node.visible {
anim.phase = UiAnimationPhase::IntroPlaying;
anim.progress = 0.0;
}
}
self
}
pub fn with_outro(self, animation: UiAnimationType, duration: f32) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
let anim = node.animation.get_or_insert(UiNodeAnimation {
intro: None,
outro: None,
duration,
progress: 0.0,
phase: UiAnimationPhase::Idle,
});
anim.outro = Some(animation);
anim.duration = duration;
}
self
}
pub fn with_drag_source(self, payload: DragPayload) -> Self {
let this = if self
.tree
.world
.ui
.get_ui_node_interaction(self.entity)
.is_none()
{
self.with_interaction()
} else {
self
};
this.tree
.world
.ui
.set_ui_drag_source(this.entity, UiDragSource { payload });
this
}
pub fn with_drop_target(self) -> Self {
let this = if self
.tree
.world
.ui
.get_ui_node_interaction(self.entity)
.is_none()
{
self.with_interaction()
} else {
self
};
this.tree
.world
.ui
.set_ui_drop_target(this.entity, UiDropTarget::default());
this
}
pub fn with_filtered_drop_target(
self,
filter: crate::ecs::ui::components::DragAcceptFilter,
) -> Self {
let this = if self
.tree
.world
.ui
.get_ui_node_interaction(self.entity)
.is_none()
{
self.with_interaction()
} else {
self
};
this.tree.world.ui.set_ui_drop_target(
this.entity,
UiDropTarget {
accepted: true,
filter,
},
);
this
}
pub fn with_layer(self, layer: UiLayer) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.layer = Some(layer);
}
self
}
pub fn with_children(self, child_builder: impl FnOnce(&mut UiTreeBuilder<'a>)) -> Self {
let entity = self.entity;
let tree = self.tree;
tree.push_parent(entity);
child_builder(tree);
tree.pop_parent();
UiNodeBuilder { tree, entity }
}
pub fn with_responsive_flow(
self,
max_width: f32,
direction: crate::ecs::ui::layout_types::FlowDirection,
) -> Self {
if let Some(node) = self.tree.world.ui.get_ui_layout_node_mut(self.entity) {
node.responsive_flow = Some(crate::ecs::ui::layout_types::ResponsiveFlowOverride {
max_width,
direction,
});
}
self
}
}
pub struct ParentScope<'b, 'a: 'b> {
tree: &'b mut UiTreeBuilder<'a>,
}
impl<'b, 'a: 'b> ParentScope<'b, 'a> {
pub(crate) fn push(tree: &'b mut UiTreeBuilder<'a>, parent: freecs::Entity) -> Self {
tree.push_parent(parent);
Self { tree }
}
}
impl<'b, 'a: 'b> std::ops::Deref for ParentScope<'b, 'a> {
type Target = UiTreeBuilder<'a>;
fn deref(&self) -> &Self::Target {
self.tree
}
}
impl<'b, 'a: 'b> std::ops::DerefMut for ParentScope<'b, 'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.tree
}
}
impl Drop for ParentScope<'_, '_> {
fn drop(&mut self) {
self.tree.pop_parent();
}
}