Removed legacy UI modules, screen and skin system.

This commit is contained in:
Harrison Deng 2020-02-20 16:03:18 -05:00
parent b9105e7179
commit 46e072b9f4
21 changed files with 0 additions and 2881 deletions

View File

@ -1,211 +0,0 @@
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

@ -1,57 +0,0 @@
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

@ -1,85 +0,0 @@
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

@ -1,155 +0,0 @@
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

@ -1,92 +0,0 @@
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

@ -1,206 +0,0 @@
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

@ -1,451 +0,0 @@
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

@ -1,190 +0,0 @@
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

@ -1,136 +0,0 @@
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

@ -1,46 +0,0 @@
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

@ -1,166 +0,0 @@
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

@ -1,204 +0,0 @@
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

@ -1,213 +0,0 @@
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

@ -1,28 +0,0 @@
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

@ -1,20 +0,0 @@
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

@ -1,25 +0,0 @@
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

@ -1,32 +0,0 @@
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

@ -1,54 +0,0 @@
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

@ -1,88 +0,0 @@
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

@ -1,183 +0,0 @@
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

@ -1,239 +0,0 @@
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);
}
}
}