Compare commits
	
		
			3 Commits 
		
	
	
		
			b9dc61dad1
			...
			e159346b89
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | e159346b89 | 2 years ago | 
|  | 434965cdf3 | 2 years ago | 
|  | 563e8c07d2 | 2 years ago | 
| @ -1,8 +1,32 @@ | |||||||
| - [x] basic gltf inspector | - [x] basic gltf inspector | ||||||
|   - [x] with animation previews |   - [x] with animation previews | ||||||
|   - [ ] inspect specific models |   - [x] inspect specific models | ||||||
|   - [ ] Use gltf camera |   - [x] Use gltf camera | ||||||
| - [x] basic text inspector | - [x] basic text inspector | ||||||
|   - [x] with simple text animation |   - [x] with simple text animation | ||||||
| - [ ] audio inspector | - [x] audio inspector | ||||||
| - [x] debug info (FPS) | - [x] debug info (FPS) | ||||||
|  | 
 | ||||||
|  | The Big Kahuna: | ||||||
|  | > Game editor for creating levels | ||||||
|  | - [ ] Drag and Drop to import: | ||||||
|  |   - [ ] Gltf scenes | ||||||
|  |   - [ ] Audio assets | ||||||
|  |   - [ ] Scripts (format TBD) | ||||||
|  | - [ ] UI | ||||||
|  |   - [ ] Navigate GLTFs  | ||||||
|  |     - [ ] Scenes | ||||||
|  |     - [ ] Cameras | ||||||
|  |     - [ ] Animations | ||||||
|  | - [ ] Monologue Scripting | ||||||
|  |   - [ ] Create/edit scripts | ||||||
|  | - [ ] Model tweaking | ||||||
|  |   - [ ] Toggle multiple GLTF assets | ||||||
|  |   - [ ] Move/Rotate/Resize scene | ||||||
|  | - [ ] Level creation | ||||||
|  |   - [ ] Import assets 1+ times | ||||||
|  |   - [ ] "Use" imported asset | ||||||
|  |   - [ ] "Delete" imported asset | ||||||
|  | - [ ] Preview a level | ||||||
|  | - [ ] Export level | ||||||
|  | - [ ] Import level | ||||||
|  | |||||||
| @ -0,0 +1,360 @@ | |||||||
|  | // Monologue Trees Editor
 | ||||||
|  | //
 | ||||||
|  | // Editor for creating Monologue Trees levels
 | ||||||
|  | //
 | ||||||
|  | // TODO:
 | ||||||
|  | // * Tree Organization: GLTF contains Animations and Scenes
 | ||||||
|  | // * Camera can only select one at a time.
 | ||||||
|  | // * (easy) Load audios like current GLTFs
 | ||||||
|  | // * (easy) Loop audio enable/disable
 | ||||||
|  | // * (easy) Better Colorscheme
 | ||||||
|  | // * (easy) Interactive buttons (hover/click)
 | ||||||
|  | // * (medium) Visual errors for bad GLTFs
 | ||||||
|  | // * (medium) Collapsable containers (Gltfs, Animations, Scenes, Audio Clips, Cameras)
 | ||||||
|  | // * (medium) Spawn clicked scene
 | ||||||
|  | // * (medium) Play clicked animation
 | ||||||
|  | // * (idea) Use enum instead of markers for exclusive UI
 | ||||||
|  | 
 | ||||||
|  | use bevy::{ | ||||||
|  |     asset::{AssetPath, Assets}, | ||||||
|  |     gltf::Gltf, | ||||||
|  |     input::{keyboard::KeyboardInput, ButtonState}, | ||||||
|  |     prelude::*, | ||||||
|  |     utils::HashSet, | ||||||
|  | }; | ||||||
|  | use monologue_trees::{debug::*, ui::*}; | ||||||
|  | 
 | ||||||
