bevy_dioxus/src/apply_mutations.rs

360 lines
14 KiB
Rust

use crate::{events::is_supported_event, parse_attributes::set_attribute, tick::IntrinsicTextNode};
use bevy::{
ecs::{entity::Entity, system::Command, world::World},
hierarchy::{BuildWorldChildren, Children, DespawnRecursive, Parent},
prelude::default,
render::color::Color,
text::{Text, TextLayoutInfo, TextStyle},
ui::{
node_bundles::{NodeBundle, TextBundle},
widget::TextFlags,
*,
},
utils::{EntityHashMap, HashMap},
};
use dioxus::core::{
BorrowedAttributeValue, ElementId, Mutation, Mutations, Template, TemplateAttribute,
TemplateNode,
};
pub fn apply_mutations(
mutations: Mutations,
element_id_to_bevy_ui_entity: &mut HashMap<ElementId, Entity>,
bevy_ui_entity_to_element_id: &mut EntityHashMap<Entity, ElementId>,
templates: &mut HashMap<String, BevyTemplate>,
root_entity: Entity,
world: &mut World,
) {
for new_template in mutations.templates {
templates.insert(
new_template.name.to_owned(),
BevyTemplate::from_dioxus(&new_template),
);
}
element_id_to_bevy_ui_entity.insert(ElementId(0), root_entity);
bevy_ui_entity_to_element_id.insert(root_entity, ElementId(0));
let mut stack = vec![root_entity];
for edit in mutations.edits {
match edit {
Mutation::AppendChildren { id, m } => {
let mut parent = world.entity_mut(element_id_to_bevy_ui_entity[&id]);
for child in stack.drain((stack.len() - m)..) {
parent.add_child(child);
}
}
Mutation::AssignId { path, id } => {
let mut entity = *stack.last().unwrap();
for index in path {
entity = world.entity(entity).get::<Children>().unwrap()[*index as usize];
}
element_id_to_bevy_ui_entity.insert(id, entity);
bevy_ui_entity_to_element_id.insert(entity, id);
}
Mutation::CreatePlaceholder { id } => {
let entity = world.spawn(NodeBundle::default()).id();
element_id_to_bevy_ui_entity.insert(id, entity);
bevy_ui_entity_to_element_id.insert(entity, id);
stack.push(entity);
}
Mutation::CreateTextNode { value, id } => {
let entity =
BevyTemplateNode::from_dioxus(&TemplateNode::Text { text: value }).spawn(world);
element_id_to_bevy_ui_entity.insert(id, entity);
bevy_ui_entity_to_element_id.insert(entity, id);
stack.push(entity);
}
Mutation::HydrateText { path, value, id } => {
let mut entity = *stack.last().unwrap();
for index in path {
entity = world.entity(entity).get::<Children>().unwrap()[*index as usize];
}
world.entity_mut(entity).insert((
Text::from_section(value, TextStyle::default()),
TextLayoutInfo::default(),
TextFlags::default(),
ContentSize::default(),
));
element_id_to_bevy_ui_entity.insert(id, entity);
bevy_ui_entity_to_element_id.insert(entity, id);
}
Mutation::LoadTemplate { name, index, id } => {
let entity = templates[name].roots[index].spawn(world);
element_id_to_bevy_ui_entity.insert(id, entity);
bevy_ui_entity_to_element_id.insert(entity, id);
stack.push(entity);
}
Mutation::ReplaceWith { id, m } => {
let existing = element_id_to_bevy_ui_entity[&id];
let existing_parent = world.entity(existing).get::<Parent>().unwrap().get();
let mut existing_parent = world.entity_mut(existing_parent);
let existing_index = existing_parent
.get::<Children>()
.unwrap()
.iter()
.position(|child| *child == existing)
.unwrap();
existing_parent.insert_children(existing_index, &stack.split_off(stack.len() - m));
DespawnRecursive { entity: existing }.apply(world);
// TODO: We're not removing child entities from the element maps
if let Some(existing_element_id) = bevy_ui_entity_to_element_id.remove(&existing) {
element_id_to_bevy_ui_entity.remove(&existing_element_id);
}
}
Mutation::ReplacePlaceholder { path, m } => {
let mut existing = stack[stack.len() - m - 1];
for index in path {
existing = world.entity(existing).get::<Children>().unwrap()[*index as usize];
}
let existing_parent = world.entity(existing).get::<Parent>().unwrap().get();
let mut existing_parent = world.entity_mut(existing_parent);
let existing_index = existing_parent
.get::<Children>()
.unwrap()
.iter()
.position(|child| *child == existing)
.unwrap();
existing_parent.insert_children(existing_index, &stack.split_off(stack.len() - m));
DespawnRecursive { entity: existing }.apply(world);
// TODO: We're not removing child entities from the element maps
if let Some(existing_element_id) = bevy_ui_entity_to_element_id.remove(&existing) {
element_id_to_bevy_ui_entity.remove(&existing_element_id);
}
}
Mutation::InsertAfter { id, m } => {
let entity = element_id_to_bevy_ui_entity[&id];
let parent = world.entity(entity).get::<Parent>().unwrap().get();
let mut parent = world.entity_mut(parent);
let index = parent
.get::<Children>()
.unwrap()
.iter()
.position(|child| *child == entity)
.unwrap();
parent.insert_children(index + 1, &stack.split_off(stack.len() - m));
}
Mutation::InsertBefore { id, m } => {
let existing = element_id_to_bevy_ui_entity[&id];
let parent = world.entity(existing).get::<Parent>().unwrap().get();
let mut parent = world.entity_mut(parent);
let index = parent
.get::<Children>()
.unwrap()
.iter()
.position(|child| *child == existing)
.unwrap();
parent.insert_children(index, &stack.split_off(stack.len() - m));
}
Mutation::SetAttribute {
name,
value,
id,
ns: _,
} => {
let value = match value {
BorrowedAttributeValue::Text(value) => value,
value => {
panic!("Encountered unsupported bevy_dioxus attribute `{name}: {value:?}`.")
}
};
let (mut style, mut background_color, mut text) = world
.query::<(&mut Style, &mut BackgroundColor, Option<&mut Text>)>()
.get_mut(world, element_id_to_bevy_ui_entity[&id])
.unwrap();
set_attribute(
name,
value,
&mut style,
&mut background_color,
text.as_deref_mut(),
);
}
Mutation::SetText { value, id } => {
world
.entity_mut(element_id_to_bevy_ui_entity[&id])
.insert(Text::from_section(value, TextStyle::default()));
}
Mutation::NewEventListener { name, id: _ } => {
if !is_supported_event(name) {
panic!("Encountered unsupported bevy_dioxus event `{name}`.");
}
}
Mutation::RemoveEventListener { .. } => {}
Mutation::Remove { id } => {
let entity = element_id_to_bevy_ui_entity[&id];
DespawnRecursive { entity }.apply(world);
// TODO: We're not removing child entities from the element maps
if let Some(existing_element_id) = bevy_ui_entity_to_element_id.remove(&entity) {
element_id_to_bevy_ui_entity.remove(&existing_element_id);
}
}
Mutation::PushRoot { id } => stack.push(element_id_to_bevy_ui_entity[&id]),
}
}
}
pub struct BevyTemplate {
roots: Box<[BevyTemplateNode]>,
}
enum BevyTemplateNode {
Node {
style: (Style, BackgroundColor),
children: Box<[Self]>,
},
TextNode {
text: Text,
style: (Style, BackgroundColor),
children: Box<[Self]>,
},
IntrinsicTextNode(Text),
}
impl BevyTemplate {
fn from_dioxus(template: &Template) -> Self {
Self {
roots: template
.roots
.iter()
.map(BevyTemplateNode::from_dioxus)
.collect(),
}
}
}
impl BevyTemplateNode {
fn from_dioxus(node: &TemplateNode) -> Self {
match node {
TemplateNode::Element {
tag: "node",
namespace: Some("bevy_ui"),
attrs,
children,
} => {
let (style, background_color, _) = parse_template_attributes(attrs);
Self::Node {
style: (style, background_color),
children: children.iter().map(Self::from_dioxus).collect(),
}
}
TemplateNode::Element {
tag: "text",
namespace: Some("bevy_ui"),
attrs,
children,
} => {
let (style, background_color, text) = parse_template_attributes(attrs);
Self::TextNode {
text,
style: (style, background_color),
children: children.iter().map(Self::from_dioxus).collect(),
}
}
TemplateNode::Text { text } => {
Self::IntrinsicTextNode(Text::from_section(*text, TextStyle::default()))
}
TemplateNode::Dynamic { id: _ } => Self::Node {
style: (Style::default(), Color::NONE.into()),
children: Box::new([]),
},
TemplateNode::DynamicText { id: _ } => {
Self::IntrinsicTextNode(Text::from_section("", TextStyle::default()))
}
TemplateNode::Element {
tag,
namespace: None,
..
} => {
panic!("Encountered unsupported bevy_dioxus tag `{tag}`.")
}
TemplateNode::Element {
tag,
namespace: Some(namespace),
..
} => {
panic!("Encountered unsupported bevy_dioxus tag `{namespace}::{tag}`.")
}
}
}
fn spawn(&self, world: &mut World) -> Entity {
match self {
BevyTemplateNode::Node {
style: (style, background_color),
children,
} => {
let children = children
.iter()
.map(|child| child.spawn(world))
.collect::<Box<[_]>>();
world
.spawn(NodeBundle {
style: style.clone(),
background_color: background_color.clone(),
..default()
})
.push_children(&children)
.id()
}
BevyTemplateNode::TextNode {
text,
style: (style, background_color),
children,
} => {
let children = children
.iter()
.map(|child| child.spawn(world))
.collect::<Box<[_]>>();
world
.spawn(TextBundle {
text: text.clone(),
style: style.clone(),
background_color: background_color.clone(),
..default()
})
.push_children(&children)
.id()
}
Self::IntrinsicTextNode(text) => world
.spawn((
TextBundle {
text: text.clone(),
..default()
},
IntrinsicTextNode,
))
.id(),
}
}
}
fn parse_template_attributes(attributes: &[TemplateAttribute]) -> (Style, BackgroundColor, Text) {
let mut style = Style::default();
let mut background_color = Color::NONE.into();
let mut text = Text::from_section("", TextStyle::default());
for attribute in attributes {
if let TemplateAttribute::Static {
name,
value,
namespace: _,
} = attribute
{
set_attribute(
name,
value,
&mut style,
&mut background_color,
Some(&mut text),
);
}
}
(style, background_color, text)
}