beuvy 0.1.0

Facade crate for beuvy-runtime plus optional declarative UI authoring.
Documentation
use super::resolve::resolve_runtime_condition;
use crate::ast::DeclarativeContainerKind;
use crate::runtime::state::{
    DeclarativeContainerSemantic, DeclarativeDisabledExpr, DeclarativeExplicitDisabled,
    DeclarativeFieldsetState, DeclarativeLocalState, DeclarativeRefRects,
    DeclarativeRootComputedLocals, DeclarativeRootViewModel, DeclarativeUiRuntimeValues,
};
use beuvy_runtime::button::{Button, ButtonLabel, DisabledButton};
use beuvy_runtime::input::{DisabledInput, InputField, set_input_disabled};
use beuvy_runtime::text::FontResource;
use beuvy_runtime::{
    Select, SelectPanel, selected_option, sync_select_label, trigger_label_entity,
};
use bevy::prelude::*;

pub(crate) fn sync_declarative_disabled(
    mut commands: Commands,
    values: Res<DeclarativeUiRuntimeValues>,
    font_resource: Res<FontResource>,
    localization: Option<Res<bevy_localization::Localization>>,
    parents: Query<&ChildOf>,
    local_states: Query<&DeclarativeLocalState>,
    computed: Query<&DeclarativeRootComputedLocals>,
    roots: Query<&DeclarativeRootViewModel>,
    ref_rects: Res<DeclarativeRefRects>,
    button_labels: Query<&ButtonLabel>,
    semantics: Query<&DeclarativeContainerSemantic>,
    fieldsets: Query<&DeclarativeFieldsetState>,
    mut inputs: Query<
        (
            Entity,
            &DeclarativeExplicitDisabled,
            Option<&DeclarativeDisabledExpr>,
            &InputField,
            Has<DisabledInput>,
        ),
        With<InputField>,
    >,
    buttons: Query<
        (
            Entity,
            &DeclarativeExplicitDisabled,
            Option<&DeclarativeDisabledExpr>,
            Has<DisabledButton>,
        ),
        (With<Button>, Without<InputField>),
    >,
    mut selects: Query<(
        Entity,
        &DeclarativeExplicitDisabled,
        Option<&DeclarativeDisabledExpr>,
        &mut Select,
    )>,
    mut panel_nodes: Query<&mut Node, With<SelectPanel>>,
) {
    for (entity, explicit_disabled, binding, field, disabled) in &mut inputs {
        let next = effective_disabled(
            entity,
            explicit_disabled.0,
            binding.map(|value| &value.0),
            &parents,
            &local_states,
            &computed,
            &roots,
            &values,
            &ref_rects,
            &semantics,
            &fieldsets,
        );
        if disabled != next {
            set_input_disabled(&mut commands, &font_resource, entity, field, next);
        }
    }

    for (entity, explicit_disabled, binding, disabled) in &buttons {
        let next = effective_disabled(
            entity,
            explicit_disabled.0,
            binding.map(|value| &value.0),
            &parents,
            &local_states,
            &computed,
            &roots,
            &values,
            &ref_rects,
            &semantics,
            &fieldsets,
        );
        if disabled == next {
            continue;
        }
        let Ok(mut entity_commands) = commands.get_entity(entity) else {
            continue;
        };
        if next {
            entity_commands
                .try_insert((DisabledButton, beuvy_runtime::interaction_style::UiDisabled));
        } else {
            entity_commands
                .try_remove::<DisabledButton>()
                .try_remove::<beuvy_runtime::interaction_style::UiDisabled>();
        }
    }

    for (entity, explicit_disabled, binding, mut select) in &mut selects {
        let next = effective_disabled(
            entity,
            explicit_disabled.0,
            binding.map(|value| &value.0),
            &parents,
            &local_states,
            &computed,
            &roots,
            &values,
            &ref_rects,
            &semantics,
            &fieldsets,
        );
        if select.disabled == next {
            continue;
        }
        select.disabled = next;
        if next {
            select.open = false;
            if let Ok(mut panel) = panel_nodes.get_mut(select.panel) {
                panel.display = Display::None;
            }
        }
        if let (Some(localization), Some(label_entity)) = (
            localization.as_deref(),
            trigger_label_entity(&button_labels, &select),
        ) {
            sync_select_label(
                &mut commands,
                Some(localization),
                label_entity,
                selected_option(&select),
                &select.value,
            );
        }
    }
}

fn effective_disabled(
    entity: Entity,
    explicit_disabled: bool,
    disabled_expr: Option<&crate::DeclarativeConditionExpr>,
    parents: &Query<&ChildOf>,
    local_states: &Query<&DeclarativeLocalState>,
    computed: &Query<&DeclarativeRootComputedLocals>,
    roots: &Query<&DeclarativeRootViewModel>,
    values: &DeclarativeUiRuntimeValues,
    ref_rects: &DeclarativeRefRects,
    semantics: &Query<&DeclarativeContainerSemantic>,
    fieldsets: &Query<&DeclarativeFieldsetState>,
) -> bool {
    explicit_disabled
        || disabled_expr.is_some_and(|expr| {
            resolve_runtime_condition(
                entity,
                expr,
                parents,
                local_states,
                computed,
                roots,
                values,
                ref_rects,
            )
        })
        || ancestor_fieldset_disabled(entity, parents, semantics, fieldsets)
}

fn ancestor_fieldset_disabled(
    mut entity: Entity,
    parents: &Query<&ChildOf>,
    semantics: &Query<&DeclarativeContainerSemantic>,
    fieldsets: &Query<&DeclarativeFieldsetState>,
) -> bool {
    while let Ok(parent) = parents.get(entity) {
        entity = parent.parent();
        if semantics
            .get(entity)
            .is_ok_and(|semantic| semantic.kind == DeclarativeContainerKind::Fieldset)
            && fieldsets.get(entity).is_ok_and(|fieldset| fieldset.disabled)
        {
            return true;
        }
    }
    false
}