beuvy 0.1.0

Facade crate for beuvy-runtime plus optional declarative UI authoring.
Documentation
use crate::runtime::state::{DeclarativeLabelForTarget, DeclarativeLabelNode};
use beuvy_runtime::input::{InputField, InputValueChangedMessage};
use bevy::input_focus::InputFocus;
use bevy::picking::pointer::PointerButton;
use bevy::prelude::*;

pub(crate) fn handle_declarative_label_click(
    mut events: MessageReader<Pointer<Click>>,
    mut input_focus: ResMut<InputFocus>,
    labels: Query<&DeclarativeLabelForTarget>,
    mut fields: ParamSet<(
        Query<(Entity, &Name, &InputField), With<InputField>>,
        Query<&mut InputField>,
    )>,
    mut value_changed: MessageWriter<InputValueChangedMessage>,
) {
    for event in events.read() {
        if event.button != PointerButton::Primary {
            continue;
        }
        let Ok(label) = labels.get(event.entity) else {
            continue;
        };
        let Some((target_entity, target_input_type, target_checked, target_name)) = fields
            .p0()
            .iter()
            .find(|(_, name, _)| name.as_str() == label.0.as_str())
            .map(|(entity, _, field)| (entity, field.input_type, field.checked, field.name.clone()))
        else {
            continue;
        };
        if let Ok(mut field) = fields.p1().get_mut(target_entity) {
            if field.input_type == beuvy_runtime::input::InputType::Checkbox {
                field.checked = !field.checked;
                value_changed.write(InputValueChangedMessage {
                    entity: target_entity,
                    name: field.name.clone(),
                    value: field.submitted_value(),
                    runtime_value: beuvy_runtime::input::InputRuntimeValue::Bool(field.checked),
                });
            } else if target_input_type == beuvy_runtime::input::InputType::Radio && !target_checked {
                let radio_group = target_name;
                let radio_targets = fields
                    .p0()
                    .iter()
                    .filter_map(|(entity, _, field)| {
                        (field.input_type == beuvy_runtime::input::InputType::Radio
                            && field.name == radio_group)
                            .then_some(entity)
                    })
                    .collect::<Vec<_>>();
                for radio_target in radio_targets {
                    if let Ok(mut radio_field) = fields.p1().get_mut(radio_target) {
                        let next_checked = radio_target == target_entity;
                        if radio_field.checked != next_checked {
                            radio_field.checked = next_checked;
                            value_changed.write(InputValueChangedMessage {
                                entity: radio_target,
                                name: radio_field.name.clone(),
                                value: radio_field.submitted_value(),
                                runtime_value: beuvy_runtime::input::InputRuntimeValue::Text(
                                    radio_field.value().to_string(),
                                ),
                            });
                        }
                    }
                }
            }
        }
        input_focus.set(target_entity);
    }
}

pub(crate) fn infer_wrapped_label_targets(
    mut commands: Commands,
    labels: Query<
        (Entity, &Children),
        (
            With<DeclarativeLabelNode>,
            Without<DeclarativeLabelForTarget>,
        ),
    >,
    inputs: Query<&Name, With<InputField>>,
) {
    for (entity, children) in &labels {
        let Some(target) = children
            .iter()
            .find_map(|child| inputs.get(child).ok().map(|name| name.as_str().to_string()))
        else {
            continue;
        };
        if let Ok(mut entity_commands) = commands.get_entity(entity) {
            entity_commands.try_insert(DeclarativeLabelForTarget(target));
        }
    }
}