Files renamed to RecrownedGTK.

This commit is contained in:
Harrison Deng 2020-02-16 21:44:21 -05:00
parent 2c62be1c6b
commit dee5f96ea7
69 changed files with 5891 additions and 0 deletions

View File

@ -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<NinePatchData>
{
public override NinePatchData Import(string filename, ContentImporterContext context)
{
return JsonConvert.DeserializeObject<NinePatchData>(File.ReadAllText(filename));
}
}
}

View File

@ -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<NinePatchData, NinePatchData>
{
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;
}
}
}

View File

@ -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<NinePatchData>
{
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);
}
}
}

View File

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RecrownedGTK\RecrownedGTK.csproj" PrivateAssets="All"/>
</ItemGroup>
</Project>

View File

@ -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<TextureAtlasData>
{
public override TextureAtlasData Import(string filename, ContentImporterContext context)
{
return JsonConvert.DeserializeObject<TextureAtlasData>(File.ReadAllText(filename));
}
}
}

View File

@ -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<TextureAtlasData, TextureAtlasData>
{
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;
}
}
}

View File

@ -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<TextureAtlasData>
{
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);
}
}
}
}
}

View File

@ -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<EngineCommand> commands;
public CommandEngine()
{
commands = new List<EngineCommand>();
}
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<string> argumentsList = new List<string>();
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;
}
}
}

View File

@ -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();
}
}
}

View File

@ -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("--------");
}
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,183 @@
using System;
using System.Text;
namespace RecrownedGTK.Tools.CommandProcessor
{
public abstract class EngineCommand
{
bool caseSensitiveName = false;
/// <summary>
/// the words a user can type that will invoke this command.
/// </summary>
protected string[] invokeStrings;
public string[] InvokeStrings { get { return invokeStrings; } }
/// <summary>
/// Arguments that this command should accept and take into account.
/// </summary>
protected EngineCommandArgument[] commandArguments;
protected string help;
public EngineCommand(params string[] invokeStrings)
{
this.invokeStrings = invokeStrings ?? throw new ArgumentNullException();
}
/// <summary>
/// Whether or not this command should be invoked given the string.
/// </summary>
/// <param name="command">The string that acts as the command.</param>
/// <returns>whether or not this command is invoked.</returns>
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;
}
/// <summary>
/// Runs the command.
/// </summary>
/// <param name="arguments">arguments to be used. May be null.</param>
public abstract void Run(string[] arguments = null);
/// <summary>
/// Check if given argument is viable for command.
/// </summary>
/// <param name="argument">Argument to check.</param>
/// <returns>True if valid.</returns>
public bool Validate(string argument)
{
return EngineCommandArgumentOf(argument) == null ? false : true;
}
/// <summary>
/// Returns the <see cref="EngineCommandArgument"/> of the given argument or null if the string is an invalid argument.
/// </summary>
/// <param name="argument">The argument string.</param>
/// <returns>null if not viable or the <see cref="EngineCommandArgumentOf(argument)"/> if viable.</returns>
public EngineCommandArgument EngineCommandArgumentOf(string argument)
{
for (int i = 0; i < commandArguments.Length; i++)
{
if (commandArguments[i].invokeString == argument)
{
return commandArguments[i];
}
}
return null;
}
/// <summary>
/// Finds the index of the argument given in an array of arguments for the command.
/// </summary>
/// <param name="argument">The argument to find the index of.</param>
/// <param name="arguments">The array containing all arguments.</param>
/// <returns>The index or throws argument exception if it doesn't exist.</returns>
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);
}
/// <summary>
/// Finds the index of the argument given in an array of arguments for the command.
/// </summary>
/// <param name="argument">The argument to find the index of.</param>
/// <param name="arguments">The array containing all arguments.</param>
/// <returns>The index or throws argument exception if it doesn't exist.</returns>
public int IndexOfArgumentIn(EngineCommandArgument argument, string[] arguments)
{
return IndexOfArgument(argument.invokeString, arguments);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="argument">Potential argument to request help for.</param>
/// <returns>The string for the help.</returns>
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();
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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));
}
}
}

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<ApplicationIcon />
<StartupObject />
<AssemblyName>RecrownedGTK.Tools</AssemblyName>
<RootNamespace>RecrownedGTK.Tools</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<Optimize>true</Optimize>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" PrivateAssets="All"/>
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0005" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta0005" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RecrownedGTK\RecrownedGTK.csproj" PrivateAssets="All"/>
</ItemGroup>
</Project>

View File

@ -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<ImageHandler> imageHandlers;
Dictionary<string, NinePatchData> 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; } }
/// <summary>
/// Machine to pack multiple textures into one large texture.
/// </summary>
/// <param name="rootDirectoryPath">Path to textures.</param>
/// <param name="powLimit">Power of two limit for auto expanding texture. Default is 12 which is a 4096x4096 texture.</param>
/// <param name="startingPower">What power to start at and build up from. Default is 8 which is a 256x256 texture.</param>
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<ImageHandler> imageHandlers = new List<ImageHandler>();
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<string, NinePatchData>();
ConsoleUtilities.WriteWrappedLine("Reading ninepatch data for: " + paths[pathID]);
string serialized = File.ReadAllText(paths[pathID]);
NinePatchData npData = JsonConvert.DeserializeObject<NinePatchData>(serialized);
ninePatchDictionary.Add(npData.textureName, npData);
}
}
imageHandlers.Sort();
this.imageHandlers = imageHandlers;
}
/// <summary>
/// Builds a texture atlas.
/// </summary>
/// <param name="AutoCorrectAtlasSize">Whether or not to automatically upscale atlas' texture in the case it is too small. Goes up to 4096 by default.</param>
public void Build(bool AutoCorrectAtlasSize = true)
{
masterNode = new Node();
masterNode.region.Width = TextureLength;
masterNode.region.Height = TextureLength;
Queue<ImageHandler> imageHandlerQueue = new Queue<ImageHandler>(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);
}
}
}
}
/// <summary>
/// Renders the build into a PNG file and generates the respective <see cref="TextureAtlasData"/> meant for serialization and later to be loaded.
/// </summary>
/// <param name="output">directory to output to.</param>
/// <param name="atlasName">name of atlas.</param>
public void Save(string output, string atlasName)
{
GraphicsOptions gOptions = new GraphicsOptions();
TextureAtlasData.AtlasRegionData[] regions = new TextureAtlasData.AtlasRegionData[TexturesFound];
using (Image<Rgba32> atlasTexture = new Image<Rgba32>(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<Rgba32> image = Image.Load<Rgba32>(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;
}
/// <summary>
/// Attempts to insert image within the node. This builds the node to have children if needed.
/// </summary>
/// <param name="imageHandler">the image to insert.</param>
/// <returns>The node the image is placed in.</returns>
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<ImageHandler>
{
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;
}
}
}
}

View File

@ -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 + ".");
}
}
}

View File

@ -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();
}
}
}
}

View File

@ -0,0 +1,4 @@
{
"folders": [],
"settings": {}
}

View File

@ -0,0 +1,207 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace RecrownedGTK.Assets
{
/// <summary>
/// Wrapper for the content manager that helps with controlling it by adding automated multithreaded content loading.
/// </summary>
public class AssetManager
{
Thread thread;
readonly AssetManager contentManager;
readonly Queue<ContentData> queue;
Dictionary<string, Object> assets;
/// <summary>
/// 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.
/// </summary>
private readonly Dictionary<Type, IAssetPathResolver> contentPathModifier;
/// <summary>
/// Used when no path modifier is defined for that specific type.
/// </summary>
public IAssetPathResolver normalPathModifier = new NormalAssetPathResolver();
volatile float progress;
volatile bool running;
/// <summary>
/// Whether or not the queue is empty and all content is loaded.
/// </summary>
public bool Done { get { return !running && queue.Count == 0; } }
/// <summary>
/// The progress of the loading. 1 is complete while 0 is incomplete.
/// </summary>
public float Progress { get { return progress; } }
/// <summary>
/// Wraps the <see cref="ContentManager"/>.
/// </summary>
/// <param name="contentManager">The manager to wrap.</param>
public AssetManager()
{
assets = new Dictionary<string, Object>();
queue = new Queue<ContentData>();
contentPathModifier = new Dictionary<Type, IAssetPathResolver>();
}
/// <summary>
/// Adds a <see cref="IContentPathResolver"/> to this handler.
/// </summary>
/// <param name="assetType"></param>
/// <param name="contentResolver"></param>
public void AddContentPathResolver(Type assetType, IAssetPathResolver contentResolver) {
contentPathModifier.Add(assetType, contentResolver);
}
/// <summary>
/// Removes the <see cref="IContentPathResolver"/> for the key.
/// </summary>
/// <param name="assetType"></param>
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<Object>(path));
}
/// <summary>
/// Gets the requested asset.
/// </summary>
/// <typeparam name="T">The type of the asset for an alternative way to cast.</typeparam>
/// <param name="assetName">The name of the asset.</param>
/// <returns>The asset casted to the type given with T.</returns>
public T Get<T>(string assetName)
{
lock (queue)
{
return (T)assets[assetName];
}
}
/// <summary>
/// Queues an asset to be loaded.
/// </summary>
/// <typeparam name="T">The type of the asset to be queued.</typeparam>
/// <param name="assetName">Name of asset to look for.</param>
/// <param name="usePathModifier">Whether or not to use the path modifiers.</param>
public void Queue<T>(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);
}
}
}
/// <summary>
/// Called whenever a batch of assets should be loaded from the queue. Safe to call once every frame.
/// </summary>
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;
}
/// <summary>
/// Removes the asset from the list of assets in the system.
/// Cannot remove from queue.
/// </summary>
/// <param name="name">the string name used to load the asset</param>
public void Remove(string name)
{
lock (queue)
{
if (assets.ContainsKey(name))
{
if (assets[name] is IDisposable)
{
((IDisposable)assets[name]).Dispose();
}
assets.Remove(name);
}
}
}
/// <summary>
/// Clears the queue.
/// </summary>
public void ClearQueue()
{
lock (queue)
{
queue.Clear();
}
}
/// <summary>
/// Unloads everything from both queue and loaded list while properly disposing of the assets loaded.
/// </summary>
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;
}
}
}
}

View File

@ -0,0 +1,15 @@
namespace RecrownedGTK.Assets
{
/// <summary>
/// Modifies the given path based on a name. Used to simplify long paths for the <see cref="AssetManager"/>
/// </summary>
public interface IAssetPathResolver
{
/// <summary>
/// Returns the complete path with the content folder as root.
/// </summary>
/// <param name="contentPath">Is the asset's name</param>
/// <returns></returns>
string Modify(string contentPath);
}
}

View File

@ -0,0 +1,18 @@
namespace RecrownedGTK.Assets
{
/// <summary>
/// A resolver that does nothing. Used for looking in the root by default.
/// </summary>
public class NormalAssetPathResolver : IAssetPathResolver
{
/// <summary>
/// Passes the path through without modification as this is the normal content resolver and is meant to just pass things on.
/// </summary>
/// <param name="contentPath">The path to modify.</param>
/// <returns></returns>
public string Modify(string contentPath)
{
return contentPath;
}
}
}

