beuvy 0.1.0

Facade crate for beuvy-runtime plus optional declarative UI authoring.
Documentation
use super::resolve::resolve_runtime_path;
use crate::runtime::state::{
    DeclarativeCheckedBinding, DeclarativeLocalState, DeclarativeModelBinding, DeclarativeRefRects,
    DeclarativeRootComputedLocals, DeclarativeRootViewModel, DeclarativeUiRuntimeValues,
    DeclarativeValueBinding,
};
use beuvy_runtime::button::ButtonLabel;
use beuvy_runtime::input::{
    DisabledInput, InputField, InputRuntimeValue, InputType, InputValueChangedMessage,
    set_input_value,
};
use beuvy_runtime::text::FontResource;
use beuvy_runtime::{
    Select, SelectValueChangedMessage, selected_option, sync_select_label, trigger_label_entity,
};
use bevy::prelude::*;

pub(crate) fn sync_declarative_field_values(
    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>,
    mut inputs: Query<(
        Entity,
        &DeclarativeValueBinding,
        Option<&DeclarativeCheckedBinding>,
        &mut InputField,
        Has<DisabledInput>,
    )>,
    mut selects: Query<(Entity, &DeclarativeValueBinding, &mut Select)>,
) {
    for (entity, binding, checked_binding, mut field, disabled) in &mut inputs {
        if matches!(field.input_type, InputType::Checkbox) {
            let binding_path = checked_binding
                .map(|binding| binding.0.as_str())
                .unwrap_or(&binding.0);
            if let Some(next_checked) = resolve_runtime_path(
                entity,
                binding_path,
                &parents,
                &local_states,
                &computed,
                &roots,
                &values,
                &ref_rects,
            )
            .and_then(|value| value.bool())
            {
                field.checked = next_checked;
            }
            continue;
        }
        if matches!(field.input_type, InputType::Radio) {
            let binding_path = checked_binding
                .map(|binding| binding.0.as_str())
                .unwrap_or(&binding.0);
            if let Some(next_value) = resolve_runtime_path(
                entity,
                binding_path,
                &parents,
                &local_states,
                &computed,
                &roots,
                &values,
                &ref_rects,
            )
            .and_then(|value| value.text().map(str::to_string))
            {
                field.checked = field.value() == next_value;
            }
            continue;
        }
        let Some(value) = resolve_runtime_path(
            entity,
            &binding.0,
            &parents,
            &local_states,
            &computed,
            &roots,
            &values,
            &ref_rects,
        )
        .and_then(|value| match field.input_type {
            InputType::Text | InputType::Textarea | InputType::Password => {
                value.text().map(str::to_string)
            }
            InputType::Number | InputType::Range => value
                .number()
                .map(|value| value.to_string())
                .or_else(|| value.text().map(str::to_string)),
            InputType::Checkbox | InputType::Radio => None,
        }) else {
            continue;
        };
        match field.input_type {
            InputType::Checkbox => {
                let next = value == "true";
                if field.checked != next {
                    field.checked = next;
                }
            }
            InputType::Radio => {
                let next = field.value() == value;
                if field.checked != next {
                    field.checked = next;
                }
            }
            _ => {
                set_input_value(&mut commands, &font_resource, &mut field, disabled, value);
            }
        }
    }

    for (entity, binding, mut select) in &mut selects {
        let Some(value) = resolve_runtime_path(
            entity,
            &binding.0,
            &parents,
            &local_states,
            &computed,
            &roots,
            &values,
            &ref_rects,
        )
        .and_then(|value| value.text().map(str::to_string)) else {
            continue;
        };
        if select.value == value {
            continue;
        }
        select.value = value;
        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,
            );
        }
    }
}

pub(crate) fn write_input_values_to_runtime_store(
    mut input_changes: MessageReader<InputValueChangedMessage>,
    mut values: ResMut<DeclarativeUiRuntimeValues>,
    bindings: Query<
        (&DeclarativeValueBinding, &InputField),
        (With<InputField>, With<DeclarativeModelBinding>),
    >,
) {
    for change in input_changes.read() {
        let Ok((binding, _field)) = bindings.get(change.entity) else {
            continue;
        };
        match &change.runtime_value {
            InputRuntimeValue::Text(value) => values.set(binding.0.clone(), value.clone()),
            InputRuntimeValue::Bool(value) => values.set(binding.0.clone(), *value),
            InputRuntimeValue::Number(value) => values.set(binding.0.clone(), *value),
        }
    }
}

pub(crate) fn write_select_values_to_runtime_store(
    mut select_changes: MessageReader<SelectValueChangedMessage>,
    mut values: ResMut<DeclarativeUiRuntimeValues>,
    bindings: Query<&DeclarativeValueBinding, (With<Select>, With<DeclarativeModelBinding>)>,
) {
    for change in select_changes.read() {
        let Ok(binding) = bindings.get(change.entity) else {
            continue;
        };
        values.set(binding.0.clone(), change.value.clone());
    }
}