diff --git a/Source/Demos/Demo.Features/Demos/GuiDemo.cs b/Source/Demos/Demo.Features/Demos/GuiDemo.cs index 4d3a818a..863cf7ba 100644 --- a/Source/Demos/Demo.Features/Demos/GuiDemo.cs +++ b/Source/Demos/Demo.Features/Demos/GuiDemo.cs @@ -39,7 +39,7 @@ namespace Demo.Features.Demos var titleScreen = GuiScreen.FromFile(Content, @"Content/title-screen.json"); var guiRenderer = new GuiSpriteBatchRenderer(GraphicsDevice, _camera.GetViewMatrix); - _guiSystem = new GuiSystem(_viewportAdapter, guiRenderer) { Screen = titleScreen }; + _guiSystem = new GuiSystem(_viewportAdapter, guiRenderer) { Screens = { titleScreen } }; var quitButton = titleScreen.FindControl("QuitButton"); quitButton.Clicked += (sender, args) => _game.Back(); diff --git a/Source/Demos/Demo.Features/Demos/GuiLayoutDemo.cs b/Source/Demos/Demo.Features/Demos/GuiLayoutDemo.cs index 184401e4..d0a3ddca 100644 --- a/Source/Demos/Demo.Features/Demos/GuiLayoutDemo.cs +++ b/Source/Demos/Demo.Features/Demos/GuiLayoutDemo.cs @@ -19,23 +19,15 @@ namespace Demo.Features.Demos { _texture = new Texture2D(graphicsDevice, 1, 1); _texture.SetData(new[] { new Color(Color.Black, 0.5f) }); - } - protected override void Dispose(bool isDisposing) - { - _texture.Dispose(); - } - - public override void Initialize() - { var backgroundRegion = new TextureRegion2D(_texture); - var uniformGrid = new GuiUniformGrid(backgroundRegion) + var uniformGrid = new GuiUniformGrid(skin) { Text = "Uniform Grid", Controls = { - new GuiCanvas(backgroundRegion) + new GuiCanvas(skin) { Text = "Canvas", Controls = @@ -45,7 +37,7 @@ namespace Demo.Features.Demos Skin.Create("white-button", c => { c.Text = "Child 3"; c.Position = new Vector2(100, 100); }), } }, - new GuiStackPanel(backgroundRegion) + new GuiStackPanel(skin) { Text = "Stack Panel (Vertical)", Orientation = GuiOrientation.Vertical, @@ -57,7 +49,7 @@ namespace Demo.Features.Demos Skin.Create("white-button", c => { c.Text = "Child 4"; }), } }, - new GuiStackPanel(backgroundRegion) + new GuiStackPanel(skin) { Text = "Stack Panel (Horizontal)", Orientation = GuiOrientation.Horizontal, @@ -69,7 +61,7 @@ namespace Demo.Features.Demos Skin.Create("white-button", c => { c.Text = "Child 3"; c.Width = 80; }), } }, - new GuiListBox(backgroundRegion) + new GuiListBox(skin) { Name = "DisplayModesListBox" } @@ -84,6 +76,11 @@ namespace Demo.Features.Demos var listBox = FindControl("DisplayModesListBox"); listBox.Items.AddRange(GraphicsAdapter.DefaultAdapter.SupportedDisplayModes.Select(i => $"{i.Width}x{i.Height}")); } + + protected override void Dispose(bool isDisposing) + { + _texture.Dispose(); + } } public class GuiLayoutDemo : DemoBase @@ -107,10 +104,10 @@ namespace Demo.Features.Demos _guiSystem = new GuiSystem(viewportAdapter, guiRenderer) { - Screen = new GuiLayoutScreen(skin, GraphicsDevice) + Screens = { new GuiLayoutScreen(skin, GraphicsDevice) } }; } - + protected override void UnloadContent() { } diff --git a/Source/Demos/Demo.Features/Demos/ParticlesDemo.cs b/Source/Demos/Demo.Features/Demos/ParticlesDemo.cs index efc03d35..8bf40d12 100644 --- a/Source/Demos/Demo.Features/Demos/ParticlesDemo.cs +++ b/Source/Demos/Demo.Features/Demos/ParticlesDemo.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; @@ -92,7 +93,7 @@ namespace Demo.Features.Demos { _particleEffect = new ParticleEffect { - Emitters = new[] + Emitters = new List { new ParticleEmitter(textureRegion, 500, TimeSpan.FromSeconds(2.5), Profile.Ring(150f, Profile.CircleRadiation.In)) @@ -104,7 +105,7 @@ namespace Demo.Features.Demos Rotation = new Range(-1f, 1f), Scale = new Range(3.0f, 4.0f) }, - Modifiers = new IModifier[] + Modifiers = new Modifier[] { new AgeModifier { diff --git a/Source/Demos/Demo.Features/GameMain.cs b/Source/Demos/Demo.Features/GameMain.cs index 20217f52..2d0d320a 100644 --- a/Source/Demos/Demo.Features/GameMain.cs +++ b/Source/Demos/Demo.Features/GameMain.cs @@ -81,7 +81,7 @@ namespace Demo.Features _guiSystem = new GuiSystem(ViewportAdapter, guiRenderer) { - Screen = new SelectDemoScreen(skin, _demos, LoadDemo) + Screens = { new SelectDemoScreen(skin, _demos, LoadDemo) } }; //LoadDemo(_demoIndex); @@ -114,11 +114,11 @@ namespace Demo.Features public void Back() { - if (_guiSystem.Screen.IsVisible) + if (_guiSystem.Screens[0].IsVisible) Exit(); IsMouseVisible = false; - _guiSystem.Screen.Show(); + _guiSystem.Screens[0].Show(); } protected override void Draw(GameTime gameTime) diff --git a/Source/Demos/Demo.Features/Screens/SelectDemoScreen.cs b/Source/Demos/Demo.Features/Screens/SelectDemoScreen.cs index 3b37ac30..236a585d 100644 --- a/Source/Demos/Demo.Features/Screens/SelectDemoScreen.cs +++ b/Source/Demos/Demo.Features/Screens/SelectDemoScreen.cs @@ -32,12 +32,9 @@ namespace Demo.Features.Screens //Controls.Add(canvas); //button.Clicked += (sender, args) => onNextDemo(); - } - public override void Initialize() - { var dialog = Skin.Create("dialog"); - var grid = new GuiUniformGrid {Columns = 3}; + var grid = new GuiUniformGrid { Columns = 3 }; foreach (var demo in _demos.Values.OrderBy(i => i.Name)) { diff --git a/Source/Demos/Demo.Platformer/Entities/EntityFactory.cs b/Source/Demos/Demo.Platformer/Entities/EntityFactory.cs index 373c22c6..b62db7c8 100644 --- a/Source/Demos/Demo.Platformer/Entities/EntityFactory.cs +++ b/Source/Demos/Demo.Platformer/Entities/EntityFactory.cs @@ -110,7 +110,7 @@ namespace Demo.Platformer.Entities // Quantity = new Range(32, 64), // Rotation = new Range(-MathHelper.TwoPi, MathHelper.TwoPi) // }, - // Modifiers = new IModifier[] + // Modifiers = new Modifier[] // { // new LinearGravityModifier { Direction = Vector2.UnitY, Strength = 350 }, // new OpacityFastFadeModifier(), diff --git a/Source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapImporter.cs b/Source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapImporter.cs index 64106357..f23bf21b 100644 --- a/Source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapImporter.cs +++ b/Source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapImporter.cs @@ -42,15 +42,14 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled { var tileset = map.Tilesets[i]; - if (string.IsNullOrWhiteSpace(tileset.Source)) - continue; - - var tilesetFilePath = GetTilesetFilePath(mapFilePath, tileset); - map.Tilesets[i] = ImportTileset(tilesetFilePath, tilesetSerializer, tileset); + if (!string.IsNullOrWhiteSpace(tileset.Source)) + { + var tilesetFilePath = GetTilesetFilePath(mapFilePath, tileset); + map.Tilesets[i] = ImportTileset(tilesetFilePath, tilesetSerializer, tileset); + } } map.Name = mapFilePath; - return map; } } diff --git a/Source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapProcessor.cs b/Source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapProcessor.cs index de8ee3fb..2f3e8a05 100644 --- a/Source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapProcessor.cs +++ b/Source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapProcessor.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.IO.Compression; @@ -21,54 +20,48 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled var previousWorkingDirectory = Environment.CurrentDirectory; var newWorkingDirectory = Path.GetDirectoryName(map.FilePath); + if (string.IsNullOrEmpty(newWorkingDirectory)) throw new NullReferenceException(); + Environment.CurrentDirectory = newWorkingDirectory; foreach (var layer in map.Layers) { var imageLayer = layer as TiledMapImageLayerContent; + if (imageLayer != null) { ContentLogger.Log($"Processing image layer '{imageLayer.Name}'"); - - var model = CreateImageLayerModel(imageLayer); - layer.Models = new[] - { - model - }; - + layer.Models = new[] { CreateImageLayerModel(imageLayer) }; ContentLogger.Log($"Processed image layer '{imageLayer.Name}'"); - continue; } var tileLayer = layer as TiledMapTileLayerContent; - if (tileLayer == null) - continue; - var data = tileLayer.Data; - var encodingType = data.Encoding ?? "xml"; - var compressionType = data.Compression ?? "xml"; + if (tileLayer != null) + { + var data = tileLayer.Data; + var encodingType = data.Encoding ?? "xml"; + var compressionType = data.Compression ?? "xml"; - ContentLogger.Log( - $"Processing tile layer '{tileLayer.Name}': Encoding: '{encodingType}', Compression: '{compressionType}'"); + ContentLogger.Log($"Processing tile layer '{tileLayer.Name}': Encoding: '{encodingType}', Compression: '{compressionType}'"); - var tileData = DecodeTileLayerData(encodingType, tileLayer); - var tiles = CreateTiles(map.RenderOrder, map.Width, map.Height, tileData); - tileLayer.Tiles = tiles; + var tileData = DecodeTileLayerData(encodingType, tileLayer); + var tiles = CreateTiles(map.RenderOrder, map.Width, map.Height, tileData); + tileLayer.Tiles = tiles; - layer.Models = CreateTileLayerModels(map, tileLayer.Name, tiles).ToArray(); + layer.Models = CreateTileLayerModels(map, tileLayer.Name, tiles).ToArray(); - ContentLogger.Log($"Processed tile layer '{tileLayer}': {tiles.Length} tiles"); + ContentLogger.Log($"Processed tile layer '{tileLayer}': {tiles.Length} tiles"); + } } Environment.CurrentDirectory = previousWorkingDirectory; - return map; } - private static List DecodeTileLayerData(string encodingType, - TiledMapTileLayerContent tileLayer) + private static List DecodeTileLayerData(string encodingType, TiledMapTileLayerContent tileLayer) { List tiles; @@ -90,8 +83,7 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled return tiles; } - private static TiledMapTile[] CreateTiles(TiledMapTileDrawOrderContent renderOrder, int mapWidth, int mapHeight, - List tileData) + private static TiledMapTile[] CreateTiles(TiledMapTileDrawOrderContent renderOrder, int mapWidth, int mapHeight, List tileData) { TiledMapTile[] tiles; @@ -116,9 +108,7 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled return tiles.ToArray(); } - private static IEnumerable CreateTilesInLeftDownOrder( - // ReSharper disable once SuggestBaseTypeForParameter - List tileLayerData, int mapWidth, int mapHeight) + private static IEnumerable CreateTilesInLeftDownOrder(List tileLayerData, int mapWidth, int mapHeight) { for (var y = 0; y < mapHeight; y++) for (var x = mapWidth - 1; x >= 0; x--) @@ -132,9 +122,7 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled } } - private static IEnumerable CreateTilesInLeftUpOrder( - // ReSharper disable once SuggestBaseTypeForParameter - List tileLayerData, int mapWidth, int mapHeight) + private static IEnumerable CreateTilesInLeftUpOrder(List tileLayerData, int mapWidth, int mapHeight) { for (var y = mapHeight - 1; y >= 0; y--) for (var x = mapWidth - 1; x >= 0; x--) @@ -148,9 +136,7 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled } } - private static IEnumerable CreateTilesInRightDownOrder( - // ReSharper disable once SuggestBaseTypeForParameter - List tileLayerData, int mapWidth, int mapHeight) + private static IEnumerable CreateTilesInRightDownOrder(List tileLayerData, int mapWidth, int mapHeight) { for (var y = 0; y < mapHeight; y++) for (var x = 0; x < mapWidth; x++) @@ -164,9 +150,7 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled } } - private static IEnumerable CreateTilesInRightUpOrder( - // ReSharper disable once SuggestBaseTypeForParameter - List tileLayerData, int mapWidth, int mapHeight) + private static IEnumerable CreateTilesInRightUpOrder(List tileLayerData, int mapWidth, int mapHeight) { for (var y = mapHeight - 1; y >= 0; y--) for (var x = mapWidth - 1; x >= 0; x--) @@ -180,8 +164,7 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled } } - private static List DecodeBase64Data(TiledMapTileLayerDataContent data, int width, - int height) + private static List DecodeBase64Data(TiledMapTileLayerDataContent data, int width, int height) { var tileList = new List(); var encodedData = data.Value.Trim(); @@ -242,8 +225,7 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled return model; } - private static IEnumerable CreateTileLayerModels(TiledMapContent map, - string layerName, IEnumerable tiles) + private static IEnumerable CreateTileLayerModels(TiledMapContent map, string layerName, IEnumerable tiles) { // the code below builds the geometry (triangles) for every tile // for every unique tileset used by a tile in a layer, we are going to end up with a different model (list of vertices and list of indices pair) diff --git a/Source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapWriter.cs b/Source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapWriter.cs index 0dab03ae..42b15704 100644 --- a/Source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapWriter.cs +++ b/Source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapWriter.cs @@ -204,19 +204,19 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled } } - public static TiledMapObjectType GetObjectType(TiledMapObjectContent @object) + public static TiledMapObjectType GetObjectType(TiledMapObjectContent content) { - if (@object.GlobalIdentifier > 0) + if (content.GlobalIdentifier > 0) return TiledMapObjectType.Tile; - if (@object.Ellipse != null) + if (content.Ellipse != null) return TiledMapObjectType.Ellipse; - if (@object.Polygon != null) + if (content.Polygon != null) return TiledMapObjectType.Polygon; // ReSharper disable once ConvertIfStatementToReturnStatement - if (@object.Polyline != null) + if (content.Polyline != null) return TiledMapObjectType.Polyline; return TiledMapObjectType.Rectangle; diff --git a/Source/MonoGame.Extended.Gui/Controls/GuiButton.cs b/Source/MonoGame.Extended.Gui/Controls/GuiButton.cs index b3a59303..920a5cd1 100644 --- a/Source/MonoGame.Extended.Gui/Controls/GuiButton.cs +++ b/Source/MonoGame.Extended.Gui/Controls/GuiButton.cs @@ -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; diff --git a/Source/MonoGame.Extended.Gui/Controls/GuiCanvas.cs b/Source/MonoGame.Extended.Gui/Controls/GuiCanvas.cs index 7099ae9c..88ab613d 100644 --- a/Source/MonoGame.Extended.Gui/Controls/GuiCanvas.cs +++ b/Source/MonoGame.Extended.Gui/Controls/GuiCanvas.cs @@ -9,8 +9,8 @@ namespace MonoGame.Extended.Gui.Controls { } - public GuiCanvas(TextureRegion2D backgroundRegion) - : base(backgroundRegion) + public GuiCanvas(GuiSkin skin) + : base(skin) { } diff --git a/Source/MonoGame.Extended.Gui/Controls/GuiCheckBox.cs b/Source/MonoGame.Extended.Gui/Controls/GuiCheckBox.cs index 78b02c26..3812e5a7 100644 --- a/Source/MonoGame.Extended.Gui/Controls/GuiCheckBox.cs +++ b/Source/MonoGame.Extended.Gui/Controls/GuiCheckBox.cs @@ -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) { } diff --git a/Source/MonoGame.Extended.Gui/Controls/GuiComboBox.cs b/Source/MonoGame.Extended.Gui/Controls/GuiComboBox.cs new file mode 100644 index 00000000..8beaea10 --- /dev/null +++ b/Source/MonoGame.Extended.Gui/Controls/GuiComboBox.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Gui/Controls/GuiControl.cs b/Source/MonoGame.Extended.Gui/Controls/GuiControl.cs index 9cfb13d2..6548c71e 100644 --- a/Source/MonoGame.Extended.Gui/Controls/GuiControl.cs +++ b/Source/MonoGame.Extended.Gui/Controls/GuiControl.cs @@ -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, 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; diff --git a/Source/MonoGame.Extended.Gui/Controls/GuiControlCollection.cs b/Source/MonoGame.Extended.Gui/Controls/GuiControlCollection.cs index 1d7784f8..6bf874f7 100644 --- a/Source/MonoGame.Extended.Gui/Controls/GuiControlCollection.cs +++ b/Source/MonoGame.Extended.Gui/Controls/GuiControlCollection.cs @@ -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 + public class GuiControlCollection : GuiElementCollection { public GuiControlCollection() + : base(null) { } public GuiControlCollection(GuiControl parent) + : base(parent) { - _parent = parent; - } - - private readonly GuiControl _parent; - private readonly List _list = new List(); - - public IEnumerator 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) _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; } } } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Gui/Controls/GuiDialog.cs b/Source/MonoGame.Extended.Gui/Controls/GuiDialog.cs index 40dc76d9..8152c8df 100644 --- a/Source/MonoGame.Extended.Gui/Controls/GuiDialog.cs +++ b/Source/MonoGame.Extended.Gui/Controls/GuiDialog.cs @@ -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; diff --git a/Source/MonoGame.Extended.Gui/Controls/GuiImage.cs b/Source/MonoGame.Extended.Gui/Controls/GuiImage.cs index 5bee4c2a..995fc00a 100644 --- a/Source/MonoGame.Extended.Gui/Controls/GuiImage.cs +++ b/Source/MonoGame.Extended.Gui/Controls/GuiImage.cs @@ -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; + } } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Gui/Controls/GuiItemsControl.cs b/Source/MonoGame.Extended.Gui/Controls/GuiItemsControl.cs new file mode 100644 index 00000000..c93b1047 --- /dev/null +++ b/Source/MonoGame.Extended.Gui/Controls/GuiItemsControl.cs @@ -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 Items { get; } = new List(); + 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); + } + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Gui/Controls/GuiLabel.cs b/Source/MonoGame.Extended.Gui/Controls/GuiLabel.cs index 81d7f191..073a6ada 100644 --- a/Source/MonoGame.Extended.Gui/Controls/GuiLabel.cs +++ b/Source/MonoGame.Extended.Gui/Controls/GuiLabel.cs @@ -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; } } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Gui/Controls/GuiLayoutControl.cs b/Source/MonoGame.Extended.Gui/Controls/GuiLayoutControl.cs index aa0dd2c1..176dc678 100644 --- a/Source/MonoGame.Extended.Gui/Controls/GuiLayoutControl.cs +++ b/Source/MonoGame.Extended.Gui/Controls/GuiLayoutControl.cs @@ -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); + } } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Gui/Controls/GuiListBox.cs b/Source/MonoGame.Extended.Gui/Controls/GuiListBox.cs index 9eb7465e..cdd088dc 100644 --- a/Source/MonoGame.Extended.Gui/Controls/GuiListBox.cs +++ b/Source/MonoGame.Extended.Gui/Controls/GuiListBox.cs @@ -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 Items { get; } = new List(); - 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; } } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Gui/Controls/GuiProgressBar.cs b/Source/MonoGame.Extended.Gui/Controls/GuiProgressBar.cs index 95a87d75..0f23bc63 100644 --- a/Source/MonoGame.Extended.Gui/Controls/GuiProgressBar.cs +++ b/Source/MonoGame.Extended.Gui/Controls/GuiProgressBar.cs @@ -10,8 +10,8 @@ namespace MonoGame.Extended.Gui.Controls { } - public GuiProgressBar(TextureRegion2D backgroundRegion) - : base(backgroundRegion) + public GuiProgressBar(GuiSkin skin) + : base(skin) { } diff --git a/Source/MonoGame.Extended.Gui/Controls/GuiStackPanel.cs b/Source/MonoGame.Extended.Gui/Controls/GuiStackPanel.cs index 43a7bbbf..685c0f07 100644 --- a/Source/MonoGame.Extended.Gui/Controls/GuiStackPanel.cs +++ b/Source/MonoGame.Extended.Gui/Controls/GuiStackPanel.cs @@ -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; diff --git a/Source/MonoGame.Extended.Gui/Controls/GuiTextBox.cs b/Source/MonoGame.Extended.Gui/Controls/GuiTextBox.cs index 8c4edc2c..997b5dfb 100644 --- a/Source/MonoGame.Extended.Gui/Controls/GuiTextBox.cs +++ b/Source/MonoGame.Extended.Gui/Controls/GuiTextBox.cs @@ -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) diff --git a/Source/MonoGame.Extended.Gui/Controls/GuiUniformGrid.cs b/Source/MonoGame.Extended.Gui/Controls/GuiUniformGrid.cs index f3f0debc..1496bf6c 100644 --- a/Source/MonoGame.Extended.Gui/Controls/GuiUniformGrid.cs +++ b/Source/MonoGame.Extended.Gui/Controls/GuiUniformGrid.cs @@ -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 + }; } } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Gui/GuiElement.cs b/Source/MonoGame.Extended.Gui/GuiElement.cs new file mode 100644 index 00000000..93782401 --- /dev/null +++ b/Source/MonoGame.Extended.Gui/GuiElement.cs @@ -0,0 +1,121 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using Microsoft.Xna.Framework; +using MonoGame.Extended.TextureAtlases; +using Newtonsoft.Json; + +namespace MonoGame.Extended.Gui +{ + public abstract class GuiElement + { + public string Name { get; set; } + public Vector2 Position { get; set; } + public Vector2 Origin { get; set; } + public Color Color { get; set; } + public TextureRegion2D BackgroundRegion { get; set; } + public Size2 Size { get; set; } + + private object _bindingContext; + public object BindingContext + { + get { return _bindingContext; } + set + { + if (_bindingContext != value) + { + _bindingContext = value; + OnBindingContextChanged(); + } + } + } + + private void OnBindingContextChanged() + { + UpdateElementBindings(); + + var viewModel = BindingContext as INotifyPropertyChanged; + + if (viewModel != null) + { + viewModel.PropertyChanged += (sender, args) => + { + UpdateElementBindings(); + }; + } + } + + private void UpdateElementBindings() + { + foreach (var binding in _bindings) + { + var sourceValue = _bindingContext + .GetType() + .GetRuntimeProperty(binding.ViewModelProperty) + .GetValue(_bindingContext); + + var targetProperty = binding.Element + .GetType() + .GetRuntimeProperty(binding.ViewProperty); + + targetProperty.SetValue(binding.Element, sourceValue); + } + } + + private struct Binding + { + public GuiElement Element; + public string ViewProperty; + public string ViewModelProperty; + } + + private static readonly List _bindings = new List(); + + public void SetBinding(string viewProperty, string viewModelProperty) + { + _bindings.Add(new Binding + { + Element = this, + ViewProperty = viewProperty, + ViewModelProperty = viewModelProperty + }); + } + + public float Width + { + get { return Size.Width; } + set { Size = new Size2(value, Size.Height); } + } + + public float Height + { + get { return Size.Height; } + set { Size = new Size2(Size.Width, value); } + } + + public abstract void Draw(IGuiContext context, IGuiRenderer renderer, float deltaSeconds); + } + + public abstract class GuiElement : GuiElement, IRectangular + where TParent : IRectangular + { + [EditorBrowsable(EditorBrowsableState.Never)] + [JsonIgnore] + public TParent 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); + } + } + } +} diff --git a/Source/MonoGame.Extended.Gui/GuiElementCollection.cs b/Source/MonoGame.Extended.Gui/GuiElementCollection.cs new file mode 100644 index 00000000..1d39ab60 --- /dev/null +++ b/Source/MonoGame.Extended.Gui/GuiElementCollection.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using MonoGame.Extended.Gui.Controls; + +namespace MonoGame.Extended.Gui +{ + public abstract class GuiElementCollection : IList + where TParent : class, IRectangular + where TChild : GuiElement + { + private readonly TParent _parent; + private readonly List _list = new List(); + + public Action ItemAdded { get; set; } + public Action ItemRemoved { get; set; } + + protected GuiElementCollection(TParent parent) + { + _parent = parent; + } + + public IEnumerator GetEnumerator() + { + return _list.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_list).GetEnumerator(); + } + + public void Add(TChild item) + { + item.Parent = _parent; + _list.Add(item); + ItemAdded?.Invoke(item); + } + + public void Clear() + { + foreach (var child in _list) + { + child.Parent = null; + ItemRemoved?.Invoke(child); + } + + _list.Clear(); + } + + public bool Contains(TChild item) + { + return _list.Contains(item); + } + + public void CopyTo(TChild[] array, int arrayIndex) + { + _list.CopyTo(array, arrayIndex); + } + + public bool Remove(TChild item) + { + item.Parent = null; + ItemRemoved?.Invoke(item); + return _list.Remove(item); + } + + public int Count => _list.Count; + + public bool IsReadOnly => ((ICollection)_list).IsReadOnly; + + public int IndexOf(TChild item) + { + return _list.IndexOf(item); + } + + public void Insert(int index, TChild item) + { + item.Parent = _parent; + _list.Insert(index, item); + ItemAdded?.Invoke(item); + } + + public void RemoveAt(int index) + { + var child = _list[index]; + child.Parent = null; + _list.RemoveAt(index); + ItemRemoved?.Invoke(child); + } + + public TChild this[int index] + { + get { return _list[index]; } + set { _list[index] = value; } + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Gui/GuiLayoutHelper.cs b/Source/MonoGame.Extended.Gui/GuiLayoutHelper.cs index 1738b645..725bd2a0 100644 --- a/Source/MonoGame.Extended.Gui/GuiLayoutHelper.cs +++ b/Source/MonoGame.Extended.Gui/GuiLayoutHelper.cs @@ -11,7 +11,7 @@ namespace MonoGame.Extended.Gui { public static Size2 GetSizeWithMargins(GuiControl control, IGuiContext context, Size2 availableSize) { - return control.GetDesiredSize(context, availableSize) + new Size2(control.Margin.Left + control.Margin.Right, control.Margin.Top + control.Margin.Bottom); + return control.GetDesiredSize(context, availableSize) + control.Margin.Size; } public static void PlaceControl(IGuiContext context, GuiControl control, float x, float y, float width, float height) @@ -28,6 +28,18 @@ namespace MonoGame.Extended.Gui layoutControl?.Layout(context, new RectangleF(x, y, width, height)); } + public static void PlaceWindow(IGuiContext context, GuiWindow window, float x, float y, float width, float height) + { + var rectangle = new Rectangle((int)x, (int)y, (int)width, (int)height); + var availableSize = new Size2(width, height); + var desiredSize = window.GetDesiredSize(context, availableSize); + var alignedRectangle = AlignRectangle(HorizontalAlignment.Centre, VerticalAlignment.Centre, desiredSize, rectangle); + + window.Position = new Vector2(alignedRectangle.X, alignedRectangle.Y); + window.Size = alignedRectangle.Size; + window.Layout(context, window.BoundingRectangle); + } + public static Rectangle AlignRectangle(HorizontalAlignment horizontalAlignment, VerticalAlignment verticalAlignment, Size2 size, Rectangle targetRectangle) { var x = GetHorizontalPosition(horizontalAlignment, size, targetRectangle); diff --git a/Source/MonoGame.Extended.Gui/GuiScreen.cs b/Source/MonoGame.Extended.Gui/GuiScreen.cs index 54237606..47091e39 100644 --- a/Source/MonoGame.Extended.Gui/GuiScreen.cs +++ b/Source/MonoGame.Extended.Gui/GuiScreen.cs @@ -9,26 +9,33 @@ using Newtonsoft.Json; namespace MonoGame.Extended.Gui { - public class GuiScreen : IDisposable + public class GuiScreen : GuiElement, IDisposable { public GuiScreen(GuiSkin skin) { Skin = skin; - Controls = new GuiControlCollection(); + Controls = new GuiControlCollection { ItemAdded = c => IsLayoutRequired = true }; + Windows = new GuiWindowCollection(this) { ItemAdded = w => IsLayoutRequired = true }; } [JsonProperty(Order = 1)] public GuiSkin Skin { get; set; } [JsonProperty(Order = 2)] - public GuiControlCollection Controls { get; } + public GuiControlCollection Controls { get; set; } - public float Width { get; private set; } - public float Height { get; private set; } - public Size2 Size => new Size2(Width, Height); + [JsonIgnore] + public GuiWindowCollection Windows { get; } + + public new float Width { get; private set; } + public new float Height { get; private set; } + public new Size2 Size => new Size2(Width, Height); public bool IsVisible { get; set; } = true; - public virtual void Initialize() { } + [JsonIgnore] + public bool IsLayoutRequired { get; private set; } + + public virtual void Update(GameTime gameTime) { } public void Show() { @@ -66,15 +73,23 @@ namespace MonoGame.Extended.Gui return null; } - public void Layout(IGuiContext context, RectangleF rectangle) + public void Layout(IGuiContext context, Rectangle rectangle) { Width = rectangle.Width; Height = rectangle.Height; - Initialize(); - foreach (var control in Controls) GuiLayoutHelper.PlaceControl(context, control, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); + + foreach (var window in Windows) + GuiLayoutHelper.PlaceWindow(context, window, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); + + IsLayoutRequired = false; + } + + public override void Draw(IGuiContext context, IGuiRenderer renderer, float deltaSeconds) + { + renderer.DrawRectangle(BoundingRectangle, Color.Green); } protected virtual void Dispose(bool isDisposing) diff --git a/Source/MonoGame.Extended.Gui/GuiScreenCollection.cs b/Source/MonoGame.Extended.Gui/GuiScreenCollection.cs new file mode 100644 index 00000000..10bb1479 --- /dev/null +++ b/Source/MonoGame.Extended.Gui/GuiScreenCollection.cs @@ -0,0 +1,10 @@ +namespace MonoGame.Extended.Gui +{ + public class GuiScreenCollection : GuiElementCollection + { + public GuiScreenCollection(GuiSystem parent) + : base(parent) + { + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Gui/GuiSkin.cs b/Source/MonoGame.Extended.Gui/GuiSkin.cs index f9592f9b..db24abc6 100644 --- a/Source/MonoGame.Extended.Gui/GuiSkin.cs +++ b/Source/MonoGame.Extended.Gui/GuiSkin.cs @@ -20,10 +20,9 @@ namespace MonoGame.Extended.Gui TextureAtlases = new List(); Fonts = new List(); NinePatches = new List(); - _styles = new KeyedCollection(s => s.Name); + Styles = new KeyedCollection(s => s.Name ?? s.TargetType.Name); } - private readonly KeyedCollection _styles; [JsonProperty(Order = 0)] public string Name { get; set; } @@ -44,24 +43,29 @@ namespace MonoGame.Extended.Gui public GuiCursor Cursor { get; set; } [JsonProperty(Order = 6)] - public ICollection Styles => _styles; + public KeyedCollection Styles { get; private set; } public GuiControlStyle GetStyle(string name) { - return _styles[name]; + return Styles[name]; } - public static GuiSkin FromFile(ContentManager contentManager, string path) + public GuiControlStyle GetStyle(Type controlType) + { + return Styles.FirstOrDefault(s => s.TargetType == controlType); + } + + public static GuiSkin FromFile(ContentManager contentManager, string path, params Type[] customControlTypes) { using (var stream = TitleContainer.OpenStream(path)) { - return FromStream(contentManager, stream); + return FromStream(contentManager, stream, customControlTypes); } } - public static GuiSkin FromStream(ContentManager contentManager, Stream stream) + public static GuiSkin FromStream(ContentManager contentManager, Stream stream, params Type[] customControlTypes) { - var skinSerializer = new GuiJsonSerializer(contentManager); + var skinSerializer = new GuiJsonSerializer(contentManager, customControlTypes); using (var streamReader = new StreamReader(stream)) using (var jsonReader = new JsonTextReader(streamReader)) diff --git a/Source/MonoGame.Extended.Gui/GuiSystem.cs b/Source/MonoGame.Extended.Gui/GuiSystem.cs index be5417ec..bc59fa92 100644 --- a/Source/MonoGame.Extended.Gui/GuiSystem.cs +++ b/Source/MonoGame.Extended.Gui/GuiSystem.cs @@ -13,7 +13,7 @@ namespace MonoGame.Extended.Gui Vector2 CursorPosition { get; } } - public class GuiSystem : IGuiContext + public class GuiSystem : IGuiContext, IRectangular { private readonly ViewportAdapter _viewportAdapter; private readonly IGuiRenderer _renderer; @@ -44,50 +44,72 @@ namespace MonoGame.Extended.Gui _keyboardListener = new KeyboardListener(); _keyboardListener.KeyTyped += (sender, args) => _focusedControl?.OnKeyTyped(this, args); _keyboardListener.KeyPressed += (sender, args) => _focusedControl?.OnKeyPressed(this, args); + + Screens = new GuiScreenCollection(this) { ItemAdded = InitializeScreen }; } - private GuiScreen _screen; - public GuiScreen Screen - { - get { return _screen; } - set - { - if (_screen != value) - { - _screen = value; - _screen?.Layout(this, _viewportAdapter.BoundingRectangle); - } - } - } + public GuiScreenCollection Screens { get; } + + public GuiScreen ActiveScreen => Screens.LastOrDefault(); + + public Rectangle BoundingRectangle => _viewportAdapter.BoundingRectangle; + public Vector2 CursorPosition { get; set; } - public BitmapFont DefaultFont => Screen?.Skin?.DefaultFont; + + public BitmapFont DefaultFont => ActiveScreen?.Skin?.DefaultFont; + + private void InitializeScreen(GuiScreen screen) + { + screen.Layout(this, BoundingRectangle); + } public void Update(GameTime gameTime) { _touchListener.Update(gameTime); _mouseListener.Update(gameTime); _keyboardListener.Update(gameTime); + + foreach (var screen in Screens) + { + if (screen.IsLayoutRequired) + screen.Layout(this, BoundingRectangle); + + screen.Update(gameTime); + } } public void Draw(GameTime gameTime) { - if(Screen == null || !Screen.IsVisible) - return; - var deltaSeconds = gameTime.GetElapsedSeconds(); _renderer.Begin(); - DrawChildren(Screen.Controls, deltaSeconds); + foreach (var screen in Screens) + { + if (screen.IsVisible) + { + DrawChildren(screen.Controls, deltaSeconds); + DrawWindows(screen.Windows, deltaSeconds); + } + } - var cursor = Screen.Skin?.Cursor; + var cursor = ActiveScreen.Skin?.Cursor; if (cursor != null) _renderer.DrawRegion(cursor.TextureRegion, CursorPosition, cursor.Color); _renderer.End(); } - + + private void DrawWindows(GuiWindowCollection windows, float deltaSeconds) + { + foreach (var window in windows) + { + window.Draw(this, _renderer, deltaSeconds); + DrawChildren(window.Controls, deltaSeconds); + } + } + private void DrawChildren(GuiControlCollection controls, float deltaSeconds) { foreach (var control in controls.Where(c => c.IsVisible)) @@ -99,19 +121,19 @@ namespace MonoGame.Extended.Gui private void OnPointerDown(GuiPointerEventArgs args) { - if (Screen == null || !Screen.IsVisible) + if (ActiveScreen == null || !ActiveScreen.IsVisible) return; - _preFocusedControl = FindControlAtPoint(Screen.Controls, args.Position); + _preFocusedControl = FindControlAtPoint(args.Position); _hoveredControl?.OnPointerDown(this, args); } private void OnPointerUp(GuiPointerEventArgs args) { - if (Screen == null || !Screen.IsVisible) + if (ActiveScreen == null || !ActiveScreen.IsVisible) return; - var postFocusedControl = FindControlAtPoint(Screen.Controls, args.Position); + var postFocusedControl = FindControlAtPoint(args.Position); if (_preFocusedControl == postFocusedControl) { @@ -137,10 +159,10 @@ namespace MonoGame.Extended.Gui { CursorPosition = args.Position.ToVector2(); - if (Screen == null || !Screen.IsVisible) + if (ActiveScreen == null || !ActiveScreen.IsVisible) return; - var hoveredControl = FindControlAtPoint(Screen.Controls, args.Position); + var hoveredControl = FindControlAtPoint(args.Position); if (_hoveredControl != hoveredControl) { @@ -149,8 +171,25 @@ namespace MonoGame.Extended.Gui _hoveredControl?.OnPointerEnter(this, args); } } - - private static GuiControl FindControlAtPoint(GuiControlCollection controls, Point point) + + private GuiControl FindControlAtPoint(Point point) + { + if (ActiveScreen == null || !ActiveScreen.IsVisible) + return null; + + //for(var i = Windows.Count - 1; i >= 0; i--) + //{ + // var window = Windows[i]; + // var control = FindControlAtPoint(window.Controls, point); + + // if (control != null) + // return control; + //} + + return FindControlAtPoint(ActiveScreen.Controls, point); + } + + private GuiControl FindControlAtPoint(GuiControlCollection controls, Point point) { var topMostControl = (GuiControl) null; @@ -160,7 +199,7 @@ namespace MonoGame.Extended.Gui if (control.IsVisible) { - if (topMostControl == null && control.BoundingRectangle.Contains(point)) + if (topMostControl == null && control.Contains(this, point)) topMostControl = control; if (control.Controls.Any()) diff --git a/Source/MonoGame.Extended.Gui/GuiWindow.cs b/Source/MonoGame.Extended.Gui/GuiWindow.cs new file mode 100644 index 00000000..ed7ed464 --- /dev/null +++ b/Source/MonoGame.Extended.Gui/GuiWindow.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.Xna.Framework; +using MonoGame.Extended.Gui.Controls; + +namespace MonoGame.Extended.Gui +{ + public class GuiWindow : GuiElement + { + public GuiWindow(GuiScreen parent) + { + Parent = parent; + } + + public GuiSkin Skin => Parent.Skin; + public GuiControlCollection Controls { get; } = new GuiControlCollection(); + + public void Show() + { + Parent.Windows.Add(this); + } + + public void Hide() + { + Parent.Windows.Remove(this); + } + + public override void Draw(IGuiContext context, IGuiRenderer renderer, float deltaSeconds) + { + renderer.FillRectangle(BoundingRectangle, Color.Magenta); + } + + public Size2 GetDesiredSize(IGuiContext context, Size2 availableSize) + { + return new Size2(Width, Height); + } + + public void Layout(IGuiContext context, RectangleF rectangle) + { + foreach (var control in Controls) + GuiLayoutHelper.PlaceControl(context, control, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Gui/GuiWindowCollection.cs b/Source/MonoGame.Extended.Gui/GuiWindowCollection.cs new file mode 100644 index 00000000..d79b4919 --- /dev/null +++ b/Source/MonoGame.Extended.Gui/GuiWindowCollection.cs @@ -0,0 +1,10 @@ +namespace MonoGame.Extended.Gui +{ + public class GuiWindowCollection : GuiElementCollection + { + public GuiWindowCollection(GuiScreen parent) + : base(parent) + { + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Gui/MonoGame.Extended.Gui.csproj b/Source/MonoGame.Extended.Gui/MonoGame.Extended.Gui.csproj index df64a51d..aa296744 100644 --- a/Source/MonoGame.Extended.Gui/MonoGame.Extended.Gui.csproj +++ b/Source/MonoGame.Extended.Gui/MonoGame.Extended.Gui.csproj @@ -38,27 +38,35 @@ + + + + + + + + diff --git a/Source/MonoGame.Extended.Gui/Serialization/GuiAlignmentConverter.cs b/Source/MonoGame.Extended.Gui/Serialization/GuiAlignmentConverter.cs new file mode 100644 index 00000000..4caf5aad --- /dev/null +++ b/Source/MonoGame.Extended.Gui/Serialization/GuiAlignmentConverter.cs @@ -0,0 +1,51 @@ +using System; +using Newtonsoft.Json; + +namespace MonoGame.Extended.Gui.Serialization +{ + /// + /// This converter handles the different spellings of Center. + /// + public class GuiAlignmentConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (objectType == typeof(HorizontalAlignment)) + { + var value = reader.Value.ToString(); + + if (value == "Center" || string.Equals(value, "Centre", StringComparison.OrdinalIgnoreCase)) + return HorizontalAlignment.Centre; + + HorizontalAlignment alignment; + + if (Enum.TryParse(value, true, out alignment)) + return alignment; + } + + if (objectType == typeof(VerticalAlignment)) + { + var value = reader.Value.ToString(); + + if (value == "Center" || string.Equals(value, "Centre", StringComparison.OrdinalIgnoreCase)) + return VerticalAlignment.Centre; + + VerticalAlignment alignment; + + if (Enum.TryParse(value, true, out alignment)) + return alignment; + } + + throw new InvalidOperationException($"Invalid value for '{objectType.Name}'"); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(HorizontalAlignment) || objectType == typeof(VerticalAlignment); + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Gui/Serialization/GuiControlStyleJsonConverter.cs b/Source/MonoGame.Extended.Gui/Serialization/GuiControlStyleJsonConverter.cs index b1313ea2..d3d7a8ac 100644 --- a/Source/MonoGame.Extended.Gui/Serialization/GuiControlStyleJsonConverter.cs +++ b/Source/MonoGame.Extended.Gui/Serialization/GuiControlStyleJsonConverter.cs @@ -15,12 +15,13 @@ namespace MonoGame.Extended.Gui.Serialization private const string _typeProperty = "Type"; private const string _nameProperty = "Name"; - public GuiControlStyleJsonConverter() + public GuiControlStyleJsonConverter(params Type[] customControlTypes) { _controlTypes = typeof(GuiControl) .GetTypeInfo() .Assembly .ExportedTypes + .Concat(customControlTypes) .Where(t => t.GetTypeInfo().IsSubclassOf(typeof(GuiControl))) .ToDictionary(t => t.Name); } diff --git a/Source/MonoGame.Extended.Gui/Serialization/GuiJsonSerializer.cs b/Source/MonoGame.Extended.Gui/Serialization/GuiJsonSerializer.cs index 477d244a..37d49124 100644 --- a/Source/MonoGame.Extended.Gui/Serialization/GuiJsonSerializer.cs +++ b/Source/MonoGame.Extended.Gui/Serialization/GuiJsonSerializer.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.Xna.Framework.Content; using MonoGame.Extended.BitmapFonts; using MonoGame.Extended.Serialization; @@ -7,7 +8,7 @@ namespace MonoGame.Extended.Gui.Serialization { public sealed class GuiJsonSerializer : JsonSerializer { - public GuiJsonSerializer(ContentManager contentManager) + public GuiJsonSerializer(ContentManager contentManager, params Type[] customControlTypes) { var textureRegionService = new GuiTextureRegionService(); Converters.Add(new Vector2JsonConverter()); @@ -15,10 +16,11 @@ namespace MonoGame.Extended.Gui.Serialization Converters.Add(new ColorJsonConverter()); Converters.Add(new ThicknessJsonConverter()); Converters.Add(new ContentManagerJsonConverter(contentManager, font => font.Name)); - Converters.Add(new GuiControlStyleJsonConverter()); + Converters.Add(new GuiControlStyleJsonConverter(customControlTypes)); Converters.Add(new GuiTextureAtlasJsonConverter(contentManager, textureRegionService)); Converters.Add(new GuiNinePatchRegion2DJsonConverter(textureRegionService)); Converters.Add(new TextureRegion2DJsonConverter(textureRegionService)); + Converters.Add(new GuiAlignmentConverter()); ContractResolver = new ShortNameJsonContractResolver(); Formatting = Formatting.Indented; } diff --git a/Source/MonoGame.Extended.Gui/Serialization/GuiTextureRegionService.cs b/Source/MonoGame.Extended.Gui/Serialization/GuiTextureRegionService.cs index 83fe8382..d400377c 100644 --- a/Source/MonoGame.Extended.Gui/Serialization/GuiTextureRegionService.cs +++ b/Source/MonoGame.Extended.Gui/Serialization/GuiTextureRegionService.cs @@ -7,6 +7,8 @@ namespace MonoGame.Extended.Gui.Serialization { public interface IGuiTextureRegionService : ITextureRegionService { + IList TextureAtlases { get; } + IList NinePatches { get; } } public class GuiTextureRegionService : TextureRegionService, IGuiTextureRegionService diff --git a/Source/MonoGame.Extended.Input/InputListeners/InputListener.cs b/Source/MonoGame.Extended.Input/InputListeners/InputListener.cs index 82c78bb4..63232951 100644 --- a/Source/MonoGame.Extended.Input/InputListeners/InputListener.cs +++ b/Source/MonoGame.Extended.Input/InputListeners/InputListener.cs @@ -8,8 +8,6 @@ namespace MonoGame.Extended.Input.InputListeners { } - //public ViewportAdapter ViewportAdapter { get; set; } - public abstract void Update(GameTime gameTime); } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Modifiers/AgeModifier.cs b/Source/MonoGame.Extended.Particles/Modifiers/AgeModifier.cs index 3852dd66..96a1fdcc 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/AgeModifier.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/AgeModifier.cs @@ -1,14 +1,17 @@ -using MonoGame.Extended.Particles.Modifiers.Interpolators; +using System.Collections.Generic; +using System.ComponentModel; +using MonoGame.Extended.Particles.Modifiers.Interpolators; namespace MonoGame.Extended.Particles.Modifiers { - public class AgeModifier : IModifier + public class AgeModifier : Modifier { - public IInterpolator[] Interpolators { get; set; } + [EditorBrowsable(EditorBrowsableState.Always)] + public List Interpolators { get; } = new List(); - public unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) + public override unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) { - var n = Interpolators.Length; + var n = Interpolators.Count; while (iterator.HasNext) { var particle = iterator.Next(); diff --git a/Source/MonoGame.Extended.Particles/Modifiers/Containers/CircleContainerModifier.cs b/Source/MonoGame.Extended.Particles/Modifiers/Containers/CircleContainerModifier.cs index f3890cce..30d28020 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/Containers/CircleContainerModifier.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/Containers/CircleContainerModifier.cs @@ -3,13 +3,13 @@ using Microsoft.Xna.Framework; namespace MonoGame.Extended.Particles.Modifiers.Containers { - public class CircleContainerModifier : IModifier + public class CircleContainerModifier : Modifier { public float Radius { get; set; } public bool Inside { get; set; } = true; public float RestitutionCoefficient { get; set; } = 1; - public unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) + public override unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) { var radiusSq = Radius*Radius; while (iterator.HasNext) diff --git a/Source/MonoGame.Extended.Particles/Modifiers/Containers/RectangleContainerModifier.cs b/Source/MonoGame.Extended.Particles/Modifiers/Containers/RectangleContainerModifier.cs index 375f5f73..a56e0721 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/Containers/RectangleContainerModifier.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/Containers/RectangleContainerModifier.cs @@ -2,13 +2,13 @@ namespace MonoGame.Extended.Particles.Modifiers.Containers { - public sealed class RectangleContainerModifier : IModifier + public sealed class RectangleContainerModifier : Modifier { public int Width { get; set; } public int Height { get; set; } public float RestitutionCoefficient { get; set; } = 1; - public unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) + public override unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) { while (iterator.HasNext) { diff --git a/Source/MonoGame.Extended.Particles/Modifiers/Containers/RectangleLoopContainerModifier.cs b/Source/MonoGame.Extended.Particles/Modifiers/Containers/RectangleLoopContainerModifier.cs index 62c44d7f..4da6efa9 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/Containers/RectangleLoopContainerModifier.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/Containers/RectangleLoopContainerModifier.cs @@ -2,12 +2,12 @@ namespace MonoGame.Extended.Particles.Modifiers.Containers { - public class RectangleLoopContainerModifier : IModifier + public class RectangleLoopContainerModifier : Modifier { public int Width { get; set; } public int Height { get; set; } - public unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) + public override unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) { while (iterator.HasNext) { diff --git a/Source/MonoGame.Extended.Particles/Modifiers/DragModifier.cs b/Source/MonoGame.Extended.Particles/Modifiers/DragModifier.cs index b75bdbf7..f2d6d091 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/DragModifier.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/DragModifier.cs @@ -2,12 +2,12 @@ namespace MonoGame.Extended.Particles.Modifiers { - public class DragModifier : IModifier + public class DragModifier : Modifier { public float DragCoefficient { get; set; } = 0.47f; public float Density { get; set; } = .5f; - public unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) + public override unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) { while (iterator.HasNext) { diff --git a/Source/MonoGame.Extended.Particles/Modifiers/IModifier.cs b/Source/MonoGame.Extended.Particles/Modifiers/IModifier.cs deleted file mode 100644 index 3123aa09..00000000 --- a/Source/MonoGame.Extended.Particles/Modifiers/IModifier.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MonoGame.Extended.Particles.Modifiers -{ - public interface IModifier - { - void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator); - } -} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/ColorInterpolator.cs b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/ColorInterpolator.cs index 32492b9a..f94c6897 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/ColorInterpolator.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/ColorInterpolator.cs @@ -1,41 +1,13 @@ -using System; -using MonoGame.Extended; - -namespace MonoGame.Extended.Particles.Modifiers.Interpolators +namespace MonoGame.Extended.Particles.Modifiers.Interpolators { /// - /// Defines a modifier which interpolates the color of a particle over the course of its lifetime. + /// Defines a modifier which interpolates the color of a particle over the course of its lifetime. /// - public sealed class ColorInterpolator : IInterpolator + public sealed class ColorInterpolator : Interpolator { - /// - /// Gets or sets the initial color of particles when they are released. - /// - public HslColor StartValue { get; set; } - - /// - /// Gets or sets the final color of particles when they are retired. - /// - public HslColor EndValue { get; set; } - - [Obsolete("Use StartValue instead")] - public HslColor InitialColor - { - get { return StartValue; } - set { StartValue = value; } - } - - [Obsolete("Use EndValue instead")] - public HslColor FinalColor - { - get { return EndValue; } - set { EndValue = value; } - } - - public unsafe void Update(float amount, Particle* particle) + public override unsafe void Update(float amount, Particle* particle) { particle->Color = HslColor.Lerp(StartValue, EndValue, amount); } - } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/HueInterpolator.cs b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/HueInterpolator.cs index b4706dd4..66be0672 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/HueInterpolator.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/HueInterpolator.cs @@ -2,18 +2,17 @@ namespace MonoGame.Extended.Particles.Modifiers.Interpolators { - public class HueInterpolator : IInterpolator + public class HueInterpolator : Interpolator { private float _delta; - public float StartValue { get; set; } - public float EndValue + public override float EndValue { get { return _delta + StartValue; } set { _delta = value - StartValue; } } - public unsafe void Update(float amount, Particle* particle) + public override unsafe void Update(float amount, Particle* particle) { particle->Color = new HslColor( amount*_delta + StartValue, diff --git a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/IInterpolator.cs b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/IInterpolator.cs deleted file mode 100644 index 7d8ded13..00000000 --- a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/IInterpolator.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MonoGame.Extended.Particles.Modifiers.Interpolators -{ - public interface IInterpolator - { - unsafe void Update(float amount, Particle* particle); - } - - public interface IInterpolator : IInterpolator - { - T StartValue { get; set; } - T EndValue { get; set; } - } -} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/Interpolator.cs b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/Interpolator.cs new file mode 100644 index 00000000..0ec98666 --- /dev/null +++ b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/Interpolator.cs @@ -0,0 +1,26 @@ +namespace MonoGame.Extended.Particles.Modifiers.Interpolators +{ + public abstract class Interpolator + { + protected Interpolator() + { + Name = GetType().Name; + } + + public string Name { get; set; } + public abstract unsafe void Update(float amount, Particle* particle); + } + + public abstract class Interpolator : Interpolator + { + /// + /// Gets or sets the intial value when the particles are created. + /// + public virtual T StartValue { get; set; } + + /// + /// Gets or sets the final value when the particles are retired. + /// + public virtual T EndValue { get; set; } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/OpacityInterpolator.cs b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/OpacityInterpolator.cs index 6442e004..3828ce41 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/OpacityInterpolator.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/OpacityInterpolator.cs @@ -1,18 +1,16 @@ namespace MonoGame.Extended.Particles.Modifiers.Interpolators { - public class OpacityInterpolator : IInterpolator + public class OpacityInterpolator : Interpolator { private float _delta; - public float StartValue { get; set; } - - public float EndValue + public override float EndValue { get { return _delta + StartValue; } set { _delta = value - StartValue; } } - public unsafe void Update(float amount, Particle* particle) + public override unsafe void Update(float amount, Particle* particle) { particle->Opacity = _delta*amount + StartValue; } diff --git a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/RotationInterpolator.cs b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/RotationInterpolator.cs index bfb161df..14b4fb0b 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/RotationInterpolator.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/RotationInterpolator.cs @@ -1,18 +1,16 @@ namespace MonoGame.Extended.Particles.Modifiers.Interpolators { - public class RotationInterpolator : IInterpolator + public class RotationInterpolator : Interpolator { private float _delta; - public float StartValue { get; set; } - - public float EndValue + public override float EndValue { get { return _delta + StartValue; } set { _delta = value - StartValue; } } - public unsafe void Update(float amount, Particle* particle) + public override unsafe void Update(float amount, Particle* particle) { particle->Rotation = amount*_delta + StartValue; } diff --git a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/ScaleInterpolator.cs b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/ScaleInterpolator.cs index 6595cc30..f8f1c14a 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/ScaleInterpolator.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/Interpolators/ScaleInterpolator.cs @@ -2,19 +2,17 @@ namespace MonoGame.Extended.Particles.Modifiers.Interpolators { - public class ScaleInterpolator : IInterpolator + public class ScaleInterpolator : Interpolator { private Vector2 _delta; - public Vector2 StartValue { get; set; } - - public Vector2 EndValue + public override Vector2 EndValue { get { return _delta + StartValue; } set { _delta = value - StartValue; } } - public unsafe void Update(float amount, Particle* particle) + public override unsafe void Update(float amount, Particle* particle) { particle->Scale = _delta*amount + StartValue; } diff --git a/Source/MonoGame.Extended.Particles/Modifiers/LinearGravityModifier.cs b/Source/MonoGame.Extended.Particles/Modifiers/LinearGravityModifier.cs index 24b91c48..e9ed216e 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/LinearGravityModifier.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/LinearGravityModifier.cs @@ -2,12 +2,12 @@ namespace MonoGame.Extended.Particles.Modifiers { - public class LinearGravityModifier : IModifier + public class LinearGravityModifier : Modifier { public Vector2 Direction { get; set; } public float Strength { get; set; } - public unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) + public override unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) { var vector = Direction*(Strength*elapsedSeconds); diff --git a/Source/MonoGame.Extended.Particles/Modifiers/Modifier.cs b/Source/MonoGame.Extended.Particles/Modifiers/Modifier.cs new file mode 100644 index 00000000..bd62d255 --- /dev/null +++ b/Source/MonoGame.Extended.Particles/Modifiers/Modifier.cs @@ -0,0 +1,18 @@ +namespace MonoGame.Extended.Particles.Modifiers +{ + public abstract class Modifier + { + protected Modifier() + { + Name = GetType().Name; + } + + public string Name { get; set; } + public abstract void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator); + + public override string ToString() + { + return $"{Name} [{GetType().Name}]"; + } + } +} \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Modifiers/OpacityFastFadeModifier.cs b/Source/MonoGame.Extended.Particles/Modifiers/OpacityFastFadeModifier.cs index d7fa6f66..9cc44d83 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/OpacityFastFadeModifier.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/OpacityFastFadeModifier.cs @@ -1,8 +1,8 @@ namespace MonoGame.Extended.Particles.Modifiers { - public sealed class OpacityFastFadeModifier : IModifier + public sealed class OpacityFastFadeModifier : Modifier { - public unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) + public override unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) { while (iterator.HasNext) { diff --git a/Source/MonoGame.Extended.Particles/Modifiers/RotationModifier.cs b/Source/MonoGame.Extended.Particles/Modifiers/RotationModifier.cs index f8ddc5bb..2b17058f 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/RotationModifier.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/RotationModifier.cs @@ -1,10 +1,10 @@ namespace MonoGame.Extended.Particles.Modifiers { - public class RotationModifier : IModifier + public class RotationModifier : Modifier { public float RotationRate { get; set; } - public unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) + public override unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) { var rotationRateDelta = RotationRate*elapsedSeconds; diff --git a/Source/MonoGame.Extended.Particles/Modifiers/VelocityColorModifier.cs b/Source/MonoGame.Extended.Particles/Modifiers/VelocityColorModifier.cs index d85277d6..ae9dc7b7 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/VelocityColorModifier.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/VelocityColorModifier.cs @@ -1,15 +1,14 @@ using System; -using MonoGame.Extended; namespace MonoGame.Extended.Particles.Modifiers { - public class VelocityColorModifier : IModifier + public class VelocityColorModifier : Modifier { public HslColor StationaryColor { get; set; } public HslColor VelocityColor { get; set; } public float VelocityThreshold { get; set; } - public unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) + public override unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) { var velocityThreshold2 = VelocityThreshold*VelocityThreshold; diff --git a/Source/MonoGame.Extended.Particles/Modifiers/VelocityModifier.cs b/Source/MonoGame.Extended.Particles/Modifiers/VelocityModifier.cs index 77ff6818..f089abcc 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/VelocityModifier.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/VelocityModifier.cs @@ -1,18 +1,19 @@ using System; +using System.Collections.Generic; using MonoGame.Extended.Particles.Modifiers.Interpolators; namespace MonoGame.Extended.Particles.Modifiers { - public class VelocityModifier + public class VelocityModifier : Modifier { - public IInterpolator[] Interpolators { get; set; } + public List Interpolators { get; set; } = new List(); public float VelocityThreshold { get; set; } - public unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) + public override unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) { var velocityThreshold2 = VelocityThreshold*VelocityThreshold; - var n = Interpolators.Length; + var n = Interpolators.Count; while (iterator.HasNext) { diff --git a/Source/MonoGame.Extended.Particles/Modifiers/VortexModifier.cs b/Source/MonoGame.Extended.Particles/Modifiers/VortexModifier.cs index 9f40a625..03a9999e 100644 --- a/Source/MonoGame.Extended.Particles/Modifiers/VortexModifier.cs +++ b/Source/MonoGame.Extended.Particles/Modifiers/VortexModifier.cs @@ -2,7 +2,7 @@ using Microsoft.Xna.Framework; namespace MonoGame.Extended.Particles.Modifiers { - public unsafe class VortexModifier : IModifier + public unsafe class VortexModifier : Modifier { // Note: not the real-life one private const float _gravConst = 100000f; @@ -11,7 +11,7 @@ namespace MonoGame.Extended.Particles.Modifiers public float Mass { get; set; } public float MaxSpeed { get; set; } - public void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) + public override void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) { while (iterator.HasNext) { diff --git a/Source/MonoGame.Extended.Particles/MonoGame.Extended.Particles.csproj b/Source/MonoGame.Extended.Particles/MonoGame.Extended.Particles.csproj index e2760329..33b6e3a4 100644 --- a/Source/MonoGame.Extended.Particles/MonoGame.Extended.Particles.csproj +++ b/Source/MonoGame.Extended.Particles/MonoGame.Extended.Particles.csproj @@ -50,10 +50,10 @@ - + - + diff --git a/Source/MonoGame.Extended.Particles/ParticleEffect.cs b/Source/MonoGame.Extended.Particles/ParticleEffect.cs index 5f891a30..253a60d0 100644 --- a/Source/MonoGame.Extended.Particles/ParticleEffect.cs +++ b/Source/MonoGame.Extended.Particles/ParticleEffect.cs @@ -1,4 +1,5 @@ -using System.IO; +using System.Collections.Generic; +using System.IO; using System.Linq; using Microsoft.Xna.Framework; using MonoGame.Extended.Particles.Serialization; @@ -7,16 +8,22 @@ using Newtonsoft.Json; namespace MonoGame.Extended.Particles { - public class ParticleEffect + public class ParticleEffect : Transform2D { - public ParticleEffect() + public ParticleEffect(string name = null, bool autoTrigger = true, float autoTriggerDelay = 0f) { - Emitters = new ParticleEmitter[0]; + Name = name; + AutoTrigger = autoTrigger; + AutoTriggerDelay = autoTriggerDelay; + Emitters = new List(); } - public string Name { get; set; } - public ParticleEmitter[] Emitters { get; set; } + private float _nextAutoTrigger; + public string Name { get; set; } + public bool AutoTrigger { get; set; } + public float AutoTriggerDelay { get; set; } + public List Emitters { get; set; } public int ActiveParticles => Emitters.Sum(t => t.ActiveParticles); public void FastForward(Vector2 position, float seconds, float triggerPeriod) @@ -40,31 +47,52 @@ namespace MonoGame.Extended.Particles public static ParticleEffect FromStream(ITextureRegionService textureRegionService, Stream stream) { - var skinSerializer = new ParticleJsonSerializer(textureRegionService); + var serializer = new ParticleJsonSerializer(textureRegionService); using (var streamReader = new StreamReader(stream)) using (var jsonReader = new JsonTextReader(streamReader)) { - return skinSerializer.Deserialize(jsonReader); + return serializer.Deserialize(jsonReader); } } public void Update(float elapsedSeconds) { - foreach (var e in Emitters) - e.Update(elapsedSeconds); + if (AutoTrigger) + { + _nextAutoTrigger -= elapsedSeconds; + + if (_nextAutoTrigger <= 0) + { + Trigger(); + _nextAutoTrigger = AutoTriggerDelay; + } + } + + for (var i = 0; i < Emitters.Count; i++) + Emitters[i].Update(elapsedSeconds); + } + + public void Trigger() + { + Trigger(Position); } public void Trigger(Vector2 position, float layerDepth = 0) { - foreach (var e in Emitters) - e.Trigger(position, layerDepth); + for (var i = 0; i < Emitters.Count; i++) + Emitters[i].Trigger(position, layerDepth); } public void Trigger(LineSegment line) { - foreach (var e in Emitters) - e.Trigger(line); + for (var i = 0; i < Emitters.Count; i++) + Emitters[i].Trigger(line); + } + + public override string ToString() + { + return Name; } } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/ParticleEmitter.cs b/Source/MonoGame.Extended.Particles/ParticleEmitter.cs index 9f04fb51..81108a51 100644 --- a/Source/MonoGame.Extended.Particles/ParticleEmitter.cs +++ b/Source/MonoGame.Extended.Particles/ParticleEmitter.cs @@ -1,51 +1,83 @@ using System; +using System.Collections.Generic; +using System.ComponentModel; using Microsoft.Xna.Framework; using MonoGame.Extended.Particles.Modifiers; using MonoGame.Extended.Particles.Profiles; using MonoGame.Extended.TextureAtlases; +using Newtonsoft.Json; namespace MonoGame.Extended.Particles { - public unsafe class ParticleEmitter : Transform2D, IDisposable + public unsafe class ParticleEmitter : IDisposable { private readonly FastRandom _random = new FastRandom(); - private readonly float _term; - internal readonly ParticleBuffer Buffer; - private bool _autoTrigger; - private float _totalSeconds; - public ParticleEmitter(TextureRegion2D textureRegion, int capacity, TimeSpan term, Profile profile, bool autoTrigger = true) + [JsonConstructor] + public ParticleEmitter(string name, TextureRegion2D textureRegion, int capacity, TimeSpan lifeSpan, Profile profile) { if (profile == null) throw new ArgumentNullException(nameof(profile)); - _term = (float) term.TotalSeconds; - _autoTrigger = autoTrigger; + _lifeSpanSeconds = (float)lifeSpan.TotalSeconds; + Name = name; TextureRegion = textureRegion; Buffer = new ParticleBuffer(capacity); Offset = Vector2.Zero; Profile = profile; - Modifiers = new IModifier[0]; + Modifiers = new List(); ModifierExecutionStrategy = ParticleModifierExecutionStrategy.Serial; Parameters = new ParticleReleaseParameters(); } - public int ActiveParticles => Buffer.Count; - public Vector2 Offset { get; set; } - public IModifier[] Modifiers { get; set; } - public ParticleModifierExecutionStrategy ModifierExecutionStrategy { get; set; } - - public Profile Profile { get; } - public ParticleReleaseParameters Parameters { get; set; } - public TextureRegion2D TextureRegion { get; set; } + public ParticleEmitter(TextureRegion2D textureRegion, int capacity, TimeSpan lifeSpan, Profile profile) + : this(null, textureRegion, capacity, lifeSpan, profile) + { + } public void Dispose() { Buffer.Dispose(); GC.SuppressFinalize(this); } + + ~ParticleEmitter() + { + Dispose(); + } + + public string Name { get; set; } + public int ActiveParticles => Buffer.Count; + public Vector2 Offset { get; set; } + public List Modifiers { get; } + public Profile Profile { get; set; } + public ParticleReleaseParameters Parameters { get; set; } + public TextureRegion2D TextureRegion { get; set; } + + [EditorBrowsable(EditorBrowsableState.Never)] + public ParticleModifierExecutionStrategy ModifierExecutionStrategy { get; set; } + + internal ParticleBuffer Buffer; + + public int Capacity + { + get { return Buffer.Size; } + set + { + var oldBuffer = Buffer; + oldBuffer.Dispose(); + Buffer = new ParticleBuffer(value); + } + } + + private float _lifeSpanSeconds; + public TimeSpan LifeSpan + { + get { return TimeSpan.FromSeconds(_lifeSpanSeconds); } + set { _lifeSpanSeconds = (float) value.TotalSeconds; } + } private void ReclaimExpiredParticles() { @@ -56,7 +88,7 @@ namespace MonoGame.Extended.Particles { var particle = iterator.Next(); - if (_totalSeconds - particle->Inception < _term) + if (_totalSeconds - particle->Inception < _lifeSpanSeconds) break; expired++; @@ -68,12 +100,6 @@ namespace MonoGame.Extended.Particles public bool Update(float elapsedSeconds) { - if (_autoTrigger) - { - Trigger(); - _autoTrigger = false; - } - _totalSeconds += elapsedSeconds; if (Buffer.Count == 0) @@ -86,19 +112,14 @@ namespace MonoGame.Extended.Particles while (iterator.HasNext) { var particle = iterator.Next(); - particle->Age = (_totalSeconds - particle->Inception)/_term; - particle->Position = particle->Position + particle->Velocity*elapsedSeconds; + particle->Age = (_totalSeconds - particle->Inception) / _lifeSpanSeconds; + particle->Position = particle->Position + particle->Velocity * elapsedSeconds; } ModifierExecutionStrategy.ExecuteModifiers(Modifiers, elapsedSeconds, iterator); return true; } - public void Trigger() - { - Trigger(Position); - } - public void Trigger(Vector2 position, float layerDepth = 0) { var numToRelease = _random.Next(Parameters.Quantity); @@ -112,7 +133,7 @@ namespace MonoGame.Extended.Particles for (var i = 0; i < numToRelease; i++) { - var offset = lineVector*_random.NextSingle(); + var offset = lineVector * _random.NextSingle(); Release(line.Origin + offset, 1); } } @@ -135,7 +156,7 @@ namespace MonoGame.Extended.Particles var speed = _random.NextSingle(Parameters.Speed); - particle->Velocity = heading*speed; + particle->Velocity = heading * speed; _random.NextColor(out particle->Color, Parameters.Color); @@ -148,9 +169,9 @@ namespace MonoGame.Extended.Particles } } - ~ParticleEmitter() + public override string ToString() { - Dispose(); + return Name; } } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/ParticleExtensions.cs b/Source/MonoGame.Extended.Particles/ParticleExtensions.cs index 6e012025..7566e8d0 100644 --- a/Source/MonoGame.Extended.Particles/ParticleExtensions.cs +++ b/Source/MonoGame.Extended.Particles/ParticleExtensions.cs @@ -8,8 +8,8 @@ namespace MonoGame.Extended.Particles { public static void Draw(this SpriteBatch spriteBatch, ParticleEffect effect) { - foreach (var emitter in effect.Emitters) - UnsafeDraw(spriteBatch, emitter); + for (var i = 0; i < effect.Emitters.Count; i++) + UnsafeDraw(spriteBatch, effect.Emitters[i]); } public static void Draw(this SpriteBatch spriteBatch, ParticleEmitter emitter) @@ -19,6 +19,9 @@ namespace MonoGame.Extended.Particles private static unsafe void UnsafeDraw(SpriteBatch spriteBatch, ParticleEmitter emitter) { + if(emitter.TextureRegion == null) + return; + var textureRegion = emitter.TextureRegion; var origin = new Vector2(textureRegion.Width/2f, textureRegion.Height/2f); var iterator = emitter.Buffer.Iterator; diff --git a/Source/MonoGame.Extended.Particles/ParticleModifierExecutionStrategy.cs b/Source/MonoGame.Extended.Particles/ParticleModifierExecutionStrategy.cs index a57ef039..20a4d51c 100644 --- a/Source/MonoGame.Extended.Particles/ParticleModifierExecutionStrategy.cs +++ b/Source/MonoGame.Extended.Particles/ParticleModifierExecutionStrategy.cs @@ -11,25 +11,33 @@ namespace MonoGame.Extended.Particles public static readonly ParticleModifierExecutionStrategy Serial = new SerialModifierExecutionStrategy(); public static readonly ParticleModifierExecutionStrategy Parallel = new ParallelModifierExecutionStrategy(); - internal abstract void ExecuteModifiers(IEnumerable modifiers, float elapsedSeconds, ParticleBuffer.ParticleIterator iterator); + internal abstract void ExecuteModifiers(List modifiers, float elapsedSeconds, ParticleBuffer.ParticleIterator iterator); internal class SerialModifierExecutionStrategy : ParticleModifierExecutionStrategy { - internal override void ExecuteModifiers(IEnumerable modifiers, float elapsedSeconds, - ParticleBuffer.ParticleIterator iterator) + internal override void ExecuteModifiers(List modifiers, float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) { - foreach (var modifier in modifiers) - modifier.Update(elapsedSeconds, iterator.Reset()); + for (var i = 0; i < modifiers.Count; i++) + modifiers[i].Update(elapsedSeconds, iterator.Reset()); + } + + public override string ToString() + { + return nameof(Serial); } } internal class ParallelModifierExecutionStrategy : ParticleModifierExecutionStrategy { - internal override void ExecuteModifiers(IEnumerable modifiers, float elapsedSeconds, - ParticleBuffer.ParticleIterator iterator) + internal override void ExecuteModifiers(List modifiers, float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) { TPL.Parallel.ForEach(modifiers, modifier => modifier.Update(elapsedSeconds, iterator.Reset())); } + + public override string ToString() + { + return nameof(Parallel); + } } public static ParticleModifierExecutionStrategy Parse(string value) diff --git a/Source/MonoGame.Extended.Particles/Serialization/InterpolatorJsonConverter.cs b/Source/MonoGame.Extended.Particles/Serialization/InterpolatorJsonConverter.cs index 44422d5b..21bd7fc4 100644 --- a/Source/MonoGame.Extended.Particles/Serialization/InterpolatorJsonConverter.cs +++ b/Source/MonoGame.Extended.Particles/Serialization/InterpolatorJsonConverter.cs @@ -6,7 +6,7 @@ using MonoGame.Extended.Serialization; namespace MonoGame.Extended.Particles.Serialization { - public class InterpolatorJsonConverter : BaseTypeJsonConverter + public class InterpolatorJsonConverter : BaseTypeJsonConverter { public InterpolatorJsonConverter() : base(GetSupportedTypes(), "Interpolator") @@ -15,11 +15,11 @@ namespace MonoGame.Extended.Particles.Serialization private static IEnumerable GetSupportedTypes() { - return typeof(IInterpolator) + return typeof(Interpolator) .GetTypeInfo() .Assembly .DefinedTypes - .Where(type => typeof(IInterpolator).GetTypeInfo().IsAssignableFrom(type) && !type.IsAbstract); + .Where(type => typeof(Interpolator).GetTypeInfo().IsAssignableFrom(type) && !type.IsAbstract); } } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Serialization/ModifierExecutionStrategyJsonConverter.cs b/Source/MonoGame.Extended.Particles/Serialization/ModifierExecutionStrategyJsonConverter.cs index da66a4c5..005f8416 100644 --- a/Source/MonoGame.Extended.Particles/Serialization/ModifierExecutionStrategyJsonConverter.cs +++ b/Source/MonoGame.Extended.Particles/Serialization/ModifierExecutionStrategyJsonConverter.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -8,6 +9,7 @@ namespace MonoGame.Extended.Particles.Serialization { public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { + writer.WriteValue(value.ToString()); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) @@ -18,7 +20,8 @@ namespace MonoGame.Extended.Particles.Serialization public override bool CanConvert(Type objectType) { - return objectType == typeof(ParticleModifierExecutionStrategy); + return objectType == typeof(ParticleModifierExecutionStrategy) + || objectType.GetTypeInfo().BaseType == typeof(ParticleModifierExecutionStrategy); } } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Serialization/ModifierJsonConverter.cs b/Source/MonoGame.Extended.Particles/Serialization/ModifierJsonConverter.cs index ab3bc670..5985e40d 100644 --- a/Source/MonoGame.Extended.Particles/Serialization/ModifierJsonConverter.cs +++ b/Source/MonoGame.Extended.Particles/Serialization/ModifierJsonConverter.cs @@ -6,7 +6,7 @@ using MonoGame.Extended.Serialization; namespace MonoGame.Extended.Particles.Serialization { - public class ModifierJsonConverter : BaseTypeJsonConverter + public class ModifierJsonConverter : BaseTypeJsonConverter { public ModifierJsonConverter() : base(GetSupportedTypes(), "Modifier") @@ -15,11 +15,11 @@ namespace MonoGame.Extended.Particles.Serialization private static IEnumerable GetSupportedTypes() { - return typeof(IModifier) + return typeof(Modifier) .GetTypeInfo() .Assembly .DefinedTypes - .Where(type => typeof(IModifier).GetTypeInfo().IsAssignableFrom(type) && !type.IsAbstract); + .Where(type => typeof(Modifier).GetTypeInfo().IsAssignableFrom(type) && !type.IsAbstract); } } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Particles/Serialization/ParticleJsonSerializer.cs b/Source/MonoGame.Extended.Particles/Serialization/ParticleJsonSerializer.cs index ed15965c..39e7d8c6 100644 --- a/Source/MonoGame.Extended.Particles/Serialization/ParticleJsonSerializer.cs +++ b/Source/MonoGame.Extended.Particles/Serialization/ParticleJsonSerializer.cs @@ -1,12 +1,11 @@ using MonoGame.Extended.Serialization; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace MonoGame.Extended.Particles.Serialization { public sealed class ParticleJsonSerializer : JsonSerializer { - public ParticleJsonSerializer(ITextureRegionService textureRegionService) + public ParticleJsonSerializer(ITextureRegionService textureRegionService, NullValueHandling nullValueHandling = NullValueHandling.Include) { Converters.Add(new Vector2JsonConverter()); Converters.Add(new Size2JsonConverter()); @@ -18,9 +17,11 @@ namespace MonoGame.Extended.Particles.Serialization Converters.Add(new TimeSpanJsonConverter()); Converters.Add(new RangeJsonConverter()); Converters.Add(new RangeJsonConverter()); + Converters.Add(new RangeJsonConverter()); Converters.Add(new HslColorJsonConverter()); Converters.Add(new ModifierExecutionStrategyJsonConverter()); ContractResolver = new ShortNameJsonContractResolver(); + NullValueHandling = nullValueHandling; Formatting = Formatting.Indented; } } diff --git a/Source/MonoGame.Extended.Particles/Serialization/TimeSpanJsonConverter.cs b/Source/MonoGame.Extended.Particles/Serialization/TimeSpanJsonConverter.cs index e6029152..a2159129 100644 --- a/Source/MonoGame.Extended.Particles/Serialization/TimeSpanJsonConverter.cs +++ b/Source/MonoGame.Extended.Particles/Serialization/TimeSpanJsonConverter.cs @@ -7,6 +7,8 @@ namespace MonoGame.Extended.Particles.Serialization { public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { + var timeSpan = (TimeSpan) value; + writer.WriteValue(timeSpan.TotalSeconds); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) diff --git a/Source/MonoGame.Extended.SceneGraphs/SceneEntityCollection.cs b/Source/MonoGame.Extended.SceneGraphs/SceneEntityCollection.cs index 4010ad17..88c44dce 100644 --- a/Source/MonoGame.Extended.SceneGraphs/SceneEntityCollection.cs +++ b/Source/MonoGame.Extended.SceneGraphs/SceneEntityCollection.cs @@ -2,7 +2,7 @@ namespace MonoGame.Extended.SceneGraphs { - public class SceneEntityCollection : Collection + public class SceneEntityCollection : Collection { } } \ No newline at end of file diff --git a/Source/MonoGame.Extended.Tiled/TiledMapTileObject.cs b/Source/MonoGame.Extended.Tiled/TiledMapTileObject.cs index 475a02f6..18278206 100644 --- a/Source/MonoGame.Extended.Tiled/TiledMapTileObject.cs +++ b/Source/MonoGame.Extended.Tiled/TiledMapTileObject.cs @@ -1,6 +1,5 @@ using System.Linq; using Microsoft.Xna.Framework.Content; -using MonoGame.Extended.Shapes; namespace MonoGame.Extended.Tiled { @@ -15,6 +14,7 @@ namespace MonoGame.Extended.Tiled var tile = new TiledMapTile(globalTileIdentifierWithFlags); var tileset = map.GetTilesetByTileGlobalIdentifier(tile.GlobalIdentifier); var localTileIdentifier = tile.GlobalIdentifier - tileset.FirstGlobalIdentifier; + TilesetTile = tileset.Tiles.FirstOrDefault(x => x.LocalTileIdentifier == localTileIdentifier); } } diff --git a/Source/MonoGame.Extended.Tiled/TiledMapTilesetAnimatedTile.cs b/Source/MonoGame.Extended.Tiled/TiledMapTilesetAnimatedTile.cs index b4a1cdaf..0e1b9df3 100644 --- a/Source/MonoGame.Extended.Tiled/TiledMapTilesetAnimatedTile.cs +++ b/Source/MonoGame.Extended.Tiled/TiledMapTilesetAnimatedTile.cs @@ -17,7 +17,6 @@ namespace MonoGame.Extended.Tiled public ReadOnlyCollection AnimationFrames { get; } public TiledMapTilesetTileAnimationFrame CurrentAnimationFrame { get; private set; } - // ReSharper disable once SuggestBaseTypeForParameter internal TiledMapTilesetAnimatedTile(TiledMapTileset tileset, ContentReader input, int localTileIdentifier, int animationFramesCount) : base(localTileIdentifier,input) { diff --git a/Source/MonoGame.Extended/HslColor.cs b/Source/MonoGame.Extended/HslColor.cs index 7d16a57a..b65c2058 100644 --- a/Source/MonoGame.Extended/HslColor.cs +++ b/Source/MonoGame.Extended/HslColor.cs @@ -106,6 +106,11 @@ namespace MonoGame.Extended return new HslColor(a.H + b.H, a.S + b.S, a.L + b.L); } + public static implicit operator HslColor(string value) + { + return Parse(value); + } + public int CompareTo(HslColor other) { // ReSharper disable ImpureMethodCallOnReadonlyValueField diff --git a/Source/MonoGame.Extended/IRectangular.cs b/Source/MonoGame.Extended/IRectangular.cs index 719d6757..a8b1eab8 100644 --- a/Source/MonoGame.Extended/IRectangular.cs +++ b/Source/MonoGame.Extended/IRectangular.cs @@ -1,8 +1,14 @@ - +using Microsoft.Xna.Framework; + namespace MonoGame.Extended { public interface IRectangular + { + Rectangle BoundingRectangle { get; } + } + + public interface IRectangularF { RectangleF BoundingRectangle { get; } } -} \ No newline at end of file +} diff --git a/Source/MonoGame.Extended/Point2.cs b/Source/MonoGame.Extended/Point2.cs index 10cb54eb..735fff25 100644 --- a/Source/MonoGame.Extended/Point2.cs +++ b/Source/MonoGame.Extended/Point2.cs @@ -350,6 +350,20 @@ namespace MonoGame.Extended return new Point2(vector.X, vector.Y); } + + /// + /// Performs an implicit conversion from a to a . + /// + /// The point. + /// + /// The resulting . + /// + public static implicit operator Point2(Point point) + { + return new Point2(point.X, point.Y); + } + + /// /// Returns a that represents this . /// diff --git a/Source/MonoGame.Extended/Range.cs b/Source/MonoGame.Extended/Range.cs index f1a09ab8..54022339 100644 --- a/Source/MonoGame.Extended/Range.cs +++ b/Source/MonoGame.Extended/Range.cs @@ -9,13 +9,18 @@ namespace MonoGame.Extended { public Range(T min, T max) { - if ((min.CompareTo(max) > 0) || (max.CompareTo(min) < 0)) + if (min.CompareTo(max) > 0 || max.CompareTo(min) < 0) throw new ArgumentException("Min has to be smaller than or equal to max."); Min = min; Max = max; } + public Range(T value) + : this(value, value) + { + } + /// /// Gets the minium value of the . /// diff --git a/Source/MonoGame.Extended/Serialization/BaseTypeJsonConverter.cs b/Source/MonoGame.Extended/Serialization/BaseTypeJsonConverter.cs index 24fbf3e7..18b8f0f4 100644 --- a/Source/MonoGame.Extended/Serialization/BaseTypeJsonConverter.cs +++ b/Source/MonoGame.Extended/Serialization/BaseTypeJsonConverter.cs @@ -4,23 +4,42 @@ using System.Linq; using System.Reflection; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; namespace MonoGame.Extended.Serialization { public abstract class BaseTypeJsonConverter : JsonConverter { private readonly string _suffix; - private readonly Dictionary _baseTypes; + private readonly Dictionary _namesToTypes; + private readonly Dictionary _typesToNames; + private readonly CamelCaseNamingStrategy _namingStrategy = new CamelCaseNamingStrategy(); protected BaseTypeJsonConverter(IEnumerable supportedTypes, string suffix) { _suffix = suffix; - _baseTypes = supportedTypes + _namesToTypes = supportedTypes .ToDictionary(t => TrimSuffix(t.Name, suffix), t => t.AsType(), StringComparer.OrdinalIgnoreCase); + _typesToNames = _namesToTypes.ToDictionary(i => i.Value, i => i.Key); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { + var type = value.GetType(); + var properties = type.GetRuntimeProperties(); + + writer.WriteStartObject(); + writer.WritePropertyName("type"); + writer.WriteValue(_typesToNames[type]); + + foreach (var property in properties.Where(p => p.CanWrite)) + { + var propertyName = _namingStrategy.GetPropertyName(property.Name, false); + writer.WritePropertyName(propertyName); + serializer.Serialize(writer, property.GetValue(value)); + } + + writer.WriteEndObject(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) @@ -29,14 +48,22 @@ namespace MonoGame.Extended.Serialization var key = jObject.GetValue("type", StringComparison.OrdinalIgnoreCase).ToObject(); Type type; - if (_baseTypes.TryGetValue(key, out type)) - return jObject.ToObject(type, serializer); + if (_namesToTypes.TryGetValue(key, out type)) + { + serializer.Converters.Remove(this); + var value = jObject.ToObject(type, serializer); + serializer.Converters.Add(this); + return value; + } throw new InvalidOperationException($"Unknown {_suffix} type '{key}'"); } public override bool CanConvert(Type objectType) { + if (_namesToTypes.ContainsValue(objectType)) + return true; + return objectType == typeof(T); } diff --git a/Source/MonoGame.Extended/Serialization/HslColorJsonConverter.cs b/Source/MonoGame.Extended/Serialization/HslColorJsonConverter.cs index 4b65c117..dca71fe5 100644 --- a/Source/MonoGame.Extended/Serialization/HslColorJsonConverter.cs +++ b/Source/MonoGame.Extended/Serialization/HslColorJsonConverter.cs @@ -1,9 +1,8 @@ using System; using Microsoft.Xna.Framework; -using MonoGame.Extended.Serialization; using Newtonsoft.Json; -namespace MonoGame.Extended.Particles.Serialization +namespace MonoGame.Extended.Serialization { public class HslColorJsonConverter : JsonConverter { diff --git a/Source/MonoGame.Extended/Serialization/JsonReaderExtensions.cs b/Source/MonoGame.Extended/Serialization/JsonReaderExtensions.cs index e48b5c60..5795888f 100644 --- a/Source/MonoGame.Extended/Serialization/JsonReaderExtensions.cs +++ b/Source/MonoGame.Extended/Serialization/JsonReaderExtensions.cs @@ -9,10 +9,11 @@ namespace MonoGame.Extended.Serialization { public static class JsonReaderExtensions { - private static readonly Dictionary> _stringParsers = new Dictionary> + private static readonly Dictionary> _stringParsers = new Dictionary> { {typeof(int), s => int.Parse(s, CultureInfo.InvariantCulture.NumberFormat)}, {typeof(float), s => float.Parse(s, CultureInfo.InvariantCulture.NumberFormat)}, + {typeof(HslColor), s => ColorExtensions.FromHex(s).ToHsl() } }; public static T[] ReadAsMultiDimensional(this JsonReader reader) @@ -22,26 +23,55 @@ namespace MonoGame.Extended.Serialization switch (tokenType) { case JsonToken.StartArray: - var jArray = JArray.Load(reader); - return jArray - .Select(i => i.Value()) - .ToArray(); + return reader.ReadAsJArray(); case JsonToken.String: - var value = (string)reader.Value; - var parser = _stringParsers[typeof(T)]; - return value.Split(' ') - .Select(i => parser(i)) - .Cast() - .ToArray(); + return reader.ReadAsDelimitedString(); case JsonToken.Integer: case JsonToken.Float: - return new []{ JToken.Load(reader).ToObject() }; + return reader.ReadAsSingleValue(); default: throw new NotSupportedException($"{tokenType} is not currently supported in the multi dimensional parser"); } } + + private static T[] ReadAsSingleValue(this JsonReader reader) + { + return new[] { JToken.Load(reader).ToObject() }; + } + + private static T[] ReadAsJArray(this JsonReader reader) + { + var jArray = JArray.Load(reader); + var items = new List(); + + foreach (var token in jArray) + { + if (token.Type == JTokenType.String) + { + var stringParser = _stringParsers[typeof(T)]; + var s = token.Value(); + items.Add((T)stringParser(s)); + } + else + { + items.Add(token.Value()); + } + } + + return items.ToArray(); + } + + private static T[] ReadAsDelimitedString(this JsonReader reader) + { + var value = (string)reader.Value; + var parser = _stringParsers[typeof(T)]; + return value.Split(' ') + .Select(i => parser(i)) + .Cast() + .ToArray(); + } } } \ No newline at end of file diff --git a/Source/MonoGame.Extended/Serialization/RangeJsonConverter.cs b/Source/MonoGame.Extended/Serialization/RangeJsonConverter.cs index ddf0cc36..f33ddb66 100644 --- a/Source/MonoGame.Extended/Serialization/RangeJsonConverter.cs +++ b/Source/MonoGame.Extended/Serialization/RangeJsonConverter.cs @@ -7,6 +7,18 @@ namespace MonoGame.Extended.Serialization { public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { + var range = (Range) value; + + var formatting = writer.Formatting; + writer.Formatting = Formatting.None; + writer.WriteWhitespace(" "); + writer.WriteStartArray(); + serializer.Serialize(writer, range.Min); + serializer.Serialize(writer, range.Max); + //writer.WriteValue(range.Min); + //writer.WriteValue(range.Max); + writer.WriteEndArray(); + writer.Formatting = formatting; } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) @@ -14,7 +26,13 @@ namespace MonoGame.Extended.Serialization var values = reader.ReadAsMultiDimensional(); if (values.Length == 2) - return new Range(values[0], values[1]); + { + if (values[0].CompareTo(values[1]) < 0) + return new Range(values[0], values[1]); + + return new Range(values[1], values[0]); + } + if (values.Length == 1) return new Range(values[0], values[0]); diff --git a/Source/MonoGame.Extended/Serialization/ShortNameJsonContractResolver.cs b/Source/MonoGame.Extended/Serialization/ShortNameJsonContractResolver.cs index 38708a37..e31adfa1 100644 --- a/Source/MonoGame.Extended/Serialization/ShortNameJsonContractResolver.cs +++ b/Source/MonoGame.Extended/Serialization/ShortNameJsonContractResolver.cs @@ -1,23 +1,29 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Reflection; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; namespace MonoGame.Extended.Serialization { - public class ShortNameJsonContractResolver : DefaultContractResolver + public class ShortNameJsonContractResolver : CamelCasePropertyNamesContractResolver { protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) { - var properties = base.CreateProperties(type, memberSerialization); + var properties = base.CreateProperties(type, memberSerialization) + .Where(p => p.Writable || IsListProperty(p)) + .ToList(); - if (type.GetTypeInfo().IsAbstract) + var typeInfo = type.GetTypeInfo(); + + if (typeInfo.IsAbstract) { properties.Insert(0, new JsonProperty { PropertyType = typeof(string), - PropertyName = "Type", + PropertyName = "type", Readable = true, Writable = false, ValueProvider = new JsonShortTypeNameProvider() @@ -27,6 +33,11 @@ namespace MonoGame.Extended.Serialization return properties; } + private static bool IsListProperty(JsonProperty property) + { + return typeof(IList).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo()); + } + private class JsonShortTypeNameProvider : IValueProvider { public JsonShortTypeNameProvider() diff --git a/Source/MonoGame.Extended/Serialization/TextureRegionService.cs b/Source/MonoGame.Extended/Serialization/TextureRegionService.cs index 66ef6faf..3914730a 100644 --- a/Source/MonoGame.Extended/Serialization/TextureRegionService.cs +++ b/Source/MonoGame.Extended/Serialization/TextureRegionService.cs @@ -6,9 +6,6 @@ namespace MonoGame.Extended.Serialization { public interface ITextureRegionService { - IList TextureAtlases { get; } - IList NinePatches { get; } - TextureRegion2D GetTextureRegion(string name); } diff --git a/Source/MonoGame.Extended/Serialization/ThicknessJsonConverter.cs b/Source/MonoGame.Extended/Serialization/ThicknessJsonConverter.cs index a87b3b0c..a4a57239 100644 --- a/Source/MonoGame.Extended/Serialization/ThicknessJsonConverter.cs +++ b/Source/MonoGame.Extended/Serialization/ThicknessJsonConverter.cs @@ -13,7 +13,8 @@ namespace MonoGame.Extended.Serialization public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - return Thickness.Parse((string)reader.Value); + var values = reader.ReadAsMultiDimensional(); + return Thickness.FromValues(values); } public override bool CanConvert(Type objectType) diff --git a/Source/MonoGame.Extended/Sprites/Sprite.cs b/Source/MonoGame.Extended/Sprites/Sprite.cs index 6bb00ddf..db8bc3b7 100644 --- a/Source/MonoGame.Extended/Sprites/Sprite.cs +++ b/Source/MonoGame.Extended/Sprites/Sprite.cs @@ -6,7 +6,7 @@ using MonoGame.Extended.TextureAtlases; namespace MonoGame.Extended.Sprites { - public class Sprite : Transform2D, IColorable, IRectangular, ISpriteBatchDrawable + public class Sprite : Transform2D, IColorable, IRectangularF, ISpriteBatchDrawable { private TextureRegion2D _textureRegion; diff --git a/Source/MonoGame.Extended/TextureAtlases/TextureAtlas.cs b/Source/MonoGame.Extended/TextureAtlases/TextureAtlas.cs index 26a02138..0e5a8bef 100644 --- a/Source/MonoGame.Extended/TextureAtlases/TextureAtlas.cs +++ b/Source/MonoGame.Extended/TextureAtlases/TextureAtlas.cs @@ -184,7 +184,7 @@ namespace MonoGame.Extended.TextureAtlases /// The . public TextureRegion2D GetRegion(int index) { - if ((index < 0) || (index >= _regions.Count)) + if (index < 0 || index >= _regions.Count) throw new IndexOutOfRangeException(); return _regions[index]; diff --git a/Source/MonoGame.Extended/Thickness.cs b/Source/MonoGame.Extended/Thickness.cs index e9877c1e..56f1be93 100644 --- a/Source/MonoGame.Extended/Thickness.cs +++ b/Source/MonoGame.Extended/Thickness.cs @@ -58,23 +58,29 @@ namespace MonoGame.Extended return Left == other.Left && Right == other.Right && Top == other.Top && Bottom == other.Bottom; } + public static Thickness FromValues(int[] values) + { + switch (values.Length) + { + case 1: + return new Thickness(values[0]); + case 2: + return new Thickness(values[0], values[1]); + case 4: + return new Thickness(values[0], values[1], values[2], values[3]); + default: + throw new FormatException("Invalid thickness"); + } + } + public static Thickness Parse(string value) { - var ints = value.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + var values = value + .Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) .Select(int.Parse) .ToArray(); - switch (ints.Length) - { - case 1: - return new Thickness(ints[0]); - case 2: - return new Thickness(ints[0], ints[1]); - case 4: - return new Thickness(ints[0], ints[1], ints[2], ints[3]); - default: - throw new FormatException($"Invalid thickness {value}"); - } + return FromValues(values); } public override string ToString() diff --git a/Source/MonoGame.Extended/Transform.cs b/Source/MonoGame.Extended/Transform.cs index fd1bcd73..717fea35 100644 --- a/Source/MonoGame.Extended/Transform.cs +++ b/Source/MonoGame.Extended/Transform.cs @@ -88,6 +88,7 @@ namespace MonoGame.Extended /// null disables the inheritance altogether for this instance. /// /// + [EditorBrowsable(EditorBrowsableState.Never)] public TParent Parent { get { return _parent; } diff --git a/Source/NuGet/MonoGame.Extended.Animations.nuspec b/Source/NuGet/MonoGame.Extended.Animations.nuspec index 873e142f..96d56a34 100644 --- a/Source/NuGet/MonoGame.Extended.Animations.nuspec +++ b/Source/NuGet/MonoGame.Extended.Animations.nuspec @@ -29,6 +29,7 @@ + diff --git a/Source/NuGet/MonoGame.Extended.Entities.nuspec b/Source/NuGet/MonoGame.Extended.Entities.nuspec index 31da55d2..c98aeba4 100644 --- a/Source/NuGet/MonoGame.Extended.Entities.nuspec +++ b/Source/NuGet/MonoGame.Extended.Entities.nuspec @@ -31,6 +31,7 @@ + diff --git a/Source/NuGet/MonoGame.Extended.Graphics.nuspec b/Source/NuGet/MonoGame.Extended.Graphics.nuspec index 2e16d0c9..b959f6cf 100644 --- a/Source/NuGet/MonoGame.Extended.Graphics.nuspec +++ b/Source/NuGet/MonoGame.Extended.Graphics.nuspec @@ -31,6 +31,7 @@ + diff --git a/Source/NuGet/MonoGame.Extended.Gui.nuspec b/Source/NuGet/MonoGame.Extended.Gui.nuspec index 50096d42..f23b8028 100644 --- a/Source/NuGet/MonoGame.Extended.Gui.nuspec +++ b/Source/NuGet/MonoGame.Extended.Gui.nuspec @@ -30,6 +30,7 @@ + diff --git a/Source/NuGet/MonoGame.Extended.Input.nuspec b/Source/NuGet/MonoGame.Extended.Input.nuspec index 4304fe56..8b75b9db 100644 --- a/Source/NuGet/MonoGame.Extended.Input.nuspec +++ b/Source/NuGet/MonoGame.Extended.Input.nuspec @@ -29,6 +29,7 @@ + diff --git a/Source/NuGet/MonoGame.Extended.NuclexGui.nuspec b/Source/NuGet/MonoGame.Extended.NuclexGui.nuspec index a7b14fb4..41e3c8b4 100644 --- a/Source/NuGet/MonoGame.Extended.NuclexGui.nuspec +++ b/Source/NuGet/MonoGame.Extended.NuclexGui.nuspec @@ -30,6 +30,7 @@ + diff --git a/Source/NuGet/MonoGame.Extended.Particles.nuspec b/Source/NuGet/MonoGame.Extended.Particles.nuspec index 1cbc2dc3..80f4851f 100644 --- a/Source/NuGet/MonoGame.Extended.Particles.nuspec +++ b/Source/NuGet/MonoGame.Extended.Particles.nuspec @@ -29,6 +29,7 @@ + diff --git a/Source/NuGet/MonoGame.Extended.SceneGraphs.nuspec b/Source/NuGet/MonoGame.Extended.SceneGraphs.nuspec index 3c930e9a..b4a5ebc6 100644 --- a/Source/NuGet/MonoGame.Extended.SceneGraphs.nuspec +++ b/Source/NuGet/MonoGame.Extended.SceneGraphs.nuspec @@ -29,6 +29,7 @@ + diff --git a/Source/NuGet/MonoGame.Extended.Tiled.nuspec b/Source/NuGet/MonoGame.Extended.Tiled.nuspec index 78256ece..70f231d3 100644 --- a/Source/NuGet/MonoGame.Extended.Tiled.nuspec +++ b/Source/NuGet/MonoGame.Extended.Tiled.nuspec @@ -30,6 +30,7 @@ + diff --git a/Source/NuGet/MonoGame.Extended.Tweening.nuspec b/Source/NuGet/MonoGame.Extended.Tweening.nuspec index e36cf116..94fdcbca 100644 --- a/Source/NuGet/MonoGame.Extended.Tweening.nuspec +++ b/Source/NuGet/MonoGame.Extended.Tweening.nuspec @@ -30,6 +30,7 @@ + diff --git a/Source/NuGet/MonoGame.Extended.nuspec b/Source/NuGet/MonoGame.Extended.nuspec index b0dbab5c..6891f502 100644 --- a/Source/NuGet/MonoGame.Extended.nuspec +++ b/Source/NuGet/MonoGame.Extended.nuspec @@ -30,6 +30,7 @@ + diff --git a/Source/Tests/MonoGame.Extended.Gui.Tests/Controls/GuiButtonTests.cs b/Source/Tests/MonoGame.Extended.Gui.Tests/Controls/GuiButtonTests.cs index 0be24c4f..f50c88a6 100644 --- a/Source/Tests/MonoGame.Extended.Gui.Tests/Controls/GuiButtonTests.cs +++ b/Source/Tests/MonoGame.Extended.Gui.Tests/Controls/GuiButtonTests.cs @@ -29,7 +29,7 @@ namespace MonoGame.Extended.Gui.Tests.Controls var availableSize = new Size2(800, 480); var context = Substitute.For(); var backgroundRegion = MockTextureRegion(); - var button = new GuiButton(backgroundRegion); + var button = new GuiButton { BackgroundRegion = backgroundRegion }; var desiredSize = button.GetDesiredSize(context, availableSize); Assert.That(desiredSize, Is.EqualTo(backgroundRegion.Size)); @@ -42,7 +42,7 @@ namespace MonoGame.Extended.Gui.Tests.Controls var context = Substitute.For(); var texture = new Texture2D(new TestGraphicsDevice(), 512, 512); var backgroundRegion = new NinePatchRegion2D(new TextureRegion2D(texture), new Thickness(10, 20)); - var button = new GuiButton(backgroundRegion); + var button = new GuiButton() { BackgroundRegion = backgroundRegion }; var desiredSize = button.GetDesiredSize(context, availableSize); Assert.That(desiredSize, Is.EqualTo(new Size2(20, 40))); diff --git a/Source/Tests/MonoGame.Extended.Tests/Particles/AssertionModifier.cs b/Source/Tests/MonoGame.Extended.Tests/Particles/AssertionModifier.cs index b243b3aa..eb3386a9 100644 --- a/Source/Tests/MonoGame.Extended.Tests/Particles/AssertionModifier.cs +++ b/Source/Tests/MonoGame.Extended.Tests/Particles/AssertionModifier.cs @@ -5,7 +5,7 @@ using NUnit.Framework; namespace MonoGame.Extended.Tests.Particles { - internal class AssertionModifier : IModifier + internal class AssertionModifier : Modifier { private readonly Predicate _predicate; @@ -14,7 +14,7 @@ namespace MonoGame.Extended.Tests.Particles _predicate = predicate; } - public unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) + public override unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator) { while (iterator.HasNext) { var particle = iterator.Next(); diff --git a/Source/Tests/MonoGame.Extended.Tests/Particles/EmitterTests.cs b/Source/Tests/MonoGame.Extended.Tests/Particles/EmitterTests.cs index 87758247..049a7aba 100644 --- a/Source/Tests/MonoGame.Extended.Tests/Particles/EmitterTests.cs +++ b/Source/Tests/MonoGame.Extended.Tests/Particles/EmitterTests.cs @@ -39,7 +39,7 @@ namespace MonoGame.Extended.Tests.Particles { Quantity = 1 }, - Modifiers = new IModifier[] { + Modifiers = new Modifier[] { new AssertionModifier(particle => particle.Age <= 1f) } }; @@ -55,7 +55,7 @@ namespace MonoGame.Extended.Tests.Particles [Test] public void WhenThereAreNoActiveParticles_GracefullyDoesNothing() { - var subject = new ParticleEmitter(null, 100, TimeSpan.FromSeconds(1), Profile.Point(), autoTrigger: false); + var subject = new ParticleEmitter(null, 100, TimeSpan.FromSeconds(1), Profile.Point()); subject.Update(0.5f); Assert.AreEqual(subject.ActiveParticles, 0); diff --git a/Source/Tests/Sandbox/Content/adventure-gui-skin.json b/Source/Tests/Sandbox/Content/adventure-gui-skin.json index fa1301f6..05f4915e 100644 --- a/Source/Tests/Sandbox/Content/adventure-gui-skin.json +++ b/Source/Tests/Sandbox/Content/adventure-gui-skin.json @@ -1,59 +1,34 @@ { "Name": "adventure", - "TextureAtlases": [ "adventure-gui-atlas" ], - "Fonts": [ "small-font" ], + "TextureAtlases": ["adventure-gui-atlas"], + "Fonts": ["small-font"], "Cursor": { "TextureRegion": "cursorSword_silver", "Color": "#aaaaaa" }, "NinePatches": [ - { - "TextureRegion": "buttonLong_grey", - "Padding": 10 - }, - { - "TextureRegion": "buttonLong_grey_pressed", - "Padding": 10 - }, - { - "TextureRegion": "panel_brown", - "Padding": "10" - }, - { - "TextureRegion": "panelInset_beige", - "Padding": "10" - }, - { - "TextureRegion": "grey_textBox", - "Padding": "10" - }, - { - "TextureRegion": "progress-bar-container", - "Padding": "10" - }, - { - "TextureRegion": "progress-bar-blue", - "Padding": "10" - } + { "TextureRegion": "buttonLong_grey", "Padding": 10 }, + { "TextureRegion": "buttonLong_grey_pressed", "Padding": 10 }, + { "TextureRegion": "panel_brown", "Padding": "10" }, + { "TextureRegion": "panelInset_beige", "Padding": "10" }, + { "TextureRegion": "grey_textBox", "Padding": "10" }, + { "TextureRegion": "progress-bar-container", "Padding": "10" }, + { "TextureRegion": "progress-bar-blue", "Padding": "10" } ], "Styles": [ { - "Name": "close-button", - "Type": "GuiButton", - "BackgroundRegion": "buttonRound_close", - "HoverStyle": { - "Type": "GuiButton", - "Color": "LightGray" - } + "Type": "MyPanel", + "VerticalTextAlignment": "Bottom" }, { - "Name": "white-button", "Type": "GuiButton", "Margin": "5 2", "BackgroundRegion": "buttonLong_grey", "TextOffset": "0 -2", "TextColor": "#f4a460ff", "Color": "#8b4513ff", + "HorizontalAlignment": "Center", + "VerticalAlignment": "Centre", "PressedStyle": { "Type": "GuiButton", "BackgroundRegion": "buttonLong_grey_pressed", @@ -65,6 +40,15 @@ "TextColor": "#ffffffff" } }, + { + "Name": "close-button", + "Type": "GuiButton", + "BackgroundRegion": "buttonRound_close", + "HoverStyle": { + "Type": "GuiButton", + "Color": "LightGray" + } + }, { "Name": "checkbox", "Type": "GuiCheckBox", @@ -97,7 +81,6 @@ "BackgroundRegion": "monogame-title" }, { - "Name": "text-box", "Type": "GuiTextBox", "ClipPadding": "8 0", "BackgroundRegion": "grey_textBox", @@ -113,7 +96,6 @@ { "Name": "list-box", "Type": "GuiListBox", - "Size": "200 150", "ClipPadding": "7 5", "BackgroundRegion": "grey_textBox", "TextColor": "#444444" @@ -124,6 +106,12 @@ "Padding": "10", "BackgroundRegion": "panel_brown", "TextColor": "#444444" + }, + { + "Type": "GuiComboBox", + "ClipPadding": "7 5", + "BackgroundRegion": "buttonLong_grey", + "TextColor": "#444444" } ] } diff --git a/Source/Tests/Sandbox/Content/particle-effect.json b/Source/Tests/Sandbox/Content/particle-effect.json index 4c186db8..8da75a45 100644 --- a/Source/Tests/Sandbox/Content/particle-effect.json +++ b/Source/Tests/Sandbox/Content/particle-effect.json @@ -3,12 +3,12 @@ "emitters": [ { "textureRegion": "checkBox_beige_unchecked", - "capacity": 500, - "term": 2.5, + "capacity": 1000, + "lifeSpan": 2.5, + "autoTrigger": true, + "offset": [100, 100], "profile": { - "type": "box", - "width": 800, - "height": 480 + "type": "point" }, "parameters": { "quantity": [ 3, 4 ], diff --git a/Source/Tests/Sandbox/Experiments/BindingScreen.cs b/Source/Tests/Sandbox/Experiments/BindingScreen.cs new file mode 100644 index 00000000..e68be057 --- /dev/null +++ b/Source/Tests/Sandbox/Experiments/BindingScreen.cs @@ -0,0 +1,23 @@ +using MonoGame.Extended.Gui; +using MonoGame.Extended.Gui.Controls; + +namespace Sandbox.Experiments +{ + public class BindingScreen : GuiScreen + { + public BindingScreen(GuiSkin skin, ViewModel viewModel) + : base(skin) + { + var button = new GuiButton(skin); + Controls.Add(button); + + button.SetBinding(nameof(GuiButton.Text), nameof(viewModel.Name)); + button.Clicked += (sender, args) => + { + viewModel.Name = "alliestrasza"; + }; + + BindingContext = viewModel; + } + } +} \ No newline at end of file diff --git a/Source/Tests/Sandbox/Experiments/GuiElementExtensions.cs b/Source/Tests/Sandbox/Experiments/GuiElementExtensions.cs new file mode 100644 index 00000000..033ad004 --- /dev/null +++ b/Source/Tests/Sandbox/Experiments/GuiElementExtensions.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using MonoGame.Extended.Gui; + +namespace Sandbox.Experiments +{ + public static class GuiElementExtensions + { + } +} diff --git a/Source/Tests/Sandbox/Experiments/MyPanel.cs b/Source/Tests/Sandbox/Experiments/MyPanel.cs new file mode 100644 index 00000000..4ce32d37 --- /dev/null +++ b/Source/Tests/Sandbox/Experiments/MyPanel.cs @@ -0,0 +1,12 @@ +using MonoGame.Extended.Gui; +using MonoGame.Extended.Gui.Controls; + +namespace Sandbox +{ + public class MyPanel : GuiUniformGrid + { + public MyPanel(GuiSkin skin) : base(skin) + { + } + } +} \ No newline at end of file diff --git a/Source/Tests/Sandbox/Experiments/MyScreen.cs b/Source/Tests/Sandbox/Experiments/MyScreen.cs new file mode 100644 index 00000000..5b0bae42 --- /dev/null +++ b/Source/Tests/Sandbox/Experiments/MyScreen.cs @@ -0,0 +1,53 @@ +using System; +using MonoGame.Extended.Gui; +using MonoGame.Extended.Gui.Controls; + +namespace Sandbox +{ + public class MyScreen : GuiScreen + { + public MyScreen(GuiSkin skin) + : base(skin) + { + Controls.Add(new GuiUniformGrid(Skin) + { + Controls = + { + new GuiComboBox(Skin) + { + Name = "ComboBox", + Items = + { + new { Name = "one", Number = 1 }, + new { Name = "two", Number = 2 }, + new { Name = "three", Number = 3 } + }, + SelectedIndex = 0, + NameProperty = "Number" + }, + new GuiListBox(Skin) + { + Name = "ListBox", + Items = + { + "one", + "two", + "three" + } + } + } + }); + + var listBox = FindControl("ListBox"); + var comboBox = FindControl("ComboBox"); + comboBox.SelectedIndexChanged += (sender, args) => listBox.SelectedIndex = comboBox.SelectedIndex; + listBox.SelectedIndexChanged += (sender, args) => comboBox.SelectedIndex = listBox.SelectedIndex; + } + + private void OpenDialog_Clicked(object sender, EventArgs eventArgs) + { + var dialog = new MyWindow(this); + dialog.Show(); + } + } +} \ No newline at end of file diff --git a/Source/Tests/Sandbox/Experiments/MyWindow.cs b/Source/Tests/Sandbox/Experiments/MyWindow.cs new file mode 100644 index 00000000..d4249e86 --- /dev/null +++ b/Source/Tests/Sandbox/Experiments/MyWindow.cs @@ -0,0 +1,49 @@ +using MonoGame.Extended.Gui; +using MonoGame.Extended.Gui.Controls; + +namespace Sandbox +{ + public class MyWindow : GuiWindow + { + public MyWindow(GuiScreen parent) + : base(parent) + { + Width = 300; + Height = 200; + + Controls.Add(new MyPanel(Skin) { Text = "My Panel", HorizontalTextAlignment = HorizontalAlignment.Right }); + + var button = new GuiButton(Skin) + { + Width = 100, + Height = 32, + Text = "Yay", + VerticalAlignment = VerticalAlignment.Bottom + }; + Controls.Add(button); + + Controls.Add(new GuiStackPanel(Skin) + { + Controls = + { + new GuiLabel(Skin, "propertyName"), + new GuiStackPanel + { + HorizontalAlignment = HorizontalAlignment.Centre, + VerticalAlignment = VerticalAlignment.Centre, + Orientation = GuiOrientation.Horizontal, + Controls = + { + new GuiLabel(Skin, "propertyName"), + new GuiTextBox(Skin, "Hello"), + new GuiTextBox(Skin, "World") + } + } + } + }); + + var label = new GuiLabel(Skin, "This is just a boring dialog."); + Controls.Add(label); + } + } +} \ No newline at end of file diff --git a/Source/Tests/Sandbox/Game1.cs b/Source/Tests/Sandbox/Game1.cs index 93bd99a7..b446d430 100644 --- a/Source/Tests/Sandbox/Game1.cs +++ b/Source/Tests/Sandbox/Game1.cs @@ -1,92 +1,87 @@ -using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; -using MonoGame.Extended; using MonoGame.Extended.Gui; -using MonoGame.Extended.Gui.Controls; -using MonoGame.Extended.Particles; -using MonoGame.Extended.Particles.Modifiers; -using MonoGame.Extended.Particles.Modifiers.Containers; -using MonoGame.Extended.Particles.Modifiers.Interpolators; -using MonoGame.Extended.Particles.Profiles; -using MonoGame.Extended.Serialization; -using MonoGame.Extended.TextureAtlases; using MonoGame.Extended.ViewportAdapters; -using Newtonsoft.Json; +using Sandbox.Experiments; // The Sandbox project is used for experiementing outside the normal demos. // Any code found here should be considered experimental work in progress. namespace Sandbox { + public class ViewModel : INotifyPropertyChanged + { + public ViewModel() + { + Name = "Alistrasza"; + } + + private string _name; + public string Name + { + get { return _name; } + set + { + if (_name != value) + { + _name = value; + OnPropertyChanged(); + } + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } + public class Game1 : Game { // ReSharper disable once NotAccessedField.Local - private GraphicsDeviceManager _graphicsDeviceManager; + private readonly GraphicsDeviceManager _graphicsDeviceManager; private ViewportAdapter _viewportAdapter; - private Camera2D _camera; private GuiSystem _guiSystem; - private SpriteBatch _spriteBatch; - private ParticleEffect _particleEffect; public Game1() { _graphicsDeviceManager = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; + Window.AllowUserResizing = true; } protected override void LoadContent() { IsMouseVisible = false; - _viewportAdapter = new BoxingViewportAdapter(Window, GraphicsDevice, 800, 480); - _camera = new Camera2D(_viewportAdapter); + _viewportAdapter = new BoxingViewportAdapter(Window, GraphicsDevice, _graphicsDeviceManager.PreferredBackBufferWidth, _graphicsDeviceManager.PreferredBackBufferHeight); - var skin = GuiSkin.FromFile(Content, @"Content/adventure-gui-skin.json"); - var guiRenderer = new GuiSpriteBatchRenderer(GraphicsDevice, _camera.GetViewMatrix); + var skin = GuiSkin.FromFile(Content, @"Content/adventure-gui-skin.json", typeof(MyPanel)); + var guiRenderer = new GuiSpriteBatchRenderer(GraphicsDevice, _viewportAdapter.GetScaleMatrix); + + var viewModel = new ViewModel(); _guiSystem = new GuiSystem(_viewportAdapter, guiRenderer) { - Screen = new GuiScreen(skin) + Screens = { - Controls = - { - new GuiTextBox { Text = "Hello World" } - } + new BindingScreen(skin, viewModel) } }; - - _spriteBatch = new SpriteBatch(GraphicsDevice); - - var particleTexture = new Texture2D(GraphicsDevice, 1, 1); - particleTexture.SetData(new[] { Color.White }); - - var textureRegionService = new TextureRegionService(); - textureRegionService.TextureAtlases.Add(Content.Load("adventure-gui-atlas")); - - _particleEffect = ParticleEffect.FromFile(textureRegionService, @"Content/particle-effect.json"); - - //ParticleInit(new TextureRegion2D(particleTexture)); } - + protected override void Update(GameTime gameTime) { - var deltaTime = gameTime.GetElapsedSeconds(); var keyboardState = Keyboard.GetState(); - var mouseState = Mouse.GetState(); - var p = _camera.ScreenToWorld(mouseState.X, mouseState.Y); if (keyboardState.IsKeyDown(Keys.Escape)) Exit(); - _particleEffect.Update(deltaTime); - - if (mouseState.LeftButton == ButtonState.Pressed) - _particleEffect.Trigger(new Vector2(p.X, p.Y)); - - _particleEffect.Trigger(new Vector2(400, 240)); - _guiSystem.Update(gameTime); } @@ -94,48 +89,7 @@ namespace Sandbox { GraphicsDevice.Clear(Color.Black); - _spriteBatch.Begin(blendState: BlendState.AlphaBlend, transformMatrix: _camera.GetViewMatrix()); - _spriteBatch.Draw(_particleEffect); - _spriteBatch.End(); - _guiSystem.Draw(gameTime); } - - private ParticleEffect ParticleInit(TextureRegion2D textureRegion) - { - return _particleEffect = new ParticleEffect - { - Emitters = new[] - { - new ParticleEmitter(textureRegion, 500, TimeSpan.FromSeconds(2.5), Profile.Ring(150f, Profile.CircleRadiation.In)) - { - Parameters = new ParticleReleaseParameters - { - Speed = new Range(0f, 50f), - Quantity = 3, - Rotation = new Range(-1f, 1f), - Scale = new Range(3.0f, 4.0f) - }, - Modifiers = new IModifier[] - { - new AgeModifier - { - Interpolators = new IInterpolator[] - { - new ColorInterpolator - { - StartValue = new HslColor(0.33f, 0.5f, 0.5f), - EndValue = new HslColor(0.5f, 0.9f, 1.0f) - } - } - }, - new RotationModifier {RotationRate = -2.1f}, - new RectangleContainerModifier {Width = 800, Height = 480}, - new LinearGravityModifier {Direction = -Vector2.UnitY, Strength = 30f} - } - } - } - }; - } } } diff --git a/Source/Tests/Sandbox/Sandbox.csproj b/Source/Tests/Sandbox/Sandbox.csproj index a5071b4b..d8add650 100644 --- a/Source/Tests/Sandbox/Sandbox.csproj +++ b/Source/Tests/Sandbox/Sandbox.csproj @@ -42,7 +42,12 @@ app.manifest + + + + +