beuvy 0.1.0

Facade crate for beuvy-runtime plus optional declarative UI authoring.
Documentation
use super::evaluate_runtime_expr_with_locals;
use crate::ast::{DeclarativeBinaryOp, DeclarativeRuntimeExpr};
use crate::runtime::context::resolve_path;
use crate::runtime::state::{
    DeclarativeLocalState, DeclarativeRefRects, DeclarativeRootComputedLocals,
    DeclarativeRootViewModel, DeclarativeUiRuntimeValues,
};
use crate::runtime::sync::resolve::resolve_runtime_path_with_extra_locals;
use crate::value::UiValue;
use bevy::prelude::*;
use std::collections::HashMap;

pub(super) fn resolve_runtime_path_in_scope(
    entity: Entity,
    path: &str,
    parents: &Query<&ChildOf>,
    states: &Query<&DeclarativeLocalState>,
    computed: &Query<&DeclarativeRootComputedLocals>,
    roots: &Query<&DeclarativeRootViewModel>,
    values: &DeclarativeUiRuntimeValues,
    ref_rects: &DeclarativeRefRects,
    stack: &mut Vec<String>,
    locals: &HashMap<String, UiValue>,
) -> Option<UiValue> {
    if let Some(value) = locals.get(path) {
        return Some(value.clone());
    }
    if let Some((head, tail)) = path.split_once('.')
        && let Some(value) = locals.get(head)
    {
        return resolve_path(value, tail).cloned();
    }
    resolve_runtime_path_with_extra_locals(
        entity, path, None, parents, states, computed, roots, values, ref_rects, stack,
    )
}

pub(super) fn evaluate_binary_expr(
    left: UiValue,
    op: DeclarativeBinaryOp,
    right: UiValue,
) -> Option<UiValue> {
    match op {
        DeclarativeBinaryOp::Add => Some(UiValue::from(left.number()? + right.number()?)),
        DeclarativeBinaryOp::Subtract => Some(UiValue::from(left.number()? - right.number()?)),
        DeclarativeBinaryOp::Multiply => Some(UiValue::from(left.number()? * right.number()?)),
        DeclarativeBinaryOp::Divide => Some(UiValue::from(left.number()? / right.number()?)),
        DeclarativeBinaryOp::LessThan => Some(UiValue::from(left.number()? < right.number()?)),
        DeclarativeBinaryOp::LessThanOrEqual => {
            Some(UiValue::from(left.number()? <= right.number()?))
        }
        DeclarativeBinaryOp::GreaterThan => Some(UiValue::from(left.number()? > right.number()?)),
        DeclarativeBinaryOp::GreaterThanOrEqual => {
            Some(UiValue::from(left.number()? >= right.number()?))
        }
        DeclarativeBinaryOp::Equal => Some(UiValue::from(left == right)),
        DeclarativeBinaryOp::NotEqual => Some(UiValue::from(left != right)),
    }
}

pub(super) fn evaluate_math_args(
    entity: Entity,
    args: &[DeclarativeRuntimeExpr],
    parents: &Query<&ChildOf>,
    states: &Query<&DeclarativeLocalState>,
    computed: &Query<&DeclarativeRootComputedLocals>,
    roots: &Query<&DeclarativeRootViewModel>,
    values: &DeclarativeUiRuntimeValues,
    ref_rects: &DeclarativeRefRects,
    stack: &mut Vec<String>,
    locals: &mut HashMap<String, UiValue>,
) -> Option<Vec<f64>> {
    let mut numbers = Vec::with_capacity(args.len());
    for arg in args {
        numbers.push(
            evaluate_runtime_expr_with_locals(
                entity, arg, parents, states, computed, roots, values, ref_rects, stack, locals,
            )?
            .number()?,
        );
    }
    if numbers.is_empty() {
        return None;
    }
    Some(numbers)
}

pub(crate) fn truthy(value: &UiValue) -> bool {
    match value {
        UiValue::Null => false,
        UiValue::Bool(value) => *value,
        UiValue::Number(number) => match number {
            crate::DeclarativeNumber::I32(value) => *value != 0,
            crate::DeclarativeNumber::I64(value) => *value != 0,
            crate::DeclarativeNumber::F32(value) => *value != 0.0,
            crate::DeclarativeNumber::F64(value) => *value != 0.0,
        },
        UiValue::Text(value) => !value.is_empty(),
        UiValue::Object(fields) => !fields.is_empty(),
        UiValue::List(items) => !items.is_empty(),
    }
}

pub(super) fn evaluate_anchor_popup(
    anchor_rect: &UiValue,
    shell_rect: &UiValue,
    popup_width: f32,
    popup_min_height: f32,
    gap: f32,
    margin: f32,
) -> Option<UiValue> {
    let anchor_left = field_number(anchor_rect, "left")? as f32;
    let anchor_top = field_number(anchor_rect, "top")? as f32;
    let anchor_width = field_number(anchor_rect, "width")? as f32;
    let shell_left = field_number(shell_rect, "left")? as f32;
    let shell_top = field_number(shell_rect, "top")? as f32;
    let shell_width = field_number(shell_rect, "width")? as f32;
    let shell_height = field_number(shell_rect, "height")? as f32;

    let anchor_left_local = anchor_left - shell_left;
    let anchor_top_local = anchor_top - shell_top;
    let preferred_left = anchor_left_local + anchor_width + gap;
    let left = if preferred_left + popup_width + margin <= shell_width {
        preferred_left
    } else {
        (anchor_left_local - popup_width - gap).max(margin)
    };
    let top = anchor_top_local.clamp(
        margin,
        (shell_height - popup_min_height - margin).max(margin),
    );

    Some(UiValue::object([
        ("left", UiValue::from(left)),
        ("top", UiValue::from(top)),
    ]))
}

fn field_number(value: &UiValue, name: &str) -> Option<f64> {
    value.field(name)?.number()
}