From 53c89d2e6c9f5d09286d25b299cf041721f73a0f Mon Sep 17 00:00:00 2001 From: jbuehler23 Date: Fri, 20 Jun 2025 10:55:55 +0100 Subject: [PATCH 01/21] Add Component Viewer pane and integrate bevy_remote functionality by pressing "space" key --- Cargo.toml | 4 +- .../bevy_component_viewer/Cargo.toml | 17 +++ .../bevy_component_viewer/src/lib.rs | 116 ++++++++++++++++++ crates/bevy_editor/Cargo.toml | 2 + crates/bevy_editor/src/lib.rs | 107 +++++++++++++--- crates/bevy_editor/src/ui.rs | 2 + crates/bevy_editor_launcher/Cargo.toml | 1 + 7 files changed, 232 insertions(+), 17 deletions(-) create mode 100644 bevy_editor_panes/bevy_component_viewer/Cargo.toml create mode 100644 bevy_editor_panes/bevy_component_viewer/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index fa713f56..72ff868d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,8 +28,9 @@ unused_qualifications = "warn" [workspace.dependencies] bevy = { git = "https://github.com/bevyengine/bevy.git", rev = "e9418b3845c1ffc9624a3a4003bde66a2ad6566a", features = [ - "wayland", + "wayland" ] } +bevy_remote = { git = "https://github.com/bevyengine/bevy.git", rev = "e9418b3845c1ffc9624a3a4003bde66a2ad6566a" } bevy_derive = { git = "https://github.com/bevyengine/bevy.git", rev = "e9418b3845c1ffc9624a3a4003bde66a2ad6566a" } bevy_macro_utils = { git = "https://github.com/bevyengine/bevy.git", rev = "e9418b3845c1ffc9624a3a4003bde66a2ad6566a" } thiserror = "2.0" @@ -50,6 +51,7 @@ bevy_asset_browser = { path = "bevy_editor_panes/bevy_asset_browser" } bevy_marketplace_viewer = { path = "bevy_editor_panes/bevy_marketplace_viewer" } bevy_preferences = { path = "bevy_editor_panes/bevy_preferences" } bevy_properties_pane = { path = "bevy_editor_panes/bevy_properties_pane" } +bevy_component_viewer = { path = "bevy_editor_panes/bevy_component_viewer" } bevy_scene_tree = { path = "bevy_editor_panes/bevy_scene_tree" } # bevy_widgets diff --git a/bevy_editor_panes/bevy_component_viewer/Cargo.toml b/bevy_editor_panes/bevy_component_viewer/Cargo.toml new file mode 100644 index 00000000..5d35acbf --- /dev/null +++ b/bevy_editor_panes/bevy_component_viewer/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "bevy_component_viewer" +version = "0.1.0" +edition = "2021" + +[dependencies] +bevy.workspace = true +bevy_editor_core.workspace = true +bevy_remote.workspace = true +bevy_pane_layout.workspace = true +bevy_editor_styles.workspace = true +bevy_i-cant-believe-its-not-bsn.workspace = true +serde_json = "1.0.140" +ureq = {version = "3.0.12", features = ["json"]} + +[lints] +workspace = true diff --git a/bevy_editor_panes/bevy_component_viewer/src/lib.rs b/bevy_editor_panes/bevy_component_viewer/src/lib.rs new file mode 100644 index 00000000..dd966bbb --- /dev/null +++ b/bevy_editor_panes/bevy_component_viewer/src/lib.rs @@ -0,0 +1,116 @@ +//! Crate for a Bevy Editor Component Viewer pane. This has yet to be blessed, but I thought it might be nice to specifically view components +//! +//! This crate provides a plugin and UI for viewing and interacting with components in a Bevy application. +use bevy::{color::palettes::tailwind, input::common_conditions::input_just_pressed, prelude::*}; +use bevy_pane_layout::prelude::{PaneAppExt, PaneStructure}; +use bevy_remote::{ + builtin_methods::{BrpQuery, BrpQueryFilter, BrpQueryParams, BRP_QUERY_METHOD}, + http::{DEFAULT_ADDR, DEFAULT_PORT}, + BrpRequest, +}; +use serde_json; +/// Plugin for the editor properties pane. +pub struct ComponentViewerPlugin; + +impl Plugin for ComponentViewerPlugin { + fn build(&self, app: &mut App) { + app.register_pane("Components", setup_pane).add_systems( + Update, + connect_to_remote.run_if(input_just_pressed(KeyCode::Space)), + ); + // .add_systems(PostUpdate, update_remote_component_viewer_pane); + } +} + +/// Root UI node of the properties pane. +#[derive(Component)] +struct ComponentViewerRoot; + +fn setup_pane(pane: In, mut commands: Commands) { + commands.entity(pane.content).insert(( + ComponentViewerRoot, + Node { + flex_direction: FlexDirection::Column, + flex_grow: 1.0, + column_gap: Val::Px(4.0), + padding: UiRect::all(Val::Px(8.0)), + ..Default::default() + }, + BackgroundColor(tailwind::NEUTRAL_600.into()), + )); +} + +/// Connects to a remote Bevy application via HTTP REST and sends a request to query components. +fn connect_to_remote(_commands: Commands) -> Result<()> { + //connects to a remote bevy app on a pre-definted websocket port + let components = vec!["bevy_transform::components::transform::Transform".to_string()]; + let url = format!("http://{}:{}/", DEFAULT_ADDR, DEFAULT_PORT); + info!("Connecting to remote at {}", url); + let req = BrpRequest { + jsonrpc: String::from("2.0"), + method: String::from(BRP_QUERY_METHOD), + id: Some(serde_json::to_value(1)?), + params: Some( + serde_json::to_value(BrpQueryParams { + data: BrpQuery { + components: components, + option: Vec::default(), + has: Vec::default(), + }, + strict: false, + filter: BrpQueryFilter::default(), + }) + .expect("Unable to convert query parameters to a valid JSON value"), + ), + }; + + // For some reason it fails here, with no return or error/response code? + info!("Sending request: {:#?}", req); + let res = ureq::post(&url) + .send_json(req)? + .body_mut() + .read_json::()?; + + info!("Received response: {:#?}", res); + Ok(()) +} + +// fn update_remote_component_viewer_pane( +// panes: Query>, +// world_state: Res, +// mut commands: Commands, +// ) { +// for pane in &panes { +// commands +// .entity(pane) +// .build_children(remote_component_viewer_update(&*world_state)); +// } +// } + +// fn remote_component_viewer_update(world_state: &RemoteWorldState) { +// let mut entity_nodes = Vec::new(); +// for entity in &world_state.entities { +// let mut component_nodes = Vec::new(); +// for component in &entity.components { +// component_nodes.push(template! { +// Node { +// flex_direction: FlextDirection::Row, +// ..Default::default() +// } => [ +// (Text(component.type_name.clone()), TextFont::from_font_size(12.0)), +// (Text(format!("{:?}", component.data)), TextFont::from_font_size(10.0)), +// ]; +// }); +// } +// entity_nodes.push(template! { +// Node { +// flex_direction, FlexDirection::Column, +// margin: UiRect::vertical(Val::Px(4.0)), +// ..Default::default() +// } => [ +// (Text(format!("Entity {:?}", entity.id)), TextFont::from_font_size(14.0)), +// @{ component_nodes }; +// ]; +// }); +// } +// } diff --git a/crates/bevy_editor/Cargo.toml b/crates/bevy_editor/Cargo.toml index b4f863e6..5ffe87f6 100644 --- a/crates/bevy_editor/Cargo.toml +++ b/crates/bevy_editor/Cargo.toml @@ -10,6 +10,7 @@ bevy_menu_bar.workspace = true bevy_footer_bar.workspace = true bevy_context_menu.workspace = true bevy_editor_styles.workspace = true +bevy_remote.workspace = true serde.workspace = true ron.workspace = true @@ -21,6 +22,7 @@ bevy_3d_viewport.workspace = true bevy_2d_viewport.workspace = true bevy_scene_tree.workspace = true bevy_properties_pane.workspace = true +bevy_component_viewer.workspace = true bevy_asset_browser.workspace = true [lints] diff --git a/crates/bevy_editor/src/lib.rs b/crates/bevy_editor/src/lib.rs index 63f8970c..f3fcbd50 100644 --- a/crates/bevy_editor/src/lib.rs +++ b/crates/bevy_editor/src/lib.rs @@ -11,7 +11,11 @@ //! which transforms the user's application into an editor that runs their game. //! - Finally, it will be a standalone application that communicates with a running Bevy game via the Bevy Remote Protocol. +use std::env; + use bevy::app::App as BevyApp; +use bevy::color::palettes::tailwind; +use bevy::math::ops::cos; use bevy::prelude::*; // Re-export Bevy for project use pub use bevy; @@ -24,6 +28,8 @@ use bevy_editor_styles::StylesPlugin; use bevy_2d_viewport::Viewport2dPanePlugin; use bevy_3d_viewport::Viewport3dPanePlugin; use bevy_asset_browser::AssetBrowserPanePlugin; +use bevy_remote::http::RemoteHttpPlugin; +use bevy_remote::RemotePlugin; use crate::load_gltf::LoadGltfPlugin; @@ -48,18 +54,25 @@ impl Plugin for EditorPlugin { // Update/register this project to the editor project list project::update_project_info(); info!("Loading Bevy Editor"); + bevy_app .add_plugins(( + RemotePlugin::default(), + RemoteHttpPlugin::default(), EditorCorePlugin, ContextMenuPlugin, StylesPlugin, Viewport2dPanePlugin, Viewport3dPanePlugin, ui::EditorUIPlugin, - AssetBrowserPanePlugin, LoadGltfPlugin, + AssetBrowserPanePlugin, )) - .add_systems(Startup, dummy_setup); + .add_systems(Startup, setup) + .add_systems(Update, move_cube) + .register_type::() + .register_type::() + .register_type::(); } } @@ -76,7 +89,7 @@ impl App { /// Run the application pub fn run(&self) -> AppExit { - let args = std::env::args().collect::>(); + let args = env::args().collect::>(); let editor_mode = !args.iter().any(|arg| arg == "-game"); let mut bevy_app = BevyApp::new(); @@ -85,35 +98,97 @@ impl App { bevy_app.add_plugins(EditorPlugin); } + info!( + "Running bevy editor in {} mode", + if editor_mode { "editor" } else { "game" } + ); bevy_app.run() } } -/// This is temporary, until we can load maps from the asset browser -fn dummy_setup( +#[derive(Component, Reflect)] +#[reflect(Component)] +struct Cube(f32); + +#[derive(Component, Reflect)] +#[reflect(Component)] +struct MyObject { + vec3: Vec3, + color: Color, +} + +#[derive(Resource, Reflect)] +#[reflect(Resource)] +struct MoveSpeed { + value: f32, +} + +fn setup( mut commands: Commands, mut meshes: ResMut>, - mut materials_2d: ResMut>, - mut materials_3d: ResMut>, + mut materials: ResMut>, ) { + // unnamed object + commands.spawn(MyObject { + vec3: Vec3::new(1.0, 2.0, 3.0), + color: Color::from(tailwind::BLUE_500), + }); + + // cube + let cube_handle = meshes.add(Cuboid::new(1.0, 1.0, 1.0)); commands.spawn(( - Mesh2d(meshes.add(Circle::new(50.0))), - MeshMaterial2d(materials_2d.add(Color::WHITE)), - Name::new("Circle"), + Name::new("Cube"), + Mesh3d(cube_handle.clone()), + MeshMaterial3d(materials.add(Color::from(tailwind::RED_200))), + Transform::from_xyz(0.0, 0.5, 0.0), + Cube(1.0), + children![( + Name::new("Sub-cube"), + Mesh3d(cube_handle.clone()), + MeshMaterial3d(materials.add(Color::from(tailwind::GREEN_500))), + Transform::from_xyz(0.0, 1.5, 0.0), + children![( + Name::new("Sub-sub-cube"), + Mesh3d(cube_handle), + MeshMaterial3d(materials.add(Color::from(tailwind::BLUE_800))), + Transform::from_xyz(1.5, 0.0, 0.0), + )] + )], )); + // circular base commands.spawn(( - Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.5)))), - MeshMaterial3d(materials_3d.add(Color::WHITE)), - Name::new("Plane"), + Name::new("Circular base"), + Mesh3d(meshes.add(Circle::new(4.0))), + MeshMaterial3d(materials.add(Color::from(tailwind::GREEN_300))), + Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)), )); + // light commands.spawn(( - DirectionalLight { + Name::new("Light"), + PointLight { shadows_enabled: true, ..default() }, - Transform::default().looking_to(Vec3::NEG_ONE, Vec3::Y), - Name::new("DirectionalLight"), + Transform::from_xyz(4.0, 8.0, 4.0), + )); + + // camera + commands.spawn(( + Name::new("Camera"), + Camera3d::default(), + Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::Y, Vec3::Y), )); } + +fn move_cube( + mut query: Query<&mut Transform, With>, + time: Res