View File

@ -0,0 +1,35 @@
namespace RecrownedGTK.Data
{
/// <summary>
/// Represents a data structure for 9patches.
/// </summary>
public class NinePatchData
{
/// <summary>
/// Name of texture associated with patch. May be null in the case of being apart of a <see cref="TextureAtlasData"/>
/// </summary>
public string textureName;
/// <summary>
/// the boundaries of the patch.
/// </summary>
public int left, right, bottom, top;
/// <summary>
/// Constructs patch.
/// </summary>
/// <param name="textureName">Name of the texture. May be null.</param>
/// <param name="left">Left bound.</param>
/// <param name="right">Right bound.</param>
/// <param name="bottom">Bottom bound.</param>
/// <param name="Top">Top bound.</param>
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;
}
}
}

View File

@ -0,0 +1,115 @@
using System;
using RecrownedGTK.Graphics.UI.SkinSystem.Definitions;
using OpenTK.Graphics;
using RecrownedGTK.Types;
namespace RecrownedGTK.Data
{
/// <summary>
/// Data transfer object for game skins.
/// </summary>
public class SkinData
{
/// <summary>
/// Holds metadata.
/// </summary>
public class Metadata
{
/// <summary>
/// Author name.
/// </summary>
public string author;
/// <summary>
/// Description of skin.
/// </summary>
public string description;
/// <summary>
/// Name of skin.
/// </summary>
public string skinName;
}
/// <summary>
/// The metadata for the skin file.
/// </summary>
public Metadata metadata;
/// <summary>
/// The name of the atlas with extension.
/// </summary>
public string nameOfTextureAtlas;
/// <summary>
/// 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.
/// </summary>
public string cursorTextureName;
/// <summary>
/// The color data containing the name of the color, and red, green, and blue values for the color.
/// </summary>
public ColorData[] colors;
/// <summary>
/// The skin definitions containing a name for the definition, and the definition itself.
/// </summary>
public DefinitionData[] definitions;
/// <summary>
/// Color data for data transfer.
/// </summary>
public struct ColorData
{
/// <summary>
/// Name of color to be referenced by.
/// </summary>
public string name;
/// <summary>
/// RGBA data of this color.
/// </summary>
public byte r, g, b, a;
/// <summary>
/// Sets values for data.
/// </summary>
/// <param name="name">the name to be referenced by.</param>
/// <param name="color">The color value <paramref name="name"/> represents.</param>
public ColorData(string name, Color4 color)
{
this.name = name;
r = color.GetRedAsByte();
g = color.GetGreenAsByte();
b = color.GetBlueAsByte();
a = color.GetAlphaAsByte();
}
}
/// <summary>
/// Definition data for data transfer.
/// </summary>
public struct DefinitionData
{
/// <summary>
/// Name of definition to be referenced by.
/// </summary>
public string name;
/// <summary>
/// The skin definition data.
/// </summary>
public SkinDefinitionData skin;
/// <summary>
/// Sets values for data.
/// </summary>
/// <param name="name">The name to be referenced by.</param>
/// <param name="skinDefinitionData">The skin data.</param>
public DefinitionData(string name, SkinDefinitionData skinDefinitionData)
{
this.name = name;
skin = skinDefinitionData;
}
}
}
}

View File

@ -0,0 +1,83 @@
using RecrownedGTK.Types;
namespace RecrownedGTK.Data
{
/// <summary>
/// Data transfer object for a texture atlas.
/// </summary>
public class TextureAtlasData
{
/// <summary>
/// Contains the regions of the texture atlas.
/// </summary>
public AtlasRegionData[] regions;
/// <summary>
/// The name of the file.
/// </summary>
public string textureName;
/// <summary>
/// Creates the atlas given the regions and the file name of the texture file to be used.
/// </summary>
/// <param name="textureName"></param>
/// <param name="regions"></param>
public TextureAtlasData(string textureName, AtlasRegionData[] regions)
{
this.regions = regions;
this.textureName = textureName;
}
/// <summary>
/// Data object that contains information about the region ninepatch situation of a given region in an atlas.
/// </summary>
public struct AtlasRegionData
{
/// <summary>
/// Name of the region for referencial purposes.
/// </summary>
public string name;
/// <summary>
/// The location of the patch is designated by this rectangle.
/// </summary>
public Rectangle bounds;
/// <summary>
/// The ninepatch information.
/// </summary>
public NinePatchData ninePatchData;
/// <summary>
/// Sets position in atlas for convenience.
/// </summary>
/// <param name="x">X coordinate of the position in the patch.</param>
/// <param name="y">Y coordinate of the position in the patch.</param>
public void SetPosition(int x, int y)
{
bounds.X = x;
bounds.Y = y;
}
/// <summary>
/// Sets the dimensions of the region on the atlas for convenience.
/// </summary>
/// <param name="width">Width of the region.</param>
/// <param name="height">Height of the region.</param>
public void SetSize(int width, int height)
{
bounds.Width = width;
bounds.Height = height;
}
/// <summary>
/// Sets both the coordinates and dimensions of the region on the atlas for convenience.
/// </summary>
/// <param name="x">X coordinate of the position in the patch.</param>
/// <param name="y">Y coordinate of the position in the patch.</param>
/// <param name="width">Width of the region.</param>
/// <param name="height">Height of the region.</param>
public void SetBounds(int x, int y, int width, int height)
{
SetPosition(x, y);
SetSize(width, height);
}
}
}
}

7
RecrownedGTK/Game.cs Normal file
View File

@ -0,0 +1,7 @@
namespace RecrownedGTK {
public class Game {
public Game() {
//TODO Implement interface that calls the users created game files.
}
}
}

View File

@ -0,0 +1,130 @@
using RecrownedGTK.Graphics.Render;
using System;
using OpenTK;
using OpenTK.Graphics;
namespace RecrownedGTK.Graphics
{
/// <summary>
/// An object that represents a ninepatch.
/// </summary>
public class NinePatch : ISpecialDrawable
{
/// <summary>
/// Dimensions in ninepatch. May also represent position in texture atlas.
/// </summary>
public Rectangle textureRegion;
readonly Texture2D texture;
readonly int left, right, bottom, top;
Rectangle[] sourcePatches;
/// <summary>
/// A nine patch object.
/// </summary>
/// <param name="texture">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.</param>
/// <param name="left">Left side.</param>
/// <param name="right">Right side.</param>
/// <param name="bottom">Bottom side.</param>
/// <param name="top">Top side.</param>
/// <param name="textureBounds">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.</param>
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;
}
/// <summary>
/// Draws the ninepatch.
/// </summary>
/// <param name="spriteBatch">Batch to use.</param>
/// <param name="color">The color of the patch.</param>
/// <param name="destination">Where to the patch.</param>
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();
}
/// <summary>
/// </summary>
/// <param name="spriteBatch">Spritebatch to use.</param>
/// <param name="destination">The destination to draw the patch.</param>
/// <param name="color">The tint for each patch.</param>
/// <param name="rotation">Not considered for 9patches.</param>
/// <param name="origin">Not considered for 9patches.</param>
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);
}
}
}

View File

@ -0,0 +1,90 @@
using OpenTK;
namespace RecrownedGTK.Graphics.Render
{
/// <summary>
/// A generic camera. Functions in 3D.
/// </summary>
public class BasicCamera
{
/// <summary>
/// The scale for the world.
/// </summary>
public float worldScale = 1f;
/// <summary>
/// Current position in the world.
/// </summary>
public Vector3 position;
/// <summary>
/// The place the 3D camera is looking at.
/// </summary>
public Vector3 lookAt;
/// <summary>
/// The direction up is for the camera.
/// </summary>
public Vector3 upDirection;
/// <summary>
/// The transform matrix representing the world (rotation and translations of the original world).
/// </summary>
public Matrix3 worldMatrix;
/// <summary>
/// The view matrix that describes where the camera looks.
/// </summary>
public Matrix3 ViewMatrix { get; protected set; }
/// <summary>
/// The projection matrix.
/// </summary>
public Matrix3 projectionMatrix;
/// <summary>
/// A basic effect that will be updated with the correct matrice information everytime <see cref="Apply"/> is called. Can be null and thus will not be used.
/// </summary>
public BasicEffect basicEffect;
/// <summary>
/// Constructs 3D camera with an orthographic projection matrix with dimensions of graphics devices viewport. All changes to matrices should have apply called after changes.
/// </summary>
/// <param name="basicEffect">A basic effect that will be updated with the correct matrice information everytime <see cref="Apply"/> is called. Can be null and thus will not be used.</param>
public BasicCamera(BasicEffect basicEffect = null)
{
worldMatrix = Matrix.Identity;
lookAt = Vector3.Forward;
upDirection = Vector3.Up;
projectionMatrix = Matrix.Identity;
this.basicEffect = basicEffect;
Apply();
}
/// <summary>
/// Applies the changes to the fields and properties of the camera.
/// </summary>
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;
}
}
/// <summary>
/// Moves camera by the given amount.
/// </summary>
/// <param name="move">A <see cref="Vector3"/> that contains how much in each direction to move.</param>
public void MoveCamera(Vector3 move)
{
position += move;
Apply();
}
}
}

View File

@ -0,0 +1,84 @@
using OpenTK;
using System;
namespace RecrownedGTK.Graphics.Render
{
/// <summary>
/// A virtual 2D camera that wraps the normal <see cref="BasicCamera"/>. Default projection is orthographic.
/// </summary>
public class Camera2D : BasicCamera
{
/// <summary>
/// The width of the view of the camera.
/// </summary>
public int viewWidth;
/// <summary>
/// The height of the view of the camera.
/// </summary>
public int viewHeight;
/// <summary>
/// The 2D position.
/// </summary>
public Vector2 Position { get { return new Vector2(position.X, position.Y); } set { position.X = value.X; position.Y = value.Y; } }
/// <summary>
/// Places camera in the center given the corner position.
/// </summary>
public Vector2 ConrnerPosition { set { position.X = value.X + viewWidth / 2f; position.Y = value.Y + viewHeight / 2f; } }
/// <summary>
/// A 2D camera from the generic <see cref="BasicCamera"/>.
/// </summary>
/// <param name="height">Width of camera view.</param>
/// <param name="width">Height of camera view.</param>
/// <param name="basicEffect">A basic effect that will be updated with the correct matrice information everytime <see cref="Apply"/> is called. Can be null and thus will not be used.</param>
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();
}
/// <summary>
/// Applies for 2D.
/// Sets where the camera is looking for the view matrix to the position of the camera.
/// </summary>
public override void Apply()
{
projectionMatrix = Matrix.CreateOrthographic(viewWidth, viewHeight, 0, 1);
position.Z = 0;
lookAt = new Vector3(Position, 1f);
base.Apply();
}
/// <summary>
/// Lerps to the given position.
/// </summary>
/// <param name="alpha">The multiplier for difference in distance.</param>
/// <param name="targetPosition">The target position to lerp to.</param>
/// <param name="delta">Time between this frame and the previous frame.</param>
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;
}
/// <summary>
/// Moves the camera.
/// Apply needs to be called.
/// </summary>
/// <param name="move">Magnitude of how much to move per axis.</param>
public void MoveCamera(Vector2 move)
{
Position += move;
}
}
}

