diff options
Diffstat (limited to 'source/ui')
27 files changed, 2356 insertions, 0 deletions
diff --git a/source/ui/Rect.cs b/source/ui/Rect.cs new file mode 100644 index 0000000..c26ba8b --- /dev/null +++ b/source/ui/Rect.cs @@ -0,0 +1,188 @@ +using System; +using Microsoft.Xna.Framework; + +namespace Celesteia.UI { + public struct Rect { + public static Rect AbsoluteZero = new Rect(AbsoluteUnit.WithValue(0f)); + public static Rect AbsoluteOne = new Rect(AbsoluteUnit.WithValue(1f)); + public static Rect ScreenFull = new Rect( + new ScreenSpaceUnit(0f, ScreenSpaceUnit.ScreenSpaceOrientation.Horizontal), + new ScreenSpaceUnit(0f, ScreenSpaceUnit.ScreenSpaceOrientation.Vertical), + new ScreenSpaceUnit(1f, ScreenSpaceUnit.ScreenSpaceOrientation.Horizontal), + new ScreenSpaceUnit(1f, ScreenSpaceUnit.ScreenSpaceOrientation.Vertical) + ); + public static Rect RelativeFull(Rect parent) => new Rect( + new RelativeUnit(0f, parent, RelativeUnit.Orientation.Horizontal), + new RelativeUnit(0f, parent, RelativeUnit.Orientation.Vertical), + new RelativeUnit(1f, parent, RelativeUnit.Orientation.Horizontal), + new RelativeUnit(1f, parent, RelativeUnit.Orientation.Vertical) + ); + + public IInterfaceUnit X; + public IInterfaceUnit Y; + public IInterfaceUnit Width; + public IInterfaceUnit Height; + + public Rect(IInterfaceUnit uniform) : this (uniform, uniform, uniform, uniform) { } + + public Rect(IInterfaceUnit x, IInterfaceUnit y, IInterfaceUnit width, IInterfaceUnit height) { + this.X = x; + this.Y = y; + this.Width = width; + this.Height = height; + } + + public Rect SetX(IInterfaceUnit x) { + X = x; + return this; + } + + public Rect SetY(IInterfaceUnit y) { + Y = y; + return this; + } + + public Rect SetWidth(IInterfaceUnit w) { + Width = w; + return this; + } + + public Rect SetHeight(IInterfaceUnit h) { + Height = h; + return this; + } + + public float[] Resolve() { + return new float[] { X.Resolve(), Y.Resolve(), Width.Resolve(), Height.Resolve() }; + } + + public bool Contains(Point point) { + return ( + point.X >= this.X.Resolve() && + point.Y >= this.Y.Resolve() && + point.X <= this.X.Resolve() + this.Width.Resolve() && + point.Y <= this.Y.Resolve() + this.Height.Resolve() + ); + } + + public bool Contains(int x, int y) { + return Contains(new Point(x, y)); + } + + public override string ToString() { + return $"{X.Resolve().ToString("0.00")} {Y.Resolve().ToString("0.00")} {Width.Resolve().ToString("0.00")} {Height.Resolve().ToString("0.00")}"; + } + + public Rectangle ResolveRectangle() + { + float[] resolved = this.Resolve(); + return new Rectangle( + (int) MathF.Floor(resolved[0]), + (int) MathF.Floor(resolved[1]), + (int) MathF.Floor(resolved[2]), + (int) MathF.Floor(resolved[3]) + ); + } + } + + public interface IInterfaceUnit { + public float Resolve(); + + public void SetValue(float value); + } + + public struct AbsoluteUnit : IInterfaceUnit + { + public float value { get; private set; } + + public AbsoluteUnit(float value) { + this.value = value; + } + + public static AbsoluteUnit WithValue(float value) { + return new AbsoluteUnit(value); + } + + public void SetValue(float value) { + this.value = value; + } + + public float Resolve() + { + return value; + } + } + + public struct ScreenSpaceUnit : IInterfaceUnit + { + public float value { get; private set; } + public ScreenSpaceOrientation orientation { get; private set; } + + public ScreenSpaceUnit(float value, ScreenSpaceOrientation orientation) { + this.value = value; + this.orientation = orientation; + } + + public void SetValue(float value) { + this.value = value; + } + + public void SetOrientation(ScreenSpaceOrientation orientation) { + this.orientation = orientation; + } + + public float Resolve() + { + if (UIReferences.gameWindow != null) { + switch (orientation) { + case ScreenSpaceOrientation.Horizontal: + return value * UIReferences.gameWindow.ClientBounds.Width; + case ScreenSpaceOrientation.Vertical: + return value * UIReferences.gameWindow.ClientBounds.Height; + } + } + return 0f; + } + + public enum ScreenSpaceOrientation { + Horizontal, Vertical + } + } + + public struct RelativeUnit : IInterfaceUnit + { + public float value { get; private set; } + public Rect parent { get; private set; } + public Orientation orientation { get; private set; } + + public RelativeUnit(float value, Rect parent, Orientation orientation) { + this.value = value; + this.parent = parent; + this.orientation = orientation; + } + + public void SetValue(float value) { + this.value = value; + } + + public void SetOrientation(Orientation orientation) { + this.orientation = orientation; + } + + public float Resolve() + { + switch (orientation) { + case Orientation.Horizontal: + return value * parent.Resolve()[2]; + case Orientation.Vertical: + return value * parent.Resolve()[3]; + default: + return 0f; + } + } + + public enum Orientation { + Horizontal, Vertical + } + } +}
\ No newline at end of file diff --git a/source/ui/TextAlignment.cs b/source/ui/TextAlignment.cs new file mode 100644 index 0000000..1f92828 --- /dev/null +++ b/source/ui/TextAlignment.cs @@ -0,0 +1,10 @@ +using System; + +[Flags] +public enum TextAlignment { + Center = 0, + Left = 1, + Right = 2, + Top = 4, + Bottom = 8 +}
\ No newline at end of file diff --git a/source/ui/UI.cs b/source/ui/UI.cs new file mode 100644 index 0000000..39f4136 --- /dev/null +++ b/source/ui/UI.cs @@ -0,0 +1,81 @@ +using Celesteia.Resources.Management; +using Celesteia.UI.Properties; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended.TextureAtlases; + +namespace Celesteia { + public static class UIReferences { + public static GameWindow gameWindow; + public static bool GUIEnabled = true; + public static int Scaling = 3; + } + + public static class TextUtilities { + public static void DrawAlignedText(SpriteBatch spriteBatch, Rectangle rect, FontType font, string text, Color color, TextAlignment textAlignment, float targetSize) { + // Credit for text alignment: https://stackoverflow.com/a/10263903 + + // Measure the text's size from the sprite font. + Vector2 size = font.Font.MeasureString(text); + + // Get the origin point at the center. + Vector2 origin = 0.5f * size; + + if (textAlignment.HasFlag(TextAlignment.Left)) + origin.X += rect.Width / 2f - size.X / 2f; + + if (textAlignment.HasFlag(TextAlignment.Right)) + origin.X -= rect.Width / 2f - size.X / 2f; + + if (textAlignment.HasFlag(TextAlignment.Top)) + origin.Y += rect.Height / 2f - size.Y / 2f; + + if (textAlignment.HasFlag(TextAlignment.Bottom)) + origin.Y -= rect.Height / 2f - size.Y / 2f; + + spriteBatch.DrawString(font.Font, text, new Vector2(rect.Center.X, rect.Center.Y), color, 0f, origin, font.Scale(targetSize), SpriteEffects.None, 0f); + } + + public static void DrawAlignedText(SpriteBatch spriteBatch, Rectangle rect, TextProperties textProperties) { + DrawAlignedText(spriteBatch, rect, textProperties.GetFont(), textProperties.GetText(), textProperties.GetColor(), textProperties.GetAlignment(), textProperties.GetFontSize()); + } + } + + public static class ImageUtilities { + private static int xLeft(Rectangle rectangle, int patchSize) => rectangle.X; + private static int xMiddle(Rectangle rectangle, int patchSize) => rectangle.X + (patchSize * UIReferences.Scaling); + private static int xRight(Rectangle rectangle, int patchSize) => rectangle.X + rectangle.Width - (patchSize * UIReferences.Scaling); + private static int yTop(Rectangle rectangle, int patchSize) => rectangle.Y; + private static int yMiddle(Rectangle rectangle, int patchSize) => rectangle.Y + (patchSize * UIReferences.Scaling); + private static int yBottom(Rectangle rectangle, int patchSize) => rectangle.Y + rectangle.Height - (patchSize * UIReferences.Scaling); + + public static void DrawPatched(SpriteBatch spriteBatch, Rectangle rectangle, TextureAtlas patches, int patchSize, Color color) { + int y; + int scaled = patchSize * UIReferences.Scaling; + + // Top + y = yTop(rectangle, patchSize); + { + spriteBatch.Draw(patches.GetRegion(0), new Rectangle(xLeft(rectangle, patchSize), y, scaled, scaled), color); // Top left + spriteBatch.Draw(patches.GetRegion(1), new Rectangle(xMiddle(rectangle, patchSize), y, rectangle.Width - (2 * scaled), scaled), color); // Top center + spriteBatch.Draw(patches.GetRegion(2), new Rectangle(xRight(rectangle, patchSize), y, scaled, scaled), color); // Top right + } + + // Center + y = yMiddle(rectangle, patchSize); + { + spriteBatch.Draw(patches.GetRegion(3), new Rectangle(xLeft(rectangle, patchSize), y, scaled, rectangle.Height - (2 * scaled)), color); // Left center + spriteBatch.Draw(patches.GetRegion(4), new Rectangle(xMiddle(rectangle, patchSize), y, rectangle.Width - (2 * scaled), rectangle.Height - (2 * scaled)), color); // Center + spriteBatch.Draw(patches.GetRegion(5), new Rectangle(xRight(rectangle, patchSize), y, scaled, rectangle.Height - (2 * scaled)), color); // Right center + } + + // Bottom + y = yBottom(rectangle, patchSize); + { + spriteBatch.Draw(patches.GetRegion(6), new Rectangle(xLeft(rectangle, patchSize), y, scaled, scaled), color); // Bottom left + spriteBatch.Draw(patches.GetRegion(7), new Rectangle(xMiddle(rectangle, patchSize), y, rectangle.Width - (2 * scaled), scaled), color); // Bottom center + spriteBatch.Draw(patches.GetRegion(8), new Rectangle(xRight(rectangle, patchSize), y, scaled, scaled), color); // Bottom right + } + } + } +}
\ No newline at end of file diff --git a/source/ui/elements/Button.cs b/source/ui/elements/Button.cs new file mode 100644 index 0000000..81a6aed --- /dev/null +++ b/source/ui/elements/Button.cs @@ -0,0 +1,173 @@ +using Celesteia.UI.Properties; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended.Input; +using MonoGame.Extended.TextureAtlases; + +namespace Celesteia.UI.Elements { + public class Button : Clickable { + public Button(Rect rect) { + SetRect(rect); + } + + public Button SetNewRect(Rect rect) { + SetRect(rect); + return this; + } + + public Button SetPivotPoint(Vector2 pivot) { + SetPivot(pivot); + return this; + } + + // TEXT PROPERTIES + + private TextProperties _text; + + public Button SetTextProperties(TextProperties text) { + _text = text; + return this; + } + + public Button SetText(string text) { + _text.SetText(text); + return this; + } + + // COLOR PROPERTIES + + private ButtonColorGroup _colorGroup = new ButtonColorGroup(Color.White); + private Color ButtonColor = Color.White; + private bool ButtonEnabled = true; + + public Button SetColorGroup(ButtonColorGroup colorGroup) { + _colorGroup = colorGroup; + return this; + } + + public Button SetButtonEnabled(bool enabled) { + ButtonEnabled = enabled; + return this; + } + + // CLICKING PROPERTIES + + private ClickEvent _onMouseDown = null; + private ClickEvent _onMouseUp = null; + + public Button SetOnMouseDown(ClickEvent func) { + _onMouseDown = func; + return this; + } + + public Button SetOnMouseUp(ClickEvent func) { + _onMouseUp = func; + return this; + } + + public override void OnMouseDown(MouseButton button, Point position) { + base.OnMouseDown(button, position); + _onMouseDown?.Invoke(button, position); + } + + public override void OnMouseUp(MouseButton button, Point position) { + base.OnMouseUp(button, position); + _onMouseUp?.Invoke(button, position); + } + + // DRAWING PROPERTIES + + private Texture2D _texture; + private TextureAtlas _patches; + private int _patchSize; + + public Button SetTexture(Texture2D texture) { + _texture = texture; + return this; + } + + public Button MakePatches(int size) { + if (_texture != null) { + _patchSize = size; + _patches = TextureAtlas.Create("buttonPatches", _texture, _patchSize, _patchSize); + } + return this; + } + + // https://gamedev.stackexchange.com/a/118255 + private float _colorAmount = 0.0f; + private bool _prevMouseOver = false; + private bool _prevClicked = false; + public override void Update(GameTime gameTime, out bool clickedAnything) { + clickedAnything = false; + if (ButtonColor == GetTargetColor()) return; + + if (_prevMouseOver != GetMouseOver() || _prevClicked != GetClicked()) _colorAmount = 0.0f; + + _colorAmount += (float)gameTime.ElapsedGameTime.TotalSeconds / 0.5f; + + if (_colorAmount > 1.0f) + _colorAmount = 0.0f; + + ButtonColor = Color.Lerp(ButtonColor, GetTargetColor(), _colorAmount); + + _prevMouseOver = GetMouseOver(); + _prevClicked = GetClicked(); + } + + Rectangle rectangle; + public override void Draw(SpriteBatch spriteBatch) + { + rectangle = GetRectangle(); + + // Draw the button's texture. + if (_patches != null) ImageUtilities.DrawPatched(spriteBatch, rectangle, _patches, _patchSize, ButtonColor); + else spriteBatch.Draw(GetTexture(spriteBatch), rectangle, null, ButtonColor); + + TextUtilities.DrawAlignedText(spriteBatch, rectangle, _text); + } + + public Texture2D GetTexture(SpriteBatch spriteBatch) { + if (_texture == null) { + _texture = new Texture2D(spriteBatch.GraphicsDevice, 1, 1); + _texture.SetData(new[] { Color.Gray }); + } + + return _texture; + } + + private Color GetTargetColor() { + return ButtonEnabled ? (GetMouseOver() ? (GetClicked() ? _colorGroup.Active : _colorGroup.Hover) : _colorGroup.Normal) : _colorGroup.Disabled; + } + + public Button Clone() { + return new Button(GetRect()) + .SetPivotPoint(GetPivot()) + .SetOnMouseDown(_onMouseDown) + .SetOnMouseUp(_onMouseUp) + .SetTexture(_texture) + .MakePatches(_patchSize) + .SetTextProperties(_text) + .SetColorGroup(_colorGroup); + } + } + + public struct ButtonColorGroup { + public Color Normal; + public Color Disabled; + public Color Hover; + public Color Active; + + public ButtonColorGroup(Color normal, Color disabled, Color hover, Color active) { + Normal = normal; + Disabled = disabled; + Hover = hover; + Active = active; + } + + public ButtonColorGroup(Color normal, Color disabled, Color hover) : this (normal, disabled, hover, normal) {} + + public ButtonColorGroup(Color normal, Color disabled) : this (normal, disabled, normal, normal) {} + public ButtonColorGroup(Color normal) : this (normal, normal, normal, normal) {} + } +}
\ No newline at end of file diff --git a/source/ui/elements/Clickable.cs b/source/ui/elements/Clickable.cs new file mode 100644 index 0000000..01436a3 --- /dev/null +++ b/source/ui/elements/Clickable.cs @@ -0,0 +1,21 @@ +using Microsoft.Xna.Framework; +using MonoGame.Extended.Input; + +namespace Celesteia.UI.Elements { + public class Clickable : Element, IClickable + { + private bool _clicked; + + public override void OnMouseOut() { + _clicked = false; + base.OnMouseOut(); + } + public virtual void OnMouseDown(MouseButton button, Point position) => _clicked = true; + public virtual void OnMouseUp(MouseButton button, Point position) => _clicked = false; + + public bool GetClicked() => _clicked; + } + + public delegate void HoverEvent(); + public delegate void ClickEvent(MouseButton button, Point position); +}
\ No newline at end of file diff --git a/source/ui/elements/Container.cs b/source/ui/elements/Container.cs new file mode 100644 index 0000000..9080999 --- /dev/null +++ b/source/ui/elements/Container.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using Celesteia.Game.Input; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended.Input; + +namespace Celesteia.UI.Elements { + public class Container : Element, IContainer + { + private List<IElement> Children; + + public Container(Rect rect) { + SetRect(rect); + Children = new List<IElement>(); + } + + public void AddChild(IElement element) { + Children.Add(element); + element.SetParent(this); + } + + public List<IElement> GetChildren() => Children; + + public override void Draw(SpriteBatch spriteBatch) + { + Children.ForEach(element => { if (element.GetEnabled()) element.Draw(spriteBatch); }); + } + + private Point _mousePosition; + + public override void Update(GameTime gameTime, out bool clickedAnything) + { + clickedAnything = false; + if (!UIReferences.GUIEnabled) return; + + foreach (IElement element in Children) { + element.Update(gameTime, out clickedAnything); + } + + _mousePosition = MouseHelper.Position; + + if (MouseHelper.Pressed(MouseButton.Left)) clickedAnything = ResolveMouseDown(MouseButton.Left); + else if (MouseHelper.Pressed(MouseButton.Right)) clickedAnything = ResolveMouseDown(MouseButton.Right); + + if (MouseHelper.Released(MouseButton.Left)) clickedAnything = ResolveMouseUp(MouseButton.Left); + else if (MouseHelper.Released(MouseButton.Right)) clickedAnything = ResolveMouseUp(MouseButton.Right); + + ResolveMouseOver(); + } + + public bool ResolveMouseDown(MouseButton button) { + bool clicked = false; + Children.FindAll(x => x is Clickable).ForEach(element => { + if (!element.GetEnabled()) return; + Clickable clickable = element as Clickable; + + if (clicked = clickable.GetRectangle().Contains(_mousePosition)) + clickable.OnMouseDown(button, _mousePosition); + }); + return clicked; + } + + public bool ResolveMouseUp(MouseButton button) { + bool clicked = false; + Children.FindAll(x => x is Clickable).ForEach(element => { + if (!element.GetEnabled()) return; + Clickable clickable = element as Clickable; + + if (clickable.GetRectangle().Contains(_mousePosition)) { + clicked = true; + clickable.OnMouseUp(button, _mousePosition); + } + }); + return clicked; + } + + public void ResolveMouseOver() { + Children.ForEach(element => { + if (!element.GetEnabled()) return; + bool over = element.GetRectangle().Contains(_mousePosition); + if (over && !element.GetMouseOver()) { + element.OnMouseIn(); + } else if (!over && element.GetMouseOver()) element.OnMouseOut(); + }); + } + + public void Dispose() { + Children.Clear(); + } + } +}
\ No newline at end of file diff --git a/source/ui/elements/Element.cs b/source/ui/elements/Element.cs new file mode 100644 index 0000000..b95a693 --- /dev/null +++ b/source/ui/elements/Element.cs @@ -0,0 +1,53 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Celesteia.UI.Elements { + public class Element : IElement + { + private bool _enabled = true; + private Rect _rect; + private bool _isMouseOver; + private IContainer _parent; + private Vector2 _pivot; + + public virtual bool GetEnabled() => _enabled && (_parent == null || _parent.GetEnabled()); + public virtual IContainer GetParent() => _parent; + public virtual Vector2 GetPivot() => _pivot; + public virtual Rect GetRect() => _rect; + public virtual void MoveTo(Point point) { + _rect.SetX(AbsoluteUnit.WithValue(point.X)); + _rect.SetY(AbsoluteUnit.WithValue(point.Y)); + } + + // Get element rectangle with pivot. + public virtual Rectangle GetRectangle() { + Rectangle r = GetRect().ResolveRectangle(); + + if (GetParent() != null) { + r.X += GetParent().GetRectangle().X; + r.Y += GetParent().GetRectangle().Y; + } + + r.X -= (int)MathF.Round(_pivot.X * r.Width); + r.Y -= (int)MathF.Round(_pivot.Y * r.Height); + + return r; + } + + // Interface setters. + public virtual bool SetEnabled(bool value) => _enabled = value; + public virtual void SetParent(IContainer container) => _parent = container; + public virtual void SetPivot(Vector2 pivot) => _pivot = pivot; + public virtual void SetRect(Rect rect) => _rect = rect; + + // Mouse functions + public virtual void OnMouseIn() => _isMouseOver = true; + public virtual void OnMouseOut() => _isMouseOver = false; + public virtual bool GetMouseOver() => _isMouseOver; + + // Engine functions. + public virtual void Update(GameTime gameTime, out bool clickedAnything) { clickedAnything = false; } + public virtual void Draw(SpriteBatch spriteBatch) { } + } +}
\ No newline at end of file diff --git a/source/ui/elements/IClickable.cs b/source/ui/elements/IClickable.cs new file mode 100644 index 0000000..19c2848 --- /dev/null +++ b/source/ui/elements/IClickable.cs @@ -0,0 +1,9 @@ +using Microsoft.Xna.Framework; +using MonoGame.Extended.Input; + +namespace Celesteia.UI.Elements { + public interface IClickable : IElement { + void OnMouseDown(MouseButton button, Point position); + void OnMouseUp(MouseButton button, Point position); + } +}
\ No newline at end of file diff --git a/source/ui/elements/IContainer.cs b/source/ui/elements/IContainer.cs new file mode 100644 index 0000000..0d3550c --- /dev/null +++ b/source/ui/elements/IContainer.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace Celesteia.UI.Elements { + public interface IContainer : IElement, IDisposable { + // Get the element's children. + List<IElement> GetChildren(); + + // Add to the element's children. + void AddChild(IElement element); + } +}
\ No newline at end of file diff --git a/source/ui/elements/IElement.cs b/source/ui/elements/IElement.cs new file mode 100644 index 0000000..094d7b7 --- /dev/null +++ b/source/ui/elements/IElement.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Celesteia.UI.Elements { + public interface IElement { + // Is the element enabled? + bool GetEnabled(); + + // Set whether the element is enabled. + bool SetEnabled(bool value); + + // Get the containing rect of the element. + Rect GetRect(); + + // Set the containing rect of the element. + void SetRect(Rect rect); + + // Move to a point. + void MoveTo(Point point); + + // Get the rectangle with a pivot point. + Rectangle GetRectangle(); + + // Gets the pivot point of the element; + Vector2 GetPivot(); + + // Sets the pivot point of the element; + void SetPivot(Vector2 pivot); + + // Called when the mouse position is within the element's containing rect. + void OnMouseIn(); + + // Called when the mouse position is within the element's containing rect. + void OnMouseOut(); + + // Get if the element has the mouse over it. + bool GetMouseOver(); + + // Update the element. + void Update(GameTime gameTime, out bool clickedAnything); + + // Draw the element. + void Draw(SpriteBatch spriteBatch); + + // Get the element's parent. + IContainer GetParent(); + + // Set the element's parent. + void SetParent(IContainer container); + } +}
\ No newline at end of file diff --git a/source/ui/elements/Image.cs b/source/ui/elements/Image.cs new file mode 100644 index 0000000..de661e8 --- /dev/null +++ b/source/ui/elements/Image.cs @@ -0,0 +1,59 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended.TextureAtlases; + +namespace Celesteia.UI.Elements { + public class Image : Element { + private Texture2D _texture; + public Color _color; + + public Image(Rect rect) { + SetRect(rect); + } + + public Image SetTexture(Texture2D texture) { + _texture = texture; + return this; + } + + public Image SetColor(Color color) { + _color = color; + return this; + } + + public Image SetPivotPoint(Vector2 pivot) { + SetPivot(pivot); + return this; + } + + private TextureAtlas _patches; + private int _patchSize; + public Image MakePatches(int size) { + if (_texture != null) { + _patchSize = size; + _patches = TextureAtlas.Create("patches", _texture, _patchSize, _patchSize); + } + return this; + } + + public override void Draw(SpriteBatch spriteBatch) + { + if (_patches != null) ImageUtilities.DrawPatched(spriteBatch, GetRectangle(), _patches, _patchSize, _color); + else spriteBatch.Draw(GetTexture(spriteBatch), GetRectangle(), null, _color); + } + + public Texture2D GetTexture(SpriteBatch spriteBatch) + { + if (_texture == null) { + // Make a new texture. + _texture = new Texture2D(spriteBatch.GraphicsDevice, 1, 1); + + // Set the default texture to one gray pixel. + _texture.SetData(new[] { Color.Gray }); + } + + return _texture; + } + } +}
\ No newline at end of file diff --git a/source/ui/elements/Label.cs b/source/ui/elements/Label.cs new file mode 100644 index 0000000..c875791 --- /dev/null +++ b/source/ui/elements/Label.cs @@ -0,0 +1,62 @@ +using Celesteia.UI.Properties; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Celesteia.UI.Elements { + public class Label : Element { + private Texture2D _background; + private TextProperties _text; + + public Label(Rect rect) { + SetRect(rect); + } + + public Label SetNewRect(Rect rect) { + SetRect(rect); + return this; + } + + public Label SetPivotPoint(Vector2 pivot) { + SetPivot(pivot); + return this; + } + + public Label SetBackground(Texture2D background) { + SetTexture(background); + return this; + } + + public Label SetText(string text) { + _text.SetText(text); + return this; + } + + public Label SetColor(Color color) { + _text.SetColor(color); + return this; + } + + public Label SetTextProperties(TextProperties text) { + _text = text; + return this; + } + + public override void Draw(SpriteBatch spriteBatch) + { + // Draw the label's background, if present. + if (_background != null) spriteBatch.Draw(GetTexture(), GetRectangle(), null, Color.White); + + TextUtilities.DrawAlignedText(spriteBatch, GetRectangle(), _text); + } + + public Texture2D GetTexture() => _background; + public void SetTexture(Texture2D background) => _background = background; + + public Label Clone() { + return new Label(GetRect()) + .SetPivotPoint(GetPivot()) + .SetBackground(GetTexture()) + .SetTextProperties(_text); + } + } +}
\ No newline at end of file diff --git a/source/ui/elements/game/CraftingRecipeSlot.cs b/source/ui/elements/game/CraftingRecipeSlot.cs new file mode 100644 index 0000000..72101d8 --- /dev/null +++ b/source/ui/elements/game/CraftingRecipeSlot.cs @@ -0,0 +1,173 @@ +using System; +using Celesteia.Game.Components.Items; +using Celesteia.Resources.Types; +using Celesteia.UI.Properties; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended.Input; +using MonoGame.Extended.TextureAtlases; + +namespace Celesteia.UI.Elements.Game { + public class CraftingRecipeSlot : Clickable { + public const float SLOT_SIZE = 64f; + public const float SLOT_SPACING = 16f; + + public Inventory referenceInv; + + public CraftingRecipeSlot(Rect rect) { + SetRect(rect); + } + + public CraftingRecipeSlot SetNewRect(Rect rect) { + SetRect(rect); + return this; + } + + // RECIPE REFERENCE PROPERTIES + private Recipe _recipe; + public CraftingRecipeSlot SetRecipe(Recipe recipe) { + _recipe = recipe; + return this; + } + + // DRAWING PROPERTIES + + private Texture2D _texture; + private TextureAtlas _patches; + private int _patchSize; + + public CraftingRecipeSlot SetTexture(Texture2D texture) { + _texture = texture; + return this; + } + + public CraftingRecipeSlot SetPatches(TextureAtlas patches, int size) { + if (_texture != null) { + _patchSize = size; + _patches = patches; + } + return this; + } + + // TEXT PROPERTIES + + private TextProperties _text; + + public CraftingRecipeSlot SetTextProperties(TextProperties text) { + _text = text; + return this; + } + + public CraftingRecipeSlot SetText(string text) { + _text.SetText(text); + return this; + } + + // CLICKING PROPERTIES + + private ClickEvent _onMouseDown = null; + private ClickEvent _onMouseUp = null; + public delegate void CraftHoverEvent(Recipe recipe); + private CraftHoverEvent _onMouseIn = null; + private HoverEvent _onMouseOut = null; + + public CraftingRecipeSlot SetOnMouseDown(ClickEvent func) { + _onMouseDown = func; + return this; + } + + public CraftingRecipeSlot SetOnMouseUp(ClickEvent func) { + _onMouseUp = func; + return this; + } + + public CraftingRecipeSlot SetOnMouseIn(CraftHoverEvent func) { + _onMouseIn = func; + return this; + } + + public CraftingRecipeSlot SetOnMouseOut(HoverEvent func) { + _onMouseOut = func; + return this; + } + + public override void OnMouseDown(MouseButton button, Point position) { + base.OnMouseDown(button, position); + _onMouseDown?.Invoke(button, position); + } + + public override void OnMouseUp(MouseButton button, Point position) { + base.OnMouseUp(button, position); + _onMouseUp?.Invoke(button, position); + } + + public override void OnMouseIn() { + base.OnMouseIn(); + if (_recipe != null) _onMouseIn?.Invoke(_recipe); + } + + public override void OnMouseOut() { + base.OnMouseOut(); + _onMouseOut?.Invoke(); + } + + private Rectangle GetScaledTriangle(Rectangle r, float scale) { + int newWidth = (int)Math.Round(r.Width * scale); + int newHeight = (int)Math.Round(r.Height * scale); + return new Rectangle( + (int)Math.Round(r.X + ((r.Width - newWidth) / 2f)), + (int)Math.Round(r.Y + ((r.Height - newHeight) / 2f)), + newWidth, + newHeight + ); + } + + Color color; + public override void Update(GameTime gameTime, out bool clickedAnything) { + base.Update(gameTime, out clickedAnything); + + if (!this.GetEnabled()) return; + color = _recipe.Craftable(referenceInv) ? Color.White : Color.Gray; + } + + Rectangle rectangle; + Rectangle itemRectangle; + Rectangle textRectangle; + public override void Draw(SpriteBatch spriteBatch) + { + if (_recipe == null) return; + + rectangle = GetRectangle(); + itemRectangle = GetScaledTriangle(rectangle, .6f); + textRectangle = GetScaledTriangle(rectangle, .4f); + + // Draw the slot's texture. + if (_patches != null) ImageUtilities.DrawPatched(spriteBatch, rectangle, _patches, _patchSize, color); + else spriteBatch.Draw(GetTexture(spriteBatch), rectangle, null, color); + + spriteBatch.Draw(_recipe.Result.GetItemType().Sprite, itemRectangle, color); + TextUtilities.DrawAlignedText(spriteBatch, textRectangle, _text.GetFont(), _recipe.Result.Amount + "", _text.GetColor(), _text.GetAlignment(), _text.GetFontSize()); + } + + public Texture2D GetTexture(SpriteBatch spriteBatch) { + if (_texture == null) { + _texture = new Texture2D(spriteBatch.GraphicsDevice, 1, 1); + _texture.SetData(new[] { Color.Gray }); + } + + return _texture; + } + + public CraftingRecipeSlot Clone() { + return new CraftingRecipeSlot(GetRect()) + .SetRecipe(_recipe) + .SetOnMouseDown(_onMouseDown) + .SetOnMouseUp(_onMouseUp) + .SetOnMouseIn(_onMouseIn) + .SetOnMouseOut(_onMouseOut) + .SetTextProperties(_text) + .SetTexture(_texture) + .SetPatches(_patches, _patchSize); + } + } +}
\ No newline at end of file diff --git a/source/ui/elements/game/CraftingWindow.cs b/source/ui/elements/game/CraftingWindow.cs new file mode 100644 index 0000000..e5bcc71 --- /dev/null +++ b/source/ui/elements/game/CraftingWindow.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using Celesteia.Game.Components.Items; +using Celesteia.GUIs.Game; +using Celesteia.Resources; +using Celesteia.Resources.Management; +using Celesteia.Resources.Types; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Celesteia.UI.Elements.Game { + public class CraftingWindow : Container { + private Inventory _referenceInventory; + private Image background; + private GameGUI _gameGui; + + public CraftingWindow(GameGUI gameGui, Rect rect, Texture2D backgroundImage, Inventory inventory, CraftingRecipeSlot template) : base(rect) { + _gameGui = gameGui; + + _referenceInventory = inventory; + + background = new Image(Rect.RelativeFull(rect)).SetTexture(backgroundImage).MakePatches(4).SetColor(Color.White); + AddChild(background); + + AddRecipes(27, template); + } + + int columns = 9; + private void AddRecipes(int amountPerPage, CraftingRecipeSlot template) { + int rows = (int)Math.Ceiling(amountPerPage / (double)columns); + + float o = CraftingRecipeSlot.SLOT_SPACING; + int index = 0; + int i = 0; + for (int row = 0; row < rows; row++) + for (int column = 0; column < columns; column++) { + if (i >= ResourceManager.Recipes.Recipes.Count) break; + + int slotNumber = i; + Recipe recipe = ResourceManager.Recipes.Recipes[index]; + CraftingRecipeSlot slot = template.Clone() + .SetNewRect(template.GetRect() + .SetX(AbsoluteUnit.WithValue(column * CraftingRecipeSlot.SLOT_SIZE + (column * CraftingRecipeSlot.SLOT_SPACING) + o)) + .SetY(AbsoluteUnit.WithValue(row * CraftingRecipeSlot.SLOT_SIZE + (row * CraftingRecipeSlot.SLOT_SPACING) + o)) + ) + .SetRecipe(recipe) + .SetOnMouseUp((button, point) => { + if (button == MonoGame.Extended.Input.MouseButton.Left) { + recipe.Craft(_referenceInventory); + } + }); + slot.referenceInv = _referenceInventory; + slot.SetPivot(new Vector2(0f, 0f)); + + index++; + i++; + + AddChild(slot); + } + } + } +}
\ No newline at end of file diff --git a/source/ui/elements/game/InventorySlot.cs b/source/ui/elements/game/InventorySlot.cs new file mode 100644 index 0000000..01663b4 --- /dev/null +++ b/source/ui/elements/game/InventorySlot.cs @@ -0,0 +1,191 @@ +using System; +using Celesteia.Game.Components.Items; +using Celesteia.Resources.Types; +using Celesteia.UI.Properties; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended.Input; +using MonoGame.Extended.TextureAtlases; + +namespace Celesteia.UI.Elements.Game { + public class InventorySlot : Clickable { + public const float SLOT_SIZE = 64f; + public const float SLOT_SPACING = 16f; + + public InventorySlot(Rect rect) { + SetRect(rect); + } + + public InventorySlot SetNewRect(Rect rect) { + SetRect(rect); + return this; + } + + // SELECTION + + private bool _selected = false; + public InventorySlot SetSelected(bool selected) { + _selected = selected; + return this; + } + + public bool GetSelected() { + return _selected; + } + + + // INVENTORY REFERENCE PROPERTIES + + private Inventory _inventory; + private int _slot; + + public InventorySlot SetReferenceInventory(Inventory inventory) { + _inventory = inventory; + return this; + } + + public InventorySlot SetSlot(int slot) { + _slot = slot; + return this; + } + + // DRAWING PROPERTIES + + private Texture2D _texture; + private TextureAtlas _patches; + private int _patchSize; + + public InventorySlot SetTexture(Texture2D texture) { + _texture = texture; + return this; + } + + public InventorySlot SetPatches(TextureAtlas patches, int size) { + if (_texture != null) { + _patchSize = size; + _patches = patches; + } + return this; + } + + // TEXT PROPERTIES + + private TextProperties _text; + + public InventorySlot SetTextProperties(TextProperties text) { + _text = text; + return this; + } + + public InventorySlot SetText(string text) { + _text.SetText(text); + return this; + } + + // CLICKING PROPERTIES + + private ClickEvent _onMouseDown = null; + private ClickEvent _onMouseUp = null; + public delegate void ItemHoverEvent(ItemType type); + private ItemHoverEvent _onMouseIn = null; + private HoverEvent _onMouseOut = null; + + public InventorySlot SetOnMouseDown(ClickEvent func) { + _onMouseDown = func; + return this; + } + + public InventorySlot SetOnMouseUp(ClickEvent func) { + _onMouseUp = func; + return this; + } + + public InventorySlot SetOnMouseIn(ItemHoverEvent func) { + _onMouseIn = func; + return this; + } + + public InventorySlot SetOnMouseOut(HoverEvent func) { + _onMouseOut = func; + return this; + } + + public override void OnMouseDown(MouseButton button, Point position) { + base.OnMouseDown(button, position); + _onMouseDown?.Invoke(button, position); + } + + public override void OnMouseUp(MouseButton button, Point position) { + base.OnMouseUp(button, position); + _onMouseUp?.Invoke(button, position); + } + + public override void OnMouseIn() { + base.OnMouseIn(); + if (_inventory.GetSlot(_slot) != null) _onMouseIn?.Invoke(_inventory.GetSlot(_slot).Type); + } + + public override void OnMouseOut() { + base.OnMouseOut(); + _onMouseOut?.Invoke(); + } + + private Rectangle GetScaledTriangle(Rectangle r, float scale) { + int newWidth = (int)Math.Round(r.Width * scale); + int newHeight = (int)Math.Round(r.Height * scale); + return new Rectangle( + (int)Math.Round(r.X + ((r.Width - newWidth) / 2f)), + (int)Math.Round(r.Y + ((r.Height - newHeight) / 2f)), + newWidth, + newHeight + ); + } + + Rectangle rectangle; + Rectangle itemRectangle; + Rectangle textRectangle; + ItemStack inSlot; + Color slightlyTransparent = new Color(255, 255, 255, 100); + public override void Draw(SpriteBatch spriteBatch) + { + if (_inventory == null) return; + + rectangle = GetRectangle(); + itemRectangle = GetScaledTriangle(rectangle, .6f); + textRectangle = GetScaledTriangle(rectangle, .4f); + + // Draw the slot's texture. + if (_patches != null) ImageUtilities.DrawPatched(spriteBatch, rectangle, _patches, _patchSize, _selected ? Color.DarkViolet : Color.White); + else spriteBatch.Draw(GetTexture(spriteBatch), rectangle, null, Color.White); + + // Draw item if present. + inSlot = _inventory.GetSlot(_slot); + if (inSlot != null) { + spriteBatch.Draw(inSlot.Type.Sprite, itemRectangle, Color.White); + if (inSlot.Amount > 1) TextUtilities.DrawAlignedText(spriteBatch, textRectangle, _text.GetFont(), $"{inSlot.Amount}", _text.GetColor(), _text.GetAlignment(), _text.GetFontSize()); + } else TextUtilities.DrawAlignedText(spriteBatch, rectangle, _text.GetFont(), $"{_slot + 1}", slightlyTransparent, TextAlignment.Center, 24f); + } + + public Texture2D GetTexture(SpriteBatch spriteBatch) { + if (_texture == null) { + _texture = new Texture2D(spriteBatch.GraphicsDevice, 1, 1); + _texture.SetData(new[] { Color.Gray }); + } + + return _texture; + } + + public InventorySlot Clone() { + return new InventorySlot(GetRect()) + .SetReferenceInventory(_inventory) + .SetOnMouseDown(_onMouseDown) + .SetOnMouseUp(_onMouseUp) + .SetOnMouseIn(_onMouseIn) + .SetOnMouseOut(_onMouseOut) + .SetSlot(_slot) + .SetTextProperties(_text) + .SetTexture(_texture) + .SetPatches(_patches, _patchSize); + } + } +}
\ No newline at end of file diff --git a/source/ui/elements/game/InventoryWindow.cs b/source/ui/elements/game/InventoryWindow.cs new file mode 100644 index 0000000..c8dba41 --- /dev/null +++ b/source/ui/elements/game/InventoryWindow.cs @@ -0,0 +1,56 @@ +using System; +using Celesteia.Game.Components.Items; +using Celesteia.GUIs.Game; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Celesteia.UI.Elements.Game { + public class InventoryWindow : Container { + private Inventory _referenceInventory; + private Image background; + private GameGUI _gameGui; + + public InventoryWindow(GameGUI gameGui, Rect rect, Texture2D backgroundImage, Inventory inventory, int slots, int offset, InventorySlot template) : base(rect) { + _gameGui = gameGui; + + background = new Image(Rect.RelativeFull(rect)).SetTexture(backgroundImage).MakePatches(4).SetColor(Color.White); + AddChild(background); + + _referenceInventory = inventory; + + AddSlots(slots, offset, template); + } + + int columns = 9; + private void AddSlots(int amount, int offset, InventorySlot template) { + int rows = (int)Math.Ceiling(amount / (double)columns); + + float o = InventorySlot.SLOT_SPACING; + int i = 0; + for (int row = 0; row < rows; row++) + for (int column = 0; column < 9; column++) { + if (i > amount) break; + + int slotNumber = i + offset; + InventorySlot slot = template.Clone() + .SetNewRect(template.GetRect() + .SetX(AbsoluteUnit.WithValue(column * InventorySlot.SLOT_SIZE + (column * InventorySlot.SLOT_SPACING) + o)) + .SetY(AbsoluteUnit.WithValue(row * InventorySlot.SLOT_SIZE + (row * InventorySlot.SLOT_SPACING) + o)) + ) + .SetSlot(slotNumber) + .SetOnMouseUp((button, point) => { + ItemStack itemInSlot = _referenceInventory.GetSlot(slotNumber); + if ((int)_gameGui.State > 0) { + _referenceInventory.SetSlot(slotNumber, _gameGui.CursorItem); + _gameGui.CursorItem = itemInSlot; + } + }); + slot.SetPivot(new Vector2(0f, 0f)); + + i++; + + AddChild(slot); + } + } + } +}
\ No newline at end of file diff --git a/source/ui/elements/game/PauseMenu.cs b/source/ui/elements/game/PauseMenu.cs new file mode 100644 index 0000000..39281ee --- /dev/null +++ b/source/ui/elements/game/PauseMenu.cs @@ -0,0 +1,74 @@ +using Celesteia.Game.Components.Items; +using Celesteia.GUIs.Game; +using Celesteia.Resources; +using Celesteia.Screens; +using Celesteia.UI.Properties; +using Microsoft.Xna.Framework; + +namespace Celesteia.UI.Elements.Game { + public class PauseMenu : Container { + private Image background; + private IContainer centerMenu; + private GameGUI _gameGui; + + private float buttonRow(int number) => number * (buttonHeight + buttonSpacing); + private float buttonHeight = 56f; + private float buttonSpacing = 10f; + + public PauseMenu(GameGUI gameGui, Rect rect, Button buttonTemplate) : base(rect) { + _gameGui = gameGui; + + background = new Image(Rect.RelativeFull(rect)).SetColor(new Color(0, 0, 0, 100)); + AddChild(background); + + centerMenu = new Container(new Rect( + new RelativeUnit(0.5f, GetRect(), RelativeUnit.Orientation.Horizontal), + new RelativeUnit(0.5f, GetRect(), RelativeUnit.Orientation.Vertical), + AbsoluteUnit.WithValue(350f), + AbsoluteUnit.WithValue(2 * (buttonHeight + buttonSpacing) - buttonSpacing) + )); + AddChild(centerMenu); + + AddButtons(buttonTemplate); + } + + private void AddButtons(Button template) { + centerMenu.AddChild(new Label(new Rect( + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(buttonRow(-1)), + new RelativeUnit(1f, centerMenu.GetRect(), RelativeUnit.Orientation.Horizontal), + AbsoluteUnit.WithValue(buttonHeight) + )) + .SetPivotPoint(new Vector2(0.5f, 0.5f)) + .SetTextProperties(new TextProperties().SetColor(Color.White).SetFont(ResourceManager.Fonts.GetFontType("Hobo")).SetFontSize(24f).SetTextAlignment(TextAlignment.Center)) + .SetText("Paused") + ); + + centerMenu.AddChild(template.Clone() + .SetNewRect(new Rect( + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(buttonRow(0)), + new RelativeUnit(1f, centerMenu.GetRect(), RelativeUnit.Orientation.Horizontal), + AbsoluteUnit.WithValue(buttonHeight) + )) + .SetText("Back to Game") + .SetOnMouseUp((button, point) => { + _gameGui.TogglePause(); + }) + ); + + centerMenu.AddChild(template.Clone() + .SetNewRect(new Rect( + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(buttonRow(1)), + new RelativeUnit(1f, centerMenu.GetRect(), RelativeUnit.Orientation.Horizontal), + AbsoluteUnit.WithValue(buttonHeight) + )) + .SetText("Return to Title") + .SetOnMouseUp((button, point) => { + _gameGui.Game.LoadScreen(new MainMenuScreen(_gameGui.Game),new MonoGame.Extended.Screens.Transitions.FadeTransition(_gameGui.Game.GraphicsDevice, Color.Black)); + }) + ); + } + } +}
\ No newline at end of file diff --git a/source/ui/elements/game/controls/ControlTips.cs b/source/ui/elements/game/controls/ControlTips.cs new file mode 100644 index 0000000..904b3cc --- /dev/null +++ b/source/ui/elements/game/controls/ControlTips.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using Celesteia.Game.Input; +using Celesteia.Resources; +using Celesteia.UI.Properties; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; + +namespace Celesteia.UI.Elements.Game.Controls { + public class ControlTips : Container { + private TextProperties _properties; + private Dictionary<Keys, string> _keyboardControls = new Dictionary<Keys, string>(); + private List<string> _lines = new List<string>(); + private List<Label> _labels = new List<Label>(); + + public ControlTips(Rect rect) : base(rect) { + _properties = new TextProperties() + .SetColor(Color.White) + .SetFont(ResourceManager.Fonts.DEFAULT) + .SetFontSize(12f) + .SetTextAlignment(TextAlignment.Left); + } + + private int lineHeight = 16; + private Rect LineRect(int line) => new Rect( + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(line * lineHeight), + new RelativeUnit(1f, GetRect(), RelativeUnit.Orientation.Horizontal), + AbsoluteUnit.WithValue(lineHeight) + ); + + private void UpdateLines() { + _labels.Clear(); + + foreach (Keys keys in _keyboardControls.Keys) _lines.Add($"[{keys}] {_keyboardControls[keys]}"); + + for (int i = 0; i < _lines.Count; i++) { + Label label = new Label(LineRect(i - (_lines.Count / 2))) + .SetTextProperties(_properties) + .SetText(_lines[i]); + label.SetParent(this); + _labels.Add(label); + } + } + + public override void Draw(SpriteBatch spriteBatch) + { + foreach (Label l in _labels) l.Draw(spriteBatch); + } + } +}
\ No newline at end of file diff --git a/source/ui/elements/game/tooltips/CraftingTooltipDisplay.cs b/source/ui/elements/game/tooltips/CraftingTooltipDisplay.cs new file mode 100644 index 0000000..a7c6e2f --- /dev/null +++ b/source/ui/elements/game/tooltips/CraftingTooltipDisplay.cs @@ -0,0 +1,97 @@ +using Celesteia.Resources.Types; +using Celesteia.UI.Properties; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Celesteia.UI.Elements.Game.Tooltips { + public class CraftingTooltipDisplay : TooltipDisplay + { + private const float OUTER_SPACING = 16f; + private const float INNER_SPACING = 8f; + public readonly Container Content; + public readonly Label Title; + public readonly ItemDisplay Item; + public Container Recipe; + + public CraftingTooltipDisplay(Rect rect, Texture2D background) : base(rect) { + AddChild(new Image(Rect.RelativeFull(new Rect( + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(256f + (2 * OUTER_SPACING)), + AbsoluteUnit.WithValue(64f + (1 * INNER_SPACING) + (2 * OUTER_SPACING)) + ))).SetTexture(background).MakePatches(4).SetColor(Color.White)); + + Content = new Container(new Rect( + AbsoluteUnit.WithValue(OUTER_SPACING), + AbsoluteUnit.WithValue(OUTER_SPACING), + AbsoluteUnit.WithValue(256f), + AbsoluteUnit.WithValue(64f + (1 * INNER_SPACING)) + )); + + Container titleCard = new Container(new Rect( + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(0f), + new RelativeUnit(1f, Content.GetRect(), RelativeUnit.Orientation.Horizontal), + AbsoluteUnit.WithValue(32f) + )); + titleCard.AddChild(Item = new ItemDisplay(new Rect( + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(32f), + AbsoluteUnit.WithValue(32f) + )) { + Text = new TextProperties().Standard().SetTextAlignment(TextAlignment.Bottom | TextAlignment.Right) + }); + titleCard.AddChild(Title = new Label(new Rect( + AbsoluteUnit.WithValue(72f), + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(150f), + AbsoluteUnit.WithValue(32f) + )).SetTextProperties(new Properties.TextProperties().Standard().SetTextAlignment(TextAlignment.Left)).SetPivotPoint(new Vector2(0f, 0f))); + Content.AddChild(titleCard); + + Recipe = new Container(new Rect( + new RelativeUnit(.5f, Content.GetRect(), RelativeUnit.Orientation.Horizontal), + AbsoluteUnit.WithValue(32f + INNER_SPACING), + new RelativeUnit(1f, Content.GetRect(), RelativeUnit.Orientation.Horizontal), + AbsoluteUnit.WithValue(32f) + )); + Content.AddChild(Recipe); + + AddChild(Content); + + SetEnabled(false); + } + + public void SetRecipe(Recipe recipe) { + Item.Item = recipe.Result.GetItemType(); + Title.SetText(recipe.Result.GetItemType().Name); + + if (Recipe != null) Recipe.Dispose(); + Recipe = new Container(new Rect( + new RelativeUnit(0f, Content.GetRect(), RelativeUnit.Orientation.Horizontal), + AbsoluteUnit.WithValue(32f + INNER_SPACING), + new RelativeUnit(1f, Content.GetRect(), RelativeUnit.Orientation.Horizontal), + AbsoluteUnit.WithValue(32f) + )); + Recipe.SetPivot(new Vector2(0f, 0f)); + + for (int i = 0; i < recipe.Ingredients.Count; i++) + Recipe.AddChild(new ItemDisplay(new Rect( + AbsoluteUnit.WithValue((i * INNER_SPACING) + (i * 32)), + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(32f), + AbsoluteUnit.WithValue(32f) + )) { + Item = recipe.Ingredients[i].GetItemType(), + Amount = recipe.Ingredients[i].Amount, + Text = new TextProperties().Standard() + .SetTextAlignment(TextAlignment.Bottom | TextAlignment.Right) + .SetFontSize(12f) + .SetText(recipe.Ingredients[i].Amount.ToString()) + }); + + Content.AddChild(Recipe); + } + } +}
\ No newline at end of file diff --git a/source/ui/elements/game/tooltips/ItemDisplay.cs b/source/ui/elements/game/tooltips/ItemDisplay.cs new file mode 100644 index 0000000..d3b1524 --- /dev/null +++ b/source/ui/elements/game/tooltips/ItemDisplay.cs @@ -0,0 +1,21 @@ +using Celesteia.Resources.Types; +using Celesteia.UI.Properties; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended.TextureAtlases; + +namespace Celesteia.UI.Elements.Game.Tooltips { + public class ItemDisplay : Element { + public ItemType Item; + public int Amount; + public TextProperties Text; + + public ItemDisplay(Rect rect) => SetRect(rect); + + public override void Draw(SpriteBatch spriteBatch) + { + spriteBatch.Draw(Item.Sprite, GetRectangle(), Color.White, null); + if (Amount > 1) TextUtilities.DrawAlignedText(spriteBatch, GetRectangle(), Text); + } + } +}
\ No newline at end of file diff --git a/source/ui/elements/game/tooltips/ItemTooltipDisplay.cs b/source/ui/elements/game/tooltips/ItemTooltipDisplay.cs new file mode 100644 index 0000000..9b62af4 --- /dev/null +++ b/source/ui/elements/game/tooltips/ItemTooltipDisplay.cs @@ -0,0 +1,62 @@ +using Celesteia.Resources.Types; +using Celesteia.UI.Properties; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Celesteia.UI.Elements.Game.Tooltips { + public class ItemTooltipDisplay : TooltipDisplay + { + private const float OUTER_SPACING = 16f; + private const float INNER_SPACING = 8f; + public readonly Container Content; + public readonly Label Title; + public readonly ItemDisplay Item; + + public ItemTooltipDisplay(Rect rect, Texture2D background) : base(rect) { + AddChild(new Image(Rect.RelativeFull(new Rect( + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(256f + (2 * OUTER_SPACING)), + AbsoluteUnit.WithValue(32f + (2 * OUTER_SPACING)) + ))).SetTexture(background).MakePatches(4).SetColor(Color.White)); + + Content = new Container(new Rect( + AbsoluteUnit.WithValue(OUTER_SPACING), + AbsoluteUnit.WithValue(OUTER_SPACING), + AbsoluteUnit.WithValue(256f), + AbsoluteUnit.WithValue(32f) + )); + + Container titleCard = new Container(new Rect( + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(0f), + new RelativeUnit(1f, Content.GetRect(), RelativeUnit.Orientation.Horizontal), + AbsoluteUnit.WithValue(32f) + )); + titleCard.AddChild(Item = new ItemDisplay(new Rect( + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(32f), + AbsoluteUnit.WithValue(32f) + )) { + Text = new TextProperties().Standard().SetTextAlignment(TextAlignment.Bottom | TextAlignment.Right) + }); + titleCard.AddChild(Title = new Label(new Rect( + AbsoluteUnit.WithValue(72f), + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(150f), + AbsoluteUnit.WithValue(32f) + )).SetTextProperties(new Properties.TextProperties().Standard().SetTextAlignment(TextAlignment.Left)).SetPivotPoint(new Vector2(0f, 0f))); + Content.AddChild(titleCard); + + AddChild(Content); + + SetEnabled(false); + } + + public void SetItem(ItemType type) { + Item.Item = type; + Title.SetText(type.Name); + } + } +}
\ No newline at end of file diff --git a/source/ui/elements/game/tooltips/TooltipDisplay.cs b/source/ui/elements/game/tooltips/TooltipDisplay.cs new file mode 100644 index 0000000..2ff051d --- /dev/null +++ b/source/ui/elements/game/tooltips/TooltipDisplay.cs @@ -0,0 +1,8 @@ +using Microsoft.Xna.Framework; + +namespace Celesteia.UI.Elements.Game.Tooltips { + public class TooltipDisplay : Container + { + public TooltipDisplay(Rect rect) : base(rect) {} + } +}
\ No newline at end of file diff --git a/source/ui/guis/DebugGUI.cs b/source/ui/guis/DebugGUI.cs new file mode 100644 index 0000000..7ee44ae --- /dev/null +++ b/source/ui/guis/DebugGUI.cs @@ -0,0 +1,66 @@ +using System.Diagnostics; +using Celesteia.Resources; +using Celesteia.UI; +using Celesteia.UI.Elements; +using Celesteia.UI.Properties; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using MonoGame.Extended; + +namespace Celesteia.GUIs { + public class DebugGUI : GUI { + private new GameInstance Game => (GameInstance) base.Game; + public DebugGUI(GameInstance game) : base(game, Rect.ScreenFull) {} + + private Label updateLabel; + private Label drawLabel; + + public override void LoadContent(ContentManager Content) { + float fontSize = 12f; + + Label template = new Label(new Rect( + AbsoluteUnit.WithValue(10), + AbsoluteUnit.WithValue(10), + AbsoluteUnit.WithValue(200), + AbsoluteUnit.WithValue(50) + )) + .SetTextProperties(new TextProperties() + .SetColor(Color.White) + .SetFont(ResourceManager.Fonts.GetFontType("Hobo")) + .SetFontSize(fontSize) + .SetTextAlignment(TextAlignment.Top | TextAlignment.Left) + ); + float textSpacing = 4f; + float textRow(int number) => 10f + number * (fontSize + textSpacing); + + Root.AddChild(template.Clone().SetNewRect(template.GetRect().SetY(AbsoluteUnit.WithValue(textRow(0)))).SetText($"Celesteia {GameInstance.Version}")); + Root.AddChild(updateLabel = template.Clone().SetNewRect(template.GetRect().SetY(AbsoluteUnit.WithValue(textRow(1)))).SetText("")); + Root.AddChild(drawLabel = template.Clone().SetNewRect(template.GetRect().SetY(AbsoluteUnit.WithValue(textRow(2)))).SetText("")); + + Debug.WriteLine("Loaded Debug GUI."); + } + + private double updateTime; + private double lastUpdate; + public override void Update(GameTime gameTime, out bool clickedAnything) { + clickedAnything = false; + + updateTime = gameTime.TotalGameTime.TotalMilliseconds - lastUpdate; + lastUpdate = gameTime.TotalGameTime.TotalMilliseconds; + + updateLabel.SetText($"Update: {updateTime.ToString("0.00")}ms"); + } + + private double drawTime; + private double lastDraw; + public override void Draw(GameTime gameTime) + { + drawTime = gameTime.TotalGameTime.TotalMilliseconds - lastDraw; + lastDraw = gameTime.TotalGameTime.TotalMilliseconds; + + drawLabel.SetText($"Draw: {drawTime.ToString("0.00")}ms"); + + if (GameInstance.DebugMode) base.Draw(gameTime); + } + } +}
\ No newline at end of file diff --git a/source/ui/guis/GUI.cs b/source/ui/guis/GUI.cs new file mode 100644 index 0000000..6d00070 --- /dev/null +++ b/source/ui/guis/GUI.cs @@ -0,0 +1,48 @@ +using System.Diagnostics; +using Celesteia.UI; +using Celesteia.UI.Elements; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; + +namespace Celesteia.GUIs { + public class GUI { + public GameInstance Game; + + public IContainer Root; + + public GUI(GameInstance Game, Rect rect) { + this.Game = Game; + this.Root = new Container(rect); + } + + public virtual void LoadContent(ContentManager Content) { + Debug.WriteLine("Loaded GUI."); + } + + public virtual void Update(GameTime gameTime, out bool clickedAnything) { + if (!Game.IsActive) { + clickedAnything = false; + return; + } + + Root.Update(gameTime, out clickedAnything); + } + + // Draw all elements. + public virtual void Draw(GameTime gameTime) { + + Game.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointWrap, null, null, null); + + if (UIReferences.GUIEnabled) Root.Draw(Game.SpriteBatch); + + Game.SpriteBatch.End(); + } + + // If the menu is referred to as a boolean, return whether it is non-null (true) or null (false). + public static implicit operator bool(GUI gui) + { + return !object.ReferenceEquals(gui, null); + } + } +}
\ No newline at end of file diff --git a/source/ui/guis/MainMenu.cs b/source/ui/guis/MainMenu.cs new file mode 100644 index 0000000..dd03d5a --- /dev/null +++ b/source/ui/guis/MainMenu.cs @@ -0,0 +1,225 @@ +using System.Diagnostics; +using Celesteia.Game.ECS; +using Celesteia.Resources; +using Celesteia.Screens; +using Celesteia.UI; +using Celesteia.UI.Elements; +using Celesteia.UI.Properties; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; + +namespace Celesteia.GUIs { + public class MainMenu : GUI { + public MainMenu(GameInstance game) : base(game, Rect.ScreenFull) {} + + private Texture2D logo; + private Texture2D buttonTexture; + + private IContainer MainScreen; + private IContainer OptionsScreen; + private IContainer NewWorldScreen; + + private IContainer LogoPivot; + private Label Progress; + + private Button buttonTemplate; + private float buttonRow(int number) => number * (buttonHeight + buttonSpacing); + private float buttonHeight = 56f; + private float buttonSpacing = 10f; + + public override void LoadContent(ContentManager Content) { + logo = Game.Content.Load<Texture2D>("celesteia/logo"); + buttonTexture = Game.Content.Load<Texture2D>("sprites/ui/button"); + + buttonTemplate = new Button(new Rect( + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(buttonRow(0)), + AbsoluteUnit.WithValue(250f), + AbsoluteUnit.WithValue(56f) + )) + .SetPivotPoint(new Vector2(.5f)) + .SetTexture(buttonTexture) + .MakePatches(4) + .SetTextProperties(new TextProperties() + .SetColor(Color.White) + .SetFont(ResourceManager.Fonts.GetFontType("Hobo")) + .SetFontSize(24f) + .SetTextAlignment(TextAlignment.Center)) + .SetColorGroup(new ButtonColorGroup(Color.White, Color.Black, Color.Violet, Color.DarkViolet)); + + // Load all the screens. + LoadMainScreen(); + LoadOptionsScreen(); + LoadNewWorldScreen(); + LoadGlobals(); + + base.LoadContent(Content); + } + + private void LoadMainScreen() { + Root.AddChild(MainScreen = new Container(Rect.ScreenFull)); + MainScreen.SetEnabled(true); + + IContainer menu = new Container(new Rect( + new ScreenSpaceUnit(.5f, ScreenSpaceUnit.ScreenSpaceOrientation.Horizontal), + new ScreenSpaceUnit(.5f, ScreenSpaceUnit.ScreenSpaceOrientation.Vertical), + AbsoluteUnit.WithValue(250f), + AbsoluteUnit.WithValue(0f) + )); + + menu.AddChild(LogoPivot = new Container(new Rect( + AbsoluteUnit.WithValue(0f), + new ScreenSpaceUnit(-.25f, ScreenSpaceUnit.ScreenSpaceOrientation.Vertical), + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(0f) + ))); + + float logoRatio = logo.Height / (float) logo.Width; + LogoPivot.AddChild( + new Image(new Rect( + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(0f), + new ScreenSpaceUnit(.4f, ScreenSpaceUnit.ScreenSpaceOrientation.Horizontal), + new ScreenSpaceUnit(.4f * logoRatio, ScreenSpaceUnit.ScreenSpaceOrientation.Horizontal) + )) + .SetTexture(logo) + .SetColor(Color.White) + .SetPivotPoint(new Vector2(.5f)) + ); + + menu.AddChild( + buttonTemplate.Clone() + .SetNewRect(buttonTemplate.GetRect() + .SetY(AbsoluteUnit.WithValue(buttonRow(0))) + .SetWidth(new RelativeUnit(1f, menu.GetRect(), RelativeUnit.Orientation.Horizontal)) + ) + .SetOnMouseUp(async (button, position) => { + ShowNewWorldScreen(); + Debug.WriteLine("Generating world..."); + GameWorld _gameWorld = await Game.Worlds.LoadNewWorld((progressReport) => { + Progress.SetText(progressReport); + Debug.WriteLine(" " + progressReport); + }); + Game.LoadScreen( + new GameplayScreen(Game, _gameWorld), + new MonoGame.Extended.Screens.Transitions.FadeTransition(Game.GraphicsDevice, Color.Black) + ); + }) + .SetText("Start Game") + ); + + menu.AddChild( + buttonTemplate.Clone() + .SetNewRect(buttonTemplate.GetRect() + .SetY(AbsoluteUnit.WithValue(buttonRow(1))) + .SetWidth(new RelativeUnit(1f, menu.GetRect(), RelativeUnit.Orientation.Horizontal)) + ) + .SetOnMouseUp((button, position) => { ShowOptionsScreen(); }) + .SetText("Options") + ); + + menu.AddChild( + buttonTemplate.Clone() + .SetNewRect(buttonTemplate.GetRect() + .SetY(AbsoluteUnit.WithValue(buttonRow(2))) + .SetWidth(new RelativeUnit(1f, menu.GetRect(), RelativeUnit.Orientation.Horizontal)) + ) + .SetOnMouseUp((button, position) => { Game.Exit(); }) + .SetText("Quit Game") + .SetColorGroup(new ButtonColorGroup(Color.White, Color.Black, Color.Red, Color.DarkRed)) + ); + + MainScreen.AddChild(menu); + } + private void ShowMainScreen() { + MainScreen.SetEnabled(true); + OptionsScreen.SetEnabled(false); + NewWorldScreen.SetEnabled(false); + } + + private void LoadOptionsScreen() { + Root.AddChild(OptionsScreen = new Container(Rect.ScreenFull)); + OptionsScreen.SetEnabled(false); + + IContainer menu = new Container(new Rect( + new ScreenSpaceUnit(.5f, ScreenSpaceUnit.ScreenSpaceOrientation.Horizontal), + new ScreenSpaceUnit(.5f, ScreenSpaceUnit.ScreenSpaceOrientation.Vertical), + AbsoluteUnit.WithValue(450f), + AbsoluteUnit.WithValue(0f) + )); + + menu.AddChild( + buttonTemplate.Clone() + .SetNewRect(buttonTemplate.GetRect() + .SetY(AbsoluteUnit.WithValue(buttonRow(0))) + .SetWidth(new RelativeUnit(1f, menu.GetRect(), RelativeUnit.Orientation.Horizontal)) + ) + .SetOnMouseUp((button, position) => { ShowMainScreen(); }) + .SetText("Back to Main Menu") + ); + + OptionsScreen.AddChild(menu); + } + private void ShowOptionsScreen() { + MainScreen.SetEnabled(false); + OptionsScreen.SetEnabled(true); + NewWorldScreen.SetEnabled(false); + } + + private void LoadNewWorldScreen() { + Root.AddChild(NewWorldScreen = new Container(Rect.ScreenFull)); + NewWorldScreen.SetEnabled(false); + + NewWorldScreen.AddChild(Progress = new Label( + new Rect( + new ScreenSpaceUnit(.5f, ScreenSpaceUnit.ScreenSpaceOrientation.Horizontal), + new ScreenSpaceUnit(.5f, ScreenSpaceUnit.ScreenSpaceOrientation.Vertical), + AbsoluteUnit.WithValue(200), + AbsoluteUnit.WithValue(50) + )) + .SetPivotPoint(new Vector2(0.5f, 0.5f)) + .SetTextProperties(new TextProperties() + .SetColor(Color.White) + .SetFont(ResourceManager.Fonts.DEFAULT) + .SetFontSize(24f) + .SetTextAlignment(TextAlignment.Center) + ) + .SetText("") + ); + } + private void ShowNewWorldScreen() { + MainScreen.SetEnabled(false); + OptionsScreen.SetEnabled(false); + NewWorldScreen.SetEnabled(true); + } + + private void LoadGlobals() { + Container copyPivot = new Container(new Rect( + new RelativeUnit(.5f, Root.GetRect(), RelativeUnit.Orientation.Horizontal), + new RelativeUnit(1f, Root.GetRect(), RelativeUnit.Orientation.Vertical), + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(12f) + )); + copyPivot.SetPivot(new Vector2(.5f, 1f)); + Root.AddChild(copyPivot); + + copyPivot.AddChild(new Label( + new Rect( + AbsoluteUnit.WithValue(12f), + AbsoluteUnit.WithValue(0f), + new RelativeUnit(1f, Root.GetRect(), RelativeUnit.Orientation.Horizontal), + new RelativeUnit(1f, copyPivot.GetRect(), RelativeUnit.Orientation.Vertical) + ) + ) + .SetTextProperties(new TextProperties() + .SetFont(ResourceManager.Fonts.DEFAULT) + .SetColor(Color.White) + .SetFontSize(12f) + .SetTextAlignment(TextAlignment.Bottom | TextAlignment.Center) + ) + .SetText("(c) leafal.io 2022-2023, all rights reserved.") + .SetPivotPoint(new Vector2(0.5f, 1f))); + } + } +}
\ No newline at end of file diff --git a/source/ui/guis/game/GameGUI.cs b/source/ui/guis/game/GameGUI.cs new file mode 100644 index 0000000..b8e6f4a --- /dev/null +++ b/source/ui/guis/game/GameGUI.cs @@ -0,0 +1,349 @@ +using System.Collections.Generic; +using System.Diagnostics; +using Celesteia.Game.Components.Items; +using Celesteia.Game.Input; +using Celesteia.Resources; +using Celesteia.UI; +using Celesteia.UI.Elements; +using Celesteia.UI.Elements.Game; +using Celesteia.UI.Elements.Game.Controls; +using Celesteia.UI.Elements.Game.Tooltips; +using Celesteia.UI.Properties; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended.TextureAtlases; + +namespace Celesteia.GUIs.Game { + public class GameGUI : GUI + { + private new GameInstance Game => (GameInstance) base.Game; + public GameGUI(GameInstance Game) : base(Game, Rect.ScreenFull) { } + + + public ItemStack CursorItem; + + private IContainer _pauseMenu; + + public ControlTips Controls { get; private set; } + + private IContainer ItemManagement; + + private InventorySlot _slotTemplate; + private CraftingRecipeSlot _recipeTemplate; + private IContainer Hotbar; + + + private Texture2D slotTexture; + private TextureAtlas slotPatches; + private Texture2D windowTexture; + private Texture2D tooltipTexture; + + private Inventory _inventory; + private List<InventorySlot> _slots; + + private int _hotbarSelection = 0; + public int HotbarSelection { + get => _hotbarSelection; + set { + _hotbarSelection = MathHelper.Clamp(value, 0, HotbarSlots - 1); + UpdateSelected(); + } + } + public readonly int HotbarSlots = 9; + + private IContainer _mousePivot; + private IContainer _inventoryScreen; + private IContainer _craftingScreen; + + private InventoryScreenState _state; + public InventoryScreenState State { + get => _state; + set { + _state = value; + _inventoryScreen.SetEnabled((int)_state > 0); + _craftingScreen.SetEnabled((int)_state > 1); + + if ((int)_state < 1 && CursorItem != null) { + ItemStack item = CursorItem; + CursorItem = null; + + _inventory.AddItem(item); + } + } + } + + private ItemTooltipDisplay _itemDisplay; + private bool _itemDisplayEnabled = false; + private CraftingTooltipDisplay _craftingDisplay; + private bool _craftingDisplayEnabled = false; + + public void SetReferenceInventory(Inventory inventory) { + _inventory = inventory; + + _slotTemplate.SetReferenceInventory(_inventory); + + LoadHotbar(); + LoadInventoryScreen(); + LoadCraftingScreen(); + + State = _state; + } + + public override void LoadContent(ContentManager Content) + { + Root.AddChild(Controls = new ControlTips(new Rect( + AbsoluteUnit.WithValue(8f), + new RelativeUnit(0.5f, Root.GetRect(), RelativeUnit.Orientation.Vertical), + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(0f) + ))); + + slotTexture = Content.Load<Texture2D>("sprites/ui/button"); + windowTexture = Content.Load<Texture2D>("sprites/ui/button"); + tooltipTexture = Content.Load<Texture2D>("sprites/ui/window"); + slotPatches = TextureAtlas.Create("patches", slotTexture, 4, 4); + + LoadTooltipDisplays(Content); + LoadPauseMenu(Content); + + _slotTemplate = new InventorySlot(new Rect( + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(-InventorySlot.SLOT_SPACING), + AbsoluteUnit.WithValue(InventorySlot.SLOT_SIZE), + AbsoluteUnit.WithValue(InventorySlot.SLOT_SIZE) + )) + .SetTexture(slotTexture) + .SetPatches(slotPatches, 4) + .SetTextProperties(new TextProperties() + .SetColor(Color.White) + .SetFont(ResourceManager.Fonts.GetFontType("Hobo")) + .SetFontSize(16f) + .SetTextAlignment(TextAlignment.Bottom | TextAlignment.Center) + ) + .SetOnMouseIn((item) => { + if ((int)State < 1) return; + _itemDisplay.SetItem(item); + _itemDisplayEnabled = true; + }) + .SetOnMouseOut(() => _itemDisplayEnabled = false); + + _recipeTemplate = new CraftingRecipeSlot(new Rect( + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(-CraftingRecipeSlot.SLOT_SPACING), + AbsoluteUnit.WithValue(CraftingRecipeSlot.SLOT_SIZE), + AbsoluteUnit.WithValue(CraftingRecipeSlot.SLOT_SIZE) + )) + .SetTexture(slotTexture) + .SetPatches(slotPatches, 4) + .SetTextProperties(new TextProperties() + .SetColor(Color.White) + .SetFont(ResourceManager.Fonts.GetFontType("Hobo")) + .SetFontSize(16f) + .SetTextAlignment(TextAlignment.Bottom | TextAlignment.Right) + ) + .SetOnMouseIn((recipe) => { + if ((int)State < 2) return; + _craftingDisplay.SetRecipe(recipe); + _craftingDisplayEnabled = true; + }) + .SetOnMouseOut(() => _craftingDisplayEnabled = false); + + _slots = new List<InventorySlot>(); + + ItemManagement = new Container(Rect.ScreenFull); + Root.AddChild(ItemManagement); + + LoadHotbar(); + + Debug.WriteLine("Loaded Game GUI."); + } + + public bool Paused { get; private set; } + public void TogglePause() { + Paused = !Paused; + UpdatePauseMenu(); + } + + private void LoadPauseMenu(ContentManager Content) { + _pauseMenu = new PauseMenu(this, Rect.ScreenFull, + new Button(new Rect( + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(250f), + AbsoluteUnit.WithValue(56f) + )) + .SetPivotPoint(new Vector2(.5f)) + .SetTexture(Content.Load<Texture2D>("sprites/ui/button")) + .MakePatches(4) + .SetTextProperties(new TextProperties() + .SetColor(Color.White) + .SetFont(ResourceManager.Fonts.GetFontType("Hobo")) + .SetFontSize(24f) + .SetTextAlignment(TextAlignment.Center)) + .SetColorGroup(new ButtonColorGroup(Color.White, Color.Black, Color.Violet, Color.DarkViolet)) + ); + + Root.AddChild(_pauseMenu); + + UpdatePauseMenu(); + } + + private void LoadTooltipDisplays(ContentManager Content) { + _mousePivot = new Container(new Rect( + AbsoluteUnit.WithValue(0f) + )); + + _itemDisplay = new ItemTooltipDisplay(new Rect( + AbsoluteUnit.WithValue(16f), + AbsoluteUnit.WithValue(16f), + AbsoluteUnit.WithValue(256f), + AbsoluteUnit.WithValue(64f) + ), tooltipTexture); + _itemDisplay.SetPivot(new Vector2(0f, 1f)); + + _mousePivot.AddChild(_itemDisplay); + + _craftingDisplay = new CraftingTooltipDisplay(new Rect( + AbsoluteUnit.WithValue(16f), + AbsoluteUnit.WithValue(16f), + AbsoluteUnit.WithValue(256f), + AbsoluteUnit.WithValue(64f) + ), tooltipTexture); + _craftingDisplay.SetPivot(new Vector2(0f, 0f)); + + _mousePivot.AddChild(_craftingDisplay); + } + + private void LoadHotbar() { + if (Hotbar != null) { + Hotbar.Dispose(); + _slots.Clear(); + } + + Hotbar = new Container(new Rect( + new RelativeUnit(0.5f, ItemManagement.GetRect(), RelativeUnit.Orientation.Horizontal), + new RelativeUnit(1f, ItemManagement.GetRect(), RelativeUnit.Orientation.Vertical), + AbsoluteUnit.WithValue((HotbarSlots * InventorySlot.SLOT_SIZE) + ((HotbarSlots - 1) * InventorySlot.SLOT_SPACING)), + AbsoluteUnit.WithValue(InventorySlot.SLOT_SIZE) + )); + Hotbar.SetPivot(new Vector2(0.5f, 1f)); + + for (int i = 0; i < HotbarSlots; i++) { + int slotNumber = i; + InventorySlot slot = _slotTemplate.Clone() + .SetNewRect(_slotTemplate.GetRect().SetX(AbsoluteUnit.WithValue(i * InventorySlot.SLOT_SIZE + (i * InventorySlot.SLOT_SPACING)))) + .SetSlot(slotNumber) + .SetOnMouseUp((button, point) => { + if ((int)State < 1) { + HotbarSelection = slotNumber; + UpdateSelected(); + } else { + ItemStack itemInSlot = _inventory.GetSlot(slotNumber); + + _inventory.SetSlot(slotNumber, CursorItem); + CursorItem = itemInSlot; + } + }); + slot.SetPivot(new Vector2(0f, 0f)); + slot.SetEnabled(true); + + _slots.Add(slot); + Hotbar.AddChild(slot); + } + + UpdateSelected(); + + ItemManagement.AddChild(Hotbar); + } + + private void LoadInventoryScreen() { + int remainingSlots = _inventory.Capacity - HotbarSlots; + int rows = (remainingSlots / HotbarSlots); + + Container pivot = new Container(new Rect( + new RelativeUnit(0.5f, ItemManagement.GetRect(), RelativeUnit.Orientation.Horizontal), + new RelativeUnit(1f, ItemManagement.GetRect(), RelativeUnit.Orientation.Vertical), + new AbsoluteUnit((HotbarSlots * InventorySlot.SLOT_SIZE) + ((HotbarSlots + 1) * InventorySlot.SLOT_SPACING)), + new AbsoluteUnit((rows * InventorySlot.SLOT_SIZE) + ((rows + 1) * InventorySlot.SLOT_SPACING)) + )); + + _inventoryScreen = new InventoryWindow(this, new Rect( + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(-(InventorySlot.SLOT_SIZE + (2 * InventorySlot.SLOT_SPACING))), + new RelativeUnit(1f, pivot.GetRect(), RelativeUnit.Orientation.Horizontal), + new RelativeUnit(1f, pivot.GetRect(), RelativeUnit.Orientation.Vertical) + ), windowTexture, _inventory, remainingSlots, HotbarSlots, _slotTemplate); + _inventoryScreen.SetPivot(new Vector2(0.5f, 1f)); + + pivot.AddChild(_inventoryScreen); + ItemManagement.AddChild(pivot); + } + + private void LoadCraftingScreen() { + int remainingSlots = _inventory.Capacity - HotbarSlots; + int rows = (remainingSlots / HotbarSlots); + + Container pivot = new Container(new Rect( + new RelativeUnit(0.5f, ItemManagement.GetRect(), RelativeUnit.Orientation.Horizontal), + new RelativeUnit(0f, ItemManagement.GetRect(), RelativeUnit.Orientation.Vertical), + new AbsoluteUnit((HotbarSlots * CraftingRecipeSlot.SLOT_SIZE) + ((HotbarSlots + 1) * CraftingRecipeSlot.SLOT_SPACING)), + new AbsoluteUnit((rows * CraftingRecipeSlot.SLOT_SIZE) + ((rows + 1) * CraftingRecipeSlot.SLOT_SPACING)) + )); + + _craftingScreen = new CraftingWindow(this, new Rect( + AbsoluteUnit.WithValue(0f), + AbsoluteUnit.WithValue(CraftingRecipeSlot.SLOT_SPACING), + new RelativeUnit(1f, pivot.GetRect(), RelativeUnit.Orientation.Horizontal), + new RelativeUnit(1f, pivot.GetRect(), RelativeUnit.Orientation.Vertical) + ), windowTexture, _inventory, _recipeTemplate); + _craftingScreen.SetPivot(new Vector2(0.5f, 0f)); + + pivot.AddChild(_craftingScreen); + ItemManagement.AddChild(pivot); + } + + public override void Update(GameTime gameTime, out bool clickedAnything) + { + _mousePivot.MoveTo(MouseHelper.Position); + _itemDisplay.SetEnabled(_itemDisplayEnabled && (int)_state > 0); + _craftingDisplay.SetEnabled(_craftingDisplayEnabled && (int)_state > 1); + + base.Update(gameTime, out clickedAnything); + } + + private Color _slightlyTransparent = new Color(255, 255, 255, 175); + private Vector2 scale = new Vector2(2f); + public override void Draw(GameTime gameTime) + { + base.Draw(gameTime); + + Game.SpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, SamplerState.PointClamp, null, null, null); + + if (CursorItem != null) Game.SpriteBatch.Draw(CursorItem.Type.Sprite, MouseHelper.Position.ToVector2(), _slightlyTransparent, 0f, Vector2.Zero, scale, SpriteEffects.None, 0f); + else { + _itemDisplay.Draw(Game.SpriteBatch); + _craftingDisplay.Draw(Game.SpriteBatch); + } + + Game.SpriteBatch.End(); + } + + public ItemStack GetSelectedItem() { + return _inventory.GetSlot(HotbarSelection); + } + + private void UpdateSelected() { + _slots.ForEach(slot => slot.SetSelected(false)); + _slots[HotbarSelection].SetSelected(true); + } + + private void UpdatePauseMenu() { + _pauseMenu.SetEnabled(Paused); + } + } + + public enum InventoryScreenState { + Closed, Inventory, Crafting + } +}
\ No newline at end of file diff --git a/source/ui/properties/TextProperties.cs b/source/ui/properties/TextProperties.cs new file mode 100644 index 0000000..2ee4deb --- /dev/null +++ b/source/ui/properties/TextProperties.cs @@ -0,0 +1,62 @@ +using Celesteia.Resources; +using Celesteia.Resources.Management; +using Microsoft.Xna.Framework; + +namespace Celesteia.UI.Properties { + public struct TextProperties { + private string _text; + private FontType _font; + private Color _textColor; + private float _fontSize; + private TextAlignment _textAlignment; + + public TextProperties Standard() { + _text = ""; + _font = ResourceManager.Fonts.DEFAULT; + _textColor = Color.White; + _fontSize = 16f; + _textAlignment = TextAlignment.Center; + + return this; + } + + public TextProperties SetText(string text) { + _text = text; + return this; + } + public string GetText() => _text; + + public TextProperties SetFont(FontType font) { + _font = font; + return this; + } + public FontType GetFont() => _font; + + public TextProperties SetColor(Color textColor) { + _textColor = textColor; + return this; + } + public Color GetColor() => _textColor; + + public TextProperties SetFontSize(float fontSize) { + _fontSize = fontSize; + return this; + } + public float GetFontSize() => _fontSize; + + public TextProperties SetTextAlignment(TextAlignment textAlignment) { + _textAlignment = textAlignment; + return this; + } + public TextAlignment GetAlignment() => _textAlignment; + + public TextProperties Clone() { + return new TextProperties() + .SetColor(_textColor) + .SetFont(_font) + .SetFontSize(_fontSize) + .SetText(_text) + .SetTextAlignment(_textAlignment); + } + } +}
\ No newline at end of file |
