diff --git a/studio-frontend/src/rust-machine.ts b/studio-frontend/src/rust-machine.ts index 780b950..8ae0533 100644 --- a/studio-frontend/src/rust-machine.ts +++ b/studio-frontend/src/rust-machine.ts @@ -41,10 +41,20 @@ function unpack_vnode( if (id != null) { console.log("onchange", id, input.value); const value = input.value; - register_new_change(id, value); + register_new_change(id, "onchange", value); } } } + if (node_data["onclick"] == true) { + element.onclick = (event: Event) => { + var input = event.target as HTMLInputElement; + const id = input.getAttribute("id"); + if (id != null) { + console.log("onclick", id); + register_new_change(id, "onclick", ""); + } + } + } return element; } } @@ -225,6 +235,20 @@ function append_style(parent : HTMLElement) { align-items: center; } + .blocking-button { + background-color: #3333336e; /* Темный фон */ + color: #fff; /* Светлый текст */ + border: none; /* Убрать стандартную рамку */ + border-radius: 7px; /* Закругленные границы */ + padding: 5px; /* Внутренний отступ */ + outline: none; /* Убрать контур при фокусе */ + } + + .blocking-button:hover { + border: 2px solid #555; /* Стиль границы при фокусе */ + background-color: #222; /* Немного темнее фон при фокусе */ + } + `; diff --git a/studio/src/ui/inspector.rs b/studio/src/ui/inspector.rs index 8dbfb92..fab2253 100644 --- a/studio/src/ui/inspector.rs +++ b/studio/src/ui/inspector.rs @@ -1,8 +1,9 @@ use bevy::{ecs::world::unsafe_world_cell::UnsafeEntityCell, prelude::*, window::PrimaryWindow}; +use space_editor::space_prefab::component::MeshPrimitivePrefab; use crate::{ui::inspector::wasm::notify_dom_changed, wasm::StaticBuffer}; -use super::utils::{show_reflect, Element, InspectedEntity, ReflectChange, VirtualDom}; +use super::utils::{blocking_component, show_reflect, Element, InspectedEntity, ReflectChange, VirtualDom}; pub struct InspectorPlugin; @@ -18,6 +19,10 @@ impl Plugin for InspectorPlugin { app.world .resource_mut::() .add_simple::(); + + app.world + .resource_mut::() + .add_simple::(); } } @@ -69,8 +74,28 @@ fn show_selected(world: &mut World) { let cell = world.as_unsafe_world_cell(); let mut virtial_dom = cell.get_resource_mut::().unwrap(); + //Process events from real DOM + if let Some(changes) = super::utils::wasm::ONCHAGE_BUFFER.take_message() { + for (id, kind, value) in changes { + info!("change: {} {} {}", id, kind, value); + if kind == "onchange" { + info!("change: {} : {}", id, value); + virtial_dom + .on_change_events + .execute(cell.world_mut(), id, value); + } else if kind == "onclick" { + info!("click: {} : {}", id, value); + virtial_dom + .on_click_events + .execute(cell.world_mut(), id, value); + } + } + } + + let blocking_element = blocking_component(); + let Some(selection) = cell.get_resource::() else { - let muts = virtial_dom.render(None); + let muts = virtial_dom.render(blocking_element); let q_window = cell .world_mut() .query_filtered::<&Window, With>() @@ -87,16 +112,7 @@ fn show_selected(world: &mut World) { let _app_type_registry = cell.get_resource::().unwrap(); let e = cell.get_entity(selected_entity).unwrap(); - if let Some(changes) = super::utils::wasm::ONCHAGE_BUFFER.take_message() { - for (id, kind, value) in changes { - if kind == "onchange" { - info!("change: {} : {}", id, value); - virtial_dom - .on_change_events - .execute(cell.world_mut(), id, value); - } - } - } + let mut inspected = InspectedEntity::none(); inspected.id = Some(e.id()); @@ -137,7 +153,17 @@ fn show_selected(world: &mut World) { ..Default::default() }); - let muts = virtial_dom.render(Some(element)); + let super_root = Element::Component(super::utils::Component { + tag: "div".to_string(), + attributes: vec![ + ("style".to_string(), "height: 100%; display: flex; flex-direction: column".to_string()), + ], + children: vec![blocking_element.unwrap(), element], + ..default() + }); + + + let muts = virtial_dom.render(Some(super_root)); let q_window = cell .world_mut() diff --git a/studio/src/ui/utils.rs b/studio/src/ui/utils.rs index 8ba4ee0..5d43a81 100644 --- a/studio/src/ui/utils.rs +++ b/studio/src/ui/utils.rs @@ -1,5 +1,8 @@ use ::serde::{Deserialize, Serialize}; -use bevy::{ecs::component::ComponentId, prelude::*, reflect::ReflectFromPtr, utils::HashMap}; +use bevy::{ecs::component::ComponentId, prelude::*, reflect::{DynamicEnum, DynamicStruct, DynamicVariant, ReflectFromPtr, TypeInfo, VariantInfo}, utils::HashMap}; +use space_editor::{prelude::PrefabMarker, space_prefab::component::*}; + +use crate::scene::SceneElement; trait NodeProp: Clone + PartialEq {} @@ -83,11 +86,18 @@ impl VirtualDom { onchange_enabled = true; } + let mut onclick_enabled = false; + if let Some(onclick) = component.onclick { + self.on_click_events.add_event(id.clone(), onclick); + onclick_enabled = true; + } + VNode::Component(VComponent { tag: component.tag, attributes: component.attributes.into_iter().collect(), id: Some(id), onchange: onchange_enabled, + onclick: onclick_enabled, children: component .children .into_iter() @@ -116,6 +126,40 @@ impl VirtualDom { } } + +pub fn blocking_component() -> Option { + Some(Element::Component(Component { + tag: "div".to_string(), + attributes: vec![ + ("style".to_string(), "padding: 10px;".to_string()), + ("class".to_string(), "inspector".to_string()), + ], + children: vec![ + Element::Component(Component { + tag: "button".to_string(), + attributes: vec![ + ("class".to_string(), "blocking-button".to_string()), + ], + onclick: Some(Box::new(WorldCommandExecutor { + func: Box::new(|world| { + world.spawn(( + MeshPrimitivePrefab::Cube(1.0), + SpatialBundle::default(), + SceneElement, + PrefabMarker + )); + }) + })), + children: vec![ + Element::Text("Add blocking mesh".to_string()) + ], + ..default() + }) + ], + ..default() + })) +} + #[cfg(feature = "wasm")] pub mod wasm { use wasm_bindgen::prelude::*; @@ -214,6 +258,16 @@ impl WebEventExecutor for ReflectValueExecutor { } } +struct WorldCommandExecutor { + func: Box, +} + +impl WebEventExecutor for WorldCommandExecutor { + fn execute(&mut self, world: &mut World, _value: String) { + (self.func)(world); + } +} + struct EntityChangeExecutor { entity: Entity, func: Box, @@ -450,7 +504,103 @@ pub fn show_reflect(r: &dyn Reflect, reflect: ReflectChange) -> Option bevy::reflect::ReflectRef::List(_) => None, bevy::reflect::ReflectRef::Array(_) => None, bevy::reflect::ReflectRef::Map(_) => None, - bevy::reflect::ReflectRef::Enum(_) => None, + bevy::reflect::ReflectRef::Enum(v) => { + let Some(type_info) = v.get_represented_type_info() else { + error!("invalid reflect impl: type info mismatch"); + return None; + }; + let type_info = match type_info { + TypeInfo::Enum(info) => info, + _ => unreachable!("invalid reflect impl: type info mismatch"), + }; + + + Some(Element::Component(Component { + tag: "div".to_string(), + attributes: vec![ + ("style".to_string(), "display: flex; flex-direction: column".to_string()), + ], + children: vec![ + Element::Component(Component { + tag: "select".to_string(), + attributes: vec![], + children: type_info.iter().enumerate().map(|(i, variant)| { + let mut attributes = vec![ + ("value".to_string(), variant.name().to_string()), + ]; + + let is_active_variant = i == v.variant_index(); + if is_active_variant { + attributes.push(("selected".to_string(), "".to_string())); + } + + Element::Component(Component { + tag: "option".to_string(), + attributes, + children: vec![Element::Text(variant.name().to_string())], + ..default() + }) + }).collect(), + onchange: Some( + EntityChangeExecutor::new_boxed( + reflect.entity_id, + |world, entity, value : String| { + info!("MeshPrimitivePrefab change: {:?} {}", entity, value); + + let Some(mut e) = world.get_entity_mut(entity) else { + return; + }; + + let Some(mut variant) = e.get_mut::() else { + return; + }; + + // pub enum MeshPrimitivePrefab { + // Cube(f32), + // Box(BoxPrefab), + // Sphere(SpherePrefab), + // Quad(QuadPrefab), + // Capsule(CapsulePrefab), + // Circle(CirclePrefab), + // Cylinder(CylinderPrefab), + // Icosphere(IcospherePrefab), + // Plane(PlanePrefab), + // RegularPolygon(RegularPolygonPrefab), + // Torus(TorusPrefab), + // } + + if value == "Cube" { + *variant = MeshPrimitivePrefab::Cube(1.0); + } else if value == "Box" { + *variant = MeshPrimitivePrefab::Box(BoxPrefab::default()); + } else if value == "Sphere" { + *variant = MeshPrimitivePrefab::Sphere(SpherePrefab::default()); + } else if value == "Quad" { + *variant = MeshPrimitivePrefab::Quad(QuadPrefab::default()); + } else if value == "Capsule" { + *variant = MeshPrimitivePrefab::Capsule(CapsulePrefab::default()); + } else if value == "Circle" { + *variant = MeshPrimitivePrefab::Circle(CirclePrefab::default()); + } else if value == "Cylinder" { + *variant = MeshPrimitivePrefab::Cylinder(CylinderPrefab::default()); + } else if value == "Icosphere" { + *variant = MeshPrimitivePrefab::Icosphere(IcospherePrefab::default()); + } else if value == "Plane" { + *variant = MeshPrimitivePrefab::Plane(PlanePrefab::default()); + } else if value == "RegularPolygon" { + *variant = MeshPrimitivePrefab::RegularPolygon(RegularPolygonPrefab::default()); + } else if value == "Torus" { + *variant = MeshPrimitivePrefab::Torus(TorusPrefab::default()); + } + } + ) + ), + ..default() + }) + ], + ..default() + })) + }, bevy::reflect::ReflectRef::Value(v) => { if let Some(s) = v.downcast_ref::() { Some(Element::Component(Component { @@ -512,6 +662,7 @@ pub struct VComponent { pub children: Vec, pub id: Option, pub onchange: bool, + pub onclick: bool, } #[derive(Clone, PartialEq, Serialize, Deserialize)]