nightshade 0.13.1

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, UiFocused, UiHover};
use crate::ecs::ui::types::Anchor;
use crate::ecs::ui::units::{Ab, Rl};

impl<'a> UiTreeBuilder<'a> {
    pub fn add_drag_value(&mut self, min: f32, max: f32, initial: f32) -> freecs::Entity {
        self.add_drag_value_configured(DragValueConfig::new(min, max, initial))
    }

    pub fn add_drag_value_configured(&mut self, config: DragValueConfig<'_>) -> freecs::Entity {
        let initial = config.initial;
        let min = config.min;
        let max = config.max;
        let speed = config.speed;
        let precision = config.precision;
        let prefix = config.prefix;
        let suffix = config.suffix;
        let show_arrows = config.show_arrows;
        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 display_text = format!("{prefix}{initial:.prec$}{suffix}", prec = precision);
        let text_slot = self
            .world_mut()
            .resources
            .text_cache
            .add_text(&display_text);
        let mut cursor_entity = freecs::Entity::default();
        let mut selection_entity = freecs::Entity::default();

        let wrapper_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, input_height)))
            .flow(FlowDirection::Horizontal, 0.0, 0.0)
            .without_pointer_events()
            .entity();

        self.push_parent(wrapper_entity);

        let root_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(0.0, 100.0)) + Ab(Vec2::new(0.0, 0.0)))
            .flex_grow(1.0)
            .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::EwResize)
            .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();

                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();

        let mut up_entity = None;
        let mut down_entity = None;
        if show_arrows {
            let arrow_width = 18.0;
            let half_height = input_height / 2.0;

            let arrow_col = self
                .add_node()
                .flow_child(Ab(Vec2::new(arrow_width, input_height)))
                .flow(FlowDirection::Vertical, 0.0, 0.0)
                .without_pointer_events()
                .entity();
            self.push_parent(arrow_col);

            let up_slot = self.world_mut().resources.text_cache.add_text("\u{25B2}");
            up_entity = Some(
                self.add_node()
                    .flow_child(Ab(Vec2::new(arrow_width, half_height)))
                    .with_rect(corner_radius, 1.0, border_color)
                    .with_theme_border_color(ThemeColor::Border)
                    .with_theme_color::<UiBase>(ThemeColor::InputBackground)
                    .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::zeros()), Rl(Vec2::new(100.0, 100.0)))
                            .with_text_slot(up_slot, font_size * 0.5)
                            .with_text_alignment(TextAlignment::Center, VerticalAlignment::Middle)
                            .with_theme_color::<UiBase>(ThemeColor::Text)
                            .without_pointer_events()
                            .done();
                    })
                    .done(),
            );

            let down_slot = self.world_mut().resources.text_cache.add_text("\u{25BC}");
            down_entity = Some(
                self.add_node()
                    .flow_child(Ab(Vec2::new(arrow_width, half_height)))
                    .with_rect(corner_radius, 1.0, border_color)
                    .with_theme_border_color(ThemeColor::Border)
                    .with_theme_color::<UiBase>(ThemeColor::InputBackground)
                    .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::zeros()), Rl(Vec2::new(100.0, 100.0)))
                            .with_text_slot(down_slot, font_size * 0.5)
                            .with_text_alignment(TextAlignment::Center, VerticalAlignment::Middle)
                            .with_theme_color::<UiBase>(ThemeColor::Text)
                            .without_pointer_events()
                            .done();
                    })
                    .done(),
            );

            self.pop_parent();
        }

        self.pop_parent();

        let step_val = speed * 10.0;
        self.world_mut().ui.set_ui_widget_state(
            root_entity,
            UiWidgetState::DragValue(UiDragValueData {
                value: initial,
                min,
                max,
                speed,
                precision,
                prefix: prefix.to_string(),
                suffix: suffix.to_string(),
                changed: false,
                text_slot,
                editing: false,
                cursor_entity,
                selection_entity,
                edit_text: String::new(),
                cursor_position: 0,
                selection_start: None,
                cursor_blink_timer: 0.0,
                scroll_offset: 0.0,
                drag_start_value: initial,
                undo_stack: UndoStack::new(100),
                up_entity,
                down_entity,
                step: step_val,
            }),
        );
        if let Some(interaction) = self.world_mut().ui.get_ui_node_interaction_mut(root_entity) {
            interaction.accessible_role = Some(AccessibleRole::Slider);
        }
        self.assign_tab_index(root_entity);

        root_entity
    }
}