summaryrefslogtreecommitdiff
path: root/source/ui
diff options
context:
space:
mode:
authorhazel <hazel@hazelthats.me>2026-01-26 22:04:39 +0100
committerhazel <hazel@hazelthats.me>2026-01-26 22:04:39 +0100
commit567c422f8cd42eba2437f9a8c2522716a1649be7 (patch)
tree93c5b296f3b7c14b626d0aadf5cad37764c41c74 /source/ui
downloadcelesteia-567c422f8cd42eba2437f9a8c2522716a1649be7.tar.gz
celesteia-567c422f8cd42eba2437f9a8c2522716a1649be7.tar.bz2
celesteia-567c422f8cd42eba2437f9a8c2522716a1649be7.zip
celesteia archive, last updated april 9th 2024
Signed-off-by: hazel <hazel@hazelthats.me>
Diffstat (limited to 'source/ui')
-rw-r--r--source/ui/Rect.cs188
-rw-r--r--source/ui/TextAlignment.cs10
-rw-r--r--source/ui/UI.cs81
-rw-r--r--source/ui/elements/Button.cs173
-rw-r--r--source/ui/elements/Clickable.cs21
-rw-r--r--source/ui/elements/Container.cs91
-rw-r--r--source/ui/elements/Element.cs53
-rw-r--r--source/ui/elements/IClickable.cs9
-rw-r--r--source/ui/elements/IContainer.cs12
-rw-r--r--source/ui/elements/IElement.cs52
-rw-r--r--source/ui/elements/Image.cs59
-rw-r--r--source/ui/elements/Label.cs62
-rw-r--r--source/ui/elements/game/CraftingRecipeSlot.cs173
-rw-r--r--source/ui/elements/game/CraftingWindow.cs62
-rw-r--r--source/ui/elements/game/InventorySlot.cs191
-rw-r--r--source/ui/elements/game/InventoryWindow.cs56
-rw-r--r--source/ui/elements/game/PauseMenu.cs74
-rw-r--r--source/ui/elements/game/controls/ControlTips.cs51
-rw-r--r--source/ui/elements/game/tooltips/CraftingTooltipDisplay.cs97
-rw-r--r--source/ui/elements/game/tooltips/ItemDisplay.cs21
-rw-r--r--source/ui/elements/game/tooltips/ItemTooltipDisplay.cs62
-rw-r--r--source/ui/elements/game/tooltips/TooltipDisplay.cs8
-rw-r--r--source/ui/guis/DebugGUI.cs66
-rw-r--r--source/ui/guis/GUI.cs48
-rw-r--r--source/ui/guis/MainMenu.cs225
-rw-r--r--source/ui/guis/game/GameGUI.cs349
-rw-r--r--source/ui/properties/TextProperties.cs62
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