From 477f0366e59a0036552ca49b6927432fd7492b2b Mon Sep 17 00:00:00 2001 From: Dmitry Stepanov Date: Thu, 10 Sep 2026 23:00:25 +0300 Subject: [PATCH] entity creator window - replaces chained context menu with a dedicated window - old functionality preserved, new will be added in following commits --- editor/src/lib.rs | 65 ++++--- editor/src/menu/create.rs | 209 ----------------------- editor/src/menu/mod.rs | 46 +---- editor/src/menu/ui.rs | 115 ------------- editor/src/message.rs | 2 + editor/src/ui_scene/menu.rs | 58 +++---- editor/src/world/create.rs | 327 ++++++++++++++++++++++++++++++++++++ editor/src/world/menu.rs | 146 ++-------------- editor/src/world/mod.rs | 9 +- 9 files changed, 417 insertions(+), 560 deletions(-) delete mode 100644 editor/src/menu/create.rs delete mode 100644 editor/src/menu/ui.rs create mode 100644 editor/src/world/create.rs diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 38356ddaf..c7412d3bd 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -57,7 +57,7 @@ pub mod world; pub use fyrox; -use crate::world::WorldViewerItemContextMenu; +use crate::world::create::EntityCreator; use crate::{ asset::{item::AssetItem, AssetBrowser}, audio::{preview::AudioPreviewPanel, AudioPanel}, @@ -658,6 +658,7 @@ pub struct Editor { pub engine: Engine, pub plugins: EditorPluginsContainer, pub focused: bool, + pub entity_creator: EntityCreator, pub update_loop_state: UpdateLoopState, pub is_suspended: bool, pub scene_node_context_menu: Rc>, @@ -836,15 +837,8 @@ impl Editor { ) .build(ctx); let navmesh_panel = NavmeshPanel::new(scene_viewer.frame(), ctx, message_sender.clone()); - let scene_node_context_menu = Rc::new(RefCell::new(SceneNodeContextMenu::new( - &engine.serialization_context, - &engine.widget_constructors, - ctx, - ))); - let widget_context_menu = Rc::new(RefCell::new(WidgetContextMenu::new( - &engine.widget_constructors, - ctx, - ))); + let scene_node_context_menu = Rc::new(RefCell::new(SceneNodeContextMenu::new(ctx))); + let widget_context_menu = Rc::new(RefCell::new(WidgetContextMenu::new(ctx))); let world_outliner = WorldViewer::new(ctx, message_sender.clone(), &settings); let command_stack_viewer = CommandStackViewer::new(ctx, message_sender.clone()); let log = LogPanel::new( @@ -1017,6 +1011,7 @@ impl Editor { ) .with_buttons(MessageBoxButtons::Ok) .build(ctx); + let entity_creator = EntityCreator::new(ctx); let save_scene_dialog = SaveSceneConfirmationDialog::new(ctx); @@ -1086,6 +1081,7 @@ impl Editor { // was created. So we must assume that the editor is focused by default, otherwise editor's thread // will sleep forever and the window won't come up. focused: true, + entity_creator, update_loop_state: UpdateLoopState::default(), is_suspended: false, scene_node_context_menu, @@ -1482,6 +1478,13 @@ impl Editor { ); if let Some(game_scene) = current_scene_entry.controller.downcast_mut::() { + self.entity_creator.handle_ui_message_with_game_scene( + game_scene, + engine, + message, + ¤t_scene_entry.selection, + &self.message_sender, + ); self.particle_system_control_panel.handle_ui_message( message, ¤t_scene_entry.selection, @@ -1560,6 +1563,14 @@ impl Editor { self.light_panel .handle_ui_message(message, game_scene, engine); } else if let Some(ui_scene) = current_scene_entry.controller.downcast_mut::() { + self.entity_creator.handle_ui_message_with_ui_scene( + ui_scene, + engine, + message, + ¤t_scene_entry.selection, + &self.message_sender, + ); + self.bbcode_panel.handle_ui_message( message, ¤t_scene_entry.selection, @@ -2232,7 +2243,9 @@ impl Editor { } fn on_scene_changed(&mut self, old_selection: Selection) { - let ui = &self.engine.user_interfaces.first(); + self.refresh_entity_creator(); + + let ui = self.engine.user_interfaces.first_mut(); let entry = self.scenes.current_scene_entry_ref(); if let Some(game_scene) = entry.controller.downcast_ref::() { self.world_viewer.item_context_menu = Some(self.scene_node_context_menu.clone()); @@ -2247,7 +2260,6 @@ impl Editor { self.message_sender .send(Message::SelectionChanged { old_selection }); - self.menu.on_scene_changed(&*entry.controller, ui); self.world_viewer.clear(ui); @@ -2685,6 +2697,10 @@ impl Editor { ) } } + Message::OpenEntityCreator(mode) => { + self.entity_creator + .open(mode, self.engine.user_interfaces.first()); + } Message::SetAssetBrowserCurrentDir(path) => { self.asset_browser .request_current_path(path, self.engine.user_interfaces.first()); @@ -2841,21 +2857,22 @@ impl Editor { ); } - fn on_game_plugin_added(&mut self) { - let mut widget_context_menu = self.widget_context_menu.borrow_mut(); - let mut scene_node_context_menu = self.scene_node_context_menu.borrow_mut(); - for menu in [ - &mut *widget_context_menu as &mut dyn WorldViewerItemContextMenu, - &mut *scene_node_context_menu as &mut dyn WorldViewerItemContextMenu, - ] { - menu.on_plugin_added( - &self.engine.serialization_context, - &self.engine.widget_constructors, - self.engine.user_interfaces.first_mut(), - ); + fn refresh_entity_creator(&mut self) { + let ui = self.engine.user_interfaces.first_mut(); + let entry = self.scenes.current_scene_entry_ref(); + if entry.controller.downcast_ref::().is_some() { + self.entity_creator + .on_constructors_changed(ui, &self.engine.serialization_context.node_constructors); + } else if entry.controller.downcast_ref::().is_some() { + self.entity_creator + .on_constructors_changed(ui, &self.engine.widget_constructors); } } + fn on_game_plugin_added(&mut self) { + self.refresh_entity_creator(); + } + pub fn add_game_plugin

(&mut self, plugin: P) where P: Plugin + 'static, diff --git a/editor/src/menu/create.rs b/editor/src/menu/create.rs deleted file mode 100644 index b62ef9a6d..000000000 --- a/editor/src/menu/create.rs +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -use crate::{ - command::{Command, CommandGroup}, - fyrox::{ - core::{algebra::Vector3, pool::Handle}, - engine::{Engine, SerializationContext}, - fxhash::FxHashMap, - gui::{ - menu::MenuItemMessage, message::UiMessage, widget::WidgetMessage, BuildContext, - UserInterface, - }, - scene::node::Node, - }, - menu::{create_menu_item, create_root_menu_item, ui::UiMenu}, - message::MessageSender, - scene::{ - commands::graph::{AddNodeCommand, MoveNodeCommand}, - controller::SceneController, - GameScene, Selection, - }, - ui_scene::UiScene, - Mode, -}; -use fyrox::core::log::Log; -use fyrox::core::uuid::{uuid, Uuid}; -use fyrox::graph::constructor::{VariantConstructor, VariantResult}; -use fyrox::gui::constructor::WidgetConstructorContainer; -use fyrox::gui::menu::{MenuItem, SortingPredicate}; -use fyrox::scene::graph::Graph; - -pub struct CreateEntityRootMenu { - pub menu: Handle, - pub sub_menus: CreateEntityMenu, -} - -impl CreateEntityRootMenu { - pub const CREATE: Uuid = uuid!("edc4d319-2e8e-4173-8b35-73ff4982f380"); - - pub fn new( - serialization_context: &SerializationContext, - widget_constructors_container: &WidgetConstructorContainer, - ctx: &mut BuildContext, - ) -> Self { - let sub_menus = - CreateEntityMenu::new(serialization_context, widget_constructors_container, ctx); - - let menu = create_root_menu_item("Create", Self::CREATE, sub_menus.root_items.clone(), ctx); - - ctx.inner().send( - menu, - MenuItemMessage::Sort(SortingPredicate::sort_by_text()), - ); - - Self { menu, sub_menus } - } - - pub fn handle_ui_message( - &mut self, - message: &UiMessage, - sender: &MessageSender, - controller: &mut dyn SceneController, - selection: &Selection, - engine: &mut Engine, - ) { - if let Some(node) = self - .sub_menus - .handle_ui_message(message, sender, controller, selection, engine) - { - if let Some(game_scene) = controller.downcast_ref::() { - let scene = &engine.scenes[game_scene.scene]; - - let position = game_scene - .camera_controller - .placement_position(&scene.graph, Default::default()); - - let node_handle = scene.graph.generate_free_handles(1)[0]; - sender.do_command(CommandGroup::from(vec![ - Command::new(AddNodeCommand::new(node, Handle::NONE, true)), - Command::new(MoveNodeCommand::new( - node_handle, - Vector3::default(), - position, - )), - ])); - } - } - } - - pub fn on_scene_changed(&self, controller: &dyn SceneController, ui: &UserInterface) { - self.sub_menus.on_scene_changed(controller, ui); - } - - pub fn on_mode_changed(&mut self, ui: &UserInterface, mode: &Mode) { - ui.send(self.menu, WidgetMessage::Enabled(mode.is_edit())); - } -} - -pub struct CreateEntityMenu { - ui_menu: UiMenu, - pub root_items: Vec>, - constructor_views: FxHashMap, VariantConstructor>, -} - -impl CreateEntityMenu { - pub fn new( - serialization_context: &SerializationContext, - widget_constructor_container: &WidgetConstructorContainer, - ctx: &mut BuildContext, - ) -> Self { - let ui_menu = UiMenu::new(widget_constructor_container, "UI", ctx); - - let mut root_items = vec![ui_menu.menu]; - let mut groups = FxHashMap::default(); - let mut constructor_views = FxHashMap::default(); - let constructors = serialization_context.node_constructors.map(); - for constructor in constructors.values() { - for variant in constructor.variants.iter() { - let item = create_menu_item(&variant.name, Uuid::new_v4(), vec![], ctx); - constructor_views.insert(item, variant.constructor.clone()); - if constructor.group.is_empty() { - root_items.push(item); - } else { - let group = *groups.entry(constructor.group).or_insert_with(|| { - let group = - create_menu_item(constructor.group, Uuid::new_v4(), vec![], ctx); - root_items.push(group); - group - }); - ctx.inner().send(group, MenuItemMessage::AddItem(item)) - } - } - } - - for root_item in root_items.iter() { - ctx.inner().send( - *root_item, - MenuItemMessage::Sort(SortingPredicate::sort_by_text()), - ) - } - - Self { - ui_menu, - constructor_views, - root_items, - } - } - - pub fn on_scene_changed(&self, controller: &dyn SceneController, ui: &UserInterface) { - let is_ui_scene = controller.downcast_ref::().is_some(); - - ui.send(self.ui_menu.menu, WidgetMessage::Enabled(is_ui_scene)); - - for widget in self.root_items.iter() { - if *widget == self.ui_menu.menu { - continue; - } - ui.send(*widget, WidgetMessage::Enabled(!is_ui_scene)); - } - } - - pub fn handle_ui_message( - &mut self, - message: &UiMessage, - sender: &MessageSender, - controller: &mut dyn SceneController, - selection: &Selection, - engine: &mut Engine, - ) -> Option { - if let Some(ui_scene) = controller.downcast_mut::() { - self.ui_menu - .handle_ui_message(sender, message, ui_scene, selection); - } else if let Some(game_scene) = controller.downcast_mut::() { - let graph = &mut engine.scenes[game_scene.scene].graph; - if let Some(MenuItemMessage::Click) = message.data::() { - if let Some(constructor) = self - .constructor_views - .get(&message.destination().to_variant()) - { - if let VariantResult::Owned(node) = constructor(graph) { - return Some(node); - } else { - Log::err("Unsupported"); - } - } - } - } - - None - } -} diff --git a/editor/src/menu/mod.rs b/editor/src/menu/mod.rs index 2f0ffd26b..26596db37 100644 --- a/editor/src/menu/mod.rs +++ b/editor/src/menu/mod.rs @@ -22,45 +22,38 @@ use crate::{ asset::preview::cache::IconRequest, export::ExportWindow, fyrox::{ + asset::manager::ResourceManager, + core::uuid::Uuid, core::{algebra::Vector2, pool::Handle}, gui::{ - menu::{MenuBuilder, MenuItemBuilder, MenuItemContent}, + file_browser::FileType, + image::{Image, ImageBuilder}, + menu::{MenuBuilder, MenuItem, MenuItemBuilder, MenuItemContent}, message::UiMessage, + texture::TextureResource, widget::{WidgetBuilder, WidgetMessage}, + window::Window, BuildContext, Thickness, UserInterface, }, }, - menu::{ - create::CreateEntityRootMenu, edit::EditMenu, file::FileMenu, help::HelpMenu, - utils::UtilsMenu, view::ViewMenu, - }, + menu::{edit::EditMenu, file::FileMenu, help::HelpMenu, utils::UtilsMenu, view::ViewMenu}, message::MessageSender, - scene::{container::EditorSceneEntry, controller::SceneController}, + scene::container::EditorSceneEntry, settings::Settings, stats::StatisticsWindow, Engine, Mode, SceneSettingsWindow, }; -use fyrox::asset::manager::ResourceManager; -use fyrox::core::uuid::Uuid; -use fyrox::gui::file_browser::FileType; -use fyrox::gui::image::{Image, ImageBuilder}; -use fyrox::gui::menu::MenuItem; -use fyrox::gui::texture::TextureResource; -use fyrox::gui::window::Window; use std::path::PathBuf; use std::sync::mpsc::Sender; -pub mod create; pub mod edit; pub mod file; pub mod help; -pub mod ui; pub mod utils; pub mod view; pub struct Menu { pub menu: Handle, - pub create_entity_menu: CreateEntityRootMenu, pub edit_menu: EditMenu, pub file_menu: FileMenu, pub view_menu: ViewMenu, @@ -160,11 +153,6 @@ impl Menu { pub fn new(engine: &mut Engine, message_sender: MessageSender, settings: &Settings) -> Self { let file_menu = FileMenu::new(engine, settings); let ctx = &mut engine.user_interfaces.first_mut().build_ctx(); - let create_entity_menu = CreateEntityRootMenu::new( - &engine.serialization_context, - &engine.widget_constructors, - ctx, - ); let edit_menu = EditMenu::new(ctx); let view_menu = ViewMenu::new(ctx); let utils_menu = UtilsMenu::new(ctx); @@ -174,7 +162,6 @@ impl Menu { .with_items(vec![ file_menu.menu, edit_menu.menu, - create_entity_menu.menu, view_menu.menu, utils_menu.menu, help_menu.menu, @@ -183,7 +170,6 @@ impl Menu { Self { menu, - create_entity_menu, edit_menu, message_sender, file_menu, @@ -216,7 +202,6 @@ impl Menu { self.file_menu.close_scene, self.file_menu.save, self.file_menu.save_as, - self.create_entity_menu.menu, self.edit_menu.menu, self.file_menu.open_scene_settings, ] @@ -236,14 +221,6 @@ impl Menu { ctx.engine, ); - self.create_entity_menu.handle_ui_message( - message, - &self.message_sender, - &mut *entry.controller, - &entry.selection, - ctx.engine, - ); - self.utils_menu.handle_ui_message( message, &mut ctx.panels, @@ -266,12 +243,7 @@ impl Menu { self.help_menu.handle_ui_message(message); } - pub fn on_scene_changed(&self, controller: &dyn SceneController, ui: &UserInterface) { - self.create_entity_menu.on_scene_changed(controller, ui); - } - pub fn on_mode_changed(&mut self, ui: &UserInterface, mode: &Mode) { - self.create_entity_menu.on_mode_changed(ui, mode); self.edit_menu.on_mode_changed(ui, mode); self.file_menu.on_mode_changed(ui, mode); } diff --git a/editor/src/menu/ui.rs b/editor/src/menu/ui.rs deleted file mode 100644 index 4dabc91c7..000000000 --- a/editor/src/menu/ui.rs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -use crate::{ - fyrox::{ - core::{log::Log, pool::Handle}, - fxhash::FxHashMap, - graph::constructor::{VariantConstructor, VariantResult}, - gui::{ - constructor::WidgetConstructorContainer, menu::MenuItemMessage, message::UiMessage, - BuildContext, UiNode, UserInterface, - }, - }, - menu::create_menu_item, - message::MessageSender, - scene::Selection, - ui_scene::{commands::graph::AddWidgetCommand, UiScene}, -}; -use fyrox::core::uuid::{uuid, Uuid}; -use fyrox::gui::menu::{MenuItem, SortingPredicate}; - -pub struct UiMenu { - pub menu: Handle, - constructor_views: FxHashMap, VariantConstructor>, -} - -impl UiMenu { - pub const MENU: Uuid = uuid!("3a3f7035-529e-4d79-a413-a4d887d5e32e"); - - pub fn new( - constructors: &WidgetConstructorContainer, - name: &str, - ctx: &mut BuildContext, - ) -> Self { - let mut root_items = vec![]; - let mut groups = FxHashMap::default(); - let mut constructor_views = FxHashMap::default(); - let constructors = constructors.map(); - for constructor in constructors.values() { - for variant in constructor.variants.iter() { - let item = create_menu_item(&variant.name, Uuid::new_v4(), vec![], ctx); - constructor_views.insert(item, variant.constructor.clone()); - if constructor.group.is_empty() { - root_items.push(item); - } else { - let group = *groups.entry(constructor.group).or_insert_with(|| { - let group = - create_menu_item(constructor.group, Uuid::new_v4(), vec![], ctx); - root_items.push(group); - group - }); - ctx.inner().send(group, MenuItemMessage::AddItem(item)); - } - } - } - - let menu = create_menu_item(name, Self::MENU, root_items.clone(), ctx); - - for root_item in root_items.iter().chain(&[menu]) { - ctx.inner().send( - *root_item, - MenuItemMessage::Sort(SortingPredicate::sort_by_text()), - ) - } - - Self { - menu, - constructor_views, - } - } - - pub fn handle_ui_message( - &mut self, - sender: &MessageSender, - message: &UiMessage, - scene: &mut UiScene, - selection: &Selection, - ) { - if let Some(MenuItemMessage::Click) = message.data::() { - if let Some(constructor) = self - .constructor_views - .get_mut(&message.destination().to_variant()) - { - if let VariantResult::Handle(ui_node_handle) = constructor(&mut scene.ui) { - let sub_graph = scene.ui.take_reserve_sub_graph(ui_node_handle); - let parent = if let Some(selection) = selection.as_ui() { - selection.widgets.first().cloned().unwrap_or_default() - } else { - Handle::NONE - }; - sender.do_command(AddWidgetCommand::new(sub_graph, parent, true)); - } else { - Log::err("Unsupported"); - } - } - } - } -} diff --git a/editor/src/message.rs b/editor/src/message.rs index 52f7292e4..3e1fcfec1 100644 --- a/editor/src/message.rs +++ b/editor/src/message.rs @@ -18,6 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +use crate::world::create::EntityCreatorMode; use crate::{ command::{Command, CommandGroup, CommandTrait}, fyrox::{ @@ -122,6 +123,7 @@ pub enum Message { ViewSurfaceData(SurfaceResource), SyncInteractionModes, SetAssetBrowserCurrentDir(PathBuf), + OpenEntityCreator(EntityCreatorMode), } #[derive(Clone, Debug, Reflect)] diff --git a/editor/src/ui_scene/menu.rs b/editor/src/ui_scene/menu.rs index 4ab4dba85..58889b114 100644 --- a/editor/src/ui_scene/menu.rs +++ b/editor/src/ui_scene/menu.rs @@ -18,6 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +use crate::world::create::EntityCreatorMode; use crate::{ fyrox::{ asset::manager::ResourceManager, @@ -25,19 +26,17 @@ use crate::{ pool::Handle, uuid::{uuid, Uuid}, }, - engine::SerializationContext, graph::{NodeWrapper, SceneGraph}, gui::{ - constructor::WidgetConstructorContainer, menu::{ContextMenuBuilder, MenuItem, MenuItemMessage}, message::UiMessage, popup::{Placement, PopupBuilder, PopupMessage}, - stack_panel::{StackPanel, StackPanelBuilder}, + stack_panel::StackPanelBuilder, widget::{WidgetBuilder, WidgetMessage}, - BuildContext, RcUiNodeHandle, UiNode, UserInterface, + BuildContext, RcUiNodeHandle, UiNode, }, }, - menu::{create_menu_item, create_menu_item_shortcut, ui::UiMenu}, + menu::{create_menu_item, create_menu_item_shortcut}, message::MessageSender, scene::{controller::SceneController, Selection}, ui_scene::{ @@ -54,36 +53,17 @@ pub struct WidgetContextMenu { menu: RcUiNodeHandle, delete_selection: Handle, copy_selection: Handle, - widgets_menu: UiMenu, + create_child: Handle, placement_target: Handle, paste: Handle, make_root: Handle, open_asset: Handle, - stack_panel: Handle, } impl WorldViewerItemContextMenu for WidgetContextMenu { fn menu(&self) -> RcUiNodeHandle { self.menu.clone() } - - fn on_plugin_added( - &mut self, - _serialization_context: &SerializationContext, - widget_constructors_container: &WidgetConstructorContainer, - ui: &mut UserInterface, - ) { - ui.send(self.widgets_menu.menu, WidgetMessage::Remove); - self.widgets_menu = UiMenu::new( - widget_constructors_container, - "Create Child Widget", - &mut ui.build_ctx(), - ); - ui.send( - self.widgets_menu.menu, - WidgetMessage::LinkWith(self.stack_panel.to_base()), - ); - } } fn resource_path_of_first_selected_node( @@ -110,21 +90,17 @@ impl WidgetContextMenu { pub const DELETE_SELECTION: Uuid = uuid!("30eef2a7-9f12-4e64-9142-b604f25e9e06"); pub const COPY_SELECTION: Uuid = uuid!("0a2d10bc-de1e-4196-aba6-0a51c54eb238"); pub const PASTE_AS_CHILD: Uuid = uuid!("d3b86c8c-1efd-4917-9543-b0f5f32f8cbd"); + pub const CREATE_CHILD_WIDGET: Uuid = uuid!("26b2ed1f-c4b8-4ec4-8112-9f29fc600101"); pub const MAKE_ROOT: Uuid = uuid!("968318f6-21c7-430f-a13d-36aefb61cde2"); pub const OPEN_ASSET: Uuid = uuid!("f3f7d0fa-e905-4371-8973-dfc1eb758e5a"); - pub fn new( - widget_constructors_container: &WidgetConstructorContainer, - ctx: &mut BuildContext, - ) -> Self { + pub fn new(ctx: &mut BuildContext) -> Self { let delete_selection; let copy_selection; let paste; let make_root; let open_asset; - - let widgets_menu = UiMenu::new(widget_constructors_container, "Create Child Widget", ctx); - + let create_child; let stack_panel; let menu = ContextMenuBuilder::new( PopupBuilder::new(WidgetBuilder::new().with_visibility(false)) @@ -172,7 +148,15 @@ impl WidgetContextMenu { create_menu_item("Open Asset", Self::OPEN_ASSET, vec![], ctx); open_asset }) - .with_child(widgets_menu.menu), + .with_child({ + create_child = create_menu_item( + "Create Child Widget", + Self::CREATE_CHILD_WIDGET, + vec![], + ctx, + ); + create_child + }), ) .build(ctx); stack_panel @@ -183,7 +167,7 @@ impl WidgetContextMenu { let menu = RcUiNodeHandle::new(menu, ctx.sender()); Self { - widgets_menu, + create_child, menu, delete_selection, copy_selection, @@ -191,7 +175,6 @@ impl WidgetContextMenu { paste, make_root, open_asset, - stack_panel, } } @@ -204,9 +187,6 @@ impl WidgetContextMenu { sender: &MessageSender, ) { if let Some(ui_scene) = controller.downcast_mut::() { - self.widgets_menu - .handle_ui_message(sender, message, ui_scene, editor_selection); - if let Some(MenuItemMessage::Click) = message.data::() { if message.destination() == self.delete_selection { if let Some(ui_selection) = editor_selection.as_ui() { @@ -247,6 +227,8 @@ impl WidgetContextMenu { sender.send(Message::LoadScene(path)); } } + } else if message.destination() == self.create_child { + sender.send(Message::OpenEntityCreator(EntityCreatorMode::CreateChild)); } } else if let Some(PopupMessage::Placement(Placement::Cursor(target))) = message.data() { diff --git a/editor/src/world/create.rs b/editor/src/world/create.rs new file mode 100644 index 000000000..59847c64a --- /dev/null +++ b/editor/src/world/create.rs @@ -0,0 +1,327 @@ +// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use crate::{ + command::{Command, CommandGroup}, + fyrox::{ + core::{algebra::Vector3, pool::Handle}, + engine::Engine, + fxhash::FxHashMap, + graph::constructor::{ConstructorVariantId, GraphNodeConstructorContainer, VariantResult}, + gui::{ + button::{Button, ButtonBuilder, ButtonMessage}, + grid::{Column, GridBuilder, Row}, + list_view::{ListView, ListViewBuilder}, + message::UiMessage, + scroll_viewer::ScrollViewerBuilder, + stack_panel::StackPanelBuilder, + text::TextBuilder, + tree::{Tree, TreeBuilder, TreeMessage, TreeRoot, TreeRootBuilder, TreeRootMessage}, + widget::WidgetBuilder, + window::{Window, WindowAlignment, WindowBuilder, WindowMessage, WindowTitle}, + BuildContext, HorizontalAlignment, Orientation, Thickness, UiNode, UserInterface, + }, + scene::{graph::Graph, node::Node}, + }, + message::MessageSender, + scene::commands::graph::{ + AddNodeCommand, LinkNodesCommand, MoveNodeCommand, ReplaceNodeCommand, SetGraphRootCommand, + }, + scene::{GameScene, Selection}, + ui_scene::{commands::graph::AddWidgetCommand, UiScene}, +}; +use fyrox::gui::widget::WidgetMessage; + +#[derive(Default, Eq, PartialEq, Copy, Clone, Debug)] +pub enum EntityCreatorMode { + #[default] + CreateChild, + CreateParent, + CreateReplacement, +} + +pub struct EntityCreator { + window: Handle, + recent_list: Handle, + groups_tree: Handle, + items_map: FxHashMap, ConstructorVariantId>, + create: Handle