View File

@ -0,0 +1,104 @@
namespace RecrownedGTK.Graphics.Render
{
/// <summary>
/// A <see cref="SpriteBatch"/> that keeps it's settings through begin and end unless manually changed either by the <see cref="Begin(SpriteSortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Effect, Matrix?)"/> 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 <see cref="SpriteBatch"/> will not persist the configuration and will call the normal <see cref="SpriteBatch.Begin(SpriteSortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Effect, Matrix?)"/>.
/// </summary>
public class ConsistentSpriteBatch : SpriteBatch
{
/// <summary>
/// How to blend the colors. Uses <see cref="SpriteBatch"/>'s default if not set before or during <see cref="Begin(SpriteSortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Effect, Matrix?)"/> call.
/// Changes will only take effect on <see cref="Begin(SpriteSortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Effect, Matrix?)"/> call.
/// </summary>
public BlendState blendState;
/// <summary>
/// The state of sampler to use for the spritebatch. Uses <see cref="SpriteBatch"/>'s default if not set before or during <see cref="Begin(SpriteSortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Effect, Matrix?)"/> call.
/// Changes will only take effect on <see cref="Begin(SpriteSortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Effect, Matrix?)"/> call.
/// </summary>
public SamplerState samplerState;
/// <summary>
/// The state of the depth-stencil buffer. Uses <see cref="SpriteBatch"/>'s defaultdefault if not set before or during <see cref="Begin(SpriteSortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Effect, Matrix?)"/> call.
/// Changes will only take effect on <see cref="Begin(SpriteSortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Effect, Matrix?)"/> call.
/// </summary>
public DepthStencilState depthStencilState;
/// <summary>
/// The state of rasterizer to use. Uses <see cref="SpriteBatch"/>'s default if not set before or during <see cref="Begin(SpriteSortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Effect, Matrix?)"/> call.
/// Changes will only take effect on <see cref="Begin(SpriteSortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Effect, Matrix?)"/> call.
/// </summary>
public RasterizerState rasterizerState;
/// <summary>
/// An effect to apply to the batch. Uses <see cref="SpriteBatch"/>'s default if not set before or during <see cref="Begin(SpriteSortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Effect, Matrix?)"/> call.
/// Changes will only take effect on <see cref="Begin(SpriteSortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Effect, Matrix?)"/> call.
/// </summary>
public Effect effect;
/// <summary>
/// A matrix to be applied to transform the sprites geometry. An identity matrix is used if not provided before or during <see cref="Begin(SpriteSortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Effect, Matrix?)"/> call.
/// Changes will only take effect on <see cref="Begin(SpriteSortMode, BlendState, SamplerState, DepthStencilState, RasterizerState, Effect, Matrix?)"/> call.
/// </summary>
public Matrix? transformMatrix;
/// <summary>
/// Creates a consistent sprite batch with default values.
/// </summary>
/// <param name="graphicsDevice">The graphics device to use to create a <see cref="SpriteBatch"/>.</param>
public ConsistentSpriteBatch(GraphicsDevice graphicsDevice) : base(graphicsDevice)
{
}
/// <summary>
/// Begins the consistent sprite batch. The configuration passed to this function is saved for later begin calls.
/// </summary>
/// <param name="sortMode">Defines the spritebatch's method of sorting the items in each batch. Uses <see cref="SpriteBatch"/>'s default.</param>
/// <param name="blendState">How to blend the colors. Uses <see cref="SpriteBatch"/>'s default if not set and field is also not set.</param>
/// <param name="samplerState">The state of sampler to use for the spritebatch. Uses <see cref="SpriteBatch"/>'s default if not set and field is also not set.</param>
/// <param name="depthStencilState">What type of rasterization to use. Uses <see cref="SpriteBatch"/>'s default if not set and field is also not set.</param>
/// <param name="rasterizerState">What type of rasterization to use. Uses <see cref="SpriteBatch"/>'s default if not set and field is also not set.</param>
/// <param name="effect">An effect to apply to the batch. Uses <see cref="SpriteBatch"/>'s default if not set and field is also not set.</param>
/// <param name="transformMatrix">A matrix to be applied to transform the sprites geometry. Uses <see cref="SpriteBatch"/>'s default if not set and field is also not set.</param>
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);
}
/// <summary>
/// Begins the consistent sprite batch without saving the configuration. Useful for one time changes to one portion of the configuration.
/// </summary>
/// <param name="sortMode">Defines the spritebatch's method of sorting the items in each batch. Uses <see cref="SpriteBatch"/>'s default.</param>
/// <param name="blendState">How to blend the colors. Uses <see cref="SpriteBatch"/>'s default if not set and field is also not set.</param>
/// <param name="samplerState">The state of sampler to use for the spritebatch. Uses <see cref="SpriteBatch"/>'s default if not set and field is also not set.</param>
/// <param name="depthStencilState">What type of rasterization to use. Uses <see cref="SpriteBatch"/>'s default if not set and field is also not set.</param>
/// <param name="rasterizerState">What type of rasterization to use. Uses <see cref="SpriteBatch"/>'s default if not set and field is also not set.</param>
/// <param name="effect">An effect to apply to the batch. Uses <see cref="SpriteBatch"/>'s default if not set and field is also not set.</param>
/// <param name="transformMatrix">A matrix to be applied to transform the sprites geometry. Uses <see cref="SpriteBatch"/>'s default if not set and field is also not set.</param>
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);
}
}
}

View File

@ -0,0 +1,158 @@
using OpenTK.Graphics;
using OpenTK;
using System;
namespace RecrownedGTK.Graphics.Render
{
/// <summary>
/// A batch used to draw primitive shapes by batching together vertices.
/// </summary>
public class PrimitiveBatch : IDisposable
{
VertexPositionColor[] vertices;
int bufferPosition;
Effect effect;
PrimitiveType primitiveType;
private int verticesPerPrimitive;
/// <summary>
/// Amount of primitives.
/// </summary>
public int primitiveCount;
GraphicsDevice graphicsDevice;
bool began;
bool disposed;
BasicEffect basicEffect;
/// <summary>
/// Creates a batch used to draw primitives.
/// </summary>
/// <param name="graphicsDevice">The graphics device used to draw the primitives.</param>
/// <param name="verticesPerBatch">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.</param>
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);
}
/// <summary>
/// Starts the batch. Batch cannot be started twice.
/// </summary>
/// <param name="primitiveType">The type of primitive this batch would be drawing.</param>
/// <param name="effect">Effect to use to render the primitives. Default will use a <see cref="BasicEffect"/> with parameters set up during creation.</param>
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;
}
/// <summary>
/// Ends the batch. Begin needs to be called before end.
/// </summary>
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;
}
/// <summary>
/// Adds a vertex position for the primitive being drawn. The batch needs to have beens started before this.
/// </summary>
/// <param name="vertex">The vector that represents the vertex.</param>
/// <param name="color">The color of that vertex.</param>
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++;
}
/// <summary>
/// Flushes the batch. Automatically called if required if using <see cref="PrimitiveType.LineList"/> or <see cref="PrimitiveType.TriangleList"/>. Otherwise, manual flushing is required.
/// <see cref="primitiveCount"/> is used if not zero. Is reset to zero every flush.
/// </summary>
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;
}
/// <summary>
/// Disposes this.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Overridable dispose.
/// </summary>
/// <param name="disposing">True for when user called for this to be disposed. False otherwise.</param>
public virtual void Dispose(bool disposing)
{
if (disposed) throw new ObjectDisposedException(this.GetType().Name);
disposed = true;
if (disposing && !disposed)
{
basicEffect.Dispose();
}
}
/// <summary>
/// Destructor.
/// </summary>
~PrimitiveBatch()
{
Dispose(false);
}
}
}

View File

@ -0,0 +1,119 @@
using OpenTK.Graphics;
using OpenTK;
using RecrownedGTK.Types;
using System;
namespace RecrownedGTK.Graphics.Render
{
/// <summary>
/// Renders rectangles using the <see cref="PrimitiveBatch"/>.
/// </summary>
public class RectangleRenderer : IDisposable
{
private bool filled;
private bool disposed;
/// <summary>
/// The <see cref="PrimitiveBatch"/> used. Needs to be disposed.
/// </summary>
private readonly PrimitiveBatch primitiveBatch;
/// <summary>
/// Creates a rectangle renderer with the given <see cref="PrimitiveBatch"/>.
/// </summary>
/// <param name="graphicsDevice">Graphics device to use.</param>
public RectangleRenderer(GraphicsDevice graphicsDevice)
{
primitiveBatch = new PrimitiveBatch(graphicsDevice);
}
/// <summary>
/// Disposes the rectangle renderer.
/// </summary>
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// Overridable for dispose.
/// </summary>
/// <param name="disposing">True when its a player calling the dispose.</param>
public virtual void Dispose(bool disposing)
{
if (disposed) throw new ObjectDisposedException(GetType().Name);
if (disposing)
{
primitiveBatch.Dispose();
}
disposed = true;
}
/// <summary>
/// Begins a batch for rectangles.
/// </summary>
/// <param name="filled">Whether or not this batch should be filled rectangles.</param>
public void Begin(bool filled = false)
{
this.filled = filled;
primitiveBatch.Begin(filled ? PrimitiveType.TriangleStrip : PrimitiveType.LineStrip);
}
/// <summary>
/// Ends the batch.
/// </summary>
public void End()
{
primitiveBatch.End();
}
/// <summary>
/// Draws a basic rectangle given bottom left and top right.
/// </summary>
/// <param name="x">X coordinate of bottom left.</param>
/// <param name="y">Y coordinate of bottom left.</param>
/// <param name="width">Width of rectangle.</param>
/// <param name="height">Height of rectangle.</param>
/// <param name="color">Color of all vertices of this rectangle.</param>
/// <param name="rotation">Rotation of rectangle. Default is 0 radians.</param>
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();
}
/// <summary>
/// Draws the given rectangle.
/// </summary>
/// <param name="rectangle">Uses the x, y and dimensions to draw a rectangle.</param>
/// <param name="color">The color of the rectangle.</param>
public void Draw(Rectangle rectangle, Color4 color)
{
Draw(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, color);
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,10 @@
out vec4 outputColor;
in vec2 texCoord;
uniform sampler2D texture0;
void main()
{
outputColor = texture(texture0, texCoord);
}

View File

@ -0,0 +1,8 @@
#version 330 core
in vec3 aPosition;
in mat4 aTransform;
void main()
{
gl_Position = vec4(aPosition, 1) * aTransform;
}

View File

@ -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;
}
}
}

View File

@ -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[]));
}
}
}
}
}

View File

