From 698dd9d2f027fbba7d442046a6e9e0c1a28bc0d8 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Thu, 29 Nov 2018 20:41:06 -0600 Subject: [PATCH] moved utilities for rhythmbullet to separate project for a better custom content loaders for the monogame pipeline as well as for future projects and of course learning how things work. --- Recrowned-Athenaeum/Camera/Camera2D.cs | 45 +++++ .../ContentSystem/ContentLoad.cs | 22 +++ .../ContentSystem/ContentManagerController.cs | 157 +++++++++++++++++ .../ContentSystem/IContentPathModifier.cs | 18 ++ .../ContentSystem/NormalContentResolver.cs | 16 ++ Recrowned-Athenaeum/DataTypes/NinePatch.cs | 160 ++++++++++++++++++ Recrowned-Athenaeum/DataTypes/Resolution.cs | 38 +++++ Recrowned-Athenaeum/Input/IInputListener.cs | 16 ++ Recrowned-Athenaeum/Input/InputListener.cs | 22 +++ Recrowned-Athenaeum/Input/InputUtilities.cs | 73 ++++++++ .../{Class1.cs => ParticleSystem/Particle.cs} | 4 +- .../Persistence/Preferences.cs | 13 ++ .../Persistence/PreferencesManager.cs | 79 +++++++++ .../Recrowned-Athenaeum.csproj | 41 ++++- .../ScreenSystem/ITransition.cs | 48 ++++++ .../ScreenSystem/LoadingScreen.cs | 136 +++++++++++++++ Recrowned-Athenaeum/ScreenSystem/Screen.cs | 146 ++++++++++++++++ .../ScreenSystem/ScreenManager.cs | 137 +++++++++++++++ Recrowned-Athenaeum/UI/Book/Book.cs | 93 ++++++++++ Recrowned-Athenaeum/UI/Book/Page.cs | 35 ++++ .../UI/Modular/Modules/Image.cs | 63 +++++++ .../UI/Modular/Modules/Interactive/Button.cs | 62 +++++++ .../Modular/Modules/Interactive/TextButton.cs | 37 ++++ .../UI/Modular/Modules/TextLabel.cs | 146 ++++++++++++++++ Recrowned-Athenaeum/UI/Modular/UIModule.cs | 74 ++++++++ .../UI/Modular/UIModuleGroup.cs | 109 ++++++++++++ 26 files changed, 1787 insertions(+), 3 deletions(-) create mode 100644 Recrowned-Athenaeum/Camera/Camera2D.cs create mode 100644 Recrowned-Athenaeum/ContentSystem/ContentLoad.cs create mode 100644 Recrowned-Athenaeum/ContentSystem/ContentManagerController.cs create mode 100644 Recrowned-Athenaeum/ContentSystem/IContentPathModifier.cs create mode 100644 Recrowned-Athenaeum/ContentSystem/NormalContentResolver.cs create mode 100644 Recrowned-Athenaeum/DataTypes/NinePatch.cs create mode 100644 Recrowned-Athenaeum/DataTypes/Resolution.cs create mode 100644 Recrowned-Athenaeum/Input/IInputListener.cs create mode 100644 Recrowned-Athenaeum/Input/InputListener.cs create mode 100644 Recrowned-Athenaeum/Input/InputUtilities.cs rename Recrowned-Athenaeum/{Class1.cs => ParticleSystem/Particle.cs} (66%) create mode 100644 Recrowned-Athenaeum/Persistence/Preferences.cs create mode 100644 Recrowned-Athenaeum/Persistence/PreferencesManager.cs create mode 100644 Recrowned-Athenaeum/ScreenSystem/ITransition.cs create mode 100644 Recrowned-Athenaeum/ScreenSystem/LoadingScreen.cs create mode 100644 Recrowned-Athenaeum/ScreenSystem/Screen.cs create mode 100644 Recrowned-Athenaeum/ScreenSystem/ScreenManager.cs create mode 100644 Recrowned-Athenaeum/UI/Book/Book.cs create mode 100644 Recrowned-Athenaeum/UI/Book/Page.cs create mode 100644 Recrowned-Athenaeum/UI/Modular/Modules/Image.cs create mode 100644 Recrowned-Athenaeum/UI/Modular/Modules/Interactive/Button.cs create mode 100644 Recrowned-Athenaeum/UI/Modular/Modules/Interactive/TextButton.cs create mode 100644 Recrowned-Athenaeum/UI/Modular/Modules/TextLabel.cs create mode 100644 Recrowned-Athenaeum/UI/Modular/UIModule.cs create mode 100644 Recrowned-Athenaeum/UI/Modular/UIModuleGroup.cs diff --git a/Recrowned-Athenaeum/Camera/Camera2D.cs b/Recrowned-Athenaeum/Camera/Camera2D.cs new file mode 100644 index 0000000..904bc05 --- /dev/null +++ b/Recrowned-Athenaeum/Camera/Camera2D.cs @@ -0,0 +1,45 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.Utilities.Camera +{ + public class Camera2D + { + public float Zoom; + public Vector2 Position; + public Matrix TransformMatrix { get; private set; } + public GraphicsDevice graphicsDevice; + + public Camera2D(GraphicsDevice graphicsDevice) + { + this.graphicsDevice = graphicsDevice; + Zoom = 1f; + Position.X = this.graphicsDevice.Viewport.Width * 0.5f; + Position.Y = this.graphicsDevice.Viewport.Height * 0.5f; + } + + public void Update() + { + Rectangle bounds = graphicsDevice.Viewport.Bounds; + TransformMatrix = + Matrix.CreateTranslation(new Vector3(-Position.X, -Position.Y, 0)) * + Matrix.CreateScale(Zoom) * + Matrix.CreateTranslation(new Vector3(bounds.Width * 0.5f, bounds.Height * 0.5f, 0f)); + } + + 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; + } + } +} diff --git a/Recrowned-Athenaeum/ContentSystem/ContentLoad.cs b/Recrowned-Athenaeum/ContentSystem/ContentLoad.cs new file mode 100644 index 0000000..52e1318 --- /dev/null +++ b/Recrowned-Athenaeum/ContentSystem/ContentLoad.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.Utilities.ContentSystem +{ + struct LoadableContent + { + internal Type type; + internal string assetName; + internal bool usePathModifier; + + public LoadableContent(string assetName, Type type, bool usePathModifier) + { + this.type = type; + this.assetName = assetName; + this.usePathModifier = usePathModifier; + } + } +} diff --git a/Recrowned-Athenaeum/ContentSystem/ContentManagerController.cs b/Recrowned-Athenaeum/ContentSystem/ContentManagerController.cs new file mode 100644 index 0000000..06babc2 --- /dev/null +++ b/Recrowned-Athenaeum/ContentSystem/ContentManagerController.cs @@ -0,0 +1,157 @@ +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace RhythmBullet.Utilities.ContentSystem +{ + public class ContentManagerController + { + Thread thread; + readonly ContentManager contentManager; + readonly Queue queue; + Dictionary assets; + public readonly Dictionary contentPathModifier; + volatile float progress; + volatile bool running; + + public ContentManagerController(ContentManager contentManager) + { + this.contentManager = contentManager; + assets = new Dictionary(); + queue = new Queue(); + contentPathModifier = new Dictionary(); + } + + private void Load(string assetName, Type type, bool usePathModifier) + { + string path = assetName; + if (usePathModifier) + { + IContentPathModifier handler = contentPathModifier[type]; + path = handler.Modify(assetName); + + } + assets.Add(assetName, contentManager.Load(path)); + + Debug.WriteLine("Loaded asset: " + assetName); + } + + public T Get(string assetName) + { + lock (queue) + { + return (T)assets[assetName]; + } + } + + public void Queue(string assetName, bool usePathModifier = true) where T : IDisposable + { + lock (queue) + { + if (!assets.ContainsKey(assetName)) + { + queue.Enqueue(new LoadableContent(assetName, typeof(T), usePathModifier)); + Debug.WriteLine("Queued asset: " + assetName); + } + else + { + throw new InvalidOperationException("Did not queue asset due to asset with same name being loaded: " + assetName); + } + } + } + + /// + /// Called whenever a batch of assets should be loaded from the queue. Safe to call once every frame. + /// + public void Update() + { + if (queue.Count > 0 && (thread == null || !thread.IsAlive)) + { + thread = new Thread(LoadBatch); + thread.Start(); + } + } + + private void LoadBatch() + { + running = true; + int totalTasks = queue.Count; + int tasksCompleted = 0; + while (queue.Count != 0) + { + lock (queue) + { + LoadableContent content = queue.Dequeue(); + Load(content.assetName, content.type, content.usePathModifier); + tasksCompleted++; + progress = (float)tasksCompleted / totalTasks; + } + } + running = false; + } + /// + /// Removes the asset from the list of assets in the system. + /// Cannot remove from queue. + /// + /// the string name used to load the asset + public void Remove(string name) + { + lock (queue) + { + if (assets.ContainsKey(name)) + { + assets[name].Dispose(); + assets.Remove(name); + } + } + } + + /// + /// Clears the queue. + /// + public void ClearQueue() + { + lock (queue) + { + queue.Clear(); + } + } + + /// + /// Unloads everything from both queue and loaded list while properly disposing of the assets loaded. + /// + public void UnloadAll() + { + lock (queue) + { + contentManager.Unload(); + assets.Clear(); + ClearQueue(); + Debug.WriteLine("Unloaded all assets."); + } + } + + public bool Done + { + get + { + return !running && queue.Count == 0; + } + } + + public float Progress + { + get + { + return progress; + } + } + } +} diff --git a/Recrowned-Athenaeum/ContentSystem/IContentPathModifier.cs b/Recrowned-Athenaeum/ContentSystem/IContentPathModifier.cs new file mode 100644 index 0000000..7ef1219 --- /dev/null +++ b/Recrowned-Athenaeum/ContentSystem/IContentPathModifier.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.Utilities.ContentSystem +{ + public interface IContentPathModifier + { + /// + /// Returns the complete path with the content folder as root. + /// + /// Is the asset's name + /// + string Modify(string assetName); + } +} diff --git a/Recrowned-Athenaeum/ContentSystem/NormalContentResolver.cs b/Recrowned-Athenaeum/ContentSystem/NormalContentResolver.cs new file mode 100644 index 0000000..b470c50 --- /dev/null +++ b/Recrowned-Athenaeum/ContentSystem/NormalContentResolver.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.Utilities.ContentSystem +{ + class NormalContentResolver : IContentPathModifier + { + public string Modify(string assetName) + { + return assetName; + } + } +} diff --git a/Recrowned-Athenaeum/DataTypes/NinePatch.cs b/Recrowned-Athenaeum/DataTypes/NinePatch.cs new file mode 100644 index 0000000..75a4bbf --- /dev/null +++ b/Recrowned-Athenaeum/DataTypes/NinePatch.cs @@ -0,0 +1,160 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.Utilities.DataTypes +{ + public class NinePatch + { + public Color color; + readonly Texture2D texture; + readonly int a, b, c, d; + private Rectangle sourceRectangle; + private Rectangle drawnRectangle; + + /// + /// A nine patch object. + /// + /// Texture used for the nine patch. Dimensions must be greater than their sum border counter parts. + /// Left side. + /// Right side. + /// Bottom side. + /// Top side. + public NinePatch(Texture2D texture, int a, int b, int c, int d) + { + this.texture = texture; + if (a + b >= texture.Width) throw new ArgumentOutOfRangeException("a and b cannot be greater than or equal to the width of texture."); + if (c + d >= texture.Height) throw new ArgumentOutOfRangeException("c and d cannot be greater or equal to the height of the texture."); + this.a = a; + this.b = b; + this.c = c; + this.d = d; + + color = Color.White; + } + + public void Draw(SpriteBatch batch, Rectangle destination) + { + //1x1 + drawnRectangle.X = destination.X; + drawnRectangle.Y = destination.Y; + drawnRectangle.Width = a; + drawnRectangle.Height = c; + + sourceRectangle.X = 0; + sourceRectangle.Y = 0; + sourceRectangle.Width = a; + sourceRectangle.Height = c; + + batch.Draw(texture, drawnRectangle, sourceRectangle, color); + + //2x1 + drawnRectangle.X = destination.X + a; + drawnRectangle.Y = destination.Y; + drawnRectangle.Width = destination.Width - a - b; + drawnRectangle.Height = c; + + sourceRectangle.X = a; + sourceRectangle.Y = 0; + sourceRectangle.Width = texture.Width - a - b; + sourceRectangle.Height = c; + + batch.Draw(texture, drawnRectangle, sourceRectangle, color); + + //3x1 + drawnRectangle.X = destination.X + destination.Width - b; + drawnRectangle.Y = destination.Y; + drawnRectangle.Width = b; + drawnRectangle.Height = c; + + sourceRectangle.X = texture.Width - b; + sourceRectangle.Y = 0; + sourceRectangle.Width = b; + sourceRectangle.Height = c; + + batch.Draw(texture, drawnRectangle, sourceRectangle, color); + + //1x2 + drawnRectangle.X = destination.X; + drawnRectangle.Y = destination.Y + c; + drawnRectangle.Width = a; + drawnRectangle.Height = destination.Height - d - c; + + sourceRectangle.X = 0; + sourceRectangle.Y = c; + sourceRectangle.Width = a; + sourceRectangle.Height = texture.Height - c - d; + + batch.Draw(texture, drawnRectangle, sourceRectangle, color); + + //2x2 + drawnRectangle.X = destination.X + a; + drawnRectangle.Y = destination.Y + c; + drawnRectangle.Width = destination.Width - a - b; + drawnRectangle.Height = destination.Height - c - d; + + sourceRectangle.X = a; + sourceRectangle.Y = c; + sourceRectangle.Width = texture.Width - a - b; + sourceRectangle.Height = texture.Height - c - d; + + batch.Draw(texture, drawnRectangle, sourceRectangle, color); + + //3x2 + drawnRectangle.X = destination.X + destination.Width - b; + drawnRectangle.Y = destination.Y + c; + drawnRectangle.Width = b; + drawnRectangle.Height = destination.Height - c - d; + + sourceRectangle.X = texture.Width - b; + sourceRectangle.Y = c; + sourceRectangle.Width = b; + sourceRectangle.Height = texture.Height - c - d; + + batch.Draw(texture, drawnRectangle, sourceRectangle, color); + + //1x3 + drawnRectangle.X = destination.X; + drawnRectangle.Y = destination.Height - d; + drawnRectangle.Width = a; + drawnRectangle.Height = d; + + sourceRectangle.X = a; + sourceRectangle.Y = texture.Height - d; + sourceRectangle.Width = a; + sourceRectangle.Height = d; + + batch.Draw(texture, drawnRectangle, sourceRectangle, color); + + //2x3 + drawnRectangle.X = destination.X + a; + drawnRectangle.Y = destination.Height - d; + drawnRectangle.Width = destination.Width - a - b; + drawnRectangle.Height = d; + + sourceRectangle.X = a; + sourceRectangle.Y = texture.Height - d; + sourceRectangle.Width = texture.Width - a - b; + sourceRectangle.Height = d; + + batch.Draw(texture, drawnRectangle, sourceRectangle, color); + + //3x3 + drawnRectangle.X = destination.X + destination.Width - b; + drawnRectangle.Y = destination.Height - d; + drawnRectangle.Width = b; + drawnRectangle.Height = d; + + sourceRectangle.X = texture.Width - b; + sourceRectangle.Y = texture.Height - d; + sourceRectangle.Width = b; + sourceRectangle.Height = d; + + batch.Draw(texture, drawnRectangle, sourceRectangle, color); + } + } +} diff --git a/Recrowned-Athenaeum/DataTypes/Resolution.cs b/Recrowned-Athenaeum/DataTypes/Resolution.cs new file mode 100644 index 0000000..230e732 --- /dev/null +++ b/Recrowned-Athenaeum/DataTypes/Resolution.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.Utilities.DataTypes +{ + public class Resolution : IComparable + { + public int Width, Height; + + public Resolution(int width, int height) + { + Width = width; + Height = height; + } + + public Resolution() + { + } + + public int CompareTo(Resolution other) + { + return Area() - other.Area(); + } + + public override string ToString() + { + return Width + "x" + Height; + } + + public int Area() + { + return Width * Height; + } + } +} diff --git a/Recrowned-Athenaeum/Input/IInputListener.cs b/Recrowned-Athenaeum/Input/IInputListener.cs new file mode 100644 index 0000000..bfe33a4 --- /dev/null +++ b/Recrowned-Athenaeum/Input/IInputListener.cs @@ -0,0 +1,16 @@ +using Microsoft.Xna.Framework.Input; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.Utilities.Input +{ + public interface IInputListener + { + bool KeyboardStateChanged(KeyboardState state); + + bool MouseStateChanged(MouseState state); + } +} diff --git a/Recrowned-Athenaeum/Input/InputListener.cs b/Recrowned-Athenaeum/Input/InputListener.cs new file mode 100644 index 0000000..95280f1 --- /dev/null +++ b/Recrowned-Athenaeum/Input/InputListener.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Xna.Framework.Input; + +namespace RhythmBullet.Utilities.Input +{ + class InputListener : IInputListener + { + public bool KeyboardStateChanged(KeyboardState state) + { + return true; + } + + public bool MouseStateChanged(MouseState state) + { + return true; + } + } +} diff --git a/Recrowned-Athenaeum/Input/InputUtilities.cs b/Recrowned-Athenaeum/Input/InputUtilities.cs new file mode 100644 index 0000000..d36788e --- /dev/null +++ b/Recrowned-Athenaeum/Input/InputUtilities.cs @@ -0,0 +1,73 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using RhythmBullet.Utilities.Input; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.Utilities.Input +{ + public static class InputUtilities + { + public static List InputListeners; + static KeyboardState keyboardState; + static MouseState mouseState; + + static InputUtilities() + { + InputListeners = new List(); + } + + public static void Update() + { + KeyboardState updatedKeyboardState = Keyboard.GetState(); + MouseState updatedMouseState = Mouse.GetState(); + bool disableKeyboard = false; + bool disableMouse = false; + foreach (IInputListener inputListener in InputListeners) + { + if (!disableKeyboard && keyboardState != updatedKeyboardState) + { + disableKeyboard = inputListener.KeyboardStateChanged(updatedKeyboardState); + } + if (!disableMouse && mouseState != updatedMouseState) + { + disableMouse = inputListener.MouseStateChanged(updatedMouseState); + } + if (disableKeyboard && disableMouse) + { + break; + } + } + + UpdateStates(); + } + + public static bool MouseClicked() + { + if (mouseState.LeftButton == ButtonState.Pressed) + { + return Mouse.GetState().LeftButton != ButtonState.Pressed; + } + return false; + } + + private static void UpdateStates() + { + keyboardState = Keyboard.GetState(); + mouseState = Mouse.GetState(); + } + + public static bool MouseWithinBoundries(Rectangle bounds) + { + MouseState mouseState = Mouse.GetState(); + if (mouseState.X >= bounds.X && mouseState.X <= (bounds.X + bounds.Width) && mouseState.Y >= bounds.Y && mouseState.Y <= (bounds.Y + bounds.Height)) + { + return true; + } + return false; + } + } +} diff --git a/Recrowned-Athenaeum/Class1.cs b/Recrowned-Athenaeum/ParticleSystem/Particle.cs similarity index 66% rename from Recrowned-Athenaeum/Class1.cs rename to Recrowned-Athenaeum/ParticleSystem/Particle.cs index 6cd5f68..e61266a 100644 --- a/Recrowned-Athenaeum/Class1.cs +++ b/Recrowned-Athenaeum/ParticleSystem/Particle.cs @@ -4,9 +4,9 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Recrowned_Athenaeum +namespace RhythmBullet.Utilities.ParticleSystem { - public class Class1 + class Particle { } } diff --git a/Recrowned-Athenaeum/Persistence/Preferences.cs b/Recrowned-Athenaeum/Persistence/Preferences.cs new file mode 100644 index 0000000..85dbe2c --- /dev/null +++ b/Recrowned-Athenaeum/Persistence/Preferences.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; + +namespace RhythmBullet.Utilities.Persistence +{ + public class Preferences + { + } +} diff --git a/Recrowned-Athenaeum/Persistence/PreferencesManager.cs b/Recrowned-Athenaeum/Persistence/PreferencesManager.cs new file mode 100644 index 0000000..397afc1 --- /dev/null +++ b/Recrowned-Athenaeum/Persistence/PreferencesManager.cs @@ -0,0 +1,79 @@ +using RhythmBullet.Preferences; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Xml.Serialization; + +namespace RhythmBullet.Utilities.Persistence +{ + public class PreferencesManager + { + private readonly Dictionary preferenceList; + string savePath; + XmlSerializer xmlSerializer; + + public PreferencesManager(string savePath, params Preferences[] preferences) + { + this.savePath = savePath; + Directory.CreateDirectory(savePath); + preferenceList = new Dictionary(); + Debug.WriteLine("Preference manager setting up..."); + Type[] preferenceTypes = new Type[preferences.Length]; + for (int prefID = 0; prefID < preferences.Length; prefID++) + { + preferenceList.Add(preferences[prefID].GetType(), preferences[prefID]); + preferenceTypes[prefID] = preferences[prefID].GetType(); + Debug.WriteLine(preferences[prefID] + " added to list of preferences"); + } + + xmlSerializer = new XmlSerializer(typeof(Preferences), preferenceTypes); + } + + public T GetPreferences() where T : Preferences + { + return (T)preferenceList[typeof(T)]; + } + + public void Load() + { + List keys = new List(preferenceList.Keys); + + foreach (Type key in keys) + { + if (!LoadSpecific(key)) + { + SaveSpecific(key); + } + } + } + + public void Save() + { + foreach (KeyValuePair prefs in preferenceList) + { + SaveSpecific(prefs.Key); + } + } + + private bool LoadSpecific(Type preference) + { + string path = savePath + "/" + preference.Name + ".xml"; + if (File.Exists(path)) + { + Stream stream = new FileStream(path, FileMode.Open); + preferenceList[preference] = (Preferences)xmlSerializer.Deserialize(stream); + stream.Close(); + return true; + } + return false; + } + + private void SaveSpecific(Type preference) + { + Stream stream = new FileStream(savePath + "/" + preference.Name + ".xml", FileMode.Create); + xmlSerializer.Serialize(stream, preferenceList[preference]); + stream.Close(); + } + } +} diff --git a/Recrowned-Athenaeum/Recrowned-Athenaeum.csproj b/Recrowned-Athenaeum/Recrowned-Athenaeum.csproj index e02ce05..a33ad7c 100644 --- a/Recrowned-Athenaeum/Recrowned-Athenaeum.csproj +++ b/Recrowned-Athenaeum/Recrowned-Athenaeum.csproj @@ -31,6 +31,7 @@ 4 + @@ -41,8 +42,46 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Recrowned-Athenaeum/ScreenSystem/ITransition.cs b/Recrowned-Athenaeum/ScreenSystem/ITransition.cs new file mode 100644 index 0000000..a2a58fb --- /dev/null +++ b/Recrowned-Athenaeum/ScreenSystem/ITransition.cs @@ -0,0 +1,48 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.Utilities.ScreenSystem +{ + public interface ITransition + { + /// + /// Called once when the transition is needed. + /// The dimensions of the screen. + /// + void InitiateTransition(Rectangle dimensions); + + /// + /// Called every frame if the state of the screen this transition is placed upon is in the enter transition phase. + /// + /// The time passed in seconds since the last frame. + /// Whether or not if all assets have been loaded by the . + /// The frame being rendered by the screen as it exits. This can be null. + /// If this returns true, then it is considered that this transition is complete. + bool EnteringTransition(double delta, bool assetsLoaded); + + /// + /// Called every frame if the state of the screen this transition is placed upon is in the exit phase. + /// + /// The time passed in seconds since the last frame. + /// Whether or not if all assets have been loaded by the . + /// If this returns true, then it is considered that this transition is complete. + bool ExitingTransition(double delta, bool assetsLoaded); + + /// + /// Called once every frame while transition is active. Meant to draw transition. + /// + /// + void DrawTransition(SpriteBatch spriteBatch); + + /// + /// Updates if the previous screen uses a render target for exit transition. + /// + /// The frame of the previous screen. + void UpdatePreviousScreenFrame(RenderTarget2D previousScreenFrame); + } +} diff --git a/Recrowned-Athenaeum/ScreenSystem/LoadingScreen.cs b/Recrowned-Athenaeum/ScreenSystem/LoadingScreen.cs new file mode 100644 index 0000000..3d79dae --- /dev/null +++ b/Recrowned-Athenaeum/ScreenSystem/LoadingScreen.cs @@ -0,0 +1,136 @@ + +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Diagnostics; + +namespace RhythmBullet.Utilities.ScreenSystem +{ + public class LoadingScreen : Screen, ITransition + { + private const float ENTER_TIME = 2f; + private const float EXIT_TIME = 1f; + Game game; + readonly Texture2D texture; + Color 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; + + public LoadingScreen(Game game, Texture2D screenImage, float proportion, bool rotate = false) : base(true) + { + this.game = game; + this.texture = screenImage; + this.proportion = proportion; + this.rotate = rotate; + Transitions.Add(this); + } + + public override void Show() + { + game.IsMouseVisible = false; + base.Show(); + } + + public override void Hide() + { + game.IsMouseVisible = true; + base.Hide(); + } + + public void InitiateTransition(Rectangle dimensions) + { + color = Color.White; + textureBounds.Width = (int)(ScreenSize.Height * proportion); + textureBounds.Height = (int)(ScreenSize.Height * proportion); + textureBounds.X = (ScreenSize.Width) / 2; + textureBounds.Y = (ScreenSize.Height) / 2; + origin.X = texture.Width / 2; + origin.Y = texture.Height / 2; + } + + void DoRotate(float deltaf) + { + rotation += (2f / 3f) * (float)Math.PI * deltaf; + if (rotation >= 2 * Math.PI) + { + rotation = 0; + } + } + + public void DrawTransition(SpriteBatch spriteBatch) + { + spriteBatch.Draw(texture, textureBounds, null, color, rotation, origin, SpriteEffects.None, 0f); + } + + + public bool EnteringTransition(double delta, bool assetsLoaded) + { + float deltaf = (float)delta; + if (rotate) + { + DoRotate(deltaf); + } + if (assetsLoaded) + { + if (progR < 254 || progG < 254 || progB < 254) + { + if (!recorded) + { + rR = (Color.White.R - BackgroundColor.R) / ENTER_TIME; + rG = (Color.White.G - BackgroundColor.G) / ENTER_TIME; + rB = (Color.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; + } + + public bool ExitingTransition(double delta, bool assetsLoaded) + { + float deltaf = (float)delta; + if (rotate) + { + DoRotate(deltaf); + } + if (progC > 0) + { + float rate = deltaf * 255 / EXIT_TIME; + progC -= rate; + progC = Math.Max(progC, 0); + color.R = (byte)progC; + color.G = (byte)progC; + color.B = (byte)progC; + color.A = (byte)progC; + } + else + { + return true; + } + return false; + } + + } +} diff --git a/Recrowned-Athenaeum/ScreenSystem/Screen.cs b/Recrowned-Athenaeum/ScreenSystem/Screen.cs new file mode 100644 index 0000000..4c0ae13 --- /dev/null +++ b/Recrowned-Athenaeum/ScreenSystem/Screen.cs @@ -0,0 +1,146 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using RhythmBullet.Utilities.Camera; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.Utilities.ScreenSystem +{ + public enum ScreenState { EnterTransition, ExitTransition, Normal } + + public class Screen + { + public List Transitions; + public bool UseRenderTargetForExitTransition; + public Color BackgroundColor; + public GraphicsDevice GraphicsDevice { get; private set; } + public Screen NextScreen { get; set; } + public Rectangle ScreenSize { get; private set; } + public bool Initiated { get; private set; } + public Camera2D Camera { get; private set; } + public Screen(bool useEnterTransition = false) + { + State = useEnterTransition ? ScreenState.EnterTransition : ScreenState.Normal; + Transitions = new List(); + } + + /// + /// Called only once after initialization to give required parameters. + /// + /// + /// + /// + public virtual void Initiate(GraphicsDevice graphicsDevice, Rectangle screenSize, Camera2D camera) + { + this.Camera = camera; + this.ScreenSize = screenSize; + GraphicsDevice = graphicsDevice; + Initiated = true; + } + + public virtual void Update(GameTime gameTime) + { + + } + + public virtual void Draw(SpriteBatch spriteBatch) + { + foreach (ITransition transition in Transitions) + { + transition.DrawTransition(spriteBatch); + } + } + + /// + /// Updates the transition. + /// + /// Time passed since last frame in seconds. + /// If waiting on assets. + /// The previous screen's frame. May be null. + /// Only returns true if exit transition is complete. Returns false otherwise. + public bool UpdateTransition(double delta, bool assetsLoaded) + { + switch (State) + { + case ScreenState.EnterTransition: + EnteringTransition(delta, assetsLoaded); + break; + case ScreenState.ExitTransition: + return ExitingTransition(delta, assetsLoaded); + } + + return false; + } + + private void EnteringTransition(double delta, bool assetsLoaded) + { + bool complete = true; + for (int transitionID = 0; transitionID < Transitions.Count; transitionID++) + { + ITransition transition = Transitions[transitionID]; + if (!transition.EnteringTransition(delta, assetsLoaded)) + { + complete = false; + } + } + if (complete && State != ScreenState.ExitTransition) + { + State = ScreenState.Normal; + } + } + + private bool ExitingTransition(double delta, bool assetsLoaded) + { + bool complete = true; + foreach (ITransition transition in Transitions) + { + if (!transition.ExitingTransition(delta, assetsLoaded)) + { + complete = false; + } + + } + return complete; + } + + /// + /// Called when the screen is shown. + /// + public virtual void Show() + { + foreach (ITransition transition in Transitions) + { + transition.InitiateTransition(ScreenSize); + } + } + + public virtual void Hide() + { + + } + + public virtual void AssetLoadStateChange(bool state) + { + + } + + public ScreenState State { get; private set; } + + public void StartExitTransition(bool UseRenderTargetForExitTransition = false) + { + this.UseRenderTargetForExitTransition = UseRenderTargetForExitTransition; + State = ScreenState.ExitTransition; + } + + public virtual void UpdatePreviousScreenFrame(RenderTarget2D previousScreenFrame) + { + foreach (ITransition transition in Transitions) + { + transition.UpdatePreviousScreenFrame(previousScreenFrame); + } + } + } +} diff --git a/Recrowned-Athenaeum/ScreenSystem/ScreenManager.cs b/Recrowned-Athenaeum/ScreenSystem/ScreenManager.cs new file mode 100644 index 0000000..18db349 --- /dev/null +++ b/Recrowned-Athenaeum/ScreenSystem/ScreenManager.cs @@ -0,0 +1,137 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using RhythmBullet.Utilities.Camera; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.Utilities.ScreenSystem +{ + public delegate void FirstScreenChange(Screen screen); + + public class ScreenManager : IDisposable + { + public event FirstScreenChange RequireNextScreenEvent; + private GraphicsDeviceManager graphics; + private Screen previousScreen; + private RenderTarget2D previousScreenRenderTarget; + private Screen currentScreen; + private Camera2D camera; + private bool firstScreenChangeComplete; + public Screen Screen + { + get + { + return currentScreen; + } + set + { + previousScreen = currentScreen; + currentScreen = value; + if (!Screen.Initiated) + { + Screen.Initiate(graphics.GraphicsDevice, new Rectangle(0, 0, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight), camera); + } + if (previousScreen != null && previousScreenRenderTarget == null && previousScreen.UseRenderTargetForExitTransition) + { + previousScreenRenderTarget = new RenderTarget2D(graphics.GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); + } + graphics.GraphicsDevice.SetRenderTarget(previousScreenRenderTarget); + graphics.GraphicsDevice.Clear(Color.Black); + + Debug.WriteLine("Showing " + value.GetType().Name); + Screen.Show(); + previousScreen?.Hide(); + } + } + + public ScreenManager(GraphicsDeviceManager graphicsDeviceManager, Camera2D camera) + { + this.graphics = graphicsDeviceManager; + this.camera = camera; + } + + public void UpdateCurrentScreen(GameTime gameTime, bool assetsDone) + { + switch (Screen.State) + { + case ScreenState.EnterTransition: + if (previousScreen != null && previousScreen.UseRenderTargetForExitTransition) + { + previousScreen.UpdateTransition(gameTime.ElapsedGameTime.TotalSeconds, assetsDone); + } + Screen.UpdateTransition(gameTime.ElapsedGameTime.TotalSeconds, assetsDone); + break; + case ScreenState.ExitTransition: + if (Screen.UseRenderTargetForExitTransition || Screen.UpdateTransition(gameTime.ElapsedGameTime.TotalSeconds, assetsDone)) + { + if (!firstScreenChangeComplete) + { + firstScreenChangeComplete = true; + OnFirstScreenChange(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); + } + + public void DrawCurrentScreen(SpriteBatch spriteBatch) + { + graphics.GraphicsDevice.Clear(Screen.BackgroundColor); + if (Screen.State == ScreenState.EnterTransition && previousScreen != null && previousScreen.UseRenderTargetForExitTransition) + { + graphics.GraphicsDevice.SetRenderTarget(previousScreenRenderTarget); + graphics.GraphicsDevice.Clear(previousScreen.BackgroundColor); + spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, camera.TransformMatrix); + previousScreen.Draw(spriteBatch); + spriteBatch.End(); + graphics.GraphicsDevice.SetRenderTarget(null); + Screen.UpdatePreviousScreenFrame(previousScreenRenderTarget); + } + spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, camera.TransformMatrix); + Screen.Draw(spriteBatch); + spriteBatch.End(); + } + + public void Resize(LoadingScreen loadingScreen) + { + Screen.AssetLoadStateChange(true); + Screen = loadingScreen; + previousScreenRenderTarget.Dispose(); + previousScreenRenderTarget = null; + } + + public void PostResize() + { + Screen.AssetLoadStateChange(false); + } + + public void OnFirstScreenChange(Screen screen) + { + RequireNextScreenEvent?.Invoke(screen); + } + + public void Dispose() + { + previousScreenRenderTarget?.Dispose(); + } + } +} diff --git a/Recrowned-Athenaeum/UI/Book/Book.cs b/Recrowned-Athenaeum/UI/Book/Book.cs new file mode 100644 index 0000000..34fba0a --- /dev/null +++ b/Recrowned-Athenaeum/UI/Book/Book.cs @@ -0,0 +1,93 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using RhythmBullet.Utilities.Camera; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.UI.Book +{ + class Book + { + Camera2D camera; + Page targetPage; + Rectangle dimensions; + Dictionary pages = new Dictionary(); + + /// + /// Should be called whenever a valid camera and dimensions for the book exist. + /// Initializes book with given parameters. + /// + /// Camera game is currently using. + /// Dimensions of the book and the dimensions the pages will use. + public void Initiate(Camera2D camera, Rectangle dimensions) + { + this.camera = camera; + this.dimensions = dimensions; + } + + public void Draw(SpriteBatch batch) + { + for (int pageIndex = 0; pageIndex < pages.Count; pageIndex++) + { + Page page = pages.ElementAt(pageIndex).Value; + page.Draw(batch); + } + } + + public void Update(GameTime gameTime) + { + if (targetPage != null) + { + Vector2 position; + Rectangle targetBounds = targetPage.bounds; + 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.NeedsSizeUpdate) page.ApplySize(dimensions.Width, dimensions.Height); + page.Update(gameTime); + } + } + + public void AddPages(params Page[] pages) + { + foreach (Page page in pages) + { + this.pages.Add(page.Name, page); + } + } + + public void RemovePage(Page page) + { + RemovePage(page.Name); + } + + public void RemovePage(string name) + { + pages.Remove(name); + } + + public void LerpToPage(Page page) + { + targetPage = page; + } + + public void GoToPage(Page page) + { + Rectangle targetBounds = page.bounds; + camera.Position.X = targetBounds.X + (targetBounds.Width * 0.5f); + camera.Position.Y = targetBounds.Y + (targetBounds.Height * 0.5f); + } + } +} diff --git a/Recrowned-Athenaeum/UI/Book/Page.cs b/Recrowned-Athenaeum/UI/Book/Page.cs new file mode 100644 index 0000000..0fe333c --- /dev/null +++ b/Recrowned-Athenaeum/UI/Book/Page.cs @@ -0,0 +1,35 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using RhythmBullet.UI.Modular; +using RhythmBullet.Utilities.Camera; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.UI.Book +{ + class Page : UIModuleGroup + { + private readonly int pageX, pageY; + public bool NeedsSizeUpdate; + + public Page(int pageX, int pageY) : base(false) + { + this.pageX = pageX; + this.pageY = pageY; + NeedsSizeUpdate = true; + Name = ToString(); + } + + public virtual void ApplySize(int width, int height) + { + bounds.X = pageX * width; + bounds.Y = pageY * height; + bounds.Width = width; + bounds.Height = height; + NeedsSizeUpdate = false; + } + } +} diff --git a/Recrowned-Athenaeum/UI/Modular/Modules/Image.cs b/Recrowned-Athenaeum/UI/Modular/Modules/Image.cs new file mode 100644 index 0000000..c306b23 --- /dev/null +++ b/Recrowned-Athenaeum/UI/Modular/Modules/Image.cs @@ -0,0 +1,63 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using RhythmBullet.UI.Modular; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.Utilities.UI.Modular.Modules +{ + class Image : UIModule + { + public float rotation = 0f; + public Texture2D Texture { get; set; } + public float ScaleX + { + get + { + return (float)bounds.Width / Texture.Width; + } + + set + { + bounds.Width = (int)(Texture.Width * value); + } + } + + public float ScaleY + { + get + { + return (float)bounds.Height / Texture.Height; + } + + set + { + bounds.Height = (int)(Texture.Height * value); + } + } + + public float Scale + { + set + { + bounds.Height = (int)(Texture.Height * value); + bounds.Width = (int)(Texture.Width * value); + } + } + + public Image(Texture2D texture) + { + Texture = texture ?? throw new ArgumentException("Image requires a texture."); + bounds = texture.Bounds; + } + + public override void Draw(SpriteBatch batch) + { + batch.Draw(Texture, bounds, null, color, rotation, origin, SpriteEffects.None, 0f); + base.Draw(batch); + } + } +} diff --git a/Recrowned-Athenaeum/UI/Modular/Modules/Interactive/Button.cs b/Recrowned-Athenaeum/UI/Modular/Modules/Interactive/Button.cs new file mode 100644 index 0000000..2acafbe --- /dev/null +++ b/Recrowned-Athenaeum/UI/Modular/Modules/Interactive/Button.cs @@ -0,0 +1,62 @@ +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using RhythmBullet.UI.Modular; +using RhythmBullet.Utilities.DataTypes; +using RhythmBullet.Utilities.Input; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.Utilities.UI.Modular.Modules.Interactive +{ + public delegate bool Clicked(); + + public class Button : UIModule + { + private NinePatch background; + public event Clicked Listeners; + + public Button(NinePatch background = null) + { + this.background = background; + } + + public override void Draw(SpriteBatch batch) + { + background?.Draw(batch, bounds); + base.Draw(batch); + } + + public sealed override bool MouseStateChanged(MouseState state) + { + if (InputUtilities.MouseWithinBoundries(bounds)) + { + if (InputUtilities.MouseClicked()) + { + OnClick(); + } + Highlighted = true; + } + else + { + Highlighted = false; + } + + return base.MouseStateChanged(state); + } + + public sealed override bool KeyboardStateChanged(KeyboardState state) + { + return base.KeyboardStateChanged(state); + } + + protected void OnClick() + { + Listeners?.Invoke(); + } + + public bool Highlighted { get; private set; } + } +} diff --git a/Recrowned-Athenaeum/UI/Modular/Modules/Interactive/TextButton.cs b/Recrowned-Athenaeum/UI/Modular/Modules/Interactive/TextButton.cs new file mode 100644 index 0000000..e33bbf9 --- /dev/null +++ b/Recrowned-Athenaeum/UI/Modular/Modules/Interactive/TextButton.cs @@ -0,0 +1,37 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using RhythmBullet.UI.Modular.Modules; +using RhythmBullet.Utilities.DataTypes; +using RhythmBullet.Utilities.UI.Modular.Modules.Interactive; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.Utilities.UI.Modular.Modules.Interactive +{ + internal class TextButton : Button + { + private TextLabel label; + + internal TextButton(string text, SpriteFont font, NinePatch background) : base(background) + { + label = new TextLabel(font, text); + label.autoScale = true; + } + + public override void Update(GameTime gameTime) + { + label.bounds = bounds; + label.Update(gameTime); + base.Update(gameTime); + } + + public override void Draw(SpriteBatch batch) + { + label.Draw(batch); + base.Draw(batch); + } + } +} diff --git a/Recrowned-Athenaeum/UI/Modular/Modules/TextLabel.cs b/Recrowned-Athenaeum/UI/Modular/Modules/TextLabel.cs new file mode 100644 index 0000000..4fdf286 --- /dev/null +++ b/Recrowned-Athenaeum/UI/Modular/Modules/TextLabel.cs @@ -0,0 +1,146 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using RhythmBullet.Utilities.Camera; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.UI.Modular.Modules +{ + public class TextLabel : UIModule + { + private SpriteFont font; + private float scale; + private Vector2 position; + private string originalText; + private string displayedText; + private Vector2 modifiedTextSize; + public bool autoWrap; + public bool autoScale; + public bool useEllipses; + public string ellipsis = "..."; + private string ModifiedText + { + get + { + return displayedText; + } + + set + { + displayedText = value; + modifiedTextSize = font.MeasureString(value); + } + } + public string Text + { + get + { + return originalText; + } + set + { + if (value == null) value = "..."; + modifiedTextSize = font.MeasureString(value); + originalText = value; + displayedText = value; + } + } + + public TextLabel(SpriteFont font, string text = null) + { + Text = text; + this.font = font; + } + + public override void Update(GameTime gameTime) + { + position.X = bounds.X; + position.Y = bounds.Y; + + if (useEllipses) AttemptToApplyEllipsis(); + if (autoWrap) AttemptToWrapText(); + if (autoScale) AttemptToScaleFont(); + + base.Update(gameTime); + } + + public override void Draw(SpriteBatch batch) + { + batch.DrawString(font, Text, position, color, 0f, origin, scale, SpriteEffects.None, 0f); + base.Draw(batch); + } + + public void AttemptToApplyEllipsis() + { + if (modifiedTextSize.X * scale > bounds.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 > bounds.Width); + + ModifiedText = stringBuilder.ToString(); + } + } + + public void AttemptToScaleFont() + { + if (modifiedTextSize.X * scale > bounds.Width || modifiedTextSize.X * scale < bounds.Width - 5) + { + scale = bounds.Width / modifiedTextSize.X; + } + + if (modifiedTextSize.Y * scale > bounds.Height || modifiedTextSize.Y * scale < bounds.Height - 5) + { + scale = bounds.Height / modifiedTextSize.Y; + } + } + + public void RemoveLineBreaks() + { + ModifiedText = ModifiedText.Replace("\n", " "); + } + + public void ResetToOriginalText() + { + ModifiedText = originalText; + } + + public void AttemptToWrapText(bool unwrap = false) + { + if (unwrap) RemoveLineBreaks(); + if (modifiedTextSize.X * scale > bounds.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 <= bounds.Width) + { + stringBuilder.Append(word); + currentScaledLineWidth += scaledWidth; + } + else + { + stringBuilder.AppendLine(); + currentScaledLineWidth = 0; + } + } + ModifiedText = stringBuilder.ToString(); + } + } + } +} diff --git a/Recrowned-Athenaeum/UI/Modular/UIModule.cs b/Recrowned-Athenaeum/UI/Modular/UIModule.cs new file mode 100644 index 0000000..c7db325 --- /dev/null +++ b/Recrowned-Athenaeum/UI/Modular/UIModule.cs @@ -0,0 +1,74 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using RhythmBullet.Utilities.Camera; +using RhythmBullet.Utilities.Input; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.UI.Modular +{ + public class UIModule : IInputListener + { + public Rectangle bounds; + public Vector2 origin; + public UIModuleGroup Parent; + public string Name; + public Color color = Color.White; + + public virtual void Update(GameTime gameTime) + { + } + + public virtual void Draw(SpriteBatch batch) + { + + } + + public Rectangle ConvertToParentCoordinates(Rectangle bounds) + { + if (Parent != null) + { + Rectangle parentHitbox = Parent.ConvertToParentCoordinates(bounds); + int tX = bounds.X + parentHitbox.X; + int tY = bounds.Y + parentHitbox.Y; + return new Rectangle(tX, tY, bounds.Width, bounds.Height); + } else + { + return bounds; + } + } + + public void RemoveFromParent() + { + if (Parent == null) + { + throw new InvalidOperationException("Parent is null."); + } + Parent.RemoveModule(this); + } + + /// + /// Called whenever the keyboard state is changed. + /// + /// The current keyboard state. + /// Returning whether or not to continue to call the next listener. + public virtual bool KeyboardStateChanged(KeyboardState state) + { + return true; + } + + /// + /// Called whenever the state of the mouse changes. This includes movement. + /// + /// The current state of the mouse. + /// Returning whether or not to continue to call the next listener. + public virtual bool MouseStateChanged(MouseState state) + { + return true; + } + } +} diff --git a/Recrowned-Athenaeum/UI/Modular/UIModuleGroup.cs b/Recrowned-Athenaeum/UI/Modular/UIModuleGroup.cs new file mode 100644 index 0000000..b561f49 --- /dev/null +++ b/Recrowned-Athenaeum/UI/Modular/UIModuleGroup.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using RhythmBullet.Utilities.Camera; +using RhythmBullet.Utilities.Input; + +namespace RhythmBullet.UI.Modular +{ + public class UIModuleGroup : UIModule + { + List modules = new List(); + Rectangle scissorBounds; + RasterizerState scissorRasterizer = new RasterizerState(); + public Camera2D Camera { get; set; } + + public UIModuleGroup(bool crop = false, Camera2D camera = null) + { + Camera = camera; + if (crop) + { + scissorRasterizer.ScissorTestEnable = true; + scissorBounds = new Rectangle(); + } + } + + public override void Draw(SpriteBatch batch) + { + if (scissorBounds != null) + { + batch.End(); + batch.Begin(SpriteSortMode.Deferred, null, null, null, scissorRasterizer, null, Camera?.TransformMatrix); + scissorBounds.Width = bounds.Width; + scissorBounds.Height = bounds.Height; + scissorBounds.X = bounds.X; + scissorBounds.Y = bounds.Y; + Rectangle scissor = scissorBounds; + scissorBounds = batch.GraphicsDevice.ScissorRectangle; + batch.GraphicsDevice.ScissorRectangle = scissor; + } + foreach (UIModule module in modules) + { + int offsetX = module.bounds.X; + int offsetY = module.bounds.Y; + module.bounds.X = bounds.X + offsetX - (int)module.origin.X; + module.bounds.Y = bounds.Y + offsetY - (int)module.origin.Y; + module.Draw(batch); + module.bounds.X = offsetX; + module.bounds.Y = offsetY; + } + + if (scissorBounds != null) + { + batch.GraphicsDevice.ScissorRectangle = scissorBounds; + batch.End(); + batch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, Camera?.TransformMatrix); + } + } + + public override void Update(GameTime gameTime) + { + foreach (UIModule module in modules) + { + module.Update(gameTime); + } + } + + public void AddModule(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); + } + } + + public void RemoveModule(UIModule module) + { + module.Parent = null; + modules.Remove(module); + } + + public override bool KeyboardStateChanged(KeyboardState state) + { + foreach (UIModule module in modules) + { + module.KeyboardStateChanged(state); + } + return base.KeyboardStateChanged(state); + } + + public override bool MouseStateChanged(MouseState state) + { + foreach (UIModule module in modules) + { + module.MouseStateChanged(state); + } + return base.MouseStateChanged(state); + } + } +}