added settings window in project manager + open in ide ability

- `open in ide` uses configurable shell command
This commit is contained in:
Dmitry Stepanov
2024-12-15 22:42:53 +03:00
parent eefc5e9f0d
commit 4724d987e9
6 changed files with 251 additions and 33 deletions
+2 -2
View File
@@ -170,7 +170,7 @@ use crate::{
world::{graph::menu::SceneNodeContextMenu, graph::EditorSceneWrapper, WorldViewer},
};
use fyrox::gui::log::LogPanel;
use fyrox_build_tools::BuildCommand;
use fyrox_build_tools::CommandDescriptor;
pub use message::Message;
use plugins::inspector::InspectorPlugin;
use std::process::Stdio;
@@ -303,7 +303,7 @@ pub fn make_save_file_selector(
pub enum Mode {
Edit,
Build {
queue: VecDeque<BuildCommand>,
queue: VecDeque<CommandDescriptor>,
process: Option<std::process::Child>,
},
Play {
+3 -3
View File
@@ -67,7 +67,7 @@ use crate::{
},
Editor, MSG_SYNC_FLAG,
};
use fyrox_build_tools::{BuildCommand, BuildProfile, EnvironmentVariable};
use fyrox_build_tools::{BuildProfile, CommandDescriptor, EnvironmentVariable};
use std::sync::Arc;
fn make_property_editors_container(
@@ -100,8 +100,8 @@ fn make_property_editors_container(
container.insert(InspectablePropertyEditorDefinition::<EnvironmentVariable>::new());
container.insert(VecCollectionPropertyEditorDefinition::<BuildProfile>::new());
container.insert(InspectablePropertyEditorDefinition::<BuildProfile>::new());
container.insert(VecCollectionPropertyEditorDefinition::<BuildCommand>::new());
container.insert(InspectablePropertyEditorDefinition::<BuildCommand>::new());
container.insert(VecCollectionPropertyEditorDefinition::<CommandDescriptor>::new());
container.insert(InspectablePropertyEditorDefinition::<CommandDescriptor>::new());
container.insert(HotKeyPropertyEditorDefinition);
Arc::new(container)
}
+15 -15
View File
@@ -32,13 +32,13 @@ pub struct EnvironmentVariable {
#[derive(Deserialize, Serialize, PartialEq, Clone, Debug, Default, Reflect, TypeUuidProvider)]
#[type_uuid(id = "67b93136-17fe-4776-b5f0-f4a9ef3d8972")]
pub struct BuildCommand {
pub struct CommandDescriptor {
pub command: String,
pub args: Vec<String>,
pub environment_variables: Vec<EnvironmentVariable>,
}
impl BuildCommand {
impl CommandDescriptor {
pub fn make_command(&self) -> std::process::Command {
let mut command = std::process::Command::new(&self.command);
@@ -52,7 +52,7 @@ impl BuildCommand {
}
}
impl Display for BuildCommand {
impl Display for CommandDescriptor {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for var in self.environment_variables.iter() {
write!(f, "{}=\"{}\" ", var.name, var.value)?;
@@ -73,11 +73,11 @@ impl Display for BuildCommand {
pub struct BuildProfile {
pub name: String,
#[reflect(description = "A set of commands that will be used to build your game.")]
pub build_commands: Vec<BuildCommand>,
pub build_commands: Vec<CommandDescriptor>,
#[reflect(description = "A set of commands that will be used to run your game. \
This set of commands will be executed right after build commands (if the \
build was successful)")]
pub run_command: BuildCommand,
pub run_command: CommandDescriptor,
}
impl BuildProfile {
@@ -91,7 +91,7 @@ impl BuildProfile {
}
}
pub fn build_and_run_queue(&self) -> VecDeque<BuildCommand> {
pub fn build_and_run_queue(&self) -> VecDeque<CommandDescriptor> {
let mut queue = self.build_commands.iter().cloned().collect::<VecDeque<_>>();
queue.push_back(self.run_command.clone());
queue
@@ -100,7 +100,7 @@ impl BuildProfile {
pub fn debug() -> Self {
BuildProfile {
name: "Debug".to_string(),
build_commands: vec![BuildCommand {
build_commands: vec![CommandDescriptor {
command: "cargo".to_string(),
args: vec![
"build".to_string(),
@@ -109,7 +109,7 @@ impl BuildProfile {
],
environment_variables: vec![],
}],
run_command: BuildCommand {
run_command: CommandDescriptor {
command: "cargo".to_string(),
args: vec![
"run".to_string(),
@@ -133,7 +133,7 @@ impl BuildProfile {
name: "Debug (HR)".to_string(),
build_commands: vec![
Self::build_game(), // Build the executor.
BuildCommand {
CommandDescriptor {
command: "cargo".to_string(),
args: vec![
"build".to_string(),
@@ -163,9 +163,9 @@ impl BuildProfile {
release_hot_reloading
}
fn build_game() -> BuildCommand {
fn build_game() -> CommandDescriptor {
// Build the game plugin DLL first.
BuildCommand {
CommandDescriptor {
command: "cargo".to_string(),
args: vec![
"build".to_string(),
@@ -184,8 +184,8 @@ impl BuildProfile {
}
}
fn run_hot_reload(package_name: &str) -> BuildCommand {
BuildCommand {
fn run_hot_reload(package_name: &str) -> CommandDescriptor {
CommandDescriptor {
command: "cargo".to_string(),
args: vec![
"run".to_string(),
@@ -222,7 +222,7 @@ impl BuildProfile {
pub fn debug_editor() -> Self {
BuildProfile {
name: "Debug Editor".to_string(),
build_commands: vec![BuildCommand {
build_commands: vec![CommandDescriptor {
command: "cargo".to_string(),
args: vec![
"build".to_string(),
@@ -231,7 +231,7 @@ impl BuildProfile {
],
environment_variables: vec![],
}],
run_command: BuildCommand {
run_command: CommandDescriptor {
command: "cargo".to_string(),
args: vec![
"run".to_string(),
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

+77 -8
View File
@@ -18,10 +18,11 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use crate::settings::MANIFEST_PATH_VAR;
use crate::{
build::BuildWindow,
project::ProjectWizard,
settings::{Project, Settings},
settings::{Project, Settings, SettingsWindow},
upgrade::UpgradeTool,
utils::{self, is_production_ready, load_image, make_button},
};
@@ -47,14 +48,14 @@ use fyrox::{
stack_panel::StackPanelBuilder,
style::{resource::StyleResourceExt, Style},
text::{TextBuilder, TextMessage},
utils::make_simple_tooltip,
utils::{make_image_button_with_tooltip, make_simple_tooltip},
widget::{WidgetBuilder, WidgetMessage},
window::{WindowBuilder, WindowMessage, WindowTitle},
BuildContext, HorizontalAlignment, Orientation, Thickness, UiNode, UserInterface,
VerticalAlignment,
},
};
use fyrox_build_tools::{BuildCommand, BuildProfile};
use fyrox_build_tools::{BuildProfile, CommandDescriptor};
use std::{
collections::VecDeque,
path::{Path, PathBuf},
@@ -64,7 +65,7 @@ use std::{
enum Mode {
Normal,
Build {
queue: VecDeque<BuildCommand>,
queue: VecDeque<CommandDescriptor>,
process: Option<std::process::Child>,
current_dir: PathBuf,
},
@@ -94,7 +95,10 @@ pub struct ProjectManager {
deletion_confirmation_dialog: Handle<UiNode>,
upgrade: Handle<UiNode>,
locate: Handle<UiNode>,
open_settings: Handle<UiNode>,
open_ide: Handle<UiNode>,
upgrade_tool: Option<UpgradeTool>,
settings_window: Option<SettingsWindow>,
}
fn make_project_item(
@@ -294,9 +298,18 @@ impl ProjectManager {
.with_height(25.0),
)
.build(ctx);
let open_settings = make_image_button_with_tooltip(
ctx,
18.0,
18.0,
load_image(include_bytes!("../resources/gear.png")),
"Settings\nHotkey: Ctrl+S",
Some(7),
);
ctx[open_settings].set_column(3);
let message_count;
let open_log = ButtonBuilder::new(WidgetBuilder::new().on_column(3).with_visibility(false))
let open_log = ButtonBuilder::new(WidgetBuilder::new().on_column(4).with_visibility(false))
.with_content(
GridBuilder::new(
WidgetBuilder::new()
@@ -341,12 +354,14 @@ impl ProjectManager {
.with_child(create)
.with_child(import)
.with_child(search_bar)
.with_child(open_settings)
.with_child(open_log),
)
.add_column(Column::auto())
.add_column(Column::auto())
.add_column(Column::stretch())
.add_column(Column::auto())
.add_column(Column::auto())
.add_row(Row::auto())
.build(ctx);
@@ -362,13 +377,25 @@ impl ProjectManager {
experimental and unsafe nature of code hot reloading.\
\nHotkey: Ctrl+H";
let locate_tooltip = "Opens project folder in the default OS file manager.\
\nHotkey: Ctrl+O";
\nHotkey: Ctrl+L";
let open_ide_tooltip = "Opens project folder in the currently selected IDE \
(can be changed in settings).\nHotkey: Ctrl+O";
let edit = make_button("Edit", 130.0, 25.0, 5, 0, 0, Some(edit_tooltip), ctx);
let run = make_button("Run", 130.0, 25.0, 6, 0, 0, Some(run_tooltip), ctx);
let delete = make_button("Delete", 130.0, 25.0, 7, 0, 0, Some(delete_tooltip), ctx);
let upgrade = make_button("Upgrade", 130.0, 25.0, 8, 0, 0, Some(upgrade_tooltip), ctx);
let locate = make_button("Locate", 130.0, 25.0, 9, 0, 0, Some(locate_tooltip), ctx);
let open_ide = make_button(
"Open IDE",
130.0,
25.0,
9,
0,
0,
Some(open_ide_tooltip),
ctx,
);
let hot_reload = CheckBoxBuilder::new(
WidgetBuilder::new()
.with_tab_index(Some(4))
@@ -392,7 +419,8 @@ impl ProjectManager {
.with_child(run)
.with_child(delete)
.with_child(upgrade)
.with_child(locate),
.with_child(locate)
.with_child(open_ide),
)
.build(ctx);
@@ -468,7 +496,10 @@ impl ProjectManager {
deletion_confirmation_dialog: Default::default(),
upgrade,
locate,
open_settings,
open_ide,
upgrade_tool: None,
settings_window: None,
}
}
@@ -559,6 +590,8 @@ impl ProjectManager {
if let Some(build_window) = self.build_window.as_mut() {
build_window.update(ui);
}
self.settings.try_save();
}
fn try_import(&mut self, path: &Path, ui: &mut UserInterface) {
@@ -689,6 +722,10 @@ impl ProjectManager {
self.on_delete_clicked(ui);
} else if button == self.locate {
self.on_locate_click();
} else if button == self.open_ide {
self.on_open_ide_click();
} else if button == self.open_settings {
self.on_open_settings_click(ui);
}
}
@@ -698,6 +735,32 @@ impl ProjectManager {
Log::verify(open::that_detached(folder));
}
fn on_open_ide_click(&mut self) {
let project = some_or_return!(self.selection.and_then(|i| self.settings.projects.get(i)));
let mut open_ide_command = self.settings.open_ide_command.clone();
if let Some(manifest_path_arg) = open_ide_command
.args
.iter_mut()
.find(|cmd| cmd.as_str() == MANIFEST_PATH_VAR)
{
*manifest_path_arg = project.manifest_path.to_string_lossy().to_string();
} else {
Log::warn(format!("{} variable is not specified!", MANIFEST_PATH_VAR));
}
let mut command = open_ide_command.make_command();
if let Err(err) = command.spawn() {
Log::err(format!(
"Unable to open the IDE using {} command. Reason: {:?}",
open_ide_command, err
));
}
}
fn on_open_settings_click(&mut self, ui: &mut UserInterface) {
let ctx = &mut ui.build_ctx();
self.settings_window = Some(SettingsWindow::new(&self.settings, ctx));
}
fn run_build_profile(
&mut self,
name: &str,
@@ -740,7 +803,9 @@ impl ProjectManager {
KeyCode::KeyI if modifiers.control => self.on_import_clicked(ui),
KeyCode::KeyC if modifiers.control => self.on_create_clicked(ui),
KeyCode::KeyH if modifiers.control => self.on_hot_reload_changed(true, ui),
KeyCode::KeyO if modifiers.control => self.on_locate_click(),
KeyCode::KeyL if modifiers.control => self.on_locate_click(),
KeyCode::KeyO if modifiers.control => self.on_open_ide_click(),
KeyCode::KeyS if modifiers.control => self.on_open_settings_click(ui),
_ => (),
}
}
@@ -758,6 +823,10 @@ impl ProjectManager {
build_window.handle_ui_message(message, ui);
}
if let Some(settings_window) = self.settings_window.take() {
self.settings_window = settings_window.handle_ui_message(&mut self.settings, message);
}
if let Some(upgrade_tool) = self.upgrade_tool.take() {
if let Some(index) = self.selection {
if let Some(project) = self.settings.projects.get(index) {
+154 -5
View File
@@ -18,16 +18,55 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use fyrox::{
core::{log::Log, pool::Handle, reflect::prelude::*},
gui::{
inspector::{
editors::{
collection::VecCollectionPropertyEditorDefinition,
inspectable::InspectablePropertyEditorDefinition,
PropertyEditorDefinitionContainer,
},
InspectorBuilder, InspectorContext,
},
inspector::{InspectorMessage, PropertyAction},
message::{MessageDirection, UiMessage},
scroll_viewer::ScrollViewerBuilder,
widget::WidgetBuilder,
window::{WindowBuilder, WindowMessage, WindowTitle},
BuildContext, UiNode,
},
};
use fyrox_build_tools::{CommandDescriptor, EnvironmentVariable};
use ron::ser::PrettyConfig;
use serde::{Deserialize, Serialize};
use std::{fs::File, io::Write, path::PathBuf};
use std::ops::{Deref, DerefMut};
use std::{fs::File, io::Write, path::PathBuf, sync::Arc};
#[derive(Default, Serialize, Deserialize)]
pub struct Settings {
pub const MANIFEST_PATH_VAR: &str = "%MANIFEST_PATH%";
#[derive(Default, Serialize, Deserialize, Reflect, Debug)]
pub struct SettingsData {
#[serde(default = "default_open_ide_command")]
#[reflect(
description = "Defines a command to run an IDE in a project folder. This command \
should use %MANIFEST_PATH% built-in variable to provide the selected project path to the \
chosen IDE."
)]
pub open_ide_command: CommandDescriptor,
#[reflect(hidden)]
pub projects: Vec<Project>,
}
impl Settings {
fn default_open_ide_command() -> CommandDescriptor {
CommandDescriptor {
command: "rustrover64".to_string(),
args: vec![MANIFEST_PATH_VAR.to_string()],
environment_variables: vec![],
}
}
impl SettingsData {
pub const PATH: &'static str = "pm_settings.ron";
pub fn load() -> Self {
@@ -47,9 +86,119 @@ impl Settings {
}
}
#[derive(Serialize, Deserialize)]
pub struct Settings {
data: SettingsData,
need_save: bool,
}
impl Deref for Settings {
type Target = SettingsData;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl DerefMut for Settings {
fn deref_mut(&mut self) -> &mut Self::Target {
self.need_save = true;
&mut self.data
}
}
impl Settings {
pub fn load() -> Self {
Self {
data: SettingsData::load(),
need_save: false,
}
}
pub fn try_save(&mut self) {
if self.need_save {
self.need_save = false;
self.data.save();
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Project {
pub manifest_path: PathBuf,
pub name: String,
pub hot_reload: bool,
}
pub struct SettingsWindow {
window: Handle<UiNode>,
inspector: Handle<UiNode>,
}
impl SettingsWindow {
pub fn new(settings: &Settings, ctx: &mut BuildContext) -> Self {
let property_editors = PropertyEditorDefinitionContainer::with_default_editors();
property_editors.insert(InspectablePropertyEditorDefinition::<CommandDescriptor>::new());
property_editors
.insert(VecCollectionPropertyEditorDefinition::<EnvironmentVariable>::new());
property_editors.insert(InspectablePropertyEditorDefinition::<EnvironmentVariable>::new());
let property_editors = Arc::new(property_editors);
let context = InspectorContext::from_object(
settings.deref(),
ctx,
property_editors,
None,
1,
0,
true,
Default::default(),
200.0,
);
let inspector = InspectorBuilder::new(WidgetBuilder::new())
.with_context(context)
.build(ctx);
let window = WindowBuilder::new(WidgetBuilder::new().with_width(300.0).with_height(400.0))
.with_content(
ScrollViewerBuilder::new(WidgetBuilder::new())
.with_content(inspector)
.build(ctx),
)
.open(false)
.with_title(WindowTitle::text("Settings"))
.with_remove_on_close(true)
.build(ctx);
ctx.sender()
.send(WindowMessage::open_modal(
window,
MessageDirection::ToWidget,
true,
true,
))
.unwrap();
Self { window, inspector }
}
pub fn handle_ui_message(self, settings: &mut Settings, message: &UiMessage) -> Option<Self> {
if let Some(WindowMessage::Close) = message.data() {
if message.destination() == self.window {
return None;
}
} else if let Some(InspectorMessage::PropertyChanged(args)) = message.data() {
if message.destination() == self.inspector
&& message.direction() == MessageDirection::FromWidget
{
PropertyAction::from_field_kind(&args.value).apply(
&args.path(),
settings.deref_mut(),
&mut |result| {
Log::verify(result);
},
);
}
}
Some(self)
}
}