diff --git a/editor/src/asset/menu.rs b/editor/src/asset/menu.rs index 5f0816d25..d63475773 100644 --- a/editor/src/asset/menu.rs +++ b/editor/src/asset/menu.rs @@ -52,7 +52,7 @@ use crate::{ }, }, message::MessageSender, - Message, + utils, Message, }; use std::{ fs::File, @@ -382,7 +382,7 @@ impl AssetItemContextMenu { return true; } else if message.destination() == self.show_in_explorer { if let Ok(canonical_path) = item.path.canonicalize() { - asset::show_in_explorer(canonical_path) + utils::show_in_explorer(canonical_path) } } else if message.destination() == self.open { item.open(); diff --git a/editor/src/asset/mod.rs b/editor/src/asset/mod.rs index c8e73851d..0f36dcfec 100644 --- a/editor/src/asset/mod.rs +++ b/editor/src/asset/mod.rs @@ -85,34 +85,6 @@ pub mod preview; mod selection; pub mod selector; -fn show_in_explorer>(path: P) { - // opener crate is bugged on Windows, so using explorer's command directly. - #[cfg(target_os = "windows")] - { - use std::process::Command; - - fn execute_command(command: &mut Command) { - match command.spawn() { - Ok(mut process) => Log::verify(process.wait()), - Err(err) => Log::err(format!( - "Failed to show asset item in explorer. Reason: {err:?}" - )), - } - } - - execute_command(Command::new("explorer").arg("/select,").arg(path.as_ref())) - } - - #[cfg(not(target_os = "windows"))] - { - if let Err(err) = opener::reveal(path) { - Log::err(format!( - "Failed to show asset item in explorer. Reason: {err:?}" - )) - } - } -} - fn open_in_explorer>(path: P) { if let Ok(path) = path.as_ref().canonicalize() { Log::verify(open::that(path)) diff --git a/editor/src/scene_viewer/menu.rs b/editor/src/scene_viewer/menu.rs new file mode 100644 index 000000000..e251ca423 --- /dev/null +++ b/editor/src/scene_viewer/menu.rs @@ -0,0 +1,107 @@ +// 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::{ + pool::Handle, + uuid::{uuid, Uuid}, + }, + gui::{ + menu::{ContextMenuBuilder, MenuItem, MenuItemMessage}, + message::UiMessage, + popup::PopupBuilder, + stack_panel::StackPanelBuilder, + widget::WidgetBuilder, + BuildContext, RcUiNodeHandle, + }, + }, + menu::create_menu_item, +}; +use fyrox::graph::SceneGraph; +use fyrox::gui::popup::{Placement, PopupMessage}; +use fyrox::gui::{UiNode, UserInterface}; + +pub struct SceneItemMenu { + pub menu: RcUiNodeHandle, + close: Handle, + show_in_explorer: Handle, + placement_target: Handle, +} + +pub enum SceneItemAction { + None, + Close(Uuid), + ShowInExplorer(Uuid), +} + +impl SceneItemMenu { + pub const CLOSE: Uuid = uuid!("e4c7f0eb-c9a7-4cf2-af4b-78c028be9c58"); + pub const SHOW_IN_EXPLORER: Uuid = uuid!("97a43a80-7860-4fce-b0fa-26ae6677d132"); + + pub fn new(ctx: &mut BuildContext) -> Self { + let close = create_menu_item("Close", Self::CLOSE, vec![], ctx); + let show_in_explorer = + create_menu_item("Show In Explorer", Self::SHOW_IN_EXPLORER, vec![], ctx); + let content = StackPanelBuilder::new( + WidgetBuilder::new() + .with_child(close) + .with_child(show_in_explorer), + ) + .build(ctx); + let menu = ContextMenuBuilder::new( + PopupBuilder::new(WidgetBuilder::new().with_visibility(false)).with_content(content), + ) + .build(ctx); + let menu = RcUiNodeHandle::new(menu, ctx.sender()); + + Self { + menu, + close, + show_in_explorer, + placement_target: Default::default(), + } + } + + pub fn handle_ui_message( + &mut self, + ui: &UserInterface, + message: &UiMessage, + ) -> SceneItemAction { + if let Some(MenuItemMessage::Click) = message.data() { + if let Some(placement_target_id) = self.placement_target_id(ui) { + if message.destination == self.close { + return SceneItemAction::Close(placement_target_id); + } else if message.destination == self.show_in_explorer { + return SceneItemAction::ShowInExplorer(placement_target_id); + } + } + } else if let Some(PopupMessage::Placement(Placement::Cursor(target))) = + message.data_from(self.menu.handle()) + { + self.placement_target = *target; + } + SceneItemAction::None + } + + fn placement_target_id(&self, ui: &UserInterface) -> Option { + ui.try_get(self.placement_target).ok().map(|n| n.id) + } +} diff --git a/editor/src/scene_viewer/mod.rs b/editor/src/scene_viewer/mod.rs index 35df2e081..59dbb8f17 100644 --- a/editor/src/scene_viewer/mod.rs +++ b/editor/src/scene_viewer/mod.rs @@ -18,6 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +use crate::scene_viewer::menu::{SceneItemAction, SceneItemMenu}; use crate::{ fyrox::{ core::{color::Color, math::Rect, pool::Handle, uuid::Uuid}, @@ -60,6 +61,7 @@ use crate::{ scene::container::EditorSceneEntry, scene_viewer::gizmo::{SceneGizmo, SceneGizmoAction}, settings::SettingsMessage, + utils, utils::enable_widget, DropdownListBuilder, GameScene, Message, Mode, SaveSceneConfirmationDialogAction, SceneContainer, Settings, @@ -82,6 +84,7 @@ use strum::{IntoEnumIterator, VariantNames}; use strum_macros::{AsRefStr, EnumIter, EnumString, VariantNames}; mod gizmo; +mod menu; #[derive(Default, Clone, Debug, EnumIter, AsRefStr, EnumString, VariantNames)] pub enum GraphicsDebugSwitches { @@ -284,6 +287,7 @@ pub struct SceneViewer { scene_gizmo_image: Handle, debug_switches: Handle, grid_snap_menu: GridSnappingMenu, + scene_item_menu: SceneItemMenu, } impl SceneViewer { @@ -558,6 +562,9 @@ impl SceneViewer { .with_title(WindowTitle::text("Scene Preview")) .with_tab_label("Scene") .build(ctx); + + let scene_item_menu = SceneItemMenu::new(ctx); + Self { sender, window, @@ -577,6 +584,7 @@ impl SceneViewer { debug_switches, grid_snap_menu, build, + scene_item_menu, } } } @@ -671,6 +679,19 @@ impl SceneViewer { self.sync_interaction_modes(new_scene, ui) } + fn close_scene(&self, scenes: &mut SceneContainer, uuid: &Uuid) { + if let Some(entry) = scenes.entry_by_scene_id(*uuid) { + if entry.need_save() { + self.sender.send(Message::OpenSaveSceneConfirmationDialog { + id: entry.id, + action: SaveSceneConfirmationDialogAction::CloseScene(entry.id), + }); + } else { + self.sender.send(Message::CloseScene(entry.id)); + } + } + } + pub fn handle_ui_message( &mut self, message: &mut UiMessage, @@ -683,6 +704,20 @@ impl SceneViewer { let ui = engine.user_interfaces.first_mut(); + match self.scene_item_menu.handle_ui_message(ui, message) { + SceneItemAction::None => {} + SceneItemAction::Close(scene_id) => self.close_scene(scenes, &scene_id), + SceneItemAction::ShowInExplorer(scene_id) => { + if let Some(entry) = scenes.entry_by_scene_id(scene_id) { + if let Some(entry_path) = entry.path.as_ref() { + if let Ok(canonical_path) = entry_path.canonicalize() { + utils::show_in_explorer(canonical_path); + } + } + } + } + } + if let Some(ButtonMessage::Click) = message.data::() { for (mode_id, mode_button) in self.interaction_modes.iter() { if message.destination() == *mode_button { @@ -744,18 +779,7 @@ impl SceneViewer { && message.direction() == MessageDirection::FromWidget { match msg { - TabControlMessage::CloseTab(uuid) => { - if let Some(entry) = scenes.entry_by_scene_id(*uuid) { - if entry.need_save() { - self.sender.send(Message::OpenSaveSceneConfirmationDialog { - id: entry.id, - action: SaveSceneConfirmationDialogAction::CloseScene(entry.id), - }); - } else { - self.sender.send(Message::CloseScene(entry.id)); - } - } - } + TabControlMessage::CloseTab(uuid) => self.close_scene(scenes, uuid), TabControlMessage::ActiveTab(Some(uuid)) => { if let Some(entry) = scenes.entry_by_scene_id(*uuid) { self.sender.send(Message::SetCurrentScene(entry.id)); @@ -920,7 +944,7 @@ impl SceneViewer { content: Default::default(), can_be_closed: true, user_data: None, - context_menu: None, + context_menu: Some(self.scene_item_menu.menu.clone()), }), ); } diff --git a/editor/src/utils/mod.rs b/editor/src/utils/mod.rs index 56ce5be73..5f113c703 100644 --- a/editor/src/utils/mod.rs +++ b/editor/src/utils/mod.rs @@ -36,6 +36,7 @@ use crate::{ }, load_image, }; +use fyrox::core::log::Log; use fyrox::core::pool::ObjectOrVariant; use fyrox::gui::button::Button; use fyrox::gui::file_browser::{FileSelector, FileSelectorMode, FileType}; @@ -176,6 +177,34 @@ pub fn is_native_scene(path: &Path) -> bool { } } +pub fn show_in_explorer>(path: P) { + // opener crate is bugged on Windows, so using explorer's command directly. + #[cfg(target_os = "windows")] + { + use std::process::Command; + + fn execute_command(command: &mut Command) { + match command.spawn() { + Ok(mut process) => Log::verify(process.wait()), + Err(err) => Log::err(format!( + "Failed to show asset item in explorer. Reason: {err:?}" + )), + } + } + + execute_command(Command::new("explorer").arg("/select,").arg(path.as_ref())) + } + + #[cfg(not(target_os = "windows"))] + { + if let Err(err) = opener::reveal(path) { + Log::err(format!( + "Failed to show asset item in explorer. Reason: {err:?}" + )) + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/fyrox-ui/src/tab_control.rs b/fyrox-ui/src/tab_control.rs index 478d122bf..eb24a19eb 100644 --- a/fyrox-ui/src/tab_control.rs +++ b/fyrox-ui/src/tab_control.rs @@ -523,6 +523,7 @@ impl Header { WidgetBuilder::new() .on_row(0) .on_column(0) + .with_id(tab_definition.uuid) .with_opt_context_menu(tab_definition.context_menu.clone()), ) .with_back({