@ -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
{
/// <summary>
/// Holds information about an image file that contains various textures in various regions in the file.
/// </summary>
public class TextureAtlas : IDisposable
{
private Texture2D texture;
private bool disposed;
private Dictionary<string, Region> dictionaryOfRegions = new Dictionary<string, Region>();
/// <summary>
/// Given a name, can return a <see cref="Region"/>.
/// </summary>
/// <param name="name">Name of <see cref="Region"/> to obtain.</param>
/// <returns><see cref="Region"/> based off name.</returns>
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."); } }
/// <summary>
/// Creates a texture atlas with given main texture as well as an array of <see cref="Region"/> to represent locations of which textures reside within the atlas. Region names will be used to refer to the regions within the dictionary.
/// </summary>
/// <param name="texture">The texture representing the overall atlas.</param>
/// <param name="regions">The sub regions that represent the individual textures.</param>
public TextureAtlas(Texture2D texture, Region[] regions)
{
this.texture = texture;
foreach (Region region in regions)
{
dictionaryOfRegions.Add(region.name, region);
}
}
/// <summary>
/// Creates a texture region given a dictionary of regions keyed to strings that can be used to refer to them.
/// </summary>
/// <param name="texture">The texture representing the overall atlas.</param>
/// <param name="dictionaryOfRegions"></param>
public TextureAtlas(Texture2D texture, Dictionary<string, Region> dictionaryOfRegions)
{
this.texture = texture;
this.dictionaryOfRegions = dictionaryOfRegions;
}
/// <summary>
/// Draw the region given by a string in the atlas onto a destination rectangle.
/// </summary>
/// <param name="name">Name of region to draw.</param>
/// <param name="batch">SpriteBatch to be used.</param>
/// <param name="destination">The location to draw this region.</param>
/// <param name="color">Color to use.</param>
/// <param name="rotation">Rotation of texture drawn.</param>
/// <param name="origin">Origin used by rotation.</param>
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);
}
/// <summary>
/// Creates or obtains a previously created texture of a region.
/// </summary>
/// <param name="name">Name of region.</param>
/// <param name="graphicsDevice">graphics device to be used to generate the texture.</param>
/// <returns>The texture from the region.</returns>
public Texture2D ObtainRegionAsTexture(string name, GraphicsDevice graphicsDevice)
{
return dictionaryOfRegions[name].AsTexture2D(graphicsDevice);
}
/// <summary>
/// Whether or not this atlas contains the given region name.
/// </summary>
/// <param name="regionName">The name of the region to check for.</param>
/// <returns>True if this atlas does contain the region given by name.</returns>
public bool ContainsRegion(string regionName)
{
return dictionaryOfRegions.ContainsKey(regionName);
}
/// <summary>
/// Disposes unmanaged resources for the texture atlas.
/// </summary>
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// Overridable disposal method.
/// </summary>
/// <param name="disposing">Only true if user calls <see cref="Dispose()"/></param>
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();
}
}
/// <summary>
/// Destructor.
/// </summary>
~TextureAtlas()
{
Dispose(false);
}
/// <summary>
/// A region of a <see cref="TextureAtlas"/>.
/// </summary>
public class Region : ISpecialDrawable, IDisposable
{
/// <summary>
/// The name of the region. Mostly used to be refered to within the context of a <see cref="TextureAtlas"/>.
/// </summary>
public readonly string name;
/// <summary>
/// The location and dimensions of where the original texture resides on the texture representing the atlas.
/// </summary>
public readonly Rectangle sourceRectangle;
readonly NinePatch ninepatch;
Texture2D atlasTexture;
Texture2D regionTexture;
/// <summary>
/// If region has already been disposed.
/// </summary>
public bool Disposed { get; private set; }
/// <summary>
/// A specified region in a texture atlas.
/// </summary>
/// <param name="name">Name of region.</param>
/// <param name="sourceRegion">The location of the region on the atlas.</param>
/// <param name="ninePatch">A <see cref="NinePatch"/> definition for the region.</param>
/// <param name="atlasTexture">The texture that holds the image data for the atlas.</param>
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;
}
/// <summary>
/// Draws the region. If ninepatch, rotation and origin are ignored.
/// </summary>
/// <param name="batch">The batch to use. Should be began.</param>
/// <param name="destination">The destination rectangle to draw to.</param>
/// <param name="color">The color to use.</param>
/// <param name="rotation">Rotation of the final drawing. Ignored if is a 9patch.</param>
/// <param name="origin">The origin of the drawing. Ignored if is a 9patch.</param>
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);
}
}
/// <summary>
/// Create or obtains a previously created texture of this region.
/// </summary>
/// <param name="graphicsDevice">The graphics device to use to create the texture.</param>
/// <returns>The texture of the region.</returns>
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;
}
/// <summary>
/// Call this to dispose.
/// </summary>
public void Dispose()
{
if (Disposed) throw new ObjectDisposedException(GetType().Name);
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Overridable dispose.
/// </summary>
/// <param name="disposing">Whether or not this was a user made call.</param>
public virtual void Dispose(bool disposing)
{
if (disposing && !Disposed)
{
regionTexture?.Dispose();
}
Disposed = true;
}
/// <summary>
/// Destructor.
/// </summary>
~Region()
{
Dispose(false);
}
}
}
}

View File

@ -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
{
/// <summary>
/// Contains the pages.
/// </summary>
public class Book : IInputListener
{
readonly AssetManager assets;
readonly ISkin skin;
Page targetPage;
int width, height;
Dictionary<string, Page> pages = new Dictionary<string, Page>();
List<Page> orderedPages = new List<Page>();
/// <summary>
/// The camera to use to change between pages.
/// </summary>
public Camera2D camera;
private BasicScissor basicScissor;
/// <summary>
/// Creates a book.
/// </summary>
/// <param name="assets"><see cref="AssetManager"/> that holds the assets that are to be used in the pages for this book during initialization.</param>
/// <param name="skin">The skin that will be passed to pages during their initialization.</param>
/// <param name="camera">Camera to move to change pages.</param>
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();
}
/// <summary>
/// Should be called whenever a valid camera and dimensions for the book exist.
/// Initializes book with given parameters.
/// Generally used with a <see cref="ScreenSystem.Screen"/> and called in the <see cref="ScreenSystem.Screen.ApplySize(int, int)"/> function.
/// </summary>
/// <param name="camera">Camera to move to change pages.</param>
/// <param name="dimensions">Dimensions of the book and the dimensions the pages will use.</param>
public void Initiate(Camera2D camera, Rectangle dimensions)
{
this.camera = camera;
}
/// <summary>
/// Applies the size if the book's pages.
/// </summary>
/// <param name="width">The width of one page.</param>
/// <param name="height">The height of one page.</param>
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;
}
}
}
/// <summary>
/// Draws the pages.
/// </summary>
/// <param name="batch">Batch used to draw.</param>
public void Draw(ConsistentSpriteBatch batch)
{
for (int pageIndex = 0; pageIndex < pages.Count; pageIndex++)
{
orderedPages[pageIndex].Draw(batch);
}
}
/// <summary>
/// Updates the book.
/// </summary>
/// <param name="gameTime">Snapshot of information of the game time.</param>
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);
}
}
/// <summary>
/// Adds the page(s).
/// </summary>
/// <param name="pages">The page(s) to add.</param>
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);
}
}
/// <summary>
/// Removes the page.
/// </summary>
/// <param name="page">Page to remove.</param>
public void RemovePage(Page page)
{
RemovePage(page.name);
}
/// <summary>
/// Removes the page.
/// </summary>
/// <param name="name">Name of page to remove.</param>
public void RemovePage(string name)
{
orderedPages.Remove(pages[name]);
pages.Remove(name);
}
/// <summary>
/// Removes all pages.
/// </summary>
public void ClearPages()
{
orderedPages.Clear();
pages.Clear();
}
/// <summary>
/// Perform a step of linear interpolation to the given page.
/// </summary>
/// <param name="page">The page to lerp to.</param>
public void LerpToPage(Page page)
{
targetPage = page;
}
/// <summary>
/// Goes to page instantly.
/// </summary>
/// <param name="page">Page to go to.</param>
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);
}
/// <summary>
/// Passes the new keyboard state down to each page in order of when it was added.
/// </summary>
/// <param name="state"></param>
/// <returns>True if the state change should to trigger further input listeners.</returns>
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;
}
/// <summary>
/// Passes the new mouse state down to each page in order of when it was added.
/// </summary>
/// <param name="state"></param>
/// <returns>True if the state change should to trigger further input listeners.</returns>
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;
}
}
}

View File

@ -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
{
/// <summary>
/// A page a part of a <see cref="Book"/>.
/// </summary>
public class Page : UIModuleGroup
{
private readonly int pageX, pageY;
/// <summary>
/// Whether or not this book needs to be refreshed with new dimensions.
/// </summary>
public bool requiresSizeUpdate;
/// <summary>
/// Constructs a page.
/// </summary>
/// <param name="pageX">The X position in the book.</param>
/// <param name="pageY">The Y position in the book.</param>
public Page(int pageX, int pageY) : base()
{
this.pageX = pageX;
this.pageY = pageY;
requiresSizeUpdate = true;
name = ToString();
}
/// <summary>
/// Called when this page is flagged as needing a size update.
/// </summary>
/// <param name="width">New width.</param>
/// <param name="height">New Height</param>
public virtual void ApplySize(int width, int height)
{
X = pageX * width;
Y = pageY * height;
Width = width;
Height = height;
requiresSizeUpdate = false;
}
/// <summary>
/// Called only once after a page is added to a <see cref="Book"/>. Generally used to instantiate the modules of the page.
/// </summary>
/// <param name="assets">The assets to be used during initialization passed by the book this page belongs to.</param>
/// <param name="skin">The skin the book containing this page is given that can be used by this page.</param>
/// <param name="basicScissor">The scissor box to use for cropping.</param>
protected internal virtual void Initialize(AssetManager assets, ISkin skin, BasicScissor basicScissor)
{
this.basicScissor = basicScissor;
}
}
}

View File

@ -0,0 +1,85 @@
using RecrownedGTK.Graphics.Render;
using RecrownedGTK.Types;
using OpenTK;
using OpenTK.Graphics;
using System;
namespace RecrownedGTK.Graphics.UI.Modular.Modules
{
/// <summary>
/// Represents a texture with more information.
/// </summary>
public class Image : UIModule, ISpecialDrawable
{
/// <summary>
/// The rotation of the image.
/// </summary>
public float rotation = 0f;
/// <summary>
/// The texture to be rendered.
/// </summary>
public Texture2D texture;
/// <summary>
/// Scale of of the X axis.
/// </summary>
public float ScaleX { get { return (float)Width / texture.Width; } set { Width = (int)(texture.Width * value); } }
/// <summary>
/// Scale of the Y axis.
/// </summary>
public float ScaleY { get { return (float)Height / texture.Height; } set { Height = (int)(texture.Height * value); } }
/// <summary>
/// Sets scale of X and Y.
/// </summary>
public float Scale { set { ScaleY = value; ScaleX = value; } }
/// <summary>
/// Constructs an image given a texture.
/// </summary>
/// <param name="texture">Texture to use.</param>
public Image(Texture2D texture)
{
this.texture = texture ?? throw new ArgumentException("Image requires a texture.");
SetPositionAndDimensions(texture.Bounds);
}
/// <summary>
/// Draws the image with default values.
/// </summary>
/// <param name="batch">The batch to use.</param>
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);
}
/// <summary>
/// Draws the image with more options.
/// </summary>
/// <param name="spriteBatch">Batch used.</param>
/// <param name="destination">Where to draw texture to.</param>
/// <param name="color">The color tint to use.</param>
/// <param name="rotation">Rotation of image.</param>
/// <param name="origin">Origin for the rotation.</param>
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);
}
/// <summary>
/// Center's the origin to the middle of the dimensions of the texture.
/// </summary>
public override void CenterOrigin()
{
origin.X = texture.Bounds.Width / 2f;
origin.Y = texture.Bounds.Height / 2f;
}
}
}