|  | fn main() { | ||||||
|  |     App::new() | ||||||
|  |         .add_plugins(( | ||||||
|  |             DefaultPlugins.set(WindowPlugin { | ||||||
|  |                 primary_window: Some(Window { | ||||||
|  |                     title: "Monologue Trees Editor".into(), | ||||||
|  |                     resolution: (640., 480.).into(), | ||||||
|  |                     ..default() | ||||||
|  |                 }), | ||||||
|  |                 ..default() | ||||||
|  |             }), | ||||||
|  |             DebugInfoPlugin, | ||||||
|  |             GameUiPlugin, | ||||||
|  |         )) | ||||||
|  |         .init_resource::<AssetRegistry>() | ||||||
|  |         .add_systems(Startup, (initialize_ui,)) | ||||||
|  |         .add_systems( | ||||||
|  |             Update, | ||||||
|  |             ( | ||||||
|  |                 // GLTF Systems
 | ||||||
|  |                 load_gltf, | ||||||
|  |                 unload_gltf, | ||||||
|  |                 manage_gltf_ui, | ||||||
|  |                 // Scene Systems
 | ||||||
|  |                 manage_scene_ui, | ||||||
|  |                 spawn_scene, | ||||||
|  |                 // Animation systems
 | ||||||
|  |                 manage_animation_ui, | ||||||
|  |                 // Camera systems
 | ||||||
|  |                 manage_camera_ui, | ||||||
|  |                 // Audio Systems
 | ||||||
|  |                 load_audio, | ||||||
|  |                 unload_audio, | ||||||
|  |                 play_audio, | ||||||
|  |                 // Level Import/Export systems
 | ||||||
|  |                 export_level, | ||||||
|  |                 import_level, | ||||||
|  |                 // Misc/Debug Systems
 | ||||||
|  |                 load_bogus, | ||||||
|  |             ), | ||||||
|  |         ) | ||||||
|  |         .run(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// A generic referenece used for UI elements to point to assets or entities
 | ||||||
|  | #[derive(Debug, Component)] | ||||||
|  | enum UiRef<T> { | ||||||
|  |     Handle(T), | ||||||
|  |     Entity(T), | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// UI:
 | ||||||
|  | /// * GLTFs
 | ||||||
|  | ///   * Scenes
 | ||||||
|  | ///   * Cameras
 | ||||||
|  | ///   * Animations
 | ||||||
|  | /// * Audios
 | ||||||
|  | fn initialize_ui(mut commands: Commands) { | ||||||
|  |     commands.spawn(( | ||||||
|  |         Camera2dBundle { ..default() }, | ||||||
|  |         UiCameraConfig { show_ui: true }, | ||||||
|  |     )); | ||||||
|  | 
 | ||||||
|  |     commands | ||||||
|  |         .spawn(NodeBundle { | ||||||
|  |             style: Style { | ||||||
|  |                 width: Val::Percent(100.0), | ||||||
|  |                 height: Val::Percent(100.0), | ||||||
|  |                 ..default() | ||||||
|  |             }, | ||||||
|  |             ..default() | ||||||
|  |         }) | ||||||
|  |         .with_children(|parent| { | ||||||
|  |             parent | ||||||
|  |                 .spawn(( | ||||||
|  |                     GameUiList, | ||||||
|  |                     Name::new("GLTFs"), | ||||||
|  |                     NodeBundle { ..default() }, | ||||||
|  |                     GltfsUi, | ||||||
|  |                 )) | ||||||
|  |                 .with_children(|parent| { | ||||||
|  |                     parent.spawn(( | ||||||
|  |                         GameUiList, | ||||||
|  |                         Name::new("Scenes"), | ||||||
|  |                         NodeBundle { ..default() }, | ||||||
|  |                         ScenesUi, | ||||||
|  |                     )); | ||||||
|  |                     parent.spawn(( | ||||||
|  |                         GameUiList, | ||||||
|  |                         Name::new("Cameras"), | ||||||
|  |                         NodeBundle { ..default() }, | ||||||
|  |                         CamerasUi, | ||||||
|  |                     )); | ||||||
|  |                     parent.spawn(( | ||||||
|  |                         GameUiList, | ||||||
|  |                         Name::new("Animations"), | ||||||
|  |                         NodeBundle { ..default() }, | ||||||
|  |                         AnimationsUi, | ||||||
|  |                     )); | ||||||
|  |                 }); | ||||||
|  |             parent.spawn(( | ||||||
|  |                 GameUiSet, | ||||||
|  |                 Name::new("Audio Clips"), | ||||||
|  |                 NodeBundle { ..default() }, | ||||||
|  |                 AudioClipsUi, | ||||||
|  |             )); | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn load_bogus( | ||||||
|  |     mut events: EventReader<KeyboardInput>, | ||||||
|  |     root: Query<Entity, With<AnimationsUi>>, | ||||||
|  |     mut commands: Commands, | ||||||
|  | ) { | ||||||
|  |     events | ||||||
|  |         .iter() | ||||||
|  |         .filter( | ||||||
|  |             |&KeyboardInput { | ||||||
|  |                  key_code, state, .. | ||||||
|  |              }| *key_code == Some(KeyCode::Space) && *state == ButtonState::Pressed, | ||||||
|  |         ) | ||||||
|  |         .for_each(|_| { | ||||||
|  |             commands | ||||||
|  |                 .spawn((GameUiButton, Name::new("bogus"), NodeBundle { ..default() })) | ||||||
|  |                 .set_parent(root.single()); | ||||||
|  |         }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Resource, Default, Debug)] | ||||||
|  | struct AssetRegistry(HashSet<HandleUntyped>); | ||||||
|  | 
 | ||||||
|  | /// Component marking UI for loaded Gltf assets
 | ||||||
|  | #[derive(Component)] | ||||||
|  | struct GltfsUi; | ||||||
|  | 
 | ||||||
|  | /// Drag+Drop import GLTF to editor
 | ||||||
|  | fn load_gltf( | ||||||
|  |     mut events: EventReader<FileDragAndDrop>, | ||||||
|  |     server: Res<AssetServer>, | ||||||
|  |     mut assets: ResMut<AssetRegistry>, | ||||||
|  | ) { | ||||||
|  |     events | ||||||
|  |         .iter() | ||||||
|  |         .filter_map(|event| match event { | ||||||
|  |             FileDragAndDrop::DroppedFile { path_buf, .. } => Some(path_buf), | ||||||
|  |             _ => None, | ||||||
|  |         }) | ||||||
|  |         .for_each(|path_buf| { | ||||||
|  |             let path = path_buf.as_path(); | ||||||
|  |             let handle = server.load_untyped(path); | ||||||
|  |             assets.0.insert(handle); | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Helper method to extract the stripped filename given a full asset path
 | ||||||
|  | fn get_fname(asset_path: AssetPath, suffixes: &[&str]) -> String { | ||||||
|  |     let path = asset_path.path().file_name().expect("Filename"); | ||||||
|  |     let path_str = path.to_str().expect("Asset Path to Str"); | ||||||
|  |     let name_str = suffixes | ||||||
|  |         .iter() | ||||||
|  |         .rfold(path_str, |acc, &suffix| acc.trim_end_matches(suffix)); | ||||||
|  |     String::from(name_str) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Sync GLTF assets with UI
 | ||||||
|  | ///
 | ||||||
|  | /// TODO: Handle failed load events
 | ||||||
|  | ///       Options:
 | ||||||
|  | ///       * Show Error message, do not add to UI
 | ||||||
|  | ///       * Add to UI with visual indicator
 | ||||||
|  | ///       This should be a separate async system
 | ||||||
|  | fn manage_gltf_ui( | ||||||
|  |     mut events: EventReader<AssetEvent<Gltf>>, | ||||||
|  |     root: Query<Entity, With<GltfsUi>>, | ||||||
|  |     mut commands: Commands, | ||||||
|  |     server: Res<AssetServer>, | ||||||
|  | ) { | ||||||
|  |     events | ||||||
|  |         .iter() | ||||||
|  |         .filter_map(|event| match event { | ||||||
|  |             AssetEvent::Created { handle } => { | ||||||
|  |                 let asset_path = server | ||||||
|  |                     .get_handle_path(handle.clone()) | ||||||
|  |                     .expect("Fetch Asset Path"); | ||||||
|  |                 let name = get_fname(asset_path, &[".gltf", ".glb"]); | ||||||
|  |                 Some((handle.clone(), String::from(name))) | ||||||
|  |             } | ||||||
|  |             _ => None, | ||||||
|  |         }) | ||||||
|  |         .for_each(|(handle, name)| { | ||||||
|  |             commands | ||||||
|  |                 .spawn(( | ||||||
|  |                     GameUiButton, | ||||||
|  |                     Name::new(name), | ||||||
|  |                     NodeBundle { ..default() }, | ||||||
|  |                     GltfsUi, | ||||||
|  |                     UiRef::Handle(handle.clone()), | ||||||
|  |                 )) | ||||||
|  |                 .set_parent(root.single()); | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Remove gltf from editor
 | ||||||
|  | fn unload_gltf() {} | ||||||
|  | 
 | ||||||
|  | /// Component marking UI for Scene assets
 | ||||||
|  | #[derive(Component)] | ||||||
|  | struct ScenesUi; | ||||||
|  | 
 | ||||||
|  | /// Sync scene assets with UI
 | ||||||
|  | fn manage_scene_ui( | ||||||
|  |     mut events: EventReader<AssetEvent<Scene>>, | ||||||
|  |     root: Query<Entity, With<ScenesUi>>, | ||||||
|  |     mut commands: Commands, | ||||||
|  |     gltfs: Res<Assets<Gltf>>, | ||||||
|  |     registry: Res<AssetRegistry>, | ||||||
|  | ) { | ||||||
|  |     events | ||||||
|  |         .iter() | ||||||
|  |         .filter_map(|event| match event { | ||||||
|  |             AssetEvent::Created { handle } => { | ||||||
|  |                 let name = registry | ||||||
|  |                     .0 | ||||||
|  |                     .iter() | ||||||
|  |                     .find_map( | ||||||
|  |                         |gltf_handle| match gltfs.get(&gltf_handle.clone().typed::<Gltf>()) { | ||||||
|  |                             Some(gltf) => { | ||||||
|  |                                 gltf.named_scenes.iter().find_map(|(name, scene_handle)| { | ||||||
|  |                                     info!( | ||||||
|  |                                         "scene_handle({:?}) == handle({:?})", | ||||||
|  |                                         scene_handle, handle | ||||||
|  |                                     ); | ||||||
|  |                                     (scene_handle == handle).then_some(name) | ||||||
|  |                                 }) | ||||||
|  |                             } | ||||||
|  |                             None => None, | ||||||
|  |                         }, | ||||||
|  |                     ) | ||||||
|  |                     .expect("Find scene name"); | ||||||
|  |                 Some((handle.clone(), String::from(name))) | ||||||
|  |             } | ||||||
|  |             _ => None, | ||||||
|  |         }) | ||||||
|  |         .for_each(|(handle, name)| { | ||||||
|  |             commands | ||||||
|  |                 .spawn(( | ||||||
|  |                     GameUiButton, | ||||||
|  |                     Name::new(name), | ||||||
|  |                     NodeBundle { ..default() }, | ||||||
|  |                     ScenesUi, | ||||||
|  |                     UiRef::Handle(handle.clone()), | ||||||
|  |                 )) | ||||||
|  |                 .set_parent(root.single()); | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Component marking UI for Camera assets
 | ||||||
|  | #[derive(Component)] | ||||||
|  | struct CamerasUi; | ||||||
|  | 
 | ||||||
|  | fn manage_camera_ui() {} | ||||||
|  | 
 | ||||||
|  | /// Component marking UI for Animation assets
 | ||||||
|  | #[derive(Component)] | ||||||
|  | struct AnimationsUi; | ||||||
|  | 
 | ||||||
|  | fn manage_animation_ui( | ||||||
|  |     mut events: EventReader<AssetEvent<AnimationClip>>, | ||||||
|  |     root: Query<Entity, With<AnimationsUi>>, | ||||||
|  |     mut commands: Commands, | ||||||
|  |     gltfs: Res<Assets<Gltf>>, | ||||||
|  |     registry: Res<AssetRegistry>, | ||||||
|  | ) { | ||||||
|  |     events | ||||||
|  |         .iter() | ||||||
|  |         .filter_map(|event| match event { | ||||||
|  |             AssetEvent::Created { handle } => { | ||||||
|  |                 let name = | ||||||
|  |                     registry | ||||||
|  |                         .0 | ||||||
|  |                         .iter() | ||||||
|  |                         .find_map(|gltf_handle| { | ||||||
|  |                             match gltfs.get(&gltf_handle.clone().typed::<Gltf>()) { | ||||||
|  |                                 Some(gltf) => gltf.named_animations.iter().find_map( | ||||||
|  |                                     |(name, animation_handle)| { | ||||||
|  |                                         info!( | ||||||
|  |                                             "animation_handle({:?}) == handle({:?})", | ||||||
|  |                                             animation_handle, handle | ||||||
|  |                                         ); | ||||||
|  |                                         (animation_handle == handle).then_some(name) | ||||||
|  |                                     }, | ||||||
|  |                                 ), | ||||||
|  |                                 None => None, | ||||||
|  |                             } | ||||||
|  |                         }) | ||||||
|  |                         .expect("Find animation name"); | ||||||
|  |                 Some((handle.clone(), String::from(name))) | ||||||
|  |             } | ||||||
|  |             _ => None, | ||||||
|  |         }) | ||||||
|  |         .for_each(|(handle, name)| { | ||||||
|  |             commands | ||||||
|  |                 .spawn(( | ||||||
|  |                     GameUiButton, | ||||||
|  |                     Name::new(name), | ||||||
|  |                     NodeBundle { ..default() }, | ||||||
|  |                     AnimationsUi, | ||||||
|  |                     UiRef::Handle(handle.clone()), | ||||||
|  |                 )) | ||||||
|  |                 .set_parent(root.single()); | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Component marking UI for Audio Clip assets
 | ||||||
|  | #[derive(Component)] | ||||||
|  | struct AudioClipsUi; | ||||||
|  | 
 | ||||||
|  | /// Drag+Drop import Audio to editor
 | ||||||
|  | fn load_audio() {} | ||||||
|  | 
 | ||||||
|  | /// Remove audio from editor
 | ||||||
|  | fn unload_audio() {} | ||||||
|  | 
 | ||||||
|  | /// Spawn Scene
 | ||||||
|  | fn spawn_scene() {} | ||||||
|  | 
 | ||||||
|  | /// Play/Loop Audio
 | ||||||
|  | fn play_audio() {} | ||||||
|  | 
 | ||||||
|  | /// Export level
 | ||||||
|  | fn export_level() {} | ||||||
|  | 
 | ||||||
|  | /// Import Level
 | ||||||
|  | fn import_level() {} | ||||||
| @ -1,3 +1,5 @@ | |||||||
| pub mod debug; | pub mod debug; | ||||||
| 
 | 
 | ||||||
| pub mod text; | pub mod text; | ||||||
|  | 
 | ||||||
|  | pub mod ui; | ||||||
|  | |||||||
| @ -0,0 +1,121 @@ | |||||||
|  | use bevy::{prelude::*, window::PrimaryWindow}; | ||||||
|  | 
 | ||||||
|  | pub struct GameUiPlugin; | ||||||
|  | 
 | ||||||
|  | impl Plugin for GameUiPlugin { | ||||||
|  |     fn build(&self, app: &mut App) { | ||||||
|  |         app.add_systems( | ||||||
|  |             Update, | ||||||
|  |             ( | ||||||
|  |                 manage_ui_list, | ||||||
|  |                 manage_ui_set, | ||||||
|  |                 manage_ui_button, | ||||||
|  |                 manage_cursor, | ||||||
|  |             ), | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// GameUiList for holding ordered collections of objects
 | ||||||
|  | #[derive(Debug, Component)] | ||||||
|  | pub struct GameUiList; | ||||||
|  | 
 | ||||||
|  | /// Manage UI Lists: lists of UI entities.
 | ||||||
|  | fn manage_ui_list(events: Query<(Entity, &Name), Added<GameUiList>>, mut commands: Commands) { | ||||||
|  |     events.iter().for_each(|(entity, name)| { | ||||||
|  |         commands | ||||||
|  |             .entity(entity) | ||||||
|  |             .insert(NodeBundle { | ||||||
|  |                 style: Style { | ||||||
|  |                     // width: Val::Px(100.0),
 | ||||||
|  |                     margin: UiRect::all(Val::Px(2.0)), | ||||||
|  |                     padding: UiRect::all(Val::Px(2.0)), | ||||||
|  |                     border: UiRect::all(Val::Px(2.0)), | ||||||
|  |                     flex_direction: FlexDirection::Column, | ||||||
|  |                     align_items: AlignItems::Stretch, | ||||||
|  |                     justify_items: JustifyItems::Center, | ||||||
|  |                     align_content: AlignContent::FlexStart, | ||||||
|  |                     ..default() | ||||||
|  |                 }, | ||||||
|  |                 background_color: BackgroundColor(Color::RED), | ||||||
|  |                 border_color: BorderColor(Color::BLACK), | ||||||
|  |                 ..default() | ||||||
|  |             }) | ||||||
|  |             .with_children(|parent| { | ||||||
|  |                 parent.spawn(TextBundle::from_section(name, TextStyle { ..default() })); | ||||||
|  |             }); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// GameUiSet Component for holding collections of objects
 | ||||||
|  | #[derive(Debug, Component)] | ||||||
|  | pub struct GameUiSet; | ||||||
|  | 
 | ||||||
|  | /// Manage UI Sets: collections of UI entities.
 | ||||||
|  | fn manage_ui_set(events: Query<(Entity, &Name), Added<GameUiSet>>, mut commands: Commands) { | ||||||
|  |     events.iter().for_each(|(entity, name)| { | ||||||
|  |         commands | ||||||
|  |             .entity(entity) | ||||||
|  |             .insert(NodeBundle { | ||||||
|  |                 style: Style { | ||||||
|  |                     // width: Val::Px(100.0),
 | ||||||
|  |                     margin: UiRect::all(Val::Px(2.0)), | ||||||
|  |                     padding: UiRect::all(Val::Px(2.0)), | ||||||
|  |                     border: UiRect::all(Val::Px(2.0)), | ||||||
|  |                     align_items: AlignItems::FlexStart, | ||||||
|  |                     align_content: AlignContent::FlexStart, | ||||||
|  |                     flex_direction: FlexDirection::Row, | ||||||
|  |                     flex_wrap: FlexWrap::Wrap, | ||||||
|  |                     ..default() | ||||||
|  |                 }, | ||||||
|  |                 background_color: BackgroundColor(Color::BLUE), | ||||||
|  |                 border_color: BorderColor(Color::BLACK), | ||||||
|  |                 ..default() | ||||||
|  |             }) | ||||||
|  |             .with_children(|parent| { | ||||||
|  |                 parent.spawn(TextBundle::from_section(name, TextStyle { ..default() })); | ||||||
|  |             }); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// GameUiButton for interactive elements
 | ||||||
|  | #[derive(Debug, Component)] | ||||||
|  | pub struct GameUiButton; | ||||||
|  | 
 | ||||||
|  | /// Manage UI Buttons. interactive buttons.
 | ||||||
|  | fn manage_ui_button(events: Query<(Entity, &Name), Added<GameUiButton>>, mut commands: Commands) { | ||||||
|  |     events.iter().for_each(|(entity, name)| { | ||||||
|  |         commands | ||||||
|  |             .entity(entity) | ||||||
|  |             .insert(ButtonBundle { | ||||||
|  |                 style: Style { | ||||||
|  |                     margin: UiRect::all(Val::Px(2.0)), | ||||||
|  |                     padding: UiRect::all(Val::Px(2.0)), | ||||||
|  |                     border: UiRect::all(Val::Px(2.0)), | ||||||
|  |                     justify_content: JustifyContent::Center, | ||||||
|  |                     ..default() | ||||||
|  |                 }, | ||||||
|  |                 background_color: BackgroundColor(Color::GREEN), | ||||||
|  |                 border_color: BorderColor(Color::BLACK), | ||||||
|  |                 ..default() | ||||||
|  |             }) | ||||||
|  |             .with_children(|parent| { | ||||||
|  |                 parent.spawn(TextBundle::from_section(name, TextStyle::default())); | ||||||
|  |             }); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Manage the cursor icon for better immersion
 | ||||||
|  | fn manage_cursor( | ||||||
|  |     mut primary_window: Query<&mut Window, With<PrimaryWindow>>, | ||||||
|  |     events: Query<&Interaction, With<Interaction>>, | ||||||
|  | ) { | ||||||
|  |     events.iter().for_each(|event| { | ||||||
|  |         let mut window = primary_window.single_mut(); | ||||||
|  |         window.cursor.icon = match event { | ||||||
|  |             Interaction::Pressed => CursorIcon::Grabbing, | ||||||
|  |             Interaction::Hovered => CursorIcon::Hand, | ||||||
|  |             Interaction::None => CursorIcon::Default, | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | } | ||||||
					Loading…
					
					
				
		Reference in New Issue