// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using FlaxEditor.Content.Settings; using FlaxEditor.CustomEditors; using FlaxEditor.GUI; using FlaxEditor.GUI.Tabs; using FlaxEditor.Utilities; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; // ReSharper disable InconsistentNaming // ReSharper disable MemberCanBePrivate.Local #pragma warning disable 649 namespace FlaxEditor.Windows { /// /// Editor tool window for building games using . /// /// public sealed class GameCookerWindow : EditorWindow { /// /// Proxy object for the Build tab. /// [HideInEditor] [CustomEditor(typeof(BuildTabProxy.Editor))] private class BuildTabProxy { public readonly GameCookerWindow GameCookerWin; public readonly PlatformSelector Selector; internal readonly Dictionary PerPlatformOptions = new Dictionary { { PlatformType.Windows, new Windows() }, { PlatformType.XboxOne, new XboxOne() }, { PlatformType.UWP, new UWP() }, { PlatformType.Linux, new Linux() }, { PlatformType.PS4, new PS4() }, { PlatformType.XboxScarlett, new XboxScarlett() }, { PlatformType.Android, new Android() }, { PlatformType.Switch, new Switch() }, { PlatformType.PS5, new PS5() }, { PlatformType.Mac, new Mac() }, { PlatformType.iOS, new iOS() }, }; public BuildTabProxy(GameCookerWindow win, PlatformSelector platformSelector) { GameCookerWin = win; Selector = platformSelector; PerPlatformOptions[PlatformType.Windows].Init("Output/Windows", "Windows"); PerPlatformOptions[PlatformType.XboxOne].Init("Output/XboxOne", "XboxOne"); PerPlatformOptions[PlatformType.UWP].Init("Output/UWP", "UWP"); PerPlatformOptions[PlatformType.Linux].Init("Output/Linux", "Linux"); PerPlatformOptions[PlatformType.PS4].Init("Output/PS4", "PS4"); PerPlatformOptions[PlatformType.XboxScarlett].Init("Output/XboxScarlett", "XboxScarlett"); PerPlatformOptions[PlatformType.Android].Init("Output/Android", "Android"); PerPlatformOptions[PlatformType.Switch].Init("Output/Switch", "Switch"); PerPlatformOptions[PlatformType.PS5].Init("Output/PS5", "PS5"); PerPlatformOptions[PlatformType.Mac].Init("Output/Mac", "Mac"); PerPlatformOptions[PlatformType.iOS].Init("Output/iOS", "iOS"); } [HideInEditor] internal abstract class Platform { [HideInEditor] public bool IsSupported; [HideInEditor] public bool IsAvailable; [EditorOrder(10), Tooltip("Output folder path")] public string Output; [EditorOrder(11), Tooltip("Show output folder in Explorer after build")] public bool ShowOutput = true; [EditorOrder(20), Tooltip("Configuration build mode")] public BuildConfiguration ConfigurationMode = BuildConfiguration.Development; [EditorOrder(90), Tooltip("The list of custom defines passed to the build tool when compiling project scripts. Can be used in build scripts for configuration (Configuration.CustomDefines).")] public string[] CustomDefines; protected abstract BuildPlatform BuildPlatform { get; } protected virtual BuildOptions Options { get { BuildOptions options = BuildOptions.None; if (ShowOutput) options |= BuildOptions.ShowOutput; return options; } } public virtual void Init(string output, string platformDataSubDir) { Output = output; // Check if can build on that platform #if PLATFORM_WINDOWS switch (BuildPlatform) { case BuildPlatform.MacOSx64: case BuildPlatform.MacOSARM64: case BuildPlatform.iOSARM64: IsSupported = false; break; default: IsSupported = true; break; } #elif PLATFORM_LINUX switch (BuildPlatform) { case BuildPlatform.LinuxX64: case BuildPlatform.AndroidARM64: IsSupported = true; break; default: IsSupported = false; break; } #elif PLATFORM_MAC switch (BuildPlatform) { case BuildPlatform.MacOSx64: case BuildPlatform.MacOSARM64: case BuildPlatform.iOSARM64: case BuildPlatform.AndroidARM64: IsSupported = true; break; default: IsSupported = false; break; } #else #error "Unknown platform." #endif // TODO: restore build settings from the Editor cache! // Check if can find installed tools for this platform IsAvailable = Directory.Exists(Path.Combine(Globals.StartupFolder, "Source", "Platforms", platformDataSubDir, "Binaries")); } public virtual void OnNotAvailableLayout(LayoutElementsContainer layout) { string text = "Missing platform data tools for the target platform."; if (FlaxEditor.Editor.IsOfficialBuild()) { switch (BuildPlatform) { #if PLATFORM_WINDOWS case BuildPlatform.Windows32: case BuildPlatform.Windows64: case BuildPlatform.UWPx86: case BuildPlatform.UWPx64: case BuildPlatform.LinuxX64: case BuildPlatform.AndroidARM64: text += "\nUse Flax Launcher and download the required package."; break; #endif default: text += "\nEngine source is required to target this platform."; break; } } else { text += "\nTo target this platform separate engine source package is required."; switch (BuildPlatform) { case BuildPlatform.XboxOne: case BuildPlatform.XboxScarlett: case BuildPlatform.PS4: case BuildPlatform.PS5: case BuildPlatform.Switch: text += "\nTo get access please contact via https://flaxengine.com/contact"; break; } } var label = layout.Label(text, TextAlignment.Center); label.Label.AutoHeight = true; } public virtual void Build() { var output = StringUtils.ConvertRelativePathToAbsolute(Globals.ProjectFolder, StringUtils.NormalizePath(Output)); GameCooker.Build(BuildPlatform, ConfigurationMode, output, Options, CustomDefines); } } class Windows : Platform { protected override BuildPlatform BuildPlatform => BuildPlatform.Windows64; } class UWP : Platform { protected override BuildPlatform BuildPlatform => BuildPlatform.UWPx64; } class XboxOne : Platform { protected override BuildPlatform BuildPlatform => BuildPlatform.XboxOne; } class Linux : Platform { protected override BuildPlatform BuildPlatform => BuildPlatform.LinuxX64; } class PS4 : Platform { protected override BuildPlatform BuildPlatform => BuildPlatform.PS4; } class XboxScarlett : Platform { protected override BuildPlatform BuildPlatform => BuildPlatform.XboxScarlett; } class Android : Platform { protected override BuildPlatform BuildPlatform => BuildPlatform.AndroidARM64; } class Switch : Platform { protected override BuildPlatform BuildPlatform => BuildPlatform.Switch; } class PS5 : Platform { protected override BuildPlatform BuildPlatform => BuildPlatform.PS5; } class Mac : Platform { public enum Archs { [EditorDisplay(null, "arm64")] ARM64, [EditorDisplay(null, "x64")] x64, } public Archs CPU = Archs.ARM64; protected override BuildPlatform BuildPlatform => CPU == Archs.ARM64 ? BuildPlatform.MacOSARM64 : BuildPlatform.MacOSx64; } class iOS : Platform { protected override BuildPlatform BuildPlatform => BuildPlatform.iOSARM64; } class Editor : CustomEditor { private PlatformType _platform; private Button _buildButton; public override void Initialize(LayoutElementsContainer layout) { var proxy = (BuildTabProxy)Values[0]; _platform = proxy.Selector.Selected; var platformObj = proxy.PerPlatformOptions[_platform]; if (!platformObj.IsSupported) { layout.Label("This platform is not supported on this system.", TextAlignment.Center); } else if (platformObj.IsAvailable) { string name; switch (_platform) { case PlatformType.Windows: name = "Windows"; break; case PlatformType.XboxOne: name = "Xbox One"; break; case PlatformType.UWP: name = "Windows Store"; layout.Label("UWP (Windows Store) platform has been deprecated and is no longer supported", TextAlignment.Center).Label.TextColor = Color.Red; break; case PlatformType.Linux: name = "Linux"; break; case PlatformType.PS4: name = "PlayStation 4"; break; case PlatformType.XboxScarlett: name = "Xbox Scarlett"; break; case PlatformType.Android: name = "Android"; break; case PlatformType.Switch: name = "Switch"; break; case PlatformType.PS5: name = "PlayStation 5"; break; case PlatformType.Mac: name = "Mac"; break; case PlatformType.iOS: name = "iOS"; break; default: name = Utilities.Utils.GetPropertyNameUI(_platform.ToString()); break; } var group = layout.Group(name); group.Object(new ReadOnlyValueContainer(platformObj)); _buildButton = layout.Button("Build").Button; _buildButton.Clicked += OnBuildClicked; } else { platformObj.OnNotAvailableLayout(layout); } } private void OnBuildClicked() { var proxy = (BuildTabProxy)Values[0]; var platformObj = proxy.PerPlatformOptions[_platform]; platformObj.Build(); } public override void Refresh() { base.Refresh(); if (_buildButton != null) { _buildButton.Enabled = !GameCooker.IsRunning; } if (Values.Count > 0 && Values[0] is BuildTabProxy proxy && proxy.Selector.Selected != _platform) { RebuildLayout(); } } } } private class PresetsTargetsColumnBase : ContainerControl { protected GameCookerWindow _cooker; protected PresetsTargetsColumnBase(ContainerControl parent, GameCookerWindow cooker, bool isPresets, Action addClicked) { AnchorPreset = AnchorPresets.VerticalStretchLeft; Parent = parent; Offsets = new Margin(isPresets ? 0 : 140, 140, 0, 0); _cooker = cooker; var title = new Label { Bounds = new Rectangle(0, 0, Width, 19), Text = isPresets ? "Presets" : "Targets", Parent = this, }; var addButton = new Button { Text = isPresets ? "New preset" : "Add target", Bounds = new Rectangle(6, 22, Width - 12, title.Bottom), Parent = this, }; addButton.Clicked += addClicked; } protected void RemoveButtons() { for (int i = ChildrenCount - 1; i >= 0; i--) { if (Children[i].Tag != null) { Children[i].Dispose(); } } } protected void AddButton(string name, int index, int selectedIndex, Action