View File

@ -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
{
/// <summary>
/// Function to be called when button is clicked.
/// </summary>
/// <param name="button">The button that was clicked.</param>
public delegate void Clicked(Button button);
/// <summary>
/// A very primitive button containing all the basic functions.
/// </summary>
public class Button : UIModule
{
private ButtonSkinDefinition skinDefinition;
private ISpecialDrawable downTexture, upTexture, highlightedTexture, disabledTexture;
/// <summary>
/// Click event listeners.
/// </summary>
public event Clicked Listeners;
private bool pressed;
/// <summary>
/// Whether or not this button should be currently disabled.
/// </summary>
public bool disabled = false;
/// <summary>
/// Whether or not this button is currently being hovered on.
/// </summary>
public bool Highlighted { get; private set; }
/// <summary>
/// Constructs this button using <see cref="ISpecialDrawable"/>s for the different states it could be in.
/// </summary>
/// <param name="down">Button being pressed.</param>
/// <param name="up">Button not being pressed.</param>
/// <param name="disabled">Disabled button.</param>
/// <param name="selected">Button being highlighted.</param>
public Button(ISpecialDrawable down, ISpecialDrawable up, ISpecialDrawable disabled = null, ISpecialDrawable selected = null)
{
this.downTexture = down;
this.upTexture = up;
this.disabledTexture = disabled;
this.highlightedTexture = selected;
}
/// <summary>
/// Constructs this button using the skin system.
/// </summary>
/// <param name="skin">The skin containing the information of the textures and design to follow.</param>
/// <param name="definitionName">The name of the definition in the skin. Can be null to select the default.</param>
public Button(ISkin skin, string definitionName = null)
{
skinDefinition = skin.ObtainDefinition<ButtonSkinDefinition>(definitionName);
downTexture = skin.GetTextureAtlasRegion(skinDefinition.downRegion, true);
upTexture = skin.GetTextureAtlasRegion(skinDefinition.upRegion, true);
disabledTexture = skin.GetTextureAtlasRegion(skinDefinition.disabledRegion);
highlightedTexture = skin.GetTextureAtlasRegion(skinDefinition.selectedRegion);
}
/// <summary>
/// Instantiates a button using a definition.
/// </summary>
/// <param name="skin">The skin the definition is defined in.</param>
/// <param name="skinDefinition">The definition itself.</param>
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))
{ }
/// <summary>
/// Draws the button.
/// </summary>
/// <param name="batch">Batch used to draw the button.</param>
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);
}
/// <summary>
/// Called when the mouse changes state.
/// </summary>
/// <param name="state">The new state.</param>
/// <returns>Whether or not to continue calling the next mouse change listener.</returns>
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);
}
/// <summary>
/// Called when the state of the keyboard changes.
/// </summary>
/// <param name="state">The new state.</param>
/// <returns>Whether or not the next keyboard change listener should be called.</returns>
public sealed override bool KeyboardStateChanged(KeyboardState state)
{
return base.KeyboardStateChanged(state);
}
internal void OnClick()
{
Listeners?.Invoke(this);
}
}
}

View File

@ -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
{
/// <summary>
/// Button that holds a string.
/// </summary>
public class TextButton : Button
{
/// <summary>
/// The text that is used to display the string.
/// </summary>
public readonly Text text;
/// <summary>
/// The color the font should be rendered in.
/// </summary>
public Color4 FontColor { get { return text.color; } set { text.color = value; } }
/// <summary>
/// Constructs text button with the positions represented by <see cref="ISpecialDrawable"/>
/// </summary>
/// <param name="text">The string representing the text to be displayed.</param>
/// <param name="font">The font to be used to display the text.</param>
/// <param name="down">What to draw as button is pushed down.</param>
/// <param name="up">What to draw as button is not pushed.</param>
/// <param name="disabled">What to draw as button is disabled.</param>
/// <param name="selected">What to draw as button is selected.</param>
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;
}
/// <summary>
/// Constructs a text button using a skin and definition.
/// </summary>
/// <param name="text">The text to display.</param>
/// <param name="font">The font to be used.</param>
/// <param name="skin">The skin to use.</param>
/// <param name="definitionName">Name of the definition for this type in the skin given.</param>
public TextButton(string text, SpriteFont font, ISkin skin, string definitionName = null) : base(skin, skin.ObtainDefinition<TextButtonSkinDefinition>(definitionName))
{
TextButtonSkinDefinition skinDefinition = skin.ObtainDefinition<TextButtonSkinDefinition>(definitionName);
this.text = new Text(font, text);
this.text.autoScale = true;
this.text.centered = true;
FontColor = skin.GetColor(skinDefinition.fontColor);
}
/// <summary>
/// Creates a text button with a given definition.
/// </summary>
/// <param name="text">The text to be displayed on this button.</param>
/// <param name="font">The font to use for this button.</param>
/// <param name="skin">The skin the definition is from.</param>
/// <param name="skinDefinition">The definition to use.</param>
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))
{ }
/// <summary>
/// Updates the text button.
/// </summary>
/// <param name="gameTime">Snapshot of information about time for game.</param>
public override void Update(GameTime gameTime)
{
text.SetPositionAndDimensions(Boundaries);
text.Update(gameTime);
base.Update(gameTime);
}
/// <summary>
/// Called whenever game wants to render this button.
/// </summary>
/// <param name="batch">Batch to use. Batch should already be started.</param>
public override void Draw(ConsistentSpriteBatch batch)
{
base.Draw(batch);
text.Draw(batch);
}
}
}

View File

@ -0,0 +1,206 @@
using OpenTK;
using RecrownedGTK.Graphics.Render;
using System;
using System.Text;
namespace RecrownedGTK.Graphics.UI.Modular.Modules
{
/// <summary>
/// Represents text for the UI.
/// </summary>
public class Text : UIModule
{
private SpriteFont font;
private float scale = 1f;
private Vector2 position;
private string originalText;
private string displayedText;
private Vector2 modifiedTextSize;
/// <summary>
/// Centers the text int bounds.
/// </summary>
public bool centered;
/// <summary>
/// Whether or not to try and wrap text automatically. Meaning will check and attempt to wrap every update.
/// </summary>
public bool autoWrap;
/// <summary>
/// Whether or not to automatically scale the text every update. Happens after auto wrap if enabled.
/// </summary>
public bool autoScale;
/// <summary>
/// Should this use ellipses? Will perform this operation before auto wrapping or auto scalling.
/// </summary>
public bool useEllipses;
/// <summary>
/// The text to use for the ellipsis.
/// </summary>
public string ellipsis = "...";
private string ModifiedText { get { return displayedText; } set { displayedText = value; modifiedTextSize = font.MeasureString(value); } }
/// <summary>
/// The string to be displayed.
/// </summary>
public string Content { get { return originalText; } set { originalText = value; if (value == null) value = ellipsis; modifiedTextSize = font.MeasureString(value); displayedText = value; } }
/// <summary>
/// Creates a UI text object.
/// </summary>
/// <param name="font">The font to use.</param>
/// <param name="content">The string for the text.</param>
public Text(SpriteFont font, string content = null)
{
this.font = font ?? throw new ArgumentNullException("Font cannot be null.");
Content = content;
}
/// <summary>
/// Updates the positioning and attempts to perform any operations that were marked automatic.
/// </summary>
/// <param name="gameTime">The game time.</param>
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);
}
/// <summary>
/// Draws the text.
/// </summary>
/// <param name="batch">Batch to use.</param>
public override void Draw(ConsistentSpriteBatch batch)
{
batch.DrawString(font, Content, position, color, 0f, default(Vector2), scale, SpriteEffects.None, 0f);
base.Draw(batch);
}
/// <summary>
/// Attempts to apply ellipsis. Checks of nessecary.
/// </summary>
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();
}
}
/// <summary>
/// Attempts to scale the font. Checks if nessecary.
/// </summary>
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);
}
}
}
/// <summary>
/// Removes line breaks.
/// </summary>
public void RemoveLineBreaks()
{
ModifiedText = ModifiedText.Replace("\n", " ");
}
/// <summary>
/// Resets to original text.
/// </summary>
public void ResetToOriginalText()
{
Content = originalText;
}
/// <summary>
/// Attempts to wrap text. Checks if nessecary.
/// </summary>
/// <param name="unwrap">If true, will first unwrap text, and the wrap again. This occurs before nessecity check.</param>
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;
}
}
}

View File

@ -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;
/// <summary>
/// How fast the bars fade away in opacity (0 to 254) per second.
/// </summary>
public float barFadeSpeed = 250;
ISpecialDrawable horizontalScrollBar, verticalScrollBar;
ISpecialDrawable background, horizontalBarTrack, verticalBarTrack;
private int topPadding, bottomPadding, leftPadding, rightPadding;
/// <summary>
///
/// </summary>
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(); } }
/// <summary>
/// The minimum bar length for the scroll bar.
/// </summary>
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;
/// <summary>
/// Whether or not to hide scroll bars.
/// </summary>
public bool HideScrollBars
{
get { return hideScrollBars; }
set
{
hideScrollBars = value;
WidthOrXChange(true);
HeightOrYChange(true);
if (!value)
{
opacityOfBar = 1f;
scrollBarColor = color.ReturnMultipliedByFloat(opacityOfBar);
}
}
}
/// <summary>
/// Set to true to change from the normal position to the new position.
/// </summary>
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<UIScrollableSkinDefinition>().horizontalBar, true),
skin.GetTextureAtlasRegion(skin.ObtainDefinition<UIScrollableSkinDefinition>().verticalBar, true),
skin.GetTextureAtlasRegion(skin.ObtainDefinition<UIScrollableSkinDefinition>().horizontalBarTrack),
skin.GetTextureAtlasRegion(skin.ObtainDefinition<UIScrollableSkinDefinition>().verticalBarTrack),
skin.GetTextureAtlasRegion(skin.ObtainDefinition<UIScrollableSkinDefinition>().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);
}
}
}

View File

