From dee5f96ea70551aa44cf3530805afc215d6ddc89 Mon Sep 17 00:00:00 2001 From: Harrison Date: Sun, 16 Feb 2020 21:44:21 -0500 Subject: [PATCH] Files renamed to RecrownedGTK. --- .../NinePatch/NinePatchImporter.cs | 16 + .../NinePatch/NinePatchProcessor.cs | 19 + .../NinePatch/NinePatchWriter.cs | 26 + .../RecrownedAthenaeum.Pipeline.csproj | 10 + .../TextureAtlas/TextureAtlasImporter.cs | 18 + .../TextureAtlas/TextureAtlasProcessor.cs | 21 + .../TextureAtlas/TextureAtlasWriter.cs | 41 ++ .../CommandProcessor/CommandEngine.cs | 97 ++++ .../Commands/ClearConsoleCommand.cs | 17 + .../CommandProcessor/Commands/HelpCommand.cs | 53 ++ .../CommandProcessor/Commands/StopCommand.cs | 18 + .../CommandProcessor/EngineCommand.cs | 183 +++++++ .../CommandProcessor/EngineCommandArgument.cs | 15 + RecrownedGTK.Tools/ConsoleUtilities.cs | 50 ++ .../NinePatchTools/NinePatchCommand.cs | 58 +++ .../RecrownedAthenaeum.Tools.csproj | 26 + .../TextureAtlasTools/TexturePacker.cs | 292 ++++++++++++ .../TextureAtlasTools/TexturePackerCommand.cs | 118 +++++ RecrownedGTK.Tools/Tools.cs | 47 ++ RecrownedGTK.code-workspace | 4 + RecrownedGTK/Assets/AssetManager.cs | 207 ++++++++ RecrownedGTK/Assets/IAssetPathResolver.cs | 15 + RecrownedGTK/Assets/NormalContentResolver.cs | 18 + RecrownedGTK/Data/NinePatchData.cs | 35 ++ RecrownedGTK/Data/SkinData.cs | 115 +++++ RecrownedGTK/Data/TextureAtlasData.cs | 83 ++++ RecrownedGTK/Game.cs | 7 + RecrownedGTK/Graphics/NinePatch.cs | 130 +++++ RecrownedGTK/Graphics/Render/Camera.cs | 90 ++++ RecrownedGTK/Graphics/Render/Camera2D.cs | 84 ++++ .../Graphics/Render/ConsistentSpriteBatch.cs | 104 ++++ .../Graphics/Render/PrimitiveBatch.cs | 158 ++++++ .../Graphics/Render/RectangleRenderer.cs | 119 +++++ RecrownedGTK/Graphics/Render/Shader/Shader.cs | 80 ++++ .../Graphics/Render/Shader/default.frag | 10 + .../Graphics/Render/Shader/default.vert | 8 + RecrownedGTK/Graphics/Render/TextureBatch.cs | 17 + RecrownedGTK/Graphics/Texture.cs | 34 ++ RecrownedGTK/Graphics/TextureAtlas.cs | 236 +++++++++ RecrownedGTK/Graphics/UI/BookSystem/Book.cs | 211 ++++++++ RecrownedGTK/Graphics/UI/BookSystem/Page.cs | 57 +++ .../Graphics/UI/Modular/Modules/Image.cs | 85 ++++ .../UI/Modular/Modules/Interactive/Button.cs | 155 ++++++ .../Modular/Modules/Interactive/TextButton.cs | 92 ++++ .../Graphics/UI/Modular/Modules/Text.cs | 206 ++++++++ .../UI/Modular/Modules/UIScrollable.cs | 451 ++++++++++++++++++ RecrownedGTK/Graphics/UI/Modular/UIModule.cs | 190 ++++++++ .../Graphics/UI/Modular/UIModuleGroup.cs | 136 ++++++ .../Graphics/UI/ScreenSystem/ITransition.cs | 46 ++ .../Graphics/UI/ScreenSystem/LoadingScreen.cs | 166 +++++++ .../Graphics/UI/ScreenSystem/Screen.cs | 204 ++++++++ .../Graphics/UI/ScreenSystem/ScreenManager.cs | 213 +++++++++ .../Definitions/ButtonSkinDefinition.cs | 28 ++ .../SkinSystem/Definitions/SkinDefinition.cs | 20 + .../Definitions/TextButtonSkinDefinition.cs | 25 + .../Definitions/UIScrollableSkinDefinition.cs | 32 ++ RecrownedGTK/Graphics/UI/SkinSystem/ISkin.cs | 54 +++ .../Graphics/UI/SkinSystem/MergedSkin.cs | 88 ++++ RecrownedGTK/Graphics/UI/SkinSystem/Skin.cs | 183 +++++++ .../Graphics/UI/SkinSystem/SkinManager.cs | 239 ++++++++++ RecrownedGTK/Graphics/VertexArrayHandle.cs | 39 ++ RecrownedGTK/Graphics/VertexBufferHandle.cs | 46 ++ RecrownedGTK/ParticleSystem/Particle.cs | 6 + .../Persistence/PreferencesManager.cs | 103 ++++ RecrownedGTK/RecrownedAthenaeum.csproj | 9 + RecrownedGTK/Types/Extensions.cs | 33 ++ RecrownedGTK/Types/IRectangleDrawable.cs | 17 + RecrownedGTK/Types/Rectangle.cs | 24 + RecrownedGTK/Types/Resolution.cs | 54 +++ 69 files changed, 5891 insertions(+) create mode 100644 RecrownedGTK.Pipeline/NinePatch/NinePatchImporter.cs create mode 100644 RecrownedGTK.Pipeline/NinePatch/NinePatchProcessor.cs create mode 100644 RecrownedGTK.Pipeline/NinePatch/NinePatchWriter.cs create mode 100644 RecrownedGTK.Pipeline/RecrownedAthenaeum.Pipeline.csproj create mode 100644 RecrownedGTK.Pipeline/TextureAtlas/TextureAtlasImporter.cs create mode 100644 RecrownedGTK.Pipeline/TextureAtlas/TextureAtlasProcessor.cs create mode 100644 RecrownedGTK.Pipeline/TextureAtlas/TextureAtlasWriter.cs create mode 100644 RecrownedGTK.Tools/CommandProcessor/CommandEngine.cs create mode 100644 RecrownedGTK.Tools/CommandProcessor/Commands/ClearConsoleCommand.cs create mode 100644 RecrownedGTK.Tools/CommandProcessor/Commands/HelpCommand.cs create mode 100644 RecrownedGTK.Tools/CommandProcessor/Commands/StopCommand.cs create mode 100644 RecrownedGTK.Tools/CommandProcessor/EngineCommand.cs create mode 100644 RecrownedGTK.Tools/CommandProcessor/EngineCommandArgument.cs create mode 100644 RecrownedGTK.Tools/ConsoleUtilities.cs create mode 100644 RecrownedGTK.Tools/NinePatchTools/NinePatchCommand.cs create mode 100644 RecrownedGTK.Tools/RecrownedAthenaeum.Tools.csproj create mode 100644 RecrownedGTK.Tools/TextureAtlasTools/TexturePacker.cs create mode 100644 RecrownedGTK.Tools/TextureAtlasTools/TexturePackerCommand.cs create mode 100644 RecrownedGTK.Tools/Tools.cs create mode 100644 RecrownedGTK.code-workspace create mode 100644 RecrownedGTK/Assets/AssetManager.cs create mode 100644 RecrownedGTK/Assets/IAssetPathResolver.cs create mode 100644 RecrownedGTK/Assets/NormalContentResolver.cs create mode 100644 RecrownedGTK/Data/NinePatchData.cs create mode 100644 RecrownedGTK/Data/SkinData.cs create mode 100644 RecrownedGTK/Data/TextureAtlasData.cs create mode 100644 RecrownedGTK/Game.cs create mode 100644 RecrownedGTK/Graphics/NinePatch.cs create mode 100644 RecrownedGTK/Graphics/Render/Camera.cs create mode 100644 RecrownedGTK/Graphics/Render/Camera2D.cs create mode 100644 RecrownedGTK/Graphics/Render/ConsistentSpriteBatch.cs create mode 100644 RecrownedGTK/Graphics/Render/PrimitiveBatch.cs create mode 100644 RecrownedGTK/Graphics/Render/RectangleRenderer.cs create mode 100644 RecrownedGTK/Graphics/Render/Shader/Shader.cs create mode 100644 RecrownedGTK/Graphics/Render/Shader/default.frag create mode 100644 RecrownedGTK/Graphics/Render/Shader/default.vert create mode 100644 RecrownedGTK/Graphics/Render/TextureBatch.cs create mode 100644 RecrownedGTK/Graphics/Texture.cs create mode 100644 RecrownedGTK/Graphics/TextureAtlas.cs create mode 100644 RecrownedGTK/Graphics/UI/BookSystem/Book.cs create mode 100644 RecrownedGTK/Graphics/UI/BookSystem/Page.cs create mode 100644 RecrownedGTK/Graphics/UI/Modular/Modules/Image.cs create mode 100644 RecrownedGTK/Graphics/UI/Modular/Modules/Interactive/Button.cs create mode 100644 RecrownedGTK/Graphics/UI/Modular/Modules/Interactive/TextButton.cs create mode 100644 RecrownedGTK/Graphics/UI/Modular/Modules/Text.cs create mode 100644 RecrownedGTK/Graphics/UI/Modular/Modules/UIScrollable.cs create mode 100644 RecrownedGTK/Graphics/UI/Modular/UIModule.cs create mode 100644 RecrownedGTK/Graphics/UI/Modular/UIModuleGroup.cs create mode 100644 RecrownedGTK/Graphics/UI/ScreenSystem/ITransition.cs create mode 100644 RecrownedGTK/Graphics/UI/ScreenSystem/LoadingScreen.cs create mode 100644 RecrownedGTK/Graphics/UI/ScreenSystem/Screen.cs create mode 100644 RecrownedGTK/Graphics/UI/ScreenSystem/ScreenManager.cs create mode 100644 RecrownedGTK/Graphics/UI/SkinSystem/Definitions/ButtonSkinDefinition.cs create mode 100644 RecrownedGTK/Graphics/UI/SkinSystem/Definitions/SkinDefinition.cs create mode 100644 RecrownedGTK/Graphics/UI/SkinSystem/Definitions/TextButtonSkinDefinition.cs create mode 100644 RecrownedGTK/Graphics/UI/SkinSystem/Definitions/UIScrollableSkinDefinition.cs create mode 100644 RecrownedGTK/Graphics/UI/SkinSystem/ISkin.cs create mode 100644 RecrownedGTK/Graphics/UI/SkinSystem/MergedSkin.cs create mode 100644 RecrownedGTK/Graphics/UI/SkinSystem/Skin.cs create mode 100644 RecrownedGTK/Graphics/UI/SkinSystem/SkinManager.cs create mode 100644 RecrownedGTK/Graphics/VertexArrayHandle.cs create mode 100644 RecrownedGTK/Graphics/VertexBufferHandle.cs create mode 100644 RecrownedGTK/ParticleSystem/Particle.cs create mode 100644 RecrownedGTK/Persistence/PreferencesManager.cs create mode 100644 RecrownedGTK/RecrownedAthenaeum.csproj create mode 100644 RecrownedGTK/Types/Extensions.cs create mode 100644 RecrownedGTK/Types/IRectangleDrawable.cs create mode 100644 RecrownedGTK/Types/Rectangle.cs create mode 100644 RecrownedGTK/Types/Resolution.cs diff --git a/RecrownedGTK.Pipeline/NinePatch/NinePatchImporter.cs b/RecrownedGTK.Pipeline/NinePatch/NinePatchImporter.cs new file mode 100644 index 0000000..d56fb66 --- /dev/null +++ b/RecrownedGTK.Pipeline/NinePatch/NinePatchImporter.cs @@ -0,0 +1,16 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Newtonsoft.Json; +using RecrownedGTK.Data; +using System.IO; + +namespace RecrownedGTK.Pipeline.NinePatch +{ + [ContentImporter(".9p", DisplayName = "Nine Patch Importer - RecrownedGTK", DefaultProcessor = "NinePatchProcessor")] + internal class NinePatchImporter : ContentImporter + { + public override NinePatchData Import(string filename, ContentImporterContext context) + { + return JsonConvert.DeserializeObject(File.ReadAllText(filename)); + } + } +} diff --git a/RecrownedGTK.Pipeline/NinePatch/NinePatchProcessor.cs b/RecrownedGTK.Pipeline/NinePatch/NinePatchProcessor.cs new file mode 100644 index 0000000..a33818f --- /dev/null +++ b/RecrownedGTK.Pipeline/NinePatch/NinePatchProcessor.cs @@ -0,0 +1,19 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Newtonsoft.Json; +using RecrownedGTK.Data; +using System.IO; +using System.Text; + +namespace RecrownedGTK.Pipeline.NinePatch +{ + [ContentImporter(DisplayName = "Nine Patch - RecrownedGTK")] + class NinePatchProcessor : ContentProcessor + { + public override NinePatchData Process(NinePatchData input, ContentProcessorContext context) + { + if (Path.GetFileNameWithoutExtension(context.SourceIdentity.SourceFilename) == Path.GetFileNameWithoutExtension(input.textureName)) throw new InvalidContentException("Ninepatch data and texture for the data can't have the same name."); + context.AddDependency(input.textureName); + return input; + } + } +} diff --git a/RecrownedGTK.Pipeline/NinePatch/NinePatchWriter.cs b/RecrownedGTK.Pipeline/NinePatch/NinePatchWriter.cs new file mode 100644 index 0000000..531aa62 --- /dev/null +++ b/RecrownedGTK.Pipeline/NinePatch/NinePatchWriter.cs @@ -0,0 +1,26 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler; +using Newtonsoft.Json; +using RecrownedGTK.Data; +using System.IO; + +namespace RecrownedGTK.Pipeline.NinePatch +{ + [ContentTypeWriter] + class NinePatchWriter : ContentTypeWriter + { + public override string GetRuntimeReader(TargetPlatform targetPlatform) + { + return "RecrownedGTK.ContentReaders.NinePatchDataReader, RecrownedGTK"; + } + + protected override void Write(ContentWriter output, NinePatchData value) + { + output.Write(Path.GetFileNameWithoutExtension(value.textureName)); + output.Write(value.left); + output.Write(value.right); + output.Write(value.bottom); + output.Write(value.top); + } + } +} diff --git a/RecrownedGTK.Pipeline/RecrownedAthenaeum.Pipeline.csproj b/RecrownedGTK.Pipeline/RecrownedAthenaeum.Pipeline.csproj new file mode 100644 index 0000000..5794b33 --- /dev/null +++ b/RecrownedGTK.Pipeline/RecrownedAthenaeum.Pipeline.csproj @@ -0,0 +1,10 @@ + + + netstandard2.0 + + + + + + + \ No newline at end of file diff --git a/RecrownedGTK.Pipeline/TextureAtlas/TextureAtlasImporter.cs b/RecrownedGTK.Pipeline/TextureAtlas/TextureAtlasImporter.cs new file mode 100644 index 0000000..f5cd29c --- /dev/null +++ b/RecrownedGTK.Pipeline/TextureAtlas/TextureAtlasImporter.cs @@ -0,0 +1,18 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Graphics; +using Microsoft.Xna.Framework.Content.Pipeline.Processors; +using Newtonsoft.Json; +using RecrownedGTK.Data; +using System.IO; + +namespace RecrownedGTK.Pipeline.TextureAtlas +{ + [ContentImporter(".tatlas", DisplayName = "Texture Atlas Importer - RecrownedGTK", DefaultProcessor = "TextureAtlasProcessor")] + internal class TextureAtlasImporter : ContentImporter + { + public override TextureAtlasData Import(string filename, ContentImporterContext context) + { + return JsonConvert.DeserializeObject(File.ReadAllText(filename)); + } + } +} diff --git a/RecrownedGTK.Pipeline/TextureAtlas/TextureAtlasProcessor.cs b/RecrownedGTK.Pipeline/TextureAtlas/TextureAtlasProcessor.cs new file mode 100644 index 0000000..518437e --- /dev/null +++ b/RecrownedGTK.Pipeline/TextureAtlas/TextureAtlasProcessor.cs @@ -0,0 +1,21 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Graphics; +using Microsoft.Xna.Framework.Content.Pipeline.Processors; +using Newtonsoft.Json; +using RecrownedGTK.Data; +using System.IO; +using System.Text; + +namespace RecrownedGTK.Pipeline.TextureAtlas +{ + [ContentProcessor(DisplayName = "Texture Atlas - RecrownedGTK")] + class TextureAtlasProcessor : ContentProcessor + { + public override TextureAtlasData Process(TextureAtlasData input, ContentProcessorContext context) + { + if (context.SourceIdentity.SourceFilename == input.textureName) throw new InvalidContentException("Texture atlas data and texture file for the atlas can't have the same name."); + context.AddDependency(input.textureName); + return input; + } + } +} diff --git a/RecrownedGTK.Pipeline/TextureAtlas/TextureAtlasWriter.cs b/RecrownedGTK.Pipeline/TextureAtlas/TextureAtlasWriter.cs new file mode 100644 index 0000000..ff75b90 --- /dev/null +++ b/RecrownedGTK.Pipeline/TextureAtlas/TextureAtlasWriter.cs @@ -0,0 +1,41 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler; +using Newtonsoft.Json; +using RecrownedGTK.Data; +using System.IO; + +namespace RecrownedGTK.Pipeline.TextureAtlas +{ + [ContentTypeWriter] + class TextureAtlasWriter : ContentTypeWriter + { + public override string GetRuntimeReader(TargetPlatform targetPlatform) + { + return "RecrownedGTK.ContentReaders.TextureAtlasDataReader, RecrownedGTK"; + } + + protected override void Write(ContentWriter output, TextureAtlasData value) + { + output.Write(Path.GetFileNameWithoutExtension(value.textureName)); + output.Write(value.regions.Length); + + for (int i = 0; i < value.regions.Length; i++) + { + output.Write(value.regions[i].name); + output.Write(value.regions[i].bounds.X); + output.Write(value.regions[i].bounds.Y); + output.Write(value.regions[i].bounds.Width); + output.Write(value.regions[i].bounds.Height); + bool hasNPatch = value.regions[i].ninePatchData != null; + output.Write(hasNPatch); + if (hasNPatch) + { + output.Write(value.regions[i].ninePatchData.left); + output.Write(value.regions[i].ninePatchData.right); + output.Write(value.regions[i].ninePatchData.bottom); + output.Write(value.regions[i].ninePatchData.top); + } + } + } + } +} diff --git a/RecrownedGTK.Tools/CommandProcessor/CommandEngine.cs b/RecrownedGTK.Tools/CommandProcessor/CommandEngine.cs new file mode 100644 index 0000000..9edc1f9 --- /dev/null +++ b/RecrownedGTK.Tools/CommandProcessor/CommandEngine.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace RecrownedGTK.Tools.CommandProcessor +{ + internal class CommandEngine + { + public bool running = true; + public readonly List commands; + + public CommandEngine() + { + commands = new List(); + } + + internal void Run() + { + while (running) + { + ConsoleUtilities.WriteWrappedLine("\nAwaiting command."); + string command = Console.ReadLine(); + try + { + Process(command); + + } + catch (ArgumentException ae) + { + ConsoleUtilities.WriteWrappedLine("Error: " + ae.Message); + } + } + } + + public void Process(string commandAndArguments) + { + string command = commandAndArguments; + string[] arguments = null; + + if (commandAndArguments.Contains(' ')) + { + command = command.Remove(command.IndexOf(' ')); + if (!ContainsCommand(command)) throw new ArgumentException("Command not found."); + string[] argumentsSplit = commandAndArguments.Substring(command.Length + 1).Split(' '); + List argumentsList = new List(); + + for (int i = 0; i < argumentsSplit.Length; i++) + { + if (argumentsSplit[i].Contains('"') && !argumentsSplit[i].EndsWith('"')) + { + StringBuilder quoteBuilder = new StringBuilder(); + do + { + quoteBuilder.Append(' '); + quoteBuilder.Append(argumentsSplit[i]); + i++; + if (i >= argumentsSplit.Length) + { + throw new ArgumentException("Argument is missing terminating \'\"\'"); + } + + } while (!argumentsSplit[i].Contains('"')); + quoteBuilder.Append(' '); + quoteBuilder.Append(argumentsSplit[i]); + argumentsList.Add(quoteBuilder.ToString().Replace("\"", "").Trim()); + } + else + { + argumentsList.Add(argumentsSplit[i]); + } + } + arguments = argumentsList.ToArray(); + } + if (!ContainsCommand(command)) throw new ArgumentException("Command not found. Type \"help\" for a list of commands."); + GetCommand(command).Run(arguments); + } + + public bool ContainsCommand(string command) + { + for (int i = 0; i < commands.Count; i++) + { + if (commands[i].IsInvoked(command)) return true; + } + return false; + } + + public EngineCommand GetCommand(string command) + { + for (int i = 0; i < commands.Count; i++) + { + if (commands[i].IsInvoked(command)) return commands[i]; + } + + return null; + } + } +} diff --git a/RecrownedGTK.Tools/CommandProcessor/Commands/ClearConsoleCommand.cs b/RecrownedGTK.Tools/CommandProcessor/Commands/ClearConsoleCommand.cs new file mode 100644 index 0000000..e6d8b09 --- /dev/null +++ b/RecrownedGTK.Tools/CommandProcessor/Commands/ClearConsoleCommand.cs @@ -0,0 +1,17 @@ +using System; + +namespace RecrownedGTK.Tools.CommandProcessor.Commands +{ + internal class ClearConsoleCommand : EngineCommand + { + public ClearConsoleCommand() : base("clear") + { + help = "Clears the console."; + } + + public override void Run(string[] arguments = null) + { + Console.Clear(); + } + } +} diff --git a/RecrownedGTK.Tools/CommandProcessor/Commands/HelpCommand.cs b/RecrownedGTK.Tools/CommandProcessor/Commands/HelpCommand.cs new file mode 100644 index 0000000..a2448c1 --- /dev/null +++ b/RecrownedGTK.Tools/CommandProcessor/Commands/HelpCommand.cs @@ -0,0 +1,53 @@ +using System; + +namespace RecrownedGTK.Tools.CommandProcessor.Commands +{ + internal class HelpCommand : EngineCommand + { + CommandEngine commandEngine; + + public HelpCommand(CommandEngine commandEngine) : base("help") + { + this.commandEngine = commandEngine; + help = "help [command] [arg]"; + } + + + public override void Run(string[] arguments) + { + if (arguments != null) + { + if (commandEngine.ContainsCommand(arguments[0])) + { + if (arguments.Length < 2) ConsoleUtilities.WriteWrappedLine(commandEngine.GetCommand(arguments[0]).Help(null)); + for (int i = 1; i < arguments.Length; i++) + { + ConsoleUtilities.WriteWrappedLine(commandEngine.GetCommand(arguments[0]).Help(arguments[i])); + } + } + else + { + throw new ArgumentException(arguments[0] + " not a command. Type \"help\" for a list of commands."); + } + } + else + { + ConsoleUtilities.WriteWrappedLine("Tools for RecrownedGTK library. Possible commands are as follows:\n"); + foreach (EngineCommand engineCommand in commandEngine.commands) + { + for (int i = 0; i < engineCommand.InvokeStrings.Length; i++) + { + ConsoleUtilities.WriteWrapped(engineCommand.InvokeStrings[i]); + if (i + 1 < engineCommand.InvokeStrings.Length) + { + ConsoleUtilities.WriteWrapped(", "); + } + } + ConsoleUtilities.WriteWrapped(" : "); + ConsoleUtilities.WriteWrapped(engineCommand.Help().Replace("\n", "\n\t"), true); + Console.WriteLine("--------"); + } + } + } + } +} diff --git a/RecrownedGTK.Tools/CommandProcessor/Commands/StopCommand.cs b/RecrownedGTK.Tools/CommandProcessor/Commands/StopCommand.cs new file mode 100644 index 0000000..e83056d --- /dev/null +++ b/RecrownedGTK.Tools/CommandProcessor/Commands/StopCommand.cs @@ -0,0 +1,18 @@ +namespace RecrownedGTK.Tools.CommandProcessor.Commands +{ + internal class StopCommand : EngineCommand + { + CommandEngine commandEngine; + + public StopCommand(CommandEngine commandEngine) : base("quit", "stop", "q", "exit") + { + this.commandEngine = commandEngine; + help = "Exits the tool."; + } + + public override void Run(string[] arguments = null) + { + commandEngine.running = false; + } + } +} diff --git a/RecrownedGTK.Tools/CommandProcessor/EngineCommand.cs b/RecrownedGTK.Tools/CommandProcessor/EngineCommand.cs new file mode 100644 index 0000000..06eed85 --- /dev/null +++ b/RecrownedGTK.Tools/CommandProcessor/EngineCommand.cs @@ -0,0 +1,183 @@ +using System; +using System.Text; + +namespace RecrownedGTK.Tools.CommandProcessor +{ + public abstract class EngineCommand + { + + bool caseSensitiveName = false; + + /// + /// the words a user can type that will invoke this command. + /// + protected string[] invokeStrings; + public string[] InvokeStrings { get { return invokeStrings; } } + + /// + /// Arguments that this command should accept and take into account. + /// + protected EngineCommandArgument[] commandArguments; + protected string help; + + public EngineCommand(params string[] invokeStrings) + { + this.invokeStrings = invokeStrings ?? throw new ArgumentNullException(); + } + + /// + /// Whether or not this command should be invoked given the string. + /// + /// The string that acts as the command. + /// whether or not this command is invoked. + public bool IsInvoked(string command) + { + if (!caseSensitiveName) command = command.ToLower(); + for (int i = 0; i < invokeStrings.Length; i++) + { + if (!caseSensitiveName) + { + if (invokeStrings[i].ToLower() == command) return true; + } + else + { + if (invokeStrings[i] == command) return true; + } + } + return false; + } + + /// + /// Runs the command. + /// + /// arguments to be used. May be null. + public abstract void Run(string[] arguments = null); + + /// + /// Check if given argument is viable for command. + /// + /// Argument to check. + /// True if valid. + public bool Validate(string argument) + { + return EngineCommandArgumentOf(argument) == null ? false : true; + } + + /// + /// Returns the of the given argument or null if the string is an invalid argument. + /// + /// The argument string. + /// null if not viable or the if viable. + public EngineCommandArgument EngineCommandArgumentOf(string argument) + { + for (int i = 0; i < commandArguments.Length; i++) + { + if (commandArguments[i].invokeString == argument) + { + return commandArguments[i]; + } + } + return null; + } + + /// + /// Finds the index of the argument given in an array of arguments for the command. + /// + /// The argument to find the index of. + /// The array containing all arguments. + /// The index or throws argument exception if it doesn't exist. + public int IndexOfArgument(string argument, string[] arguments) + { + if (arguments != null) + { + for (int i = 0; i < arguments.Length; i++) + { + if (arguments[i] == argument) + { + return i; + } + } + } + throw new ArgumentException("Expected argument " + argument + " is missing. Type \"help\" for more information."); + } + + public bool HasArgument(string argument, string[] arguments) + { + try + { + IndexOfArgument(argument, arguments); + } + catch (ArgumentException) + { + return false; + } + return true; + } + + public bool HasArgument(EngineCommandArgument argument, string[] arguments) + { + return HasArgument(argument.invokeString, arguments); + } + + /// + /// Finds the index of the argument given in an array of arguments for the command. + /// + /// The argument to find the index of. + /// The array containing all arguments. + /// The index or throws argument exception if it doesn't exist. + public int IndexOfArgumentIn(EngineCommandArgument argument, string[] arguments) + { + return IndexOfArgument(argument.invokeString, arguments); + } + + /// + /// Called when help "command trigger" [argument] is invoked. Argument is optional and therefore, may be null. If the valid arguments are properly registered, this command will take care of it. If no arguments are provided, it will return the general help defined for this command. + /// + /// Potential argument to request help for. + /// The string for the help. + public string Help(string argument = null) + { + if (argument != null && commandArguments != null) + { + if (Validate(argument)) + { + EngineCommandArgument eca = EngineCommandArgumentOf(argument); + string helpString = eca.help; + if (eca.required) helpString = helpString + " required."; + return EngineCommandArgumentOf(argument).help; + } + else + { + return "The argument " + argument + " does not exist. Type \"help " + invokeStrings[0] + "\" (or any of its aliases) for a list of arguments."; + } + } + else + { + StringBuilder helpBuilder = new StringBuilder(); + helpBuilder.Append(help); + if (commandArguments != null) + { + helpBuilder.AppendLine(); + helpBuilder.Append("Possible arguments are: "); + for (int i = 0; i < commandArguments.Length; i++) + { + helpBuilder.Append(commandArguments[i].invokeString); + if (commandArguments[i].required) helpBuilder.Append('*'); + if (i == commandArguments.Length - 2) + { + helpBuilder.Append(", and "); + } + else if (i < commandArguments.Length - 2) + { + helpBuilder.Append(", "); + } + } + helpBuilder.Append('.'); + helpBuilder.AppendLine(" [* are required arguments.]"); + } + return helpBuilder.ToString(); + } + + } + } +} diff --git a/RecrownedGTK.Tools/CommandProcessor/EngineCommandArgument.cs b/RecrownedGTK.Tools/CommandProcessor/EngineCommandArgument.cs new file mode 100644 index 0000000..734dda1 --- /dev/null +++ b/RecrownedGTK.Tools/CommandProcessor/EngineCommandArgument.cs @@ -0,0 +1,15 @@ +namespace RecrownedGTK.Tools.CommandProcessor +{ + public class EngineCommandArgument + { + public string invokeString; + public string help; + public bool required; + public EngineCommandArgument(string invokeString, string help, bool required = false) + { + this.invokeString = invokeString; + this.help = help; + this.required = required; + } + } +} diff --git a/RecrownedGTK.Tools/ConsoleUtilities.cs b/RecrownedGTK.Tools/ConsoleUtilities.cs new file mode 100644 index 0000000..b4aae00 --- /dev/null +++ b/RecrownedGTK.Tools/ConsoleUtilities.cs @@ -0,0 +1,50 @@ +using System; +using System.Text; + +namespace RecrownedGTK.Tools +{ + internal class ConsoleUtilities + { + public static void WriteWrapped(string message, bool line = false) + { + message = WrapMessageToConsoleWidth(message); + if (line) message = message + "\n"; + Console.Write(message); + } + + public static string WrapMessageToConsoleWidth(string message) + { + string[] words = message.Split(' '); + StringBuilder stringBuilder = new StringBuilder(); + int currentLineSize = 0; + for (int i = 0; i < words.Length; i++) + { + if (currentLineSize + words[i].Length >= Console.BufferWidth - Console.CursorLeft -1) + { + currentLineSize = 0; + stringBuilder.AppendLine(); + } + if (words[i].Contains("\n")) + { + currentLineSize = 0; + } + currentLineSize += words[i].Length + 1; + if (words[i].Contains("\n")) + { + currentLineSize -= 2; + } + stringBuilder.Append(words[i]); + if (i + 1 < words.Length) + { + stringBuilder.Append(' '); + } + } + return stringBuilder.ToString(); + } + + public static void WriteWrappedLine(string message) + { + WriteWrapped(message, true); + } + } +} diff --git a/RecrownedGTK.Tools/NinePatchTools/NinePatchCommand.cs b/RecrownedGTK.Tools/NinePatchTools/NinePatchCommand.cs new file mode 100644 index 0000000..96b409a --- /dev/null +++ b/RecrownedGTK.Tools/NinePatchTools/NinePatchCommand.cs @@ -0,0 +1,58 @@ +using Newtonsoft.Json; +using RecrownedGTK.Data; +using RecrownedGTK.Tools.CommandProcessor; +using System; +using System.IO; + +namespace RecrownedGTK.Tools.NinePatchTools +{ + public class NinePatchCommand : EngineCommand + { + + public NinePatchCommand() : base("9p", "ninepatch", "9patch") + { + help = "Generates a 9 patch file for a given image."; + + commandArguments = new EngineCommandArgument[6]; + + commandArguments[0] = new EngineCommandArgument("-i", "defines the path to the texture to be used for the nine patch.", true); + commandArguments[1] = new EngineCommandArgument("-o", "defines path of output file."); + commandArguments[2] = new EngineCommandArgument("-l", "left patch.", true); + commandArguments[3] = new EngineCommandArgument("-r", "right patch.", true); + commandArguments[4] = new EngineCommandArgument("-t", "top patch.", true); + commandArguments[5] = new EngineCommandArgument("-b", "bottom patch.", true); + } + + public override void Run(string[] arguments = null) + { + if (arguments == null) throw new ArgumentException("Missing arguments. Type \"help 9p\" for more information."); + int leftBound = 0, rightBound = 0, topBound = 0, bottomBound = 0; + string imagePath, outPath; + if (IndexOfArgument("-i", arguments) + 1 >= arguments.Length) throw new ArgumentException("Missing -i path after argument."); + imagePath = arguments[IndexOfArgument("-i", arguments) + 1]; + if (!File.Exists(imagePath)) throw new ArgumentException("Input file does not exist at " + imagePath + "."); + + if (HasArgument(commandArguments[1], arguments)) + { + if (IndexOfArgument("-o", arguments) + 1 >= arguments.Length) throw new ArgumentException("Missing -o path after argument."); + outPath = arguments[IndexOfArgument("-o", arguments) + 1]; + } else + { + outPath = imagePath.Substring(0, imagePath.Length - Path.GetExtension(imagePath).Length); + } + if (IndexOfArgument("-l", arguments) + 1 >= arguments.Length || !int.TryParse(arguments[IndexOfArgument("-l", arguments) + 1], out leftBound)) throw new ArgumentException("Missing -l argument bound."); + if (IndexOfArgument("-r", arguments) + 1 >= arguments.Length || !int.TryParse(arguments[IndexOfArgument("-r", arguments) + 1], out rightBound)) throw new ArgumentException("Missing -r argument bound."); + if (IndexOfArgument("-t", arguments) + 1 >= arguments.Length || !int.TryParse(arguments[IndexOfArgument("-t", arguments) + 1], out topBound)) throw new ArgumentException("Missing -u argument bound."); + if (IndexOfArgument("-b", arguments) + 1 >= arguments.Length || !int.TryParse(arguments[IndexOfArgument("-b", arguments) + 1], out bottomBound)) throw new ArgumentException("Missing -d argument bound."); + + NinePatchData npData = new NinePatchData(Path.GetFileName(imagePath), leftBound, rightBound, bottomBound, topBound); + string serialized = JsonConvert.SerializeObject(npData, Formatting.Indented); + string modifiedPath = Directory.GetParent(imagePath) + Path.PathSeparator.ToString() + Path.GetFileNameWithoutExtension(imagePath) + "-texture" + Path.GetExtension(imagePath); + File.Move(imagePath, modifiedPath); + File.WriteAllText(outPath + ".9p", serialized); + + ConsoleUtilities.WriteWrappedLine("Done. Written to \"" + outPath + "\" with values: left = " + leftBound + " right = " + rightBound + " top = " + topBound + " bottom = " + bottomBound); + ConsoleUtilities.WriteWrappedLine("Image renamed to: " + Path.GetFileName(modifiedPath)); + } + } +} diff --git a/RecrownedGTK.Tools/RecrownedAthenaeum.Tools.csproj b/RecrownedGTK.Tools/RecrownedAthenaeum.Tools.csproj new file mode 100644 index 0000000..1f92827 --- /dev/null +++ b/RecrownedGTK.Tools/RecrownedAthenaeum.Tools.csproj @@ -0,0 +1,26 @@ + + + + Exe + netcoreapp2.1 + + + RecrownedGTK.Tools + RecrownedGTK.Tools + + + + true + + + + + + + + + + + + + diff --git a/RecrownedGTK.Tools/TextureAtlasTools/TexturePacker.cs b/RecrownedGTK.Tools/TextureAtlasTools/TexturePacker.cs new file mode 100644 index 0000000..136cdac --- /dev/null +++ b/RecrownedGTK.Tools/TextureAtlasTools/TexturePacker.cs @@ -0,0 +1,292 @@ +using Newtonsoft.Json; +using RecrownedGTK.Data; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.Primitives; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security; + +namespace RecrownedGTK.Tools.TextureAtlas +{ + + public class TexturePacker + { + private enum SupportedExtensions + { + jpeg, jpg, png + } + + int powLimit; + Node masterNode; + List imageHandlers; + Dictionary ninePatchDictionary; + int tpl; + int TexturePowerLength { get { return tpl; } set { TextureLength = (int)Math.Pow(2, value); tpl = value; } } + public int TextureLength { get; private set; } + public int TexturesFound { get { return imageHandlers.Count; } } + /// + /// Machine to pack multiple textures into one large texture. + /// + /// Path to textures. + /// Power of two limit for auto expanding texture. Default is 12 which is a 4096x4096 texture. + /// What power to start at and build up from. Default is 8 which is a 256x256 texture. + internal TexturePacker(string rootDirectoryPath, int powLimit = 12, int startingPower = 8) + { + this.powLimit = powLimit; + string[] paths; + try + { + paths = Directory.GetFiles(rootDirectoryPath); + } + catch (IOException) + { + throw new ArgumentException("Path " + rootDirectoryPath + " couldn't be resolved."); + } + TexturePowerLength = startingPower; + List imageHandlers = new List(); + int minAreaRequired = 0; + for (int pathID = 0; pathID < paths.Length; pathID++) + { + SupportedExtensions extension; + if (Enum.TryParse(Path.GetExtension(paths[pathID]).ToLower().Substring(1), out extension)) + { + ConsoleUtilities.WriteWrappedLine("Reading texture data for: " + paths[pathID]); + ImageHandler image = new ImageHandler(paths[pathID]); + imageHandlers.Add(image); + minAreaRequired += image.Area; + while (minAreaRequired > TextureLength * TextureLength) + { + TexturePowerLength++; + } + } + else if (Path.GetExtension(paths[pathID]).ToLower() == ".9p") + { + if (ninePatchDictionary == null) ninePatchDictionary = new Dictionary(); + ConsoleUtilities.WriteWrappedLine("Reading ninepatch data for: " + paths[pathID]); + string serialized = File.ReadAllText(paths[pathID]); + NinePatchData npData = JsonConvert.DeserializeObject(serialized); + ninePatchDictionary.Add(npData.textureName, npData); + } + } + imageHandlers.Sort(); + this.imageHandlers = imageHandlers; + } + + /// + /// Builds a texture atlas. + /// + /// Whether or not to automatically upscale atlas' texture in the case it is too small. Goes up to 4096 by default. + public void Build(bool AutoCorrectAtlasSize = true) + { + masterNode = new Node(); + masterNode.region.Width = TextureLength; + masterNode.region.Height = TextureLength; + Queue imageHandlerQueue = new Queue(imageHandlers); + ImageHandler imageHandler; + while (imageHandlerQueue.TryDequeue(out imageHandler)) + { + Node activeNode = null; + activeNode = masterNode.InsertImageHandler(imageHandler); + if (activeNode == null) + { + if (!AutoCorrectAtlasSize || TexturePowerLength + 1 > powLimit) + { + throw new InvalidOperationException("Texture not large enough. Current size: " + TextureLength + "x" + TextureLength + "."); + } + TexturePowerLength += 1; + imageHandlerQueue.Clear(); + Build(AutoCorrectAtlasSize); + } + if (ninePatchDictionary != null && ninePatchDictionary.ContainsKey(imageHandler.name)) + { + NinePatchData npd = ninePatchDictionary[imageHandler.name]; + imageHandler.ninePatchData = npd; + if (npd.textureName.Contains("-texture")) + { + imageHandler.name = imageHandler.name.Remove(imageHandler.name.IndexOf("-texture"), 8); + } + } + } + + } + + /// + /// Renders the build into a PNG file and generates the respective meant for serialization and later to be loaded. + /// + /// directory to output to. + /// name of atlas. + public void Save(string output, string atlasName) + { + GraphicsOptions gOptions = new GraphicsOptions(); + + TextureAtlasData.AtlasRegionData[] regions = new TextureAtlasData.AtlasRegionData[TexturesFound]; + + using (Image atlasTexture = new Image(TextureLength, TextureLength)) + { + ImageHandler[] imageHandlers = this.imageHandlers.ToArray(); + + for (int i = 0; i < imageHandlers.Length; i++) + { + regions[i] = new TextureAtlasData.AtlasRegionData(); + ImageHandler ih = imageHandlers[i]; + regions[i].SetBounds(ih.x, ih.y, ih.Width, ih.Height); + regions[i].ninePatchData = ih.ninePatchData; + if (regions[i].ninePatchData != null) regions[i].ninePatchData.textureName = null; + regions[i].name = Path.GetFileNameWithoutExtension(ih.name); + using (Image image = Image.Load(ih.path)) + { + atlasTexture.Mutate(img => img.DrawImage(gOptions, image, new Point(ih.x, ih.y))); + } + } + Directory.CreateDirectory(output); + using (FileStream stream = new FileStream(output + "/" + atlasName + "-texture" + ".png", FileMode.Create)) + { + atlasTexture.SaveAsPng(stream); + } + } + string serialized = JsonConvert.SerializeObject(new TextureAtlasData(atlasName + "-texture" + ".png", regions), Formatting.Indented); + File.WriteAllText(output + "/" + atlasName + ".tatlas", serialized); + } + + public void SetNinePatch(string fileName, int a, int b, int c, int d) + { + NinePatchData ninePatchData = new NinePatchData(fileName, a, b, c, d); + RetrieveImageHandler(fileName).ninePatchData = ninePatchData; + } + + public void RemoveNinePatch(string name) + { + + RetrieveImageHandler(name).ninePatchData = null; + } + + private ImageHandler RetrieveImageHandler(string name) + { + for (int i = 0; i < TexturesFound; i++) + { + if (imageHandlers[i].name == name) + { + return imageHandlers[i]; + } + } + throw new ArgumentException("Couldn't find texture with name: " + name); + } + + private class Node + { + public Node parent; + private Node a, b; + public Node childA { get { if (a == null) a = new Node(this); return a; } set { value.parent = this; a = value; } } + public Node childB { get { if (b == null) { b = new Node(this); } return b; } set { value.parent = this; b = value; } } + public Rectangle region; + public bool ContainsImage = false; + public bool CanPlaceImage { get { return (a == null && b == null && !ContainsImage); } } + public Node(Node parent = null) + { + this.parent = parent; + if (parent != null) region = parent.region; + } + + /// + /// Attempts to insert image within the node. This builds the node to have children if needed. + /// + /// the image to insert. + /// The node the image is placed in. + public Node InsertImageHandler(ImageHandler imageHandler) + { + if (imageHandler.Width != region.Width) + { + if (imageHandler.Width < region.Width) + { + if (a == null) + { + childA.region.Width = imageHandler.Width; + } + Node attemptedNode = null; + if (!childA.ContainsImage && imageHandler.Width <= childA.region.Width) + { + attemptedNode = childA.InsertImageHandler(imageHandler); + } + + if (attemptedNode == null && !childB.ContainsImage) + { + childB.region.Width = region.Width - childA.region.Width; + childB.region.X = childA.region.X + childA.region.Width; + attemptedNode = childB.InsertImageHandler(imageHandler); + } + + return attemptedNode; + } + } + else if (imageHandler.Height != region.Height) + { + if (imageHandler.Height < region.Height) + { + if (a == null) + { + childA.region.Height = imageHandler.Height; + } + Node attemptedNode = null; + if (!childA.ContainsImage && imageHandler.Height <= childA.region.Height) + { + attemptedNode = childA.InsertImageHandler(imageHandler); + } + + if (attemptedNode == null && !childB.ContainsImage) + { + childB.region.Height = region.Height - childA.region.Height; + childB.region.Y = childA.region.Y + childA.region.Height; + attemptedNode = childB.InsertImageHandler(imageHandler); + } + return attemptedNode; + } + } + else if (CanPlaceImage) + { + imageHandler.x = region.X; + imageHandler.y = region.Y; + ContainsImage = true; + return this; + } + return null; + } + } + + private class ImageHandler : IComparable + { + public readonly string path; + public readonly IImageInfo image; + public int Area { get { return image.Width * image.Height; } } + public string name; + public int Width { get { return image.Width; } } + public int Height { get { return image.Height; } } + public int x, y; + public NinePatchData ninePatchData; + + internal ImageHandler(string path) + { + this.path = path; + name = Path.GetFileName(path); + try + { + using (FileStream stream = new FileStream(path, FileMode.Open)) + { + image = Image.Identify(stream); + } + } catch (SecurityException) + { + throw new ArgumentException("Security exception occurred for image: " + path); + } + } + + public int CompareTo(ImageHandler tImage) + { + return Area - tImage.Area; + } + } + } +} diff --git a/RecrownedGTK.Tools/TextureAtlasTools/TexturePackerCommand.cs b/RecrownedGTK.Tools/TextureAtlasTools/TexturePackerCommand.cs new file mode 100644 index 0000000..38253f9 --- /dev/null +++ b/RecrownedGTK.Tools/TextureAtlasTools/TexturePackerCommand.cs @@ -0,0 +1,118 @@ +using RecrownedGTK.Tools.CommandProcessor; +using RecrownedGTK.Tools.TextureAtlas; +using System; +using System.IO; + +namespace RecrownedGTK.Tools.TextureAtlasTools +{ + class TexturePackerCommand : EngineCommand + { + private const int DMP = 9; + private const int DSP = 6; + public TexturePackerCommand() : base("texturepacker") + { + help = "Packs a given directory composed of png and jpg files into an atlas. Can also add 9patch properties. Images with the associated \".9p\" files will automatically be defined in the resulting .tatlas file, but can be overwritten by use of the \"-9p\" argument."; + commandArguments = new[] { + new EngineCommandArgument("-interactive", "runs in interactive mode. Ninepatches must still be defined with arguments or previously defined with associated \".9p\" files. Other arguments will be ignored."), + new EngineCommandArgument("-i", "for input directory containing the textures.", true), + new EngineCommandArgument("-o", "Path for output files. Points to non-existent file. Will create texture and definitions file with name.", true), + new EngineCommandArgument("-9p", "Can be used multiple times for defining a 9patch. This parameter requires a name, left patch, right patch, top patch, and bottom patch in the format \"name,a,b,c,d\"."), + new EngineCommandArgument("-sp", "Starting power for one side of the texture. Default is " + DSP + "."), + new EngineCommandArgument("-mp", "Maximum power for one side of the texture. Default is " + DMP + "."), + new EngineCommandArgument("-dau", "Disables automatically upscaling the texture."), + }; + } + + public override void Run(string[] arguments) + { + + TexturePacker texturePacker = null; + string path = null; + int mp = DMP; + int sp = DSP; + bool dau = false; + string output = null; + + if (HasArgument("-interactive", arguments)) + { + string input; + ConsoleUtilities.WriteWrappedLine("Texture packer interactive mode triggered. Type \"q\" at any time to exit."); + ConsoleUtilities.WriteWrappedLine("Please enter path of folder containing the textures to be packed."); + input = Console.ReadLine(); + if (input == "q") return; + path = input.Replace("\"", ""); + + ConsoleUtilities.WriteWrappedLine("Please enter output path of file name."); + input = Console.ReadLine(); + if (input == "q") return; + output = input.Replace("\"", ""); + + do + { + ConsoleUtilities.WriteWrappedLine("Please enter a valid starting power of two for the lengths of the resulting texture atlas. Leave blank for default of " + sp + "."); + input = Console.ReadLine(); + if (input == "q") return; + if (input.Length == 0) break; + } while (!int.TryParse(input, out sp)); + + do + { + ConsoleUtilities.WriteWrappedLine("Please enter a valid maximum power by two of the lengths of the resulting texture atlas. Leave blank for default of " + mp + "."); + input = Console.ReadLine(); + if (input == "q") return; + if (input.Length == 0) break; + } while (!int.TryParse(input, out mp)); + + } + else + { + int indexOfInputArg = IndexOfArgument("-i", arguments); + if (indexOfInputArg + 1 >= arguments.Length) throw new ArgumentException("-i is not followed by input path."); + path = arguments[1 + IndexOfArgument("-i", arguments)]; + if (HasArgument("-mp", arguments)) + { + int.TryParse(arguments[IndexOfArgument("-mp", arguments) + 1], out mp); + } + if (HasArgument("-sp", arguments)) + { + int.TryParse(arguments[IndexOfArgument("-sp", arguments) + 1], out sp); + } + int indexOfOutputArg = IndexOfArgument("-o", arguments); + if (indexOfOutputArg + 1 >= arguments.Length) throw new ArgumentException("-o is not followed by input path."); + output = arguments[IndexOfArgument("-o", arguments) + 1]; + if (HasArgument("-dau", arguments)) dau = true; + } + + texturePacker = new TexturePacker(path, mp, sp); + ConsoleUtilities.WriteWrappedLine("Calculated minimum texture size: " + texturePacker.TextureLength + "x" + texturePacker.TextureLength + " with a total of " + texturePacker.TexturesFound + " textures."); + + try + { + texturePacker.Build(!dau); + } + catch (InvalidOperationException e) + { + throw new ArgumentException(e.Message); + } + + for (int i = 0; i < arguments.Length; i++) + { + if (arguments[i] == "-9p") + { + if (i + 1 >= arguments.Length) throw new ArgumentException("-9p is not followed by proper specifiers for a 9Patch (format: \"-9p textureName,a,b,c,d\" where a, b, c, and d are integers definining the border regions for the 9patch.)"); + string[] nPatchArgs = arguments[i + 1].Split(','); + try + { + texturePacker.SetNinePatch(nPatchArgs[0], int.Parse(nPatchArgs[1]), int.Parse(nPatchArgs[2]), int.Parse(nPatchArgs[3]), int.Parse(nPatchArgs[4])); + } + catch (FormatException) + { + throw new ArgumentException("-9p argument parameters must be in the format \"-9p textureName,a,b,c,d\" where a, b, c, and d are integers definining the border regions for the 9patch."); + } + } + } + texturePacker.Save(Path.GetDirectoryName(output), Path.GetFileName(output)); + Console.WriteLine("Complete. Final texture size: " + texturePacker.TextureLength + "x" + texturePacker.TextureLength + "."); + } + } +} diff --git a/RecrownedGTK.Tools/Tools.cs b/RecrownedGTK.Tools/Tools.cs new file mode 100644 index 0000000..bdd9c30 --- /dev/null +++ b/RecrownedGTK.Tools/Tools.cs @@ -0,0 +1,47 @@ +using RecrownedGTK.Tools.CommandProcessor; +using RecrownedGTK.Tools.CommandProcessor.Commands; +using RecrownedGTK.Tools.NinePatchTools; +using RecrownedGTK.Tools.TextureAtlasTools; +using System; +using System.Reflection; +using System.Text; + +namespace RecrownedGTK.Tools +{ + internal class Tools + { + static void Main(string[] args) + { + CommandEngine ce = new CommandEngine(); + ConsoleUtilities.WriteWrappedLine("Recrowned Athenaeum Console Tools version " + Assembly.GetExecutingAssembly().GetName().Version.ToString()); + ConsoleUtilities.WriteWrappedLine("Type \"help\" for help."); + + ce.commands.Add(new HelpCommand(ce)); + ce.commands.Add(new TexturePackerCommand()); + ce.commands.Add(new StopCommand(ce)); + ce.commands.Add(new ClearConsoleCommand()); + ce.commands.Add(new NinePatchCommand()); + + if (args.Length > 0) + { + ConsoleUtilities.WriteWrappedLine("Executing as one time use."); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < args.Length; i++) sb.Append(args[i] + ' '); + string commandAndArgs = sb.ToString().TrimEnd(); + try + { + ConsoleUtilities.WriteWrappedLine("Command and argument received: " + commandAndArgs); + ce.Process(commandAndArgs); + } + catch (ArgumentException e) + { + ConsoleUtilities.WriteWrappedLine("An error has occurred: " + e.Message); + } + } + else + { + ce.Run(); + } + } + } +} diff --git a/RecrownedGTK.code-workspace b/RecrownedGTK.code-workspace new file mode 100644 index 0000000..3e70edd --- /dev/null +++ b/RecrownedGTK.code-workspace @@ -0,0 +1,4 @@ +{ + "folders": [], + "settings": {} +} \ No newline at end of file diff --git a/RecrownedGTK/Assets/AssetManager.cs b/RecrownedGTK/Assets/AssetManager.cs new file mode 100644 index 0000000..c674c97 --- /dev/null +++ b/RecrownedGTK/Assets/AssetManager.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; + +namespace RecrownedGTK.Assets +{ + /// + /// Wrapper for the content manager that helps with controlling it by adding automated multithreaded content loading. + /// + public class AssetManager + { + Thread thread; + readonly AssetManager contentManager; + readonly Queue queue; + Dictionary assets; + /// + /// Path modifiers to change the path in which the content manager looks to load a file. Used for better organizing things while not needing to type entire path. + /// + private readonly Dictionary contentPathModifier; + /// + /// Used when no path modifier is defined for that specific type. + /// + public IAssetPathResolver normalPathModifier = new NormalAssetPathResolver(); + volatile float progress; + volatile bool running; + + /// + /// Whether or not the queue is empty and all content is loaded. + /// + public bool Done { get { return !running && queue.Count == 0; } } + + /// + /// The progress of the loading. 1 is complete while 0 is incomplete. + /// + public float Progress { get { return progress; } } + + /// + /// Wraps the . + /// + /// The manager to wrap. + public AssetManager() + { + assets = new Dictionary(); + queue = new Queue(); + contentPathModifier = new Dictionary(); + } + /// + /// Adds a to this handler. + /// + /// + /// + public void AddContentPathResolver(Type assetType, IAssetPathResolver contentResolver) { + contentPathModifier.Add(assetType, contentResolver); + } + /// + /// Removes the for the key. + /// + /// + public void RemoveContentResolver(Type assetType) { + contentPathModifier.Remove(assetType); + } + private void Load(string assetName, Type type, bool usePathModifier) + { + Debug.WriteLine("Loading asset: " + assetName); + string path = assetName; + if (usePathModifier) + { + IAssetPathResolver handler; + if (contentPathModifier.ContainsKey(type)) + { + handler = contentPathModifier[type]; + } + else + { + handler = normalPathModifier; + } + path = handler.Modify(assetName); + } + assets.Add(assetName, contentManager.Load(path)); + + } + + /// + /// Gets the requested asset. + /// + /// The type of the asset for an alternative way to cast. + /// The name of the asset. + /// The asset casted to the type given with T. + public T Get(string assetName) + { + lock (queue) + { + return (T)assets[assetName]; + } + } + + /// + /// Queues an asset to be loaded. + /// + /// The type of the asset to be queued. + /// Name of asset to look for. + /// Whether or not to use the path modifiers. + public void Queue(string assetName, bool usePathModifier = true) + { + lock (queue) + { + if (!assets.ContainsKey(assetName)) + { + queue.Enqueue(new ContentData(assetName, typeof(T), usePathModifier)); + Debug.WriteLine("Queued asset: " + assetName); + } + else + { + throw new InvalidOperationException("Did not queue asset due to asset with same name being loaded: " + assetName); + } + } + } + + /// + /// Called whenever a batch of assets should be loaded from the queue. Safe to call once every frame. + /// + public void Update() + { + if (queue.Count > 0 && (thread == null || !thread.IsAlive)) + { + thread = new Thread(LoadBatch); + thread.Start(); + } + } + + private void LoadBatch() + { + running = true; + int totalTasks = queue.Count; + int tasksCompleted = 0; + while (queue.Count != 0) + { + lock (queue) + { + ContentData content = queue.Dequeue(); + Load(content.assetName, content.type, content.usePathModifier); + tasksCompleted++; + progress = (float)tasksCompleted / totalTasks; + } + } + running = false; + } + /// + /// Removes the asset from the list of assets in the system. + /// Cannot remove from queue. + /// + /// the string name used to load the asset + public void Remove(string name) + { + lock (queue) + { + if (assets.ContainsKey(name)) + { + if (assets[name] is IDisposable) + { + ((IDisposable)assets[name]).Dispose(); + } + assets.Remove(name); + } + } + } + + /// + /// Clears the queue. + /// + public void ClearQueue() + { + lock (queue) + { + queue.Clear(); + } + } + + /// + /// Unloads everything from both queue and loaded list while properly disposing of the assets loaded. + /// + public void UnloadAll() + { + lock (queue) + { + contentManager.Unload(); + assets.Clear(); + ClearQueue(); + Debug.WriteLine("Unloaded all assets."); + } + } + private struct ContentData + { + internal Type type; + internal string assetName; + internal bool usePathModifier; + + public ContentData(string assetName, Type type, bool usePathModifier) + { + this.type = type; + this.assetName = assetName; + this.usePathModifier = usePathModifier; + } + } + } +} diff --git a/RecrownedGTK/Assets/IAssetPathResolver.cs b/RecrownedGTK/Assets/IAssetPathResolver.cs new file mode 100644 index 0000000..88cb2a5 --- /dev/null +++ b/RecrownedGTK/Assets/IAssetPathResolver.cs @@ -0,0 +1,15 @@ +namespace RecrownedGTK.Assets +{ + /// + /// Modifies the given path based on a name. Used to simplify long paths for the + /// + public interface IAssetPathResolver + { + /// + /// Returns the complete path with the content folder as root. + /// + /// Is the asset's name + /// + string Modify(string contentPath); + } +} diff --git a/RecrownedGTK/Assets/NormalContentResolver.cs b/RecrownedGTK/Assets/NormalContentResolver.cs new file mode 100644 index 0000000..6741cbc --- /dev/null +++ b/RecrownedGTK/Assets/NormalContentResolver.cs @@ -0,0 +1,18 @@ +namespace RecrownedGTK.Assets +{ + /// + /// A resolver that does nothing. Used for looking in the root by default. + /// + public class NormalAssetPathResolver : IAssetPathResolver + { + /// + /// Passes the path through without modification as this is the normal content resolver and is meant to just pass things on. + /// + /// The path to modify. + /// + public string Modify(string contentPath) + { + return contentPath; + } + } +} diff --git a/RecrownedGTK/Data/NinePatchData.cs b/RecrownedGTK/Data/NinePatchData.cs new file mode 100644 index 0000000..e48b69e --- /dev/null +++ b/RecrownedGTK/Data/NinePatchData.cs @@ -0,0 +1,35 @@ +namespace RecrownedGTK.Data +{ + /// + /// Represents a data structure for 9patches. + /// + public class NinePatchData + { + /// + /// Name of texture associated with patch. May be null in the case of being apart of a + /// + public string textureName; + + /// + /// the boundaries of the patch. + /// + public int left, right, bottom, top; + + /// + /// Constructs patch. + /// + /// Name of the texture. May be null. + /// Left bound. + /// Right bound. + /// Bottom bound. + /// Top bound. + public NinePatchData(string textureName, int left, int right, int bottom, int Top) + { + this.textureName = textureName; + this.left = left; + this.right = right; + this.bottom = bottom; + this.top = Top; + } + } +} diff --git a/RecrownedGTK/Data/SkinData.cs b/RecrownedGTK/Data/SkinData.cs new file mode 100644 index 0000000..e2630f9 --- /dev/null +++ b/RecrownedGTK/Data/SkinData.cs @@ -0,0 +1,115 @@ +using System; +using RecrownedGTK.Graphics.UI.SkinSystem.Definitions; +using OpenTK.Graphics; +using RecrownedGTK.Types; + +namespace RecrownedGTK.Data +{ + /// + /// Data transfer object for game skins. + /// + public class SkinData + { + /// + /// Holds metadata. + /// + public class Metadata + { + /// + /// Author name. + /// + public string author; + + /// + /// Description of skin. + /// + public string description; + + /// + /// Name of skin. + /// + public string skinName; + } + + /// + /// The metadata for the skin file. + /// + public Metadata metadata; + + /// + /// The name of the atlas with extension. + /// + public string nameOfTextureAtlas; + + /// + /// Name of the region or file designating the cursor. If there is an extension, will check for file first then texture atlas. Otherwise, will just check region. + /// + public string cursorTextureName; + + /// + /// The color data containing the name of the color, and red, green, and blue values for the color. + /// + public ColorData[] colors; + + /// + /// The skin definitions containing a name for the definition, and the definition itself. + /// + public DefinitionData[] definitions; + + /// + /// Color data for data transfer. + /// + public struct ColorData + { + /// + /// Name of color to be referenced by. + /// + public string name; + + /// + /// RGBA data of this color. + /// + public byte r, g, b, a; + + /// + /// Sets values for data. + /// + /// the name to be referenced by. + /// The color value represents. + public ColorData(string name, Color4 color) + { + this.name = name; + r = color.GetRedAsByte(); + g = color.GetGreenAsByte(); + b = color.GetBlueAsByte(); + a = color.GetAlphaAsByte(); + } + } + + /// + /// Definition data for data transfer. + /// + public struct DefinitionData + { + /// + /// Name of definition to be referenced by. + /// + public string name; + /// + /// The skin definition data. + /// + public SkinDefinitionData skin; + + /// + /// Sets values for data. + /// + /// The name to be referenced by. + /// The skin data. + public DefinitionData(string name, SkinDefinitionData skinDefinitionData) + { + this.name = name; + skin = skinDefinitionData; + } + } + } +} diff --git a/RecrownedGTK/Data/TextureAtlasData.cs b/RecrownedGTK/Data/TextureAtlasData.cs new file mode 100644 index 0000000..cf83a75 --- /dev/null +++ b/RecrownedGTK/Data/TextureAtlasData.cs @@ -0,0 +1,83 @@ +using RecrownedGTK.Types; +namespace RecrownedGTK.Data +{ + /// + /// Data transfer object for a texture atlas. + /// + public class TextureAtlasData + { + /// + /// Contains the regions of the texture atlas. + /// + public AtlasRegionData[] regions; + /// + /// The name of the file. + /// + public string textureName; + + /// + /// Creates the atlas given the regions and the file name of the texture file to be used. + /// + /// + /// + public TextureAtlasData(string textureName, AtlasRegionData[] regions) + { + this.regions = regions; + this.textureName = textureName; + } + + /// + /// Data object that contains information about the region ninepatch situation of a given region in an atlas. + /// + public struct AtlasRegionData + { + /// + /// Name of the region for referencial purposes. + /// + public string name; + /// + /// The location of the patch is designated by this rectangle. + /// + public Rectangle bounds; + /// + /// The ninepatch information. + /// + public NinePatchData ninePatchData; + + /// + /// Sets position in atlas for convenience. + /// + /// X coordinate of the position in the patch. + /// Y coordinate of the position in the patch. + public void SetPosition(int x, int y) + { + bounds.X = x; + bounds.Y = y; + } + + /// + /// Sets the dimensions of the region on the atlas for convenience. + /// + /// Width of the region. + /// Height of the region. + public void SetSize(int width, int height) + { + bounds.Width = width; + bounds.Height = height; + } + + /// + /// Sets both the coordinates and dimensions of the region on the atlas for convenience. + /// + /// X coordinate of the position in the patch. + /// Y coordinate of the position in the patch. + /// Width of the region. + /// Height of the region. + public void SetBounds(int x, int y, int width, int height) + { + SetPosition(x, y); + SetSize(width, height); + } + } + } +} diff --git a/RecrownedGTK/Game.cs b/RecrownedGTK/Game.cs new file mode 100644 index 0000000..58202e4 --- /dev/null +++ b/RecrownedGTK/Game.cs @@ -0,0 +1,7 @@ +namespace RecrownedGTK { + public class Game { + public Game() { + //TODO Implement interface that calls the users created game files. + } + } +} \ No newline at end of file diff --git a/RecrownedGTK/Graphics/NinePatch.cs b/RecrownedGTK/Graphics/NinePatch.cs new file mode 100644 index 0000000..a6cbb17 --- /dev/null +++ b/RecrownedGTK/Graphics/NinePatch.cs @@ -0,0 +1,130 @@ +using RecrownedGTK.Graphics.Render; +using System; +using OpenTK; +using OpenTK.Graphics; + +namespace RecrownedGTK.Graphics +{ + /// + /// An object that represents a ninepatch. + /// + public class NinePatch : ISpecialDrawable + { + /// + /// Dimensions in ninepatch. May also represent position in texture atlas. + /// + public Rectangle textureRegion; + readonly Texture2D texture; + readonly int left, right, bottom, top; + + Rectangle[] sourcePatches; + + /// + /// A nine patch object. + /// + /// Texture used for the nine patch. Dimensions must be greater than their sum border counter parts. If used as part of texture atlas, the texture should be the texture of the entire atlas. + /// Left side. + /// Right side. + /// Bottom side. + /// Top side. + /// The dimensions and potentially the coordinates of the rectangle on an atlas. If left to default of null, will only be set to texture bounds. + public NinePatch(Texture2D texture, int left, int right, int bottom, int top, Rectangle? textureBounds = null) + { + this.texture = texture; + if (textureBounds.HasValue) textureRegion = textureBounds.Value; else textureRegion = texture.Bounds; + if (left + right >= textureRegion.Width) throw new ArgumentOutOfRangeException("left and right values cannot be greater than or equal to the width of region. Left value is " + left + " and right value is " + right + ". Bounds of texture are: " + textureRegion); + if (bottom + top >= textureRegion.Height) throw new ArgumentOutOfRangeException("Bottom and top values cannot be greater than or equal to the width of region. Bottom value is " + bottom + " and top value is " + top + "."); + this.left = left; + this.right = right; + this.bottom = bottom; + this.top = top; + + Rectangle[] patches = +{ + new Rectangle(0, 0, left, bottom), + new Rectangle(left, 0, textureRegion.Width - left - right, bottom), + new Rectangle(textureRegion.Width - right, 0, right, bottom), + new Rectangle(0, bottom, left, textureRegion.Height - top - bottom), + new Rectangle(left, bottom, textureRegion.Width - left - right, textureRegion.Height - bottom - top), + new Rectangle(textureRegion.Width - right, bottom, right, textureRegion.Height - top - bottom), + new Rectangle(0, textureRegion.Height - top, left, top), + new Rectangle(left, textureRegion.Height - top, textureRegion.Width - left - right, top), + new Rectangle(textureRegion.Width - right, textureRegion.Height - top, right, top), + }; + + for (int i = 0; i < patches.Length; i++) + { + patches[i].X += textureRegion.X; + patches[i].Y += textureRegion.Y; + } + + sourcePatches = patches; + + } + + private Rectangle[] GenenerateDestinationRectangles(int width, int height) + { + Rectangle[] patches = + { + new Rectangle(0, 0, left, bottom), + new Rectangle(left, 0, width - left - right, bottom), + new Rectangle(width -right, 0, right, bottom), + new Rectangle(0, bottom, left, height - bottom - top), + new Rectangle(left, bottom, width - left - right, height - top - bottom), + new Rectangle(width - right, bottom, right, height - top - bottom), + new Rectangle(0, height - top, left, top), + new Rectangle(left, height - top, width - left - right, top), + new Rectangle(width - right, height - top, right, top), + }; + return patches; + } + + /// + /// Draws the ninepatch. + /// + /// Batch to use. + /// The color of the patch. + /// Where to the patch. + public void Draw(ConsistentSpriteBatch spriteBatch, Color4 color, Rectangle destination) + { + try + { + spriteBatch.End(); + } catch (InvalidOperationException) + { + throw new InvalidOperationException("Begin must be called to draw a nine patch!"); + } + + spriteBatch.BeginWithoutSaving(samplerState: SamplerState.PointClamp); + + Rectangle[] destinations = GenenerateDestinationRectangles(destination.Width, destination.Height); + for (int i = 0; i < destinations.Length; i++) + { + destinations[i].X += destination.X; + destinations[i].Y += destination.Y; + spriteBatch.Draw(texture, destinations[i], sourcePatches[i], color); + } + spriteBatch.End(); + + spriteBatch.Begin(); + } + + /// + /// + /// Spritebatch to use. + /// The destination to draw the patch. + /// The tint for each patch. + /// Not considered for 9patches. + /// Not considered for 9patches. + public void Draw(ConsistentSpriteBatch spriteBatch, Rectangle destination, Color4 color, float rotation = 0, Vector2 origin = default(Vector2)) + { + if (rotation != 0) throw new NotImplementedException("Ninepatches can't be rotated."); + if (origin != default(Vector2)) + { + destination.X -= (int)origin.X; + destination.Y -= (int)origin.Y; + } + Draw(spriteBatch, color, destination); + } + } +} diff --git a/RecrownedGTK/Graphics/Render/Camera.cs b/RecrownedGTK/Graphics/Render/Camera.cs new file mode 100644 index 0000000..af0c070 --- /dev/null +++ b/RecrownedGTK/Graphics/Render/Camera.cs @@ -0,0 +1,90 @@ +using OpenTK; +namespace RecrownedGTK.Graphics.Render +{ + /// + /// A generic camera. Functions in 3D. + /// + public class BasicCamera + { + /// + /// The scale for the world. + /// + public float worldScale = 1f; + + /// + /// Current position in the world. + /// + public Vector3 position; + + /// + /// The place the 3D camera is looking at. + /// + public Vector3 lookAt; + + /// + /// The direction up is for the camera. + /// + public Vector3 upDirection; + + /// + /// The transform matrix representing the world (rotation and translations of the original world). + /// + public Matrix3 worldMatrix; + + /// + /// The view matrix that describes where the camera looks. + /// + public Matrix3 ViewMatrix { get; protected set; } + + /// + /// The projection matrix. + /// + public Matrix3 projectionMatrix; + + /// + /// A basic effect that will be updated with the correct matrice information everytime is called. Can be null and thus will not be used. + /// + public BasicEffect basicEffect; + + /// + /// Constructs 3D camera with an orthographic projection matrix with dimensions of graphics devices viewport. All changes to matrices should have apply called after changes. + /// + /// A basic effect that will be updated with the correct matrice information everytime is called. Can be null and thus will not be used. + public BasicCamera(BasicEffect basicEffect = null) + { + worldMatrix = Matrix.Identity; + lookAt = Vector3.Forward; + upDirection = Vector3.Up; + + projectionMatrix = Matrix.Identity; + this.basicEffect = basicEffect; + Apply(); + } + + /// + /// Applies the changes to the fields and properties of the camera. + /// + public virtual void Apply() + { + ViewMatrix = Matrix.CreateLookAt(position, lookAt, upDirection); + worldMatrix *= Matrix.CreateScale(worldScale); + + if (basicEffect != null) + { + basicEffect.World = worldMatrix; + basicEffect.Projection = projectionMatrix; + basicEffect.View = ViewMatrix; + } + } + + /// + /// Moves camera by the given amount. + /// + /// A that contains how much in each direction to move. + public void MoveCamera(Vector3 move) + { + position += move; + Apply(); + } + } +} \ No newline at end of file diff --git a/RecrownedGTK/Graphics/Render/Camera2D.cs b/RecrownedGTK/Graphics/Render/Camera2D.cs new file mode 100644 index 0000000..91e2309 --- /dev/null +++ b/RecrownedGTK/Graphics/Render/Camera2D.cs @@ -0,0 +1,84 @@ +using OpenTK; +using System; + +namespace RecrownedGTK.Graphics.Render +{ + /// + /// A virtual 2D camera that wraps the normal . Default projection is orthographic. + /// + public class Camera2D : BasicCamera + { + /// + /// The width of the view of the camera. + /// + public int viewWidth; + + /// + /// The height of the view of the camera. + /// + public int viewHeight; + + /// + /// The 2D position. + /// + public Vector2 Position { get { return new Vector2(position.X, position.Y); } set { position.X = value.X; position.Y = value.Y; } } + + /// + /// Places camera in the center given the corner position. + /// + public Vector2 ConrnerPosition { set { position.X = value.X + viewWidth / 2f; position.Y = value.Y + viewHeight / 2f; } } + + /// + /// A 2D camera from the generic . + /// + /// Width of camera view. + /// Height of camera view. + /// A basic effect that will be updated with the correct matrice information everytime is called. Can be null and thus will not be used. + public Camera2D(int width, int height, BasicEffect basicEffect = null) : base(basicEffect) + { + this.viewWidth = width; + this.viewHeight = height; + upDirection = Vector3.Down; + ConrnerPosition = new Vector2(0, 0); + Apply(); + } + + /// + /// Applies for 2D. + /// Sets where the camera is looking for the view matrix to the position of the camera. + /// + public override void Apply() + { + projectionMatrix = Matrix.CreateOrthographic(viewWidth, viewHeight, 0, 1); + position.Z = 0; + lookAt = new Vector3(Position, 1f); + base.Apply(); + } + + /// + /// Lerps to the given position. + /// + /// The multiplier for difference in distance. + /// The target position to lerp to. + /// Time between this frame and the previous frame. + public void LinearInterpolationToPosition(float alpha, Vector2 targetPosition, float delta) + { + if (alpha <= 0 && alpha > 1f) throw new ArgumentException("Alpha can't be greater than 1f, less than or equal to 0."); + + Vector2 distance = targetPosition - Position; + distance *= (float)(1.0f - Math.Pow(1 - alpha, delta / 0.02f)); + + Position += distance; + } + + /// + /// Moves the camera. + /// Apply needs to be called. + /// + /// Magnitude of how much to move per axis. + public void MoveCamera(Vector2 move) + { + Position += move; + } + } +} diff --git a/RecrownedGTK/Graphics/Render/ConsistentSpriteBatch.cs b/RecrownedGTK/Graphics/Render/ConsistentSpriteBatch.cs new file mode 100644 index 0000000..8e3f8e9 --- /dev/null +++ b/RecrownedGTK/Graphics/Render/ConsistentSpriteBatch.cs @@ -0,0 +1,104 @@ +namespace RecrownedGTK.Graphics.Render +{ + /// + /// A that keeps it's settings through begin and end unless manually changed either by the or through changing the fields. Note that changing the fields while the batch has begun will not take effect until the next time the batch is started. + /// Casting this as a will not persist the configuration and will call the normal . + /// + public class ConsistentSpriteBatch : SpriteBatch + { + /// + /// How to blend the colors. Uses 's default if not set before or during call. + /// Changes will only take effect on call. + /// + public BlendState blendState; + + /// + /// The state of sampler to use for the spritebatch. Uses 's default if not set before or during call. + /// Changes will only take effect on call. + /// + public SamplerState samplerState; + + /// + /// The state of the depth-stencil buffer. Uses 's defaultdefault if not set before or during call. + /// Changes will only take effect on call. + /// + public DepthStencilState depthStencilState; + + /// + /// The state of rasterizer to use. Uses 's default if not set before or during call. + /// Changes will only take effect on call. + /// + public RasterizerState rasterizerState; + + /// + /// An effect to apply to the batch. Uses 's default if not set before or during call. + /// Changes will only take effect on call. + /// + public Effect effect; + + /// + /// A matrix to be applied to transform the sprites geometry. An identity matrix is used if not provided before or during call. + /// Changes will only take effect on call. + /// + public Matrix? transformMatrix; + + /// + /// Creates a consistent sprite batch with default values. + /// + /// The graphics device to use to create a . + public ConsistentSpriteBatch(GraphicsDevice graphicsDevice) : base(graphicsDevice) + { + } + + /// + /// Begins the consistent sprite batch. The configuration passed to this function is saved for later begin calls. + /// + /// Defines the spritebatch's method of sorting the items in each batch. Uses 's default. + /// How to blend the colors. Uses 's default if not set and field is also not set. + /// The state of sampler to use for the spritebatch. Uses 's default if not set and field is also not set. + /// What type of rasterization to use. Uses 's default if not set and field is also not set. + /// What type of rasterization to use. Uses 's default if not set and field is also not set. + /// An effect to apply to the batch. Uses 's default if not set and field is also not set. + /// A matrix to be applied to transform the sprites geometry. Uses 's default if not set and field is also not set. + public new void Begin(SpriteSortMode sortMode = SpriteSortMode.Deferred, BlendState blendState = null, SamplerState samplerState = null, DepthStencilState depthStencilState = null, RasterizerState rasterizerState = null, Effect effect = null, Matrix? transformMatrix = null) + { + if (blendState != null) this.blendState = blendState; + if (samplerState != null) this.samplerState = samplerState; + if (depthStencilState != null) this.depthStencilState = depthStencilState; + if (rasterizerState != null) this.rasterizerState = rasterizerState; + if (effect != null) this.effect = effect; + if (transformMatrix != null) this.transformMatrix = transformMatrix; + + base.Begin( + sortMode: sortMode, + blendState: this.blendState, + samplerState: this.samplerState, + depthStencilState: this.depthStencilState, + rasterizerState: this.rasterizerState, + effect: this.effect, + transformMatrix: this.transformMatrix); + } + + /// + /// Begins the consistent sprite batch without saving the configuration. Useful for one time changes to one portion of the configuration. + /// + /// Defines the spritebatch's method of sorting the items in each batch. Uses 's default. + /// How to blend the colors. Uses 's default if not set and field is also not set. + /// The state of sampler to use for the spritebatch. Uses 's default if not set and field is also not set. + /// What type of rasterization to use. Uses 's default if not set and field is also not set. + /// What type of rasterization to use. Uses 's default if not set and field is also not set. + /// An effect to apply to the batch. Uses 's default if not set and field is also not set. + /// A matrix to be applied to transform the sprites geometry. Uses 's default if not set and field is also not set. + public void BeginWithoutSaving(SpriteSortMode sortMode = SpriteSortMode.Deferred, BlendState blendState = null, SamplerState samplerState = null, DepthStencilState depthStencilState = null, RasterizerState rasterizerState = null, Effect effect = null, Matrix? transformMatrix = null) + { + base.Begin( + sortMode: sortMode, + blendState: blendState ?? this.blendState, + samplerState: samplerState ?? this.samplerState, + depthStencilState: depthStencilState ?? this.depthStencilState, + rasterizerState: rasterizerState ?? this.rasterizerState, + effect: effect ?? this.effect, + transformMatrix: transformMatrix ?? this.transformMatrix); + } + } +} diff --git a/RecrownedGTK/Graphics/Render/PrimitiveBatch.cs b/RecrownedGTK/Graphics/Render/PrimitiveBatch.cs new file mode 100644 index 0000000..6acce73 --- /dev/null +++ b/RecrownedGTK/Graphics/Render/PrimitiveBatch.cs @@ -0,0 +1,158 @@ +using OpenTK.Graphics; +using OpenTK; +using System; + +namespace RecrownedGTK.Graphics.Render +{ + /// + /// A batch used to draw primitive shapes by batching together vertices. + /// + public class PrimitiveBatch : IDisposable + { + VertexPositionColor[] vertices; + int bufferPosition; + Effect effect; + PrimitiveType primitiveType; + private int verticesPerPrimitive; + + /// + /// Amount of primitives. + /// + public int primitiveCount; + + GraphicsDevice graphicsDevice; + bool began; + bool disposed; + + BasicEffect basicEffect; + /// + /// Creates a batch used to draw primitives. + /// + /// The graphics device used to draw the primitives. + /// The amount of vertices every batch can hold before flushing. Default is 450. Should be changed to be the most optimal number if possible to prevent unnecessary resizing. Especially if using strip primitive types. + public PrimitiveBatch(GraphicsDevice graphicsDevice, int verticesPerBatch = 500) + { + vertices = new VertexPositionColor[verticesPerBatch + 1]; + this.graphicsDevice = graphicsDevice; + basicEffect = new BasicEffect(graphicsDevice); + basicEffect.VertexColorEnabled = true; + basicEffect.Projection = Matrix.CreateOrthographicOffCenter(0, graphicsDevice.Viewport.Width, graphicsDevice.Viewport.Height, 0, 0, 1); + basicEffect.World = Matrix.Identity; + basicEffect.View = Matrix.CreateLookAt(Vector3.Zero, Vector3.Forward, Vector3.Up); + } + + /// + /// Starts the batch. Batch cannot be started twice. + /// + /// The type of primitive this batch would be drawing. + /// Effect to use to render the primitives. Default will use a with parameters set up during creation. + public void Begin(PrimitiveType primitiveType, Effect effect = null) + { + if (began) throw new InvalidOperationException("Begin is being called twice before being ended."); + if (disposed) throw new ObjectDisposedException(this.GetType().Name); + this.primitiveType = primitiveType; + switch (primitiveType) + { + case PrimitiveType.LineList: verticesPerPrimitive = 2; break; + case PrimitiveType.TriangleList: verticesPerPrimitive = 3; break; + case PrimitiveType.LineStrip: verticesPerPrimitive = 1; break; + case PrimitiveType.TriangleStrip: verticesPerPrimitive = 3; break; + } + this.effect = effect ?? basicEffect; + began = true; + } + + /// + /// Ends the batch. Begin needs to be called before end. + /// + public void End() + { + if (disposed) throw new ObjectDisposedException(this.GetType().Name); + if (!began) throw new InvalidOperationException("Begin must be called before ending."); + Flush(); + began = false; + } + + /// + /// Adds a vertex position for the primitive being drawn. The batch needs to have beens started before this. + /// + /// The vector that represents the vertex. + /// The color of that vertex. + public void AddVertex(Vector2 vertex, Color4 color) + { + if (!began) throw new InvalidOperationException("Begin needs to be called before adding vertex."); + if (disposed) throw new ObjectDisposedException(this.GetType().Name); + if (primitiveType != PrimitiveType.LineStrip && primitiveType != PrimitiveType.TriangleStrip) + { + if (bufferPosition + verticesPerPrimitive >= vertices.Length) + { + if ((bufferPosition % vertices.Length == 0)) + { + Flush(); + } + else + { + throw new InvalidOperationException("Buffer size doesn't match with primitive type."); + } + } + } + else if (bufferPosition + verticesPerPrimitive > vertices.Length) + { + throw new InvalidOperationException("Buffer size isn't large enough."); + } + + vertices[bufferPosition] = new VertexPositionColor(new Vector3(vertex, 0), color); + bufferPosition++; + } + + /// + /// Flushes the batch. Automatically called if required if using or . Otherwise, manual flushing is required. + /// is used if not zero. Is reset to zero every flush. + /// + public void Flush() + { + if (!began) throw new InvalidOperationException("Begin needs to be called before flushing."); + if (disposed) throw new ObjectDisposedException(this.GetType().Name); + if (bufferPosition == 0) return; + if (primitiveCount == 0) primitiveCount = bufferPosition / verticesPerPrimitive; + foreach (EffectPass effectPass in effect.CurrentTechnique.Passes) + { + effectPass.Apply(); + graphicsDevice.DrawUserPrimitives(primitiveType, vertices, 0, primitiveCount); + } + bufferPosition = 0; + primitiveCount = 0; + } + + /// + /// Disposes this. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Overridable dispose. + /// + /// True for when user called for this to be disposed. False otherwise. + public virtual void Dispose(bool disposing) + { + if (disposed) throw new ObjectDisposedException(this.GetType().Name); + disposed = true; + if (disposing && !disposed) + { + basicEffect.Dispose(); + } + } + + /// + /// Destructor. + /// + ~PrimitiveBatch() + { + Dispose(false); + } + } +} diff --git a/RecrownedGTK/Graphics/Render/RectangleRenderer.cs b/RecrownedGTK/Graphics/Render/RectangleRenderer.cs new file mode 100644 index 0000000..72484c0 --- /dev/null +++ b/RecrownedGTK/Graphics/Render/RectangleRenderer.cs @@ -0,0 +1,119 @@ +using OpenTK.Graphics; +using OpenTK; +using RecrownedGTK.Types; +using System; + +namespace RecrownedGTK.Graphics.Render +{ + /// + /// Renders rectangles using the . + /// + public class RectangleRenderer : IDisposable + { + private bool filled; + private bool disposed; + /// + /// The used. Needs to be disposed. + /// + private readonly PrimitiveBatch primitiveBatch; + + /// + /// Creates a rectangle renderer with the given . + /// + /// Graphics device to use. + public RectangleRenderer(GraphicsDevice graphicsDevice) + { + primitiveBatch = new PrimitiveBatch(graphicsDevice); + } + + /// + /// Disposes the rectangle renderer. + /// + public void Dispose() + { + Dispose(true); + } + + /// + /// Overridable for dispose. + /// + /// True when its a player calling the dispose. + public virtual void Dispose(bool disposing) + { + if (disposed) throw new ObjectDisposedException(GetType().Name); + if (disposing) + { + primitiveBatch.Dispose(); + } + disposed = true; + } + + /// + /// Begins a batch for rectangles. + /// + /// Whether or not this batch should be filled rectangles. + public void Begin(bool filled = false) + { + this.filled = filled; + primitiveBatch.Begin(filled ? PrimitiveType.TriangleStrip : PrimitiveType.LineStrip); + } + + /// + /// Ends the batch. + /// + public void End() + { + primitiveBatch.End(); + } + /// + /// Draws a basic rectangle given bottom left and top right. + /// + /// X coordinate of bottom left. + /// Y coordinate of bottom left. + /// Width of rectangle. + /// Height of rectangle. + /// Color of all vertices of this rectangle. + /// Rotation of rectangle. Default is 0 radians. + public void Draw(int x, int y, int width, int height, Color4 color, float rotation = 0) + { + primitiveBatch.primitiveCount = filled ? 3 : 4; + Vector2[] corners = new Vector2[4]; + corners[1] = new Vector2(width, 0); + corners[2] = new Vector2(width, height); + corners[3] = new Vector2(0, height); + + if (rotation != 0) + { + Matrix rotMat = Matrix.CreateRotationZ(rotation); + for (int i = 0; i < corners.Length; i++) + { + corners[i] = Vector2.Transform(corners[i], rotMat); + } + } + + for (int i = 0; i < corners.Length; i++) + { + corners[i].X += x; + corners[i].Y += y; + } + + primitiveBatch.AddVertex(corners[0], color); + primitiveBatch.AddVertex(corners[1], color); + primitiveBatch.AddVertex(corners[2], color); + primitiveBatch.AddVertex(corners[3], color); + primitiveBatch.AddVertex(corners[0], color); + + primitiveBatch.Flush(); + } + + /// + /// Draws the given rectangle. + /// + /// Uses the x, y and dimensions to draw a rectangle. + /// The color of the rectangle. + public void Draw(Rectangle rectangle, Color4 color) + { + Draw(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, color); + } + } +} diff --git a/RecrownedGTK/Graphics/Render/Shader/Shader.cs b/RecrownedGTK/Graphics/Render/Shader/Shader.cs new file mode 100644 index 0000000..6260138 --- /dev/null +++ b/RecrownedGTK/Graphics/Render/Shader/Shader.cs @@ -0,0 +1,80 @@ +using System; +using System.IO; +using System.Text; +using OpenTK.Graphics.OpenGL; +namespace RecrownedGTK.Graphics.Render.Shader { + public class Shader : IDisposable { + int handle; + public bool IsDisposed { + get; + private set; + } + public Shader(string vertexPath, string fragmentPath) { + IsDisposed = false; + + int vertShader = 0; + int fragShader = 0; + + string vertShaderSource; + string fragShaderSource; + + using (StreamReader stream = new StreamReader(vertexPath, Encoding.UTF8)) { + vertShaderSource = stream.ReadToEnd(); + } + using (StreamReader stream = new StreamReader(fragmentPath, Encoding.UTF8)) { + fragShaderSource = stream.ReadToEnd(); + } + + vertShader = GL.CreateShader(ShaderType.VertexShader); + GL.ShaderSource(vertShader, vertShaderSource); + fragShader = GL.CreateShader(ShaderType.FragmentShader); + GL.ShaderSource(fragShader, fragShaderSource); + + string log; + GL.CompileShader(vertShader); + if ((log = GL.GetShaderInfoLog(vertShader)) == "") { + throw new ArgumentException("Error while compiling vertex shader: " + log, "vertexPath"); + } + GL.CompileShader(fragShader); + if ((log = GL.GetShaderInfoLog(fragShader)) == "") { + throw new ArgumentException("Error while compiling fragment shader: " + log, "fragmentPath"); + } + handle = GL.CreateProgram(); + GL.AttachShader(handle, vertShader); + GL.AttachShader(handle, fragShader); + GL.LinkProgram(handle); + GL.DetachShader(handle, vertShader); + GL.DetachShader(handle, fragShader); + GL.DeleteShader(vertShader); + GL.DeleteShader(fragShader); + } + + public void Use() { + GL.UseProgram(handle); + } + + protected virtual void Dispose(bool disposing) { + if (IsDisposed) { + return; + } + + if (disposing) { + } + GL.DeleteProgram(handle); + IsDisposed = true; + } + + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~Shader() { + Dispose(false); + } + public static Shader CreateBasic2D() { + Shader shader = new Shader("Graphics/Render/Shader/default.vert", "Graphics/Render/Shader/default.frag"); + return shader; + } + } +} \ No newline at end of file diff --git a/RecrownedGTK/Graphics/Render/Shader/default.frag b/RecrownedGTK/Graphics/Render/Shader/default.frag new file mode 100644 index 0000000..55d4ec1 --- /dev/null +++ b/RecrownedGTK/Graphics/Render/Shader/default.frag @@ -0,0 +1,10 @@ +out vec4 outputColor; + +in vec2 texCoord; + +uniform sampler2D texture0; + +void main() +{ + outputColor = texture(texture0, texCoord); +} \ No newline at end of file diff --git a/RecrownedGTK/Graphics/Render/Shader/default.vert b/RecrownedGTK/Graphics/Render/Shader/default.vert new file mode 100644 index 0000000..0950890 --- /dev/null +++ b/RecrownedGTK/Graphics/Render/Shader/default.vert @@ -0,0 +1,8 @@ +#version 330 core +in vec3 aPosition; +in mat4 aTransform; + +void main() +{ + gl_Position = vec4(aPosition, 1) * aTransform; +} \ No newline at end of file diff --git a/RecrownedGTK/Graphics/Render/TextureBatch.cs b/RecrownedGTK/Graphics/Render/TextureBatch.cs new file mode 100644 index 0000000..caaab18 --- /dev/null +++ b/RecrownedGTK/Graphics/Render/TextureBatch.cs @@ -0,0 +1,17 @@ +using System; +namespace RecrownedGTK.Graphics.Render { + public class TextureBatch { + private bool begun; + public TextureBatch() { + //TODO Finish batch. + } + public void Begin() { + if (begun) throw new InvalidOperationException("This batch has already been started."); + begun = true; + } + public void End() { + if (!begun) throw new InvalidOperationException("The batch has not begun."); + begun = false; + } + } +} \ No newline at end of file diff --git a/RecrownedGTK/Graphics/Texture.cs b/RecrownedGTK/Graphics/Texture.cs new file mode 100644 index 0000000..9f7c08c --- /dev/null +++ b/RecrownedGTK/Graphics/Texture.cs @@ -0,0 +1,34 @@ +using System.Drawing; +using System.IO; + +namespace RecrownedGTK.Graphics { + public class Texture : IRectangleDrawable { + //TODO Complete a basic texture capable of being rendered by a batch. + byte[] textureData; + public byte[] ColorData { + get { + return textureData; + } + } + public Texture() { + } + + public Texture(byte[] textureData) { + this.textureData = textureData; + } + + public Texture(string path) { + LoadFromPNG(path); + } + + public void LoadFromPNG(string path) { + using (FileStream file = new FileStream(path, FileMode.Open)) { + using (Bitmap bitmap = new Bitmap(file)) { + ImageConverter converter = new ImageConverter(); + textureData = (byte[]) converter.ConvertTo(bitmap, typeof(byte[])); + } + } + + } + } +} \ No newline at end of file diff --git a/RecrownedGTK/Graphics/TextureAtlas.cs b/RecrownedGTK/Graphics/TextureAtlas.cs new file mode 100644 index 0000000..5f0dbb8 --- /dev/null +++ b/RecrownedGTK/Graphics/TextureAtlas.cs @@ -0,0 +1,236 @@ +using RecrownedGTK.Graphics.Render; +using System; +using System.Collections.Generic; +using System.Linq; +using OpenTK; +using OpenTK.Graphics; + +namespace RecrownedGTK.Graphics +{ + /// + /// Holds information about an image file that contains various textures in various regions in the file. + /// + public class TextureAtlas : IDisposable + { + private Texture2D texture; + private bool disposed; + private Dictionary dictionaryOfRegions = new Dictionary(); + + /// + /// Given a name, can return a . + /// + /// Name of to obtain. + /// based off name. + public Region this[string name] { get { if (name != null && dictionaryOfRegions.ContainsKey(name)) return dictionaryOfRegions[name]; else throw new KeyNotFoundException("Given key \"" + name + "\" does not exist."); } } + + /// + /// Creates a texture atlas with given main texture as well as an array of to represent locations of which textures reside within the atlas. Region names will be used to refer to the regions within the dictionary. + /// + /// The texture representing the overall atlas. + /// The sub regions that represent the individual textures. + public TextureAtlas(Texture2D texture, Region[] regions) + { + this.texture = texture; + foreach (Region region in regions) + { + dictionaryOfRegions.Add(region.name, region); + } + } + + /// + /// Creates a texture region given a dictionary of regions keyed to strings that can be used to refer to them. + /// + /// The texture representing the overall atlas. + /// + public TextureAtlas(Texture2D texture, Dictionary dictionaryOfRegions) + { + this.texture = texture; + this.dictionaryOfRegions = dictionaryOfRegions; + } + + /// + /// Draw the region given by a string in the atlas onto a destination rectangle. + /// + /// Name of region to draw. + /// SpriteBatch to be used. + /// The location to draw this region. + /// Color to use. + /// Rotation of texture drawn. + /// Origin used by rotation. + public void Draw(string name, ConsistentSpriteBatch batch, Rectangle destination, Color4 color = default(Color4), float rotation = 0, Vector2 origin = new Vector2()) + { + dictionaryOfRegions[name].Draw(batch, destination, color, rotation, origin); + } + + /// + /// Creates or obtains a previously created texture of a region. + /// + /// Name of region. + /// graphics device to be used to generate the texture. + /// The texture from the region. + public Texture2D ObtainRegionAsTexture(string name, GraphicsDevice graphicsDevice) + { + return dictionaryOfRegions[name].AsTexture2D(graphicsDevice); + } + + /// + /// Whether or not this atlas contains the given region name. + /// + /// The name of the region to check for. + /// True if this atlas does contain the region given by name. + public bool ContainsRegion(string regionName) + { + return dictionaryOfRegions.ContainsKey(regionName); + } + + /// + /// Disposes unmanaged resources for the texture atlas. + /// + public void Dispose() + { + Dispose(true); + } + + /// + /// Overridable disposal method. + /// + /// Only true if user calls + public virtual void Dispose(bool disposing) + { + disposed = true; + if (!disposed && disposing) + { + texture.Dispose(); + for (int i = 0; i < dictionaryOfRegions.Count; i++) + { + Region region = dictionaryOfRegions.ElementAt(i).Value; + + if (!region.Disposed) + { + dictionaryOfRegions.ElementAt(i).Value.Dispose(); + } + } + dictionaryOfRegions.Clear(); + } + } + + /// + /// Destructor. + /// + ~TextureAtlas() + { + Dispose(false); + } + + /// + /// A region of a . + /// + public class Region : ISpecialDrawable, IDisposable + { + /// + /// The name of the region. Mostly used to be refered to within the context of a . + /// + public readonly string name; + + /// + /// The location and dimensions of where the original texture resides on the texture representing the atlas. + /// + public readonly Rectangle sourceRectangle; + readonly NinePatch ninepatch; + Texture2D atlasTexture; + Texture2D regionTexture; + + /// + /// If region has already been disposed. + /// + public bool Disposed { get; private set; } + + /// + /// A specified region in a texture atlas. + /// + /// Name of region. + /// The location of the region on the atlas. + /// A definition for the region. + /// The texture that holds the image data for the atlas. + public Region(string name, Rectangle sourceRegion, NinePatch ninePatch, Texture2D atlasTexture) + { + this.atlasTexture = atlasTexture ?? throw new ArgumentNullException("Name parameters can be null."); + this.name = name ?? throw new ArgumentNullException("Name parameters can be null."); + sourceRectangle = sourceRegion; + ninepatch = ninePatch; + } + + /// + /// Draws the region. If ninepatch, rotation and origin are ignored. + /// + /// The batch to use. Should be began. + /// The destination rectangle to draw to. + /// The color to use. + /// Rotation of the final drawing. Ignored if is a 9patch. + /// The origin of the drawing. Ignored if is a 9patch. + public void Draw(ConsistentSpriteBatch batch, Rectangle destination, Color4 color, float rotation = 0, Vector2 origin = default(Vector2)) + { + if (Disposed) throw new ObjectDisposedException(GetType().Name); + + if (ninepatch != null) + { + ninepatch.Draw(batch, color, destination); + } + else + { + batch.Draw(atlasTexture, destination, sourceRectangle, color, rotation, origin, SpriteEffects.None, 0f); + } + } + + /// + /// Create or obtains a previously created texture of this region. + /// + /// The graphics device to use to create the texture. + /// The texture of the region. + public Texture2D AsTexture2D(GraphicsDevice graphicsDevice) + { + if (Disposed) throw new ObjectDisposedException(GetType().Name); + + if (regionTexture == null) + { + Color4[] data = new Color4[sourceRectangle.Width * sourceRectangle.Height]; + regionTexture = new Texture2D(graphicsDevice, sourceRectangle.Width, sourceRectangle.Height); + atlasTexture.GetData(0, sourceRectangle, data, 0, sourceRectangle.Width * sourceRectangle.Height); + regionTexture.SetData(data); + } + return regionTexture; + } + + /// + /// Call this to dispose. + /// + public void Dispose() + { + if (Disposed) throw new ObjectDisposedException(GetType().Name); + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Overridable dispose. + /// + /// Whether or not this was a user made call. + public virtual void Dispose(bool disposing) + { + if (disposing && !Disposed) + { + regionTexture?.Dispose(); + } + Disposed = true; + } + + /// + /// Destructor. + /// + ~Region() + { + Dispose(false); + } + } + } +} diff --git a/RecrownedGTK/Graphics/UI/BookSystem/Book.cs b/RecrownedGTK/Graphics/UI/BookSystem/Book.cs new file mode 100644 index 0000000..51b8e67 --- /dev/null +++ b/RecrownedGTK/Graphics/UI/BookSystem/Book.cs @@ -0,0 +1,211 @@ +using RecrownedGTK.Input; +using RecrownedGTK.Assets; +using RecrownedGTK.Graphics.Render; +using RecrownedGTK.Graphics.UI.SkinSystem; +using System; +using System.Collections.Generic; +using System.Linq; +using RecrownedGTK.Types; +using OpenTK; + +namespace RecrownedGTK.Graphics.UI.BookSystem +{ + /// + /// Contains the pages. + /// + public class Book : IInputListener + { + readonly AssetManager assets; + readonly ISkin skin; + Page targetPage; + int width, height; + Dictionary pages = new Dictionary(); + List orderedPages = new List(); + + /// + /// The camera to use to change between pages. + /// + public Camera2D camera; + private BasicScissor basicScissor; + + /// + /// Creates a book. + /// + /// that holds the assets that are to be used in the pages for this book during initialization. + /// The skin that will be passed to pages during their initialization. + /// Camera to move to change pages. + public Book(AssetManager assets, ISkin skin, Camera2D camera) + { + this.assets = assets; + this.skin = skin; + + this.camera = camera ?? throw new ArgumentNullException("Camera can't be null since book needs a camera to move to change between the slides (pages)."); + this.basicScissor = new BasicScissor(); + } + + /// + /// Should be called whenever a valid camera and dimensions for the book exist. + /// Initializes book with given parameters. + /// Generally used with a and called in the function. + /// + /// Camera to move to change pages. + /// Dimensions of the book and the dimensions the pages will use. + public void Initiate(Camera2D camera, Rectangle dimensions) + { + this.camera = camera; + } + + /// + /// Applies the size if the book's pages. + /// + /// The width of one page. + /// The height of one page. + public void ApplySize(int width, int height) + { + if (this.width == width && this.height == height) return; + + this.width = width; + this.height = height; + for (int pageIndex = 0; pageIndex < pages.Count; pageIndex++) + { + Page page = orderedPages[pageIndex]; + if (page.Boundaries.Width != width || page.Boundaries.Height != height) + { + page.requiresSizeUpdate = true; + } + } + } + + /// + /// Draws the pages. + /// + /// Batch used to draw. + public void Draw(ConsistentSpriteBatch batch) + { + for (int pageIndex = 0; pageIndex < pages.Count; pageIndex++) + { + orderedPages[pageIndex].Draw(batch); + } + } + + /// + /// Updates the book. + /// + /// Snapshot of information of the game time. + public void Update(GameTime gameTime) + { + if (targetPage != null) + { + Vector2 position; + Rectangle targetBounds = targetPage.Boundaries; + position.X = targetBounds.X + (targetBounds.Width * 0.5f); + position.Y = targetBounds.Y + (targetBounds.Height * 0.5f); + camera.LinearInterpolationToPosition(0.4f, position, (float)gameTime.ElapsedGameTime.TotalSeconds); + if (camera.Position == position) + { + targetPage = null; + } + } + + for (int pageIndex = 0; pageIndex < pages.Count; pageIndex++) + { + Page page = pages.ElementAt(pageIndex).Value; + if (page.requiresSizeUpdate) page.ApplySize(width, height); + page.Update(gameTime); + } + } + + /// + /// Adds the page(s). + /// + /// The page(s) to add. + public void AddPages(params Page[] pages) + { + foreach (Page page in pages) + { + orderedPages.Add(page); + this.pages.Add(page.name, page); + page.Initialize(assets, skin, basicScissor); + } + } + + /// + /// Removes the page. + /// + /// Page to remove. + public void RemovePage(Page page) + { + RemovePage(page.name); + } + + /// + /// Removes the page. + /// + /// Name of page to remove. + public void RemovePage(string name) + { + orderedPages.Remove(pages[name]); + pages.Remove(name); + } + + + /// + /// Removes all pages. + /// + public void ClearPages() + { + orderedPages.Clear(); + pages.Clear(); + } + + /// + /// Perform a step of linear interpolation to the given page. + /// + /// The page to lerp to. + public void LerpToPage(Page page) + { + targetPage = page; + } + + /// + /// Goes to page instantly. + /// + /// Page to go to. + public void GoToPage(Page page) + { + Rectangle targetBounds = page.Boundaries; + camera.position.X = targetBounds.X + (targetBounds.Width * 0.5f); + camera.position.Y = targetBounds.Y + (targetBounds.Height * 0.5f); + } + + /// + /// Passes the new keyboard state down to each page in order of when it was added. + /// + /// + /// True if the state change should to trigger further input listeners. + public bool KeyboardStateChanged(KeyboardState state) + { + for (int pageIndex = 0; pageIndex < pages.Count; pageIndex++) + { + Page page = orderedPages[pageIndex]; + if (!page.KeyboardStateChanged(state)) return false; + } + return true; + } + + /// + /// Passes the new mouse state down to each page in order of when it was added. + /// + /// + /// True if the state change should to trigger further input listeners. + public bool MouseStateChanged(MouseState state) + { + for (int pageIndex = 0; pageIndex < pages.Count; pageIndex++) + { + Page page = orderedPages[pageIndex]; + if (!page.MouseStateChanged(state)) return false; + } + return true; + } + } +} diff --git a/RecrownedGTK/Graphics/UI/BookSystem/Page.cs b/RecrownedGTK/Graphics/UI/BookSystem/Page.cs new file mode 100644 index 0000000..0d771b8 --- /dev/null +++ b/RecrownedGTK/Graphics/UI/BookSystem/Page.cs @@ -0,0 +1,57 @@ +using RecrownedGTK.Assets; +using RecrownedGTK.Graphics.Render; +using RecrownedGTK.Graphics.UI.Modular; +using RecrownedGTK.Graphics.UI.SkinSystem; + +namespace RecrownedGTK.Graphics.UI.BookSystem +{ + /// + /// A page a part of a . + /// + public class Page : UIModuleGroup + { + private readonly int pageX, pageY; + /// + /// Whether or not this book needs to be refreshed with new dimensions. + /// + public bool requiresSizeUpdate; + + /// + /// Constructs a page. + /// + /// The X position in the book. + /// The Y position in the book. + public Page(int pageX, int pageY) : base() + { + this.pageX = pageX; + this.pageY = pageY; + requiresSizeUpdate = true; + name = ToString(); + } + + /// + /// Called when this page is flagged as needing a size update. + /// + /// New width. + /// New Height + public virtual void ApplySize(int width, int height) + { + X = pageX * width; + Y = pageY * height; + Width = width; + Height = height; + requiresSizeUpdate = false; + } + + /// + /// Called only once after a page is added to a . Generally used to instantiate the modules of the page. + /// + /// The assets to be used during initialization passed by the book this page belongs to. + /// The skin the book containing this page is given that can be used by this page. + /// The scissor box to use for cropping. + protected internal virtual void Initialize(AssetManager assets, ISkin skin, BasicScissor basicScissor) + { + this.basicScissor = basicScissor; + } + } +} diff --git a/RecrownedGTK/Graphics/UI/Modular/Modules/Image.cs b/RecrownedGTK/Graphics/UI/Modular/Modules/Image.cs new file mode 100644 index 0000000..ddf295d --- /dev/null +++ b/RecrownedGTK/Graphics/UI/Modular/Modules/Image.cs @@ -0,0 +1,85 @@ +using RecrownedGTK.Graphics.Render; +using RecrownedGTK.Types; +using OpenTK; +using OpenTK.Graphics; +using System; + +namespace RecrownedGTK.Graphics.UI.Modular.Modules +{ + /// + /// Represents a texture with more information. + /// + public class Image : UIModule, ISpecialDrawable + { + /// + /// The rotation of the image. + /// + public float rotation = 0f; + + /// + /// The texture to be rendered. + /// + public Texture2D texture; + + /// + /// Scale of of the X axis. + /// + public float ScaleX { get { return (float)Width / texture.Width; } set { Width = (int)(texture.Width * value); } } + + /// + /// Scale of the Y axis. + /// + public float ScaleY { get { return (float)Height / texture.Height; } set { Height = (int)(texture.Height * value); } } + + /// + /// Sets scale of X and Y. + /// + public float Scale { set { ScaleY = value; ScaleX = value; } } + + /// + /// Constructs an image given a texture. + /// + /// Texture to use. + public Image(Texture2D texture) + { + this.texture = texture ?? throw new ArgumentException("Image requires a texture."); + SetPositionAndDimensions(texture.Bounds); + } + + /// + /// Draws the image with default values. + /// + /// The batch to use. + public override void Draw(ConsistentSpriteBatch batch) + { + batch.Draw(texture, new Rectangle(X, Y, Width, Height), null, color, rotation, origin, SpriteEffects.None, 0f); + base.Draw(batch); + } + + /// + /// Draws the image with more options. + /// + /// Batch used. + /// Where to draw texture to. + /// The color tint to use. + /// Rotation of image. + /// Origin for the rotation. + public void Draw(ConsistentSpriteBatch spriteBatch, Rectangle destination, Color4 color, float rotation = 0, Vector2 origin = default(Vector2)) + { + this.color = color; + this.rotation = rotation; + this.origin = origin; + SetPositionAndDimensions(destination); + Draw(spriteBatch); + } + + /// + /// Center's the origin to the middle of the dimensions of the texture. + /// + public override void CenterOrigin() + { + origin.X = texture.Bounds.Width / 2f; + origin.Y = texture.Bounds.Height / 2f; + } + } +} diff --git a/RecrownedGTK/Graphics/UI/Modular/Modules/Interactive/Button.cs b/RecrownedGTK/Graphics/UI/Modular/Modules/Interactive/Button.cs new file mode 100644 index 0000000..d0a424a --- /dev/null +++ b/RecrownedGTK/Graphics/UI/Modular/Modules/Interactive/Button.cs @@ -0,0 +1,155 @@ +using RecrownedGTK.Types; +using RecrownedGTK.Input; +using RecrownedGTK.Graphics.UI.SkinSystem.Definitions; +using RecrownedGTK.Graphics.UI.SkinSystem; +using RecrownedGTK.Graphics.Render; + +namespace RecrownedGTK.Graphics.UI.Modular.Modules.Interactive +{ + /// + /// Function to be called when button is clicked. + /// + /// The button that was clicked. + public delegate void Clicked(Button button); + + /// + /// A very primitive button containing all the basic functions. + /// + public class Button : UIModule + { + private ButtonSkinDefinition skinDefinition; + private ISpecialDrawable downTexture, upTexture, highlightedTexture, disabledTexture; + /// + /// Click event listeners. + /// + public event Clicked Listeners; + private bool pressed; + /// + /// Whether or not this button should be currently disabled. + /// + public bool disabled = false; + + /// + /// Whether or not this button is currently being hovered on. + /// + public bool Highlighted { get; private set; } + + /// + /// Constructs this button using s for the different states it could be in. + /// + /// Button being pressed. + /// Button not being pressed. + /// Disabled button. + /// Button being highlighted. + public Button(ISpecialDrawable down, ISpecialDrawable up, ISpecialDrawable disabled = null, ISpecialDrawable selected = null) + { + this.downTexture = down; + this.upTexture = up; + this.disabledTexture = disabled; + this.highlightedTexture = selected; + } + + /// + /// Constructs this button using the skin system. + /// + /// The skin containing the information of the textures and design to follow. + /// The name of the definition in the skin. Can be null to select the default. + public Button(ISkin skin, string definitionName = null) + { + skinDefinition = skin.ObtainDefinition(definitionName); + downTexture = skin.GetTextureAtlasRegion(skinDefinition.downRegion, true); + upTexture = skin.GetTextureAtlasRegion(skinDefinition.upRegion, true); + disabledTexture = skin.GetTextureAtlasRegion(skinDefinition.disabledRegion); + highlightedTexture = skin.GetTextureAtlasRegion(skinDefinition.selectedRegion); + } + + /// + /// Instantiates a button using a definition. + /// + /// The skin the definition is defined in. + /// The definition itself. + public Button(ISkin skin, ButtonSkinDefinition skinDefinition) : + this(skin.GetTextureAtlasRegion(skinDefinition.downRegion, true), + skin.GetTextureAtlasRegion(skinDefinition.upRegion, true), + skin.GetTextureAtlasRegion(skinDefinition.disabledRegion), + skin.GetTextureAtlasRegion(skinDefinition.selectedRegion)) + { } + + /// + /// Draws the button. + /// + /// Batch used to draw the button. + public override void Draw(ConsistentSpriteBatch batch) + { + if (disabled) + { + disabledTexture?.Draw(batch, Boundaries, color); + } + else + { + if (pressed) + { + downTexture.Draw(batch, Boundaries, color); + } + else if (Highlighted) + { + highlightedTexture?.Draw(batch, Boundaries, color); + } + else + { + upTexture.Draw(batch, Boundaries, color); + } + } + + base.Draw(batch); + } + + /// + /// Called when the mouse changes state. + /// + /// The new state. + /// Whether or not to continue calling the next mouse change listener. + public sealed override bool MouseStateChanged(MouseState state) + { + if (InputUtilities.MouseWithinBoundries(Boundaries)) + { + if (state.LeftButton == ButtonState.Pressed) + { + pressed = true; + } + else + { + pressed = false; + } + if (InputUtilities.MouseClicked()) + { + OnClick(); + } + Highlighted = true; + } + else + { + Highlighted = false; + pressed = false; + } + + return base.MouseStateChanged(state); + } + + /// + /// Called when the state of the keyboard changes. + /// + /// The new state. + /// Whether or not the next keyboard change listener should be called. + public sealed override bool KeyboardStateChanged(KeyboardState state) + { + return base.KeyboardStateChanged(state); + } + + internal void OnClick() + { + Listeners?.Invoke(this); + } + + } +} diff --git a/RecrownedGTK/Graphics/UI/Modular/Modules/Interactive/TextButton.cs b/RecrownedGTK/Graphics/UI/Modular/Modules/Interactive/TextButton.cs new file mode 100644 index 0000000..bc60bcf --- /dev/null +++ b/RecrownedGTK/Graphics/UI/Modular/Modules/Interactive/TextButton.cs @@ -0,0 +1,92 @@ +using RecrownedGTK.Graphics.Render; +using OpenTK.Graphics; +using RecrownedGTK.Graphics.UI.SkinSystem; +using RecrownedGTK.Graphics.UI.SkinSystem.Definitions; + +namespace RecrownedGTK.Graphics.UI.Modular.Modules.Interactive +{ + /// + /// Button that holds a string. + /// + public class TextButton : Button + { + /// + /// The text that is used to display the string. + /// + public readonly Text text; + + /// + /// The color the font should be rendered in. + /// + public Color4 FontColor { get { return text.color; } set { text.color = value; } } + + /// + /// Constructs text button with the positions represented by + /// + /// The string representing the text to be displayed. + /// The font to be used to display the text. + /// What to draw as button is pushed down. + /// What to draw as button is not pushed. + /// What to draw as button is disabled. + /// What to draw as button is selected. + public TextButton(string text, SpriteFont font, ISpecialDrawable down, ISpecialDrawable up, ISpecialDrawable disabled = null, ISpecialDrawable selected = null) : base(down, up, disabled, selected) + { + this.text = new Text(font, text); + this.text.autoScale = true; + this.text.centered = true; + } + + /// + /// Constructs a text button using a skin and definition. + /// + /// The text to display. + /// The font to be used. + /// The skin to use. + /// Name of the definition for this type in the skin given. + public TextButton(string text, SpriteFont font, ISkin skin, string definitionName = null) : base(skin, skin.ObtainDefinition(definitionName)) + { + TextButtonSkinDefinition skinDefinition = skin.ObtainDefinition(definitionName); + this.text = new Text(font, text); + this.text.autoScale = true; + this.text.centered = true; + FontColor = skin.GetColor(skinDefinition.fontColor); + } + + /// + /// Creates a text button with a given definition. + /// + /// The text to be displayed on this button. + /// The font to use for this button. + /// The skin the definition is from. + /// The definition to use. + public TextButton(string text, SpriteFont font, ISkin skin, TextButtonSkinDefinition skinDefinition) : + this(text, + font, + skin.GetTextureAtlasRegion(skinDefinition.downRegion, true), + skin.GetTextureAtlasRegion(skinDefinition.upRegion, true), + skin.GetTextureAtlasRegion(skinDefinition.disabledRegion), + skin.GetTextureAtlasRegion(skinDefinition.selectedRegion)) + { } + + /// + /// Updates the text button. + /// + /// Snapshot of information about time for game. + public override void Update(GameTime gameTime) + { + text.SetPositionAndDimensions(Boundaries); + text.Update(gameTime); + base.Update(gameTime); + } + + /// + /// Called whenever game wants to render this button. + /// + /// Batch to use. Batch should already be started. + public override void Draw(ConsistentSpriteBatch batch) + { + base.Draw(batch); + text.Draw(batch); + } + } +} diff --git a/RecrownedGTK/Graphics/UI/Modular/Modules/Text.cs b/RecrownedGTK/Graphics/UI/Modular/Modules/Text.cs new file mode 100644 index 0000000..43b2b13 --- /dev/null +++ b/RecrownedGTK/Graphics/UI/Modular/Modules/Text.cs @@ -0,0 +1,206 @@ +using OpenTK; +using RecrownedGTK.Graphics.Render; +using System; +using System.Text; + +namespace RecrownedGTK.Graphics.UI.Modular.Modules +{ + /// + /// Represents text for the UI. + /// + public class Text : UIModule + { + private SpriteFont font; + private float scale = 1f; + private Vector2 position; + private string originalText; + private string displayedText; + private Vector2 modifiedTextSize; + + /// + /// Centers the text int bounds. + /// + public bool centered; + + /// + /// Whether or not to try and wrap text automatically. Meaning will check and attempt to wrap every update. + /// + public bool autoWrap; + + /// + /// Whether or not to automatically scale the text every update. Happens after auto wrap if enabled. + /// + public bool autoScale; + + /// + /// Should this use ellipses? Will perform this operation before auto wrapping or auto scalling. + /// + public bool useEllipses; + + /// + /// The text to use for the ellipsis. + /// + public string ellipsis = "..."; + private string ModifiedText { get { return displayedText; } set { displayedText = value; modifiedTextSize = font.MeasureString(value); } } + + /// + /// The string to be displayed. + /// + public string Content { get { return originalText; } set { originalText = value; if (value == null) value = ellipsis; modifiedTextSize = font.MeasureString(value); displayedText = value; } } + + /// + /// Creates a UI text object. + /// + /// The font to use. + /// The string for the text. + public Text(SpriteFont font, string content = null) + { + this.font = font ?? throw new ArgumentNullException("Font cannot be null."); + Content = content; + } + + /// + /// Updates the positioning and attempts to perform any operations that were marked automatic. + /// + /// The game time. + public override void Update(GameTime gameTime) + { + position.X = X; + position.Y = Y; + + if (useEllipses) AttemptToApplyEllipsis(); + if (autoWrap) AttemptToWrapText(); + if (autoScale) AttemptToScaleFont(); + if (centered) Center(); + + base.Update(gameTime); + } + + /// + /// Draws the text. + /// + /// Batch to use. + public override void Draw(ConsistentSpriteBatch batch) + { + batch.DrawString(font, Content, position, color, 0f, default(Vector2), scale, SpriteEffects.None, 0f); + base.Draw(batch); + } + + /// + /// Attempts to apply ellipsis. Checks of nessecary. + /// + public void AttemptToApplyEllipsis() + { + if (modifiedTextSize.X * scale > Width && ModifiedText.Length > ellipsis.Length + 1) + { + RemoveLineBreaks(); + StringBuilder stringBuilder = new StringBuilder(ModifiedText); + do + { + stringBuilder.Remove(stringBuilder.Length, ellipsis.Length - 1); + stringBuilder.Insert(stringBuilder.Length, ellipsis); + } + while (font.MeasureString(stringBuilder).X * scale > Width); + + ModifiedText = stringBuilder.ToString(); + } + } + + /// + /// Attempts to scale the font. Checks if nessecary. + /// + public void AttemptToScaleFont() + { + + if (Width < Height) + { + if (Math.Round(modifiedTextSize.X * scale ) != Width) + { + scale = Width / modifiedTextSize.X; + } + } + else + { + if (Math.Round(modifiedTextSize.Y * scale ) != Height) + { + scale = Height / (modifiedTextSize.Y); + } + } + } + + /// + /// Removes line breaks. + /// + public void RemoveLineBreaks() + { + ModifiedText = ModifiedText.Replace("\n", " "); + } + + /// + /// Resets to original text. + /// + public void ResetToOriginalText() + { + Content = originalText; + } + + /// + /// Attempts to wrap text. Checks if nessecary. + /// + /// If true, will first unwrap text, and the wrap again. This occurs before nessecity check. + public void AttemptToWrapText(bool unwrap = false) + { + if (unwrap) RemoveLineBreaks(); + if (modifiedTextSize.X * scale > Width) + { + ModifiedText = ModifiedText.Replace("\n", " "); + string[] words = ModifiedText.Split(' '); + StringBuilder stringBuilder = new StringBuilder(); + float currentScaledLineWidth = 0f; + + for (int w = 0; w < words.Length; w++) + { + string word = words[w]; + float scaledWidth = font.MeasureString(word).X * scale; + + if (currentScaledLineWidth + scaledWidth <= Width) + { + stringBuilder.Append(word); + currentScaledLineWidth += scaledWidth; + } + else + { + stringBuilder.AppendLine(); + currentScaledLineWidth = 0; + } + } + ModifiedText = stringBuilder.ToString(); + } + } + + private bool Center() + { + Vector2 textSize = new Vector2(modifiedTextSize.X * scale, modifiedTextSize.Y * scale); + + if (textSize.X <= Width) + { + position.X = X + (Width - textSize.X) / 2f; + } + else + { + return false; + } + + if (textSize.Y <= Height) + { + position.Y = Y + (Height - textSize.Y) / 2f; + } + else + { + return false; + } + + return true; + } + } +} diff --git a/RecrownedGTK/Graphics/UI/Modular/Modules/UIScrollable.cs b/RecrownedGTK/Graphics/UI/Modular/Modules/UIScrollable.cs new file mode 100644 index 0000000..413241b --- /dev/null +++ b/RecrownedGTK/Graphics/UI/Modular/Modules/UIScrollable.cs @@ -0,0 +1,451 @@ +using RecrownedGTK.Input; +using RecrownedGTK.Graphics.Render; +using RecrownedGTK.Types; +using RecrownedGTK.Graphics.UI.SkinSystem; +using RecrownedGTK.Graphics.UI.SkinSystem.Definitions; +using OpenTK; +using OpenTK.Graphics; +using System; + +namespace RecrownedGTK.Graphics.UI.Modular.Modules +{ + public class UIScrollable : UIModule + { + BasicScissor basicScissor; + Rectangle viewport; + UIModuleGroup group; + + Color4 scrollBarColor = Color4.White; + float opacityOfBar = 1f; + bool showingBars; + private bool mouseWasPressed; + private bool horizontalSelected; + private Vector2 mouseRelativePosition; + float shiftX, shiftY; + + public float XScrollPosition + { + get + { + return (Width - horizontalScrollBarBounds.Width) * (-shiftX / (group.Width - viewport.Width)); + } + + set + { + if (value > Width - verticalScrollBarBounds.Height) + { + value = Width - verticalScrollBarBounds.Height; + } + if (value < 0) value = 0; + shiftX = -(group.Width - viewport.Width) * (value / (Width - horizontalScrollBarBounds.Width)); + WidthOrXChange(true); + } + } + public float YScrollPosition + { + get + { + return (Height - verticalScrollBarBounds.Height) * (-shiftY / (group.Height - viewport.Height)); + } + + set + { + if (value > Height - verticalScrollBarBounds.Height) + { + value = Height - verticalScrollBarBounds.Height; + } + if (value < 0) value = 0; + shiftY = -(group.Height - viewport.Height) * (value / (Height - verticalScrollBarBounds.Height)); + HeightOrYChange(true); + } + } + + UIModule furthestXModule, furthestYMod; + + bool horScrollAvailable, vertScrollAvailable; + + Rectangle horizontalScrollBarBounds, verticalScrollBarBounds; + /// + /// How fast the bars fade away in opacity (0 to 254) per second. + /// + public float barFadeSpeed = 250; + + ISpecialDrawable horizontalScrollBar, verticalScrollBar; + ISpecialDrawable background, horizontalBarTrack, verticalBarTrack; + + private int topPadding, bottomPadding, leftPadding, rightPadding; + + /// + /// + /// + public int PadTop { get { return topPadding; } set { topPadding = value; HeightOrYChange(); } } + public int PadBottom { get { return bottomPadding; } set { bottomPadding = value; HeightOrYChange(); } } + public int PadLeft { get { return leftPadding; } set { leftPadding = value; WidthOrXChange(); } } + public int PadRight { get { return rightPadding; } set { rightPadding = value; WidthOrXChange(); } } + + /// + /// The minimum bar length for the scroll bar. + /// + public int minimumBarLength = 16; + + public int HorizontalBarThickness { get { return horizontalScrollBarBounds.Height; } set { horizontalScrollBarBounds.Height = value; HeightOrYChange(); } } + public int VerticalBarThickness { get { return verticalScrollBarBounds.Width; } set { verticalScrollBarBounds.Width = value; WidthOrXChange(); } } + + bool hideScrollBars; + + /// + /// Whether or not to hide scroll bars. + /// + public bool HideScrollBars + { + get { return hideScrollBars; } + set + { + hideScrollBars = value; + WidthOrXChange(true); + HeightOrYChange(true); + if (!value) + { + opacityOfBar = 1f; + scrollBarColor = color.ReturnMultipliedByFloat(opacityOfBar); + } + } + } + + + /// + /// Set to true to change from the normal position to the new position. + /// + public bool verticalBarLeftPosition, horizontalBarTopPosition; + + + public override int Width + { + get + { + return base.Width; + } + set + { + base.Width = value; + WidthOrXChange(); + } + } + + public override int Height + { + get + { + return base.Height; + } + set + { + base.Height = value; + HeightOrYChange(); + } + } + + public override int X + { + get + { + return base.X; + } + set + { + WidthOrXChange(true); + base.X = value; + } + } + + public override int Y + { + get + { + return base.Y; + } + set + { + HeightOrYChange(true); + base.Y = value; + } + } + + public UIScrollable(ISpecialDrawable horizontalScrollBar, ISpecialDrawable verticalScrollBar, ISpecialDrawable horizontalBarTrack = null, ISpecialDrawable verticalBarTrack = null, ISpecialDrawable background = null) + { + this.horizontalScrollBar = horizontalScrollBar; + this.verticalScrollBar = verticalScrollBar; + this.horizontalBarTrack = horizontalBarTrack; + this.verticalBarTrack = verticalBarTrack; + this.background = background; + + basicScissor = new BasicScissor(); + group = new UIModuleGroup(); + HorizontalBarThickness = 12; + VerticalBarThickness = 12; + } + + public UIScrollable(ISkin skin, string definition = null) : + this(skin.GetTextureAtlasRegion(skin.ObtainDefinition().horizontalBar, true), + skin.GetTextureAtlasRegion(skin.ObtainDefinition().verticalBar, true), + skin.GetTextureAtlasRegion(skin.ObtainDefinition().horizontalBarTrack), + skin.GetTextureAtlasRegion(skin.ObtainDefinition().verticalBarTrack), + skin.GetTextureAtlasRegion(skin.ObtainDefinition().background)) + { + } + + public override void Update(GameTime gameTime) + { + if (hideScrollBars) + { + if (!showingBars && !mouseWasPressed) + { + if (opacityOfBar > 0f) + { + opacityOfBar -= (barFadeSpeed / 255f) * (float)gameTime.ElapsedGameTime.TotalSeconds; + } + } + else + { + opacityOfBar = 1f; + } + scrollBarColor = color.ReturnMultipliedByFloat(opacityOfBar); + } + + if (horScrollAvailable) + { + horizontalScrollBarBounds.X = (int)XScrollPosition; + } + + if (vertScrollAvailable) + { + verticalScrollBarBounds.Y = (int)YScrollPosition; + } + + + base.Update(gameTime); + } + + public override void Draw(ConsistentSpriteBatch spriteBatch) + { + background?.Draw(spriteBatch, Boundaries, color, origin: origin); + spriteBatch.End(); + basicScissor.Begin(viewport, spriteBatch); + group.Draw(spriteBatch); + basicScissor.End(); + spriteBatch.Begin(); + if (horScrollAvailable) + { + horizontalScrollBar.Draw(spriteBatch, horizontalScrollBarBounds, scrollBarColor); + } + if (vertScrollAvailable) + { + horizontalScrollBar.Draw(spriteBatch, verticalScrollBarBounds, scrollBarColor); + } + base.Draw(spriteBatch); + } + + public void AddModules(params UIModule[] addModules) + { + group.AddModules(addModules); + + for (int i = 0; i < addModules.Length; i++) + { + UIModule m = addModules[i]; + int mFurthestX = m.Boundaries.X + m.Boundaries.Width; + int mFurthestY = m.Boundaries.Y + m.Boundaries.Height; + if (mFurthestX > group.Width) + { + furthestXModule = m; + group.Width = mFurthestX; + } + if (mFurthestY > group.Height) + { + furthestYMod = m; + group.Height = mFurthestY; + } + } + + WidthOrXChange(); + HeightOrYChange(); + } + + public void RemoveModule(UIModule module) + { + group.RemoveModule(module); + + if (module == furthestXModule) + { + group.Width = 0; + UIModule[] modules = group.GetCopyOfModules(); + for (int i = 0; i < modules.Length; i++) + { + UIModule m = modules[i]; + int mFurthestX = m.Boundaries.X + m.Boundaries.Width; + if (mFurthestX > group.Width) + { + furthestXModule = m; + group.Width = mFurthestX; + } + } + } + + if (module == furthestYMod) + { + group.Height = 0; + UIModule[] modules = group.GetCopyOfModules(); + + for (int i = 0; i < modules.Length; i++) + { + UIModule m = modules[i]; + int mFurthestY = m.Boundaries.Y + m.Boundaries.Height; + if (mFurthestY > group.Height) + { + furthestYMod = m; + group.Height = mFurthestY; + } + } + } + } + + private void WidthOrXChange(bool onlyXChange = false) + { + group.X = X + leftPadding + (int)shiftX; + if (!onlyXChange) + { + viewport.X = X + leftPadding; + viewport.Width = Width - rightPadding - leftPadding; + if (Width < group.Width) + { + horScrollAvailable = true; + horizontalScrollBarBounds.Width = (int)(Width * ((float)Width / group.Width)); + horizontalScrollBarBounds.Width = Math.Max(horizontalScrollBarBounds.Width, minimumBarLength); + } + else { horScrollAvailable = false; } + + verticalScrollBarBounds.X = X; + if (!hideScrollBars) + { + viewport.Width -= VerticalBarThickness; + if (!verticalBarLeftPosition) + { + verticalScrollBarBounds.X += viewport.Width; + } + } + else + { + if (!verticalBarLeftPosition) + { + verticalScrollBarBounds.X += viewport.Width - verticalScrollBarBounds.Width; + } + } + } + } + + private void HeightOrYChange(bool onlyYChange = false) + { + group.Y = Y + bottomPadding + (int)shiftY; + if (!onlyYChange) + { + viewport.Y = Y + bottomPadding; + viewport.Height = Height - bottomPadding - topPadding; + if (Height < group.Height) + { + vertScrollAvailable = true; + verticalScrollBarBounds.Height = (int)(Height * ((float)Height / group.Height)); + verticalScrollBarBounds.Height = Math.Max(verticalScrollBarBounds.Height, minimumBarLength); + } + else { vertScrollAvailable = false; } + + horizontalScrollBarBounds.Y = Y; + if (!hideScrollBars) + { + viewport.Height -= HorizontalBarThickness; + if (!horizontalBarTopPosition) + { + horizontalScrollBarBounds.Y += viewport.Height; + } + else + { + viewport.Y += horizontalScrollBarBounds.Height; + } + } + else + { + if (!horizontalBarTopPosition) + { + horizontalScrollBarBounds.Y += viewport.Height - horizontalScrollBarBounds.Height; + } + } + } + } + + public override bool KeyboardStateChanged(KeyboardState state) + { + if (state.IsKeyDown(Keys.Right)) + { + XScrollPosition += 1; + } + + return base.KeyboardStateChanged(state); + } + + public override bool MouseStateChanged(MouseState state) + { + if (InputUtilities.MouseWithinBoundries(Boundaries)) + { + showingBars = true; + } + else + { + showingBars = false; + } + + if (InputUtilities.MouseWithinBoundries(horizontalScrollBarBounds)) + { + if (state.LeftButton == ButtonState.Pressed) + { + mouseWasPressed = true; + horizontalSelected = true; + } + if (!mouseWasPressed) + { + mouseRelativePosition.X = state.X - horizontalScrollBarBounds.X; + } + } + + if (InputUtilities.MouseWithinBoundries(verticalScrollBarBounds)) + { + if (state.LeftButton == ButtonState.Pressed) + { + mouseWasPressed = true; + horizontalSelected = false; + } + if (!mouseWasPressed) + { + mouseRelativePosition.Y = state.Y - verticalScrollBarBounds.Y; + } + } + + if (mouseWasPressed) + { + if (horizontalSelected) + { + float latestPosition = state.X - mouseRelativePosition.X - X; + XScrollPosition = latestPosition; + } + else + { + float latestPosition = state.Y - mouseRelativePosition.Y - Y; + YScrollPosition = latestPosition; + } + + if (state.LeftButton == ButtonState.Released) + { + mouseWasPressed = false; + } + } + return base.MouseStateChanged(state); + } + } +} diff --git a/RecrownedGTK/Graphics/UI/Modular/UIModule.cs b/RecrownedGTK/Graphics/UI/Modular/UIModule.cs new file mode 100644 index 0000000..0741361 --- /dev/null +++ b/RecrownedGTK/Graphics/UI/Modular/UIModule.cs @@ -0,0 +1,190 @@ +using RecrownedGTK.Types; +using OpenTK; +using OpenTK.Graphics; +using RecrownedGTK.Input; +using RecrownedGTK.Graphics.Render; +using System; + +namespace RecrownedGTK.Graphics.UI.Modular +{ + + /// + /// Module for UI layout. + /// + public abstract class UIModule : IInputListener + { + /// + /// The width of the module. + /// + public virtual int Width { get; set; } + + /// + /// The height of the module. + /// + public virtual int Height { get; set; } + + /// + /// The X position of the module. + /// + public virtual int X { get; set; } + + /// + /// The Y position of the module. + /// + public virtual int Y { get; set; } + + /// + /// Bounds of this module (after factoring in the origin). + /// + public Rectangle Boundaries + { + get + { + return new Rectangle((int)(X - origin.X), (int)(Y - origin.Y), Width, Height); + } + } + + /// + /// Origin of this module. + /// + public Vector2 origin; + + /// + /// The parent of this module. May be null. + /// + public UIModuleGroup parent; + + /// + /// Name of this module. For organizational/referencial purposes mostly. + /// + public string name; + + /// + /// The color tint of this module. + /// + public Color4 color = Color4.White; + + /// + /// Called every frame to update this module. Calculations and movement should go here. + /// + /// Game time information. + public virtual void Update(GameTime gameTime) + { + + } + + /// + /// Called every frame to draw this module. Anything that needs to be drawn should go here. + /// + /// Batch used to draw. + public virtual void Draw(ConsistentSpriteBatch batch) + { + } + + /// + /// Converts the given rectangle to the coordinates of the parent. + /// + /// Rectangle to convert. + /// + public Rectangle ConvertToParentCoordinates(Rectangle rectangle) + { + if (parent != null) + { + Rectangle parentHitbox = parent.ConvertToParentCoordinates(rectangle); + int tX = rectangle.X + parentHitbox.X; + int tY = rectangle.Y + parentHitbox.Y; + return new Rectangle(tX, tY, rectangle.Width, rectangle.Height); + } + else + { + return rectangle; + } + } + + /// + /// Removes this module from the parent. + /// + public void RemoveFromParent() + { + if (parent == null) throw new InvalidOperationException("Parent is null."); + parent.RemoveModule(this); + } + + /// + /// Called whenever the keyboard state is changed. + /// + /// The current keyboard state. + /// Returning whether or not to continue to call the next listener. + public virtual bool KeyboardStateChanged(KeyboardState state) + { + return true; + } + + /// + /// Called whenever the state of the mouse changes. This includes movement. + /// + /// The current state of the mouse. + /// Returning whether or not to continue to call the next listener. + public virtual bool MouseStateChanged(MouseState state) + { + return true; + } + + /// + /// Sets the origin to be the center using the and . + /// + public virtual void CenterOrigin() + { + origin.X = Width / 2f; + origin.Y = Height / 2f; + } + + /// + /// Centers this module's on the horizontal axis relative to the parent . + /// + /// True if possible and false if not. + public bool CenterHorizontally() + { + if (parent != null) + { + Rectangle rectangle = parent.Boundaries; + if (parent != null && rectangle.Width >= Boundaries.Width) + { + X = rectangle.Width / 2 + X; + return true; + } + } + return false; + } + + /// + /// Centers this module's origin on the vertical axis relative to the parent . + /// + /// True if possible. + public virtual bool CenterVertically() + { + if (parent != null) + { + Rectangle rectangle = parent.Boundaries; + if (rectangle.Height >= Boundaries.Height) + { + Y = rectangle.Height / 2 + Y; + return true; + } + } + return false; + } + + /// + /// Sets the position and dimension of this module. + /// + /// The rectangle that represents the position and dimensions of the module. + public virtual void SetPositionAndDimensions(Rectangle rectangle) + { + X = rectangle.X; + Y = rectangle.Y; + Width = rectangle.Width; + Height = rectangle.Height; + } + } +} diff --git a/RecrownedGTK/Graphics/UI/Modular/UIModuleGroup.cs b/RecrownedGTK/Graphics/UI/Modular/UIModuleGroup.cs new file mode 100644 index 0000000..cca58e6 --- /dev/null +++ b/RecrownedGTK/Graphics/UI/Modular/UIModuleGroup.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using RecrownedGTK.Graphics.Render; + +namespace RecrownedGTK.Graphics.UI.Modular +{ + + /// + /// Contains a group of modules and has its own relative coordinate system. + /// + public class UIModuleGroup : UIModule + { + List modules = new List(); + + /// + /// Set this to crop anything that flows out of the boundaries of this group. Will not crop if this is null. + /// + public BasicScissor basicScissor; + + /// + /// Instantiates the UI module group. + /// + /// Sets the field. + public UIModuleGroup(BasicScissor basicScissor = null) + { + this.basicScissor = basicScissor; + } + + /// + /// Draws this group of modules. If scissoring, will use the matrix and effect designated in the to begin the batch normally again. + /// + /// Batch used to draw the group. + public override void Draw(ConsistentSpriteBatch spriteBatch) + { + if (basicScissor != null) + { + spriteBatch.End(); + basicScissor.Begin(Boundaries, spriteBatch); + } + + foreach (UIModule module in modules) + { + int offsetX = module.X; + int offsetY = module.Y; + module.X = X + offsetX; + module.Y = Y + offsetY; + module.Draw(spriteBatch); + module.X = offsetX; + module.Y = offsetY; + } + + if (basicScissor != null) + { + basicScissor.End(); + spriteBatch.Begin(); + } + } + + /// + /// Updates the group of modules. + /// + /// Game time used. + public override void Update(GameTime gameTime) + { + foreach (UIModule module in modules) + { + module.Update(gameTime); + } + } + + /// + /// Adds module(s) to this group. + /// + /// The module(s) to add. + public virtual void AddModules(params UIModule[] addModules) + { + foreach (UIModule module in addModules) + { + if (modules.Contains(module)) + { + throw new InvalidOperationException(module.ToString() + " already exists in " + this.ToString()); + } + module.parent = this; + modules.Add(module); + } + } + + /// + /// Removes given module from group. + /// + /// module to remove. + public virtual void RemoveModule(UIModule module) + { + module.parent = null; + modules.Remove(module); + } + + /// + /// Obtains an array snapshot of all the modules. + /// + public UIModule[] GetCopyOfModules() + { + return modules.ToArray(); + } + + /// + /// Updates the keyboard state of the modules in this group. + /// + /// The new state. + /// Whether or not to continue updating the other listeners. + public override bool KeyboardStateChanged(KeyboardState state) + { + foreach (UIModule module in modules) + { + module.KeyboardStateChanged(state); + } + return base.KeyboardStateChanged(state); + } + + /// + /// Updates the moues state of the modules in this group. + /// + /// The new state. + /// Whether or not to continue updating other listeners. + public override bool MouseStateChanged(MouseState state) + { + foreach (UIModule module in modules) + { + module.MouseStateChanged(state); + } + return base.MouseStateChanged(state); + } + } +} diff --git a/RecrownedGTK/Graphics/UI/ScreenSystem/ITransition.cs b/RecrownedGTK/Graphics/UI/ScreenSystem/ITransition.cs new file mode 100644 index 0000000..3bdde32 --- /dev/null +++ b/RecrownedGTK/Graphics/UI/ScreenSystem/ITransition.cs @@ -0,0 +1,46 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using RecrownedGTK.Graphics.Render; + +namespace RecrownedGTK.Graphics.UI.ScreenSystem +{ + /// + /// Contracts a transition that the can use. + /// + public interface ITransition + { + /// + /// Called once when the transition is needed. + /// The dimensions of the screen. + /// + void InitiateTransition(Rectangle dimensions); + + /// + /// Called every frame if the state of the screen this transition is placed upon is in the enter transition phase. + /// + /// The time passed in seconds since the last frame. + /// Whether or not this transition is waiting on something. Usually the . + /// If this returns true, then it is considered that this transition is complete. + bool UpdateEnteringTransition(double delta, bool waiting); + + /// + /// Called every frame if the state of the screen this transition is placed upon is in the exit phase. + /// + /// The time passed in seconds since the last frame. + /// Whether or not this transition is waiting on something. Usually the . + /// If this returns true, then it is considered that this transition is complete. + bool UpdateExitingTransition(double delta, bool waiting); + + /// + /// Called once every frame while transition is active. Meant to draw transition. + /// + /// + void DrawTransition(ConsistentSpriteBatch spriteBatch); + + /// + /// Updates if the previous screen uses a render target for exit transition. + /// + /// The frame of the previous screen. + void UpdatePreviousScreenFrame(RenderTarget2D previousScreenFrame); + } +} diff --git a/RecrownedGTK/Graphics/UI/ScreenSystem/LoadingScreen.cs b/RecrownedGTK/Graphics/UI/ScreenSystem/LoadingScreen.cs new file mode 100644 index 0000000..b786feb --- /dev/null +++ b/RecrownedGTK/Graphics/UI/ScreenSystem/LoadingScreen.cs @@ -0,0 +1,166 @@ +using OpenTK.Graphics; +using OpenTK; +using RecrownedGTK.Graphics.Render; +using RecrownedGTK.Types; +using System; + +namespace RecrownedGTK.Graphics.UI.ScreenSystem +{ + /// + /// A screen specifically meant to fill in loading times. + /// + public class LoadingScreen : Screen, ITransition + { + private const float ENTER_TIME = 2f; + private const float EXIT_TIME = 1f; + Game game; + readonly Texture2D texture; + Color4 color; + Rectangle textureBounds; + readonly float proportion; + bool recorded; + float rR, rG, rB; + float progR, progG, progB; + float progC = 254; + float rotation; + readonly bool rotate; + Vector2 origin; + + /// + /// Constructs a loading screen. + /// + /// The game itself to adjust mouse settings and such. + /// + /// + /// + public LoadingScreen(Game game, Texture2D screenImage, float proportion, bool rotate = false) : base(true) + { + this.game = game; + this.texture = screenImage; + this.proportion = proportion; + this.rotate = rotate; + Transitions.Add(this); + } + + /// + public override void Show() + { + game.IsMouseVisible = false; + base.Show(); + } + + /// + public override void Hide() + { + game.IsMouseVisible = true; + base.Hide(); + } + + /// + /// Sets things to correct values for start of transition. + /// + /// The window dimensions. + public void InitiateTransition(Rectangle dimensions) + { + color = Color4.White; + textureBounds.Width = (int)(height * proportion); + textureBounds.Height = (int)(height * proportion); + textureBounds.X = (width) / 2; + textureBounds.Y = (height) / 2; + origin.X = texture.Width / 2f; + origin.Y = texture.Height / 2f; + } + + void DoRotate(float deltaf) + { + rotation += (2f / 3f) * (float)Math.PI * deltaf; + if (rotation >= 2 * Math.PI) + { + rotation = 0; + } + } + + /// + /// Draws the transition. + /// + /// Batch to use. + public void DrawTransition(ConsistentSpriteBatch spriteBatch) + { + spriteBatch.Draw(texture, textureBounds, null, color, rotation, origin, SpriteEffects.None, 0f); + } + + /// + /// Updates the entering transition. + /// + /// Time passed between frames. + /// Whether or not this transition should be waiting. + /// Whether or not transition is complete. + public bool UpdateEnteringTransition(double delta, bool waiting) + { + float deltaf = (float)delta; + if (rotate) + { + DoRotate(deltaf); + } + if (waiting) + { + if (progR < 254 || progG < 254 || progB < 254) + { + if (!recorded) + { + rR = (Color4.White.R - BackgroundColor.R) / ENTER_TIME; + rG = (Color4.White.G - BackgroundColor.G) / ENTER_TIME; + rB = (Color4.White.B - BackgroundColor.B) / ENTER_TIME; + recorded = true; + } + progR += rR * deltaf; + progR = Math.Min(progR, 254); + progG += rG * deltaf; + progG = Math.Min(progG, 254); + progB += rB * deltaf; + progB = Math.Min(progB, 254); + BackgroundColor.R = (byte)progR; + BackgroundColor.G = (byte)progG; + BackgroundColor.B = (byte)progB; + } + else + { + StartExitTransition(true); + return true; + } + } + return false; + } + + /// + /// Updates the exiting transition. + /// + /// Time passed between frames. + /// Whether or not this transition should be waiting. + /// Whether or not transition is complete. + public bool UpdateExitingTransition(double delta, bool waiting) + { + float deltaf = (float)delta; + if (rotate) + { + DoRotate(deltaf); + } + if (progC > 0) + { + float rate = deltaf * 255 / EXIT_TIME; + progC -= rate; + progC = Math.Max(progC, 0); + color.R = (byte)progC; + color.G = (byte)progC; + color.B = (byte)progC; + color.A = (byte)progC; + } + else + { + return true; + } + return false; + } + + } +} diff --git a/RecrownedGTK/Graphics/UI/ScreenSystem/Screen.cs b/RecrownedGTK/Graphics/UI/ScreenSystem/Screen.cs new file mode 100644 index 0000000..6688d97 --- /dev/null +++ b/RecrownedGTK/Graphics/UI/ScreenSystem/Screen.cs @@ -0,0 +1,204 @@ +using OpenTK.Graphics; +using RecrownedGTK.Graphics.Render; +using System.Collections.Generic; + +namespace RecrownedGTK.Graphics.UI.ScreenSystem +{ + /// + /// Represents one of the poosible states a screen can be in. + /// + public enum ScreenState { + /// + /// Screen is transitioning in. + /// + EnterTransition, + /// + /// Screen is transitioning out. + /// + ExitTransition, + /// + /// Screen is currently displayed normally without transition. + /// + Normal + } + + /// + /// A screen represents a virtual system of management that controls an system of items to be displayed. + /// + public class Screen + { + /// + /// Transitions to apply. + /// + public readonly List Transitions; + + /// + /// Whether or not to continue rendering this screen onto a buffer for the benefit of the next screen to use as a transition. + /// + public bool UseRenderTargetForExitTransition { get; private set; } + + /// + /// The background color to be used to clear the screen. + /// + public Color4 BackgroundColor; + + /// + /// The next screen to be displayed after exit transition finishes. May be null, leading to transition to previous screen or loading screen. + /// + public Screen NextScreen { get; set; } + + /// + /// The current window dimensions. + /// + public int width, height; + + /// + /// Current state of the screen. + /// + public ScreenState State { get; private set; } + + /// + /// Creates a new screen. + /// + /// True to start in entering transition state. + public Screen(bool useEnterTransition = false) + { + State = useEnterTransition ? ScreenState.EnterTransition : ScreenState.Normal; + Transitions = new List(); + } + + /// + /// Called when screen size is set. + /// + /// The width of the screen. + /// The height of the screen. + public virtual void ApplySize(int width, int height) + { + this.width = width; + this.height = height; + } + + /// + /// Called to update the screen. + /// + /// Game time information. + public virtual void Update(GameTime gameTime) + { + + } + + /// + /// Called to draw this screen. + /// + /// SpriteBatch to use. + public virtual void Draw(ConsistentSpriteBatch spriteBatch) + { + foreach (ITransition transition in Transitions) + { + transition.DrawTransition(spriteBatch); + } + } + + /// + /// Updates the transition based on the current state of the screen. + /// + /// Time passed since last frame in seconds. + /// If the this transition should wait. + /// Only returns true if exit transition is complete. Returns false otherwise. + public bool UpdateTransition(double delta, bool waiting) + { + switch (State) + { + case ScreenState.EnterTransition: + EnteringTransition(delta, waiting); + break; + case ScreenState.ExitTransition: + return ExitingTransition(delta, waiting); + } + + return false; + } + + private void EnteringTransition(double delta, bool waiting) + { + bool complete = true; + for (int transitionID = 0; transitionID < Transitions.Count; transitionID++) + { + ITransition transition = Transitions[transitionID]; + if (!transition.UpdateEnteringTransition(delta, waiting)) + { + complete = false; + } + } + if (complete && State != ScreenState.ExitTransition) + { + State = ScreenState.Normal; + } + } + + private bool ExitingTransition(double delta, bool waiting) + { + bool complete = true; + foreach (ITransition transition in Transitions) + { + if (!transition.UpdateExitingTransition(delta, waiting)) + { + complete = false; + } + + } + return complete; + } + + /// + /// Called when the screen is shown. + /// + public virtual void Show() + { + foreach (ITransition transition in Transitions) + { + transition.InitiateTransition(new Rectangle(0, 0, width, height)); + } + } + + /// + /// Called when this screen is no longer the displayed screen. + /// + public virtual void Hide() + { + + } + + /// + /// Called whenever the status of assets changes from being loaded to unloaded or unloaded to loaded. + /// + /// True for loaded, false for unloaded. + public virtual void AssetLoadStateChange(bool state) + { + + } + + + /// + /// Call this to begin exit transition. + /// + /// Whether or not to use a render target for the next screen to use. + public void StartExitTransition(bool UseRenderTargetForExitTransition = false) + { + this.UseRenderTargetForExitTransition = UseRenderTargetForExitTransition; + State = ScreenState.ExitTransition; + } + + /// + /// Called everytime the previous screen frame buffer updates. Used for transition purposes. + /// + /// The previous screen's render buffer. + public virtual void UpdatePreviousScreenFrame(RenderTarget2D previousScreenFrame) + { + foreach (ITransition transition in Transitions) + { + transition.UpdatePreviousScreenFrame(previousScreenFrame); + } + } + } +} diff --git a/RecrownedGTK/Graphics/UI/ScreenSystem/ScreenManager.cs b/RecrownedGTK/Graphics/UI/ScreenSystem/ScreenManager.cs new file mode 100644 index 0000000..9cd7aa6 --- /dev/null +++ b/RecrownedGTK/Graphics/UI/ScreenSystem/ScreenManager.cs @@ -0,0 +1,213 @@ +using OpenTK.Graphics; +using RecrownedGTK.Graphics.Render; +using System; +using System.Diagnostics; + +namespace RecrownedGTK.Graphics.UI.ScreenSystem +{ + /// + /// Called when the first screen is being shown. + /// + /// The screen to show after the loading screen. + public delegate void NeedNextScreen(Screen screen); + + /// + /// A manager for screens. Helps with transitions and updating screens as well as resizes. + /// + public class ScreenManager : IDisposable + { + bool disposed = false; + + /// + /// Called when the first loading screen is done, and needs to show the landing screen. + /// + public event NeedNextScreen NeedNextScreen; + + /// + /// The settings this manager will use to begin a sprite batch. + /// + private GraphicsDeviceManager graphics; + private Screen previousScreen; + private RenderTarget2D previousScreenRenderTarget; + private Screen currentScreen; + private bool firstScreenChangeComplete; + private bool resizing; + /// + /// Currently displayed screen. + /// + public Screen Screen + { + get + { + return currentScreen; + } + set + { + previousScreen = currentScreen; + currentScreen = value; + if (Screen.width != graphics.PreferredBackBufferWidth || Screen.height != graphics.PreferredBackBufferHeight) + { + Screen.ApplySize(graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); + } + if (previousScreen != null && previousScreenRenderTarget == null && previousScreen.UseRenderTargetForExitTransition) + { + previousScreenRenderTarget = new RenderTarget2D(graphics.GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); + } + graphics.GraphicsDevice.SetRenderTarget(previousScreenRenderTarget); + graphics.GraphicsDevice.Clear(Color4.Black); + + Debug.WriteLine("Showing " + value.GetType().Name); + Screen.Show(); + previousScreen?.Hide(); + } + } + + /// + /// Creates a screen manager that helps update, draw, and manage multiple screens and their transitions. Uses its own . + /// + /// The graphics device manager to be used. + public ScreenManager(GraphicsDeviceManager graphicsDeviceManager) + { + graphics = graphicsDeviceManager ?? throw new ArgumentNullException("Graphics device manager argument cannot be null."); + } + + /// + /// Updates the screens. Should be called once every frame. + /// + /// Contains the time that has passed from the last frame. + /// Whether or not there is something a transition should be waiting for. Usually used to wait for assets to complete loading. + public void UpdateCurrentScreen(GameTime gameTime, bool waiting) + { + waiting = !waiting; + switch (Screen.State) + { + case ScreenState.EnterTransition: + if (previousScreen != null && previousScreen.UseRenderTargetForExitTransition) + { + previousScreen.UpdateTransition(gameTime.ElapsedGameTime.TotalSeconds, waiting); + } + Screen.UpdateTransition(gameTime.ElapsedGameTime.TotalSeconds, waiting); + break; + case ScreenState.ExitTransition: + if (Screen.UseRenderTargetForExitTransition || Screen.UpdateTransition(gameTime.ElapsedGameTime.TotalSeconds, waiting)) + { + if (!firstScreenChangeComplete) + { + firstScreenChangeComplete = true; + OnNeedNextScreen(Screen); + } + if (Screen.NextScreen != null) + { + Debug.WriteLine("Changing to the next given screen."); + Screen = Screen.NextScreen; + } + else if (previousScreen != null) + { + Debug.WriteLine("Changing to previous screen."); + Screen = previousScreen; + } + else + { + throw new InvalidOperationException("No next screen provided and no previous screen to return to."); + } + } + break; + } + Screen.Update(gameTime); + } + + /// + /// Renders screen into window. + /// Starts and ends the given . + /// + /// The consistent sprite batch to use. Needs to be consistent as changing render targets requires ending and beginning the sprite batch. + public void DrawScreen(ConsistentSpriteBatch consistentSpriteBatch) + { + if (consistentSpriteBatch == null) + { + throw new ArgumentNullException(nameof(consistentSpriteBatch)); + } + if (Screen == null) return; + + graphics.GraphicsDevice.Clear(Screen.BackgroundColor); + if (Screen.State == ScreenState.EnterTransition && previousScreen != null && previousScreen.UseRenderTargetForExitTransition) + { + graphics.GraphicsDevice.SetRenderTarget(previousScreenRenderTarget); + graphics.GraphicsDevice.Clear(previousScreen.BackgroundColor); + + consistentSpriteBatch.Begin(); + previousScreen.Draw(consistentSpriteBatch); + consistentSpriteBatch.End(); + + graphics.GraphicsDevice.SetRenderTarget(null); + Screen.UpdatePreviousScreenFrame(previousScreenRenderTarget); + } + + consistentSpriteBatch.Begin(); + Screen.Draw(consistentSpriteBatch); + consistentSpriteBatch.End(); + } + + /// + /// Should be called when resize is occurring to change to a loading screen. + /// This will notify the screen of the status of the assets, change the screen to a loading screen, and dispose of the previous screen buffer. + /// + /// The loading screen to change to. + public void Resize(LoadingScreen loadingScreen) + { + if (resizing) throw new InvalidOperationException("Already resizing."); + resizing = true; + if (Screen != null) { + Screen.AssetLoadStateChange(false); + Screen = loadingScreen; + previousScreenRenderTarget.Dispose(); + previousScreenRenderTarget = null; + } + } + + /// + /// Notifies all screen that assets have completed being loaded after a resize. + /// + public void PostResize() + { + if (!resizing) throw new InvalidOperationException("Was never resizing."); + Screen.AssetLoadStateChange(true); + } + + private void OnNeedNextScreen(Screen screen) + { + NeedNextScreen?.Invoke(screen); + } + + /// + /// Disposes this. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// An overridable dispose. + /// + /// True of user invoked dispose called. + public virtual void Dispose(bool disposing) + { + if (disposed) throw new ObjectDisposedException(GetType().Name); + if (disposing) + { + previousScreenRenderTarget?.Dispose(); + } + disposed = true; + } + + /// + /// Destructor. + /// + ~ScreenManager() + { + Dispose(false); + } + } +} diff --git a/RecrownedGTK/Graphics/UI/SkinSystem/Definitions/ButtonSkinDefinition.cs b/RecrownedGTK/Graphics/UI/SkinSystem/Definitions/ButtonSkinDefinition.cs new file mode 100644 index 0000000..7a56ce6 --- /dev/null +++ b/RecrownedGTK/Graphics/UI/SkinSystem/Definitions/ButtonSkinDefinition.cs @@ -0,0 +1,28 @@ +using RecrownedGTK.Graphics.UI.Modular.Modules.Interactive; + +namespace RecrownedGTK.Graphics.UI.SkinSystem.Definitions +{ + /// + /// Skin definition for a button. + /// + public class ButtonSkinDefinition : SkinDefinitionData + { + /// + /// Names for the regions in the texture atlas respectively. + /// + public string upRegion, downRegion, disabledRegion, selectedRegion; + + /// + /// Constructs the definition with minimum requirements. + /// + /// Name of region specifying the texture shown for when the button is pressed down. + /// Name of region specifying the texture shown for when the button is not pressed. + public ButtonSkinDefinition(string downRegion, string upRegion) + { + UIModuleType = typeof(Button); + this.downRegion = downRegion; + this.upRegion = upRegion; + } + + } +} diff --git a/RecrownedGTK/Graphics/UI/SkinSystem/Definitions/SkinDefinition.cs b/RecrownedGTK/Graphics/UI/SkinSystem/Definitions/SkinDefinition.cs new file mode 100644 index 0000000..f25d9c7 --- /dev/null +++ b/RecrownedGTK/Graphics/UI/SkinSystem/Definitions/SkinDefinition.cs @@ -0,0 +1,20 @@ +using System; + +namespace RecrownedGTK.Graphics.UI.SkinSystem.Definitions +{ + /// + /// A definition containing the data for the skin system. Needs to follow data transfer object model. + /// + public abstract class SkinDefinitionData + { + /// + /// The full name of the UI module this definition defines. + /// + public string uiModuleTypeFullName; + + /// + /// Sets the module type by setting . + /// + public Type UIModuleType { set { uiModuleTypeFullName = value.FullName; } } + } +} diff --git a/RecrownedGTK/Graphics/UI/SkinSystem/Definitions/TextButtonSkinDefinition.cs b/RecrownedGTK/Graphics/UI/SkinSystem/Definitions/TextButtonSkinDefinition.cs new file mode 100644 index 0000000..c55fb43 --- /dev/null +++ b/RecrownedGTK/Graphics/UI/SkinSystem/Definitions/TextButtonSkinDefinition.cs @@ -0,0 +1,25 @@ +using RecrownedGTK.Graphics.UI.Modular.Modules.Interactive; + +namespace RecrownedGTK.Graphics.UI.SkinSystem.Definitions +{ + /// + /// Definition for a text button for a skin theme. + /// + public class TextButtonSkinDefinition : ButtonSkinDefinition + { + /// + /// Name of color from the skin to use for the font. + /// + public string fontColor; + + /// + /// Creates this definition with the most minimal requirements. + /// + /// Texture region from skin that represents when the button is pressed down. + /// The texture region that represents when the button is not pressed. + public TextButtonSkinDefinition(string downRegion, string upRegion) : base(downRegion, upRegion) + { + UIModuleType = typeof(TextButton); + } + } +} diff --git a/RecrownedGTK/Graphics/UI/SkinSystem/Definitions/UIScrollableSkinDefinition.cs b/RecrownedGTK/Graphics/UI/SkinSystem/Definitions/UIScrollableSkinDefinition.cs new file mode 100644 index 0000000..c9d4ba3 --- /dev/null +++ b/RecrownedGTK/Graphics/UI/SkinSystem/Definitions/UIScrollableSkinDefinition.cs @@ -0,0 +1,32 @@ +using RecrownedGTK.Graphics.UI.Modular.Modules; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RecrownedGTK.Graphics.UI.SkinSystem.Definitions +{ + /// + /// Skin definition of a scroll module. + /// + public class UIScrollableSkinDefinition : SkinDefinitionData + { + /// + /// Name of the region that specifies the texture needed. + /// + public string horizontalBar, verticalBar, horizontalBarTrack, verticalBarTrack, background; + + /// + /// Instantiates the definition with the minimum requirements. + /// + /// Name of the region used by the skin that defines what the horizontal scroll bar looks like. + /// Name of the region used by the skin that defines what the vertical scroll bar looks like. + public UIScrollableSkinDefinition(string horizontalBar, string verticalBar) + { + this.horizontalBar = horizontalBar; + this.verticalBar = verticalBar; + UIModuleType = typeof(UIScrollable); + } + } +} diff --git a/RecrownedGTK/Graphics/UI/SkinSystem/ISkin.cs b/RecrownedGTK/Graphics/UI/SkinSystem/ISkin.cs new file mode 100644 index 0000000..8f4671d --- /dev/null +++ b/RecrownedGTK/Graphics/UI/SkinSystem/ISkin.cs @@ -0,0 +1,54 @@ +using RecrownedGTK.Graphics.Render; +using RecrownedGTK.Types; +using RecrownedGTK.Graphics.UI.SkinSystem.Definitions; +using OpenTK.Graphics; +using OpenTK; + +namespace RecrownedGTK.Graphics.UI.SkinSystem +{ + /// + /// The output requirements of a skin. This allows for very customized skin systems if needed. + /// + public interface ISkin + { + /// + /// The texture for the cursor. + /// + Texture2D CursorTexture { get; } + + /// + /// Draws a region from the texture atlas. + /// + /// Region to draw. + /// The color to tint the region. + /// The batch to use. + /// The destination to draw to. + /// The rotation to use in radians. + /// The origin for the rotation. + void Draw(string regionName, string color, ConsistentSpriteBatch batch, Rectangle destination, float rotation = 0, Vector2 origin = default(Vector2)); + + /// + /// Returns a with given name of defined color; + /// Should use value "default" if is null. + /// + /// Name of defined color. + /// The defined color based on the name given. + Color4 GetColor(string name = null); + + /// + /// Returns a with given name of region. + /// + /// Name of region. + /// Whether or not the region is required. If true, it will throw an error if the region does not exist while if false, will return null if the region does not exist. + /// The region corresponding to the name and may return null depending on if the region exists, and is required. + TextureAtlas.Region GetTextureAtlasRegion(string name, bool required = false); + + /// + /// Returns the proper definition for the given parameters or throws exception in the case the requested definition does not exist. + /// + /// Convenience to cast to the needed definition type. + /// The name of the definition. Default is null and will be replaced with "default" for name. + /// The definition cast to T. + T ObtainDefinition(string definitionName = null) where T : SkinDefinitionData; + } +} \ No newline at end of file diff --git a/RecrownedGTK/Graphics/UI/SkinSystem/MergedSkin.cs b/RecrownedGTK/Graphics/UI/SkinSystem/MergedSkin.cs new file mode 100644 index 0000000..20d0b7d --- /dev/null +++ b/RecrownedGTK/Graphics/UI/SkinSystem/MergedSkin.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using RecrownedGTK.Graphics.Render; +using RecrownedGTK.Types; +using RecrownedGTK.Graphics.UI.SkinSystem.Definitions; +using OpenTK.Graphics; +using OpenTK; + +namespace RecrownedGTK.Graphics.UI.SkinSystem +{ + internal class MergedSkin : ISkin + { + /// + /// The skin to try to use first. + /// + public ISkin mainSkin; + + /// + /// The fallback skin. + /// + public ISkin alternateSkin; + + public Texture2D CursorTexture + { + get + { + if (mainSkin.CursorTexture != null) { + return mainSkin.CursorTexture; + } else { + return alternateSkin.CursorTexture; + } + } + } + + public void Draw(string regionName, string color, ConsistentSpriteBatch batch, Rectangle destination, float rotation = 0, Vector2 origin = default(Vector2)) + { + try + { + mainSkin.Draw(regionName, color, batch, destination, rotation, origin); + } catch (KeyNotFoundException) + { + alternateSkin.Draw(regionName, color, batch, destination, rotation, origin); + } catch (NullReferenceException) + { + alternateSkin.Draw(regionName, color, batch, destination, rotation, origin); + } + } + + public Color4 GetColor(string name) + { + try + { + return mainSkin.GetColor(name); + } catch (KeyNotFoundException) + { + return alternateSkin.GetColor(name); + } + catch (NullReferenceException) + { + return alternateSkin.GetColor(name); + } + } + + public TextureAtlas.Region GetTextureAtlasRegion(string name, bool required = false) + { + try + { + return mainSkin.GetTextureAtlasRegion(name); + } + catch (NullReferenceException) + { + return alternateSkin.GetTextureAtlasRegion(name, required); + } + } + + public T ObtainDefinition(string definitionName = null) where T : SkinDefinitionData + { + try + { + return mainSkin.ObtainDefinition(definitionName); + } + catch (NullReferenceException) + { + return alternateSkin.ObtainDefinition(definitionName); + } + } + } +} diff --git a/RecrownedGTK/Graphics/UI/SkinSystem/Skin.cs b/RecrownedGTK/Graphics/UI/SkinSystem/Skin.cs new file mode 100644 index 0000000..6f53514 --- /dev/null +++ b/RecrownedGTK/Graphics/UI/SkinSystem/Skin.cs @@ -0,0 +1,183 @@ +using RecrownedGTK.Graphics.Render; +using RecrownedGTK.Types; +using RecrownedGTK.Graphics.UI.SkinSystem.Definitions; +using System; +using System.Collections.Generic; +using OpenTK.Graphics; +using OpenTK; + +namespace RecrownedGTK.Graphics.UI.SkinSystem +{ + /// + /// A skin is used to group a theme which can then be applied to the UI via the use of modules. + /// + public class Skin : IDisposable, ISkin + { + /// + /// Whether or not this skin is completed being built and thus ready to use. + /// + public bool Laminated { get; private set; } + private bool disposed; + + private TextureAtlas textureAtlas; + + Dictionary colors; + readonly Dictionary definitionOfType; + readonly Dictionary> definitions; + + /// + /// The texture for the cursor. + /// + public virtual Texture2D CursorTexture { get; private set; } + + /// + /// Creates a basic unfilled skin. + /// + /// The texture atlas to use for this skin. + /// The texture the cursor will be. + public Skin(TextureAtlas textureAtlas, Texture2D cursorTexture) + { + this.textureAtlas = textureAtlas; + this.CursorTexture = cursorTexture; + colors = new Dictionary(); + definitionOfType = new Dictionary(); + definitions = new Dictionary>(); + } + + /// + /// Returns a with given name of region. Null values acceptable. Will return null if parameter is null. + /// + /// Name of region. + /// Whether or not this texture is mandatory for the module to work. If true, will throw error on failing to retrieve. + /// The region corresponding to the name or null if the requested region doesn't exist. + public TextureAtlas.Region GetTextureAtlasRegion(string name, bool required = false) + { + if (!required && (name == null || !textureAtlas.ContainsRegion(name))) + { + return null; + } else + { + return textureAtlas[name]; + } + } + + /// + /// Returns a with given name of defined color; + /// + /// Name of defined color. Will use "default" if null. Default value is null. + /// The defined color based on the name given. + public Color4 GetColor(string name = null) + { + if (name == null) name = "default"; + return colors[name]; + } + + /// + /// Draws a region from the texture atlas. + /// + /// Region to draw. + /// The color to tint the region. + /// The batch to use. + /// The destination to draw to. + /// The rotation to use in radians. + /// The origin for the rotation. + public void Draw(string regionName, string color, ConsistentSpriteBatch batch, Rectangle destination, float rotation = 0, Vector2 origin = default(Vector2)) + { + if (disposed) throw new ObjectDisposedException(GetType().Name); + textureAtlas.Draw(regionName, batch, destination, colors[color], rotation, origin); + } + + private SkinDefinitionData ObtainDefinition(string typeFullName, string definitionName) + { + if (disposed) throw new ObjectDisposedException(GetType().Name); + if (!Laminated) throw new InvalidOperationException("Skin has yet to be laminated yet."); + if (definitionName == null) definitionName = "default"; + if (!definitions.ContainsKey(typeFullName)) throw new KeyNotFoundException("Could not find any skin definition defining type \"" + typeFullName + "\""); + if (!definitions[typeFullName].ContainsKey(definitionName)) throw new KeyNotFoundException("Could not find skin definition defining type \"" + typeFullName + "\" with name \"" + definitionName + "\""); + return definitions[typeFullName][definitionName]; + } + + /// + /// Returns the proper definition for the given parameters or throws exception in the case the requested definition does not exist. + /// + /// Convenience to cast to the needed definition type. + /// The name of the definition. + /// The definition cast to T. + public T ObtainDefinition(string definitionName = null) where T : SkinDefinitionData + { + return (T)ObtainDefinition(definitionOfType[typeof(T).FullName], definitionName); + } + + /// + /// Adds the definition. + /// + /// The name of the definition. Default (if left blank) name is "default". + /// The definition itself. + public void AddDefinition(SkinDefinitionData skinDefinition, string definitionName = null) + { + if (disposed) throw new ObjectDisposedException(GetType().Name); + if (Laminated) throw new InvalidOperationException("This skin has been laminated and cannot be edited."); + if (definitionName == null) definitionName = "default"; + if (!definitions.ContainsKey(skinDefinition.uiModuleTypeFullName)) + { + definitionOfType.Add(skinDefinition.GetType().FullName, skinDefinition.uiModuleTypeFullName); + definitions.Add(skinDefinition.uiModuleTypeFullName, new Dictionary()); + } + else if (definitions[skinDefinition.uiModuleTypeFullName].ContainsKey(definitionName)) throw new ArgumentException("Type of definition with that name already exists!"); + + definitions[skinDefinition.uiModuleTypeFullName].Add(definitionName, skinDefinition); + } + + /// + /// Adds color to skin. + /// + /// + /// + public void AddColor(string name, Color4 color) + { + if (Laminated) throw new InvalidOperationException("This skin has been laminated and cannot be edited."); + colors.Add(name, color); + } + + /// + /// Laminates the skin. Making sure no more additions are done and sets the skin to be ready for use. + /// Needs to be called before any use of skin. Building skin needs to be done before lamination. + /// + public void Laminate() + { + Laminated = true; + } + + /// + /// Disposes and the holding the cursor texture. + /// + public void Dispose() + { + if (disposed) throw new ObjectDisposedException(GetType().Name); + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Overridable dispose function. + /// + /// true when it's a user call to dispose. + public virtual void Dispose(bool disposing) + { + disposed = true; + if (disposing && !disposed) + { + textureAtlas.Dispose(); + CursorTexture.Dispose(); + } + } + + /// + /// Destructor. Calls the dispose with false. + /// + ~Skin() + { + Dispose(false); + } + } +} diff --git a/RecrownedGTK/Graphics/UI/SkinSystem/SkinManager.cs b/RecrownedGTK/Graphics/UI/SkinSystem/SkinManager.cs new file mode 100644 index 0000000..858de36 --- /dev/null +++ b/RecrownedGTK/Graphics/UI/SkinSystem/SkinManager.cs @@ -0,0 +1,239 @@ +using OpenTK; +using OpenTK.Graphics; +using Newtonsoft.Json; +using RecrownedGTK.Data; +using RecrownedGTK.Types; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; + +namespace RecrownedGTK.Graphics.UI.SkinSystem +{ + /// + /// Called when the skin manager has completed a async action. + /// + /// The completed action. + public delegate void AsyncComplete(SkinManager.Action actionCompleted); + + /// + /// Manages reference to default and loading of custom skins. + /// + public class SkinManager + { + private const string EXTENSION = ".rbskin"; + private readonly MergedSkin mergedSkin = new MergedSkin(); + private Thread thread; + private Action action; + private GraphicsDevice graphicsDevice; + private string selectedSkinPath; + private SkinData skinDataToUse; + + /// + /// Whether or not the skin manager is set up with a and . + /// + public bool MergingSkins { get { return (loadedSkin != null && SkinUseable); } } + + /// + /// Whether or not this manager has been set up with at least a base skin. + /// + public bool SkinUseable { get { return BaseSkin == null; } } + + /// + /// The list of paths for all found skins by . May be null. + /// + public volatile List skinPaths; + + /// + /// The event that is called when a state changes. + /// + public event AsyncComplete AsyncCompleteEvent; + + /// + /// The various possible states a skin manager could be in. + /// + public enum Action + { + /// + /// After a search has completed. + /// + SEARCH, + /// + /// Having the skin generated to be in a useable state. + /// + LOAD + } + + /// + /// the skin that favors the selected skin, but still has a fallback to the default skin in case anything is missing. + /// + public ISkin Skin { get { return mergedSkin; } } + + /// + /// The user loaded skin resulted from asynchronous . + /// + public ISkin loadedSkin { get { return mergedSkin.mainSkin; } private set { mergedSkin.mainSkin = value; } } + + /// + /// The default skin in case the selected skin doesn't cover a specific definition or color. + /// + public ISkin BaseSkin { get { return mergedSkin.alternateSkin; } set { mergedSkin.alternateSkin = value; } } + + /// + /// The directory that contains the skins. + /// + public string skinsDirectory; + + /// + /// Performs a recursive asynchronous search of the directory given in a path set by . + /// + public void SearchSkinDirectory() + { + action = Action.SEARCH; + AttemptAsync(); + } + + /// + /// Reads skin data if extension is valid. + /// + /// the path to load from. + /// A that holds all the information and some metadata for the loaded skin. + public SkinData ReadSkinData(string path) + { + if (path.ToLower().EndsWith(EXTENSION)) + { + return JsonConvert.DeserializeObject(File.ReadAllText(path)); + } + throw new ArgumentException("The path given does not point to a file with the required extension \"" + EXTENSION + "\" rather, has \"" + Path.GetExtension(path) + "\"."); + } + + /// + /// loads a skin asynchronously to the . + /// + /// The path pointing to the file with the extension "". + /// Graphics device to use for texture creation. + public void LoadSkin(string path, GraphicsDevice graphicsDevice) + { + action = Action.LOAD; + this.graphicsDevice = graphicsDevice ?? throw new ArgumentNullException("Requires graphics device to create textures."); + selectedSkinPath = path ?? throw new ArgumentNullException("Requires path to find textures."); + skinDataToUse = ReadSkinData(path); + + AttemptAsync(); + } + + private void AttemptAsync() + { + if (thread != null && thread.IsAlive) throw new InvalidOperationException("Already performing task: " + action); + thread = new Thread(ThreadStart); + } + + private void ThreadStart() + { + switch (action) + { + case Action.SEARCH: + SearchForSkins(); + OnAsyncComplete(action); + break; + case Action.LOAD: + loadedSkin = LoadSkinFromData(skinDataToUse, selectedSkinPath, graphicsDevice); + OnAsyncComplete(action); + break; + } + } + + private void SearchForSkins() + { + skinPaths.Clear(); + skinPaths = RecursiveSkinSearch(skinsDirectory); + } + + private Skin LoadSkinFromData(SkinData skinData, string path, GraphicsDevice graphicsDevice) + { + TextureAtlasDataReader textureAtlasDataReader = new TextureAtlasDataReader(); + FileInfo[] skinFiles = Directory.GetParent(path).GetFiles(); + Dictionary filePath = new Dictionary(); + for (int i = 0; i < skinFiles.Length; i++) + { + filePath.Add(skinFiles[i].Name, skinFiles[i].FullName); + } + TextureAtlasDataReader tatlasDataReader = new TextureAtlasDataReader(); + TextureAtlasData atlasData; + atlasData = JsonConvert.DeserializeObject(File.ReadAllText(filePath[skinData.nameOfTextureAtlas])); + Texture2D atlasTexture; + using (FileStream stream = new FileStream(filePath[atlasData.textureName], FileMode.Open)) + { + atlasTexture = Texture2D.FromStream(graphicsDevice, stream); + Vector4[] data = new Vector4[atlasTexture.Width * atlasTexture.Height]; + atlasTexture.GetData(data); + for (int i = 0; i < data.Length; i++) + { + Color4Ext.FromNonPremultiplied(ref data[i]); + } + atlasTexture.SetData(data); + } + TextureAtlas.Region[] regions = textureAtlasDataReader.GenerateAtlasRegionsFromData(atlasData, atlasTexture); + TextureAtlas textureAtlas = new TextureAtlas(atlasTexture, regions); + Texture2D cursorTexture; + if (Path.HasExtension(skinData.cursorTextureName) && filePath.ContainsKey(skinData.cursorTextureName)) + { + using (FileStream stream = new FileStream(filePath[skinData.cursorTextureName], FileMode.Open)) + { + cursorTexture = Texture2D.FromStream(graphicsDevice, stream); + Vector4[] data = new Vector4[cursorTexture.Width * cursorTexture.Height]; + atlasTexture.GetData(data); + for (int i = 0; i < data.Length; i++) + { + Color4Ext.FromNonPremultiplied(ref data[i]); + } + cursorTexture.SetData(data); + } + } + else + { + cursorTexture = textureAtlas[skinData.cursorTextureName].AsTexture2D(graphicsDevice); + } + Skin skin = new Skin(new TextureAtlas(atlasTexture, regions), cursorTexture); + + for (int i = 0; i < skinData.colors.Length; i++) + { + SkinData.ColorData colorData = skinData.colors[i]; + skin.AddColor(colorData.name, new Color4(colorData.r, colorData.g, colorData.b, colorData.a)); + } + + for (int i = 0; i < skinData.definitions.Length; i++) + { + SkinData.DefinitionData definitionData = skinData.definitions[i]; + skin.AddDefinition(definitionData.skin, definitionData.name); + } + + return skin; + } + + private List RecursiveSkinSearch(string path) + { + string[] files = Directory.GetFiles(path); + string[] folders = Directory.GetDirectories(path); + List skins = new List(); + for (int i = 0; i < files.Length; i++) + { + if (files[i].ToLower().EndsWith(EXTENSION)) + { + skins.Add(files[i]); + } + } + for (int i = 0; i < folders.Length; i++) + { + skins.AddRange(RecursiveSkinSearch(folders[i])); + } + + return skins; + } + + private void OnAsyncComplete(Action newState) + { + AsyncCompleteEvent?.Invoke(newState); + } + } +} diff --git a/RecrownedGTK/Graphics/VertexArrayHandle.cs b/RecrownedGTK/Graphics/VertexArrayHandle.cs new file mode 100644 index 0000000..47c931b --- /dev/null +++ b/RecrownedGTK/Graphics/VertexArrayHandle.cs @@ -0,0 +1,39 @@ +using System; +using OpenTK.Graphics.OpenGL; +namespace RecrownedGTK.Graphics { + public class VertexArrayHandle : IDisposable { + private bool disposed; + private int handle; + private bool begun; + public VertexArrayHandle() { + handle = GL.GenVertexArray(); + } + + public void Begin() { + if (begun) throw new InvalidOperationException("The VertexArrayHandle has already been started."); + GL.BindVertexArray(handle); + begun = true; + } + public void End() { + if (!begun) throw new InvalidOperationException("The VertexArrayHandle has never been started."); + GL.BindVertexArray(0); + begun = false; + } + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + protected virtual void Dispose(bool disposing) { + if (disposed) return; + if (disposing) { + + } + GL.BindVertexArray(0); + GL.DeleteVertexArray(handle); + disposed = true; + } + ~VertexArrayHandle() { + Dispose(false); + } + } +} \ No newline at end of file diff --git a/RecrownedGTK/Graphics/VertexBufferHandle.cs b/RecrownedGTK/Graphics/VertexBufferHandle.cs new file mode 100644 index 0000000..6f856ad --- /dev/null +++ b/RecrownedGTK/Graphics/VertexBufferHandle.cs @@ -0,0 +1,46 @@ +using System; +using OpenTK.Graphics.OpenGL; +namespace RecrownedGTK.Graphics { + public class VertexBufferHandle : IDisposable { + private bool disposed; + private int handle; + public bool Bound { + get; + private set; + } + public VertexBufferHandle() { + handle = GL.GenBuffer(); + } + public void Bind() { + Bound = true; + GL.BindBuffer(BufferTarget.ArrayBuffer, handle); + } + public void Unbind() { + Bound = false; + GL.BindBuffer(BufferTarget.ArrayBuffer, 0); + } + public void Buffer(float[] vertices, BufferUsageHint hint = BufferUsageHint.StaticDraw) { + if (Bound) { + GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, hint); + return; + } + throw new InvalidOperationException("Buffer is not bound."); + } + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + protected virtual void Dispose(bool disposing) { + if (disposed) return; + if (disposing) { + + } + Unbind(); + GL.DeleteBuffer(handle); + disposed = true; + } + ~VertexBufferHandle() { + Dispose(false); + } + } +} \ No newline at end of file diff --git a/RecrownedGTK/ParticleSystem/Particle.cs b/RecrownedGTK/ParticleSystem/Particle.cs new file mode 100644 index 0000000..41f7e9c --- /dev/null +++ b/RecrownedGTK/ParticleSystem/Particle.cs @@ -0,0 +1,6 @@ +namespace RecrownedGTK.ParticleSystem +{ + class Particle + { + } +} diff --git a/RecrownedGTK/Persistence/PreferencesManager.cs b/RecrownedGTK/Persistence/PreferencesManager.cs new file mode 100644 index 0000000..c2de213 --- /dev/null +++ b/RecrownedGTK/Persistence/PreferencesManager.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Xml.Serialization; + +namespace RecrownedGTK.Persistence +{ + /// + /// Manages a bundle of preferences. + /// + public class PreferencesManager + { + private readonly Dictionary preferenceList; + string savePath; + XmlSerializer xmlSerializer; + + /// + /// Constructs the preference manager. + /// + /// The path of the directory in which the preferences should be saved. + /// The preferences to be serialized and unserialized in XML format. + public PreferencesManager(string savePath, params object[] preferences) + { + this.savePath = savePath; + Directory.CreateDirectory(savePath); + preferenceList = new Dictionary(); + Debug.WriteLine("Preference manager setting up..."); + Type[] preferenceTypes = new Type[preferences.Length]; + for (int prefID = 0; prefID < preferences.Length; prefID++) + { + preferenceList.Add(preferences[prefID].GetType(), preferences[prefID]); + preferenceTypes[prefID] = preferences[prefID].GetType(); + Debug.WriteLine(preferences[prefID] + " added to list of preferences"); + } + + xmlSerializer = new XmlSerializer(typeof(object), preferenceTypes); + } + + /// + /// Returns the preference by type. + /// + /// The preference needed. + /// The preference needed. + public T GetPreferences() + { + return (T)preferenceList[typeof(T)]; + } + + /// + /// Loads preferences. + /// + public void Load() + { + List keys = new List(preferenceList.Keys); + + foreach (Type key in keys) + { + if (!LoadSpecific(key)) + { + SaveSpecific(key); + } + } + } + + /// + /// Saves preferences. + /// + public void Save() + { + foreach (KeyValuePair prefs in preferenceList) + { + SaveSpecific(prefs.Key); + } + } + + private bool LoadSpecific(Type preference) + { + string path = savePath + "/" + preference.Name + ".xml"; + try + { + if (File.Exists(path)) + { + Stream stream = new FileStream(path, FileMode.Open); + preferenceList[preference] = xmlSerializer.Deserialize(stream); + stream.Close(); + return true; + } + } catch (InvalidOperationException) + { + return false; + } + return false; + } + + private void SaveSpecific(Type preference) + { + Stream stream = new FileStream(savePath + "/" + preference.Name + ".xml", FileMode.Create); + xmlSerializer.Serialize(stream, preferenceList[preference]); + stream.Close(); + } + } +} diff --git a/RecrownedGTK/RecrownedAthenaeum.csproj b/RecrownedGTK/RecrownedAthenaeum.csproj new file mode 100644 index 0000000..fc2659a --- /dev/null +++ b/RecrownedGTK/RecrownedAthenaeum.csproj @@ -0,0 +1,9 @@ + + + net48 + + + + + + \ No newline at end of file diff --git a/RecrownedGTK/Types/Extensions.cs b/RecrownedGTK/Types/Extensions.cs new file mode 100644 index 0000000..5d9b6db --- /dev/null +++ b/RecrownedGTK/Types/Extensions.cs @@ -0,0 +1,33 @@ +using System; +using OpenTK.Graphics; +using OpenTK; +namespace RecrownedGTK.Types { + public static class Color4Ext { + public static byte GetRedAsByte(this Color4 color) { + return (byte) (color.R * Byte.MaxValue); + } + public static byte GetGreenAsByte(this Color4 color) { + return (byte) (color.G * Byte.MaxValue); + } + public static byte GetBlueAsByte(this Color4 color) { + return (byte) (color.B * Byte.MaxValue); + } + public static byte GetAlphaAsByte(this Color4 color) { + return (byte) (color.A * Byte.MaxValue); + } + public static void MultiplyByFloat(this Color4 color, float val) { + color.A *= val; + color.R *= val; + color.G *= val; + color.B *= val; + } + public static Color4 ReturnMultipliedByFloat(this Color4 color, float val) { + Color4 output = new Color4(color.R * val, color.G * val, color.B * val, color.A * val); + return output; + } + public static void FromNonPremultiplied(ref Vector4 vector) { + //Premultiplied. + vector = new Vector4(vector.W * vector.X, vector.W * vector.Y, vector.W * vector.Z, vector.W); + } + } +} \ No newline at end of file diff --git a/RecrownedGTK/Types/IRectangleDrawable.cs b/RecrownedGTK/Types/IRectangleDrawable.cs new file mode 100644 index 0000000..a289f08 --- /dev/null +++ b/RecrownedGTK/Types/IRectangleDrawable.cs @@ -0,0 +1,17 @@ +using RecrownedGTK.Graphics.Render; + +namespace RecrownedGTK.Types +{ + /// + /// A wrapper that makes sure anything implementing can be drawn with options. + /// + public interface IRectangleDrawable + { + byte[] ColorData { + get; + } + float[] vertices { + get; + } + } +} diff --git a/RecrownedGTK/Types/Rectangle.cs b/RecrownedGTK/Types/Rectangle.cs new file mode 100644 index 0000000..be12ee8 --- /dev/null +++ b/RecrownedGTK/Types/Rectangle.cs @@ -0,0 +1,24 @@ +namespace RecrownedGTK.Types +{ + public struct Rectangle + { + public int Width {set; get;} + public int Height {set; get;} + + public int X {set; get;} + public int Y {set; get;} + + public int Area { + get { + return Width * Height; + } + } + + public Rectangle(int x, int y, int width, int height) { + Width = width; + Height = height; + X = x; + Y = y; + } + } +} \ No newline at end of file diff --git a/RecrownedGTK/Types/Resolution.cs b/RecrownedGTK/Types/Resolution.cs new file mode 100644 index 0000000..2936ddf --- /dev/null +++ b/RecrownedGTK/Types/Resolution.cs @@ -0,0 +1,54 @@ +using System; + +namespace RecrownedGTK.Types +{ + /// + /// Holds a width and height while allowing for easier comparison between other s. + /// + public struct Resolution : IComparable + { + /// + /// Dimensions of resolution. + /// + public int Width, Height; + + /// + /// Constructs resolution given the dimensions. + /// + /// Width of resolution. + /// Height of resolution. + public Resolution(int width, int height) + { + Width = width; + Height = height; + } + + /// + /// Compares area of this resolution to another. + /// + /// The other resolution to compare to. + /// A value less than 0 for this being the smaller resolution, 0 for the two being the same in area, and larger for this being the larger in area. + public int CompareTo(Resolution other) + { + return Area() - other.Area(); + } + + /// + /// Gets a string representation of this resolution. + /// + /// "WidthxHeight" + public override string ToString() + { + return Width + "x" + Height; + } + + /// + /// Calculates area of resolution. + /// + /// Area of resolution. + public int Area() + { + return Width * Height; + } + } +}