nightshade 0.13.3

A cross-platform data-oriented game engine.
Documentation
use crate::ecs::ui::builder::UiTreeBuilder;
use crate::ecs::world::World;
use nalgebra_glm::Vec2;
use winit::keyboard::KeyCode;

pub struct UiInteractionContext {
    pub mouse_position: Vec2,
    pub scroll_delta: Vec2,
    pub frame_keys: Vec<(KeyCode, bool)>,
    pub frame_chars: Vec<char>,
    pub ctrl_held: bool,
    pub shift_held: bool,
    pub delta_time: f32,
    pub focused_entity: Option<freecs::Entity>,
    pub dpi_scale: f32,
}

pub trait CompositeWidget: Sized + 'static {
    type Value;

    fn build(tree: &mut UiTreeBuilder) -> Self;

    fn update(&mut self, _world: &mut World) {}

    fn interact(
        &mut self,
        _world: &mut World,
        _entity: freecs::Entity,
        _ctx: &UiInteractionContext,
    ) {
    }

    fn value(&self, _world: &World) -> Self::Value;
}

pub(crate) trait AnyCompositeWidget: std::any::Any {
    fn update_any(&mut self, world: &mut World);
    fn interact_any(
        &mut self,
        world: &mut World,
        entity: freecs::Entity,
        ctx: &UiInteractionContext,
    );
    fn as_any(&self) -> &dyn std::any::Any;
    fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
}

impl<C: CompositeWidget> AnyCompositeWidget for C {
    fn update_any(&mut self, world: &mut World) {
        self.update(world);
    }

    fn interact_any(
        &mut self,
        world: &mut World,
        entity: freecs::Entity,
        ctx: &UiInteractionContext,
    ) {
        self.interact(world, entity, ctx);
    }

    fn as_any(&self) -> &dyn std::any::Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
        self
    }
}

impl<'a> UiTreeBuilder<'a> {
    /// Instantiates a [`CompositeWidget`] inside the current parent and
    /// registers it for automatic [`update`](CompositeWidget::update) dispatch.
    ///
    /// Creates a vertical-flow container, calls `T::build` with a scoped
    /// builder, and returns the composite's container entity. The composite
    /// instance is stored internally and its `update` method is called each
    /// frame automatically.
    ///
    /// Use [`World::ui_composite`] to access the composite instance, or
    /// [`World::ui_composite_value`] to read its value.
    ///
    /// # Example
    ///
    /// ```ignore
    /// let entity = tree.add_composite::<Vec3Editor>();
    /// // Later, each frame:
    /// if let Some(value) = world.ui_composite_value::<Vec3Editor>(entity) {
    ///     // use value
    /// }
    /// ```
    pub fn add_composite<T: CompositeWidget>(&mut self) -> freecs::Entity {
        use crate::ecs::ui::layout_types::FlowDirection;
        use crate::ecs::ui::units::{Ab, Rl};
        use nalgebra_glm::Vec2;

        let container = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 0.0)))
            .flow(FlowDirection::Vertical, 0.0, 4.0)
            .without_pointer_events()
            .entity();

        self.push_parent(container);
        let widget = T::build(self);
        self.pop_parent();

        self.world_mut()
            .resources
            .retained_ui
            .composite_widgets
            .insert(container, Box::new(widget));

        container
    }
}

impl World {
    pub fn ui_composite<T: CompositeWidget>(&self, entity: freecs::Entity) -> Option<&T> {
        self.resources
            .retained_ui
            .composite_widgets
            .get(&entity)
            .and_then(|boxed| boxed.as_any().downcast_ref::<T>())
    }

    pub fn ui_composite_mut<T: CompositeWidget>(
        &mut self,
        entity: freecs::Entity,
    ) -> Option<&mut T> {
        self.resources
            .retained_ui
            .composite_widgets
            .get_mut(&entity)
            .and_then(|boxed| boxed.as_any_mut().downcast_mut::<T>())
    }

    pub fn ui_composite_value<T: CompositeWidget>(
        &self,
        entity: freecs::Entity,
    ) -> Option<T::Value> {
        self.ui_composite::<T>(entity)
            .map(|composite| composite.value(self))
    }
}

pub fn ui_composite_update_system(world: &mut World) {
    if !world.resources.retained_ui.enabled {
        return;
    }

    let ctx = UiInteractionContext {
        mouse_position: world.resources.input.mouse.position,
        scroll_delta: world.resources.retained_ui.scroll_delta,
        frame_keys: world.resources.retained_ui.frame_keys.clone(),
        frame_chars: world.resources.retained_ui.frame_chars.clone(),
        ctrl_held: world
            .resources
            .input
            .keyboard
            .is_key_pressed(KeyCode::ControlLeft)
            || world
                .resources
                .input
                .keyboard
                .is_key_pressed(KeyCode::ControlRight),
        shift_held: world
            .resources
            .input
            .keyboard
            .is_key_pressed(KeyCode::ShiftLeft)
            || world
                .resources
                .input
                .keyboard
                .is_key_pressed(KeyCode::ShiftRight),
        delta_time: world.resources.retained_ui.delta_time,
        focused_entity: world.resources.retained_ui.focused_entity,
        dpi_scale: world.resources.window.cached_scale_factor,
    };

    let entities: Vec<freecs::Entity> = world
        .resources
        .retained_ui
        .composite_widgets
        .keys()
        .copied()
        .collect();

    for entity in entities {
        if world.ui.get_ui_layout_node(entity).is_none() {
            world
                .resources
                .retained_ui
                .composite_widgets
                .remove(&entity);
            continue;
        }

        if let Some(mut composite) = world
            .resources
            .retained_ui
            .composite_widgets
            .remove(&entity)
        {
            composite.interact_any(world, entity, &ctx);
            composite.update_any(world);
            world
                .resources
                .retained_ui
                .composite_widgets
                .insert(entity, composite);
        }
    }
}