@ -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
{
/// <summary>
/// Module for UI layout.
/// </summary>
public abstract class UIModule : IInputListener
{
/// <summary>
/// The width of the module.
/// </summary>
public virtual int Width { get; set; }
/// <summary>
/// The height of the module.
/// </summary>
public virtual int Height { get; set; }
/// <summary>
/// The X position of the module.
/// </summary>
public virtual int X { get; set; }
/// <summary>
/// The Y position of the module.
/// </summary>
public virtual int Y { get; set; }
/// <summary>
/// Bounds of this module (after factoring in the origin).
/// </summary>
public Rectangle Boundaries
{
get
{
return new Rectangle((int)(X - origin.X), (int)(Y - origin.Y), Width, Height);
}
}
/// <summary>
/// Origin of this module.
/// </summary>
public Vector2 origin;
/// <summary>
/// The parent of this module. May be null.
/// </summary>
public UIModuleGroup parent;
/// <summary>
/// Name of this module. For organizational/referencial purposes mostly.
/// </summary>
public string name;
/// <summary>
/// The color tint of this module.
/// </summary>
public Color4 color = Color4.White;
/// <summary>
/// Called every frame to update this module. Calculations and movement should go here.
/// </summary>
/// <param name="gameTime">Game time information.</param>
public virtual void Update(GameTime gameTime)
{
}
/// <summary>
/// Called every frame to draw this module. Anything that needs to be drawn should go here.
/// </summary>
/// <param name="batch">Batch used to draw.</param>
public virtual void Draw(ConsistentSpriteBatch batch)
{
}
/// <summary>
/// Converts the given rectangle to the coordinates of the parent.
/// </summary>
/// <param name="rectangle">Rectangle to convert.</param>
/// <returns></returns>
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;
}
}
/// <summary>
/// Removes this module from the parent.
/// </summary>
public void RemoveFromParent()
{
if (parent == null) throw new InvalidOperationException("Parent is null.");
parent.RemoveModule(this);
}
/// <summary>
/// Called whenever the keyboard state is changed.
/// </summary>
/// <param name="state">The current keyboard state.</param>
/// <returns>Returning whether or not to continue to call the next listener.</returns>
public virtual bool KeyboardStateChanged(KeyboardState state)
{
return true;
}
/// <summary>
/// Called whenever the state of the mouse changes. This includes movement.
/// </summary>
/// <param name="state">The current state of the mouse.</param>
/// <returns>Returning whether or not to continue to call the next listener.</returns>
public virtual bool MouseStateChanged(MouseState state)
{
return true;
}
/// <summary>
/// Sets the origin to be the center using the <see cref="Width"/> and <see cref="Height"/>.
/// </summary>
public virtual void CenterOrigin()
{
origin.X = Width / 2f;
origin.Y = Height / 2f;
}
/// <summary>
/// Centers this module's on the horizontal axis relative to the parent <see cref="UIModuleGroup"/>.
/// </summary>
/// <returns>True if possible and false if not.</returns>
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;
}
/// <summary>
/// Centers this module's origin on the vertical axis relative to the parent <see cref="UIModuleGroup"/>.
/// </summary>
/// <returns>True if possible.</returns>
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;
}
/// <summary>
/// Sets the position and dimension of this module.
/// </summary>
/// <param name="rectangle">The rectangle that represents the position and dimensions of the module.</param>
public virtual void SetPositionAndDimensions(Rectangle rectangle)
{
X = rectangle.X;
Y = rectangle.Y;
Width = rectangle.Width;
Height = rectangle.Height;
}
}
}

View File

@ -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
{
/// <summary>
/// Contains a group of modules and has its own relative coordinate system.
/// </summary>
public class UIModuleGroup : UIModule
{
List<UIModule> modules = new List<UIModule>();
/// <summary>
/// Set this to crop anything that flows out of the boundaries of this group. Will not crop if this is null.
/// </summary>
public BasicScissor basicScissor;
/// <summary>
/// Instantiates the UI module group.
/// </summary>
/// <param name="basicScissor">Sets the <see cref="basicScissor"/> field.</param>
public UIModuleGroup(BasicScissor basicScissor = null)
{
this.basicScissor = basicScissor;
}
/// <summary>
/// Draws this group of modules. If scissoring, will use the matrix and effect designated in the <see cref="BasicScissor"/> to begin the batch normally again.
/// </summary>
/// <param name="spriteBatch">Batch used to draw the group.</param>
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();
}
}
/// <summary>
/// Updates the group of modules.
/// </summary>
/// <param name="gameTime">Game time used.</param>
public override void Update(GameTime gameTime)
{
foreach (UIModule module in modules)
{
module.Update(gameTime);
}
}
/// <summary>
/// Adds module(s) to this group.
/// </summary>
/// <param name="addModules">The module(s) to add.</param>
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);
}
}
/// <summary>
/// Removes given module from group.
/// </summary>
/// <param name="module">module to remove.</param>
public virtual void RemoveModule(UIModule module)
{
module.parent = null;
modules.Remove(module);
}
/// <summary>
/// Obtains an array snapshot of all the modules.
/// </summary>
public UIModule[] GetCopyOfModules()
{
return modules.ToArray();
}
/// <summary>
/// Updates the keyboard state of the modules in this group.
/// </summary>
/// <param name="state">The new state.</param>
/// <returns>Whether or not to continue updating the other listeners.</returns>
public override bool KeyboardStateChanged(KeyboardState state)
{
foreach (UIModule module in modules)
{
module.KeyboardStateChanged(state);
}
return base.KeyboardStateChanged(state);
}
/// <summary>
/// Updates the moues state of the modules in this group.
/// </summary>
/// <param name="state">The new state.</param>
/// <returns>Whether or not to continue updating other listeners.</returns>
public override bool MouseStateChanged(MouseState state)
{
foreach (UIModule module in modules)
{
module.MouseStateChanged(state);
}
return base.MouseStateChanged(state);
}
}
}

View File

@ -0,0 +1,46 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RecrownedGTK.Graphics.Render;
namespace RecrownedGTK.Graphics.UI.ScreenSystem
{
/// <summary>
/// Contracts a transition that the <see cref="ScreenManager"/> can use.
/// </summary>
public interface ITransition
{
/// <summary>
/// Called once when the transition is needed.
/// <param name="dimensions">The dimensions of the screen.</param>
/// </summary>
void InitiateTransition(Rectangle dimensions);
/// <summary>
/// Called every frame if the state of the screen this transition is placed upon is in the enter transition phase.
/// </summary>
/// <param name="delta">The time passed in seconds since the last frame.</param>
/// <param name="waiting">Whether or not this transition is waiting on something. Usually the <see cref="Assets"/>.</param>
/// <returns>If this returns true, then it is considered that this transition is complete.</returns>
bool UpdateEnteringTransition(double delta, bool waiting);
/// <summary>
/// Called every frame if the state of the screen this transition is placed upon is in the exit phase.
/// </summary>
/// <param name="delta">The time passed in seconds since the last frame.</param>
/// <param name="waiting">Whether or not this transition is waiting on something. Usually the <see cref="Assets"/>.</param>
/// <returns>If this returns true, then it is considered that this transition is complete.</returns>
bool UpdateExitingTransition(double delta, bool waiting);
/// <summary>
/// Called once every frame while transition is active. Meant to draw transition.
/// </summary>
/// <param name="spriteBatch"></param>
void DrawTransition(ConsistentSpriteBatch spriteBatch);
/// <summary>
/// Updates if the previous screen uses a render target for exit transition.
/// </summary>
/// <param name="previousScreenFrame">The frame of the previous screen.</param>
void UpdatePreviousScreenFrame(RenderTarget2D previousScreenFrame);
}
}

View File

@ -0,0 +1,166 @@
using OpenTK.Graphics;
using OpenTK;
using RecrownedGTK.Graphics.Render;
using RecrownedGTK.Types;
using System;
namespace RecrownedGTK.Graphics.UI.ScreenSystem
{
/// <summary>
/// A screen specifically meant to fill in loading times.
/// </summary>
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;
/// <summary>
/// Constructs a loading screen.
/// </summary>
/// <param name="game">The game itself to adjust mouse settings and such.</param>
/// <param name="screenImage"></param>
/// <param name="proportion"></param>
/// <param name="rotate"></param>
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);
}
/// <inheritdoc />
public override void Show()
{
game.IsMouseVisible = false;
base.Show();
}
/// <inheritdoc />
public override void Hide()
{
game.IsMouseVisible = true;
base.Hide();
}
/// <summary>
/// Sets things to correct values for start of transition.
/// </summary>
/// <param name="dimensions">The window dimensions.</param>
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;
}
}
/// <summary>
/// Draws the transition.
/// </summary>
/// <param name="spriteBatch">Batch to use.</param>
public void DrawTransition(ConsistentSpriteBatch spriteBatch)
{
spriteBatch.Draw(texture, textureBounds, null, color, rotation, origin, SpriteEffects.None, 0f);
}
/// <summary>
/// Updates the entering transition.
/// </summary>
/// <param name="delta">Time passed between frames.</param>
/// <param name="waiting">Whether or not this transition should be waiting.</param>
/// <returns>Whether or not transition is complete.</returns>
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;
}
/// <summary>
/// Updates the exiting transition.
/// </summary>
/// <param name="delta">Time passed between frames.</param>
/// <param name="waiting">Whether or not this transition should be waiting.</param>
/// <returns>Whether or not transition is complete.</returns>
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;
}
}
}

View File

