use bevy::{asset::AssetEvents, gltf::Gltf, prelude::*, utils::HashMap}; use monologue_trees::debug::*; fn main() { App::new() .add_plugins(( DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { title: "GLTF Inspector".into(), resolution: (640., 480.).into(), ..default() }), ..default() }), DebugInfoPlugin, )) .add_event::() .add_event::() .init_resource::() .add_systems(Startup, spawn_ui) .add_systems( Update, ( drag_and_drop, loading, reset_scene.before(spawn_scene), spawn_scene.after(reset_scene), wire_up_scenes, control_scene, wire_up_animations, control_animation, wire_up_cameras, control_camera, control_default_camera, ), ) .run(); } #[derive(Component)] struct SceneMarker; #[derive(Component)] struct MainCamera; #[derive(Component)] struct MainUi; #[derive(Component)] struct UiTitle; #[derive(Component)] struct InstructionsUi; #[derive(Component)] struct SceneSelectUi; #[derive(Component, PartialEq, Debug)] struct SceneButton(Handle); #[derive(Component)] struct AnimationSelectUi; #[derive(Component, PartialEq, Debug)] struct AnimationButton(Handle); #[derive(Component)] struct CameraSelectUi; #[derive(Component, PartialEq, Debug)] struct CameraButton(Entity); #[derive(Resource, Default)] struct Current { gltf: Handle, scenes: HashMap>, animations: HashMap>, } #[derive(Event)] struct ResetScene; #[derive(Event)] struct SpawnScene(Handle); fn drag_and_drop( mut events: EventReader, server: Res, mut current: ResMut, ) { events .iter() .filter_map(|event| { if let FileDragAndDrop::DroppedFile { path_buf, .. } = event { info!("Drag+Drop file: {:?}", path_buf); Some(path_buf) } else { None } }) .map(|path_buf| server.load(path_buf.to_str().expect("PathBuf to str"))) .for_each(|handle| current.gltf = handle.clone()); } fn spawn_ui(mut commands: Commands) { // TODO: Warn no camera (hidden) // TODO: Scene select container // TODO: Animation Play/Pause Placeholder commands.spawn(( Camera3dBundle { ..default() }, UiCameraConfig { show_ui: true }, MainCamera, )); commands .spawn(( NodeBundle { style: Style { flex_wrap: FlexWrap::Wrap, flex_direction: FlexDirection::Row, justify_content: JustifyContent::SpaceAround, width: Val::Percent(100.0), height: Val::Percent(100.0), ..default() }, ..default() }, MainUi, )) .with_children(|parent| { // TOOD: Prompt to drag+drop Gltf/Gltf file parent.spawn(( TextBundle { text: Text { sections: vec![TextSection { value: String::from("Drag and Drop .gltf/.glb file"), style: TextStyle { color: Color::WHITE, ..default() }, }], ..default() }, ..default() }, InstructionsUi, )); parent .spawn(( NodeBundle { style: Style { flex_direction: FlexDirection::Column, ..default() }, ..default() }, SceneSelectUi, )) .with_children(|parent| { parent.spawn(( TextBundle::from_section("Scenes", TextStyle { ..default() }), UiTitle, )); }); parent .spawn(( NodeBundle { style: Style { flex_direction: FlexDirection::Column, ..default() }, ..default() }, AnimationSelectUi, )) .with_children(|parent| { parent.spawn(( TextBundle::from_section("Animations", TextStyle { ..default() }), UiTitle, )); }); parent .spawn(( NodeBundle { style: Style { flex_direction: FlexDirection::Column, ..default() }, ..default() }, CameraSelectUi, )) .with_children(|parent| { parent.spawn(( TextBundle::from_section("Cameras", TextStyle { ..default() }), UiTitle, )); }); }); } fn wire_up_scenes( mut events: EventReader>, root: Query>, root_children: Query<&Children, With>, mut commands: Commands, gltfs: Res>, current: Res, existing: Query<&SceneButton, Without>, ) { events.iter().for_each(|event| { // Cleanup unused buttons root_children.iter().for_each(|children| { // Iterate over UI children elements children .iter() // Filter to just the buttons we want to despawn .filter(|&child| { // Check each child entity against the currently loaded scenes // Note: !(_.any(...)) (not-any) !((*current).scenes.iter().any(|(_, handle)| { // If the current entitie's scene button corresponds to one of the scenes // loaded, continue, otherwise existing.get(*child).map_or(false, |scene_button| { *scene_button == SceneButton(handle.cast_weak::()) }) })) }) .for_each(|&e| { commands.entity(e).despawn_recursive(); }); }); // Add buttons match event { AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { let (name, _) = gltfs .get(¤t.gltf) .expect("Load current gltf") .named_scenes .iter() .find(|(_, h)| h == &handle) .expect("Find this scene"); let mut ui = commands.entity(root.single()); ui.with_children(|parent| { parent .spawn(( ButtonBundle { background_color: BackgroundColor(Color::NONE), ..default() }, SceneButton(handle.clone()), )) .with_children(|parent| { parent.spawn(TextBundle { text: Text { sections: vec![TextSection { value: name.clone(), style: TextStyle { color: Color::WHITE, ..default() }, }], ..default() }, ..default() }); }); }); } AssetEvent::Removed { .. } => warn!("IGNORED Remove scene in list"), } }); } /// Translate UI button presses into scene spawn events fn control_scene( mut events: EventWriter, interactions: Query<(&Interaction, &SceneButton), (Changed, With