bevy_vista 0.17.1

A visual UI editor plugin for Bevy with inspector-driven editing and .vista.ron serialization.
Documentation
use bevy::prelude::*;
use bevy::reflect::PartialReflect;

use crate::core::inspector::{
    BlueprintCommand, BlueprintNodeId, InspectorEditorRegistry, InspectorEntryDescriptor,
    InspectorFieldEditor, WidgetBlueprintDocument, apply_blueprint_command, read_reflect_path,
    read_reflect_path_mut,
};
use crate::core::widget::{WidgetRegistry, WidgetStyle};

use super::{InspectorBindingTarget, InspectorControlRegistry, InspectorPanelState};

pub(crate) fn selected_node_style<'a>(
    panel_state: &InspectorPanelState,
    document: &'a WidgetBlueprintDocument,
) -> Option<&'a WidgetStyle> {
    let node_id = panel_state.selected_node?;
    let node = document.nodes.get(&node_id)?;
    if !panel_state.visible {
        return None;
    }
    Some(&node.style)
}

pub(crate) fn selected_node_widget_reflect(
    panel_state: &InspectorPanelState,
    document: &WidgetBlueprintDocument,
    widget_registry: &WidgetRegistry,
    inspector_registry: &InspectorEditorRegistry,
    control_registry: &InspectorControlRegistry,
) -> Option<Box<dyn PartialReflect>> {
    let node_id = panel_state.selected_node?;
    let node = document.nodes.get(&node_id)?;
    if !panel_state.visible {
        return None;
    }
    let registration = widget_registry.get_widget_by_path(&node.widget_path)?;
    let mut value = registration.default_inspector_value()?;
    let reflect = value.as_mut();
    for entry in registration.inspector_entries(inspector_registry) {
        let InspectorEntryDescriptor::Field(field) = entry else {
            continue;
        };
        let Some(raw) = node.props.get(&field.field_path) else {
            continue;
        };
        let Some(target) = read_reflect_path_mut(reflect, &field.field_path) else {
            continue;
        };
        let _ = control_registry.apply_serialized_value(field.editor, target, raw);
    }
    Some(value)
}

pub(crate) fn selected_node_widget_default_reflect(
    panel_state: &InspectorPanelState,
    document: &WidgetBlueprintDocument,
    widget_registry: &WidgetRegistry,
) -> Option<Box<dyn PartialReflect>> {
    let node_id = panel_state.selected_node?;
    let node = document.nodes.get(&node_id)?;
    if !panel_state.visible {
        return None;
    }
    let registration = widget_registry.get_widget_by_path(&node.widget_path)?;
    registration.default_inspector_value()
}

fn apply_widget_prop_change(
    node_id: BlueprintNodeId,
    field_path: &str,
    serialized: String,
    document: &mut WidgetBlueprintDocument,
    widget_registry: &WidgetRegistry,
) {
    let _ = apply_blueprint_command(
        BlueprintCommand::SetNodeProp {
            node: node_id,
            key: field_path.to_owned(),
            value: serialized,
        },
        document,
        widget_registry,
    );
}

pub(crate) fn clear_widget_prop_change(
    node_id: BlueprintNodeId,
    field_path: &str,
    document: &mut WidgetBlueprintDocument,
    widget_registry: &WidgetRegistry,
) {
    let _ = apply_blueprint_command(
        BlueprintCommand::RemoveNodeProp {
            node: node_id,
            key: field_path.to_owned(),
        },
        document,
        widget_registry,
    );
}

pub(crate) fn store_widget_prop_change(
    node_id: BlueprintNodeId,
    field_path: &str,
    editor: InspectorFieldEditor,
    field: &dyn PartialReflect,
    document: &mut WidgetBlueprintDocument,
    widget_registry: &WidgetRegistry,
    control_registry: &InspectorControlRegistry,
) {
    let Some(node) = document.nodes.get(&node_id) else {
        return;
    };
    let Some(serialized) = control_registry.serialize_value(editor, field) else {
        return;
    };
    let Some(registration) = widget_registry.get_widget_by_path(&node.widget_path) else {
        apply_widget_prop_change(node_id, field_path, serialized, document, widget_registry);
        return;
    };
    let Some(default_value) = registration.default_inspector_value() else {
        apply_widget_prop_change(node_id, field_path, serialized, document, widget_registry);
        return;
    };
    let Some(default_field) = read_reflect_path(default_value.as_ref(), field_path) else {
        apply_widget_prop_change(node_id, field_path, serialized, document, widget_registry);
        return;
    };

    if field.reflect_partial_eq(default_field).unwrap_or(false) {
        clear_widget_prop_change(node_id, field_path, document, widget_registry);
        return;
    }

    apply_widget_prop_change(node_id, field_path, serialized, document, widget_registry);
}

pub(crate) fn find_ancestor_with<F>(
    mut entity: Entity,
    parents: &Query<&ChildOf>,
    predicate: F,
) -> Option<Entity>
where
    F: Fn(Entity) -> bool,
{
    loop {
        if predicate(entity) {
            return Some(entity);
        }
        let Ok(parent) = parents.get(entity) else {
            return None;
        };
        entity = parent.parent();
    }
}

pub(crate) fn apply_style_change(
    node_id: BlueprintNodeId,
    style: WidgetStyle,
    document: &mut WidgetBlueprintDocument,
    widget_registry: &WidgetRegistry,
) {
    let _ = apply_blueprint_command(
        BlueprintCommand::SetNodeStyle {
            node: node_id,
            style,
        },
        document,
        widget_registry,
    );
}

pub(crate) fn selected_binding_source<'a>(
    style: &'a WidgetStyle,
    widget_reflect: Option<&'a dyn PartialReflect>,
    target: &InspectorBindingTarget,
    field_path: &str,
) -> Option<&'a dyn PartialReflect> {
    match target {
        InspectorBindingTarget::Style => {
            read_reflect_path(style as &dyn PartialReflect, field_path)
        }
        InspectorBindingTarget::WidgetProp => {
            widget_reflect.and_then(|value| read_reflect_path(value, field_path))
        }
    }
}

pub(crate) fn apply_selected_field_change<F>(
    panel_state: &InspectorPanelState,
    document: &mut WidgetBlueprintDocument,
    widget_registry: &WidgetRegistry,
    inspector_registry: &InspectorEditorRegistry,
    control_registry: &InspectorControlRegistry,
    target: &InspectorBindingTarget,
    field_path: &str,
    editor: InspectorFieldEditor,
    apply: F,
) -> bool
where
    F: FnOnce(&mut dyn PartialReflect) -> bool,
{
    let Some(node_id) = panel_state.selected_node else {
        return false;
    };

    match target {
        InspectorBindingTarget::WidgetProp => {
            let Some(mut value) = selected_node_widget_reflect(
                panel_state,
                document,
                widget_registry,
                inspector_registry,
                control_registry,
            ) else {
                return false;
            };
            let Some(field) = read_reflect_path_mut(value.as_mut(), field_path) else {
                return false;
            };
            if !apply(field) {
                return false;
            }
            store_widget_prop_change(
                node_id,
                field_path,
                editor,
                field,
                document,
                widget_registry,
                control_registry,
            );
            true
        }
        InspectorBindingTarget::Style => {
            let Some(mut style) = document.nodes.get(&node_id).map(|node| node.style.clone())
            else {
                return false;
            };
            let style_reflect: &mut dyn PartialReflect = &mut style;
            let Some(field) = read_reflect_path_mut(style_reflect, field_path) else {
                return false;
            };
            if !apply(field) {
                return false;
            }
            apply_style_change(node_id, style, document, widget_registry);
            true
        }
    }
}