beuvy 0.1.0

Facade crate for beuvy-runtime plus optional declarative UI authoring.
Documentation
use super::walk_descendants;
use crate::ast::DeclarativeContainerKind;
use crate::runtime::state::{DeclarativeContainerSemantic, DeclarativeNodeId};
use beuvy_runtime::input::{InputField, InputRuntimeValue, InputValueChangedMessage};
use beuvy_runtime::select::{Select, SelectPanel};
use bevy::input_focus::InputFocus;
use bevy::prelude::*;

pub(crate) fn handle_label_clicks(
    mut clicks: MessageReader<Pointer<Click>>,
    mut input_focus: ResMut<InputFocus>,
    semantics: Query<&DeclarativeContainerSemantic>,
    children: Query<&Children>,
    node_ids: Query<(Entity, &DeclarativeNodeId)>,
    mut inputs: Query<(Entity, &mut InputField)>,
    mut selects: Query<(Entity, &mut Select)>,
    mut panels: Query<&mut Node, With<SelectPanel>>,
    mut input_changes: MessageWriter<InputValueChangedMessage>,
) {
    for click in clicks.read() {
        let Ok(semantic) = semantics.get(click.entity) else {
            continue;
        };
        if semantic.kind != DeclarativeContainerKind::Label {
            continue;
        }

        if let Some(target) = semantic
            .label_for
            .as_deref()
            .and_then(|name| resolve_named_target(name, &inputs, &selects, &node_ids))
            .or_else(|| resolve_nested_target(click.entity, &children, &inputs, &selects))
        {
            activate_label_target(
                target,
                &mut input_focus,
                &mut inputs,
                &mut selects,
                &mut panels,
                &mut input_changes,
            );
        }
    }
}

fn resolve_named_target(
    name: &str,
    inputs: &Query<(Entity, &mut InputField)>,
    selects: &Query<(Entity, &mut Select)>,
    node_ids: &Query<(Entity, &DeclarativeNodeId)>,
) -> Option<Entity> {
    for (entity, input) in inputs.iter() {
        if input.name == name {
            return Some(entity);
        }
    }
    for (entity, select) in selects.iter() {
        if select.name == name {
            return Some(entity);
        }
    }
    for (entity, node_id) in node_ids.iter() {
        if node_id.0 == name {
            return Some(entity);
        }
    }
    None
}

fn resolve_nested_target(
    label: Entity,
    children: &Query<&Children>,
    inputs: &Query<(Entity, &mut InputField)>,
    selects: &Query<(Entity, &mut Select)>,
) -> Option<Entity> {
    let mut target = None;
    walk_descendants(label, children, &mut |entity| {
        if target.is_some() {
            return;
        }
        if inputs.contains(entity) || selects.contains(entity) {
            target = Some(entity);
        }
    });
    target
}

fn activate_label_target(
    target: Entity,
    input_focus: &mut ResMut<InputFocus>,
    inputs: &mut Query<(Entity, &mut InputField)>,
    selects: &mut Query<(Entity, &mut Select)>,
    panels: &mut Query<&mut Node, With<SelectPanel>>,
    input_changes: &mut MessageWriter<InputValueChangedMessage>,
) {
    if let Ok((entity, mut input)) = inputs.get_mut(target) {
        input_focus.set(entity);
        if input.is_toggle() {
            match input.input_type {
                beuvy_runtime::input::InputType::Checkbox => input.checked = !input.checked,
                beuvy_runtime::input::InputType::Radio => input.checked = true,
                _ => {}
            }
            let runtime_value = match input.input_type {
                beuvy_runtime::input::InputType::Checkbox => InputRuntimeValue::Bool(input.checked),
                beuvy_runtime::input::InputType::Radio => {
                    InputRuntimeValue::Text(input.value().to_string())
                }
                beuvy_runtime::input::InputType::Number
                | beuvy_runtime::input::InputType::Range => input
                    .numeric_value()
                    .map(|value| InputRuntimeValue::Number(value as f64))
                    .unwrap_or_else(|| InputRuntimeValue::Text(input.value().to_string())),
                beuvy_runtime::input::InputType::Text
                | beuvy_runtime::input::InputType::Textarea
                | beuvy_runtime::input::InputType::Password => {
                    InputRuntimeValue::Text(input.value().to_string())
                }
            };
            input_changes.write(InputValueChangedMessage {
                entity,
                name: input.name.clone(),
                value: input.value().to_string(),
                runtime_value,
            });
        }
        return;
    }

    if let Ok((_entity, mut select)) = selects.get_mut(target) {
        select.open = true;
        if let Ok(mut panel) = panels.get_mut(select.panel) {
            panel.display = Display::Flex;
        }
    }
}