diff options
author | Thomas Leyh <leyh.thomas@web.de> | 2016-07-24 08:14:18 +0200 |
---|---|---|
committer | Thomas Leyh <leyh.thomas@web.de> | 2016-07-24 08:14:18 +0200 |
commit | ced3d03bdb3ce866d832e03fb212865140905a9a (patch) | |
tree | 2a16c2063a46d3c354ce1585029dda3124f6ad93 /V3/Widgets | |
parent | 0394dccaf06e1009e591a6ff4d645895574724c1 (diff) | |
download | V3-ced3d03bdb3ce866d832e03fb212865140905a9a.tar.gz V3-ced3d03bdb3ce866d832e03fb212865140905a9a.tar.bz2 |
Diffstat (limited to 'V3/Widgets')
-rw-r--r-- | V3/Widgets/AbstractMenu.cs | 96 | ||||
-rw-r--r-- | V3/Widgets/AbstractTextWidget.cs | 113 | ||||
-rw-r--r-- | V3/Widgets/AchievementBox.cs | 91 | ||||
-rw-r--r-- | V3/Widgets/Button.cs | 111 | ||||
-rw-r--r-- | V3/Widgets/EmptyWidget.cs | 41 | ||||
-rw-r--r-- | V3/Widgets/FormMenu.cs | 75 | ||||
-rw-r--r-- | V3/Widgets/HorizontalOrientation.cs | 9 | ||||
-rw-r--r-- | V3/Widgets/IBasicWidgetFactory.cs | 38 | ||||
-rw-r--r-- | V3/Widgets/IClickable.cs | 14 | ||||
-rw-r--r-- | V3/Widgets/IImageWidget.cs | 15 | ||||
-rw-r--r-- | V3/Widgets/IMenu.cs | 33 | ||||
-rw-r--r-- | V3/Widgets/IMenuFactory.cs | 9 | ||||
-rw-r--r-- | V3/Widgets/ISelectable.cs | 27 | ||||
-rw-r--r-- | V3/Widgets/ITextWidget.cs | 43 | ||||
-rw-r--r-- | V3/Widgets/IWidget.cs | 34 | ||||
-rw-r--r-- | V3/Widgets/Label.cs | 14 | ||||
-rw-r--r-- | V3/Widgets/SelectButton.cs | 108 | ||||
-rw-r--r-- | V3/Widgets/VerticalMenu.cs | 42 | ||||
-rw-r--r-- | V3/Widgets/WidgetFactory.cs | 78 |
19 files changed, 991 insertions, 0 deletions
diff --git a/V3/Widgets/AbstractMenu.cs b/V3/Widgets/AbstractMenu.cs new file mode 100644 index 0000000..f179b5e --- /dev/null +++ b/V3/Widgets/AbstractMenu.cs @@ -0,0 +1,96 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using System.Collections.Generic; +using System.Linq; +using V3.Input; + +namespace V3.Widgets +{ + /// <summary> + /// An abstract menu that handles the updating and drawing of widgtes. + /// </summary> + public abstract class AbstractMenu : IMenu + { + public List<IWidget> Widgets { get; } = new List<IWidget>(); + public Vector2 Size { get; private set; } + public Vector2 Position { get; private set; } + + private readonly GraphicsDeviceManager mGraphicsDeviceManager; + + protected AbstractMenu(GraphicsDeviceManager graphicsDeviceManager) + { + mGraphicsDeviceManager = graphicsDeviceManager; + } + + public void HandleMouseEvent(IMouseEvent mouseEvent) + { + foreach (var clickable in Widgets.OfType<IClickable>()) + clickable.HandleMouseEvent(mouseEvent); + } + + public void Draw(SpriteBatch spriteBatch) + { + UpdateWidgetRelativePositions(); + UpdateWidgetAbsolutePositions(); + spriteBatch.Begin(); + Widgets.ForEach(w => w.Draw(spriteBatch)); + spriteBatch.End(); + } + + public void Update() + { + ResetClicked(); + UpdateMouseSelection(); + UpdateWidgetSizes(); + } + + protected abstract void UpdateWidgetSizes(); + + protected abstract void UpdateWidgetRelativePositions(); + + protected abstract Vector2 GetTotalSize(); + + private void UpdateWidgetAbsolutePositions() + { + var viewport = mGraphicsDeviceManager.GraphicsDevice.Viewport; + Size = GetTotalSize(); + var viewportSize = new Vector2(viewport.Bounds.Width, viewport.Bounds.Height); + Position = (viewportSize - Size) / 2; + Widgets.ForEach(w => w.Position = w.Position + Position); + } + + private void ResetClicked() + { + foreach (var clickable in Widgets.OfType<IClickable>()) + clickable.IsClicked = false; + } + + private void UpdateMouseSelection() + { + var position = Mouse.GetState().Position; + foreach (var selectable in Widgets.OfType<ISelectable>()) + selectable.IsSelected = selectable.CheckSelected(position); + } + + protected static void MakeWidgetsSameSize(IEnumerable<IWidget> widgets) + { + var xMax = 0f; + var yMax = 0f; + + var widgetsCopy = widgets as IList<IWidget> ?? widgets.ToList(); + foreach (var widget in widgetsCopy) + { + var size = widget.GetMinimumSize(); + if (size.X > xMax) + xMax = size.X; + if (size.Y > yMax) + yMax = size.Y; + } + + var sizeMax = new Vector2(xMax, yMax); + foreach (var widget in widgetsCopy) + widget.Size = sizeMax; + } + } +} diff --git a/V3/Widgets/AbstractTextWidget.cs b/V3/Widgets/AbstractTextWidget.cs new file mode 100644 index 0000000..1f28742 --- /dev/null +++ b/V3/Widgets/AbstractTextWidget.cs @@ -0,0 +1,113 @@ +using System; +using System.Text; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using Ninject; + +namespace V3.Widgets +{ + public abstract class AbstractTextWidget : ITextWidget, IInitializable + { + private string mText; + + public Vector2 Position { get; set; } + + public Vector2 Size { get; set; } + + public string Text + { + get { return mText; } + set { mText = ReplaceUmlauteWhenOnUnix(value); } + } + + public float PaddingX { get; set; } = 80; + + public float PaddingY { get; set; } = 5; + + public Color Color { get; set; } = Color.Black; + + public HorizontalOrientation HorizontalOrientation { get; set; } = HorizontalOrientation.Center; + + public SpriteFont Font { get; set; } + + private readonly ContentManager mContentManager; + + public AbstractTextWidget(ContentManager contentManager) + { + mContentManager = contentManager; + Text = ""; + } + + public virtual void Initialize() + { + Font = mContentManager.Load<SpriteFont>("Fonts/MenuFont"); + } + + public virtual void Draw(SpriteBatch spriteBatch) + { + var calculatedSize = Font.MeasureString(Text); + var position = Position; + switch (HorizontalOrientation) + { + case HorizontalOrientation.Left: + position.X += PaddingX; + break; + case HorizontalOrientation.Center: + position.X += (Size - calculatedSize).X / 2; + break; + case HorizontalOrientation.Right: + position.X += (Size - calculatedSize).X; + position.X -= PaddingX; + break; + } + position.Y += (Size - calculatedSize).Y / 2; + spriteBatch.DrawString(Font, Text, position, GetColor()); + } + + public virtual Vector2 GetMinimumSize() + { + var size = Font.MeasureString(Text); + size.X += 2 * PaddingX; + size.Y += 2 * PaddingY; + return size; + } + + public bool CheckSelected(Point position) + { + var rectangle = new Rectangle((int) Position.X, (int) Position.Y, (int) Size.X, (int) Size.Y); + return rectangle.Contains(position); + } + + protected virtual Color GetColor() + { + return Color; + } + + /// <summary> + /// Test if execution platform is UNIX and replace german Umlaute + /// accordingly because a strange ArgumentException is thrown otherwise. + /// </summary> + /// <param name="originalString">The original input string.</param> + /// <returns></returns> + private string ReplaceUmlauteWhenOnUnix(string originalString) + { + // Taken from <http://mono.wikia.com/wiki/Detecting_the_execution_platform>. + int p = (int) Environment.OSVersion.Platform; + if ((p == 4) || (p == 6) || (p == 128)) // Running on Unix + { + var sb = new StringBuilder(); + foreach (char c in originalString) + { + if (c < 128) + { + sb.Append(c); + } + } + return sb.ToString(); + } + // NOT running on Unix. + return originalString; + } + } +} diff --git a/V3/Widgets/AchievementBox.cs b/V3/Widgets/AchievementBox.cs new file mode 100644 index 0000000..000894a --- /dev/null +++ b/V3/Widgets/AchievementBox.cs @@ -0,0 +1,91 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; + +namespace V3.Widgets +{ + // ReSharper disable once ClassNeverInstantiated.Global + public sealed class AchievementBox : AbstractTextWidget + { + public bool IsEnabled { private get; set; } + private Color BackgroundColor { get; } = Color.Gray; + + private readonly ContentManager mContentManager; + private readonly WidgetFactory mWidgetFactory; + private SpriteFont mTitleFont; + private SpriteFont mDescriptionFont; + + private Label mButtonTitle; + private Label mButtonDescription; + + private Texture2D mRectangle; + + public AchievementBox(ContentManager contentManager, WidgetFactory widgetFactory) : base(contentManager) + { + mContentManager = contentManager; + mWidgetFactory = widgetFactory; + } + + public override void Initialize() + { + mRectangle = mContentManager.Load<Texture2D>("Sprites/WhiteRectangle"); + mTitleFont = mContentManager.Load<SpriteFont>("Fonts/MenuFont"); + mDescriptionFont = mContentManager.Load<SpriteFont>("Fonts/UnitFont"); + + mButtonTitle = mWidgetFactory.CreateLabel(""); + mButtonTitle.PaddingY = 0; + mButtonTitle.PaddingX = 10; + mButtonTitle.HorizontalOrientation = HorizontalOrientation.Left; + mButtonDescription = mWidgetFactory.CreateLabel(""); + mButtonDescription.PaddingX = 10; + mButtonDescription.PaddingY = 0; + mButtonDescription.HorizontalOrientation = HorizontalOrientation.Left; + + base.Initialize(); + } + + public override void Draw(SpriteBatch spriteBatch) + { + var rectangle = new Rectangle((int)Position.X, (int)Position.Y, (int)Size.X, (int)Size.Y); + + if (IsEnabled) + { + var borderRectangle = new Rectangle((int)Position.X - 2, (int)Position.Y - 2, (int)Size.X + 4, (int)Size.Y + 4); + spriteBatch.Draw(mRectangle, borderRectangle, Color.Gray); + } + + mButtonTitle.Color = IsEnabled ? Color.Black : Color.Gray; + mButtonDescription.Color = IsEnabled ? Color.Black : Color.Gray; + + spriteBatch.Draw(mRectangle, rectangle, GetBackgroundColor()); + mButtonTitle.Position = Position + new Vector2(0, 10); + mButtonDescription.Position = mButtonTitle.Position + new Vector2(0, mButtonDescription.Size.Y); + mButtonDescription.Draw(spriteBatch); + mButtonTitle.Draw(spriteBatch); + base.Draw(spriteBatch); + } + + public override Vector2 GetMinimumSize() + { + var titleSize = mButtonTitle.GetMinimumSize(); + var descriptionSize = mButtonDescription.GetMinimumSize(); + + return new Vector2(MathHelper.Max(titleSize.X, descriptionSize.X), titleSize.Y + descriptionSize.Y + 20); + } + + private Color GetBackgroundColor() + { + return IsEnabled ? BackgroundColor : Color.LightGray; + } + + public void SetText(string title, string description) + { + mButtonTitle.Text = title; + mButtonDescription.Text = description; + mButtonTitle.Size = mButtonTitle.GetMinimumSize(); + mButtonDescription.Size = mButtonDescription.GetMinimumSize(); + mButtonTitle.Font = mTitleFont; + mButtonDescription.Font = mDescriptionFont; + } + } +} diff --git a/V3/Widgets/Button.cs b/V3/Widgets/Button.cs new file mode 100644 index 0000000..04307d0 --- /dev/null +++ b/V3/Widgets/Button.cs @@ -0,0 +1,111 @@ +using Castle.Core.Internal; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using V3.Input; + +namespace V3.Widgets +{ + // ReSharper disable once ClassNeverInstantiated.Global + public sealed class Button : AbstractTextWidget, IClickable, IImageWidget, ISelectable + { + public bool IsClicked { get; set; } + + public bool IsEnabled { get; set; } = true; + + public bool IsSelected { get; set; } + + public Color BackgroundColor { private get; set; } = Color.Gray; + + public Texture2D Image { get; set; } + + public string Tooltip { get; set; } + public string TooltipTitle { private get; set; } + + public Rectangle Rectangle + { + get { return new Rectangle((int)Position.X, (int)Position.Y, (int)Size.X, (int)Size.Y); } + } + + private readonly ContentManager mContentManager; + private readonly WidgetFactory mWidgetFactory; + + private Texture2D mRectangle; + private AchievementBox mTooltipBox; + + public Button(ContentManager contentManager, WidgetFactory widgetFactory) : base(contentManager) + { + mContentManager = contentManager; + mWidgetFactory = widgetFactory; + } + + public override void Initialize() + { + mRectangle = mContentManager.Load<Texture2D>("Sprites/WhiteRectangle"); + mTooltipBox = mWidgetFactory.CreateAchievementBox(); + + base.Initialize(); + } + + public void HandleMouseEvent(IMouseEvent mouseEvent) + { + if (!IsEnabled) + return; + if (mouseEvent.MouseButton == MouseButton.Left && mouseEvent.ButtonState == ButtonState.Released) + { + if (!mouseEvent.PositionReleased.HasValue) + return; + if (Rectangle.Contains(mouseEvent.PositionReleased.Value)) + IsClicked = true; + } + } + + public override void Draw(SpriteBatch spriteBatch) + { + var rectangle = new Rectangle((int)Position.X, (int)Position.Y, (int)Size.X, (int)Size.Y); + + if (IsSelected && IsEnabled) + { + var borderRectangle = new Rectangle((int)Position.X - 2, (int)Position.Y - 2, (int)Size.X + 4, (int)Size.Y + 4); + spriteBatch.Draw(mRectangle, borderRectangle, Color.Red); + } + + if (Image == null) + { + spriteBatch.Draw(mRectangle, rectangle, GetBackgroundColor()); + } + else + { + spriteBatch.Draw(Image, rectangle, Color.White); + if (!IsEnabled) + { + spriteBatch.Draw(mRectangle, rectangle, Color.Black * 0.7f); + } + } + + if (IsSelected && !TooltipTitle.IsNullOrEmpty() && !Tooltip.IsNullOrEmpty()) + { + mTooltipBox.SetText(TooltipTitle, Tooltip); + mTooltipBox.Size = mTooltipBox.GetMinimumSize(); + mTooltipBox.Position = Position - new Vector2(0, mTooltipBox.Size.Y); + mTooltipBox.IsEnabled = true; + if (mTooltipBox.Position.Y < 0) + mTooltipBox.Position = Position + new Vector2(0, Size.Y); + mTooltipBox.Draw(spriteBatch); + } + + base.Draw(spriteBatch); + } + + protected override Color GetColor() + { + return IsEnabled ? Color : Color.Gray; + } + + private Color GetBackgroundColor() + { + return IsEnabled ? BackgroundColor : Color.LightGray; + } + } +} diff --git a/V3/Widgets/EmptyWidget.cs b/V3/Widgets/EmptyWidget.cs new file mode 100644 index 0000000..d9ad8d8 --- /dev/null +++ b/V3/Widgets/EmptyWidget.cs @@ -0,0 +1,41 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace V3.Widgets +{ + /// <summary> + /// A placeholder widget. + /// </summary> + // ReSharper disable once ClassNeverInstantiated.Global + public sealed class EmptyWidget : IWidget + { + /// <summary> + /// The current position of this widget on the screen. + /// </summary> + public Vector2 Position { get; set; } + + /// <summary> + /// The size of this widget on the screen. The widget should try to + /// fill this size. Should not be smaller than the value returned by + /// GetMinimumSize(). + /// </summary> + public Vector2 Size { get; set; } + + /// <summary> + /// Returns the minimum size this widgets needs. + /// </summary> + public Vector2 GetMinimumSize() + { + return new Vector2(0, 0); + } + + /// <summary> + /// Draws this widget on the given sprite batch. It is assumed that + /// spriteBatch.Begin() has already been called. + /// </summary> + public void Draw(SpriteBatch spriteBatch) + { + } + } +} + diff --git a/V3/Widgets/FormMenu.cs b/V3/Widgets/FormMenu.cs new file mode 100644 index 0000000..746f8f4 --- /dev/null +++ b/V3/Widgets/FormMenu.cs @@ -0,0 +1,75 @@ +using Microsoft.Xna.Framework; +using System; +using System.Linq; + +namespace V3.Widgets +{ + /// <summary> + /// A menu that arranges widgets in two columns. All widgets in on column + /// are made the same size (the maximum widget size). + /// </summary> + // ReSharper disable once ClassNeverInstantiated.Global + public sealed class FormMenu : AbstractMenu + { + private const float PaddingInnerX = 10; + private const float PaddingOuterX = 10; + private const float PaddingInnerY = 10; + private const float PaddingOuterY = 10; + + public FormMenu(GraphicsDeviceManager graphicsDeviceManager) : base(graphicsDeviceManager) + { + } + + protected override Vector2 GetTotalSize() + { + var size = new Vector2(2 * PaddingOuterX, 2 * PaddingOuterY); + var maxXLeft = 0f; + var maxXRight = 0f; + for (var i = 0; i < Widgets.Count; i += 2) + { + maxXLeft = Math.Max(maxXLeft, Widgets[i].Size.X); + if (i + 1 < Widgets.Count) + { + maxXRight = Math.Max(maxXRight, Widgets[i + 1].Size.X); + size.Y += Math.Max(Widgets[i].Size.Y, Widgets[i + 1].Size.Y); + } + else + { + size.Y += Widgets[i].Size.Y; + } + if (i + 2 < Widgets.Count) + size.Y += PaddingInnerY; + } + size.X += maxXLeft + maxXRight + PaddingInnerX; + return size; + } + + protected override void UpdateWidgetRelativePositions() + { + var y = PaddingOuterY; + for (var i = 0; i < Widgets.Count; i += 2) + { + var x = PaddingOuterX; + Widgets[i].Position = new Vector2(x, y); + if (i + 1 < Widgets.Count) + { + x += Widgets[i].Size.X; + x += PaddingInnerX; + Widgets[i + 1].Position = new Vector2(x, y); + y += Math.Max(Widgets[i].Size.Y, Widgets[i + 1].Size.Y); + } + else + { + y += Widgets[i].Size.Y; + } + y += PaddingInnerY; + } + } + + protected override void UpdateWidgetSizes() + { + MakeWidgetsSameSize(Widgets.Where((w, i) => i % 2 == 0)); + MakeWidgetsSameSize(Widgets.Where((w, i) => i % 2 == 1)); + } + } +} diff --git a/V3/Widgets/HorizontalOrientation.cs b/V3/Widgets/HorizontalOrientation.cs new file mode 100644 index 0000000..489667a --- /dev/null +++ b/V3/Widgets/HorizontalOrientation.cs @@ -0,0 +1,9 @@ +namespace V3.Widgets +{ + public enum HorizontalOrientation + { + Left, + Center, + Right + } +} diff --git a/V3/Widgets/IBasicWidgetFactory.cs b/V3/Widgets/IBasicWidgetFactory.cs new file mode 100644 index 0000000..dd0a28d --- /dev/null +++ b/V3/Widgets/IBasicWidgetFactory.cs @@ -0,0 +1,38 @@ +namespace V3.Widgets +{ + /// <summary> + /// A widget factory that is automatically implemented by Ninject. + /// </summary> + public interface IBasicWidgetFactory + { + /// <summary> + /// Creates a new button widget. + /// </summary> + /// <returns>a new button widget</returns> + Button CreateButton(); + + /// <summary> + /// Creates a new empty widget. + /// </summary> + /// <returns>a new empty widget</returns> + EmptyWidget CreateEmptyWidget(); + + /// <summary> + /// Creates a new select button widget. + /// </summary> + /// <returns>a new select button widget</returns> + SelectButton CreateSelectButton(); + + /// <summary> + /// Creates a new label widget. + /// </summary> + /// <returns>a new label widget</returns> + Label CreateLabel(); + + /// <summary> + /// Creates a new Achievement Box. + /// </summary> + /// <returns>a new achievement box widget.</returns> + AchievementBox CreateAchievementBox(); + } +} diff --git a/V3/Widgets/IClickable.cs b/V3/Widgets/IClickable.cs new file mode 100644 index 0000000..276c4a1 --- /dev/null +++ b/V3/Widgets/IClickable.cs @@ -0,0 +1,14 @@ +using V3.Input; + +namespace V3.Widgets +{ + /// <summary> + /// An element that can be clicked on. + /// </summary> + public interface IClickable : IMouseEventHandler + { + bool IsClicked { get; set; } + + bool IsEnabled { get; set; } + } +} diff --git a/V3/Widgets/IImageWidget.cs b/V3/Widgets/IImageWidget.cs new file mode 100644 index 0000000..5a5b368 --- /dev/null +++ b/V3/Widgets/IImageWidget.cs @@ -0,0 +1,15 @@ +using Microsoft.Xna.Framework.Graphics; + +namespace V3.Widgets +{ + /// <summary> + /// A widget that displays an image. + /// </summary> + public interface IImageWidget : IWidget + { + /// <summary> + /// The image to display. + /// </summary> + Texture2D Image { get; set; } + } +} diff --git a/V3/Widgets/IMenu.cs b/V3/Widgets/IMenu.cs new file mode 100644 index 0000000..ad121e0 --- /dev/null +++ b/V3/Widgets/IMenu.cs @@ -0,0 +1,33 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Collections.Generic; +using V3.Input; + +namespace V3.Widgets +{ + /// <summary> + /// A menu that displays a list of widgets. + /// </summary> + public interface IMenu : IMouseEventHandler + { + /// <summary> + /// The widgets in this menu. The order of the widgets in this list + /// is the order in which they are displayed. + /// </summary> + List<IWidget> Widgets { get; } + + /// <summary> + /// The total size of the widgets in this menu. + /// </summary> + Vector2 Size { get; } + + /// <summary> + /// The current position of the widgets. + /// </summary> + Vector2 Position { get; } + + void Draw(SpriteBatch spriteBatch); + + void Update(); + } +} diff --git a/V3/Widgets/IMenuFactory.cs b/V3/Widgets/IMenuFactory.cs new file mode 100644 index 0000000..24ed311 --- /dev/null +++ b/V3/Widgets/IMenuFactory.cs @@ -0,0 +1,9 @@ +namespace V3.Widgets +{ + public interface IMenuFactory + { + FormMenu CreateFormMenu(); + + VerticalMenu CreateVerticalMenu(); + } +}
\ No newline at end of file diff --git a/V3/Widgets/ISelectable.cs b/V3/Widgets/ISelectable.cs new file mode 100644 index 0000000..6eb78dc --- /dev/null +++ b/V3/Widgets/ISelectable.cs @@ -0,0 +1,27 @@ +using Microsoft.Xna.Framework; + +namespace V3.Widgets +{ + /// <summary> + /// An element that can be selected. + /// </summary> + public interface ISelectable + { + bool IsSelected { get; set; } + + /// <summary> + /// The tooltip to show if this widget is selected. Can be null or + /// empty if no tooltip should be shown. + /// </summary> + string Tooltip { get; set; } + + /// <summary> + /// Checks whether this element is selected by a mouse click at the + /// given position. + /// </summary> + /// <param name="position">the position of the mouse click</param> + /// <returns>true if the element is selected by the mouse click at + /// that position, otherwise false</returns> + bool CheckSelected(Point position); + } +} diff --git a/V3/Widgets/ITextWidget.cs b/V3/Widgets/ITextWidget.cs new file mode 100644 index 0000000..e5f8203 --- /dev/null +++ b/V3/Widgets/ITextWidget.cs @@ -0,0 +1,43 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace V3.Widgets +{ + /// <summary> + /// A widget that contains some text. + /// </summary> + public interface ITextWidget : IWidget + { + /// <summary> + /// The text of this widget. + /// </summary> + string Text { get; set; } + + /// <summary> + /// The space that is added before and after the text in the + /// horizontal direction. + /// </summary> + float PaddingX { get; set; } + + /// <summary> + /// The space that is added before and after the text in the + /// vertical direction. + /// </summary> + float PaddingY { get; set; } + + /// <summary> + /// The horizontal orientation of the text within the size. + /// </summary> + HorizontalOrientation HorizontalOrientation { get; set; } + + /// <summary> + /// The default color of the text. + /// </summary> + Color Color { get; set; } + + /// <summary> + /// The font for text rendering. + /// </summary> + SpriteFont Font { get; set; } + } +} diff --git a/V3/Widgets/IWidget.cs b/V3/Widgets/IWidget.cs new file mode 100644 index 0000000..23466fe --- /dev/null +++ b/V3/Widgets/IWidget.cs @@ -0,0 +1,34 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace V3.Widgets +{ + /// <summary> + /// A simple widget that has a size and a position and that can be drawn. + /// </summary> + public interface IWidget + { + /// <summary> + /// The current position of this widget on the screen. + /// </summary> + Vector2 Position { get; set; } + + /// <summary> + /// The size of this widget on the screen. The widget should try to + /// fill this size. Should not be smaller than the value returned by + /// GetMinimumSize(). + /// </summary> + Vector2 Size { get; set; } + + /// <summary> + /// Returns the minimum size this widgets needs. + /// </summary> + Vector2 GetMinimumSize(); + + /// <summary> + /// Drawst this widget on the given sprite batch. It is assumed that + /// spriteBatch.Begin() has already been called. + /// </summary> + void Draw(SpriteBatch spriteBatch); + } +} diff --git a/V3/Widgets/Label.cs b/V3/Widgets/Label.cs new file mode 100644 index 0000000..6b60d4b --- /dev/null +++ b/V3/Widgets/Label.cs @@ -0,0 +1,14 @@ +using Microsoft.Xna.Framework.Content; + +namespace V3.Widgets +{ + // ReSharper disable once ClassNeverInstantiated.Global + public sealed class Label : AbstractTextWidget + { + public Label(ContentManager contentManager) : base(contentManager) + { + HorizontalOrientation = HorizontalOrientation.Left; + PaddingX = 20; + } + } +} diff --git a/V3/Widgets/SelectButton.cs b/V3/Widgets/SelectButton.cs new file mode 100644 index 0000000..9079c6d --- /dev/null +++ b/V3/Widgets/SelectButton.cs @@ -0,0 +1,108 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using System.Collections.Generic; +using System.Linq; +using V3.Input; + +namespace V3.Widgets +{ + // ReSharper disable once ClassNeverInstantiated.Global + public sealed class SelectButton : AbstractTextWidget, IClickable + { + public List<string> Values { get; } = new List<string>(); + + public int SelectedIndex { get; set; } + + public bool IsClicked { get; set; } + + public bool IsEnabled { get; set; } = true; + + private readonly ContentManager mContentManager; + + private Texture2D mTriangle; + private Rectangle mBoxArrowLeft; + private Rectangle mBoxArrowRight; + + public SelectButton(ContentManager contentManager) : base(contentManager) + { + mContentManager = contentManager; + } + + public override void Initialize() + { + mTriangle = mContentManager.Load<Texture2D>("Menu/arrow_white"); + base.Initialize(); + } + + public void HandleMouseEvent(IMouseEvent mouseEvent) + { + if (mouseEvent.MouseButton == MouseButton.Left && mouseEvent.ButtonState == ButtonState.Released) + { + if (!mouseEvent.PositionReleased.HasValue) + return; + var rectangle = new Rectangle((int)Position.X, (int)Position.Y, (int)Size.X, (int)Size.Y); + var position = mouseEvent.PositionReleased.Value; + if (rectangle.Contains(position)) + IsClicked = true; + + int change; + if (mBoxArrowLeft.Contains(position)) + change = -1; + else if (mBoxArrowRight.Contains(position)) + change = 1; + else + return; + + SelectedIndex += change; + if (SelectedIndex < 0) + SelectedIndex += Values.Count; + if (SelectedIndex >= Values.Count) + SelectedIndex -= Values.Count; + } + } + + public override void Draw(SpriteBatch spriteBatch) + { + UpdateSelection(); + + var arrowPadding = 30; + var arrowLength = 20; + var arrowY = (int)Position.Y + (int)(Size.Y / 2) - arrowLength / 2; + mBoxArrowLeft = new Rectangle((int)Position.X + arrowPadding, + arrowY, arrowLength, arrowLength); + mBoxArrowRight = new Rectangle((int)Position.X + (int)Size.X - arrowPadding, + arrowY, arrowLength, arrowLength); + + + spriteBatch.Draw(mTriangle, mBoxArrowLeft, null, Color.Gray, 0, new Vector2(0, 0), SpriteEffects.FlipHorizontally, 0); + spriteBatch.Draw(mTriangle, mBoxArrowRight, Color.Gray); + + base.Draw(spriteBatch); + } + + public override Vector2 GetMinimumSize() + { + Vector2 size; + try + { + size = new Vector2(Values.Max(v => Font.MeasureString(v).X), Font.MeasureString(Text).Y); + } + catch (ArgumentException) + { + // Return whatever. + size = new Vector2(100, 40); + } + size.X += 2 * PaddingX; + size.Y += 2 * PaddingY; + return size; + } + + private void UpdateSelection() + { + Text = Values[SelectedIndex]; + } + } +} diff --git a/V3/Widgets/VerticalMenu.cs b/V3/Widgets/VerticalMenu.cs new file mode 100644 index 0000000..bc18439 --- /dev/null +++ b/V3/Widgets/VerticalMenu.cs @@ -0,0 +1,42 @@ +using Microsoft.Xna.Framework; +using System.Linq; + +namespace V3.Widgets +{ + /// <summary> + /// A simple menu with vertically arranged widgets. All widgets are made + /// the same size (the maximum widget size). + /// </summary> + // ReSharper disable once ClassNeverInstantiated.Global + public sealed class VerticalMenu : AbstractMenu + { + private float mPadding = 10; + + public VerticalMenu(GraphicsDeviceManager graphicsDeviceManager) : base(graphicsDeviceManager) + { + } + + protected override void UpdateWidgetRelativePositions() + { + var y = 0f; + foreach (var widget in Widgets) + { + widget.Position = new Vector2(0, y); + y += widget.Size.Y; + y += mPadding; + } + } + + protected override Vector2 GetTotalSize() + { + var totalX = Widgets.Max(w => w.Size.X); + var totalY = (Widgets.Count - 1) * mPadding + Widgets.Sum(w => w.Size.Y); + return new Vector2(totalX, totalY); + } + + protected override void UpdateWidgetSizes() + { + MakeWidgetsSameSize(Widgets); + } + } +} diff --git a/V3/Widgets/WidgetFactory.cs b/V3/Widgets/WidgetFactory.cs new file mode 100644 index 0000000..35e8e1b --- /dev/null +++ b/V3/Widgets/WidgetFactory.cs @@ -0,0 +1,78 @@ +namespace V3.Widgets +{ + /// <summary> + /// A widget factory. + /// </summary> + // ReSharper disable once ClassNeverInstantiated.Global + public sealed class WidgetFactory + { + private IBasicWidgetFactory mFactory; + + public WidgetFactory(IBasicWidgetFactory factory) + { + mFactory = factory; + } + + /// <summary> + /// Creates a new button with the given text. + /// </summary> + /// <param name="text">the text of the button</param> + /// <returns>the created button</returns> + public Button CreateButton(string text) + { + return CreateTextWidget(mFactory.CreateButton(), text); + } + + /// <summary> + /// Creates a new empty widget. + /// </summary> + /// <returns>the created widget</returns> + public EmptyWidget CreateEmptyWidget() + { + return mFactory.CreateEmptyWidget(); + } + + /// <summary> + /// Creates a new select button with the given text options. + /// </summary> + /// <param name="values">the values of the button</param> + /// <returns>the created button</returns> + public SelectButton CreateSelectButton(string[] values) + { + var widget = CreateTextWidget(mFactory.CreateSelectButton(), ""); + foreach (var val in values) + widget.Values.Add(val); + return widget; + } + + /// <summary> + /// Creates a new select button without options. + /// </summary> + /// <returns>the created button</returns> + public SelectButton CreateSelectButton() + { + return mFactory.CreateSelectButton(); + } + + /// <summary> + /// Creates a new label with the given text. + /// </summary> + /// <param name="text">the text of the label</param> + /// <returns>the created label</returns> + public Label CreateLabel(string text) + { + return CreateTextWidget(mFactory.CreateLabel(), text); + } + + private T CreateTextWidget<T>(T widget, string text) where T : ITextWidget + { + widget.Text = text; + return widget; + } + + public AchievementBox CreateAchievementBox() + { + return mFactory.CreateAchievementBox(); + } + } +} |