nightshade 0.13.0

A cross-platform data-oriented game engine.
Documentation
use crate::ecs::ui::components::UiAnimationPhase;
use crate::ecs::ui::state::{
    STATE_COUNT, UiBase, UiDisabled, UiFocused, UiHover, UiPressed, UiStateTrait,
};
use crate::ecs::world::World;

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

    let delta_time = world.resources.window.timing.delta_time;
    let reduced_motion = world.resources.retained_ui.reduced_motion;
    let total_state_count = world.resources.retained_ui.total_state_count();

    let entities: Vec<freecs::Entity> = world
        .ui
        .query_entities(crate::ecs::world::UI_STATE_WEIGHTS)
        .collect();

    let mut any_weight_changed = false;

    for entity in entities {
        let (hover_target, pressed_target, focused_target, disabled_target) = {
            match world.ui.get_ui_node_interaction(entity) {
                Some(interaction) => (
                    if interaction.hovered { 1.0 } else { 0.0 },
                    if interaction.pressed { 1.0 } else { 0.0 },
                    if interaction.focused { 1.0 } else { 0.0 },
                    if interaction.disabled { 1.0 } else { 0.0 },
                ),
                None => (0.0, 0.0, 0.0, 0.0),
            }
        };

        if let Some(state_weights) = world.ui.get_ui_state_weights_mut(entity) {
            state_weights.ensure_state_capacity(total_state_count);

            let targets = [
                (UiHover::INDEX, hover_target),
                (UiPressed::INDEX, pressed_target),
                (UiFocused::INDEX, focused_target),
                (UiDisabled::INDEX, disabled_target),
            ];

            for (index, target) in targets {
                let previous = state_weights.weights[index];

                if (state_weights.targets[index] - target).abs() > f32::EPSILON {
                    state_weights.targets[index] = target;
                    state_weights.start_weights[index] = previous;
                    state_weights.progress[index] = 0.0;
                }

                if reduced_motion {
                    state_weights.weights[index] = state_weights.targets[index];
                    state_weights.progress[index] = 1.0;
                } else if state_weights.progress[index] < 1.0 {
                    let transition = state_weights.transitions[index].unwrap_or_default();
                    let speed = if target > state_weights.start_weights[index] {
                        transition.enter_speed
                    } else {
                        transition.exit_speed
                    };

                    state_weights.progress[index] =
                        (state_weights.progress[index] + speed * delta_time).min(1.0);

                    let eased = transition.easing.evaluate(state_weights.progress[index]);
                    let start = state_weights.start_weights[index];
                    state_weights.weights[index] =
                        (start + (target - start) * eased).clamp(0.0, 1.0);
                }

                if (state_weights.weights[index] - previous).abs() > f32::EPSILON {
                    any_weight_changed = true;
                }
            }

            for index in STATE_COUNT..state_weights.weights.len() {
                let previous = state_weights.weights[index];
                let target = state_weights.targets[index];

                if reduced_motion {
                    state_weights.weights[index] = target;
                    state_weights.progress[index] = 1.0;
                } else if state_weights.progress[index] < 1.0 {
                    let transition = state_weights.transitions[index].unwrap_or_default();
                    let speed = if target > state_weights.start_weights[index] {
                        transition.enter_speed
                    } else {
                        transition.exit_speed
                    };

                    state_weights.progress[index] =
                        (state_weights.progress[index] + speed * delta_time).min(1.0);

                    let eased = transition.easing.evaluate(state_weights.progress[index]);
                    let start = state_weights.start_weights[index];
                    state_weights.weights[index] =
                        (start + (target - start) * eased).clamp(0.0, 1.0);
                }

                if (state_weights.weights[index] - previous).abs() > f32::EPSILON {
                    any_weight_changed = true;
                }
            }

            let non_base_sum: f32 = state_weights.weights[1..].iter().sum();
            state_weights.weights[UiBase::INDEX] = (1.0 - non_base_sum).clamp(0.0, 1.0);
        }
    }

    if any_weight_changed {
        world.resources.retained_ui.layout_dirty = true;
    }

    let anim_entities: Vec<freecs::Entity> = world
        .ui
        .query_entities(crate::ecs::world::UI_LAYOUT_NODE)
        .collect();

    for entity in anim_entities {
        let should_hide = if let Some(node) = world.ui.get_ui_layout_node_mut(entity)
            && let Some(animation) = &mut node.animation
        {
            match animation.phase {
                UiAnimationPhase::IntroPlaying => {
                    animation.progress += delta_time / animation.duration;
                    if animation.progress >= 1.0 {
                        animation.progress = 1.0;
                        animation.phase = UiAnimationPhase::Idle;
                    }
                    world.resources.retained_ui.layout_dirty = true;
                    false
                }
                UiAnimationPhase::OutroPlaying => {
                    animation.progress += delta_time / animation.duration;
                    if animation.progress >= 1.0 {
                        animation.progress = 1.0;
                        animation.phase = UiAnimationPhase::OutroComplete;
                        world.resources.retained_ui.layout_dirty = true;
                        true
                    } else {
                        world.resources.retained_ui.layout_dirty = true;
                        false
                    }
                }
                _ => false,
            }
        } else {
            false
        };

        if should_hide && let Some(node) = world.ui.get_ui_layout_node_mut(entity) {
            node.visible = false;
        }
    }

    world.ui_tick_toasts();
}