nightshade 0.13.3

A cross-platform data-oriented game engine.
Documentation
use crate::ecs::ui::components::UiWidgetState;
use crate::ecs::world::World;

pub fn ui_property_sync_system(world: &mut World) {
    let property_ids: Vec<crate::ecs::ui::resources::PropertyId> = world
        .resources
        .retained_ui
        .bound_properties
        .keys()
        .copied()
        .collect();

    for id in property_ids {
        let (entity, dirty_from_code, value_clone) = {
            let Some(prop) = world.resources.retained_ui.bound_properties.get(&id) else {
                continue;
            };
            (
                prop.entity,
                prop.dirty_from_code,
                match &prop.value {
                    crate::ecs::ui::resources::PropertyValue::F32(v) => {
                        crate::ecs::ui::resources::PropertyValue::F32(*v)
                    }
                    crate::ecs::ui::resources::PropertyValue::Bool(v) => {
                        crate::ecs::ui::resources::PropertyValue::Bool(*v)
                    }
                    crate::ecs::ui::resources::PropertyValue::Usize(v) => {
                        crate::ecs::ui::resources::PropertyValue::Usize(*v)
                    }
                    crate::ecs::ui::resources::PropertyValue::String(v) => {
                        crate::ecs::ui::resources::PropertyValue::String(v.clone())
                    }
                    crate::ecs::ui::resources::PropertyValue::Vec4(v) => {
                        crate::ecs::ui::resources::PropertyValue::Vec4(*v)
                    }
                },
            )
        };

        let widget_changed = if let Some(state) = world.ui.get_ui_widget_state(entity) {
            match state {
                UiWidgetState::Slider(data) => data.changed,
                UiWidgetState::DragValue(data) => data.changed,
                UiWidgetState::Toggle(data) => data.changed,
                UiWidgetState::Checkbox(data) => data.changed,
                UiWidgetState::TextInput(data) => data.changed,
                UiWidgetState::Dropdown(data) => data.changed,
                UiWidgetState::TextArea(data) => data.changed,
                UiWidgetState::RichTextEditor(data) => data.changed,
                UiWidgetState::ColorPicker(data) => data.changed,
                UiWidgetState::TabBar(data) => data.changed,
                UiWidgetState::SelectableLabel(data) => data.changed,
                UiWidgetState::CollapsingHeader(data) => data.changed,
                UiWidgetState::Splitter(data) => data.changed,
                UiWidgetState::RangeSlider(data) => data.changed,
                UiWidgetState::Radio(data) => {
                    let group_id = data.group_id;
                    let members = world
                        .resources
                        .retained_ui
                        .radio_groups
                        .get(&group_id)
                        .cloned()
                        .unwrap_or_default();
                    members.iter().any(|&member| {
                        matches!(
                            world.ui.get_ui_widget_state(member),
                            Some(UiWidgetState::Radio(r)) if r.changed
                        )
                    })
                }
                _ => false,
            }
        } else {
            false
        };

        if widget_changed {
            if let Some(state) = world.ui.get_ui_widget_state(entity) {
                let new_value = match state {
                    UiWidgetState::Slider(data) => {
                        Some(crate::ecs::ui::resources::PropertyValue::F32(data.value))
                    }
                    UiWidgetState::DragValue(data) => {
                        Some(crate::ecs::ui::resources::PropertyValue::F32(data.value))
                    }
                    UiWidgetState::Toggle(data) => {
                        Some(crate::ecs::ui::resources::PropertyValue::Bool(data.value))
                    }
                    UiWidgetState::Checkbox(data) => {
                        Some(crate::ecs::ui::resources::PropertyValue::Bool(data.value))
                    }
                    UiWidgetState::TextInput(data) => Some(
                        crate::ecs::ui::resources::PropertyValue::String(data.text.clone()),
                    ),
                    UiWidgetState::Dropdown(data) => Some(
                        crate::ecs::ui::resources::PropertyValue::Usize(data.selected_index),
                    ),
                    UiWidgetState::TextArea(data) => Some(
                        crate::ecs::ui::resources::PropertyValue::String(data.text.clone()),
                    ),
                    UiWidgetState::RichTextEditor(data) => Some(
                        crate::ecs::ui::resources::PropertyValue::String(data.text.clone()),
                    ),
                    UiWidgetState::ColorPicker(data) => {
                        Some(crate::ecs::ui::resources::PropertyValue::Vec4(data.color))
                    }
                    UiWidgetState::TabBar(data) => Some(
                        crate::ecs::ui::resources::PropertyValue::Usize(data.selected_tab),
                    ),
                    UiWidgetState::SelectableLabel(data) => Some(
                        crate::ecs::ui::resources::PropertyValue::Bool(data.selected),
                    ),
                    UiWidgetState::CollapsingHeader(data) => {
                        Some(crate::ecs::ui::resources::PropertyValue::Bool(data.open))
                    }
                    UiWidgetState::Radio(data) => {
                        let group_id = data.group_id;
                        world
                            .ui_radio_group_value(group_id)
                            .map(crate::ecs::ui::resources::PropertyValue::Usize)
                    }
                    UiWidgetState::Splitter(data) => {
                        Some(crate::ecs::ui::resources::PropertyValue::F32(data.ratio))
                    }
                    UiWidgetState::RangeSlider(data) => {
                        Some(crate::ecs::ui::resources::PropertyValue::Vec4(
                            nalgebra_glm::Vec4::new(data.low_value, data.high_value, 0.0, 0.0),
                        ))
                    }
                    _ => None,
                };
                if let Some(val) = new_value
                    && let Some(prop) = world.resources.retained_ui.bound_properties.get_mut(&id)
                {
                    prop.value = val;
                    prop.dirty_from_widget = true;
                }
            }
        } else if dirty_from_code && let Some(state) = world.ui.get_ui_widget_state(entity) {
            match (state, &value_clone) {
                (UiWidgetState::Slider(_), crate::ecs::ui::resources::PropertyValue::F32(v)) => {
                    let v = *v;
                    world.ui_slider_set_value(entity, v);
                }
                (UiWidgetState::DragValue(_), crate::ecs::ui::resources::PropertyValue::F32(v)) => {
                    let v = *v;
                    world.ui_drag_value_set_value(entity, v);
                }
                (UiWidgetState::Toggle(_), crate::ecs::ui::resources::PropertyValue::Bool(v)) => {
                    let v = *v;
                    world.ui_toggle_set_value(entity, v);
                }
                (UiWidgetState::Checkbox(_), crate::ecs::ui::resources::PropertyValue::Bool(v)) => {
                    if let Some(UiWidgetState::Checkbox(data)) =
                        world.ui.get_ui_widget_state_mut(entity)
                    {
                        data.value = *v;
                    }
                }
                (
                    UiWidgetState::SelectableLabel(_),
                    crate::ecs::ui::resources::PropertyValue::Bool(v),
                ) => {
                    let v = *v;
                    world.ui_set_selected(entity, v);
                }
                (
                    UiWidgetState::CollapsingHeader(header_data),
                    crate::ecs::ui::resources::PropertyValue::Bool(v),
                ) => {
                    let new_open = *v;
                    let content_entity = header_data.content_entity;
                    let arrow_text_slot = header_data.arrow_text_slot;
                    if let Some(UiWidgetState::CollapsingHeader(data)) =
                        world.ui.get_ui_widget_state_mut(entity)
                    {
                        data.open = new_open;
                    }
                    if let Some(node) = world.ui.get_ui_layout_node_mut(content_entity) {
                        node.visible = new_open;
                    }
                    world.resources.text_cache.set_text(
                        arrow_text_slot,
                        if new_open { "\u{25BC}" } else { "\u{25B6}" },
                    );
                }
                (
                    UiWidgetState::Radio(radio_data),
                    crate::ecs::ui::resources::PropertyValue::Usize(v),
                ) => {
                    let target_index = *v;
                    let group_id = radio_data.group_id;
                    let members = world
                        .resources
                        .retained_ui
                        .radio_groups
                        .get(&group_id)
                        .cloned()
                        .unwrap_or_default();
                    for member in members {
                        if let Some(UiWidgetState::Radio(member_data)) =
                            world.ui.get_ui_widget_state(member).cloned().as_ref()
                        {
                            let should_select = member_data.option_index == target_index;
                            if let Some(node) =
                                world.ui.get_ui_layout_node_mut(member_data.inner_entity)
                            {
                                node.visible = should_select;
                            }
                            if let Some(UiWidgetState::Radio(wd)) =
                                world.ui.get_ui_widget_state_mut(member)
                            {
                                wd.selected = should_select;
                            }
                        }
                    }
                }
                (
                    UiWidgetState::TextInput(_),
                    crate::ecs::ui::resources::PropertyValue::String(v),
                ) => {
                    world.ui_text_input_set_value(entity, v);
                }
                (
                    UiWidgetState::TextArea(_),
                    crate::ecs::ui::resources::PropertyValue::String(v),
                ) => {
                    world.ui_text_area_set_value(entity, v);
                }
                (
                    UiWidgetState::RichTextEditor(_),
                    crate::ecs::ui::resources::PropertyValue::String(v),
                ) => {
                    world.ui_rich_text_editor_set_value(entity, v);
                }
                (
                    UiWidgetState::Dropdown(_),
                    crate::ecs::ui::resources::PropertyValue::Usize(v),
                ) => {
                    let v = *v;
                    world.ui_dropdown_set_value(entity, v);
                }
                (UiWidgetState::TabBar(_), crate::ecs::ui::resources::PropertyValue::Usize(v)) => {
                    let v = *v;
                    world.ui_tab_bar_set_value(entity, v);
                }
                (
                    UiWidgetState::ColorPicker(_),
                    crate::ecs::ui::resources::PropertyValue::Vec4(v),
                ) => {
                    let v = *v;
                    world.ui_color_picker_set_value(entity, v);
                }
                (UiWidgetState::Splitter(_), crate::ecs::ui::resources::PropertyValue::F32(v)) => {
                    if let Some(UiWidgetState::Splitter(data)) =
                        world.ui.get_ui_widget_state_mut(entity)
                    {
                        data.ratio = *v;
                    }
                }
                (
                    UiWidgetState::RangeSlider(_),
                    crate::ecs::ui::resources::PropertyValue::Vec4(v),
                ) => {
                    world.ui_range_slider_set_values(entity, v.x, v.y);
                }
                _ => {}
            }
        }
    }

    for _iteration in 0..8 {
        let mut dirty_props: Vec<(String, crate::ecs::ui::resources::PropertyValue)> = Vec::new();
        for (name, property_id) in &world.resources.retained_ui.named_properties {
            if let Some(bp) = world
                .resources
                .retained_ui
                .bound_properties
                .get(property_id)
                && (bp.dirty_from_widget || bp.dirty_from_code)
            {
                dirty_props.push((name.clone(), bp.value.clone()));
            }
        }

        if dirty_props.is_empty() {
            break;
        }

        for (name, _) in &dirty_props {
            if let Some(property_id) = world
                .resources
                .retained_ui
                .named_properties
                .get(name)
                .copied()
                && let Some(bp) = world
                    .resources
                    .retained_ui
                    .bound_properties
                    .get_mut(&property_id)
            {
                bp.dirty_from_widget = false;
                bp.dirty_from_code = false;
            }
        }

        let mut reactions = std::mem::take(&mut world.resources.retained_ui.property_reactions);
        for (name, value) in &dirty_props {
            if let Some(reaction_list) = reactions.get_mut(name) {
                for reaction in reaction_list.iter_mut() {
                    reaction(value, world);
                }
            }
        }
        let newly_registered = std::mem::take(&mut world.resources.retained_ui.property_reactions);
        for (name, mut new_reactions) in newly_registered {
            reactions
                .entry(name)
                .or_default()
                .append(&mut new_reactions);
        }
        world.resources.retained_ui.property_reactions = reactions;
    }
}