diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4c5d2ac8..4a3bea04 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -48,7 +48,7 @@ jobs: - name: Install Dependencies run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev - name: Run cargo test - run: cargo test --workspace --no-default-features + run: cargo test --workspace --no-default-features --release test_all: name: Test all features @@ -72,7 +72,7 @@ jobs: - name: Install Dependencies run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev - name: Run cargo test - run: cargo test --workspace --all-features + run: cargo test --workspace --all-features --release # Run cargo clippy -- -D warnings clippy_check: diff --git a/Cargo.lock b/Cargo.lock index 814148a8..ae3cce89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3959,9 +3959,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" dependencies = [ "log", "ring", diff --git a/Cargo.toml b/Cargo.toml index 53d0d838..632fc3a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ repository = "https://github.com/rewin123/space_editor" members = [ "crates/*", "modules/bevy_xpbd_plugin" -, "crates/editor_tabs"] +] [workspace.package] version = "0.6.0" @@ -29,7 +29,7 @@ repository = "https://github.com/rewin123/space_editor" homepage = "https://github.com/rewin123/space_editor" [workspace.dependencies] -bevy = "0.13" +bevy = "0.13.1" # Editor Crates space_prefab = { version = "0.6.0", path = "crates/prefab" } diff --git a/README.md b/README.md index 4a8927ee..9992dfae 100644 --- a/README.md +++ b/README.md @@ -158,11 +158,15 @@ Any pull request is welcome too:) - PR to main: Bug Fixes and Tests - New features and others: next version branch **(ex: last released is 0.5, so create PRs for branch v0.6)** +### Branch Policy +* **v0.x Branches:** These branches house versions of the editor that are actively being developed. Currently, the primary focus is on branch v0.6. +* **Main Branch:** The main branch exclusively hosts stable versions of the space_editor without any known bugs. Updates to this branch are limited to bug fixes, documentation improvements, or merging in the v0.x branch once all identified issues have been resolved, and the version is considered complete. + ### License MIT - https://choosealicense.com/licenses/mit/ ### Project naming -I'm using the editor to create my own Sci-Fi space game, so the name of the project starts with space_ :) +Space_editor started as part of my prototype space game, which I feel could be useful in development, so I thought I'd share my inbuilt editor. Since the game is about space and the name of the editor starts with space_*:) diff --git a/crates/editor_tabs/src/editor_tab.rs b/crates/editor_tabs/src/editor_tab.rs index ef1f8097..cfa56654 100644 --- a/crates/editor_tabs/src/editor_tab.rs +++ b/crates/editor_tabs/src/editor_tab.rs @@ -1,4 +1,4 @@ -use crate::NewTabBehaviour; +use crate::{tab_name::TabNameHolder, NewTabBehaviour}; /// This module contains the implementation of the editor tabs use bevy::prelude::*; use bevy_egui::egui; @@ -11,19 +11,5 @@ pub const TAB_MODES: [NewTabBehaviour; 3] = [ pub trait EditorTab { fn ui(&mut self, ui: &mut egui::Ui, commands: &mut Commands, world: &mut World); - fn title(&self) -> egui::WidgetText; -} - -#[derive(Clone, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)] -pub enum EditorTabName { - CameraView, - EventDispatcher, - GameView, - Hierarchy, - Inspector, - Resource, - RuntimeAssets, - Settings, - ToolBox, - Other(String), + fn tab_name(&self) -> TabNameHolder; } diff --git a/crates/editor_tabs/src/lib.rs b/crates/editor_tabs/src/lib.rs index 6620d50d..2d3d43b0 100644 --- a/crates/editor_tabs/src/lib.rs +++ b/crates/editor_tabs/src/lib.rs @@ -3,8 +3,12 @@ pub mod colors; pub mod editor_tab; pub mod schedule_editor_tab; pub mod sizing; +pub mod start_layout; +pub mod tab_name; pub mod tab_viewer; +use std::fmt::Display; + use bevy::{ecs::system::CommandQueue, prelude::*, utils::HashMap, window::PrimaryWindow}; use bevy_egui::egui::FontFamily::{Monospace, Proportional}; @@ -18,6 +22,8 @@ use editor_tab::*; use egui_dock::DockArea; use schedule_editor_tab::*; use sizing::{to_label, Sizing}; +use start_layout::StartLayout; +use tab_name::{TabName, TabNameHolder}; use tab_viewer::*; use bevy_egui::egui::TextStyle as ETextStyle; @@ -27,6 +33,8 @@ pub mod prelude { pub use super::editor_tab::*; pub use super::schedule_editor_tab::*; pub use super::sizing::*; + pub use super::start_layout::*; + pub use super::tab_name::*; pub use super::tab_viewer::*; pub use super::{ @@ -79,8 +87,8 @@ pub fn show_editor_ui(world: &mut World) { /// This resource contains registered editor tabs and current dock tree state #[derive(Resource)] pub struct EditorUi { - pub registry: HashMap, - pub tree: egui_dock::DockState, + pub registry: HashMap, + pub tree: egui_dock::DockState, } impl Default for EditorUi { @@ -92,21 +100,11 @@ impl Default for EditorUi { } } -pub type EditorTabShowFn = Box; -pub type EditorTabGetTitleFn = Box egui::WidgetText + Send + Sync>; - -/// This enum determine how tab was registered. -/// ResourceBased - tab will be registered as resource -/// Schedule - tab will be registered as system -pub enum EditorUiReg { - ResourceBased { - show_command: EditorTabShowFn, - title_command: EditorTabGetTitleFn, - }, - Schedule, -} - impl EditorUi { + pub fn set_layout(&mut self, layout: &T) { + self.tree = layout.build(); + } + pub fn ui(&mut self, world: &mut World, ctx: &mut egui::Context) { //collect tab names to vec to detect visible let mut visible = vec![]; @@ -192,22 +190,23 @@ impl EditorUi { /// Trait for registering editor tabs via app.** pub trait EditorUiAppExt { - fn editor_tab_by_trait(&mut self, tab_id: EditorTabName, tab: T) -> &mut Self + fn editor_tab_by_trait(&mut self, tab: T) -> &mut Self where T: EditorTab + Resource + Send + Sync + 'static; - fn editor_tab( + fn editor_tab( &mut self, - tab_id: EditorTabName, - title: egui::WidgetText, + tab_name: N, tab_systems: impl IntoSystemConfigs, ) -> &mut Self; } impl EditorUiAppExt for App { - fn editor_tab_by_trait(&mut self, tab_id: EditorTabName, tab: T) -> &mut Self + fn editor_tab_by_trait(&mut self, tab: T) -> &mut Self where T: EditorTab + Resource + Send + Sync + 'static, { + let tab_name = tab.tab_name(); + self.insert_resource(tab); let show_fn = Box::new( |ui: &mut egui::Ui, commands: &mut Commands, world: &mut World| { @@ -220,26 +219,31 @@ impl EditorUiAppExt for App { show_command: show_fn, title_command: Box::new(|world| { let sizing = world.resource::().clone(); - to_label(world.resource_mut::().title().text(), sizing.text).into() + to_label( + world.resource_mut::().tab_name().title.as_str(), + sizing.text, + ) + .into() }), }; self.world .resource_mut::() .registry - .insert(tab_id, reg); + .insert(tab_name, reg); self } - fn editor_tab( + fn editor_tab( &mut self, - tab_id: EditorTabName, - title: egui::WidgetText, + tab_name: N, tab_systems: impl IntoSystemConfigs, ) -> &mut Self { + let tab_name_holder = TabNameHolder::new(tab_name); + let mut tab = ScheduleEditorTab { schedule: Schedule::default(), - title, + tab_name: tab_name_holder.clone(), }; tab.schedule.add_systems(tab_systems); @@ -247,15 +251,29 @@ impl EditorUiAppExt for App { self.world .resource_mut::() .0 - .insert(tab_id.clone(), tab); + .insert(tab_name_holder.clone(), tab); self.world .resource_mut::() .registry - .insert(tab_id, EditorUiReg::Schedule); + .insert(tab_name_holder, EditorUiReg::Schedule); self } } +pub type EditorTabShowFn = Box; +pub type EditorTabGetTitleFn = Box egui::WidgetText + Send + Sync>; + +/// This enum determine how tab was registered. +/// ResourceBased - tab will be registered as resource +/// Schedule - tab will be registered as system +pub enum EditorUiReg { + ResourceBased { + show_command: EditorTabShowFn, + title_command: EditorTabGetTitleFn, + }, + Schedule, +} + #[derive(Default, Reflect, PartialEq, Eq, Clone)] pub enum NewTabBehaviour { Pop, @@ -264,14 +282,13 @@ pub enum NewTabBehaviour { SplitNode, } -impl ToString for NewTabBehaviour { - fn to_string(&self) -> String { +impl Display for NewTabBehaviour { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Pop => "New window", - Self::SameNode => "Same Node", - Self::SplitNode => "Splits Node", + Self::Pop => write!(f, "New window"), + Self::SameNode => write!(f, "Same Node"), + Self::SplitNode => write!(f, "Splits Node"), } - .to_string() } } diff --git a/crates/editor_tabs/src/schedule_editor_tab.rs b/crates/editor_tabs/src/schedule_editor_tab.rs index ac5ba124..0cc6da5a 100644 --- a/crates/editor_tabs/src/schedule_editor_tab.rs +++ b/crates/editor_tabs/src/schedule_editor_tab.rs @@ -1,14 +1,14 @@ use bevy::{prelude::*, utils::HashMap}; use bevy_egui::egui; -use crate::{EditorTab, EditorTabName}; +use crate::{tab_name::TabNameHolder, EditorTab}; /// Temporary resource for pretty system, based tab registration pub struct EditorUiRef(pub egui::Ui); pub struct ScheduleEditorTab { pub schedule: Schedule, - pub title: egui::WidgetText, + pub tab_name: TabNameHolder, } impl EditorTab for ScheduleEditorTab { @@ -20,10 +20,10 @@ impl EditorTab for ScheduleEditorTab { world.remove_non_send_resource::(); } - fn title(&self) -> egui::WidgetText { - self.title.clone() + fn tab_name(&self) -> TabNameHolder { + self.tab_name.clone() } } #[derive(Resource, Default)] -pub struct ScheduleEditorTabStorage(pub HashMap); +pub struct ScheduleEditorTabStorage(pub HashMap); diff --git a/crates/editor_tabs/src/start_layout.rs b/crates/editor_tabs/src/start_layout.rs new file mode 100644 index 00000000..5c34a2af --- /dev/null +++ b/crates/editor_tabs/src/start_layout.rs @@ -0,0 +1,108 @@ +use crate::{ + tab_name::{TabName, TabNameHolder}, + EditorUi, +}; +/// This mod contains start layouts templates for tabs +/// It was created for easy customization of shown tabs on startup +use bevy::prelude::*; + +pub trait StartLayout { + fn build(&self) -> egui_dock::DockState; +} + +pub trait GroupLayout: StartLayout { + /// Add tab to group to end + fn push(&mut self, group: G, tab: N); + /// Add tab to group to start + fn push_front(&mut self, group: G, tab: N); +} + +pub enum DoublePanel { + TopPanel, + BottomPanel, + MainPanel, +} + +#[derive(Default, Resource)] +pub struct DoublePanelGroup { + pub top_panel: Vec, + pub bottom_panel: Vec, + pub main_panel: Vec, +} + +impl StartLayout for DoublePanelGroup { + fn build(&self) -> egui_dock::DockState { + let mut state = egui_dock::DockState::new(self.main_panel.clone()); + + let [_game, panels] = state.main_surface_mut().split_left( + egui_dock::NodeIndex::root(), + 0.2, + self.top_panel.clone(), + ); + + let [_, _] = state + .main_surface_mut() + .split_below(panels, 0.3, self.bottom_panel.clone()); + + state + } +} + +impl GroupLayout for DoublePanelGroup { + fn push(&mut self, group: DoublePanel, tab: N) { + match group { + DoublePanel::TopPanel => self.top_panel.push(tab.into()), + DoublePanel::BottomPanel => self.bottom_panel.push(tab.into()), + DoublePanel::MainPanel => self.main_panel.push(tab.into()), + } + } + + fn push_front(&mut self, group: DoublePanel, tab: N) { + match group { + DoublePanel::TopPanel => self.top_panel.insert(0, tab.into()), + DoublePanel::BottomPanel => self.bottom_panel.insert(0, tab.into()), + DoublePanel::MainPanel => self.main_panel.insert(0, tab.into()), + } + } +} + +pub trait GroupLayoutExt { + fn layout_push + Resource, N: TabName, G>( + &mut self, + group: G, + tab: N, + ) -> &mut Self; + fn layout_push_front + Resource, N: TabName, G>( + &mut self, + group: G, + tab: N, + ) -> &mut Self; + fn init_layout_group + Resource + Default, G>(&mut self) -> &mut Self; +} + +impl GroupLayoutExt for App { + fn layout_push + Resource, N: TabName, G>( + &mut self, + group: G, + tab: N, + ) -> &mut Self { + self.world.resource_mut::().push(group, tab); + self + } + + fn layout_push_front + Resource, N: TabName, G>( + &mut self, + group: G, + tab: N, + ) -> &mut Self { + self.world.resource_mut::().push_front(group, tab); + self + } + + fn init_layout_group + Resource + Default, G>(&mut self) -> &mut Self { + self.init_resource::(); + self.add_systems(Startup, |mut editor: ResMut, layout: Res| { + editor.set_layout(layout.as_ref()); + }) + } +} diff --git a/crates/editor_tabs/src/tab_name.rs b/crates/editor_tabs/src/tab_name.rs new file mode 100644 index 00000000..0a05a672 --- /dev/null +++ b/crates/editor_tabs/src/tab_name.rs @@ -0,0 +1,63 @@ +/// This module contains the tab naming logic +use std::{ + any::{Any, TypeId}, + fmt::Debug, +}; + +pub trait TabName: Debug + Any { + fn clear_background(&self) -> bool; + fn title(&self) -> String; +} + +#[derive(Clone, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)] +pub struct TabNameHolder { + pub value: String, + pub type_id: TypeId, + pub clear_background: bool, + pub title: String, +} + +impl TabNameHolder { + pub fn new(value: T) -> Self { + Self { + value: format!("{:?}", value), + type_id: TypeId::of::(), + clear_background: value.clear_background(), + title: value.title(), + } + } +} + +impl From for TabNameHolder { + fn from(value: T) -> Self { + Self::new(value) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Debug)] + struct TestTabName(String); + + impl TabName for TestTabName { + fn clear_background(&self) -> bool { + false + } + + fn title(&self) -> String { + self.0.clone() + } + } + + #[test] + fn test_tab_name_holder() { + let holder = TabNameHolder::new(TestTabName("test".to_string())); + + assert_eq!(holder.value, "TestTabName(\"test\")"); + assert_eq!(holder.type_id, TypeId::of::()); + assert_eq!(holder.clear_background, false); + assert_eq!(holder.title, "test"); + } +} diff --git a/crates/editor_tabs/src/tab_viewer.rs b/crates/editor_tabs/src/tab_viewer.rs index d1ea5e0e..cc42530f 100644 --- a/crates/editor_tabs/src/tab_viewer.rs +++ b/crates/editor_tabs/src/tab_viewer.rs @@ -1,7 +1,8 @@ use crate::{ prelude::{to_label, Sizing}, schedule_editor_tab::ScheduleEditorTabStorage, - EditorTab, EditorTabName, EditorUiReg, ERROR_COLOR, + tab_name::TabNameHolder, + EditorTab, EditorUiReg, ERROR_COLOR, }; use bevy::{prelude::*, utils::HashMap}; use bevy_egui::egui; @@ -9,7 +10,7 @@ use convert_case::{Case, Casing}; pub enum EditorTabCommand { Add { - name: EditorTabName, + name: TabNameHolder, surface: egui_dock::SurfaceIndex, node: egui_dock::NodeIndex, }, @@ -18,13 +19,13 @@ pub enum EditorTabCommand { pub struct EditorTabViewer<'a, 'w, 's> { pub world: &'a mut World, pub commands: &'a mut Commands<'w, 's>, - pub registry: &'a mut HashMap, - pub visible: Vec, + pub registry: &'a mut HashMap, + pub visible: Vec, pub tab_commands: Vec, } impl<'a, 'w, 's> egui_dock::TabViewer for EditorTabViewer<'a, 'w, 's> { - type Tab = EditorTabName; + type Tab = TabNameHolder; fn ui(&mut self, ui: &mut egui::Ui, tab_name: &mut Self::Tab) { if let Some(reg) = self.registry.get_mut(tab_name) { @@ -67,7 +68,7 @@ impl<'a, 'w, 's> egui_dock::TabViewer for EditorTabViewer<'a, 'w, 's> { .get(tab) .map_or_else( || to_label(&format!("{tab:?}"), sizing.text).into(), - |tab| to_label(tab.title.text(), sizing.text).into(), + |tab| to_label(&tab.tab_name.title, sizing.text).into(), ), } } else { @@ -76,7 +77,7 @@ impl<'a, 'w, 's> egui_dock::TabViewer for EditorTabViewer<'a, 'w, 's> { } fn clear_background(&self, window: &Self::Tab) -> bool { - !matches!(window, EditorTabName::GameView) + window.clear_background } fn add_popup( @@ -88,19 +89,16 @@ impl<'a, 'w, 's> egui_dock::TabViewer for EditorTabViewer<'a, 'w, 's> { ui.set_min_width(200.0); ui.style_mut().visuals.button_frame = false; let mut counter = 0; - let mut tab_registry: Vec<(&EditorTabName, &EditorUiReg)> = self.registry.iter().collect(); + let mut tab_registry: Vec<(&TabNameHolder, &EditorUiReg)> = self.registry.iter().collect(); tab_registry.sort_by(|a, b| a.0.cmp(b.0)); for registry in tab_registry.iter() { if !self.visible.contains(registry.0) { - let format_name; - if let EditorTabName::Other(name) = registry.0 { - format_name = name.clone(); - } else { - format_name = format!("{:?}", registry.0) - .from_case(Case::Pascal) - .to_case(Case::Title); - } + let format_name = registry + .0 + .title + .from_case(Case::Pascal) + .to_case(Case::Title); if ui.button(format_name).clicked() { self.tab_commands.push(EditorTabCommand::Add { diff --git a/crates/editor_ui/src/camera_view.rs b/crates/editor_ui/src/camera_view.rs index e8d4aa01..02c42928 100644 --- a/crates/editor_ui/src/camera_view.rs +++ b/crates/editor_ui/src/camera_view.rs @@ -17,7 +17,9 @@ use bevy_egui::{ use space_prefab::component::PlaymodeCamera; use space_shared::{toast::ToastMessage, *}; -use crate::{prelude::GameModeSettings, DisableCameraSkip, RenderLayers}; +use crate::{ + editor_tab_name::EditorTabName, prelude::GameModeSettings, DisableCameraSkip, RenderLayers, +}; use space_editor_tabs::prelude::*; @@ -25,7 +27,7 @@ pub struct CameraViewTabPlugin; impl Plugin for CameraViewTabPlugin { fn build(&self, app: &mut App) { - app.editor_tab_by_trait(EditorTabName::CameraView, CameraViewTab::default()); + app.editor_tab_by_trait(CameraViewTab::default()); app.add_systems(PreUpdate, set_camera_viewport.in_set(EditorSet::Editor)); app.add_systems(OnEnter(EditorState::Game), clean_camera_view_tab); } @@ -235,8 +237,8 @@ impl EditorTab for CameraViewTab { } } - fn title(&self) -> bevy_egui::egui::WidgetText { - "Camera view".into() + fn tab_name(&self) -> space_editor_tabs::tab_name::TabNameHolder { + EditorTabName::CameraView.into() } } diff --git a/crates/editor_ui/src/change_chain.rs b/crates/editor_ui/src/change_chain.rs index 3af64e65..a9967b9c 100644 --- a/crates/editor_ui/src/change_chain.rs +++ b/crates/editor_ui/src/change_chain.rs @@ -3,14 +3,13 @@ use bevy::prelude::*; use space_editor_tabs::prelude::*; use space_undo::ChangeChain; +use crate::editor_tab_name::EditorTabName; + pub struct ChangeChainViewPlugin; impl Plugin for ChangeChainViewPlugin { fn build(&self, app: &mut App) { - app.editor_tab_by_trait( - EditorTabName::Other("Change Chain".to_string()), - ChangeChainView, - ); + app.editor_tab_by_trait(ChangeChainView); } } @@ -31,7 +30,7 @@ impl EditorTab for ChangeChainView { } } - fn title(&self) -> bevy_egui::egui::WidgetText { - "Change Chain".into() + fn tab_name(&self) -> space_editor_tabs::tab_name::TabNameHolder { + EditorTabName::ChangeChain.into() } } diff --git a/crates/editor_ui/src/debug_panels.rs b/crates/editor_ui/src/debug_panels.rs index 4e29a06c..1c0344fe 100644 --- a/crates/editor_ui/src/debug_panels.rs +++ b/crates/editor_ui/src/debug_panels.rs @@ -2,6 +2,8 @@ use bevy::prelude::*; use bevy_egui::egui; use space_editor_tabs::prelude::*; +use crate::editor_tab_name::EditorTabName; + #[derive(Resource)] pub struct DebugWorldInspector {} @@ -10,7 +12,7 @@ impl EditorTab for DebugWorldInspector { bevy_inspector_egui::bevy_inspector::ui_for_world(world, ui); } - fn title(&self) -> egui::WidgetText { - "Debug World Inspector".into() + fn tab_name(&self) -> space_editor_tabs::tab_name::TabNameHolder { + EditorTabName::DebugWorldInspector.into() } } diff --git a/crates/editor_ui/src/editor_tab_name.rs b/crates/editor_ui/src/editor_tab_name.rs new file mode 100644 index 00000000..7e1c9592 --- /dev/null +++ b/crates/editor_ui/src/editor_tab_name.rs @@ -0,0 +1,38 @@ +use space_editor_tabs::tab_name::TabName; + +#[derive(Clone, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)] +pub enum EditorTabName { + CameraView, + EventDispatcher, + GameView, + Hierarchy, + Inspector, + Resource, + RuntimeAssets, + Settings, + ToolBox, + ChangeChain, + DebugWorldInspector, +} + +impl TabName for EditorTabName { + fn clear_background(&self) -> bool { + *self != Self::GameView + } + + fn title(&self) -> String { + match self { + Self::CameraView => "Camera View".to_string(), + Self::EventDispatcher => "Event Dispatcher".to_string(), + Self::GameView => "Game View".to_string(), + Self::Hierarchy => "Hierarchy".to_string(), + Self::Inspector => "Inspector".to_string(), + Self::Resource => "Resource".to_string(), + Self::RuntimeAssets => "Runtime Assets".to_string(), + Self::Settings => "Settings".to_string(), + Self::ToolBox => "Tool Box".to_string(), + Self::ChangeChain => "Change Chain".to_string(), + Self::DebugWorldInspector => "Debug World Inspector".to_string(), + } + } +} diff --git a/crates/editor_ui/src/game_view.rs b/crates/editor_ui/src/game_view.rs index c5493875..f28988e9 100644 --- a/crates/editor_ui/src/game_view.rs +++ b/crates/editor_ui/src/game_view.rs @@ -5,6 +5,8 @@ use space_undo::UndoRedo; use space_shared::*; +use crate::editor_tab_name::EditorTabName; + use super::tool::EditorTool; use space_editor_tabs::prelude::*; @@ -12,7 +14,7 @@ pub struct GameViewPlugin; impl Plugin for GameViewPlugin { fn build(&self, app: &mut App) { - app.editor_tab_by_trait(EditorTabName::GameView, GameViewTab::default()); + app.editor_tab_by_trait(GameViewTab::default()); } } @@ -92,8 +94,8 @@ impl EditorTab for GameViewTab { }); } - fn title(&self) -> bevy_egui::egui::WidgetText { - "Game view".into() + fn tab_name(&self) -> space_editor_tabs::tab_name::TabNameHolder { + EditorTabName::GameView.into() } } diff --git a/crates/editor_ui/src/hierarchy.rs b/crates/editor_ui/src/hierarchy.rs index b707dea4..36c26215 100644 --- a/crates/editor_ui/src/hierarchy.rs +++ b/crates/editor_ui/src/hierarchy.rs @@ -14,6 +14,8 @@ use space_shared::*; use space_editor_tabs::prelude::*; +use crate::editor_tab_name::EditorTabName; + pub const WARN_COLOR: egui::Color32 = egui::Color32::from_rgb(225, 206, 67); /// Event to clone entity with clone all registered components @@ -29,7 +31,7 @@ pub struct SpaceHierarchyPlugin {} impl Plugin for SpaceHierarchyPlugin { fn build(&self, app: &mut App) { app.init_resource::(); - app.editor_tab(EditorTabName::Hierarchy, "Hierarchy".into(), show_hierarchy); + app.editor_tab(EditorTabName::Hierarchy, show_hierarchy); // app.add_systems(Update, show_hierarchy.before(crate::editor::ui_camera_block).in_set(EditorSet::Editor)); app.add_systems(Update, clone_enitites.in_set(EditorSet::Editor)); diff --git a/crates/editor_ui/src/inspector/events_dispatcher.rs b/crates/editor_ui/src/inspector/events_dispatcher.rs index 973d4559..c7d972ff 100644 --- a/crates/editor_ui/src/inspector/events_dispatcher.rs +++ b/crates/editor_ui/src/inspector/events_dispatcher.rs @@ -14,8 +14,8 @@ impl EditorTab for EventDispatcherTab { inspect(ui, world, &mut self.open_events); } - fn title(&self) -> egui::WidgetText { - "Event Dispatcher".into() + fn tab_name(&self) -> space_editor_tabs::tab_name::TabNameHolder { + TabNameHolder::new(EditorTabName::EventDispatcher) } } diff --git a/crates/editor_ui/src/inspector/mod.rs b/crates/editor_ui/src/inspector/mod.rs index d488b34a..54b40a98 100644 --- a/crates/editor_ui/src/inspector/mod.rs +++ b/crates/editor_ui/src/inspector/mod.rs @@ -22,7 +22,7 @@ use space_shared::ext::bevy_inspector_egui::{ inspector_egui_impls::InspectorEguiImpl, reflect_inspector::InspectorUi, }; -use crate::icons::add_component_icon; +use crate::{editor_tab_name::EditorTabName, icons::add_component_icon}; use space_editor_tabs::prelude::*; use self::{ @@ -48,13 +48,10 @@ impl Plugin for SpaceInspectorPlugin { app.editor_component_priority::(0); app.editor_component_priority::(1); - app.editor_tab_by_trait(EditorTabName::Inspector, InspectorTab::default()); - app.editor_tab_by_trait(EditorTabName::Resource, ResourceTab::default()); - app.editor_tab_by_trait( - EditorTabName::EventDispatcher, - EventDispatcherTab::default(), - ); - app.editor_tab_by_trait(EditorTabName::RuntimeAssets, RuntimeAssetsTab::default()); + app.editor_tab_by_trait(InspectorTab::default()); + app.editor_tab_by_trait(ResourceTab::default()); + app.editor_tab_by_trait(EventDispatcherTab::default()); + app.editor_tab_by_trait(RuntimeAssetsTab::default()); app.add_systems(Update, execute_inspect_command); @@ -349,8 +346,8 @@ impl EditorTab for InspectorTab { } } - fn title(&self) -> egui::WidgetText { - "Inspector".into() + fn tab_name(&self) -> space_editor_tabs::tab_name::TabNameHolder { + space_editor_tabs::tab_name::TabNameHolder::new(EditorTabName::Inspector) } } diff --git a/crates/editor_ui/src/inspector/resources.rs b/crates/editor_ui/src/inspector/resources.rs index 1437ea70..ac12df0e 100644 --- a/crates/editor_ui/src/inspector/resources.rs +++ b/crates/editor_ui/src/inspector/resources.rs @@ -14,8 +14,8 @@ impl EditorTab for ResourceTab { inspect(ui, world, &mut self.open_resources); } - fn title(&self) -> egui::WidgetText { - "Resource".into() + fn tab_name(&self) -> space_editor_tabs::tab_name::TabNameHolder { + EditorTabName::Resource.into() } } diff --git a/crates/editor_ui/src/inspector/runtime_assets.rs b/crates/editor_ui/src/inspector/runtime_assets.rs index b8b0d59b..00929bda 100644 --- a/crates/editor_ui/src/inspector/runtime_assets.rs +++ b/crates/editor_ui/src/inspector/runtime_assets.rs @@ -14,8 +14,8 @@ impl EditorTab for RuntimeAssetsTab { inspect(ui, world, &mut self.open_assets); } - fn title(&self) -> egui::WidgetText { - "Runtime Assets".into() + fn tab_name(&self) -> space_editor_tabs::tab_name::TabNameHolder { + EditorTabName::RuntimeAssets.into() } } diff --git a/crates/editor_ui/src/lib.rs b/crates/editor_ui/src/lib.rs index 0b742ef5..53e40837 100644 --- a/crates/editor_ui/src/lib.rs +++ b/crates/editor_ui/src/lib.rs @@ -51,6 +51,9 @@ pub mod camera_plugin; ///Selection logic pub mod selection; +/// Editor tab name +pub mod editor_tab_name; + pub mod icons; use bevy_debug_grid::{Grid, GridAxis, SubGrid, TrackedGrid, DEFAULT_GRID_ALPHA}; @@ -118,6 +121,8 @@ pub mod prelude { pub use crate::simple_editor_setup; pub use crate::ui_plugin::*; pub use crate::EditorPlugin; + + pub use crate::editor_tab_name::*; } /// External dependencies for editor crate diff --git a/crates/editor_ui/src/settings.rs b/crates/editor_ui/src/settings.rs index ae8e5cf7..692c399e 100644 --- a/crates/editor_ui/src/settings.rs +++ b/crates/editor_ui/src/settings.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use bevy::{ prelude::*, utils::{HashMap, HashSet}, @@ -12,13 +14,15 @@ use space_persistence::*; use space_editor_tabs::prelude::*; +use crate::editor_tab_name::EditorTabName; + const GAME_MODES: [GameMode; 2] = [GameMode::Game2D, GameMode::Game3D]; pub struct SettingsWindowPlugin; impl Plugin for SettingsWindowPlugin { fn build(&self, app: &mut App) { - app.editor_tab_by_trait(EditorTabName::Settings, SettingsWindow::default()); + app.editor_tab_by_trait(SettingsWindow::default()); app.register_type::() .init_resource::() .init_resource::() @@ -43,11 +47,11 @@ pub enum GameMode { Game3D, } -impl ToString for GameMode { - fn to_string(&self) -> String { +impl Display for GameMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Game2D => String::from("2D"), - Self::Game3D => String::from("3D"), + Self::Game2D => write!(f, "2D"), + Self::Game3D => write!(f, "3D"), } } } @@ -248,7 +252,7 @@ impl EditorTab for SettingsWindow { } } - fn title(&self) -> egui::WidgetText { - "Settings".into() + fn tab_name(&self) -> space_editor_tabs::tab_name::TabNameHolder { + EditorTabName::Settings.into() } } diff --git a/crates/editor_ui/src/ui_plugin.rs b/crates/editor_ui/src/ui_plugin.rs index 963732cf..1391e17c 100644 --- a/crates/editor_ui/src/ui_plugin.rs +++ b/crates/editor_ui/src/ui_plugin.rs @@ -2,7 +2,7 @@ use crate::*; use bevy::prelude::*; use meshless_visualizer::draw_light_gizmo; -use self::change_chain::ChangeChainViewPlugin; +use self::{change_chain::ChangeChainViewPlugin, editor_tab_name::EditorTabName}; /// All systems for editor ui will be placed in UiSystemSet #[derive(SystemSet, Hash, PartialEq, Eq, Debug, Clone, Copy)] @@ -65,18 +65,13 @@ pub struct DefaultEditorLayoutPlugin; impl Plugin for DefaultEditorLayoutPlugin { fn build(&self, app: &mut App) { - let mut editor = app.world.resource_mut::(); - editor.tree = egui_dock::DockState::new(vec![EditorTabName::GameView]); + app.init_layout_group::(); - let [_game, hierarchy] = editor.tree.main_surface_mut().split_left( - egui_dock::NodeIndex::root(), - 0.2, - vec![EditorTabName::Hierarchy], - ); - let [_hierarchy, _inspector] = editor.tree.main_surface_mut().split_below( - hierarchy, - 0.3, - vec![EditorTabName::Inspector], + app.layout_push::(DoublePanel::MainPanel, EditorTabName::GameView); + app.layout_push::(DoublePanel::TopPanel, EditorTabName::Hierarchy); + app.layout_push::( + DoublePanel::BottomPanel, + EditorTabName::Inspector, ); } } @@ -130,12 +125,9 @@ impl Plugin for EditorUiCore { reset_camera_viewport.run_if(in_state(EditorState::Game)), ); app.add_systems(OnEnter(ShowEditorUi::Hide), reset_camera_viewport); - app.editor_tab_by_trait(EditorTabName::GameView, GameViewTab::default()); + app.editor_tab_by_trait(GameViewTab::default()); - app.editor_tab_by_trait( - EditorTabName::Other("Debug World Inspector".to_string()), - self::debug_panels::DebugWorldInspector {}, - ); + app.editor_tab_by_trait(self::debug_panels::DebugWorldInspector {}); app.init_resource::(); diff --git a/examples/custom_editor_tab.rs b/examples/custom_editor_tab.rs new file mode 100644 index 00000000..4ae65aed --- /dev/null +++ b/examples/custom_editor_tab.rs @@ -0,0 +1,92 @@ +use bevy::prelude::*; +use space_editor::prelude::*; + +/// This example shows how to create custom editor tabs +/// space_editor allows to create tabs by implementing trait EditorTab +/// or by using system based tabs + +fn main() { + App::default() + .add_plugins(DefaultPlugins) + .add_plugins(SpaceEditorPlugin) + .add_systems(Startup, simple_editor_setup) + // Add trait based tab + .editor_tab_by_trait(TraitEditorTab) + // Add system based tab + .editor_tab(CustomTabName::SystemBased, system_tab) + // Add trait based tab as first tab in Bottom panel + .layout_push_front::( + DoublePanel::BottomPanel, + CustomTabName::TraitBased, + ) + // Add system based tab as first tab in Main panel + .layout_push_front::( + DoublePanel::MainPanel, + CustomTabName::SystemBased, + ) + .run(); +} + +#[derive(Debug)] +/// A custom tab name +enum CustomTabName { + /// A trait based tab + TraitBased, + /// A system based tab + SystemBased, +} + +impl TabName for CustomTabName { + /// Set clear_background to true to clear background of the panel + fn clear_background(&self) -> bool { + true + } + + /// Return title of the tab + fn title(&self) -> String { + match self { + CustomTabName::TraitBased => String::from("Trait Based"), + CustomTabName::SystemBased => String::from("System Based"), + } + } +} + +#[derive(Resource)] +/// A struct that implements EditorTab trait +/// which allows to create custom tabs in the editor +struct TraitEditorTab; + +impl EditorTab for TraitEditorTab { + /// This function is called when tab needs to be rendered + /// ui is bevy_egui::egui::Ui and it allows to build ui inside the tab + fn ui( + &mut self, + ui: &mut space_prefab::prelude::ext::bevy_inspector_egui::egui::Ui, + _commands: &mut Commands, + _world: &mut World, + ) { + ui.label("Trait Based"); + } + + /// Return name of the tab + fn tab_name(&self) -> TabNameHolder { + CustomTabName::TraitBased.into() + } +} + +/// This function is a system that will be called every frame and will construct tab ui +fn system_tab(mut commands: Commands, mut ui: NonSendMut) { + let ui = &mut ui.0; + + ui.label("System Based"); + + // If button is clicked spawn a new entity with + // SpatialBundle and Name components + if ui.button("Add").clicked() { + commands.spawn(( + SpatialBundle::default(), + PrefabMarker, + Name::new("New Entity".to_string()), + )); + } +} diff --git a/examples/platformer.rs b/examples/platformer.rs index e9f1cc15..f5f7a82f 100644 --- a/examples/platformer.rs +++ b/examples/platformer.rs @@ -19,21 +19,11 @@ fn main() { .editor_relation::() .editor_registry::() .editor_relation::() - .editor_tab( - EditorTabName::Other("simple_tab".to_string()), - "Simple tab".into(), - simple_tab_system, - ) .add_systems(Update, move_player.run_if(in_state(EditorState::Game))) .add_systems(Update, camera_follow.run_if(in_state(EditorState::Game))) .run(); } -fn simple_tab_system(mut ui: NonSendMut) { - let ui = &mut ui.0; - ui.label("Hello editor"); -} - fn configure_editor(mut load_event: EventWriter) { load_event.send(EditorEvent::Load(EditorPrefabPath::File( "scenes/platformer.scn.ron".to_string(),