diff --git a/.editorconfig b/.editorconfig index 342cc4c85f..3e5e0a6a99 100644 --- a/.editorconfig +++ b/.editorconfig @@ -16,7 +16,7 @@ insert_final_newline = false # XML Code Style Rules # ################################## -[*.{xaml,props,targets,proj,csproj,shproj,ttproj,vcxproj}] +[*.{axaml,xaml,props,targets,proj,csproj,shproj,ttproj,vcxproj}] # enable this once we have merged most of our branches into master indent_style = space indent_size = 2 diff --git a/sources/editor/Stride.Core.Assets.Editor.Avalonia/Views/ImageResources.axaml b/sources/editor/Stride.Core.Assets.Editor.Avalonia/Views/ImageResources.axaml index c8249c12ab..cdaff2ef38 100644 --- a/sources/editor/Stride.Core.Assets.Editor.Avalonia/Views/ImageResources.axaml +++ b/sources/editor/Stride.Core.Assets.Editor.Avalonia/Views/ImageResources.axaml @@ -137,7 +137,7 @@ - + @@ -151,4 +151,14 @@ + + + + + + F1 M 2,2 L 14,8 L 2,14 Z + + + + diff --git a/sources/editor/Stride.GameStudio.Avalonia/App.axaml.cs b/sources/editor/Stride.GameStudio.Avalonia/App.axaml.cs index 4445532993..e65588ba17 100644 --- a/sources/editor/Stride.GameStudio.Avalonia/App.axaml.cs +++ b/sources/editor/Stride.GameStudio.Avalonia/App.axaml.cs @@ -4,6 +4,7 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Data.Core.Plugins; +using Avalonia.Input; using Avalonia.Markup.Xaml; using Avalonia.Media; using Avalonia.Styling; diff --git a/sources/editor/Stride.GameStudio.Avalonia/ViewModels/MainViewModel.cs b/sources/editor/Stride.GameStudio.Avalonia/ViewModels/MainViewModel.cs index c7ad06f1d2..c05e340f62 100644 --- a/sources/editor/Stride.GameStudio.Avalonia/ViewModels/MainViewModel.cs +++ b/sources/editor/Stride.GameStudio.Avalonia/ViewModels/MainViewModel.cs @@ -45,6 +45,7 @@ public MainViewModel(IViewModelServiceProvider serviceProvider) OpenDebugWindowCommand = new AnonymousTaskCommand(serviceProvider, OnOpenDebugWindow, () => DialogService.HasMainWindow); OpenSettingsWindowCommand = new AnonymousTaskCommand(serviceProvider, OnOpenSettingsWindow, () => DialogService.HasMainWindow); OpenWebPageCommand = new AnonymousTaskCommand(serviceProvider, OnOpenWebPage); + RunCurrentProjectCommand = new AnonymousTaskCommand(serviceProvider, RunCurrentProject); Status = new StatusViewModel(ServiceProvider); Status.PushStatus("Ready"); @@ -80,6 +81,8 @@ public string Title public ICommandBase OpenWebPageCommand { get; } + public ICommandBase RunCurrentProjectCommand { get; } + private EditorDialogService DialogService => ServiceProvider.Get(); public async Task OpenSession(UFile? filePath, CancellationToken token = default) @@ -141,6 +144,128 @@ private void OnExit() DialogService.Exit(); } + private static async Task BuildProject(string projectPath, string framework, string workingDirectory) + { + using var process = new Process(); + process.StartInfo = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = $"build \"{projectPath}\" --framework {framework}", + WorkingDirectory = workingDirectory, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + }; + + process.OutputDataReceived += (_, e) => { if (e.Data != null) Console.Out.WriteLine("[build] " + e.Data); }; + process.ErrorDataReceived += (_, e) => { if (e.Data != null) Console.Error.WriteLine("[build-err] " + e.Data); }; + + try + { + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + await process.WaitForExitAsync(); + return process.ExitCode == 0; + } + catch (Exception ex) + { + await Console.Error.WriteLineAsync("Build process failed: " + ex); + return false; + } + } + + private async Task RunCurrentProject() + { + var mainProjectPath = Session?.CurrentProject?.RootDirectory; + if (mainProjectPath == null) return; + + var projectDir = Path.GetDirectoryName(mainProjectPath.FullPath); + var projectBaseName = Path.GetFileNameWithoutExtension(mainProjectPath.FullPath); + + string platformSuffix, framework, platformRuntime; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + platformSuffix = "Windows"; + platformRuntime = "win-x64"; + framework = "net8.0-windows"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + platformSuffix = "Linux"; + platformRuntime = "linux-x64"; + framework = "net8.0-linux"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + platformSuffix = "macOS"; + platformRuntime = "osx-x64"; + framework = "net8.0-macos"; + } + else + { + await ShowError("Unsupported OS platform"); + return; + } + + var platformProjectName = $"{projectBaseName}.{platformSuffix}.csproj"; + var platformProjectPath = Path.Combine(projectDir, $"{projectBaseName}.{platformSuffix}", platformProjectName); + var execPath = Path.Combine(projectDir, "Bin", platformSuffix, "Debug", platformRuntime); + var dllPath = Path.Combine(execPath, $"{projectBaseName}.{platformSuffix}.dll"); + + + if (!File.Exists(platformProjectPath)) + { + await ShowError($"Platform-specific project not found: {platformProjectPath}"); + return; + } + + Status.PushStatus("Building project..."); + await Console.Out.WriteLineAsync("Building project..."); + bool buildSuccess = await Task.Run(() => BuildProject(platformProjectPath, framework, projectDir)); + if (!buildSuccess) + { + Status.PushStatus("Build failed."); + await Console.Out.WriteLineAsync("Build failed."); + await ShowError("Build failed. See output for details."); + return; + } + + Status.PushStatus("Running project..."); + await Console.Out.WriteLineAsync("Running project..."); + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = $"\"{dllPath}\"", + WorkingDirectory = projectDir, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = false, + } + }; + + process.OutputDataReceived += (_, e) => { if (e.Data != null) Console.Out.WriteLine("[run] " + e.Data); }; + process.ErrorDataReceived += (_, e) => { if (e.Data != null) Console.Error.WriteLine("[run-err] " + e.Data); }; + + try + { + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + } + catch (Exception ex) + { + await Console.Error.WriteLineAsync("Run process failed: " + ex); + await ShowError("Failed to start the game process. See output for details."); + } + + // FIXME: should we wait for process end? + } + private async Task OnOpen(UFile? initialPath) { await OpenSession(initialPath); @@ -169,4 +294,9 @@ private async Task OnOpenWebPage(string url) await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Error); } } + + private async Task ShowError(string message) + { + await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Error); + } } diff --git a/sources/editor/Stride.GameStudio.Avalonia/Views/MainView.axaml b/sources/editor/Stride.GameStudio.Avalonia/Views/MainView.axaml index 8b59052e8f..ab56f85dea 100644 --- a/sources/editor/Stride.GameStudio.Avalonia/Views/MainView.axaml +++ b/sources/editor/Stride.GameStudio.Avalonia/Views/MainView.axaml @@ -16,7 +16,7 @@ x:DataType="gsvm:MainViewModel"> + to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) --> @@ -27,7 +27,7 @@ - + Command="{Binding OpenWebPageCommand}" + CommandParameter="{x:Static gsh:StrideGameStudio.DocumentationUrl}"/> + + + + + - + BorderThickness="2" + BorderBrush="Red" + Padding="2" + DataContext="{Binding Session?.EditorCollection}"/> @@ -113,10 +128,10 @@ + BorderThickness="2" + BorderBrush="Orange" + Padding="2" + DataContext="{Binding Session?.ActiveProperties}"/> @@ -124,18 +139,18 @@ ColumnDefinitions="*, 2, 2*, 2, *"> + BorderThickness="2" + BorderBrush="Black" + Padding="2" + DataContext="{Binding Session}"/> + BorderBrush="Blue" + Padding="2" + DataContext="{Binding Session?.AssetCollection}"/> + BorderBrush="Cyan" + Padding="2"/> -