diff --git a/OpenRA.Game/ContentInstaller.cs b/OpenRA.Game/ContentInstaller.cs index 1d4c388939..7847ccfbdd 100644 --- a/OpenRA.Game/ContentInstaller.cs +++ b/OpenRA.Game/ContentInstaller.cs @@ -10,6 +10,8 @@ #endregion using System.Collections.Generic; +using System.IO; +using System.Linq; namespace OpenRA { diff --git a/OpenRA.Game/ModContent.cs b/OpenRA.Game/ModContent.cs new file mode 100644 index 0000000000..acb8f65d5d --- /dev/null +++ b/OpenRA.Game/ModContent.cs @@ -0,0 +1,118 @@ +#region Copyright & License Information +/* + * Copyright 2007-2016 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace OpenRA +{ + public class ModContent : IGlobalModData + { + public class ModPackage + { + public readonly string Title; + public readonly string[] TestFiles = { }; + public readonly string[] Discs = { }; + public readonly bool Required; + public readonly string Download; + + public ModPackage(MiniYaml yaml) + { + Title = yaml.Value; + FieldLoader.Load(this, yaml); + } + + public bool IsInstalled() + { + return TestFiles.All(file => File.Exists(Path.GetFullPath(Platform.ResolvePath(file)))); + } + } + + public class ModDisc + { + public readonly string Title; + public readonly Dictionary IDFiles; + + [FieldLoader.Ignore] public readonly List Install; + + public ModDisc(MiniYaml yaml) + { + Title = yaml.Value; + var installNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Install"); + if (installNode != null) + Install = installNode.Value.Nodes; + + FieldLoader.Load(this, yaml); + } + } + + public class ModDownload + { + public readonly string Title; + public readonly string URL; + public readonly string MirrorList; + public readonly Dictionary Extract; + + public ModDownload(MiniYaml yaml) + { + Title = yaml.Value; + FieldLoader.Load(this, yaml); + } + } + + public readonly string InstallPromptMessage; + public readonly string QuickDownload; + public readonly string HeaderMessage; + + [FieldLoader.LoadUsing("LoadPackages")] + public readonly Dictionary Packages = new Dictionary(); + + static object LoadPackages(MiniYaml yaml) + { + var packages = new Dictionary(); + var packageNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Packages"); + if (packageNode != null) + foreach (var node in packageNode.Value.Nodes) + packages.Add(node.Key, new ModPackage(node.Value)); + + return packages; + } + + [FieldLoader.LoadUsing("LoadDownloads")] + public readonly Dictionary Downloads; + + static object LoadDownloads(MiniYaml yaml) + { + var downloads = new Dictionary(); + var downloadNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Downloads"); + if (downloadNode != null) + foreach (var node in downloadNode.Value.Nodes) + downloads.Add(node.Key, new ModDownload(node.Value)); + + return downloads; + } + + [FieldLoader.LoadUsing("LoadDiscs")] + public readonly Dictionary Discs; + + static object LoadDiscs(MiniYaml yaml) + { + var discs = new Dictionary(); + var discNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Discs"); + if (discNode != null) + foreach (var node in discNode.Value.Nodes) + discs.Add(node.Key, new ModDisc(node.Value)); + + return discs; + } + } +} diff --git a/OpenRA.Game/ModMetadata.cs b/OpenRA.Game/ModMetadata.cs index f0f271e098..12c95206b5 100644 --- a/OpenRA.Game/ModMetadata.cs +++ b/OpenRA.Game/ModMetadata.cs @@ -31,6 +31,7 @@ namespace OpenRA public Dictionary RequiresMods; public ContentInstaller Content; + public ModContent ModContent; public IReadOnlyPackage Package; static Dictionary ValidateMods() @@ -81,6 +82,9 @@ namespace OpenRA if (nd.ContainsKey("ContentInstaller")) metadata.Content = FieldLoader.Load(nd["ContentInstaller"]); + if (nd.ContainsKey("ModContent")) + metadata.ModContent = FieldLoader.Load(nd["ModContent"]); + // Mods in the support directory and oramod packages (which are listed later // in the CandidateMods list) override mods in the main install. ret[pair.First] = metadata; diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index ae3b220ae1..3679465ac8 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -249,6 +249,7 @@ + diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 260b39f710..89ad221f3e 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -752,6 +752,11 @@ + + + + + diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/DownloadPackageLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/DownloadPackageLogic.cs new file mode 100644 index 0000000000..4ec8588251 --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/DownloadPackageLogic.cs @@ -0,0 +1,234 @@ +#region Copyright & License Information +/* + * Copyright 2007-2016 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Net; +using System.Text; +using ICSharpCode.SharpZipLib; +using ICSharpCode.SharpZipLib.Zip; +using OpenRA.Support; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets.Logic +{ + public class DownloadPackageLogic : ChromeLogic + { + static readonly string[] SizeSuffixes = { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; + readonly ModContent.ModDownload download; + readonly Action onSuccess; + + readonly Widget panel; + readonly ProgressBarWidget progressBar; + + Func getStatusText = () => ""; + string downloadHost; + + [ObjectCreator.UseCtor] + public DownloadPackageLogic(Widget widget, ModContent.ModDownload download, Action onSuccess) + { + this.download = download; + this.onSuccess = onSuccess; + + Log.AddChannel("install", "install.log"); + + panel = widget.Get("PACKAGE_DOWNLOAD_PANEL"); + progressBar = panel.Get("PROGRESS_BAR"); + + var statusLabel = panel.Get("STATUS_LABEL"); + var statusFont = Game.Renderer.Fonts[statusLabel.Font]; + var status = new CachedTransform(s => WidgetUtils.TruncateText(s, statusLabel.Bounds.Width, statusFont)); + statusLabel.GetText = () => status.Update(getStatusText()); + + var text = "Downloading {0}".F(download.Title); + panel.Get("TITLE").Text = text; + + ShowDownloadDialog(); + } + + void ShowDownloadDialog() + { + getStatusText = () => "Fetching list of mirrors..."; + progressBar.Indeterminate = true; + + var retryButton = panel.Get("RETRY_BUTTON"); + retryButton.IsVisible = () => false; + + var cancelButton = panel.Get("CANCEL_BUTTON"); + + var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + Action deleteTempFile = () => + { + Log.Write("install", "Deleting temporary file " + file); + File.Delete(file); + }; + + Action onDownloadProgress = i => + { + var dataReceived = 0.0f; + var dataTotal = 0.0f; + var mag = 0; + var dataSuffix = ""; + + if (i.TotalBytesToReceive < 0) + { + dataTotal = float.NaN; + dataReceived = i.BytesReceived; + dataSuffix = SizeSuffixes[0]; + } + else + { + mag = (int)Math.Log(i.TotalBytesToReceive, 1024); + dataTotal = i.TotalBytesToReceive / (float)(1L << (mag * 10)); + dataReceived = i.BytesReceived / (float)(1L << (mag * 10)); + dataSuffix = SizeSuffixes[mag]; + } + + progressBar.Indeterminate = false; + progressBar.Percentage = i.ProgressPercentage; + + getStatusText = () => "Downloading from {4} {1:0.00}/{2:0.00} {3} ({0}%)".F(i.ProgressPercentage, + dataReceived, dataTotal, dataSuffix, + downloadHost ?? "unknown host"); + }; + + Action onExtractProgress = s => Game.RunAfterTick(() => getStatusText = () => s); + + Action onError = s => Game.RunAfterTick(() => + { + Log.Write("install", "Download failed: " + s); + + progressBar.Indeterminate = false; + progressBar.Percentage = 100; + getStatusText = () => "Error: " + s; + retryButton.IsVisible = () => true; + }); + + Action onDownloadComplete = (i, cancelled) => + { + if (i.Error != null) + { + onError(Download.FormatErrorMessage(i.Error)); + return; + } + + if (cancelled) + { + onError("Download cancelled"); + return; + } + + // Automatically extract + getStatusText = () => "Extracting..."; + progressBar.Indeterminate = true; + + var extracted = new List(); + try + { + using (var stream = File.OpenRead(file)) + using (var z = new ZipFile(stream)) + { + foreach (var kv in download.Extract) + { + var entry = z.GetEntry(kv.Value); + if (!entry.IsFile) + continue; + + onExtractProgress("Extracting " + entry.Name); + Log.Write("install", "Extracting " + entry.Name); + var targetPath = Platform.ResolvePath(kv.Key); + Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); + extracted.Add(targetPath); + + using (var zz = z.GetInputStream(entry)) + using (var f = File.Create(targetPath)) + zz.CopyTo(f); + } + + z.Close(); + } + + Game.RunAfterTick(() => { Ui.CloseWindow(); onSuccess(); }); + } + catch (Exception) + { + Log.Write("install", "Extraction failed"); + + foreach (var f in extracted) + { + Log.Write("install", "Deleting " + f); + File.Delete(f); + } + + onError("Invalid archive"); + } + finally + { + deleteTempFile(); + } + }; + + Action downloadUrl = url => + { + Log.Write("install", "Downloading " + url); + + downloadHost = new Uri(url).Host; + var dl = new Download(url, file, onDownloadProgress, onDownloadComplete); + cancelButton.OnClick = () => { dl.Cancel(); deleteTempFile(); Ui.CloseWindow(); }; + retryButton.OnClick = () => { dl.Cancel(); ShowDownloadDialog(); }; + }; + + if (download.MirrorList != null) + { + Log.Write("install", "Fetching mirrors from " + download.MirrorList); + + Action onFetchMirrorsComplete = (i, cancelled) => + { + progressBar.Indeterminate = true; + + if (i.Error != null) + { + onError(Download.FormatErrorMessage(i.Error)); + return; + } + + if (cancelled) + { + onError("Download cancelled"); + return; + } + + try + { + var data = Encoding.UTF8.GetString(i.Result); + var mirrorList = data.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + downloadUrl(mirrorList.Random(new MersenneTwister())); + } + catch (Exception e) + { + Log.Write("install", "Mirror selection failed with error:"); + Log.Write("install", e.ToString()); + onError("Online mirror is not available. Please install from an original disc."); + } + }; + + var updateMirrors = new Download(download.MirrorList, onDownloadProgress, onFetchMirrorsComplete); + cancelButton.OnClick = () => { updateMirrors.Cancel(); Ui.CloseWindow(); }; + retryButton.OnClick = () => { updateMirrors.Cancel(); ShowDownloadDialog(); }; + } + else + downloadUrl(download.URL); + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs new file mode 100644 index 0000000000..b451812e6e --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromDiscLogic.cs @@ -0,0 +1,422 @@ +#region Copyright & License Information +/* + * Copyright 2007-2016 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Threading.Tasks; +using OpenRA.FileFormats; +using OpenRA.Mods.Common.FileFormats; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets.Logic +{ + public class InstallFromDiscLogic : ChromeLogic + { + enum Mode { Progress, Message, List } + + readonly ModContent content; + + readonly Widget panel; + readonly LabelWidget titleLabel; + readonly ButtonWidget primaryButton; + readonly ButtonWidget secondaryButton; + + // Progress panel + readonly Widget progressContainer; + readonly ProgressBarWidget progressBar; + readonly LabelWidget progressLabel; + + // Message panel + readonly Widget messageContainer; + readonly LabelWidget messageLabel; + + // List Panel + readonly Widget listContainer; + readonly ScrollPanelWidget listPanel; + readonly LabelWidget listTemplate; + readonly LabelWidget listLabel; + + Mode visible = Mode.Progress; + + [ObjectCreator.UseCtor] + public InstallFromDiscLogic(Widget widget, ModContent content, Action afterInstall) + { + this.content = content; + + Log.AddChannel("install", "install.log"); + + // this.afterInstall = afterInstall; + panel = widget.Get("DISC_INSTALL_PANEL"); + + titleLabel = panel.Get("TITLE"); + + primaryButton = panel.Get("PRIMARY_BUTTON"); + secondaryButton = panel.Get("SECONDARY_BUTTON"); + + // Progress view + progressContainer = panel.Get("PROGRESS"); + progressContainer.IsVisible = () => visible == Mode.Progress; + progressBar = panel.Get("PROGRESS_BAR"); + progressLabel = panel.Get("PROGRESS_MESSAGE"); + progressLabel.IsVisible = () => visible == Mode.Progress; + + // Message view + messageContainer = panel.Get("MESSAGE"); + messageContainer.IsVisible = () => visible == Mode.Message; + messageLabel = messageContainer.Get("MESSAGE_MESSAGE"); + + // List view + listContainer = panel.Get("LIST"); + listContainer.IsVisible = () => visible == Mode.List; + + listPanel = listContainer.Get("LIST_PANEL"); + listTemplate = listPanel.Get("LIST_TEMPLATE"); + listPanel.RemoveChildren(); + + listLabel = listContainer.Get("LIST_MESSAGE"); + + DetectContentDisks(); + } + + void DetectContentDisks() + { + var message = "Detecting drives"; + ShowProgressbar("Checking Discs", () => message); + ShowBackRetry(DetectContentDisks); + + new Task(() => + { + var volumes = DriveInfo.GetDrives() + .Where(v => v.DriveType == DriveType.CDRom && v.IsReady) + .Select(v => v.RootDirectory.FullName); + + foreach (var kv in content.Discs) + { + message = "Searching for " + kv.Value.Title; + + foreach (var volume in volumes) + { + if (PathIsDiscMount(volume, kv.Value)) + { + var packages = content.Packages.Values + .Where(p => p.Discs.Contains(kv.Key) && !p.IsInstalled()) + .Select(p => p.Title); + + // Ignore disc if content is already installed + if (packages.Any()) + { + Game.RunAfterTick(() => + { + ShowList(kv.Value.Title, "The following content packages will be installed:", packages); + ShowContinueCancel(() => InstallFromDisc(volume, kv.Value)); + }); + + return; + } + } + } + } + + var discTitles = content.Packages.Values + .Where(p => !p.IsInstalled()) + .SelectMany(p => p.Discs) + .Select(d => content.Discs[d].Title) + .Distinct(); + + Game.RunAfterTick(() => + { + ShowList("Disc Content Not Found", "Please insert or mount one of the following discs and try again", discTitles); + ShowBackRetry(DetectContentDisks); + }); + }).Start(); + } + + void InstallFromDisc(string path, ModContent.ModDisc disc) + { + var message = ""; + ShowProgressbar("Installing Content", () => message); + ShowDisabledCancel(); + + new Task(() => + { + var extracted = new List(); + + try + { + foreach (var i in disc.Install) + { + switch (i.Key) + { + case "copy": + { + var sourceDir = Path.Combine(path, i.Value.Value); + foreach (var node in i.Value.Nodes) + { + var sourcePath = Path.Combine(sourceDir, node.Value.Value); + var targetPath = Platform.ResolvePath(node.Key); + if (File.Exists(targetPath)) + { + Log.Write("install", "Ignoring installed file " + targetPath); + continue; + } + + Log.Write("install", "Copying {0} -> {1}".F(sourcePath, targetPath)); + message = "Copying " + Path.GetFileName(sourcePath); + extracted.Add(targetPath); + Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); + File.Copy(sourcePath, targetPath); + } + + break; + } + + case "extract-raw": + { + ExtractFromPackage(ExtractionType.Raw, path, i.Value, extracted, ref message); + break; + } + + case "extract-blast": + { + ExtractFromPackage(ExtractionType.Blast, path, i.Value, extracted, ref message); + break; + } + + case "extract-mscab": + { + ExtractFromMSCab(path, i.Value, extracted, m => message = m); + break; + } + + default: + Game.Debug("debug", "Unknown installation command {0} - ignoring", i.Key); + break; + } + } + + Game.RunAfterTick(Ui.CloseWindow); + } + catch (Exception e) + { + Log.Write("install", e.ToString()); + + foreach (var f in extracted) + { + Log.Write("install", "Deleting " + f); + File.Delete(f); + } + + Game.RunAfterTick(() => + { + ShowMessage("Installation Failed", "Refer to install.log in the logs directory for details."); + ShowBackRetry(() => InstallFromDisc(path, disc)); + }); + } + }).Start(); + } + + enum ExtractionType { Raw, Blast } + + static void ExtractFromPackage(ExtractionType type, string path, MiniYaml actionYaml, List extractedFiles, ref string progressMessage) + { + var sourcePath = Path.Combine(path, actionYaml.Value); + using (var source = File.OpenRead(sourcePath)) + { + foreach (var node in actionYaml.Nodes) + { + var targetPath = Platform.ResolvePath(node.Key); + + if (File.Exists(targetPath)) + { + Log.Write("install", "Skipping installed file " + targetPath); + continue; + } + + var offsetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "Offset"); + if (offsetNode == null) + { + Log.Write("install", "Skipping entry with missing Offset definition " + targetPath); + continue; + } + + var lengthNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "Length"); + if (lengthNode == null) + { + Log.Write("install", "Skipping entry with missing Length definition " + targetPath); + continue; + } + + var length = FieldLoader.GetValue("Length", lengthNode.Value.Value); + + source.Position = FieldLoader.GetValue("Offset", offsetNode.Value.Value); + + extractedFiles.Add(targetPath); + Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); + using (var target = File.OpenWrite(targetPath)) + { + // This is a bit dumb memory-wise, but we load the whole thing when running the game anyway + Log.Write("install", "Extracting {0} -> {1}".F(sourcePath, targetPath)); + progressMessage = "Extracting " + Path.GetFileName(Path.GetFileName(targetPath)); + + var data = source.ReadBytes(length); + if (type == ExtractionType.Blast) + data = Blast.Decompress(data); + + target.Write(data); + } + } + } + } + + static void ExtractFromMSCab(string path, MiniYaml actionYaml, List extractedFiles, Action updateMessage) + { + var sourcePath = Path.Combine(path, actionYaml.Value); + using (var source = File.OpenRead(sourcePath)) + { + var reader = new MSCabCompression(source); + foreach (var node in actionYaml.Nodes) + { + var targetPath = Platform.ResolvePath(node.Key); + + if (File.Exists(targetPath)) + { + Log.Write("install", "Skipping installed file " + targetPath); + continue; + } + + extractedFiles.Add(targetPath); + Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); + using (var target = File.OpenWrite(targetPath)) + { + // This is a bit dumb memory-wise, but we load the whole thing when running the game anyway + Log.Write("install", "Extracting {0} -> {1}".F(sourcePath, targetPath)); + + var displayFilename = Path.GetFileName(Path.GetFileName(targetPath)); + Action onProgress = percent => updateMessage("Extracting {0} ({1}%)".F(displayFilename, percent)); + target.Write(reader.ExtractFile(node.Value.Value, onProgress)); + } + } + } + } + + bool PathIsDiscMount(string path, ModContent.ModDisc disc) + { + try + { + foreach (var kv in disc.IDFiles) + { + var filePath = Path.Combine(path, kv.Key); + if (!File.Exists(filePath)) + return false; + + using (var fileStream = File.OpenRead(filePath)) + using (var csp = SHA1.Create()) + { + var hash = new string(csp.ComputeHash(fileStream).SelectMany(a => a.ToString("x2")).ToArray()); + if (hash != kv.Value) + return false; + } + } + } + catch (Exception) + { + return false; + } + + return true; + } + + void ShowMessage(string title, string message) + { + visible = Mode.Message; + titleLabel.Text = title; + messageLabel.Text = message; + + primaryButton.Bounds.Y += messageContainer.Bounds.Height - panel.Bounds.Height; + secondaryButton.Bounds.Y += messageContainer.Bounds.Height - panel.Bounds.Height; + panel.Bounds.Y -= (messageContainer.Bounds.Height - panel.Bounds.Height) / 2; + panel.Bounds.Height = messageContainer.Bounds.Height; + } + + void ShowProgressbar(string title, Func getMessage) + { + visible = Mode.Progress; + titleLabel.Text = title; + progressBar.IsIndeterminate = () => true; + + var font = Game.Renderer.Fonts[progressLabel.Font]; + var status = new CachedTransform(s => WidgetUtils.TruncateText(s, progressLabel.Bounds.Width, font)); + progressLabel.GetText = () => status.Update(getMessage()); + + primaryButton.Bounds.Y += progressContainer.Bounds.Height - panel.Bounds.Height; + secondaryButton.Bounds.Y += progressContainer.Bounds.Height - panel.Bounds.Height; + panel.Bounds.Y -= (progressContainer.Bounds.Height - panel.Bounds.Height) / 2; + panel.Bounds.Height = progressContainer.Bounds.Height; + } + + void ShowList(string title, string message, IEnumerable items) + { + visible = Mode.List; + titleLabel.Text = title; + listLabel.Text = message; + + listPanel.RemoveChildren(); + foreach (var i in items) + { + var item = i; + var labelWidget = (LabelWidget)listTemplate.Clone(); + labelWidget.GetText = () => item; + listPanel.AddChild(labelWidget); + } + + primaryButton.Bounds.Y += listContainer.Bounds.Height - panel.Bounds.Height; + secondaryButton.Bounds.Y += listContainer.Bounds.Height - panel.Bounds.Height; + panel.Bounds.Y -= (listContainer.Bounds.Height - panel.Bounds.Height) / 2; + panel.Bounds.Height = listContainer.Bounds.Height; + } + + void ShowContinueCancel(Action continueAction) + { + primaryButton.OnClick = continueAction; + primaryButton.Text = "Continue"; + primaryButton.Visible = true; + + secondaryButton.OnClick = Ui.CloseWindow; + secondaryButton.Text = "Cancel"; + secondaryButton.Visible = true; + secondaryButton.Disabled = false; + Game.RunAfterTick(Ui.ResetTooltips); + } + + void ShowBackRetry(Action retryAction) + { + primaryButton.OnClick = retryAction; + primaryButton.Text = "Retry"; + primaryButton.Visible = true; + + secondaryButton.OnClick = Ui.CloseWindow; + secondaryButton.Text = "Back"; + secondaryButton.Visible = true; + secondaryButton.Disabled = false; + Game.RunAfterTick(Ui.ResetTooltips); + } + + void ShowDisabledCancel() + { + primaryButton.Visible = false; + secondaryButton.Disabled = true; + Game.RunAfterTick(Ui.ResetTooltips); + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallLogic.cs index 3e5d47fb87..3d2d693901 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallLogic.cs @@ -9,6 +9,7 @@ */ #endregion +using System; using OpenRA.Widgets; namespace OpenRA.Mods.Common.Widgets.Logic diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentDiscTooltipLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentDiscTooltipLogic.cs new file mode 100644 index 0000000000..4f679567b6 --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentDiscTooltipLogic.cs @@ -0,0 +1,53 @@ +#region Copyright & License Information +/* + * Copyright 2007-2016 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.IO; +using System.Linq; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets.Logic +{ + public class ModContentDiscTooltipLogic : ChromeLogic + { + [ObjectCreator.UseCtor] + public ModContentDiscTooltipLogic(Widget widget, Func getText) + { + var discs = widget.Get("DISCS"); + var template = discs.Get("DISC_TEMPLATE"); + discs.RemoveChildren(); + + var desc = widget.Get("DESCRIPTION"); + + var font = Game.Renderer.Fonts[template.Font]; + var discTitles = getText().Split('\n'); + + var maxWidth = 0; + var sideMargin = desc.Bounds.X; + var bottomMargin = discs.Bounds.Height; + foreach (var disc in discTitles) + { + var label = (LabelWidget)template.Clone(); + var title = disc; + label.GetText = () => title; + label.Bounds.Y = discs.Bounds.Height; + label.Bounds.Width = font.Measure(disc).X; + + maxWidth = Math.Max(maxWidth, label.Bounds.Width + label.Bounds.X); + discs.AddChild(label); + discs.Bounds.Height += label.Bounds.Height; + } + + widget.Bounds.Width = 2 * sideMargin + maxWidth; + widget.Bounds.Height = discs.Bounds.Y + bottomMargin + discs.Bounds.Height; + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentLogic.cs new file mode 100644 index 0000000000..2b5ca3cf04 --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentLogic.cs @@ -0,0 +1,127 @@ +#region Copyright & License Information +/* + * Copyright 2007-2016 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.IO; +using System.Linq; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets.Logic +{ + public class ModContentLogic : ChromeLogic + { + readonly ModContent content; + readonly ScrollPanelWidget scrollPanel; + readonly Widget template; + bool discAvailable; + + [ObjectCreator.UseCtor] + public ModContentLogic(Widget widget, string modId, Action onCancel) + { + var panel = widget.Get("CONTENT_PANEL"); + + content = ModMetadata.AllMods[modId].ModContent; + scrollPanel = panel.Get("PACKAGES"); + template = scrollPanel.Get("PACKAGE_TEMPLATE"); + + var headerTemplate = panel.Get("HEADER_TEMPLATE"); + var headerLines = !string.IsNullOrEmpty(content.HeaderMessage) ? content.HeaderMessage.Replace("\\n", "\n").Split('\n') : new string[0]; + var headerHeight = 0; + foreach (var l in headerLines) + { + var line = (LabelWidget)headerTemplate.Clone(); + line.GetText = () => l; + line.Bounds.Y += headerHeight; + panel.AddChild(line); + + headerHeight += headerTemplate.Bounds.Height; + } + + panel.Bounds.Height += headerHeight; + panel.Bounds.Y -= headerHeight / 2; + scrollPanel.Bounds.Y += headerHeight; + + var discButton = panel.Get("CHECK_DISC_BUTTON"); + discButton.Bounds.Y += headerHeight; + discButton.IsVisible = () => discAvailable; + + discButton.OnClick = () => Ui.OpenWindow("DISC_INSTALL_PANEL", new WidgetArgs + { + { "afterInstall", () => { } }, + { "content", content } + }); + + var backButton = panel.Get("BACK_BUTTON"); + backButton.Bounds.Y += headerHeight; + backButton.OnClick = () => { Ui.CloseWindow(); onCancel(); }; + + PopulateContentList(); + Game.RunAfterTick(Ui.ResetTooltips); + } + + public override void BecameVisible() + { + PopulateContentList(); + } + + void PopulateContentList() + { + scrollPanel.RemoveChildren(); + + foreach (var p in content.Packages) + { + var container = template.Clone(); + var titleWidget = container.Get("TITLE"); + var title = p.Value.Title; + titleWidget.GetText = () => title; + + var requiredWidget = container.Get("REQUIRED"); + requiredWidget.IsVisible = () => p.Value.Required; + + var discWidget = container.Get("DISC"); + var discs = p.Value.Discs.Select(s => content.Discs[s].Title).Distinct(); + var discList = discs.JoinWith("\n"); + var discAvailable = discs.Any(); + discWidget.GetTooltipText = () => discList; + discWidget.IsVisible = () => discAvailable; + + var installed = p.Value.IsInstalled(); + var downloadButton = container.Get("DOWNLOAD"); + var downloadEnabled = !installed && p.Value.Download != null; + downloadButton.IsVisible = () => downloadEnabled; + + if (downloadEnabled) + { + var download = content.Downloads[p.Value.Download]; + var widgetArgs = new WidgetArgs + { + { "download", download }, + { "onSuccess", () => { } } + }; + + downloadButton.OnClick = () => Ui.OpenWindow("PACKAGE_DOWNLOAD_PANEL", widgetArgs); + } + + var installedWidget = container.Get("INSTALLED"); + installedWidget.IsVisible = () => installed; + + var requiresDiscWidget = container.Get("REQUIRES_DISC"); + requiresDiscWidget.IsVisible = () => !installed && !downloadEnabled; + if (!discAvailable) + requiresDiscWidget.GetText = () => "Manual Install"; + + scrollPanel.AddChild(container); + } + + discAvailable = content.Packages.Values.Any(p => p.Discs.Any() && !p.IsInstalled()); + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentPromptLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentPromptLogic.cs new file mode 100644 index 0000000000..63d41c0e15 --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentPromptLogic.cs @@ -0,0 +1,72 @@ +#region Copyright & License Information +/* + * Copyright 2007-2016 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets.Logic +{ + public class ModContentPromptLogic : ChromeLogic + { + [ObjectCreator.UseCtor] + public ModContentPromptLogic(Widget widget, string modId, Action continueLoading) + { + var panel = widget.Get("CONTENT_PROMPT_PANEL"); + + var mod = ModMetadata.AllMods[modId]; + var content = ModMetadata.AllMods[modId].ModContent; + + var headerTemplate = panel.Get("HEADER_TEMPLATE"); + var headerLines = !string.IsNullOrEmpty(content.InstallPromptMessage) ? content.InstallPromptMessage.Replace("\\n", "\n").Split('\n') : new string[0]; + var headerHeight = 0; + foreach (var l in headerLines) + { + var line = (LabelWidget)headerTemplate.Clone(); + line.GetText = () => l; + line.Bounds.Y += headerHeight; + panel.AddChild(line); + + headerHeight += headerTemplate.Bounds.Height; + } + + panel.Bounds.Height += headerHeight; + panel.Bounds.Y -= headerHeight / 2; + + var advancedButton = panel.Get("ADVANCED_BUTTON"); + advancedButton.Bounds.Y += headerHeight; + advancedButton.OnClick = () => + { + Ui.OpenWindow("CONTENT_PANEL", new WidgetArgs + { + { "modId", modId }, + { "onCancel", Ui.CloseWindow } + }); + }; + + var quickButton = panel.Get("QUICK_BUTTON"); + quickButton.IsVisible = () => !string.IsNullOrEmpty(content.QuickDownload); + quickButton.Bounds.Y += headerHeight; + quickButton.OnClick = () => + { + Ui.OpenWindow("PACKAGE_DOWNLOAD_PANEL", new WidgetArgs + { + { "download", content.Downloads[content.QuickDownload] }, + { "onSuccess", continueLoading } + }); + }; + + var backButton = panel.Get("BACK_BUTTON"); + backButton.Bounds.Y += headerHeight; + backButton.OnClick = Ui.CloseWindow; + Game.RunAfterTick(Ui.ResetTooltips); + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/ModBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/ModBrowserLogic.cs index c766f1d4cc..aed538ce94 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/ModBrowserLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/ModBrowserLogic.cs @@ -27,8 +27,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic readonly ModMetadata[] allMods; readonly Dictionary previews = new Dictionary(); readonly Dictionary logos = new Dictionary(); - readonly Cache modInstallStatus; - readonly Cache modPrerequisitesFulfilled; readonly Widget modChooserPanel; readonly ButtonWidget loadButton; readonly SheetBuilder sheetBuilder; @@ -40,14 +38,24 @@ namespace OpenRA.Mods.Common.Widgets.Logic [ObjectCreator.UseCtor] public ModBrowserLogic(Widget widget, ModData modData) { - modInstallStatus = new Cache(IsModInstalled); - modPrerequisitesFulfilled = new Cache(Game.IsModInstalled); - modChooserPanel = widget; loadButton = modChooserPanel.Get("LOAD_BUTTON"); loadButton.OnClick = () => LoadMod(selectedMod); loadButton.IsDisabled = () => selectedMod.Id == modData.Manifest.Mod.Id; + var contentButton = modChooserPanel.Get("CONFIGURE_BUTTON"); + contentButton.IsDisabled = () => selectedMod.ModContent == null; + contentButton.OnClick = () => + { + var widgetArgs = new WidgetArgs + { + { "modId", selectedMod.Id }, + { "onCancel", () => { } } + }; + + Ui.OpenWindow("CONTENT_PANEL", widgetArgs); + }; + modChooserPanel.Get("QUIT_BUTTON").OnClick = Game.Exit; modList = modChooserPanel.Get("MOD_LIST"); @@ -160,14 +168,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic var selectedIndex = Array.IndexOf(allMods, mod); if (selectedIndex - modOffset > 4) modOffset = selectedIndex - 4; - - loadButton.Text = !modPrerequisitesFulfilled[mod.Id] ? "Install mod" : - modInstallStatus[mod] ? "Load Mod" : "Install Assets"; } void LoadMod(ModMetadata mod) { - if (!modPrerequisitesFulfilled[mod.Id]) + if (!Game.IsModInstalled(mod.Id)) { var widgetArgs = new WidgetArgs { @@ -178,17 +183,31 @@ namespace OpenRA.Mods.Common.Widgets.Logic return; } - if (!modInstallStatus[mod]) + if (!IsModInstalled(mod)) { - var widgetArgs = new WidgetArgs + if (mod.ModContent != null) { - { "continueLoading", () => - Game.RunAfterTick(() => Game.InitializeMod(Game.Settings.Game.Mod, new Arguments())) }, - { "mirrorListUrl", mod.Content.PackageMirrorList }, - { "modId", mod.Id } - }; + var widgetArgs = new WidgetArgs + { + { "continueLoading", () => + Game.RunAfterTick(() => Game.InitializeMod(mod.Id, new Arguments())) }, + { "modId", mod.Id } + }; - Ui.OpenWindow("INSTALL_PANEL", widgetArgs); + Ui.OpenWindow("CONTENT_PROMPT_PANEL", widgetArgs); + } + else + { + var widgetArgs = new WidgetArgs + { + { "continueLoading", () => + Game.RunAfterTick(() => Game.InitializeMod(Game.Settings.Game.Mod, new Arguments())) }, + { "mirrorListUrl", mod.Content.PackageMirrorList }, + { "modId", mod.Id } + }; + + Ui.OpenWindow("INSTALL_PANEL", widgetArgs); + } return; } diff --git a/mods/modchooser/chrome.png b/mods/modchooser/chrome.png index ce09238bda..7659af598a 100644 Binary files a/mods/modchooser/chrome.png and b/mods/modchooser/chrome.png differ diff --git a/mods/modchooser/chrome.yaml b/mods/modchooser/chrome.yaml index 632e84ca8a..cea14509ba 100644 --- a/mods/modchooser/chrome.yaml +++ b/mods/modchooser/chrome.yaml @@ -31,6 +31,17 @@ panel-thinborder: chrome.png corner-bl: 1,573,2,2 corner-br: 61,573,2,2 +panel-thinborder-light: chrome.png + background: 643,515,58,58 + border-r: 701,515,2,58 + border-l: 641,515,2,58 + border-b: 643,573,58,2 + border-t: 643,513,58,2 + corner-tl: 641,513,2,2 + corner-tr: 701,513,2,2 + corner-bl: 641,573,2,2 + corner-br: 701,573,2,2 + button: chrome.png background: 138,522,44,44 border-r: 182,522,10,44 @@ -53,19 +64,9 @@ button-hover: chrome.png corner-bl: 192,566,10,10 corner-br: 246,566,10,10 -# Copy of button button-disabled: chrome.png - background: 138,522,44,44 - border-r: 182,522,10,44 - border-l: 128,522,10,44 - border-b: 138,566,44,10 - border-t: 138,512,44,10 - corner-tl: 128,512,10,10 - corner-tr: 182,512,10,10 - corner-bl: 128,566,10,10 - corner-br: 182,566,10,10 + Inherits: button -# Copy of button-highlighted-hover button-pressed: chrome.png background: 330,522,44,44 border-r: 374,522,10,44 @@ -89,40 +90,42 @@ button-highlighted: chrome.png corner-br: 310,566,10,10 button-highlighted-hover: chrome.png - background: 330,522,44,44 - border-r: 374,522,10,44 - border-l: 320,522,10,44 - border-b: 330,566,44,10 - border-t: 330,512,44,10 - corner-tl: 320,512,10,10 - corner-tr: 374,512,10,10 - corner-bl: 320,566,10,10 - corner-br: 374,566,10,10 + Inherits: button-pressed -# Copy of button-mod-highlighted-hover button-highlighted-pressed: chrome.png - background: 330,522,44,44 - border-r: 374,522,10,44 - border-l: 320,522,10,44 - border-b: 330,566,44,10 - border-t: 330,512,44,10 - corner-tl: 320,512,10,10 - corner-tr: 374,512,10,10 - corner-bl: 320,566,10,10 - corner-br: 374,566,10,10 + Inherits: button-pressed - -# Copy of button-mod-highlighted button-highlighted-disabled: chrome.png - background: 266,522,44,44 - border-r: 310,522,10,44 - border-l: 256,522,10,44 - border-b: 266,566,44,10 - border-t: 266,512,44,10 - corner-tl: 256,512,10,10 - corner-tr: 310,512,10,10 - corner-bl: 256,566,10,10 - corner-br: 310,566,10,10 + Inherits: button-highlighted + +button-highlighted-thin: chrome.png + background: 522,522,44,44 + border-r: 566,522,10,44 + border-l: 512,522,10,44 + border-b: 522,566,44,10 + border-t: 522,512,44,10 + corner-tl: 512,512,10,10 + corner-tr: 566,512,10,10 + corner-bl: 512,566,10,10 + corner-br: 566,566,10,10 + +button-highlighted-thin-hover: chrome.png + Inherits: button-highlighted-thin-pressed + +button-highlighted-thin-pressed: chrome.png + background: 586,522,44,44 + border-r: 630,522,10,44 + border-l: 576,522,10,44 + border-b: 586,566,44,10 + border-t: 586,512,44,10 + corner-tl: 576,512,10,10 + corner-tr: 630,512,10,10 + corner-bl: 576,566,10,10 + corner-br: 630,566,10,10 + +button-highlighted-thin-disabled: chrome.png + Inherits: button-highlighted-thin + progressbar-bg: chrome.png background: 453,565,56,6 @@ -155,4 +158,26 @@ background: chrome.png modchooser: chrome.png logo: 0,576,280,128 leftarrow:384,512,20,64 - rightarrow:404,512,20,64 \ No newline at end of file + rightarrow:404,512,20,64 + cdicon: 448,512,20,20 + +scrollpanel-bg: chrome.png + Inherits: panel-thinborder + +scrollpanel-button: chrome.png + Inherits: panel-thinborder + +scrollpanel-button-hover: chrome.png + Inherits: panel-thinborder-light + +scrollpanel-button-disabled: chrome.png + Inherits: panel-thinborder + +scrollpanel-button-pressed: chrome.png + Inherits: panel-thinborder-light + +scrollbar: chrome.png + down_arrow: 480,512,16,16 + down_pressed: 480,512,16,16 + up_arrow: 480,528,16,16 + up_pressed: 480,528,16,16 diff --git a/mods/modchooser/content.yaml b/mods/modchooser/content.yaml new file mode 100644 index 0000000000..541b713e8b --- /dev/null +++ b/mods/modchooser/content.yaml @@ -0,0 +1,370 @@ +Background@CONTENT_PANEL: + Logic: ModContentLogic + X: (WINDOW_RIGHT - WIDTH)/2 + Y: (WINDOW_BOTTOM - HEIGHT)/2 + Width: 500 + Height: 268 + Background: panel-bg + Children: + Background@RULE: + X: 30 + Y: 50 + Width: 440 + Height:150 + Background:panel-rule + Label@TITLE: + X: 0 + Y: 12 + Width: PARENT_RIGHT + Height: 25 + Text: Manage Content + Align: Center + Font: MediumBold + Label@HEADER_TEMPLATE: + Y: 65 + Width: PARENT_RIGHT + Height: 16 + Align: Center + ScrollPanel@PACKAGES: + X: 30 + Y: 84 + Width: PARENT_RIGHT - 60 + Height: 115 + TopBottomSpacing: 4 + ItemSpacing: 2 + BorderWidth: 2 + Children: + Container@PACKAGE_TEMPLATE: + X: 6 + Width: PARENT_RIGHT - 16 + Height: 23 + Children: + Label@TITLE: + Width: 275 + Height: 23 + Label@REQUIRED: + X: 185 + Y: 0-2 + Width: 90 + Height: 23 + Align: Center + Font: Bold + TextColor: CC0000 + Text: Required + Image@DISC: + X: 275 + Y: 1 + Width: 20 + Height: 20 + ImageCollection: modchooser + ImageName: cdicon + TooltipContainer: TOOLTIP_CONTAINER + TooltipTemplate: DISC_TOOLTIP + Button@DOWNLOAD: + X: 304 + Y: 0 + Width: 100 + Height: 23 + Background: button-highlighted-thin + Text: Download + Label@INSTALLED: + X: 304 + Y: 0-2 + Width: 100 + Height: 23 + Align: Center + Font: Bold + TextColor: 00CC00 + Text: Installed + Label@REQUIRES_DISC: + X: 304 + Y: 0-2 + Width: 100 + Height: 23 + Align: Center + Font: Bold + TextColor: DDDDDD + Text: Requires Disc + Button@CHECK_DISC_BUTTON: + X: 30 + Y: PARENT_BOTTOM - 52 + Background:button-highlighted + Width: 110 + Height: 32 + Text: Detect Disc + Font: Bold + Button@BACK_BUTTON: + X: PARENT_RIGHT - 140 + Y: PARENT_BOTTOM - 52 + Background:button-highlighted + Width: 110 + Height: 32 + Text: Back + Font: Bold + Key: escape + TooltipContainer@TOOLTIP_CONTAINER: + +Background@DISC_TOOLTIP: + Logic: ModContentDiscTooltipLogic + Background: panel-thinborder + Height: 25 + Children: + Label@DESCRIPTION: + X: 5 + Height: 23 + Font: Bold + Text: Content available from: + Container@DISCS: + Y: 15 + Width: PARENT_RIGHT - 10 + Height: 7 # used as bottom margin + Children: + Label@DISC_TEMPLATE: + X: 20 + Height: 14 + Font: TinyBold + +Container@PACKAGE_DOWNLOAD_PANEL: + Logic: DownloadPackageLogic + X: (WINDOW_RIGHT - WIDTH) / 2 + Y: (WINDOW_BOTTOM - HEIGHT) / 2 + Width: 500 + Height: 177 + Children: + Background: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Background: panel-bg + Background@RULE: + X: 30 + Y: 50 + Width: 440 + Height: 150 + Background: panel-rule + Label@TITLE: + X: 0 + Y: 12 + Width: PARENT_RIGHT + Height: 25 + Align: Center + Font: MediumBold + ProgressBar@PROGRESS_BAR: + X: 30 + Y: 64 + Width: PARENT_RIGHT - 60 + Height: 16 + BarMargin: 0, 0 + Label@STATUS_LABEL: + X: 30 + Y: 85 + Width: PARENT_RIGHT - 60 + Height: 25 + Align: Left + Button@RETRY_BUTTON: + X: 30 + Y: PARENT_BOTTOM - 52 + Background:button-highlighted + Width: 120 + Height: 32 + Visible: false + Text: Retry + Font: Bold + Key: return + Button@CANCEL_BUTTON: + X: PARENT_RIGHT - 30 - WIDTH + Y: PARENT_BOTTOM - 52 + Background:button-highlighted + Width: 110 + Height: 32 + Text: Cancel + Font: Bold + Key: escape + +Background@DISC_INSTALL_PANEL: + Logic: InstallFromDiscLogic + X: (WINDOW_RIGHT - WIDTH)/2 + Y: (WINDOW_BOTTOM - HEIGHT)/2 + Width: 500 + Height: 177 + Background: panel-bg + Children: + Background@RULE: + X: 30 + Y: 50 + Width: 440 + Height:150 + Background:panel-rule + Label@TITLE: + Y: 12 + Width: PARENT_RIGHT + Height: 25 + Align: Center + Font: MediumBold + Container@PROGRESS: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Visible: false + Children: + ProgressBar@PROGRESS_BAR: + X: 30 + Y: 60 + Width: PARENT_RIGHT - 60 + Height: 16 + BarMargin: 0, 0 + Label@PROGRESS_MESSAGE: + X: 30 + Y: 80 + Width: PARENT_RIGHT - 60 + Height: 25 + Align: Left + Container@MESSAGE: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Height: 157 + Visible: false + Children: + Label@MESSAGE_MESSAGE: + Y: 65 + Width: PARENT_RIGHT + Height: 25 + Align: Center + Container@LIST: + Width: PARENT_RIGHT + Height: 268 + Visible: false + Children: + Label@LIST_MESSAGE: + Y: 65 + Width: PARENT_RIGHT + Height: 16 + Align: Center + ScrollPanel@LIST_PANEL: + X: 30 + Y: 99 + Width: PARENT_RIGHT - 60 + Height: 100 + TopBottomSpacing: 4 + ItemSpacing: 2 + BorderWidth: 2 + Children: + Label@LIST_TEMPLATE: + X: 6 + Width: PARENT_RIGHT - 16 + Height: 23 + Button@PRIMARY_BUTTON: + X: 30 + Y: PARENT_BOTTOM - 52 + Background:button-highlighted + Width: 110 + Height: 32 + Font: Bold + Key: return + Button@SECONDARY_BUTTON: + X: PARENT_RIGHT - 140 + Y: PARENT_BOTTOM - 52 + Background:button-highlighted + Width: 110 + Height: 32 + Font: Bold + Key: escape + +Background@CONTENT_PROMPT_PANEL: + Logic: ModContentPromptLogic + X: (WINDOW_RIGHT - WIDTH)/2 + Y: (WINDOW_BOTTOM - HEIGHT)/2 + Width: 500 + Height: 140 + Background: panel-bg + Children: + Background@RULE: + X: 30 + Y: 50 + Width: 440 + Height:150 + Background:panel-rule + Label@TITLE: + X: 0 + Y: 12 + Width: PARENT_RIGHT + Height: 25 + Text: Install Content + Align: Center + Font: MediumBold + Label@HEADER_TEMPLATE: + Y: 65 + Width: PARENT_RIGHT + Height: 16 + Align: Center + Button@ADVANCED_BUTTON: + X: 30 + Y: PARENT_BOTTOM - 52 + Background:button-highlighted + Width: 140 + Height: 32 + Text: Advanced Install + Font: Bold + Button@QUICK_BUTTON: + X: 185 + Y: PARENT_BOTTOM - 52 + Background: button-highlighted + Width: 110 + Height: 32 + Text: Quick Install + Font: Bold + Button@BACK_BUTTON: + X: PARENT_RIGHT - WIDTH - 30 + Y: PARENT_BOTTOM - 52 + Background:button-highlighted + Width: 110 + Height: 32 + Text: Back + Font: Bold + Key: escape + +Container@INSTALL_MOD_PANEL: + Logic: InstallModLogic + X: (WINDOW_RIGHT - WIDTH)/2 + Y: (WINDOW_BOTTOM - HEIGHT)/2 + Width: 500 + Height: 177 + Children: + Background: + Width: PARENT_RIGHT + Height: PARENT_BOTTOM + Background: panel-bg + Background@RULE: + X: 30 + Y: 50 + Width: 440 + Height: 150 + Background: panel-rule + Label@TITLE: + X: 0 + Y: 12 + Width: PARENT_RIGHT + Height: 25 + Text: Missing dependencies + Align: Center + Font: MediumBold + Label@DESC: + X: 0 + Y: 65 + Width: PARENT_RIGHT + Height: 25 + Align: Center + Text: Please fully install the following mods then try again: + Label@MOD_LIST: + X: 0 + Y: 85 + Width: PARENT_RIGHT + Height: 25 + Align: Center + Button@BACK_BUTTON: + X: PARENT_RIGHT - 130 + Y: PARENT_BOTTOM - 52 + Background: button-highlighted + Width: 110 + Height: 32 + Text: Back + Font: Bold + Key: escape diff --git a/mods/modchooser/install.yaml b/mods/modchooser/install.yaml index 92921272d2..7f8d32093d 100644 --- a/mods/modchooser/install.yaml +++ b/mods/modchooser/install.yaml @@ -260,50 +260,3 @@ Container@INSTALL_MUSIC_PANEL: Text: Back Font: Bold Key: escape -Container@INSTALL_MOD_PANEL: - Logic: InstallModLogic - X: (WINDOW_RIGHT - WIDTH)/2 - Y: (WINDOW_BOTTOM - HEIGHT)/2 - Width: 500 - Height: 177 - Children: - Background: - Width: PARENT_RIGHT - Height: PARENT_BOTTOM - Background: panel-bg - Background@RULE: - X: 30 - Y: 50 - Width: 440 - Height: 150 - Background: panel-rule - Label@TITLE: - X: 0 - Y: 12 - Width: PARENT_RIGHT - Height: 25 - Text: Missing dependencies - Align: Center - Font: MediumBold - Label@DESC: - X: 0 - Y: 65 - Width: PARENT_RIGHT - Height: 25 - Align: Center - Text: Please fully install the following mods then try again: - Label@MOD_LIST: - X: 0 - Y: 85 - Width: PARENT_RIGHT - Height: 25 - Align: Center - Button@BACK_BUTTON: - X: PARENT_RIGHT - 130 - Y: PARENT_BOTTOM - 52 - Background: button-highlighted - Width: 110 - Height: 32 - Text: Back - Font: Bold - Key: escape diff --git a/mods/modchooser/mod.yaml b/mods/modchooser/mod.yaml index 96ce3e0d00..b18a2cdd6c 100644 --- a/mods/modchooser/mod.yaml +++ b/mods/modchooser/mod.yaml @@ -23,6 +23,7 @@ Assemblies: ChromeLayout: modchooser|modchooser.yaml modchooser|install.yaml + modchooser|content.yaml Notifications: modchooser|notifications.yaml diff --git a/mods/modchooser/modchooser.yaml b/mods/modchooser/modchooser.yaml index a4d83bc54e..c52557ead6 100644 --- a/mods/modchooser/modchooser.yaml +++ b/mods/modchooser/modchooser.yaml @@ -78,34 +78,34 @@ Background@MODCHOOSER_DIALOG: ImageCollection:modchooser ImageName:rightarrow Background@RULE: - X:53 + X:30 Y:PARENT_BOTTOM - 249 - Width:PARENT_RIGHT-106 + Width:PARENT_RIGHT-30 Height:150 Background:panel-rule Label@MOD_TITLE: - X:PARENT_RIGHT - 53 - 140 - 170 + X:PARENT_RIGHT - 400 Y:PARENT_BOTTOM-220 Align:Left Font:Bold Label@MOD_AUTHOR: - X:PARENT_RIGHT - 53 - 140 - 170 + X:PARENT_RIGHT - 400 Y:PARENT_BOTTOM-205 Align:Left Font:TinyBold Label@MOD_VERSION: - X:PARENT_RIGHT - 53 - 140 - 170 + X:PARENT_RIGHT - 400 Y:PARENT_BOTTOM-192 Align:Left Font:Tiny Label@MOD_DESC: - X:PARENT_RIGHT - 53 - 140 - 170 + X:PARENT_RIGHT - 400 Y:PARENT_BOTTOM-175 Align:Left VAlign:Top Font:Tiny Background@PREVIEW: - X:53 + X:30 Y:PARENT_BOTTOM - 25 - HEIGHT Width:300 Height:200 @@ -120,20 +120,27 @@ Background@MODCHOOSER_DIALOG: X:2 Y:2 Button@LOAD_BUTTON: + X:PARENT_RIGHT - 300 - WIDTH + Y:PARENT_BOTTOM - 25 - HEIGHT + Width:100 + Height:32 + Text:Play Background:button-highlighted Key:return - X:PARENT_RIGHT - 53 - WIDTH - 170 + Button@CONFIGURE_BUTTON: + X:PARENT_RIGHT - 145 - WIDTH Y:PARENT_BOTTOM - 25 - HEIGHT Width:140 Height:32 - Text:Load Mod - Button@QUIT_BUTTON: + Text:Manage Content Background:button-highlighted - X:PARENT_RIGHT - 53 - WIDTH + Button@QUIT_BUTTON: + X:PARENT_RIGHT - 30 - WIDTH Y:PARENT_BOTTOM - 25 - HEIGHT - Width:140 + Width:100 Height:32 Text:Quit + Background:button-highlighted Background@DIALOG_HEADER: Width:PARENT_RIGHT Height:72 @@ -157,6 +164,6 @@ Background@BUTTON_TOOLTIP: Height:23 Font:Bold Label@HOTKEY: - TextColor:255,255,0 + TextColor:FFFF00 Height:23 Font:Bold \ No newline at end of file