@ -0,0 +1,204 @@
using OpenTK.Graphics;
using RecrownedGTK.Graphics.Render;
using System.Collections.Generic;
namespace RecrownedGTK.Graphics.UI.ScreenSystem
{
/// <summary>
/// Represents one of the poosible states a screen can be in.
/// </summary>
public enum ScreenState {
/// <summary>
/// Screen is transitioning in.
/// </summary>
EnterTransition,
/// <summary>
/// Screen is transitioning out.
/// </summary>
ExitTransition,
/// <summary>
/// Screen is currently displayed normally without transition.
/// </summary>
Normal
}
/// <summary>
/// A screen represents a virtual system of management that controls an system of items to be displayed.
/// </summary>
public class Screen
{
/// <summary>
/// Transitions to apply.
/// </summary>
public readonly List<ITransition> Transitions;
/// <summary>
/// Whether or not to continue rendering this screen onto a buffer for the benefit of the next screen to use as a transition.
/// </summary>
public bool UseRenderTargetForExitTransition { get; private set; }
/// <summary>
/// The background color to be used to clear the screen.
/// </summary>
public Color4 BackgroundColor;
/// <summary>
/// The next screen to be displayed after exit transition finishes. May be null, leading to transition to previous screen or loading screen.
/// </summary>
public Screen NextScreen { get; set; }
/// <summary>
/// The current window dimensions.
/// </summary>
public int width, height;
/// <summary>
/// Current state of the screen.
/// </summary>
public ScreenState State { get; private set; }
/// <summary>
/// Creates a new screen.
/// </summary>
/// <param name="useEnterTransition">True to start in entering transition state.</param>
public Screen(bool useEnterTransition = false)
{
State = useEnterTransition ? ScreenState.EnterTransition : ScreenState.Normal;
Transitions = new List<ITransition>();
}
/// <summary>
/// Called when screen size is set.
/// </summary>
/// <param name="width">The width of the screen.</param>
/// <param name="height">The height of the screen.</param>
public virtual void ApplySize(int width, int height)
{
this.width = width;
this.height = height;
}
/// <summary>
/// Called to update the screen.
/// </summary>
/// <param name="gameTime">Game time information.</param>
public virtual void Update(GameTime gameTime)
{
}
/// <summary>
/// Called to draw this screen.
/// </summary>
/// <param name="spriteBatch">SpriteBatch to use.</param>
public virtual void Draw(ConsistentSpriteBatch spriteBatch)
{
foreach (ITransition transition in Transitions)
{
transition.DrawTransition(spriteBatch);
}
}
/// <summary>
/// Updates the transition based on the current state of the screen.
/// </summary>
/// <param name="delta">Time passed since last frame in seconds.</param>
/// <param name="waiting">If the this transition should wait.</param>
/// <returns>Only returns true if exit transition is complete. Returns false otherwise.</returns>
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;
}
/// <summary>
/// Called when the screen is shown.
/// </summary>
public virtual void Show()
{
foreach (ITransition transition in Transitions)
{
transition.InitiateTransition(new Rectangle(0, 0, width, height));
}
}
/// <summary>
/// Called when this screen is no longer the displayed screen.
/// </summary>
public virtual void Hide()
{
}
/// <summary>
/// Called whenever the status of assets changes from being loaded to unloaded or unloaded to loaded.
/// </summary>
/// <param name="state">True for loaded, false for unloaded.</param>
public virtual void AssetLoadStateChange(bool state)
{
}
/// <summary>
/// Call this to begin exit transition.
/// </summary>
/// <param name="UseRenderTargetForExitTransition">Whether or not to use a render target for the next screen to use.</param>
public void StartExitTransition(bool UseRenderTargetForExitTransition = false)
{
this.UseRenderTargetForExitTransition = UseRenderTargetForExitTransition;
State = ScreenState.ExitTransition;
}
/// <summary>
/// Called everytime the previous screen frame buffer updates. Used for transition purposes.
/// </summary>
/// <param name="previousScreenFrame">The previous screen's render buffer.</param>
public virtual void UpdatePreviousScreenFrame(RenderTarget2D previousScreenFrame)
{
foreach (ITransition transition in Transitions)
{
transition.UpdatePreviousScreenFrame(previousScreenFrame);
}
}
}
}

View File

@ -0,0 +1,213 @@
using OpenTK.Graphics;
using RecrownedGTK.Graphics.Render;
using System;
using System.Diagnostics;
namespace RecrownedGTK.Graphics.UI.ScreenSystem
{
/// <summary>
/// Called when the first screen is being shown.
/// </summary>
/// <param name="screen">The screen to show after the loading screen.</param>
public delegate void NeedNextScreen(Screen screen);
/// <summary>
/// A manager for screens. Helps with transitions and updating screens as well as resizes.
/// </summary>
public class ScreenManager : IDisposable
{
bool disposed = false;
/// <summary>
/// Called when the first loading screen is done, and needs to show the landing screen.
/// </summary>
public event NeedNextScreen NeedNextScreen;
/// <summary>
/// The settings this manager will use to begin a sprite batch.
/// </summary>
private GraphicsDeviceManager graphics;
private Screen previousScreen;
private RenderTarget2D previousScreenRenderTarget;
private Screen currentScreen;
private bool firstScreenChangeComplete;
private bool resizing;
/// <summary>
/// Currently displayed screen.
/// </summary>
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();
}
}
/// <summary>
/// Creates a screen manager that helps update, draw, and manage multiple screens and their transitions. Uses its own <see cref="SpriteBatch"/>.
/// </summary>
/// <param name="graphicsDeviceManager">The graphics device manager to be used.</param>
public ScreenManager(GraphicsDeviceManager graphicsDeviceManager)
{
graphics = graphicsDeviceManager ?? throw new ArgumentNullException("Graphics device manager argument cannot be null.");
}
/// <summary>
/// Updates the screens. Should be called once every frame.
/// </summary>
/// <param name="gameTime">Contains the time that has passed from the last frame.</param>
/// <param name="waiting">Whether or not there is something a transition should be waiting for. Usually used to wait for assets to complete loading.</param>
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);
}
/// <summary>
/// Renders screen into window.
/// Starts and ends the given <paramref name="consistentSpriteBatch"/>.
/// </summary>
/// <param name="consistentSpriteBatch">The consistent sprite batch to use. Needs to be consistent as changing render targets requires ending and beginning the sprite batch.</param>
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();
}
/// <summary>
/// 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.
/// </summary>
/// <param name="loadingScreen">The loading screen to change to.</param>
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;
}
}
/// <summary>
/// Notifies all screen that assets have completed being loaded after a resize.
/// </summary>
public void PostResize()
{
if (!resizing) throw new InvalidOperationException("Was never resizing.");
Screen.AssetLoadStateChange(true);
}
private void OnNeedNextScreen(Screen screen)
{
NeedNextScreen?.Invoke(screen);
}
/// <summary>
/// Disposes this.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// An overridable dispose.
/// </summary>
/// <param name="disposing">True of user invoked dispose called.</param>
public virtual void Dispose(bool disposing)
{
if (disposed) throw new ObjectDisposedException(GetType().Name);
if (disposing)
{
previousScreenRenderTarget?.Dispose();
}
disposed = true;
}
/// <summary>
/// Destructor.
/// </summary>
~ScreenManager()
{
Dispose(false);
}
}
}

View File

@ -0,0 +1,28 @@
using RecrownedGTK.Graphics.UI.Modular.Modules.Interactive;
namespace RecrownedGTK.Graphics.UI.SkinSystem.Definitions
{
/// <summary>
/// Skin definition for a button.
/// </summary>
public class ButtonSkinDefinition : SkinDefinitionData
{
/// <summary>
/// Names for the regions in the texture atlas respectively.
/// </summary>
public string upRegion, downRegion, disabledRegion, selectedRegion;
/// <summary>
/// Constructs the definition with minimum requirements.
/// </summary>
/// <param name="downRegion">Name of region specifying the texture shown for when the button is pressed down.</param>
/// <param name="upRegion">Name of region specifying the texture shown for when the button is not pressed.</param>
public ButtonSkinDefinition(string downRegion, string upRegion)
{
UIModuleType = typeof(Button);
this.downRegion = downRegion;
this.upRegion = upRegion;
}
}
}

View File

@ -0,0 +1,20 @@
using System;
namespace RecrownedGTK.Graphics.UI.SkinSystem.Definitions
{
/// <summary>
/// A definition containing the data for the skin system. Needs to follow data transfer object model.
/// </summary>
public abstract class SkinDefinitionData
{
/// <summary>
/// The full name of the UI module this definition defines.
/// </summary>
public string uiModuleTypeFullName;
/// <summary>
/// Sets the module type by setting <see cref="uiModuleTypeFullName"/>.
/// </summary>
public Type UIModuleType { set { uiModuleTypeFullName = value.FullName; } }
}
}

View File

@ -0,0 +1,25 @@
using RecrownedGTK.Graphics.UI.Modular.Modules.Interactive;
namespace RecrownedGTK.Graphics.UI.SkinSystem.Definitions
{
/// <summary>
/// Definition for a text button for a skin theme.
/// </summary>
public class TextButtonSkinDefinition : ButtonSkinDefinition
{
/// <summary>
/// Name of color from the skin to use for the font.
/// </summary>
public string fontColor;
/// <summary>
/// Creates this definition with the most minimal requirements.
/// </summary>
/// <param name="downRegion">Texture region from skin that represents when the button is pressed down.</param>
/// <param name="upRegion">The texture region that represents when the button is not pressed.</param>
public TextButtonSkinDefinition(string downRegion, string upRegion) : base(downRegion, upRegion)
{
UIModuleType = typeof(TextButton);
}
}
}

View File

@ -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
{
/// <summary>
/// Skin definition of a scroll module.
/// </summary>
public class UIScrollableSkinDefinition : SkinDefinitionData
{
/// <summary>
/// Name of the region that specifies the texture needed.
/// </summary>
public string horizontalBar, verticalBar, horizontalBarTrack, verticalBarTrack, background;
/// <summary>
/// Instantiates the definition with the minimum requirements.
/// </summary>
/// <param name="horizontalBar">Name of the region used by the skin that defines what the horizontal scroll bar looks like.</param>
/// <param name="verticalBar">Name of the region used by the skin that defines what the vertical scroll bar looks like.</param>
public UIScrollableSkinDefinition(string horizontalBar, string verticalBar)
{
this.horizontalBar = horizontalBar;
this.verticalBar = verticalBar;
UIModuleType = typeof(UIScrollable);
}
}
}

View File

@ -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
{
/// <summary>
/// The output requirements of a skin. This allows for very customized skin systems if needed.
/// </summary>
public interface ISkin
{
/// <summary>
/// The texture for the cursor.
/// </summary>
Texture2D CursorTexture { get; }
/// <summary>
/// Draws a region from the texture atlas.
/// </summary>
/// <param name="regionName">Region to draw.</param>
/// <param name="color">The color to tint the region.</param>
/// <param name="batch">The batch to use.</param>
/// <param name="destination">The destination to draw to.</param>
/// <param name="rotation">The rotation to use in radians.</param>
/// <param name="origin">The origin for the rotation.</param>
void Draw(string regionName, string color, ConsistentSpriteBatch batch, Rectangle destination, float rotation = 0, Vector2 origin = default(Vector2));
/// <summary>
/// Returns a <see cref="Color"/> with given name of defined color;
/// Should use value "default" if <paramref name="name"/> is null.
/// </summary>
/// <param name="name">Name of defined color.</param>
/// <returns>The defined color based on the name given.</returns>
Color4 GetColor(string name = null);
/// <summary>
/// Returns a <see cref="TextureAtlas.Region"/> with given name of region.
/// </summary>
/// <param name="name">Name of region.</param>
/// <param name="required">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.</param>
/// <returns>The region corresponding to the name and may return null depending on if the region exists, and is required.</returns>
TextureAtlas.Region GetTextureAtlasRegion(string name, bool required = false);
/// <summary>
/// Returns the proper definition for the given parameters or throws exception in the case the requested definition does not exist.
/// </summary>
/// <typeparam name="T">Convenience to cast to the needed definition type.</typeparam>
/// <param name="definitionName">The name of the definition. Default is null and will be replaced with "default" for name.</param>
/// <returns>The definition cast to T.</returns>
T ObtainDefinition<T>(string definitionName = null) where T : SkinDefinitionData;
}
}

View File

@ -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
{
/// <summary>
/// The skin to try to use first.
/// </summary>
public ISkin mainSkin;
/// <summary>
/// The fallback skin.
/// </summary>
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<T>(string definitionName = null) where T : SkinDefinitionData
{
try
{
return mainSkin.ObtainDefinition<T>(definitionName);
}
catch (NullReferenceException)
{
return alternateSkin.ObtainDefinition<T>(definitionName);
}
}
}
}

