Gui dialogs (#410)

* started refactoring for gui windows

* a bit more work on gui windows

* fixed some bugs and broken build stuff

* fixed some bugs in gui controls

* a little code cleanup

* got gui dialogs positioning and experimenting with named styles by type

* apply control styles directly in the constructor of controls

* got everything compiling again

* all gui controls have skinnable constructors

* rework uniform grid desired size calculations

* turns out gui layouts are complicated

* include pdb files in nuget packages

* fixed a bug in find nearest glyph

* custom control styling

* added a range constructor overload that takes one value

* rethinking control desired size behaviour

* made particle emitters more editable at runtime

* moved the auto trigger code to the particle effect instead of the emitters

* tweaks

* changed the particle modifer interface to an abstract base with an optional name

* changed the particle modifer interface to an abstract base with an optional name

* an attempt at creating a gui combo box

* rolled back the nested control experiment

* pretty good (but not perfect) combo boxes

* tweaks

* name property bindings on lists

* removed and cleaned up the hacks from the gui combo box

* minor combo box styling tweaks

* minor bug fix in gui combo box

* experiments with data binding

* fixed a mistake on the texture region service interface

* working on the ability to serialize particles

* finished particle serialization

* more robust range deserialization

* added editor browable attributes

* fixed some broken demos

* made the particle modifiers editable at runtime

* fixed some issues with particle serialization

* fixed some issues with interpolators
This commit is contained in:
Dylan Wilson
2017-07-22 21:22:13 +10:00
committed by GitHub
parent 98906f2873
commit b3a631aff5
111 changed files with 1629 additions and 820 deletions
@@ -1,26 +1,19 @@
using System;
using MonoGame.Extended.TextureAtlases;
namespace MonoGame.Extended.Gui.Controls
{
public class GuiButton : GuiControl
{
public GuiButton()
: this(null)
: base(null)
{
}
public GuiButton(TextureRegion2D backgroundRegion)
: base(backgroundRegion)
public GuiButton(GuiSkin skin)
: base(skin)
{
}
protected override Size2 CalculateDesiredSize(IGuiContext context, Size2 availableSize)
{
var size = base.CalculateDesiredSize(context, availableSize);
return size;
}
public event EventHandler Clicked;
public event EventHandler PressedStateChanged;
@@ -9,8 +9,8 @@ namespace MonoGame.Extended.Gui.Controls
{
}
public GuiCanvas(TextureRegion2D backgroundRegion)
: base(backgroundRegion)
public GuiCanvas(GuiSkin skin)
: base(skin)
{
}
@@ -1,7 +1,5 @@
using System;
using Microsoft.Xna.Framework;
using MonoGame.Extended.Shapes;
using MonoGame.Extended.TextureAtlases;
namespace MonoGame.Extended.Gui.Controls
{
@@ -11,8 +9,8 @@ namespace MonoGame.Extended.Gui.Controls
{
}
public GuiCheckBox(TextureRegion2D backgroundRegion)
: base(backgroundRegion)
public GuiCheckBox(GuiSkin skin)
: base(skin)
{
}
@@ -0,0 +1,108 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended.Input.InputListeners;
using MonoGame.Extended.TextureAtlases;
namespace MonoGame.Extended.Gui.Controls
{
public class GuiComboBox : GuiItemsControl
{
public GuiComboBox()
: this(null)
{
}
public GuiComboBox(GuiSkin skin)
: base(skin)
{
}
public bool IsOpen { get; set; }
public TextureRegion2D DropDownRegion { get; set; }
public Color DropDownColor { get; set; } = Color.White;
public override void OnKeyPressed(IGuiContext context, KeyboardEventArgs args)
{
base.OnKeyPressed(context, args);
if (args.Key == Keys.Enter)
IsOpen = false;
}
public override void OnPointerDown(IGuiContext context, GuiPointerEventArgs args)
{
base.OnPointerDown(context, args);
IsOpen = !IsOpen;
}
protected override Rectangle GetContentRectangle(IGuiContext context)
{
return GetDropDownRectangle(context);
}
public override bool Contains(IGuiContext context, Point point)
{
return base.Contains(context, point) || IsOpen && GetContentRectangle(context).Contains(point);
}
protected override Size2 CalculateDesiredSize(IGuiContext context, Size2 availableSize)
{
var width = 0f;
var height = 0f;
foreach (var item in Items)
{
var itemSize = GetItemSize(context, availableSize, item);
if (itemSize.Width > width)
width = itemSize.Width;
if (itemSize.Height > height)
height = itemSize.Height;
}
return new Size2(width + ClipPadding.Size.Width, height + ClipPadding.Size.Height);
}
protected override void DrawBackground(IGuiContext context, IGuiRenderer renderer, float deltaSeconds)
{
base.DrawBackground(context, renderer, deltaSeconds);
if (IsOpen)
{
var dropDownRectangle = GetContentRectangle(context);
if (DropDownRegion != null)
renderer.DrawRegion(DropDownRegion, dropDownRectangle, DropDownColor);
else
renderer.FillRectangle(dropDownRectangle, DropDownColor);
}
}
protected override void DrawForeground(IGuiContext context, IGuiRenderer renderer, float deltaSeconds, TextInfo textInfo)
{
var selectedTextInfo = GetItemTextInfo(context, ClippingRectangle, SelectedItem, ClippingRectangle);
base.DrawForeground(context, renderer, deltaSeconds, selectedTextInfo);
if (IsOpen)
DrawItemList(context, renderer);
}
private Rectangle GetDropDownRectangle(IGuiContext context)
{
var dropDownRectangle = BoundingRectangle;
dropDownRectangle.Y = dropDownRectangle.Y + dropDownRectangle.Height;
var height = 0f;
foreach (var item in Items)
{
var itemSize = GetItemSize(context, new Size2(BoundingRectangle.Width, 100), item);
height += itemSize.Height;
}
dropDownRectangle.Height = (int)height;
return dropDownRectangle;
}
}
}
@@ -1,48 +1,35 @@
using System.ComponentModel;
using System;
using System.ComponentModel;
using Microsoft.Xna.Framework;
using MonoGame.Extended.BitmapFonts;
using MonoGame.Extended.Input.InputListeners;
using MonoGame.Extended.TextureAtlases;
using Newtonsoft.Json;
namespace MonoGame.Extended.Gui.Controls
{
public abstract class GuiControl : IMovable, ISizable
public abstract class GuiControl : GuiElement<GuiControl>, IMovable, ISizable, IRectangular
{
protected GuiControl()
: this(skin: null)
{
}
protected GuiControl(GuiSkin skin)
{
Skin = skin;
Color = Color.White;
TextColor = Color.White;
IsEnabled = true;
IsVisible = true;
Controls = new GuiControlCollection(this);
Origin = Vector2.Zero;
}
protected GuiControl(TextureRegion2D backgroundRegion)
: this()
{
BackgroundRegion = backgroundRegion;
var style = skin?.GetStyle(GetType());
style?.Apply(this);
}
[EditorBrowsable(EditorBrowsableState.Never)]
[JsonIgnore]
public GuiControl Parent { get; internal set; }
[EditorBrowsable(EditorBrowsableState.Never)]
[JsonIgnore]
public Rectangle BoundingRectangle
{
get
{
var offset = Vector2.Zero;
if (Parent != null)
offset = Parent.BoundingRectangle.Location.ToVector2();
return new Rectangle((offset + Position - Size * Origin).ToPoint(), (Point)Size);
}
}
public GuiSkin Skin { get; }
[EditorBrowsable(EditorBrowsableState.Never)]
public Thickness Margin { get; set; }
@@ -63,69 +50,76 @@ namespace MonoGame.Extended.Gui.Controls
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsFocused { get; set; }
public string Name { get; set; }
public Vector2 Position { get; set; }
public Vector2 Offset { get; set; }
public Vector2 Origin { get; set; }
public Color Color { get; set; }
public BitmapFont Font { get; set; }
public string Text { get; set; }
public Color TextColor { get; set; }
public Vector2 TextOffset { get; set; }
public GuiControlCollection Controls { get; }
public HorizontalAlignment HorizontalAlignment { get; set; } = HorizontalAlignment.Stretch;
public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Stretch;
public TextureRegion2D BackgroundRegion { get; set; }
public HorizontalAlignment HorizontalAlignment { get; set; } = HorizontalAlignment.Centre;
public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Centre;
public HorizontalAlignment HorizontalTextAlignment { get; set; } = HorizontalAlignment.Centre;
public VerticalAlignment VerticalTextAlignment { get; set; } = VerticalAlignment.Centre;
public Size2 Size { get; set; }
public float Width
private string _text;
public string Text
{
get { return Size.Width; }
set { Size = new Size2(value, Size.Height); }
get { return _text; }
set
{
if (_text != value)
{
_text = value;
OnTextChanged();
}
}
}
public float Height
protected virtual void OnTextChanged()
{
get { return Size.Height; }
set { Size = new Size2(Size.Width, value); }
TextChanged?.Invoke(this, EventArgs.Empty);
}
public event EventHandler TextChanged;
public Size2 GetDesiredSize(IGuiContext context, Size2 availableSize)
{
return CalculateDesiredSize(context, availableSize);
}
protected virtual Size2 CalculateDesiredSize(IGuiContext context, Size2 availableSize)
{
var minimumSize = Size2.Empty;
var fixedSize = Size;
var desiredSize = CalculateDesiredSize(context, availableSize);
var ninePatch = BackgroundRegion as NinePatchRegion2D;
if (ninePatch != null)
{
minimumSize.Width += ninePatch.LeftPadding + ninePatch.RightPadding;
minimumSize.Height += ninePatch.TopPadding + ninePatch.BottomPadding;
desiredSize.Width = Math.Max(desiredSize.Width, ninePatch.Padding.Size.Width);
desiredSize.Height = Math.Max(desiredSize.Height, ninePatch.Padding.Size.Height);
}
else if(BackgroundRegion != null)
else if (BackgroundRegion != null)
{
minimumSize.Width += BackgroundRegion.Width;
minimumSize.Height += BackgroundRegion.Height;
desiredSize.Width = Math.Max(desiredSize.Width, BackgroundRegion.Width);
desiredSize.Height = Math.Max(desiredSize.Height, BackgroundRegion.Height);
}
var font = Font ?? context.DefaultFont;
if (font != null && !string.IsNullOrEmpty(Text))
if (font != null && Text != null)
{
var textSize = font.MeasureString(Text);
minimumSize.Width += textSize.Width;
minimumSize.Height += textSize.Height;
desiredSize.Width += textSize.Width;
desiredSize.Height += textSize.Height;
}
desiredSize.Width = Math.Min(desiredSize.Width, availableSize.Width);
desiredSize.Height = Math.Min(desiredSize.Height, availableSize.Height);
// ReSharper disable CompareOfFloatsByEqualityOperator
return new Size2(Size.Width == 0 ? minimumSize.Width : Size.Width, Size.Height == 0 ? minimumSize.Height : Size.Height);
return new Size2(fixedSize.Width == 0 ? desiredSize.Width : fixedSize.Width, fixedSize.Height == 0 ? desiredSize.Height : fixedSize.Height);
// ReSharper restore CompareOfFloatsByEqualityOperator
}
protected virtual Size2 CalculateDesiredSize(IGuiContext context, Size2 availableSize)
{
return Size2.Empty;
}
private bool _isEnabled;
public bool IsEnabled
{
@@ -171,26 +165,31 @@ namespace MonoGame.Extended.Gui.Controls
HoverStyle?.Revert(this);
}
public void Draw(IGuiContext context, IGuiRenderer renderer, float deltaSeconds)
public virtual bool Contains(IGuiContext context, Point point)
{
DrawBackground(context, renderer, deltaSeconds);
DrawForeground(context, renderer, deltaSeconds, GetTextInfo(context, Text, BoundingRectangle, HorizontalAlignment.Centre, VerticalAlignment.Centre));
return BoundingRectangle.Contains(point);
}
protected TextInfo GetTextInfo(IGuiContext context, string text, Rectangle targetRectangle, HorizontalAlignment horizontalAlignment, VerticalAlignment verticalAlignment)
public override void Draw(IGuiContext context, IGuiRenderer renderer, float deltaSeconds)
{
DrawBackground(context, renderer, deltaSeconds);
DrawForeground(context, renderer, deltaSeconds, GetTextInfo(context, Text, BoundingRectangle, HorizontalTextAlignment, VerticalTextAlignment));
}
protected TextInfo GetTextInfo(IGuiContext context, string text, Rectangle targetRectangle, HorizontalAlignment horizontalAlignment, VerticalAlignment verticalAlignment, Rectangle? clippingRectangle = null)
{
var font = Font ?? context.DefaultFont;
var textSize = font.GetStringRectangle(text ?? string.Empty, Vector2.Zero).Size;
var destinationRectangle = GuiLayoutHelper.AlignRectangle(horizontalAlignment, verticalAlignment, textSize, targetRectangle);
var textPosition = destinationRectangle.Location.ToVector2();
var textInfo = new TextInfo(text, font, textPosition, textSize, TextColor, ClippingRectangle);
var textInfo = new TextInfo(text, font, textPosition, textSize, TextColor, clippingRectangle ?? ClippingRectangle);
return textInfo;
}
protected virtual void DrawBackground(IGuiContext context, IGuiRenderer renderer, float deltaSeconds)
{
renderer.DrawRegion(BackgroundRegion, BoundingRectangle, Color);
//renderer.DrawRectangle(BoundingRectangle, Color.Red);
if (BackgroundRegion != null)
renderer.DrawRegion(BackgroundRegion, BoundingRectangle, Color);
}
protected virtual void DrawForeground(IGuiContext context, IGuiRenderer renderer, float deltaSeconds, TextInfo textInfo)
@@ -203,7 +202,7 @@ namespace MonoGame.Extended.Gui.Controls
{
public TextInfo(string text, BitmapFont font, Vector2 position, Vector2 size, Color color, Rectangle? clippingRectangle)
{
Text = text;
Text = text ?? string.Empty;
Font = font;
Size = size;
Color = color;
@@ -1,87 +1,15 @@
using System.Collections;
using System.Collections.Generic;
namespace MonoGame.Extended.Gui.Controls
namespace MonoGame.Extended.Gui.Controls
{
public class GuiControlCollection : IList<GuiControl>
public class GuiControlCollection : GuiElementCollection<GuiControl, GuiControl>
{
public GuiControlCollection()
: base(null)
{
}
public GuiControlCollection(GuiControl parent)
: base(parent)
{
_parent = parent;
}
private readonly GuiControl _parent;
private readonly List<GuiControl> _list = new List<GuiControl>();
public IEnumerator<GuiControl> GetEnumerator()
{
return _list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable) _list).GetEnumerator();
}
public void Add(GuiControl item)
{
item.Parent = _parent;
_list.Add(item);
}
public void Clear()
{
foreach (var control in _list)
control.Parent = null;
_list.Clear();
}
public bool Contains(GuiControl item)
{
return _list.Contains(item);
}
public void CopyTo(GuiControl[] array, int arrayIndex)
{
_list.CopyTo(array, arrayIndex);
}
public bool Remove(GuiControl item)
{
item.Parent = null;
return _list.Remove(item);
}
public int Count => _list.Count;
public bool IsReadOnly => ((ICollection<GuiControl>) _list).IsReadOnly;
public int IndexOf(GuiControl item)
{
return _list.IndexOf(item);
}
public void Insert(int index, GuiControl item)
{
item.Parent = _parent;
_list.Insert(index, item);
}
public void RemoveAt(int index)
{
_list[index].Parent = null;
_list.RemoveAt(index);
}
public GuiControl this[int index]
{
get { return _list[index]; }
set { _list[index] = value; }
}
}
}
@@ -9,9 +9,9 @@ namespace MonoGame.Extended.Gui.Controls
: this(null)
{
}
public GuiDialog(TextureRegion2D backgroundRegion)
: base(backgroundRegion)
public GuiDialog(GuiSkin skin)
: base(skin)
{
HorizontalAlignment = HorizontalAlignment.Centre;
VerticalAlignment = VerticalAlignment.Centre;
@@ -7,10 +7,16 @@ namespace MonoGame.Extended.Gui.Controls
public GuiImage()
{
}
public GuiImage(TextureRegion2D backgroundRegion)
: base(backgroundRegion)
public GuiImage(GuiSkin skin)
: base(skin)
{
}
public GuiImage(GuiSkin skin, TextureRegion2D image)
: base(skin)
{
BackgroundRegion = image;
}
}
}
@@ -0,0 +1,177 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended.Input.InputListeners;
namespace MonoGame.Extended.Gui.Controls
{
public abstract class GuiItemsControl : GuiControl
{
protected GuiItemsControl()
: this(null)
{
}
protected GuiItemsControl(GuiSkin skin)
: base(skin)
{
}
private int _selectedIndex = -1;
public virtual int SelectedIndex
{
get { return _selectedIndex; }
set
{
if (_selectedIndex != value)
{
_selectedIndex = value;
SelectedIndexChanged?.Invoke(this, EventArgs.Empty);
}
}
}
public virtual List<object> Items { get; } = new List<object>();
public virtual Color SelectedTextColor { get; set; } = Color.White;
public virtual Color SelectedItemColor { get; set; } = Color.CornflowerBlue;
public virtual Thickness ItemPadding { get; set; } = new Thickness(4, 2);
public virtual string NameProperty { get; set; }
public event EventHandler SelectedIndexChanged;
protected int FirstIndex;
public object SelectedItem
{
get { return SelectedIndex >= 0 && SelectedIndex <= Items.Count - 1 ? Items[SelectedIndex] : null; }
set { SelectedIndex = Items.IndexOf(value); }
}
public override void OnKeyPressed(IGuiContext context, KeyboardEventArgs args)
{
base.OnKeyPressed(context, args);
if (args.Key == Keys.Down) ScrollDown();
if (args.Key == Keys.Up) ScrollUp();
}
public override void OnScrolled(int delta)
{
base.OnScrolled(delta);
if (delta < 0) ScrollDown();
if (delta > 0) ScrollUp();
}
private void ScrollDown()
{
if (SelectedIndex < Items.Count - 1)
SelectedIndex++;
}
private void ScrollUp()
{
if (SelectedIndex > 0)
SelectedIndex--;
}
public override void OnPointerDown(IGuiContext context, GuiPointerEventArgs args)
{
base.OnPointerDown(context, args);
var contentRectangle = GetContentRectangle(context);
for (var i = FirstIndex; i < Items.Count; i++)
{
var itemRectangle = GetItemRectangle(context, i - FirstIndex, contentRectangle);
if (itemRectangle.Contains(args.Position))
{
SelectedIndex = i;
OnItemClicked(context, args);
break;
}
}
}
protected virtual void OnItemClicked(IGuiContext context, GuiPointerEventArgs args) { }
protected TextInfo GetItemTextInfo(IGuiContext context, Rectangle itemRectangle, object item, Rectangle? clippingRectangle)
{
var textRectangle = new Rectangle(itemRectangle.X + ItemPadding.Left, itemRectangle.Y + ItemPadding.Top,
itemRectangle.Width - ItemPadding.Right, itemRectangle.Height - ItemPadding.Bottom);
var itemTextInfo = GetTextInfo(context, GetItemName(item), textRectangle, HorizontalAlignment.Left, VerticalAlignment.Top, clippingRectangle);
return itemTextInfo;
}
private string GetItemName(object item)
{
if (item != null)
{
if (NameProperty != null)
{
return item.GetType()
.GetRuntimeProperty(NameProperty)
.GetValue(item)
?.ToString() ?? string.Empty;
}
return item.ToString();
}
return string.Empty;
}
protected Rectangle GetItemRectangle(IGuiContext context, int index, Rectangle contentRectangle)
{
var font = Font ?? context.DefaultFont;
var itemHeight = font.LineHeight + ItemPadding.Top + ItemPadding.Bottom;
return new Rectangle(contentRectangle.X, contentRectangle.Y + itemHeight * index, contentRectangle.Width, itemHeight);
}
protected void ScrollIntoView(IGuiContext context)
{
var contentRectangle = GetContentRectangle(context);
var selectedItemRectangle = GetItemRectangle(context, SelectedIndex - FirstIndex, contentRectangle);
if (selectedItemRectangle.Bottom > ClippingRectangle.Bottom)
FirstIndex++;
if (selectedItemRectangle.Top < ClippingRectangle.Top && FirstIndex > 0)
FirstIndex--;
}
protected Size2 GetItemSize(IGuiContext context, Size2 availableSize, object item)
{
var text = GetItemName(item);
var textInfo = GetTextInfo(context, text, new Rectangle(0, 0, (int)availableSize.Width, (int)availableSize.Height), HorizontalAlignment.Left, VerticalAlignment.Top);
var itemWidth = textInfo.Size.X + ItemPadding.Size.Height;
var itemHeight = textInfo.Size.Y + ItemPadding.Size.Width;
return new Size2(itemWidth, itemHeight);
}
protected abstract Rectangle GetContentRectangle(IGuiContext context);
protected void DrawItemList(IGuiContext context, IGuiRenderer renderer)
{
var contentRectangle = GetContentRectangle(context);
for (var i = FirstIndex; i < Items.Count; i++)
{
var item = Items[i];
var itemRectangle = GetItemRectangle(context, i - FirstIndex, contentRectangle);
var itemTextInfo = GetItemTextInfo(context, itemRectangle, item, contentRectangle);
var textColor = i == SelectedIndex ? SelectedTextColor : itemTextInfo.Color;
if (SelectedIndex == i)
renderer.FillRectangle(itemRectangle, SelectedItemColor, contentRectangle);
renderer.DrawText(itemTextInfo.Font, itemTextInfo.Text, itemTextInfo.Position + TextOffset, textColor,
itemTextInfo.ClippingRectangle);
}
}
}
}
@@ -1,6 +1,4 @@
using MonoGame.Extended.TextureAtlases;
namespace MonoGame.Extended.Gui.Controls
namespace MonoGame.Extended.Gui.Controls
{
public class GuiLabel : GuiControl
{
@@ -8,15 +6,10 @@ namespace MonoGame.Extended.Gui.Controls
{
}
public GuiLabel(string text)
public GuiLabel(GuiSkin skin, string text = null)
: base(skin)
{
Text = text;
}
public GuiLabel(string text, TextureRegion2D backgroundRegion)
: base(backgroundRegion)
{
Text = text;
Text = text ?? string.Empty;
}
}
}
@@ -1,7 +1,4 @@
using Microsoft.Xna.Framework;
using MonoGame.Extended.TextureAtlases;
namespace MonoGame.Extended.Gui.Controls
namespace MonoGame.Extended.Gui.Controls
{
public abstract class GuiLayoutControl : GuiControl
{
@@ -10,10 +7,11 @@ namespace MonoGame.Extended.Gui.Controls
{
}
protected GuiLayoutControl(TextureRegion2D backgroundRegion)
: base(backgroundRegion)
protected GuiLayoutControl(GuiSkin skin)
: base(skin)
{
Origin = Vector2.Zero;
HorizontalAlignment = HorizontalAlignment.Stretch;
VerticalAlignment = VerticalAlignment.Stretch;
}
protected override Size2 CalculateDesiredSize(IGuiContext context, Size2 availableSize)
@@ -27,5 +25,11 @@ namespace MonoGame.Extended.Gui.Controls
{
GuiLayoutHelper.PlaceControl(context, control, x, y, width, height);
}
protected override void DrawBackground(IGuiContext context, IGuiRenderer renderer, float deltaSeconds)
{
if (BackgroundRegion != null)
renderer.DrawRegion(BackgroundRegion, BoundingRectangle, Color);
}
}
}
@@ -1,134 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended.Input.InputListeners;
using MonoGame.Extended.TextureAtlases;
namespace MonoGame.Extended.Gui.Controls
{
public class GuiListBox : GuiControl
public class GuiListBox : GuiItemsControl
{
public GuiListBox()
: this(null)
{
}
public GuiListBox(TextureRegion2D backgroundRegion)
: base(backgroundRegion)
public GuiListBox(GuiSkin skin)
: base(skin)
{
}
private int _selectedIndex = -1;
public int SelectedIndex
protected override Size2 CalculateDesiredSize(IGuiContext context, Size2 availableSize)
{
get { return _selectedIndex; }
set
var width = 0f;
var height = 0f;
foreach (var item in Items)
{
if (_selectedIndex != value)
{
_selectedIndex = value;
SelectedIndexChanged?.Invoke(this, EventArgs.Empty);
}
var itemSize = GetItemSize(context, availableSize, item);
if (itemSize.Width > width)
width = itemSize.Width;
height += itemSize.Height;
}
}
public List<object> Items { get; } = new List<object>();
public Color SelectedTextColor { get; set; } = Color.White;
public Thickness ItemPadding { get; set; } = new Thickness(4, 2);
public event EventHandler SelectedIndexChanged;
private int _firstIndex;
public object SelectedItem
{
get { return SelectedIndex >= 0 && SelectedIndex <= Items.Count - 1 ? Items[SelectedIndex] : null; }
set { SelectedIndex = Items.IndexOf(value); }
}
public override void OnKeyPressed(IGuiContext context, KeyboardEventArgs args)
{
base.OnKeyPressed(context, args);
if (args.Key == Keys.Down) ScrollDown();
if (args.Key == Keys.Up) ScrollUp();
}
public override void OnScrolled(int delta)
{
base.OnScrolled(delta);
if (delta < 0) ScrollDown();
if (delta > 0) ScrollUp();
}
private void ScrollDown()
{
if (SelectedIndex < Items.Count - 1)
SelectedIndex++;
}
private void ScrollUp()
{
if (SelectedIndex > 0)
SelectedIndex--;
}
public override void OnPointerDown(IGuiContext context, GuiPointerEventArgs args)
{
base.OnPointerDown(context, args);
for (var i = _firstIndex; i < Items.Count; i++)
{
var itemRectangle = GetItemRectangle(context, i - _firstIndex);
if (itemRectangle.Contains(args.Position))
{
SelectedIndex = i;
break;
}
}
return new Size2(width + ClipPadding.Size.Width, height + ClipPadding.Size.Height);
}
protected override void DrawForeground(IGuiContext context, IGuiRenderer renderer, float deltaSeconds, TextInfo textInfo)
{
ScrollIntoView(context);
for (var i = _firstIndex; i < Items.Count; i++)
{
var itemRectangle = GetItemRectangle(context, i - _firstIndex);
if (SelectedIndex == i)
renderer.FillRectangle(itemRectangle, Color.CornflowerBlue, ClippingRectangle);
var item = Items[i];
var textRectangle = new Rectangle(itemRectangle.X + ItemPadding.Left, itemRectangle.Y + ItemPadding.Top,
itemRectangle.Width - ItemPadding.Right, itemRectangle.Height - ItemPadding.Bottom);
var itemTextInfo = GetTextInfo(context, item?.ToString() ?? string.Empty, textRectangle, HorizontalAlignment.Left, VerticalAlignment.Top);
var textColor = i == SelectedIndex ? SelectedTextColor : itemTextInfo.Color;
renderer.DrawText(itemTextInfo.Font, itemTextInfo.Text, itemTextInfo.Position + TextOffset, textColor, itemTextInfo.ClippingRectangle);
}
DrawItemList(context, renderer);
}
private Rectangle GetItemRectangle(IGuiContext context, int index)
protected override Rectangle GetContentRectangle(IGuiContext context)
{
var font = Font ?? context.DefaultFont;
var contentRectangle = ClippingRectangle;
var itemHeight = font.LineHeight + ItemPadding.Top + ItemPadding.Bottom;
return new Rectangle(contentRectangle.X, contentRectangle.Y + itemHeight * index, contentRectangle.Width, itemHeight);
}
private void ScrollIntoView(IGuiContext context)
{
var selectedItemRectangle = GetItemRectangle(context, SelectedIndex - _firstIndex);
if (selectedItemRectangle.Bottom > ClippingRectangle.Bottom)
_firstIndex++;
if (selectedItemRectangle.Top < ClippingRectangle.Top && _firstIndex > 0)
_firstIndex--;
return ClippingRectangle;
}
}
}
@@ -10,8 +10,8 @@ namespace MonoGame.Extended.Gui.Controls
{
}
public GuiProgressBar(TextureRegion2D backgroundRegion)
: base(backgroundRegion)
public GuiProgressBar(GuiSkin skin)
: base(skin)
{
}
@@ -1,19 +1,19 @@
using System;
using Microsoft.Xna.Framework;
using MonoGame.Extended.TextureAtlases;
namespace MonoGame.Extended.Gui.Controls
{
public class GuiStackPanel : GuiLayoutControl
{
public GuiStackPanel()
: base(null)
: this(null)
{
}
public GuiStackPanel(TextureRegion2D backgroundRegion)
: base(backgroundRegion)
public GuiStackPanel(GuiSkin skin)
: base(skin)
{
HorizontalAlignment = HorizontalAlignment.Centre;
VerticalAlignment = VerticalAlignment.Centre;
}
public GuiOrientation Orientation { get; set; } = GuiOrientation.Vertical;
@@ -2,31 +2,33 @@ using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended.Input.InputListeners;
using MonoGame.Extended.TextureAtlases;
namespace MonoGame.Extended.Gui.Controls
{
public class GuiTextBox : GuiControl
{
public GuiTextBox()
: this(backgroundRegion: null, text: string.Empty)
: this(null)
{
}
public GuiTextBox(TextureRegion2D backgroundRegion)
: this(backgroundRegion: backgroundRegion, text: string.Empty)
public GuiTextBox(GuiSkin skin, string text = null)
: base(skin)
{
}
public GuiTextBox(TextureRegion2D backgroundRegion, string text)
: base(backgroundRegion)
{
Text = text;
Text = text ?? string.Empty;
}
public int SelectionStart { get; set; }
public char? PasswordCharacter { get; set; }
protected override void OnTextChanged()
{
if (SelectionStart > Text.Length)
SelectionStart = Text.Length;
base.OnTextChanged();
}
public override void OnPointerDown(IGuiContext context, GuiPointerEventArgs args)
{
base.OnPointerDown(context, args);
@@ -39,20 +41,23 @@ namespace MonoGame.Extended.Gui.Controls
{
var font = Font ?? context.DefaultFont;
var textInfo = GetTextInfo(context, Text, BoundingRectangle, HorizontalAlignment.Centre, VerticalAlignment.Centre);
var glyphs = font.GetGlyphs(textInfo.Text, textInfo.Position).ToArray();
var i = 0;
for (var i = 0; i < glyphs.Length; i++)
foreach (var glyph in font.GetGlyphs(textInfo.Text, textInfo.Position))
{
var glyph = glyphs[i];
var glyphMiddle = (int)(glyph.Position.X + glyph.FontRegion.Width * 0.5f);
var fontRegionWidth = glyph.FontRegion?.Width ?? 0;
var glyphMiddle = (int)(glyph.Position.X + fontRegionWidth * 0.5f);
if (position.X >= glyphMiddle)
{
i++;
continue;
}
return i;
}
return glyphs.Length;
return i;
}
public override void OnKeyPressed(IGuiContext context, KeyboardEventArgs args)
@@ -1,6 +1,5 @@
using System;
using System.Linq;
using MonoGame.Extended.TextureAtlases;
namespace MonoGame.Extended.Gui.Controls
{
@@ -11,8 +10,8 @@ namespace MonoGame.Extended.Gui.Controls
{
}
public GuiUniformGrid(TextureRegion2D backgroundRegion)
: base(backgroundRegion)
public GuiUniformGrid(GuiSkin skin)
: base(skin)
{
}
@@ -21,36 +20,27 @@ namespace MonoGame.Extended.Gui.Controls
protected override Size2 CalculateDesiredSize(IGuiContext context, Size2 availableSize)
{
var columns = CalculateColumns();
var rows = CalculateRows(columns, Controls.Count);
var maxCellWidth = availableSize.Width / columns;
var maxCellHeight = availableSize.Height / rows;
var sizes = Controls
.Select(control => GuiLayoutHelper.GetSizeWithMargins(control, context, new Size2(maxCellWidth, maxCellHeight)))
.ToArray();
var cellWidth = Math.Min(sizes.Max(s => s.Width), maxCellWidth);
var cellHeight = Math.Min(sizes.Max(s => s.Height), maxCellHeight);
return new Size2(cellWidth * columns, cellHeight * rows);
var desiredSize = CalculateGridInfo(context, availableSize).MinCellSize;
return desiredSize;
}
public override void Layout(IGuiContext context, RectangleF rectangle)
{
var columns = CalculateColumns();
var rows = CalculateRows(columns, Controls.Count);
var cellWidth = (float)(rectangle.Width / columns);
var cellHeight = (float)(rectangle.Height / rows);
var gridInfo = CalculateGridInfo(context, rectangle.Size);
var columnIndex = 0;
var rowIndex = 0;
var cellWidth = HorizontalAlignment == HorizontalAlignment.Stretch ? gridInfo.MaxCellWidth : gridInfo.MinCellWidth;
var cellHeight = VerticalAlignment == VerticalAlignment.Stretch ? gridInfo.MaxCellHeight : gridInfo.MinCellHeight;
foreach (var control in Controls)
{
var x = columnIndex * cellWidth;
var y = rowIndex * cellHeight;
PlaceControl(context, control, x, y, cellWidth, cellHeight);
columnIndex++;
if (columnIndex > columns - 1)
if (columnIndex > gridInfo.Columns - 1)
{
columnIndex = 0;
rowIndex++;
@@ -58,14 +48,38 @@ namespace MonoGame.Extended.Gui.Controls
}
}
private int CalculateRows(int columns, int controlCount)
private struct GridInfo
{
return Rows == 0 ? (int)Math.Ceiling((float)controlCount / columns) : Rows;
public float MinCellWidth;
public float MinCellHeight;
public float MaxCellWidth;
public float MaxCellHeight;
public float Columns;
public float Rows;
public Size2 MinCellSize => new Size2(MinCellWidth * Columns, MinCellHeight * Rows);
}
private int CalculateColumns()
private GridInfo CalculateGridInfo(IGuiContext context, Size2 availableSize)
{
return Columns == 0 ? (int)Math.Ceiling(Math.Sqrt(Controls.Count)) : Columns;
var columns = Columns == 0 ? (int)Math.Ceiling(Math.Sqrt(Controls.Count)) : Columns;
var rows = Rows == 0 ? (int)Math.Ceiling((float)Controls.Count / columns) : Rows;
var maxCellWidth = availableSize.Width / columns;
var maxCellHeight = availableSize.Height / rows;
var sizes = Controls
.Select(control => GuiLayoutHelper.GetSizeWithMargins(control, context, new Size2(maxCellWidth, maxCellHeight)))
.ToArray();
var maxControlWidth = sizes.Length == 0 ? 0 : sizes.Max(s => s.Width);
var maxControlHeight = sizes.Length == 0 ? 0 : sizes.Max(s => s.Height);
return new GridInfo
{
Columns = columns,
Rows = rows,
MinCellWidth = Math.Min(maxControlWidth, maxCellWidth),
MinCellHeight = Math.Min(maxControlHeight, maxCellHeight),
MaxCellWidth = maxCellWidth,
MaxCellHeight = maxCellHeight
};
}
}
}