From ef1d594bd450f0fcfdece0b8659d835b6d2739c8 Mon Sep 17 00:00:00 2001 From: Dmitry Stepanov Date: Mon, 9 Dec 2024 23:57:23 +0300 Subject: [PATCH] added an ability to upgrade projects in project manager - minor fixes --- project-manager/src/main.rs | 3 +- project-manager/src/manager.rs | 48 +++++++++- project-manager/src/upgrade.rs | 156 +++++++++++++++++++++++++++++++++ project-manager/src/utils.rs | 31 +++++-- 4 files changed, 227 insertions(+), 11 deletions(-) create mode 100644 project-manager/src/upgrade.rs diff --git a/project-manager/src/main.rs b/project-manager/src/main.rs index d177f7ae6..32a6b9384 100644 --- a/project-manager/src/main.rs +++ b/project-manager/src/main.rs @@ -20,12 +20,11 @@ //! Project manager is used to create, import, rename, delete, run and edit projects built with Fyrox. -#![cfg_attr(target_os = "windows", windows_subsystem = "windows")] - mod build; mod manager; mod project; mod settings; +mod upgrade; mod utils; use crate::{manager::ProjectManager, utils::make_button}; diff --git a/project-manager/src/manager.rs b/project-manager/src/manager.rs index 57e0a29d1..932816643 100644 --- a/project-manager/src/manager.rs +++ b/project-manager/src/manager.rs @@ -22,6 +22,7 @@ use crate::{ build::BuildWindow, project::ProjectWizard, settings::{Project, Settings}, + upgrade::UpgradeTool, utils::{self, is_production_ready, load_image, make_button}, }; use fyrox::{ @@ -89,6 +90,8 @@ pub struct ProjectManager { open_log: Handle, message_count: Handle, deletion_confirmation_dialog: Handle, + upgrade: Handle, + upgrade_tool: Option, } fn make_project_item( @@ -96,6 +99,7 @@ fn make_project_item( path: &Path, hot_reload: bool, visible: bool, + engine_version: &str, ctx: &mut BuildContext, ) -> Handle { let icon = ImageBuilder::new( @@ -153,6 +157,17 @@ fn make_project_item( .with_opt_texture(load_image(include_bytes!("../resources/flame.png"))) .build(ctx); + let engine_version = TextBuilder::new( + WidgetBuilder::new() + .on_row(0) + .on_column(2) + .with_vertical_alignment(VerticalAlignment::Bottom) + .with_horizontal_alignment(HorizontalAlignment::Right) + .with_margin(Thickness::uniform(3.0)), + ) + .with_text(engine_version) + .build(ctx); + DecoratorBuilder::new( BorderBuilder::new( WidgetBuilder::new() @@ -163,7 +178,8 @@ fn make_project_item( WidgetBuilder::new() .with_child(icon) .with_child(item) - .with_child(hot_reload), + .with_child(hot_reload) + .with_child(engine_version), ) .add_column(Column::auto()) .add_column(Column::stretch()) @@ -193,11 +209,17 @@ fn make_project_items( .to_lowercase() .contains(&search_text.to_lowercase()); + let engine_version = utils::read_crate_metadata(&project.manifest_path) + .ok() + .and_then(|metadata| utils::fyrox_version(&metadata)) + .unwrap_or_default(); + make_project_item( &project.name, &project.manifest_path, project.hot_reload, visible, + &engine_version, ctx, ) }) @@ -323,11 +345,13 @@ impl ProjectManager { let edit_tooltip = "Build the editor and run it."; let run_tooltip = "Build the game and run it."; let delete_tooltip = "Delete the entire project with all its assets. \ - WARNING: This is irreversible operation and permanently deletes your project!"; + WARNING: This is irreversible operation and it permanently deletes your project!"; + let upgrade_tooltip = "Allows you to change the engine version in a few clicks."; let edit = make_button("Edit", 130.0, 25.0, 3, 0, 0, Some(edit_tooltip), ctx); let run = make_button("Run", 130.0, 25.0, 4, 0, 0, Some(run_tooltip), ctx); let delete = make_button("Delete", 130.0, 25.0, 5, 0, 0, Some(delete_tooltip), ctx); + let upgrade = make_button("Upgrade", 130.0, 25.0, 6, 0, 0, Some(upgrade_tooltip), ctx); let hot_reload = CheckBoxBuilder::new( WidgetBuilder::new() .with_margin(Thickness::uniform(1.0)) @@ -353,7 +377,8 @@ impl ProjectManager { .with_child(hot_reload) .with_child(edit) .with_child(run) - .with_child(delete), + .with_child(delete) + .with_child(upgrade), ) .build(ctx); @@ -420,6 +445,8 @@ impl ProjectManager { open_log, message_count, deletion_confirmation_dialog: Default::default(), + upgrade, + upgrade_tool: None, } } @@ -573,6 +600,13 @@ impl ProjectManager { let _ = open::that("https://rustup.rs/"); } else if button == self.open_log { self.log.open(ui); + } else if button == self.upgrade { + if let Some(index) = self.selection { + if let Some(project) = self.settings.projects.get(index) { + let ctx = &mut ui.build_ctx(); + self.upgrade_tool = Some(UpgradeTool::new(project, ctx)); + } + } } if let Some(index) = self.selection { @@ -643,6 +677,14 @@ impl ProjectManager { build_window.handle_ui_message(message, ui); } + if let Some(upgrade_tool) = self.upgrade_tool.take() { + if let Some(index) = self.selection { + if let Some(project) = self.settings.projects.get(index) { + self.upgrade_tool = upgrade_tool.handle_ui_message(message, ui, project); + } + } + } + if let Some(ButtonMessage::Click) = message.data() { self.on_button_click(message.destination, ui); } else if let Some(ListViewMessage::SelectionChanged(selection)) = message.data() { diff --git a/project-manager/src/upgrade.rs b/project-manager/src/upgrade.rs new file mode 100644 index 000000000..30e6884ba --- /dev/null +++ b/project-manager/src/upgrade.rs @@ -0,0 +1,156 @@ +// 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::{settings::Project, utils, utils::make_button}; +use fyrox::{ + core::{log::Log, pool::Handle}, + gui::{ + button::ButtonMessage, + dropdown_list::DropdownListBuilder, + grid::{Column, GridBuilder, Row}, + message::{MessageDirection, UiMessage}, + stack_panel::StackPanelBuilder, + text::TextBuilder, + utils::make_dropdown_list_option, + widget::WidgetBuilder, + window::{WindowBuilder, WindowMessage, WindowTitle}, + BuildContext, HorizontalAlignment, Orientation, Thickness, UiNode, UserInterface, + }, +}; + +#[allow(dead_code)] +pub struct UpgradeTool { + window: Handle, + version: Handle, + version_selector: Handle, + upgrade: Handle, + cancel: Handle, +} + +impl UpgradeTool { + pub fn new(project: &Project, ctx: &mut BuildContext) -> Self { + let version = utils::fyrox_version_or_default(&project.manifest_path); + + let version = TextBuilder::new( + WidgetBuilder::new() + .on_row(0) + .with_margin(Thickness::uniform(1.0)), + ) + .with_text(format!("Current Engine Version: {version}")) + .build(ctx); + + let version_selector = DropdownListBuilder::new( + WidgetBuilder::new() + .with_tab_index(Some(0)) + .on_row(1) + .with_margin(Thickness::uniform(1.0)), + ) + .with_items(vec![ + make_dropdown_list_option(ctx, "Specific"), + make_dropdown_list_option(ctx, "Latest"), + make_dropdown_list_option(ctx, "Nightly"), + ]) + .with_selected(0) + .build(ctx); + + let upgrade; + let cancel; + let buttons = StackPanelBuilder::new( + WidgetBuilder::new() + .with_horizontal_alignment(HorizontalAlignment::Right) + .on_row(3) + .with_child({ + upgrade = make_button("Upgrade", 130.0, 25.0, 1, 0, 0, None, ctx); + upgrade + }) + .with_child({ + cancel = make_button("Cancel", 130.0, 25.0, 2, 0, 0, None, ctx); + cancel + }), + ) + .with_orientation(Orientation::Horizontal) + .build(ctx); + + let content = GridBuilder::new( + WidgetBuilder::new() + .with_child(version) + .with_child(version_selector) + .with_child(buttons), + ) + .add_column(Column::stretch()) + .add_row(Row::auto()) + .add_row(Row::auto()) + .add_row(Row::stretch()) + .add_row(Row::auto()) + .build(ctx); + + let window = WindowBuilder::new(WidgetBuilder::new().with_width(300.0).with_height(200.0)) + .open(false) + .with_title(WindowTitle::text("Upgrade Project")) + .with_content(content) + .with_remove_on_close(true) + .build(ctx); + + ctx.sender() + .send(WindowMessage::open_modal( + window, + MessageDirection::ToWidget, + true, + true, + )) + .unwrap(); + + Self { + window, + version, + version_selector, + upgrade, + cancel, + } + } + + pub fn handle_ui_message( + self, + message: &UiMessage, + ui: &mut UserInterface, + project: &Project, + ) -> Option { + if let Some(ButtonMessage::Click) = message.data() { + if message.destination() == self.upgrade { + Log::verify(fyrox_template_core::upgrade_project( + &project.manifest_path, + "", + false, + )); + } else if message.destination() == self.cancel { + ui.send_message(WindowMessage::close( + self.window, + MessageDirection::ToWidget, + )); + } + } else if let Some(WindowMessage::Close) = message.data() { + if message.destination() == self.window { + return None; + } + } + + Some(self) + } +} diff --git a/project-manager/src/utils.rs b/project-manager/src/utils.rs index 62240be24..8fffbccfc 100644 --- a/project-manager/src/utils.rs +++ b/project-manager/src/utils.rs @@ -133,6 +133,9 @@ pub fn folder_to_manifest_path(path: &Path) -> PathBuf { pub fn read_crate_metadata(manifest_path: &Path) -> Result { match Command::new("cargo") .arg("metadata") + .arg("--no-deps") + .arg("--format-version") + .arg("1") .arg("--manifest-path") .arg(manifest_path) .stdout(Stdio::piped()) @@ -151,11 +154,27 @@ pub fn read_crate_metadata(manifest_path: &Path) -> Result { } } +pub fn fyrox_version(metadata: &Metadata) -> Option { + for package in metadata.packages.iter() { + for dependency in package.dependencies.iter() { + if dependency.name == "fyrox" { + let version = dependency.req.to_string(); + let pretty_version = version.replace('^', ""); + let pretty_version = pretty_version.replace('+', ""); + return Some(pretty_version); + } + } + } + None +} + +pub fn fyrox_version_or_default(manifest_path: &Path) -> String { + read_crate_metadata(manifest_path) + .ok() + .and_then(|metadata| fyrox_version(&metadata)) + .unwrap_or_default() +} + pub fn has_fyrox_in_deps(metadata: &Metadata) -> bool { - metadata.packages.iter().any(|package| { - package - .dependencies - .iter() - .any(|dependency| dependency.name == "fyrox") - }) + fyrox_version(metadata).is_some() }