View File

@ -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
{
/// <summary>
/// A skin is used to group a theme which can then be applied to the UI via the use of modules.
/// </summary>
public class Skin : IDisposable, ISkin
{
/// <summary>
/// Whether or not this skin is completed being built and thus ready to use.
/// </summary>
public bool Laminated { get; private set; }
private bool disposed;
private TextureAtlas textureAtlas;
Dictionary<string, Color4> colors;
readonly Dictionary<string, string> definitionOfType;
readonly Dictionary<string, Dictionary<string, SkinDefinitionData>> definitions;
/// <summary>
/// The texture for the cursor.
/// </summary>
public virtual Texture2D CursorTexture { get; private set; }
/// <summary>
/// Creates a basic unfilled skin.
/// </summary>
/// <param name="textureAtlas">The texture atlas to use for this skin.</param>
/// <param name="cursorTexture">The texture the cursor will be.</param>
public Skin(TextureAtlas textureAtlas, Texture2D cursorTexture)
{
this.textureAtlas = textureAtlas;
this.CursorTexture = cursorTexture;
colors = new Dictionary<string, Color4>();
definitionOfType = new Dictionary<string, string>();
definitions = new Dictionary<string, Dictionary<string, SkinDefinitionData>>();
}
/// <summary>
/// Returns a <see cref="TextureAtlas.Region"/> with given name of region. Null values acceptable. Will return null if parameter is null.
/// </summary>
/// <param name="name">Name of region.</param>
/// <param name="required">Whether or not this texture is mandatory for the module to work. If true, will throw error on failing to retrieve.</param>
/// <returns>The region corresponding to the name or null if the requested region doesn't exist.</returns>
public TextureAtlas.Region GetTextureAtlasRegion(string name, bool required = false)
{
if (!required && (name == null || !textureAtlas.ContainsRegion(name)))
{
return null;
} else
{
return textureAtlas[name];
}
}
/// <summary>
/// Returns a <see cref="Color"/> with given name of defined color;
/// </summary>
/// <param name="name">Name of defined color. Will use "default" if null. Default value is null.</param>
/// <returns>The defined color based on the name given.</returns>
public Color4 GetColor(string name = null)
{
if (name == null) name = "default";
return colors[name];
}
/// <summary>
/// Draws a region from the texture atlas.
/// </summary>
/// <param name="regionName">Region to draw.</param>
/// <param name="color">The color to tint the region.</param>
/// <param name="batch">The batch to use.</param>
/// <param name="destination">The destination to draw to.</param>
/// <param name="rotation">The rotation to use in radians.</param>
/// <param name="origin">The origin for the rotation.</param>
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];
}
/// <summary>
/// Returns the proper definition for the given parameters or throws exception in the case the requested definition does not exist.
/// </summary>
/// <typeparam name="T">Convenience to cast to the needed definition type.</typeparam>
/// <param name="definitionName">The name of the definition.</param>
/// <returns>The definition cast to T.</returns>
public T ObtainDefinition<T>(string definitionName = null) where T : SkinDefinitionData
{
return (T)ObtainDefinition(definitionOfType[typeof(T).FullName], definitionName);
}
/// <summary>
/// Adds the definition.
/// </summary>
/// <param name="definitionName">The name of the definition. Default (if left blank) name is "default".</param>
/// <param name="skinDefinition">The definition itself.</param>
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<string, SkinDefinitionData>());
}
else if (definitions[skinDefinition.uiModuleTypeFullName].ContainsKey(definitionName)) throw new ArgumentException("Type of definition with that name already exists!");
definitions[skinDefinition.uiModuleTypeFullName].Add(definitionName, skinDefinition);
}
/// <summary>
/// Adds color to skin.
/// </summary>
/// <param name="name"></param>
/// <param name="color"></param>
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);
}
/// <summary>
/// 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.
/// </summary>
public void Laminate()
{
Laminated = true;
}
/// <summary>
/// Disposes <see cref="textureAtlas"/> and the <see cref="Texture2D"/> holding the cursor texture.
/// </summary>
public void Dispose()
{
if (disposed) throw new ObjectDisposedException(GetType().Name);
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Overridable dispose function.
/// </summary>
/// <param name="disposing">true when it's a user call to dispose.</param>
public virtual void Dispose(bool disposing)
{
disposed = true;
if (disposing && !disposed)
{
textureAtlas.Dispose();
CursorTexture.Dispose();
}
}
/// <summary>
/// Destructor. Calls the dispose with false.
/// </summary>
~Skin()
{
Dispose(false);
}
}
}

View File

@ -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
{
/// <summary>
/// Called when the skin manager has completed a async action.
/// </summary>
/// <param name="actionCompleted">The completed action.</param>
public delegate void AsyncComplete(SkinManager.Action actionCompleted);
/// <summary>
/// Manages reference to default and loading of custom skins.
/// </summary>
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;
/// <summary>
/// Whether or not the skin manager is set up with a <see cref="BaseSkin"/> and <see cref="loadedSkin"/>.
/// </summary>
public bool MergingSkins { get { return (loadedSkin != null && SkinUseable); } }
/// <summary>
/// Whether or not this manager has been set up with at least a base skin.
/// </summary>
public bool SkinUseable { get { return BaseSkin == null; } }
/// <summary>
/// The list of paths for all found skins by <see cref="SearchSkinDirectory"/>. May be null.
/// </summary>
public volatile List<string> skinPaths;
/// <summary>
/// The event that is called when a state changes.
/// </summary>
public event AsyncComplete AsyncCompleteEvent;
/// <summary>
/// The various possible states a skin manager could be in.
/// </summary>
public enum Action
{
/// <summary>
/// After a search has completed.
/// </summary>
SEARCH,
/// <summary>
/// Having the skin generated to be in a useable state.
/// </summary>
LOAD
}
/// <summary>
/// the skin that favors the selected skin, but still has a fallback to the default skin in case anything is missing.
/// </summary>
public ISkin Skin { get { return mergedSkin; } }
/// <summary>
/// The user loaded skin resulted from asynchronous <see cref="LoadSkin(string, GraphicsDevice)"/>.
/// </summary>
public ISkin loadedSkin { get { return mergedSkin.mainSkin; } private set { mergedSkin.mainSkin = value; } }
/// <summary>
/// The default skin in case the selected skin doesn't cover a specific definition or color.
/// </summary>
public ISkin BaseSkin { get { return mergedSkin.alternateSkin; } set { mergedSkin.alternateSkin = value; } }
/// <summary>
/// The directory that contains the skins.
/// </summary>
public string skinsDirectory;
/// <summary>
/// Performs a recursive asynchronous search of the directory given in a path set by <see cref="skinsDirectory"/>.
/// </summary>
public void SearchSkinDirectory()
{
action = Action.SEARCH;
AttemptAsync();
}
/// <summary>
/// Reads skin data if extension is valid.
/// </summary>
/// <param name="path">the path to load from.</param>
/// <returns>A <see cref="SkinData"/> that holds all the information and some metadata for the loaded skin.</returns>
public SkinData ReadSkinData(string path)
{
if (path.ToLower().EndsWith(EXTENSION))
{
return JsonConvert.DeserializeObject<SkinData>(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) + "\".");
}
/// <summary>
/// loads a skin asynchronously to the <see cref="loadedSkin"/>.
/// </summary>
/// <param name="path">The path pointing to the file with the extension "<see cref="EXTENSION"/>".</param>
/// <param name="graphicsDevice">Graphics device to use for texture creation.</param>
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<string, string> filePath = new Dictionary<string, string>();
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<TextureAtlasData>(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<string> RecursiveSkinSearch(string path)
{
string[] files = Directory.GetFiles(path);
string[] folders = Directory.GetDirectories(path);
List<string> skins = new List<string>();
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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,6 @@
namespace RecrownedGTK.ParticleSystem
{
class Particle
{
}
}

View File

@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Xml.Serialization;
namespace RecrownedGTK.Persistence
{
/// <summary>
/// Manages a bundle of preferences.
/// </summary>
public class PreferencesManager
{
private readonly Dictionary<Type, object> preferenceList;
string savePath;
XmlSerializer xmlSerializer;
/// <summary>
/// Constructs the preference manager.
/// </summary>
/// <param name="savePath">The path of the directory in which the preferences should be saved.</param>
/// <param name="preferences">The preferences to be serialized and unserialized in XML format.</param>
public PreferencesManager(string savePath, params object[] preferences)
{
this.savePath = savePath;
Directory.CreateDirectory(savePath);
preferenceList = new Dictionary<Type, object>();
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);
}
/// <summary>
/// Returns the preference by type.
/// </summary>
/// <typeparam name="T">The preference needed.</typeparam>
/// <returns>The preference needed.</returns>
public T GetPreferences<T>()
{
return (T)preferenceList[typeof(T)];
}
/// <summary>
/// Loads preferences.
/// </summary>
public void Load()
{
List<Type> keys = new List<Type>(preferenceList.Keys);
foreach (Type key in keys)
{
if (!LoadSpecific(key))
{
SaveSpecific(key);
}
}
}
/// <summary>
/// Saves preferences.
/// </summary>
public void Save()
{
foreach (KeyValuePair<Type, object> 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();
}
}
}

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3"/>
<PackageReference Include="OpenTK" Version="3.1.0"/>
</ItemGroup>
</Project>

View File

@ -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);
}
}
}

View File

@ -0,0 +1,17 @@
using RecrownedGTK.Graphics.Render;
namespace RecrownedGTK.Types
{
/// <summary>
/// A wrapper that makes sure anything implementing can be drawn with options.
/// </summary>
public interface IRectangleDrawable
{
byte[] ColorData {
get;
}
float[] vertices {
get;
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,54 @@
using System;
namespace RecrownedGTK.Types
{
/// <summary>
/// Holds a width and height while allowing for easier comparison between other <see cref="Resolution"/>s.
/// </summary>
public struct Resolution : IComparable<Resolution>
{
/// <summary>
/// Dimensions of resolution.
/// </summary>
public int Width, Height;
/// <summary>
/// Constructs resolution given the dimensions.
/// </summary>
/// <param name="width">Width of resolution.</param>
/// <param name="height">Height of resolution.</param>
public Resolution(int width, int height)
{
Width = width;
Height = height;
}
/// <summary>
/// Compares area of this resolution to another.
/// </summary>
/// <param name="other">The other resolution to compare to.</param>
/// <returns>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.</returns>
public int CompareTo(Resolution other)
{
return Area() - other.Area();
}
/// <summary>
/// Gets a string representation of this resolution.
/// </summary>
/// <returns>"WidthxHeight"</returns>
public override string ToString()
{
return Width + "x" + Height;
}
/// <summary>
/// Calculates area of resolution.
/// </summary>
/// <returns>Area of resolution.</returns>
public int Area()
{
return Width * Height;
}
}
}