nightshade 0.13.0

A cross-platform data-oriented game engine.
Documentation
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, UiHover, UiPressed, UiStateTrait};
use crate::ecs::ui::units::{Ab, Rl};

impl<'a> UiTreeBuilder<'a> {
    pub fn add_button(&mut self, text: &str) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .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_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_transition::<UiPressed>(12.0, 8.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)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.world_mut().ui.set_ui_widget_state(
            button_entity,
            UiWidgetState::Button(UiButtonData {
                clicked: false,
                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
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .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)
            .with_color::<UiBase>(color)
            .with_color::<UiHover>(hover_color)
            .with_color::<UiPressed>(active_color)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_transition::<UiPressed>(12.0, 8.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)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.world_mut().ui.set_ui_widget_state(
            button_entity,
            UiWidgetState::Button(UiButtonData {
                clicked: false,
                text_slot,
            }),
        );
        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
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .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_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_transition::<UiPressed>(12.0, 8.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)
                    .without_pointer_events()
                    .done();

                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)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.world_mut().ui.set_ui_widget_state(
            button_entity,
            UiWidgetState::Button(UiButtonData {
                clicked: false,
                text_slot,
            }),
        );
        self.assign_tab_index(button_entity);

        button_entity
    }

    pub fn add_button_rich(&mut self, spans: &[TextSpan]) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .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_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_transition::<UiPressed>(12.0, 8.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()
            .without_pointer_events()
            .entity();

        self.push_parent(span_container);

        for span in spans {
            let text_slot = self.world_mut().resources.text_cache.add_text(&span.text);
            let mut span_font_size = span.font_size_override.unwrap_or(font_size);
            let span_color = span.color.unwrap_or(text_color);
            let span_font_index = span.font_index.unwrap_or(0);

            if span.bold {
                span_font_size *= 1.05;
            }

            let outline_width = if span.bold { 0.4 } else { 0.0 };
            let outline_color = if span.bold {
                span_color
            } else {
                Vec4::new(0.0, 0.0, 0.0, 0.0)
            };

            let span_wrapper = self
                .add_node()
                .flow_child(Ab(Vec2::new(0.0, button_height)))
                .auto_size(crate::ecs::ui::components::AutoSizeMode::Width)
                .flow(FlowDirection::Vertical, 0.0, 0.0)
                .without_pointer_events()
                .entity();

            self.push_parent(span_wrapper);

            let entity = self.add_node().done();
            if let Some(content) = self.world_mut().ui.get_ui_node_content_mut(entity) {
                *content = crate::ecs::ui::components::UiNodeContent::Text {
                    text_slot,
                    font_index: span_font_index,
                    font_size_override: Some(span_font_size),
                    outline_color,
                    outline_width,
                    alignment: TextAlignment::Left,
                    vertical_alignment: VerticalAlignment::Middle,
                    overflow: crate::ecs::ui::components::TextOverflow::default(),
                    monospace_width: None,
                };
            }
            if let Some(node) = self.world_mut().ui.get_ui_layout_node_mut(entity) {
                node.flow_child_size = Some(Ab(Vec2::new(0.0, button_height)).into());
                node.auto_size = crate::ecs::ui::components::AutoSizeMode::Width;
                node.pointer_events = false;
            }
            if let Some(color) = self.world_mut().ui.get_ui_node_color_mut(entity) {
                color.colors[UiBase::INDEX] = Some(span_color);
            }
            if span.color.is_none() {
                let mut binding = crate::ecs::ui::components::UiThemeBinding::default();
                binding.color_roles[UiBase::INDEX] = Some(ThemeColor::Text);
                self.world_mut().ui.set_ui_theme_binding(entity, binding);
            }

            self.pop_parent();
        }

        self.pop_parent();
        self.pop_parent();

        self.world_mut().ui.set_ui_widget_state(
            button_entity,
            UiWidgetState::Button(UiButtonData {
                clicked: false,
                text_slot: 0,
            }),
        );
        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)
            .with_color::<UiBase>(Vec4::new(1.0, 1.0, 1.0, 1.0))
            .without_pointer_events()
            .done()
    }

    pub fn add_selectable_label(&mut self, text: &str, group_id: Option<u32>) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .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))
            .with_color::<UiBase>(Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.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)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.world_mut().ui.set_ui_widget_state(
            label_entity,
            UiWidgetState::SelectableLabel(UiSelectableLabelData {
                selected: false,
                changed: false,
                text_slot,
                group_id,
            }),
        );
        if let Some(gid) = group_id {
            self.world_mut()
                .resources
                .retained_ui
                .selectable_label_groups
                .entry(gid)
                .or_default()
                .push(label_entity);
        }

        label_entity
    }
}