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};
use crate::ecs::ui::types::{Anchor, Rect};
use crate::ecs::ui::units::{Ab, Rl};
use crate::render::wgpu::passes::geometry::UiLayer;

impl<'a> UiTreeBuilder<'a> {
    pub fn add_floating_panel(&mut self, title: &str, rect: Rect) -> 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 border_color = theme.border_color;

        let title_slot = self.world_mut().resources.text_cache.add_text(title);
        let collapse_slot = self.world_mut().resources.text_cache.add_text("-");

        let focus_order = self.world_mut().resources.retained_ui.next_focus_order;
        self.world_mut().resources.retained_ui.next_focus_order += 1;

        let mut header_entity = freecs::Entity::default();
        let mut content_entity = freecs::Entity::default();
        let mut collapse_button_entity = freecs::Entity::default();

        let panel_entity = self
            .add_node()
            .window(
                Ab(Vec2::new(rect.min.x, rect.min.y)),
                Ab(Vec2::new(rect.width(), rect.height())),
                Anchor::TopLeft,
            )
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::Background)
            .with_layer(UiLayer::FloatingPanels)
            .with_clip()
            .with_depth(crate::ecs::ui::components::UiDepthMode::Set(
                20.0 + focus_order as f32 * 10.0,
            ))
            .with_children(|tree| {
                header_entity = tree
                    .add_node()
                    .boundary(
                        Rl(Vec2::new(0.0, 0.0)),
                        Ab(Vec2::new(0.0, 32.0)) + Rl(Vec2::new(100.0, 0.0)),
                    )
                    .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::BackgroundActive)
                    .with_clip()
                    .with_interaction()
                    .with_cursor_icon(winit::window::CursorIcon::Grab)
                    .with_children(|tree| {
                        tree.add_node()
                            .window(
                                Ab(Vec2::new(8.0, 16.0)),
                                Ab(Vec2::new(200.0, 20.0)),
                                Anchor::CenterLeft,
                            )
                            .with_text_slot(title_slot, font_size * 0.85)
                            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                            .with_theme_color::<UiBase>(ThemeColor::Accent)
                            .without_pointer_events()
                            .done();

                        collapse_button_entity = tree
                            .add_node()
                            .window(
                                Rl(Vec2::new(100.0, 50.0)) + Ab(Vec2::new(-8.0, 0.0)),
                                Ab(Vec2::new(20.0, 20.0)),
                                Anchor::CenterRight,
                            )
                            .with_rect(2.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                            .with_theme_color::<UiBase>(ThemeColor::BackgroundHover)
                            .with_theme_color::<UiHover>(ThemeColor::Accent)
                            .with_interaction()
                            .with_transition::<UiHover>(8.0, 6.0)
                            .with_cursor_icon(winit::window::CursorIcon::Pointer)
                            .with_children(|tree| {
                                tree.add_node()
                                    .window(
                                        Rl(Vec2::new(50.0, 50.0)),
                                        Ab(Vec2::new(20.0, 20.0)),
                                        Anchor::Center,
                                    )
                                    .with_text_slot(collapse_slot, font_size * 0.8)
                                    .with_text_alignment(
                                        TextAlignment::Center,
                                        VerticalAlignment::Middle,
                                    )
                                    .with_theme_color::<UiBase>(ThemeColor::Text)
                                    .without_pointer_events()
                                    .done();
                            })
                            .done();
                    })
                    .done();

                content_entity = tree
                    .add_node()
                    .boundary(Ab(Vec2::new(0.0, 32.0)), Rl(Vec2::new(100.0, 100.0)))
                    .flow(FlowDirection::Vertical, 8.0, 4.0)
                    .with_clip()
                    .without_pointer_events()
                    .entity();
            })
            .done();

        self.world_mut().ui.set_ui_widget_state(
            panel_entity,
            UiWidgetState::Panel(UiPanelData {
                title: title.to_string(),
                title_text_slot: title_slot,
                content_entity,
                header_entity,
                collapsed: false,
                panel_kind: UiPanelKind::Floating,
                focus_order,
                pinned: false,
                min_size: Vec2::new(150.0, 100.0),
                drag_offset: None,
                resize_edge: None,
                resize_start_rect: None,
                resize_start_mouse: None,
                undocked_rect: None,
                default_dock_size: 300.0,
                collapse_button_entity: Some(collapse_button_entity),
                collapse_button_text_slot: Some(collapse_slot),
                header_visible: true,
                resizable: true,
            }),
        );

        panel_entity
    }

    fn build_docked_panel(
        &mut self,
        title: &str,
        panel_kind: UiPanelKind,
        default_size: f32,
    ) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let border_color = theme.border_color;

        let title_slot = self.world_mut().resources.text_cache.add_text(title);

        let focus_order = self.world_mut().resources.retained_ui.next_focus_order;
        self.world_mut().resources.retained_ui.next_focus_order += 1;

        let mut header_entity = freecs::Entity::default();
        let mut content_entity = freecs::Entity::default();

        let panel_entity = self
            .add_node()
            .window(
                Ab(Vec2::new(0.0, 0.0)),
                Ab(Vec2::new(default_size, default_size)),
                Anchor::TopLeft,
            )
            .with_rect(0.0, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::Background)
            .with_layer(UiLayer::DockedPanels)
            .with_clip()
            .with_depth(crate::ecs::ui::components::UiDepthMode::Set(
                10.0 + focus_order as f32 * 10.0,
            ))
            .with_children(|tree| {
                header_entity = tree
                    .add_node()
                    .boundary(
                        Rl(Vec2::new(0.0, 0.0)),
                        Ab(Vec2::new(0.0, 32.0)) + Rl(Vec2::new(100.0, 0.0)),
                    )
                    .with_rect(0.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::BackgroundActive)
                    .with_clip()
                    .with_interaction()
                    .with_cursor_icon(winit::window::CursorIcon::Grab)
                    .with_children(|tree| {
                        tree.add_node()
                            .window(
                                Ab(Vec2::new(8.0, 16.0)),
                                Ab(Vec2::new(200.0, 20.0)),
                                Anchor::CenterLeft,
                            )
                            .with_text_slot(title_slot, font_size * 0.85)
                            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                            .with_theme_color::<UiBase>(ThemeColor::Accent)
                            .without_pointer_events()
                            .done();
                    })
                    .done();

                content_entity = tree
                    .add_node()
                    .boundary(Ab(Vec2::new(0.0, 32.0)), Rl(Vec2::new(100.0, 100.0)))
                    .flow(FlowDirection::Vertical, 8.0, 4.0)
                    .with_clip()
                    .without_pointer_events()
                    .entity();
            })
            .done();

        self.world_mut().ui.set_ui_widget_state(
            panel_entity,
            UiWidgetState::Panel(UiPanelData {
                title: title.to_string(),
                title_text_slot: title_slot,
                content_entity,
                header_entity,
                collapsed: false,
                panel_kind,
                focus_order,
                pinned: false,
                min_size: Vec2::new(100.0, 60.0),
                drag_offset: None,
                resize_edge: None,
                resize_start_rect: None,
                resize_start_mouse: None,
                undocked_rect: None,
                default_dock_size: default_size,
                collapse_button_entity: None,
                collapse_button_text_slot: None,
                header_visible: true,
                resizable: true,
            }),
        );

        panel_entity
    }

    pub fn add_docked_panel_left(&mut self, title: &str, default_width: f32) -> freecs::Entity {
        self.build_docked_panel(title, UiPanelKind::DockedLeft, default_width)
    }

    pub fn add_docked_panel_right(&mut self, title: &str, default_width: f32) -> freecs::Entity {
        self.build_docked_panel(title, UiPanelKind::DockedRight, default_width)
    }

    pub fn add_docked_panel_top(&mut self, title: &str, default_height: f32) -> freecs::Entity {
        self.build_docked_panel(title, UiPanelKind::DockedTop, default_height)
    }

    pub fn add_docked_panel_bottom(&mut self, title: &str, default_height: f32) -> freecs::Entity {
        self.build_docked_panel(title, UiPanelKind::DockedBottom, default_height)
    }

    pub fn add_color_picker(&mut self, initial_color: Vec4) -> freecs::Entity {
        self.add_color_picker_with_mode(
            initial_color,
            crate::ecs::ui::components::ColorPickerMode::Rgb,
        )
    }

    pub fn add_color_picker_hsv(&mut self, initial_color: Vec4) -> freecs::Entity {
        self.add_color_picker_with_mode(
            initial_color,
            crate::ecs::ui::components::ColorPickerMode::Hsv,
        )
    }

    pub fn add_color_picker_with_mode(
        &mut self,
        initial_color: Vec4,
        mode: crate::ecs::ui::components::ColorPickerMode,
    ) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let corner_radius = theme.corner_radius;

        let root_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 0.0)))
            .flow(FlowDirection::Horizontal, 4.0, 8.0)
            .entity();

        self.push_parent(root_entity);

        let swatch_entity = self
            .add_node()
            .flow_child(Ab(Vec2::new(48.0, 48.0)))
            .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_color::<UiBase>(initial_color)
            .without_pointer_events()
            .done();

        let sliders_container = self
            .add_node()
            .flow_child(Ab(Vec2::new(0.0, 0.0)))
            .flex_grow(1.0)
            .flow(FlowDirection::Vertical, 0.0, 4.0)
            .entity();

        self.push_parent(sliders_container);

        let slider_entities = match mode {
            crate::ecs::ui::components::ColorPickerMode::Rgb => {
                let slider_r = self.add_slider_configured(
                    crate::ecs::ui::components::SliderConfig::new(0.0, 1.0, initial_color.x)
                        .precision(2)
                        .prefix("R: "),
                );
                let slider_g = self.add_slider_configured(
                    crate::ecs::ui::components::SliderConfig::new(0.0, 1.0, initial_color.y)
                        .precision(2)
                        .prefix("G: "),
                );
                let slider_b = self.add_slider_configured(
                    crate::ecs::ui::components::SliderConfig::new(0.0, 1.0, initial_color.z)
                        .precision(2)
                        .prefix("B: "),
                );
                let slider_a = self.add_slider_configured(
                    crate::ecs::ui::components::SliderConfig::new(0.0, 1.0, initial_color.w)
                        .precision(2)
                        .prefix("A: "),
                );
                [slider_r, slider_g, slider_b, slider_a]
            }
            crate::ecs::ui::components::ColorPickerMode::Hsv => {
                let hsv = crate::ecs::ui::color::Hsva::from_rgba(initial_color);
                let slider_h = self.add_slider_configured(
                    crate::ecs::ui::components::SliderConfig::new(0.0, 360.0, hsv.hue)
                        .precision(0)
                        .prefix("H: ")
                        .suffix("\u{00b0}"),
                );
                let slider_s = self.add_slider_configured(
                    crate::ecs::ui::components::SliderConfig::new(0.0, 1.0, hsv.saturation)
                        .precision(2)
                        .prefix("S: "),
                );
                let slider_v = self.add_slider_configured(
                    crate::ecs::ui::components::SliderConfig::new(0.0, 1.0, hsv.value)
                        .precision(2)
                        .prefix("V: "),
                );
                let slider_a = self.add_slider_configured(
                    crate::ecs::ui::components::SliderConfig::new(0.0, 1.0, initial_color.w)
                        .precision(2)
                        .prefix("A: "),
                );
                [slider_h, slider_s, slider_v, slider_a]
            }
        };

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

        self.world_mut().ui.set_ui_widget_state(
            root_entity,
            UiWidgetState::ColorPicker(UiColorPickerData {
                color: initial_color,
                changed: false,
                swatch_entity,
                slider_entities,
                mode,
            }),
        );

        root_entity
    }
}