From d94e882c022db154e567a88e1395dea8262cb7cb Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Fri, 9 Sep 2022 11:17:37 +0200 Subject: [PATCH 01/33] added markdown editorconfig settings --- .editorconfig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.editorconfig b/.editorconfig index 94ba4be..30b58f9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -111,3 +111,7 @@ resharper_wrap_object_and_collection_initializer_style = wrap_if_long resharper_xmldoc_attribute_indent = align_by_first_attribute resharper_xmldoc_attribute_style = on_single_line resharper_xmldoc_pi_attribute_style = on_single_line + +[*.md] +trim_trailing_whitespace = false +indent_size = 2 From a274517861eff315ec599ab7f788dbc73b1a9b00 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Fri, 9 Sep 2022 17:55:14 +0200 Subject: [PATCH 02/33] fixed non-MLEM packages being deleted in build --- build.cake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.cake b/build.cake index 5b44f4e..e233eec 100644 --- a/build.cake +++ b/build.cake @@ -16,7 +16,7 @@ Task("Prepare").Does(() => { version += "-" + buildNum; } - DeleteFiles("**/*.nupkg"); + DeleteFiles("**/MLEM*.nupkg"); }); Task("Build").IsDependentOn("Prepare").Does(() =>{ @@ -62,7 +62,7 @@ Task("Push").WithCriteria(branch == "main" || branch == "release").IsDependentOn }; } settings.SkipDuplicate = true; - NuGetPush(GetFiles("**/*.nupkg"), settings); + NuGetPush(GetFiles("**/MLEM*.nupkg"), settings); }); Task("Document").Does(() => { From e60d3591fff634c7776b2b2f8e7c092163988b56 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Fri, 9 Sep 2022 17:55:23 +0200 Subject: [PATCH 03/33] updated tests to net6.0 --- Tests/Tests.FNA.csproj | 2 +- Tests/Tests.csproj | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/Tests.FNA.csproj b/Tests/Tests.FNA.csproj index 8f1fe64..e032b51 100644 --- a/Tests/Tests.FNA.csproj +++ b/Tests/Tests.FNA.csproj @@ -1,6 +1,6 @@ - net5.0 + net6.0 nunit Tests $(DefineConstants);FNA diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 6f1da92..1d34903 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,16 +1,16 @@ - net5.0 + net6.0 nunit - + - + @@ -23,7 +23,7 @@ - + PreserveNewest From d6e7c1086d8ebd86d23da0626122f23a3d05d835 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Mon, 12 Sep 2022 21:13:43 +0200 Subject: [PATCH 04/33] Discard old data when updating a StaticSpriteBatch --- CHANGELOG.md | 1 + MLEM/Graphics/StaticSpriteBatch.cs | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f50598c..6610c67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Additions Improvements - Improved EnumHelper.GetValues signature to return an array - Allow using external gesture handling alongside InputHandler through ExternalGestureHandling +- Discard old data when updating a StaticSpriteBatch Fixes - Fixed TokenizedString handling trailing spaces incorrectly in the last line of non-left aligned text diff --git a/MLEM/Graphics/StaticSpriteBatch.cs b/MLEM/Graphics/StaticSpriteBatch.cs index e9cf4cf..914c5de 100644 --- a/MLEM/Graphics/StaticSpriteBatch.cs +++ b/MLEM/Graphics/StaticSpriteBatch.cs @@ -42,7 +42,7 @@ namespace MLEM.Graphics { private readonly GraphicsDevice graphicsDevice; private readonly SpriteEffect spriteEffect; - private readonly List vertexBuffers = new List(); + private readonly List vertexBuffers = new List(); private readonly List textures = new List(); private readonly ISet items = new HashSet(); private IndexBuffer indices; @@ -439,8 +439,8 @@ namespace MLEM.Graphics { private void FillBuffer(int index, Texture2D texture, VertexPositionColorTexture[] data) { if (this.vertexBuffers.Count <= index) - this.vertexBuffers.Add(new VertexBuffer(this.graphicsDevice, VertexPositionColorTexture.VertexDeclaration, StaticSpriteBatch.MaxBatchItems * 4, BufferUsage.WriteOnly)); - this.vertexBuffers[index].SetData(data); + this.vertexBuffers.Add(new DynamicVertexBuffer(this.graphicsDevice, VertexPositionColorTexture.VertexDeclaration, StaticSpriteBatch.MaxBatchItems * 4, BufferUsage.WriteOnly)); + this.vertexBuffers[index].SetData(data, 0, data.Length, SetDataOptions.Discard); this.textures.Insert(index, texture); } From 742bc5243777dc19b1280b15858e1594aac12802 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Mon, 12 Sep 2022 21:51:21 +0200 Subject: [PATCH 05/33] First pass at drastically improving StaticSpriteBatch batching performance --- CHANGELOG.md | 1 + MLEM/Graphics/StaticSpriteBatch.cs | 102 ++++++++++++++++++----------- Sandbox/GameImpl.cs | 12 ++++ 3 files changed, 78 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6610c67..a31e600 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Improvements - Improved EnumHelper.GetValues signature to return an array - Allow using external gesture handling alongside InputHandler through ExternalGestureHandling - Discard old data when updating a StaticSpriteBatch +- Drastically improved StaticSpriteBatch batching performance Fixes - Fixed TokenizedString handling trailing spaces incorrectly in the last line of non-left aligned text diff --git a/MLEM/Graphics/StaticSpriteBatch.cs b/MLEM/Graphics/StaticSpriteBatch.cs index 914c5de..56ae232 100644 --- a/MLEM/Graphics/StaticSpriteBatch.cs +++ b/MLEM/Graphics/StaticSpriteBatch.cs @@ -23,7 +23,7 @@ namespace MLEM.Graphics { /// /// The amount of vertices that are currently batched. /// - public int Vertices => this.items.Count * 4; + public int Vertices => this.currentItems.Count * 4; /// /// The amount of vertex buffers that this static sprite batch has. /// To see the amount of buffers that are actually in use, see . @@ -41,13 +41,16 @@ namespace MLEM.Graphics { private readonly GraphicsDevice graphicsDevice; private readonly SpriteEffect spriteEffect; - private readonly List vertexBuffers = new List(); private readonly List textures = new List(); - private readonly ISet items = new HashSet(); + private readonly ISet currentItems = new HashSet(); + private readonly List sortedItems = new List(); + private IndexBuffer indices; private bool batching; private bool batchChanged; + private SpriteSortMode sortMode; + private Comparer comparer; /// /// Creates a new static sprite batch with the given @@ -62,10 +65,22 @@ namespace MLEM.Graphics { /// Begins batching. /// Call this method before calling Add or any of its overloads. /// + /// The drawing order for sprite drawing. by default, since it is the best in terms of rendering performance. Note that is not supported. /// Thrown if this batch is currently batching already - public void BeginBatch() { + /// Thrown if the is , which is not supported. + public void BeginBatch(SpriteSortMode sortMode = SpriteSortMode.Texture) { if (this.batching) throw new InvalidOperationException("Already batching"); + if (sortMode == SpriteSortMode.Immediate) + throw new ArgumentOutOfRangeException(nameof(sortMode), "Cannot use sprite sort mode Immediate for static batching"); + + // update comparer and re-sort our list if our sort mode changed + if (this.sortMode != sortMode) { + this.sortMode = sortMode; + this.comparer = Comparer.Create(StaticSpriteBatch.CreateComparison(sortMode)); + this.sortedItems.Sort(this.comparer); + } + this.batching = true; } @@ -73,14 +88,10 @@ namespace MLEM.Graphics { /// Ends batching. /// Call this method after calling Add or any of its overloads the desired number of times to add batched items. /// - /// The drawing order for sprite drawing. by default, since it is the best in terms of rendering performance. Note that is not supported. /// Thrown if this method is called before was called. - /// Thrown if the is , which is not supported. - public void EndBatch(SpriteSortMode sortMode = SpriteSortMode.Texture) { + public void EndBatch() { if (!this.batching) throw new InvalidOperationException("Not batching"); - if (sortMode == SpriteSortMode.Immediate) - throw new ArgumentOutOfRangeException(nameof(sortMode), "Cannot use sprite sort mode Immediate for static batching"); this.batching = false; // if we didn't add or remove any batch items, we don't have to recalculate anything @@ -90,41 +101,32 @@ namespace MLEM.Graphics { this.FilledBuffers = 0; this.textures.Clear(); - // order items according to the sort mode - IEnumerable ordered = this.items; - switch (sortMode) { - case SpriteSortMode.Texture: - // SortingKey is internal, but this will do for batching the same texture together - ordered = ordered.OrderBy(i => i.Texture.GetHashCode()); - break; - case SpriteSortMode.BackToFront: - ordered = ordered.OrderBy(i => -i.Depth); - break; - case SpriteSortMode.FrontToBack: - ordered = ordered.OrderBy(i => i.Depth); - break; - } - // fill vertex buffers var dataIndex = 0; Texture2D texture = null; - foreach (var item in ordered) { + // we use RemoveAll to iterate safely while being able to remove + this.sortedItems.RemoveAll(i => { + // we remove items in here to avoid having to search for them when removing them in Remove + if (i.Removed) + return true; + // if the texture changes, we also have to start a new buffer! - if (dataIndex > 0 && (item.Texture != texture || dataIndex >= StaticSpriteBatch.Data.Length)) { + if (dataIndex > 0 && (i.Texture != texture || dataIndex >= StaticSpriteBatch.Data.Length)) { this.FillBuffer(this.FilledBuffers++, texture, StaticSpriteBatch.Data); dataIndex = 0; } - StaticSpriteBatch.Data[dataIndex++] = item.TopLeft; - StaticSpriteBatch.Data[dataIndex++] = item.TopRight; - StaticSpriteBatch.Data[dataIndex++] = item.BottomLeft; - StaticSpriteBatch.Data[dataIndex++] = item.BottomRight; - texture = item.Texture; - } + StaticSpriteBatch.Data[dataIndex++] = i.TopLeft; + StaticSpriteBatch.Data[dataIndex++] = i.TopRight; + StaticSpriteBatch.Data[dataIndex++] = i.BottomLeft; + StaticSpriteBatch.Data[dataIndex++] = i.BottomRight; + texture = i.Texture; + return false; + }); if (dataIndex > 0) this.FillBuffer(this.FilledBuffers++, texture, StaticSpriteBatch.Data); // ensure we have enough indices - var maxItems = Math.Min(this.items.Count, StaticSpriteBatch.MaxBatchItems); + var maxItems = Math.Min(this.currentItems.Count, StaticSpriteBatch.MaxBatchItems); // each item has 2 triangles which each have 3 indices if (this.indices == null || this.indices.IndexCount < 6 * maxItems) { var newIndices = new short[6 * maxItems]; @@ -148,6 +150,10 @@ namespace MLEM.Graphics { this.indices = new IndexBuffer(this.graphicsDevice, IndexElementSize.SixteenBits, newIndices.Length, BufferUsage.WriteOnly); this.indices.SetData(newIndices); } + + // sanity check, this shouldn't be able to happen + if (this.currentItems.Count != this.sortedItems.Count) + throw new InvalidOperationException("Current and sorted items have mismatched sizes"); } /// @@ -178,7 +184,7 @@ namespace MLEM.Graphics { for (var i = 0; i < this.FilledBuffers; i++) { var buffer = this.vertexBuffers[i]; var texture = this.textures[i]; - var verts = Math.Min(this.items.Count * 4 - totalIndex, buffer.VertexCount); + var verts = Math.Min(this.currentItems.Count * 4 - totalIndex, buffer.VertexCount); this.graphicsDevice.SetVertexBuffer(buffer); if (effect != null) { @@ -362,8 +368,10 @@ namespace MLEM.Graphics { public bool Remove(Item item) { if (!this.batching) throw new InvalidOperationException("Not batching"); - if (this.items.Remove(item)) { + if (!item.Removed && this.currentItems.Remove(item)) { this.batchChanged = true; + // this item will only actually get removed from sortedItems in EndBatch for performance + item.Removed = true; return true; } return false; @@ -377,7 +385,8 @@ namespace MLEM.Graphics { public void ClearBatch() { if (!this.batching) throw new InvalidOperationException("Not batching"); - this.items.Clear(); + this.currentItems.Clear(); + this.sortedItems.Clear(); this.textures.Clear(); this.FilledBuffers = 0; this.batchChanged = true; @@ -432,7 +441,10 @@ namespace MLEM.Graphics { if (!this.batching) throw new InvalidOperationException("Not batching"); var item = new Item(texture, depth, tl, tr, bl, br); - this.items.Add(item); + this.currentItems.Add(item); + // add item in a sorted fashion + var pos = this.sortedItems.BinarySearch(item, this.comparer); + this.sortedItems.Insert(pos >= 0 ? pos : ~pos, item); this.batchChanged = true; return item; } @@ -452,6 +464,19 @@ namespace MLEM.Graphics { #endif } + private static Comparison CreateComparison(SpriteSortMode sortMode) { + switch (sortMode) { + case SpriteSortMode.Texture: + return (i1, i2) => i1.TextureHash.CompareTo(i2.TextureHash); + case SpriteSortMode.BackToFront: + return (i1, i2) => i2.Depth.CompareTo(i1.Depth); + case SpriteSortMode.FrontToBack: + return (i1, i2) => i1.Depth.CompareTo(i2.Depth); + default: + return (i1, i2) => 0; + } + } + /// /// A struct that represents an item added to a using Add or any of its overloads. /// An item returned after adding can be removed using . @@ -459,14 +484,17 @@ namespace MLEM.Graphics { public class Item { internal readonly Texture2D Texture; + internal readonly int TextureHash; internal readonly float Depth; internal readonly VertexPositionColorTexture TopLeft; internal readonly VertexPositionColorTexture TopRight; internal readonly VertexPositionColorTexture BottomLeft; internal readonly VertexPositionColorTexture BottomRight; + internal bool Removed; internal Item(Texture2D texture, float depth, VertexPositionColorTexture topLeft, VertexPositionColorTexture topRight, VertexPositionColorTexture bottomLeft, VertexPositionColorTexture bottomRight) { this.Texture = texture; + this.TextureHash = texture.GetHashCode(); this.Depth = depth; this.TopLeft = topLeft; this.TopRight = topRight; diff --git a/Sandbox/GameImpl.cs b/Sandbox/GameImpl.cs index e66e532..c062eb0 100644 --- a/Sandbox/GameImpl.cs +++ b/Sandbox/GameImpl.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using FontStashSharp; @@ -13,6 +14,7 @@ using MLEM.Extensions; using MLEM.Font; using MLEM.Formatting; using MLEM.Formatting.Codes; +using MLEM.Graphics; using MLEM.Input; using MLEM.Misc; using MLEM.Startup; @@ -351,6 +353,16 @@ public class GameImpl : MlemGame { }); } this.UiSystem.Add("WidthTest", widthPanel); + + var batch = new StaticSpriteBatch(this.GraphicsDevice); + batch.BeginBatch(SpriteSortMode.FrontToBack); + var depth = 0F; + var items = new List(); + foreach (var r in atlas.Regions) + items.Add(batch.Add(r, new Vector2(50 + r.GetHashCode() % 200, 50), ColorHelper.FromHexRgb(r.GetHashCode()), 0, Vector2.Zero, 1, SpriteEffects.None, depth += 0.0001F)); + batch.Remove(items[5]); + batch.EndBatch(); + this.OnDraw += (_, _) => batch.Draw(null, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(3)); } protected override void DoUpdate(GameTime gameTime) { From 856d67b6cf5bcfce5cee955cec0359b2ba854eaf Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Mon, 12 Sep 2022 22:57:01 +0200 Subject: [PATCH 06/33] Second pass at StaticSpriteBatch optimizations --- MLEM/Graphics/StaticSpriteBatch.cs | 108 +++++++++++++++-------------- Sandbox/GameImpl.cs | 4 +- 2 files changed, 58 insertions(+), 54 deletions(-) diff --git a/MLEM/Graphics/StaticSpriteBatch.cs b/MLEM/Graphics/StaticSpriteBatch.cs index 56ae232..daf437b 100644 --- a/MLEM/Graphics/StaticSpriteBatch.cs +++ b/MLEM/Graphics/StaticSpriteBatch.cs @@ -10,7 +10,7 @@ using System.IO; namespace MLEM.Graphics { /// - /// A static sprite batch is a variation of that keeps all batched items in a , allowing for them to be drawn multiple times. + /// A static sprite batch is a highly optimized variation of that keeps all batched items in a , allowing for them to be drawn multiple times. /// To add items to a static sprite batch, use to begin batching, to clear currently batched items, Add and its various overloads to add batch items, to remove them again, and to end batching. /// To draw the batched items, call . /// @@ -23,7 +23,7 @@ namespace MLEM.Graphics { /// /// The amount of vertices that are currently batched. /// - public int Vertices => this.currentItems.Count * 4; + public int Vertices => this.itemAmount * 4; /// /// The amount of vertex buffers that this static sprite batch has. /// To see the amount of buffers that are actually in use, see . @@ -43,14 +43,13 @@ namespace MLEM.Graphics { private readonly SpriteEffect spriteEffect; private readonly List vertexBuffers = new List(); private readonly List textures = new List(); - private readonly ISet currentItems = new HashSet(); - private readonly List sortedItems = new List(); + private readonly SortedList> items = new SortedList>(); private IndexBuffer indices; private bool batching; private bool batchChanged; private SpriteSortMode sortMode; - private Comparer comparer; + private int itemAmount; /// /// Creates a new static sprite batch with the given @@ -74,11 +73,16 @@ namespace MLEM.Graphics { if (sortMode == SpriteSortMode.Immediate) throw new ArgumentOutOfRangeException(nameof(sortMode), "Cannot use sprite sort mode Immediate for static batching"); - // update comparer and re-sort our list if our sort mode changed + // if the sort mode changed (which should be very rare in practice), we have to re-sort our list if (this.sortMode != sortMode) { this.sortMode = sortMode; - this.comparer = Comparer.Create(StaticSpriteBatch.CreateComparison(sortMode)); - this.sortedItems.Sort(this.comparer); + if (this.items.Count > 0) { + var tempItems = this.items.Values.SelectMany(s => s).ToArray(); + this.items.Clear(); + foreach (var item in tempItems) + this.AddItemToSet(item); + this.batchChanged = true; + } } this.batching = true; @@ -104,29 +108,25 @@ namespace MLEM.Graphics { // fill vertex buffers var dataIndex = 0; Texture2D texture = null; - // we use RemoveAll to iterate safely while being able to remove - this.sortedItems.RemoveAll(i => { - // we remove items in here to avoid having to search for them when removing them in Remove - if (i.Removed) - return true; - - // if the texture changes, we also have to start a new buffer! - if (dataIndex > 0 && (i.Texture != texture || dataIndex >= StaticSpriteBatch.Data.Length)) { - this.FillBuffer(this.FilledBuffers++, texture, StaticSpriteBatch.Data); - dataIndex = 0; + foreach (var itemSet in this.items.Values) { + foreach (var item in itemSet) { + // if the texture changes, we also have to start a new buffer! + if (dataIndex > 0 && (item.Texture != texture || dataIndex >= StaticSpriteBatch.Data.Length)) { + this.FillBuffer(this.FilledBuffers++, texture, StaticSpriteBatch.Data); + dataIndex = 0; + } + StaticSpriteBatch.Data[dataIndex++] = item.TopLeft; + StaticSpriteBatch.Data[dataIndex++] = item.TopRight; + StaticSpriteBatch.Data[dataIndex++] = item.BottomLeft; + StaticSpriteBatch.Data[dataIndex++] = item.BottomRight; + texture = item.Texture; } - StaticSpriteBatch.Data[dataIndex++] = i.TopLeft; - StaticSpriteBatch.Data[dataIndex++] = i.TopRight; - StaticSpriteBatch.Data[dataIndex++] = i.BottomLeft; - StaticSpriteBatch.Data[dataIndex++] = i.BottomRight; - texture = i.Texture; - return false; - }); + } if (dataIndex > 0) this.FillBuffer(this.FilledBuffers++, texture, StaticSpriteBatch.Data); // ensure we have enough indices - var maxItems = Math.Min(this.currentItems.Count, StaticSpriteBatch.MaxBatchItems); + var maxItems = Math.Min(this.itemAmount, StaticSpriteBatch.MaxBatchItems); // each item has 2 triangles which each have 3 indices if (this.indices == null || this.indices.IndexCount < 6 * maxItems) { var newIndices = new short[6 * maxItems]; @@ -150,10 +150,6 @@ namespace MLEM.Graphics { this.indices = new IndexBuffer(this.graphicsDevice, IndexElementSize.SixteenBits, newIndices.Length, BufferUsage.WriteOnly); this.indices.SetData(newIndices); } - - // sanity check, this shouldn't be able to happen - if (this.currentItems.Count != this.sortedItems.Count) - throw new InvalidOperationException("Current and sorted items have mismatched sizes"); } /// @@ -184,7 +180,7 @@ namespace MLEM.Graphics { for (var i = 0; i < this.FilledBuffers; i++) { var buffer = this.vertexBuffers[i]; var texture = this.textures[i]; - var verts = Math.Min(this.currentItems.Count * 4 - totalIndex, buffer.VertexCount); + var verts = Math.Min(this.itemAmount * 4 - totalIndex, buffer.VertexCount); this.graphicsDevice.SetVertexBuffer(buffer); if (effect != null) { @@ -368,10 +364,12 @@ namespace MLEM.Graphics { public bool Remove(Item item) { if (!this.batching) throw new InvalidOperationException("Not batching"); - if (!item.Removed && this.currentItems.Remove(item)) { + var key = item.GetSortKey(this.sortMode); + if (this.items.TryGetValue(key, out var itemSet) && itemSet.Remove(item)) { + if (itemSet.Count <= 0) + this.items.Remove(key); + this.itemAmount--; this.batchChanged = true; - // this item will only actually get removed from sortedItems in EndBatch for performance - item.Removed = true; return true; } return false; @@ -385,10 +383,10 @@ namespace MLEM.Graphics { public void ClearBatch() { if (!this.batching) throw new InvalidOperationException("Not batching"); - this.currentItems.Clear(); - this.sortedItems.Clear(); + this.items.Clear(); this.textures.Clear(); this.FilledBuffers = 0; + this.itemAmount = 0; this.batchChanged = true; } @@ -441,10 +439,8 @@ namespace MLEM.Graphics { if (!this.batching) throw new InvalidOperationException("Not batching"); var item = new Item(texture, depth, tl, tr, bl, br); - this.currentItems.Add(item); - // add item in a sorted fashion - var pos = this.sortedItems.BinarySearch(item, this.comparer); - this.sortedItems.Insert(pos >= 0 ? pos : ~pos, item); + this.AddItemToSet(item); + this.itemAmount++; this.batchChanged = true; return item; } @@ -464,17 +460,13 @@ namespace MLEM.Graphics { #endif } - private static Comparison CreateComparison(SpriteSortMode sortMode) { - switch (sortMode) { - case SpriteSortMode.Texture: - return (i1, i2) => i1.TextureHash.CompareTo(i2.TextureHash); - case SpriteSortMode.BackToFront: - return (i1, i2) => i2.Depth.CompareTo(i1.Depth); - case SpriteSortMode.FrontToBack: - return (i1, i2) => i1.Depth.CompareTo(i2.Depth); - default: - return (i1, i2) => 0; + private void AddItemToSet(Item item) { + var sortKey = item.GetSortKey(this.sortMode); + if (!this.items.TryGetValue(sortKey, out var itemSet)) { + itemSet = new HashSet(); + this.items.Add(sortKey, itemSet); } + itemSet.Add(item); } /// @@ -484,17 +476,14 @@ namespace MLEM.Graphics { public class Item { internal readonly Texture2D Texture; - internal readonly int TextureHash; internal readonly float Depth; internal readonly VertexPositionColorTexture TopLeft; internal readonly VertexPositionColorTexture TopRight; internal readonly VertexPositionColorTexture BottomLeft; internal readonly VertexPositionColorTexture BottomRight; - internal bool Removed; internal Item(Texture2D texture, float depth, VertexPositionColorTexture topLeft, VertexPositionColorTexture topRight, VertexPositionColorTexture bottomLeft, VertexPositionColorTexture bottomRight) { this.Texture = texture; - this.TextureHash = texture.GetHashCode(); this.Depth = depth; this.TopLeft = topLeft; this.TopRight = topRight; @@ -502,6 +491,19 @@ namespace MLEM.Graphics { this.BottomRight = bottomRight; } + internal float GetSortKey(SpriteSortMode sortMode) { + switch (sortMode) { + case SpriteSortMode.Texture: + return this.Texture.GetHashCode(); + case SpriteSortMode.BackToFront: + return -this.Depth; + case SpriteSortMode.FrontToBack: + return this.Depth; + default: + return 0; + } + } + } #if FNA diff --git a/Sandbox/GameImpl.cs b/Sandbox/GameImpl.cs index c062eb0..f8e79c3 100644 --- a/Sandbox/GameImpl.cs +++ b/Sandbox/GameImpl.cs @@ -355,13 +355,15 @@ public class GameImpl : MlemGame { this.UiSystem.Add("WidthTest", widthPanel); var batch = new StaticSpriteBatch(this.GraphicsDevice); - batch.BeginBatch(SpriteSortMode.FrontToBack); + batch.BeginBatch(SpriteSortMode.Deferred); var depth = 0F; var items = new List(); foreach (var r in atlas.Regions) items.Add(batch.Add(r, new Vector2(50 + r.GetHashCode() % 200, 50), ColorHelper.FromHexRgb(r.GetHashCode()), 0, Vector2.Zero, 1, SpriteEffects.None, depth += 0.0001F)); batch.Remove(items[5]); batch.EndBatch(); + batch.BeginBatch(SpriteSortMode.BackToFront); + batch.EndBatch(); this.OnDraw += (_, _) => batch.Draw(null, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(3)); } From eadabf391937b5e95c420d62be145cac6c393bcf Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Mon, 12 Sep 2022 23:09:36 +0200 Subject: [PATCH 07/33] use SortedDictionary for StaticSpriteBatch --- MLEM/Graphics/StaticSpriteBatch.cs | 2 +- Sandbox/GameImpl.cs | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/MLEM/Graphics/StaticSpriteBatch.cs b/MLEM/Graphics/StaticSpriteBatch.cs index daf437b..fec80ac 100644 --- a/MLEM/Graphics/StaticSpriteBatch.cs +++ b/MLEM/Graphics/StaticSpriteBatch.cs @@ -43,7 +43,7 @@ namespace MLEM.Graphics { private readonly SpriteEffect spriteEffect; private readonly List vertexBuffers = new List(); private readonly List textures = new List(); - private readonly SortedList> items = new SortedList>(); + private readonly SortedDictionary> items = new SortedDictionary>(); private IndexBuffer indices; private bool batching; diff --git a/Sandbox/GameImpl.cs b/Sandbox/GameImpl.cs index f8e79c3..7b51c96 100644 --- a/Sandbox/GameImpl.cs +++ b/Sandbox/GameImpl.cs @@ -355,15 +355,24 @@ public class GameImpl : MlemGame { this.UiSystem.Add("WidthTest", widthPanel); var batch = new StaticSpriteBatch(this.GraphicsDevice); - batch.BeginBatch(SpriteSortMode.Deferred); + batch.BeginBatch(); var depth = 0F; var items = new List(); foreach (var r in atlas.Regions) items.Add(batch.Add(r, new Vector2(50 + r.GetHashCode() % 200, 50), ColorHelper.FromHexRgb(r.GetHashCode()), 0, Vector2.Zero, 1, SpriteEffects.None, depth += 0.0001F)); batch.Remove(items[5]); batch.EndBatch(); - batch.BeginBatch(SpriteSortMode.BackToFront); - batch.EndBatch(); + var sortMode = SpriteSortMode.Deferred; + this.OnUpdate += (_, _) => { + if (MlemGame.Input.IsPressed(Keys.S)) { + do { + sortMode = (SpriteSortMode) (((int) sortMode + 1) % 5); + } while (sortMode == SpriteSortMode.Immediate); + Console.WriteLine(sortMode); + batch.BeginBatch(sortMode); + batch.EndBatch(); + } + }; this.OnDraw += (_, _) => batch.Draw(null, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(3)); } From b4e1b00c88f1549f32cd1b4d874f9269c0152df6 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Mon, 12 Sep 2022 23:51:12 +0200 Subject: [PATCH 08/33] finished static sprite batch optimizations --- CHANGELOG.md | 2 +- MLEM/Graphics/StaticSpriteBatch.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a31e600..d8dda3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ Improvements - Improved EnumHelper.GetValues signature to return an array - Allow using external gesture handling alongside InputHandler through ExternalGestureHandling - Discard old data when updating a StaticSpriteBatch -- Drastically improved StaticSpriteBatch batching performance +- **Drastically improved StaticSpriteBatch batching performance** Fixes - Fixed TokenizedString handling trailing spaces incorrectly in the last line of non-left aligned text diff --git a/MLEM/Graphics/StaticSpriteBatch.cs b/MLEM/Graphics/StaticSpriteBatch.cs index fec80ac..e251fc6 100644 --- a/MLEM/Graphics/StaticSpriteBatch.cs +++ b/MLEM/Graphics/StaticSpriteBatch.cs @@ -43,6 +43,7 @@ namespace MLEM.Graphics { private readonly SpriteEffect spriteEffect; private readonly List vertexBuffers = new List(); private readonly List textures = new List(); + // TODO this can still be optimized by not giving items with a unique depth a single-entry set immediately private readonly SortedDictionary> items = new SortedDictionary>(); private IndexBuffer indices; From df2d102d8e36b4c8e24a61f8074134b2ef8c19dc Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Tue, 13 Sep 2022 11:57:28 +0200 Subject: [PATCH 09/33] further improved StaticSpriteBatch performance --- MLEM/Graphics/StaticSpriteBatch.cs | 90 ++++++++++++++++++++++++------ Sandbox/GameImpl.cs | 10 +++- 2 files changed, 81 insertions(+), 19 deletions(-) diff --git a/MLEM/Graphics/StaticSpriteBatch.cs b/MLEM/Graphics/StaticSpriteBatch.cs index e251fc6..a707db7 100644 --- a/MLEM/Graphics/StaticSpriteBatch.cs +++ b/MLEM/Graphics/StaticSpriteBatch.cs @@ -43,13 +43,12 @@ namespace MLEM.Graphics { private readonly SpriteEffect spriteEffect; private readonly List vertexBuffers = new List(); private readonly List textures = new List(); - // TODO this can still be optimized by not giving items with a unique depth a single-entry set immediately - private readonly SortedDictionary> items = new SortedDictionary>(); + private readonly SortedDictionary items = new SortedDictionary(); + private SpriteSortMode sortMode = SpriteSortMode.Texture; private IndexBuffer indices; private bool batching; private bool batchChanged; - private SpriteSortMode sortMode; private int itemAmount; /// @@ -65,20 +64,20 @@ namespace MLEM.Graphics { /// Begins batching. /// Call this method before calling Add or any of its overloads. /// - /// The drawing order for sprite drawing. by default, since it is the best in terms of rendering performance. Note that is not supported. + /// The drawing order for sprite drawing. When is passed, the last used sort mode will be used again. The initial sort mode is . Note that is not supported. /// Thrown if this batch is currently batching already /// Thrown if the is , which is not supported. - public void BeginBatch(SpriteSortMode sortMode = SpriteSortMode.Texture) { + public void BeginBatch(SpriteSortMode? sortMode = null) { if (this.batching) throw new InvalidOperationException("Already batching"); if (sortMode == SpriteSortMode.Immediate) throw new ArgumentOutOfRangeException(nameof(sortMode), "Cannot use sprite sort mode Immediate for static batching"); // if the sort mode changed (which should be very rare in practice), we have to re-sort our list - if (this.sortMode != sortMode) { - this.sortMode = sortMode; + if (sortMode != null && this.sortMode != sortMode) { + this.sortMode = sortMode.Value; if (this.items.Count > 0) { - var tempItems = this.items.Values.SelectMany(s => s).ToArray(); + var tempItems = this.items.Values.SelectMany(s => s.Items).ToArray(); this.items.Clear(); foreach (var item in tempItems) this.AddItemToSet(item); @@ -110,7 +109,7 @@ namespace MLEM.Graphics { var dataIndex = 0; Texture2D texture = null; foreach (var itemSet in this.items.Values) { - foreach (var item in itemSet) { + foreach (var item in itemSet.Items) { // if the texture changes, we also have to start a new buffer! if (dataIndex > 0 && (item.Texture != texture || dataIndex >= StaticSpriteBatch.Data.Length)) { this.FillBuffer(this.FilledBuffers++, texture, StaticSpriteBatch.Data); @@ -355,6 +354,21 @@ namespace MLEM.Graphics { return this.Add(texture, destinationRectangle, null, color); } + /// + /// Adds an item to this batch. + /// Note that this batch needs to currently be batching, meaning has to have been called previously. + /// + /// The item to add. + /// The added , for chaining. + public Item Add(Item item) { + if (!this.batching) + throw new InvalidOperationException("Not batching"); + this.AddItemToSet(item); + this.itemAmount++; + this.batchChanged = true; + return item; + } + /// /// Removes the given item from this batch. /// Note that this batch needs to currently be batching, meaning has to have been called previously. @@ -367,7 +381,7 @@ namespace MLEM.Graphics { throw new InvalidOperationException("Not batching"); var key = item.GetSortKey(this.sortMode); if (this.items.TryGetValue(key, out var itemSet) && itemSet.Remove(item)) { - if (itemSet.Count <= 0) + if (itemSet.IsEmpty) this.items.Remove(key); this.itemAmount--; this.batchChanged = true; @@ -437,13 +451,7 @@ namespace MLEM.Graphics { } private Item Add(Texture2D texture, float depth, VertexPositionColorTexture tl, VertexPositionColorTexture tr, VertexPositionColorTexture bl, VertexPositionColorTexture br) { - if (!this.batching) - throw new InvalidOperationException("Not batching"); - var item = new Item(texture, depth, tl, tr, bl, br); - this.AddItemToSet(item); - this.itemAmount++; - this.batchChanged = true; - return item; + return this.Add(new Item(texture, depth, tl, tr, bl, br)); } private void FillBuffer(int index, Texture2D texture, VertexPositionColorTexture[] data) { @@ -464,7 +472,7 @@ namespace MLEM.Graphics { private void AddItemToSet(Item item) { var sortKey = item.GetSortKey(this.sortMode); if (!this.items.TryGetValue(sortKey, out var itemSet)) { - itemSet = new HashSet(); + itemSet = new ItemSet(); this.items.Add(sortKey, itemSet); } itemSet.Add(item); @@ -507,6 +515,52 @@ namespace MLEM.Graphics { } + private class ItemSet { + + public IEnumerable Items { + get { + if (this.items != null) + return this.items; + if (this.single != null) + return Enumerable.Repeat(this.single, 1); + return Enumerable.Empty(); + } + } + public bool IsEmpty => this.items == null && this.single == null; + + private HashSet items; + private Item single; + + public void Add(Item item) { + if (this.items != null) { + this.items.Add(item); + } else if (this.single != null) { + this.items = new HashSet(); + this.items.Add(this.single); + this.items.Add(item); + this.single = null; + } else { + this.single = item; + } + } + + public bool Remove(Item item) { + if (this.items != null && this.items.Remove(item)) { + if (this.items.Count <= 1) { + this.single = this.items.Single(); + this.items = null; + } + return true; + } else if (this.single == item) { + this.single = null; + return true; + } else { + return false; + } + } + + } + #if FNA private class SpriteEffect : Effect { diff --git a/Sandbox/GameImpl.cs b/Sandbox/GameImpl.cs index 7b51c96..7314feb 100644 --- a/Sandbox/GameImpl.cs +++ b/Sandbox/GameImpl.cs @@ -360,7 +360,6 @@ public class GameImpl : MlemGame { var items = new List(); foreach (var r in atlas.Regions) items.Add(batch.Add(r, new Vector2(50 + r.GetHashCode() % 200, 50), ColorHelper.FromHexRgb(r.GetHashCode()), 0, Vector2.Zero, 1, SpriteEffects.None, depth += 0.0001F)); - batch.Remove(items[5]); batch.EndBatch(); var sortMode = SpriteSortMode.Deferred; this.OnUpdate += (_, _) => { @@ -371,6 +370,15 @@ public class GameImpl : MlemGame { Console.WriteLine(sortMode); batch.BeginBatch(sortMode); batch.EndBatch(); + } else { + for (var i = 0; i < items.Count; i++) { + if (MlemGame.Input.IsPressed(Keys.D1 + i)) { + batch.BeginBatch(); + if (!batch.Remove(items[i])) + batch.Add(items[i]); + batch.EndBatch(); + } + } } }; this.OnDraw += (_, _) => batch.Draw(null, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(3)); From 55735b4c6448fa4373d4beaa91754503f91018f8 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Tue, 13 Sep 2022 14:27:49 +0200 Subject: [PATCH 10/33] Added Element.OnAddedToUi and Element.OnRemovedFromUi --- CHANGELOG.md | 1 + MLEM.Ui/Elements/Element.cs | 14 ++++++++++++-- MLEM.Ui/UiSystem.cs | 2 ++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8dda3e..8d505d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ Fixes Additions - Added some extension methods for querying Anchor types - Added Element.AutoSizeAddedAbsolute to allow for more granular control of auto-sizing +- Added Element.OnAddedToUi and Element.OnRemovedFromUi Improvements - Allow elements to auto-adjust their size even when their children are aligned oddly diff --git a/MLEM.Ui/Elements/Element.cs b/MLEM.Ui/Elements/Element.cs index ccf3748..4e168f0 100644 --- a/MLEM.Ui/Elements/Element.cs +++ b/MLEM.Ui/Elements/Element.cs @@ -407,14 +407,22 @@ namespace MLEM.Ui.Elements { /// public GamepadNextElementCallback GetGamepadNextElement; /// - /// Event that is called when a child is added to this element using + /// Event that is called when a child or any level of grandchild is added to this element using /// public OtherElementCallback OnChildAdded; /// - /// Event that is called when a child is removed from this element using + /// Event that is called when a child or any level of grandchild is removed from this element using /// public OtherElementCallback OnChildRemoved; /// + /// Event that is called when this element is added to a , that is, when this element's is set to a non- value. + /// + public GenericCallback OnAddedToUi; + /// + /// Event that is called when this element is removed from a , that is, when this element's is set to . + /// + public GenericCallback OnRemovedFromUi; + /// /// Event that is called when this element's method is called, which also happens in . /// This event is useful for unregistering global event handlers when this object should be destroyed. /// @@ -497,6 +505,7 @@ namespace MLEM.Ui.Elements { element.AndChildren(e => { e.Root = this.Root; e.System = this.System; + e.OnAddedToUi?.Invoke(e); this.Root?.InvokeOnElementAdded(e); this.OnChildAdded?.Invoke(this, e); }); @@ -520,6 +529,7 @@ namespace MLEM.Ui.Elements { element.AndChildren(e => { e.Root = null; e.System = null; + e.OnRemovedFromUi?.Invoke(e); this.Root?.InvokeOnElementRemoved(e); this.OnChildRemoved?.Invoke(this, e); }); diff --git a/MLEM.Ui/UiSystem.cs b/MLEM.Ui/UiSystem.cs index 2f4fd32..b9d78e7 100644 --- a/MLEM.Ui/UiSystem.cs +++ b/MLEM.Ui/UiSystem.cs @@ -347,6 +347,7 @@ namespace MLEM.Ui { root.Element.AndChildren(e => { e.Root = root; e.System = this; + e.OnAddedToUi?.Invoke(e); root.InvokeOnElementAdded(e); e.SetAreaDirty(); }); @@ -369,6 +370,7 @@ namespace MLEM.Ui { root.Element.AndChildren(e => { e.Root = null; e.System = null; + e.OnRemovedFromUi?.Invoke(e); root.InvokeOnElementRemoved(e); e.SetAreaDirty(); }); From d0c805cf18f8d54732102d5479a6e27b632947da Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Tue, 13 Sep 2022 15:44:12 +0200 Subject: [PATCH 11/33] Fixed Element.OnChildAdded and Element.OnChildRemoved being called for grandchildren when a child is added --- CHANGELOG.md | 1 + MLEM.Ui/Elements/Element.cs | 10 ++++++---- MLEM.Ui/UiSystem.cs | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d505d6..93824c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Fixes - Fixed paragraphs sometimes not updating their position properly when hidden because they're empty - Fixed panels sometimes not drawing children that came into view when their positions changed unexpectedly - Fixed UiMarkdownParser not parsing formatting in headings and blockquotes +- Fixed Element.OnChildAdded and Element.OnChildRemoved being called for grandchildren when a child is added ### MLEM.Data Improvements diff --git a/MLEM.Ui/Elements/Element.cs b/MLEM.Ui/Elements/Element.cs index 4e168f0..e0d7e54 100644 --- a/MLEM.Ui/Elements/Element.cs +++ b/MLEM.Ui/Elements/Element.cs @@ -407,11 +407,13 @@ namespace MLEM.Ui.Elements { /// public GamepadNextElementCallback GetGamepadNextElement; /// - /// Event that is called when a child or any level of grandchild is added to this element using + /// Event that is called when a child is added to this element using + /// Note that, while this event is only called for immediate children of this element, is called for all children and grandchildren. /// public OtherElementCallback OnChildAdded; /// - /// Event that is called when a child or any level of grandchild is removed from this element using + /// Event that is called when a child is removed from this element using . + /// Note that, while this event is only called for immediate children of this element, is called for all children and grandchildren. /// public OtherElementCallback OnChildRemoved; /// @@ -507,8 +509,8 @@ namespace MLEM.Ui.Elements { e.System = this.System; e.OnAddedToUi?.Invoke(e); this.Root?.InvokeOnElementAdded(e); - this.OnChildAdded?.Invoke(this, e); }); + this.OnChildAdded?.Invoke(this, element); this.SetSortedChildrenDirty(); element.SetAreaDirty(); return element; @@ -531,8 +533,8 @@ namespace MLEM.Ui.Elements { e.System = null; e.OnRemovedFromUi?.Invoke(e); this.Root?.InvokeOnElementRemoved(e); - this.OnChildRemoved?.Invoke(this, e); }); + this.OnChildRemoved?.Invoke(this, element); this.SetSortedChildrenDirty(); } diff --git a/MLEM.Ui/UiSystem.cs b/MLEM.Ui/UiSystem.cs index b9d78e7..1e4587b 100644 --- a/MLEM.Ui/UiSystem.cs +++ b/MLEM.Ui/UiSystem.cs @@ -572,7 +572,7 @@ namespace MLEM.Ui { /// public event Element.GenericCallback OnElementAdded; /// - /// Event that is invoked when a is removed rom this root element of any of its children. + /// Event that is invoked when a is removed rom this root element or any of its children. /// public event Element.GenericCallback OnElementRemoved; /// From a6fd2c052e4f85a6f9c0414b2a55623d05a1cd42 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Tue, 13 Sep 2022 16:14:36 +0200 Subject: [PATCH 12/33] Added ScrollBar.MouseDragScrolling Closes #5 --- CHANGELOG.md | 1 + Demos/UiDemo.cs | 4 +- MLEM.Ui/Elements/ScrollBar.cs | 69 ++++++++++++++++++++++------------- 3 files changed, 47 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93824c7..725726d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ Additions - Added some extension methods for querying Anchor types - Added Element.AutoSizeAddedAbsolute to allow for more granular control of auto-sizing - Added Element.OnAddedToUi and Element.OnRemovedFromUi +- Added ScrollBar.MouseDragScrolling Improvements - Allow elements to auto-adjust their size even when their children are aligned oddly diff --git a/Demos/UiDemo.cs b/Demos/UiDemo.cs index bc23244..c1edf93 100644 --- a/Demos/UiDemo.cs +++ b/Demos/UiDemo.cs @@ -68,7 +68,9 @@ namespace Demos { this.UiSystem.AutoScaleWithScreen = true; // create the root panel that all the other components sit on and add it to the ui system - this.root = new Panel(Anchor.Center, new Vector2(80, 100), Vector2.Zero, false, true); + this.root = new Panel(Anchor.Center, new Vector2(80, 100), Vector2.Zero, false, true) { + ScrollBar = {MouseDragScrolling = true} + }; // add the root to the demos' ui this.UiRoot.AddChild(this.root); diff --git a/MLEM.Ui/Elements/ScrollBar.cs b/MLEM.Ui/Elements/ScrollBar.cs index 541c282..e0fef5c 100644 --- a/MLEM.Ui/Elements/ScrollBar.cs +++ b/MLEM.Ui/Elements/ScrollBar.cs @@ -33,6 +33,16 @@ namespace MLEM.Ui.Elements { /// The texture of this scroll bar's scroller indicator /// public StyleProp ScrollerTexture; + /// + /// Whether smooth scrolling should be enabled for this scroll bar. + /// Smooth scrolling causes the to change gradually rather than instantly when scrolling. + /// + public StyleProp SmoothScrolling; + /// + /// The factor with which happens. + /// + public StyleProp SmoothScrollFactor; + /// /// The scroller's width and height /// @@ -86,7 +96,7 @@ namespace MLEM.Ui.Elements { /// /// This property is true while the user scrolls on the scroll bar using the mouse or touch input /// - public bool IsBeingScrolled => this.isMouseHeld || this.isDragging || this.isTouchHeld; + public bool IsBeingScrolled => this.isMouseScrolling || this.isMouseDragging || this.isTouchDragging || this.isTouchScrolling; /// /// This field determines if this scroll bar should automatically be hidden from a if there aren't enough children to allow for scrolling. /// @@ -99,18 +109,14 @@ namespace MLEM.Ui.Elements { !this.Horizontal ? 0 : this.CurrentValue / this.maxValue * (this.DisplayArea.Width - this.ScrollerSize.X * this.Scale), this.Horizontal ? 0 : this.CurrentValue / this.maxValue * (this.DisplayArea.Height - this.ScrollerSize.Y * this.Scale)); /// - /// Whether smooth scrolling should be enabled for this scroll bar. - /// Smooth scrolling causes the to change gradually rather than instantly when scrolling. + /// Whether this scroll bar should allow dragging the mouse over its attached 's content while holding the left mouse button to scroll, similarly to how scrolling using touch input works. /// - public StyleProp SmoothScrolling; - /// - /// The factor with which happens. - /// - public StyleProp SmoothScrollFactor; + public bool MouseDragScrolling; - private bool isMouseHeld; - private bool isDragging; - private bool isTouchHeld; + private bool isMouseScrolling; + private bool isMouseDragging; + private bool isTouchScrolling; + private bool isTouchDragging; private float maxValue; private float scrollAdded; private float currValue; @@ -141,18 +147,29 @@ namespace MLEM.Ui.Elements { // MOUSE INPUT var moused = this.Controls.MousedElement; - if (moused == this && this.Input.WasMouseButtonUp(MouseButton.Left) && this.Input.IsMouseButtonDown(MouseButton.Left)) { - this.isMouseHeld = true; + var wasMouseUp = this.Input.WasMouseButtonUp(MouseButton.Left); + var isMouseDown = this.Input.IsMouseButtonDown(MouseButton.Left); + if (moused == this && wasMouseUp && isMouseDown) { + this.isMouseScrolling = true; this.scrollStartOffset = this.TransformInverseAll(this.Input.ViewportMousePosition.ToVector2()) - this.ScrollerPosition; - } else if (this.isMouseHeld && !this.Input.IsMouseButtonDown(MouseButton.Left)) { - this.isMouseHeld = false; + } else if (!isMouseDown) { + this.isMouseScrolling = false; } - if (this.isMouseHeld) + if (this.isMouseScrolling) this.ScrollToPos(this.TransformInverseAll(this.Input.ViewportMousePosition.ToVector2())); - if (!this.Horizontal && moused != null && (moused == this.Parent || moused.GetParentTree().Contains(this.Parent))) { - var scroll = this.Input.LastScrollWheel - this.Input.ScrollWheel; - if (scroll != 0) - this.CurrentValue += this.StepPerScroll * Math.Sign(scroll); + if (!this.Horizontal) { + if (moused != null && (moused == this.Parent || moused.GetParentTree().Contains(this.Parent))) { + var scroll = this.Input.LastScrollWheel - this.Input.ScrollWheel; + if (scroll != 0) + this.CurrentValue += this.StepPerScroll * Math.Sign(scroll); + + if (this.MouseDragScrolling && moused != this && wasMouseUp && isMouseDown) + this.isMouseDragging = true; + } + if (!isMouseDown) + this.isMouseDragging = false; + if (this.isMouseDragging) + this.CurrentValue -= (this.Input.MousePosition.Y - this.Input.LastMousePosition.Y) / this.Scale; } // TOUCH INPUT @@ -162,29 +179,29 @@ namespace MLEM.Ui.Elements { // if the element under the drag's start position is on top of the panel, start dragging var touched = this.Parent.GetElementUnderPos(this.TransformInverseAll(drag.Position)); if (touched != null && touched != this) - this.isDragging = true; + this.isTouchDragging = true; // if we're dragging at all, then move the scroller - if (this.isDragging) + if (this.isTouchDragging) this.CurrentValue -= drag.Delta.Y / this.Scale; } else { - this.isDragging = false; + this.isTouchDragging = false; } } if (this.Input.ViewportTouchState.Count <= 0) { // if no touch has occured this tick, then reset the variable - this.isTouchHeld = false; + this.isTouchScrolling = false; } else { foreach (var loc in this.Input.ViewportTouchState) { var pos = this.TransformInverseAll(loc.Position); // if we just started touching and are on top of the scroller, then we should start scrolling if (this.DisplayArea.Contains(pos) && !loc.TryGetPreviousLocation(out _)) { - this.isTouchHeld = true; + this.isTouchScrolling = true; this.scrollStartOffset = pos - this.ScrollerPosition; break; } // scroll no matter if we're on the scroller right now - if (this.isTouchHeld) + if (this.isTouchScrolling) this.ScrollToPos(pos); } } From 38214a66a384024b80d0854f7274dc6772520e5e Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Tue, 13 Sep 2022 16:15:59 +0200 Subject: [PATCH 13/33] disable mouse drag scrolling in the ui demo --- Demos/UiDemo.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Demos/UiDemo.cs b/Demos/UiDemo.cs index c1edf93..bc23244 100644 --- a/Demos/UiDemo.cs +++ b/Demos/UiDemo.cs @@ -68,9 +68,7 @@ namespace Demos { this.UiSystem.AutoScaleWithScreen = true; // create the root panel that all the other components sit on and add it to the ui system - this.root = new Panel(Anchor.Center, new Vector2(80, 100), Vector2.Zero, false, true) { - ScrollBar = {MouseDragScrolling = true} - }; + this.root = new Panel(Anchor.Center, new Vector2(80, 100), Vector2.Zero, false, true); // add the root to the demos' ui this.UiRoot.AddChild(this.root); From 914b0d9c2d6f25207e6ed32cb3081ea58ed35fec Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 14 Sep 2022 10:40:52 +0200 Subject: [PATCH 14/33] - Improved DataTextureAtlas parsing - Added data and copy instructions to DataTextureAtlas --- CHANGELOG.md | 3 + MLEM.Data/DataTextureAtlas.cs | 132 ++++++++++++++++++++++++--------- Tests/Content/Texture.atlas | 14 +++- Tests/DataTextureAtlasTests.cs | 16 +++- 4 files changed, 125 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 725726d..a25f29d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,9 @@ Fixes - Fixed Element.OnChildAdded and Element.OnChildRemoved being called for grandchildren when a child is added ### MLEM.Data +Additions +- Added data and copy instructions to DataTextureAtlas + Improvements - Allow data texture atlas pivots and offsets to be negative - Made RuntimeTexturePacker restore texture region name and pivot when packing diff --git a/MLEM.Data/DataTextureAtlas.cs b/MLEM.Data/DataTextureAtlas.cs index 400a200..fc801f3 100644 --- a/MLEM.Data/DataTextureAtlas.cs +++ b/MLEM.Data/DataTextureAtlas.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -5,6 +6,8 @@ using System.Text.RegularExpressions; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; +using MLEM.Extensions; +using MLEM.Misc; using MLEM.Textures; #if FNA using MLEM.Extensions; @@ -18,15 +21,19 @@ namespace MLEM.Data { /// /// /// Data texture atlases are designed to be easy to write by hand. Because of this, their structure is very simple. - /// Each texture region defined in the atlas consists of its name, followed by a set of possible keywords and their arguments, separated by spaces. - /// The loc keyword defines the of the texture region as a rectangle whose origin is its top-left corner. It requires four arguments: x, y, width and height of the rectangle. - /// The (optional) piv keyword defines the of the texture region. It requires two arguments: x and y. If it is not supplied, the pivot defaults to the top-left corner of the texture region. - /// The (optional) off keyword defines an offset that is added onto the location and pivot of this texture region. This is useful when copying and pasting a previously defined texture region to create a second region that has a constant offset. It requires two arguments: The x and y offset. + /// Each texture region defined in the atlas consists of its names (where multiple names can be separated by whitespace), followed by a set of possible instructions and their arguments, also separated by whitespace. + /// + /// The loc (or location) instruction defines the of the texture region as a rectangle whose origin is its top-left corner. It requires four arguments: x, y, width and height of the rectangle. + /// The (optional) piv (or pivot) instruction defines the of the texture region. It requires two arguments: x and y. If it is not supplied, the pivot defaults to the top-left corner of the texture region. + /// The (optional) off (of offset) instruction defines an offset that is added onto the location and pivot of this texture region. This is useful when duplicating a previously defined texture region to create a second region that has a constant offset. It requires two arguments: The x and y offset. + /// The (optional and repeatable) cpy (or copy) instruction defines an additional texture region that should also be generated from the same data, but with a given offset that will be applied to the location and pivot. It requires three arguments: the copy region's name and the x and y offsets. + /// The (optional and repeatable) dat (or data) instruction defines a custom data point that can be added to the resulting 's data. It requires two arguments: the data point's name and the data point's value, the latter of which is also stored as a string value. + /// /// /// - /// The following entry defines a texture region with the name LongTableRight, whose will be a rectangle with X=32, Y=30, Width=64, Height=48, and whose will be a vector with X=80, Y=46. + /// The following entry defines a texture region with the names LongTableRight and LongTableUp, whose will be a rectangle with X=32, Y=30, Width=64, Height=48, and whose will be a vector with X=80, Y=46. /// - /// LongTableRight + /// LongTableRight LongTableUp /// loc 32 30 64 48 /// piv 80 46 /// @@ -69,6 +76,7 @@ namespace MLEM.Data { /// /// Loads a from the given loaded texture and texture data file. + /// For more information on data texture atlases, see the type documentation. /// /// The texture to use for this data texture atlas /// The content manager to use for loading @@ -78,41 +86,92 @@ namespace MLEM.Data { public static DataTextureAtlas LoadAtlasData(TextureRegion texture, ContentManager content, string infoPath, bool pivotRelative = false) { var info = Path.Combine(content.RootDirectory, infoPath); string text; - if (Path.IsPathRooted(info)) { - text = File.ReadAllText(info); - } else { - using (var reader = new StreamReader(TitleContainer.OpenStream(info))) - text = reader.ReadToEnd(); + try { + if (Path.IsPathRooted(info)) { + text = File.ReadAllText(info); + } else { + using (var reader = new StreamReader(TitleContainer.OpenStream(info))) + text = reader.ReadToEnd(); + } + } catch (Exception e) { + throw new ContentLoadException($"Couldn't load data texture atlas data from {info}", e); } var atlas = new DataTextureAtlas(texture); + var words = Regex.Split(text, @"\s+"); - // parse each texture region: " loc [piv ] [off ]" - foreach (Match match in Regex.Matches(text, @"(.+)\s+loc\s+([0-9+]+)\s+([0-9+]+)\s+([0-9+]+)\s+([0-9+]+)\s*(?:piv\s+([0-9.+-]+)\s+([0-9.+-]+))?\s*(?:off\s+([0-9.+-]+)\s+([0-9.+-]+))?")) { - // offset - var off = !match.Groups[8].Success ? Vector2.Zero : new Vector2( - float.Parse(match.Groups[8].Value, CultureInfo.InvariantCulture), - float.Parse(match.Groups[9].Value, CultureInfo.InvariantCulture)); + var namesOffsets = new List<(string, Vector2)>(); + var customData = new Dictionary(); + var location = Rectangle.Empty; + var pivot = Vector2.Zero; + var offset = Vector2.Zero; + for (var i = 0; i < words.Length; i++) { + var word = words[i]; + try { + switch (word) { + case "loc": + case "location": + location = new Rectangle( + int.Parse(words[i + 1], CultureInfo.InvariantCulture), int.Parse(words[i + 2], CultureInfo.InvariantCulture), + int.Parse(words[i + 3], CultureInfo.InvariantCulture), int.Parse(words[i + 4], CultureInfo.InvariantCulture)); + i += 4; + break; + case "piv": + case "pivot": + pivot = new Vector2( + float.Parse(words[i + 1], CultureInfo.InvariantCulture), + float.Parse(words[i + 2], CultureInfo.InvariantCulture)); + i += 2; + break; + case "off": + case "offset": + offset = new Vector2( + float.Parse(words[i + 1], CultureInfo.InvariantCulture), + float.Parse(words[i + 2], CultureInfo.InvariantCulture)); + i += 2; + break; + case "cpy": + case "copy": + var copyOffset = new Vector2( + float.Parse(words[i + 2], CultureInfo.InvariantCulture), + float.Parse(words[i + 3], CultureInfo.InvariantCulture)); + namesOffsets.Add((words[i + 1], copyOffset)); + i += 3; + break; + case "dat": + case "data": + customData.Add(words[i + 1], words[i + 2]); + i += 2; + break; + default: + // if we have a location for the previous regions, they're valid so we add them + if (location != Rectangle.Empty && namesOffsets.Count > 0) { + location.Offset(offset.ToPoint()); + pivot += offset; + if (!pivotRelative) + pivot -= location.Location.ToVector2(); - // location - var loc = new Rectangle( - int.Parse(match.Groups[2].Value), int.Parse(match.Groups[3].Value), - int.Parse(match.Groups[4].Value), int.Parse(match.Groups[5].Value)); - loc.Offset(off.ToPoint()); + foreach (var (name, off) in namesOffsets) { + var region = new TextureRegion(texture, location.OffsetCopy(off.ToPoint())) { + PivotPixels = pivot + off, + Name = name + }; + foreach (var kv in customData) + region.SetData(kv.Key, kv.Value); + atlas.regions.Add(name, region); + } + namesOffsets.Clear(); + } - // pivot - var piv = !match.Groups[6].Success ? Vector2.Zero : off + new Vector2( - float.Parse(match.Groups[6].Value, CultureInfo.InvariantCulture) - (pivotRelative ? 0 : loc.X), - float.Parse(match.Groups[7].Value, CultureInfo.InvariantCulture) - (pivotRelative ? 0 : loc.Y)); - - foreach (var name in Regex.Split(match.Groups[1].Value, @"\s")) { - var trimmed = name.Trim(); - if (trimmed.Length <= 0) - continue; - var region = new TextureRegion(texture, loc) { - PivotPixels = piv, - Name = trimmed - }; - atlas.regions.Add(trimmed, region); + // we're starting a new region (or adding another name for a new region), so clear old data + namesOffsets.Add((word.Trim(), Vector2.Zero)); + customData.Clear(); + location = Rectangle.Empty; + pivot = Vector2.Zero; + offset = Vector2.Zero; + break; + } + } catch (Exception e) { + throw new ContentLoadException($"Couldn't parse data texture atlas instruction {word} for region(s) {string.Join(", ", namesOffsets)}", e); } } @@ -128,6 +187,7 @@ namespace MLEM.Data { /// /// Loads a from the given texture and texture data file. + /// For more information on data texture atlases, see the type documentation. /// /// The content manager to use for loading /// The path to the texture file diff --git a/Tests/Content/Texture.atlas b/Tests/Content/Texture.atlas index fd90b79..ecf9128 100644 --- a/Tests/Content/Texture.atlas +++ b/Tests/Content/Texture.atlas @@ -13,11 +13,19 @@ TestRegionNegativePivot loc 0 32 +16 16 piv -32 +46 +DataTest +loc 0 0 16 16 +dat DataPoint1 ThisIsSomeData +dat DataPoint2 3.5 +dat DataPoint3 --- + LongTableUp -loc 0 32 64 48 piv 16 48 +loc 0 32 64 48 +copy Copy1 16 0 +cpy Copy2 32 4 LongTableRight LongTableDown LongTableLeft -loc 32 30 64 48 +location 32 30 64 48 piv 80 46 -off 32 2 +offset 32 2 diff --git a/Tests/DataTextureAtlasTests.cs b/Tests/DataTextureAtlasTests.cs index 551e9ae..35ed699 100644 --- a/Tests/DataTextureAtlasTests.cs +++ b/Tests/DataTextureAtlasTests.cs @@ -13,7 +13,7 @@ namespace Tests { using var game = TestGame.Create(); using var texture = new Texture2D(game.GraphicsDevice, 1, 1); var atlas = DataTextureAtlas.LoadAtlasData(new TextureRegion(texture), game.RawContent, "Texture.atlas"); - Assert.AreEqual(atlas.Regions.Count(), 8); + Assert.AreEqual(11, atlas.Regions.Count()); // no added offset var table = atlas["LongTableUp"]; @@ -29,6 +29,20 @@ namespace Tests { var negativePivot = atlas["TestRegionNegativePivot"]; Assert.AreEqual(negativePivot.Area, new Rectangle(0, 32, 16, 16)); Assert.AreEqual(negativePivot.PivotPixels, new Vector2(-32, 46 - 32)); + + // copies + var copy1 = atlas["Copy1"]; + Assert.AreEqual(copy1.Area, new Rectangle(0 + 16, 32, 64, 48)); + Assert.AreEqual(copy1.PivotPixels, new Vector2(16 + 16, 48 - 32)); + var copy2 = atlas["Copy2"]; + Assert.AreEqual(copy2.Area, new Rectangle(0 + 32, 32 + 4, 64, 48)); + Assert.AreEqual(copy2.PivotPixels, new Vector2(16 + 32, 48 - 32 + 4)); + + // data + var data = atlas["DataTest"]; + Assert.AreEqual("ThisIsSomeData", data.GetData("DataPoint1")); + Assert.AreEqual("3.5", data.GetData("DataPoint2")); + Assert.AreEqual("---", data.GetData("DataPoint3")); } } From 4918a7276070408d15c5f2546d304681aabb224a Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 14 Sep 2022 11:04:51 +0200 Subject: [PATCH 15/33] fixed some issues with the new data texture atlas parser --- MLEM.Data/DataTextureAtlas.cs | 48 +++++++++++++++++++++------------- Tests/DataTextureAtlasTests.cs | 7 ++++- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/MLEM.Data/DataTextureAtlas.cs b/MLEM.Data/DataTextureAtlas.cs index fc801f3..deb12d8 100644 --- a/MLEM.Data/DataTextureAtlas.cs +++ b/MLEM.Data/DataTextureAtlas.cs @@ -143,24 +143,8 @@ namespace MLEM.Data { i += 2; break; default: - // if we have a location for the previous regions, they're valid so we add them - if (location != Rectangle.Empty && namesOffsets.Count > 0) { - location.Offset(offset.ToPoint()); - pivot += offset; - if (!pivotRelative) - pivot -= location.Location.ToVector2(); - - foreach (var (name, off) in namesOffsets) { - var region = new TextureRegion(texture, location.OffsetCopy(off.ToPoint())) { - PivotPixels = pivot + off, - Name = name - }; - foreach (var kv in customData) - region.SetData(kv.Key, kv.Value); - atlas.regions.Add(name, region); - } - namesOffsets.Clear(); - } + // if we have data for the previous regions, they're valid so we add them + AddCurrentRegions(); // we're starting a new region (or adding another name for a new region), so clear old data namesOffsets.Add((word.Trim(), Vector2.Zero)); @@ -175,7 +159,35 @@ namespace MLEM.Data { } } + // add the last region that was started on + AddCurrentRegions(); return atlas; + + void AddCurrentRegions() { + // the location is the only mandatory instruction, which is why we check it here + if (location == Rectangle.Empty || namesOffsets.Count <= 0) + return; + + location.Offset(offset.ToPoint()); + if (pivot != Vector2.Zero) { + pivot += offset; + if (!pivotRelative) + pivot -= location.Location.ToVector2(); + } + + foreach (var (name, off) in namesOffsets) { + var region = new TextureRegion(texture, location.OffsetCopy(off.ToPoint())) { + PivotPixels = pivot + off, + Name = name + }; + foreach (var kv in customData) + region.SetData(kv.Key, kv.Value); + atlas.regions.Add(name, region); + } + + // we only clear names offsets if the location was valid, otherwise we ignore multiple names for a region + namesOffsets.Clear(); + } } } diff --git a/Tests/DataTextureAtlasTests.cs b/Tests/DataTextureAtlasTests.cs index 35ed699..2c0bd23 100644 --- a/Tests/DataTextureAtlasTests.cs +++ b/Tests/DataTextureAtlasTests.cs @@ -15,13 +15,18 @@ namespace Tests { var atlas = DataTextureAtlas.LoadAtlasData(new TextureRegion(texture), game.RawContent, "Texture.atlas"); Assert.AreEqual(11, atlas.Regions.Count()); + // no pivot + var plant = atlas["Plant"]; + Assert.AreEqual(plant.Area, new Rectangle(96, 0, 16, 32)); + Assert.AreEqual(plant.PivotPixels, Vector2.Zero); + // no added offset var table = atlas["LongTableUp"]; Assert.AreEqual(table.Area, new Rectangle(0, 32, 64, 48)); Assert.AreEqual(table.PivotPixels, new Vector2(16, 48 - 32)); // added offset - var table2 = atlas["LongTableLeft"]; + var table2 = atlas["LongTableDown"]; Assert.AreEqual(table2.Area, new Rectangle(64, 32, 64, 48)); Assert.AreEqual(table2.PivotPixels, new Vector2(112 - 64, 48 - 32)); From 740c65a88735b3945e97848b91b9d925bf138413 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 14 Sep 2022 11:20:55 +0200 Subject: [PATCH 16/33] fixed new cpy instruction yielding incorrect pivots --- MLEM.Data/DataTextureAtlas.cs | 22 ++++++++++++---------- Tests/DataTextureAtlasTests.cs | 6 +++--- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/MLEM.Data/DataTextureAtlas.cs b/MLEM.Data/DataTextureAtlas.cs index deb12d8..a43a052 100644 --- a/MLEM.Data/DataTextureAtlas.cs +++ b/MLEM.Data/DataTextureAtlas.cs @@ -167,24 +167,26 @@ namespace MLEM.Data { // the location is the only mandatory instruction, which is why we check it here if (location == Rectangle.Empty || namesOffsets.Count <= 0) return; + foreach (var (name, addedOff) in namesOffsets) { + var loc = location; + var piv = pivot; + var off = offset + addedOff; - location.Offset(offset.ToPoint()); - if (pivot != Vector2.Zero) { - pivot += offset; - if (!pivotRelative) - pivot -= location.Location.ToVector2(); - } + loc.Offset(off); + if (piv != Vector2.Zero) { + piv += off; + if (!pivotRelative) + piv -= loc.Location.ToVector2(); + } - foreach (var (name, off) in namesOffsets) { - var region = new TextureRegion(texture, location.OffsetCopy(off.ToPoint())) { - PivotPixels = pivot + off, + var region = new TextureRegion(texture, loc) { + PivotPixels = piv, Name = name }; foreach (var kv in customData) region.SetData(kv.Key, kv.Value); atlas.regions.Add(name, region); } - // we only clear names offsets if the location was valid, otherwise we ignore multiple names for a region namesOffsets.Clear(); } diff --git a/Tests/DataTextureAtlasTests.cs b/Tests/DataTextureAtlasTests.cs index 2c0bd23..0b0f722 100644 --- a/Tests/DataTextureAtlasTests.cs +++ b/Tests/DataTextureAtlasTests.cs @@ -35,13 +35,13 @@ namespace Tests { Assert.AreEqual(negativePivot.Area, new Rectangle(0, 32, 16, 16)); Assert.AreEqual(negativePivot.PivotPixels, new Vector2(-32, 46 - 32)); - // copies + // copies (pivot pixels should be identical to LongTableUp because they're region-internal) var copy1 = atlas["Copy1"]; Assert.AreEqual(copy1.Area, new Rectangle(0 + 16, 32, 64, 48)); - Assert.AreEqual(copy1.PivotPixels, new Vector2(16 + 16, 48 - 32)); + Assert.AreEqual(copy1.PivotPixels, new Vector2(16, 48 - 32)); var copy2 = atlas["Copy2"]; Assert.AreEqual(copy2.Area, new Rectangle(0 + 32, 32 + 4, 64, 48)); - Assert.AreEqual(copy2.PivotPixels, new Vector2(16 + 32, 48 - 32 + 4)); + Assert.AreEqual(copy2.PivotPixels, new Vector2(16, 48 - 32)); // data var data = atlas["DataTest"]; From ff92a00e1a59816acea8557c5ffcbfe5e01f0a6d Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 14 Sep 2022 11:24:45 +0200 Subject: [PATCH 17/33] FNA doesn't support vector offsets --- MLEM.Data/DataTextureAtlas.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MLEM.Data/DataTextureAtlas.cs b/MLEM.Data/DataTextureAtlas.cs index a43a052..05a0906 100644 --- a/MLEM.Data/DataTextureAtlas.cs +++ b/MLEM.Data/DataTextureAtlas.cs @@ -6,7 +6,6 @@ using System.Text.RegularExpressions; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; -using MLEM.Extensions; using MLEM.Misc; using MLEM.Textures; #if FNA @@ -172,7 +171,7 @@ namespace MLEM.Data { var piv = pivot; var off = offset + addedOff; - loc.Offset(off); + loc.Offset(off.ToPoint()); if (piv != Vector2.Zero) { piv += off; if (!pivotRelative) From 7d8b14ee8dec5f52ca338f2fa2ecc721e4b2ece5 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 14 Sep 2022 11:59:28 +0200 Subject: [PATCH 18/33] added from instruction to DataTextureAtlas --- CHANGELOG.md | 2 +- MLEM.Data/DataTextureAtlas.cs | 16 +++++++++++++++- Tests/Content/Texture.atlas | 6 ++++-- Tests/DataTextureAtlasTests.cs | 9 +++++++-- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a25f29d..d11e246 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,7 @@ Fixes ### MLEM.Data Additions -- Added data and copy instructions to DataTextureAtlas +- Added data, from, and copy instructions to DataTextureAtlas Improvements - Allow data texture atlas pivots and offsets to be negative diff --git a/MLEM.Data/DataTextureAtlas.cs b/MLEM.Data/DataTextureAtlas.cs index 05a0906..11fe3a0 100644 --- a/MLEM.Data/DataTextureAtlas.cs +++ b/MLEM.Data/DataTextureAtlas.cs @@ -27,6 +27,7 @@ namespace MLEM.Data { /// The (optional) off (of offset) instruction defines an offset that is added onto the location and pivot of this texture region. This is useful when duplicating a previously defined texture region to create a second region that has a constant offset. It requires two arguments: The x and y offset. /// The (optional and repeatable) cpy (or copy) instruction defines an additional texture region that should also be generated from the same data, but with a given offset that will be applied to the location and pivot. It requires three arguments: the copy region's name and the x and y offsets. /// The (optional and repeatable) dat (or data) instruction defines a custom data point that can be added to the resulting 's data. It requires two arguments: the data point's name and the data point's value, the latter of which is also stored as a string value. + /// The (optional) frm (or from) instruction defines a texture region (defined before the current region) whose data should be copied. All data from the region will be copied, but adding additional instructions afterwards modifies the data. It requires one argument: the name of the region whose data to copy. If this instruction is used, the loc instruction is not required. /// /// /// @@ -141,6 +142,19 @@ namespace MLEM.Data { customData.Add(words[i + 1], words[i + 2]); i += 2; break; + case "frm": + case "from": + var fromRegion = atlas[words[i + 1]]; + customData.Clear(); + foreach (var key in fromRegion.GetDataKeys()) + customData.Add(key, fromRegion.GetData(key)); + location = fromRegion.Area; + pivot = fromRegion.PivotPixels; + if (pivot != Vector2.Zero && !pivotRelative) + pivot += location.Location.ToVector2(); + offset = Vector2.Zero; + i += 1; + break; default: // if we have data for the previous regions, they're valid so we add them AddCurrentRegions(); @@ -163,7 +177,7 @@ namespace MLEM.Data { return atlas; void AddCurrentRegions() { - // the location is the only mandatory instruction, which is why we check it here + // the location is the only mandatory information, which is why we check it here if (location == Rectangle.Empty || namesOffsets.Count <= 0) return; foreach (var (name, addedOff) in namesOffsets) { diff --git a/Tests/Content/Texture.atlas b/Tests/Content/Texture.atlas index ecf9128..a3c47f2 100644 --- a/Tests/Content/Texture.atlas +++ b/Tests/Content/Texture.atlas @@ -20,11 +20,13 @@ dat DataPoint2 3.5 dat DataPoint3 --- LongTableUp -piv 16 48 -loc 0 32 64 48 +piv 16 48 loc 0 32 64 48 copy Copy1 16 0 cpy Copy2 32 4 +Copy3 from +LongTableUp off 2 4 + LongTableRight LongTableDown LongTableLeft location 32 30 64 48 piv 80 46 diff --git a/Tests/DataTextureAtlasTests.cs b/Tests/DataTextureAtlasTests.cs index 0b0f722..cf4a02a 100644 --- a/Tests/DataTextureAtlasTests.cs +++ b/Tests/DataTextureAtlasTests.cs @@ -13,7 +13,7 @@ namespace Tests { using var game = TestGame.Create(); using var texture = new Texture2D(game.GraphicsDevice, 1, 1); var atlas = DataTextureAtlas.LoadAtlasData(new TextureRegion(texture), game.RawContent, "Texture.atlas"); - Assert.AreEqual(11, atlas.Regions.Count()); + Assert.AreEqual(12, atlas.Regions.Count()); // no pivot var plant = atlas["Plant"]; @@ -35,7 +35,7 @@ namespace Tests { Assert.AreEqual(negativePivot.Area, new Rectangle(0, 32, 16, 16)); Assert.AreEqual(negativePivot.PivotPixels, new Vector2(-32, 46 - 32)); - // copies (pivot pixels should be identical to LongTableUp because they're region-internal) + // cpy (pivot pixels should be identical to LongTableUp because they're region-internal) var copy1 = atlas["Copy1"]; Assert.AreEqual(copy1.Area, new Rectangle(0 + 16, 32, 64, 48)); Assert.AreEqual(copy1.PivotPixels, new Vector2(16, 48 - 32)); @@ -43,6 +43,11 @@ namespace Tests { Assert.AreEqual(copy2.Area, new Rectangle(0 + 32, 32 + 4, 64, 48)); Assert.AreEqual(copy2.PivotPixels, new Vector2(16, 48 - 32)); + // frm + var copy3 = atlas["Copy3"]; + Assert.AreEqual(copy3.Area, new Rectangle(0 + 2, 32 + 4, 64, 48)); + Assert.AreEqual(copy3.PivotPixels, new Vector2(16, 48 - 32)); + // data var data = atlas["DataTest"]; Assert.AreEqual("ThisIsSomeData", data.GetData("DataPoint1")); From c3c8b132da2389990f737d4dac2d22ebf57f4735 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 14 Sep 2022 12:19:05 +0200 Subject: [PATCH 19/33] fixed DataTextureAtlas frm instruction failing if the original texture has an offset --- MLEM.Data/DataTextureAtlas.cs | 4 +++- Tests/DataTextureAtlasTests.cs | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/MLEM.Data/DataTextureAtlas.cs b/MLEM.Data/DataTextureAtlas.cs index 11fe3a0..fcd15c5 100644 --- a/MLEM.Data/DataTextureAtlas.cs +++ b/MLEM.Data/DataTextureAtlas.cs @@ -6,6 +6,7 @@ using System.Text.RegularExpressions; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; +using MLEM.Extensions; using MLEM.Misc; using MLEM.Textures; #if FNA @@ -148,7 +149,8 @@ namespace MLEM.Data { customData.Clear(); foreach (var key in fromRegion.GetDataKeys()) customData.Add(key, fromRegion.GetData(key)); - location = fromRegion.Area; + // our main texture might be a sub-region already, so we have to take that into account + location = fromRegion.Area.OffsetCopy(new Point(-texture.U, -texture.V)); pivot = fromRegion.PivotPixels; if (pivot != Vector2.Zero && !pivotRelative) pivot += location.Location.ToVector2(); diff --git a/Tests/DataTextureAtlasTests.cs b/Tests/DataTextureAtlasTests.cs index cf4a02a..f81c344 100644 --- a/Tests/DataTextureAtlasTests.cs +++ b/Tests/DataTextureAtlasTests.cs @@ -9,43 +9,44 @@ namespace Tests { public class TestDataTextureAtlas { [Test] - public void Test() { + public void Test([Values(0, 4)] int regionX, [Values(0, 4)] int regionY) { using var game = TestGame.Create(); using var texture = new Texture2D(game.GraphicsDevice, 1, 1); - var atlas = DataTextureAtlas.LoadAtlasData(new TextureRegion(texture), game.RawContent, "Texture.atlas"); + var region = new TextureRegion(texture, regionX, regionY, 1, 1); + var atlas = DataTextureAtlas.LoadAtlasData(region, game.RawContent, "Texture.atlas"); Assert.AreEqual(12, atlas.Regions.Count()); // no pivot var plant = atlas["Plant"]; - Assert.AreEqual(plant.Area, new Rectangle(96, 0, 16, 32)); + Assert.AreEqual(plant.Area, new Rectangle(96 + regionX, 0 + regionY, 16, 32)); Assert.AreEqual(plant.PivotPixels, Vector2.Zero); // no added offset var table = atlas["LongTableUp"]; - Assert.AreEqual(table.Area, new Rectangle(0, 32, 64, 48)); + Assert.AreEqual(table.Area, new Rectangle(0 + regionX, 32 + regionY, 64, 48)); Assert.AreEqual(table.PivotPixels, new Vector2(16, 48 - 32)); // added offset var table2 = atlas["LongTableDown"]; - Assert.AreEqual(table2.Area, new Rectangle(64, 32, 64, 48)); + Assert.AreEqual(table2.Area, new Rectangle(64 + regionX, 32 + regionY, 64, 48)); Assert.AreEqual(table2.PivotPixels, new Vector2(112 - 64, 48 - 32)); // negative pivot var negativePivot = atlas["TestRegionNegativePivot"]; - Assert.AreEqual(negativePivot.Area, new Rectangle(0, 32, 16, 16)); + Assert.AreEqual(negativePivot.Area, new Rectangle(0 + regionX, 32 + regionY, 16, 16)); Assert.AreEqual(negativePivot.PivotPixels, new Vector2(-32, 46 - 32)); // cpy (pivot pixels should be identical to LongTableUp because they're region-internal) var copy1 = atlas["Copy1"]; - Assert.AreEqual(copy1.Area, new Rectangle(0 + 16, 32, 64, 48)); + Assert.AreEqual(copy1.Area, new Rectangle(0 + 16 + regionX, 32 + regionY, 64, 48)); Assert.AreEqual(copy1.PivotPixels, new Vector2(16, 48 - 32)); var copy2 = atlas["Copy2"]; - Assert.AreEqual(copy2.Area, new Rectangle(0 + 32, 32 + 4, 64, 48)); + Assert.AreEqual(copy2.Area, new Rectangle(0 + 32 + regionX, 32 + 4 + regionY, 64, 48)); Assert.AreEqual(copy2.PivotPixels, new Vector2(16, 48 - 32)); // frm var copy3 = atlas["Copy3"]; - Assert.AreEqual(copy3.Area, new Rectangle(0 + 2, 32 + 4, 64, 48)); + Assert.AreEqual(copy3.Area, new Rectangle(0 + 2 + regionX, 32 + 4 + regionY, 64, 48)); Assert.AreEqual(copy3.PivotPixels, new Vector2(16, 48 - 32)); // data From fc026ad0dedcb964068af118307516f66d827b67 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 14 Sep 2022 19:24:00 +0200 Subject: [PATCH 20/33] multi-target netstandard2.0 and net6.0 --- MLEM.Data/DataTextureAtlas.cs | 3 --- MLEM.Data/MLEM.Data.FNA.csproj | 2 +- MLEM.Data/MLEM.Data.csproj | 2 +- MLEM.Extended/MLEM.Extended.FNA.csproj | 2 +- MLEM.Extended/MLEM.Extended.csproj | 12 ++++++------ MLEM.Startup/MLEM.Startup.FNA.csproj | 2 +- MLEM.Startup/MLEM.Startup.csproj | 12 ++++++------ MLEM.Templates/MLEM.Templates.csproj | 10 +++++----- MLEM.Ui/MLEM.Ui.FNA.csproj | 2 +- MLEM.Ui/MLEM.Ui.csproj | 12 ++++++------ MLEM/Input/InputHandler.cs | 3 +++ MLEM/MLEM.FNA.csproj | 2 +- MLEM/MLEM.csproj | 2 +- MLEM/Misc/EnumHelper.cs | 6 +++++- 14 files changed, 38 insertions(+), 34 deletions(-) diff --git a/MLEM.Data/DataTextureAtlas.cs b/MLEM.Data/DataTextureAtlas.cs index fcd15c5..e163e72 100644 --- a/MLEM.Data/DataTextureAtlas.cs +++ b/MLEM.Data/DataTextureAtlas.cs @@ -9,9 +9,6 @@ using Microsoft.Xna.Framework.Graphics; using MLEM.Extensions; using MLEM.Misc; using MLEM.Textures; -#if FNA -using MLEM.Extensions; -#endif namespace MLEM.Data { /// diff --git a/MLEM.Data/MLEM.Data.FNA.csproj b/MLEM.Data/MLEM.Data.FNA.csproj index d4dae9c..b247ae1 100644 --- a/MLEM.Data/MLEM.Data.FNA.csproj +++ b/MLEM.Data/MLEM.Data.FNA.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.0;net6.0 true true MLEM.Data diff --git a/MLEM.Data/MLEM.Data.csproj b/MLEM.Data/MLEM.Data.csproj index 4d47947..109e6ff 100644 --- a/MLEM.Data/MLEM.Data.csproj +++ b/MLEM.Data/MLEM.Data.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.0;net6.0 true true NU1701 diff --git a/MLEM.Extended/MLEM.Extended.FNA.csproj b/MLEM.Extended/MLEM.Extended.FNA.csproj index dd8d810..7357d76 100644 --- a/MLEM.Extended/MLEM.Extended.FNA.csproj +++ b/MLEM.Extended/MLEM.Extended.FNA.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.0;net6.0 true true MLEM.Extended diff --git a/MLEM.Extended/MLEM.Extended.csproj b/MLEM.Extended/MLEM.Extended.csproj index f66d8fb..ff63ed3 100644 --- a/MLEM.Extended/MLEM.Extended.csproj +++ b/MLEM.Extended/MLEM.Extended.csproj @@ -1,10 +1,10 @@  - netstandard2.0 + netstandard2.0;net6.0 true true - + Ellpeck MLEM Library for Extending MonoGame extension that ties in with MonoGame.Extended and other MonoGame libraries @@ -16,10 +16,10 @@ Logo.png README.md - + - + all @@ -33,9 +33,9 @@ all - + - \ No newline at end of file + diff --git a/MLEM.Startup/MLEM.Startup.FNA.csproj b/MLEM.Startup/MLEM.Startup.FNA.csproj index 57e5474..2ff3745 100644 --- a/MLEM.Startup/MLEM.Startup.FNA.csproj +++ b/MLEM.Startup/MLEM.Startup.FNA.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.0;net6.0 true true MLEM.Startup diff --git a/MLEM.Startup/MLEM.Startup.csproj b/MLEM.Startup/MLEM.Startup.csproj index 95db618..871862c 100644 --- a/MLEM.Startup/MLEM.Startup.csproj +++ b/MLEM.Startup/MLEM.Startup.csproj @@ -1,11 +1,11 @@  - + - netstandard2.0 + netstandard2.0;net6.0 true true - + Ellpeck MLEM Library for Extending MonoGame combined with some other useful libraries into a quick Game startup class @@ -17,17 +17,17 @@ Logo.png README.md - + - + all - + diff --git a/MLEM.Templates/MLEM.Templates.csproj b/MLEM.Templates/MLEM.Templates.csproj index ac2d3e9..d3a5f13 100644 --- a/MLEM.Templates/MLEM.Templates.csproj +++ b/MLEM.Templates/MLEM.Templates.csproj @@ -1,14 +1,14 @@  - + - netstandard2.0 + netstandard2.0;net6.0 true false content true NU5128 - + Template MLEM Templates @@ -21,7 +21,7 @@ Logo.png README.md - + @@ -29,4 +29,4 @@ - \ No newline at end of file + diff --git a/MLEM.Ui/MLEM.Ui.FNA.csproj b/MLEM.Ui/MLEM.Ui.FNA.csproj index 23f0414..b3d713a 100644 --- a/MLEM.Ui/MLEM.Ui.FNA.csproj +++ b/MLEM.Ui/MLEM.Ui.FNA.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.0;net6.0 true true MLEM.Ui diff --git a/MLEM.Ui/MLEM.Ui.csproj b/MLEM.Ui/MLEM.Ui.csproj index e9aecaa..7f7727b 100644 --- a/MLEM.Ui/MLEM.Ui.csproj +++ b/MLEM.Ui/MLEM.Ui.csproj @@ -1,10 +1,10 @@  - netstandard2.0 + netstandard2.0;net6.0 true true - + Ellpeck A mouse, keyboard, gamepad and touch ready Ui system for MonoGame that features automatic anchoring, sizing and several ready-to-use element types @@ -16,18 +16,18 @@ Logo.png README.md - + - + all - + - \ No newline at end of file + diff --git a/MLEM/Input/InputHandler.cs b/MLEM/Input/InputHandler.cs index 9856a7f..af2110c 100644 --- a/MLEM/Input/InputHandler.cs +++ b/MLEM/Input/InputHandler.cs @@ -823,6 +823,7 @@ namespace MLEM.Input { downTime = DateTime.UtcNow - start; return true; } + downTime = default; return false; } @@ -851,6 +852,7 @@ namespace MLEM.Input { upTime = DateTime.UtcNow - start; return true; } + upTime = default; return false; } @@ -879,6 +881,7 @@ namespace MLEM.Input { lastPressTime = DateTime.UtcNow - start; return true; } + lastPressTime = default; return false; } diff --git a/MLEM/MLEM.FNA.csproj b/MLEM/MLEM.FNA.csproj index 2e28813..4f4ccdd 100644 --- a/MLEM/MLEM.FNA.csproj +++ b/MLEM/MLEM.FNA.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.0;net6.0 true true MLEM diff --git a/MLEM/MLEM.csproj b/MLEM/MLEM.csproj index 6eb80b7..ac55ac3 100644 --- a/MLEM/MLEM.csproj +++ b/MLEM/MLEM.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.0;net6.0 true true diff --git a/MLEM/Misc/EnumHelper.cs b/MLEM/Misc/EnumHelper.cs index 645f2e8..684aa19 100644 --- a/MLEM/Misc/EnumHelper.cs +++ b/MLEM/Misc/EnumHelper.cs @@ -22,8 +22,12 @@ namespace MLEM.Misc { /// /// The type whose enum to get /// An enumerable of the values of the enum, in declaration order. - public static T[] GetValues() { + public static T[] GetValues() where T : struct, Enum { + #if NET6_0_OR_GREATER + return Enum.GetValues(); + #else return (T[]) Enum.GetValues(typeof(T)); + #endif } } From 48735c3d36e99bf8f99ae950509ce3c0a45f7764 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 14 Sep 2022 21:17:43 +0200 Subject: [PATCH 21/33] Multi-target net452, making MLEM compatible with MonoGame for consoles --- CHANGELOG.md | 11 ++++++++ Demos.DesktopGL/Demos.DesktopGL.FNA.csproj | 1 + Demos/Demos.FNA.csproj | 1 + FontStashSharp | 2 +- .../Json/JsonTypeSafeGenericDataHolder.cs | 6 +++-- MLEM.Data/MLEM.Data.FNA.csproj | 7 ++--- MLEM.Data/MLEM.Data.csproj | 2 +- MLEM.Extended/MLEM.Extended.FNA.csproj | 5 ++-- MLEM.FNA.sln | 26 ++++++++++++------- MLEM.Startup/MLEM.Startup.FNA.csproj | 7 ++--- MLEM.Startup/MLEM.Startup.csproj | 4 +-- MLEM.Templates/MLEM.Templates.csproj | 2 +- MLEM.Ui/Elements/TextField.cs | 8 +++++- MLEM.Ui/MLEM.Ui.FNA.csproj | 7 ++--- MLEM.Ui/MLEM.Ui.csproj | 4 +-- MLEM.Ui/Parsers/UiParser.cs | 26 ++++++++++++------- MLEM.Ui/UiControls.cs | 4 +-- MLEM/Extensions/CollectionExtensions.cs | 4 +-- MLEM/Input/InputHandler.cs | 16 +++++++----- MLEM/Input/Keybind.cs | 12 +++++---- MLEM/MLEM.FNA.csproj | 7 +++-- MLEM/MLEM.csproj | 4 ++- MLEM/Misc/GenericDataHolder.cs | 8 +++--- Tests/Tests.FNA.csproj | 1 + 24 files changed, 112 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d11e246..6e2928a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Improvements - Allow using external gesture handling alongside InputHandler through ExternalGestureHandling - Discard old data when updating a StaticSpriteBatch - **Drastically improved StaticSpriteBatch batching performance** +- Multi-target net452, making MLEM compatible with MonoGame for consoles Fixes - Fixed TokenizedString handling trailing spaces incorrectly in the last line of non-left aligned text @@ -36,6 +37,7 @@ Improvements - Allow elements to auto-adjust their size even when their children are aligned oddly - Close other dropdowns when opening a dropdown - Generified UiMarkdownParser by adding abstract UiParser +- Multi-target net452, making MLEM compatible with MonoGame for consoles Fixes - Fixed parents of elements that prevent spill not being notified properly @@ -51,10 +53,19 @@ Additions Improvements - Allow data texture atlas pivots and offsets to be negative - Made RuntimeTexturePacker restore texture region name and pivot when packing +- Multi-target net452, making MLEM compatible with MonoGame for consoles Fixes - Fixed data texture atlases not allowing most characters in their region names +## MLEM.Extended +Improvements +- Multi-target net452, making MLEM compatible with MonoGame for consoles + +## MLEM.Startup +Improvements +- Multi-target net452, making MLEM compatible with MonoGame for consoles + ## 6.0.0 ### MLEM diff --git a/Demos.DesktopGL/Demos.DesktopGL.FNA.csproj b/Demos.DesktopGL/Demos.DesktopGL.FNA.csproj index 7933bf0..d2d77ce 100644 --- a/Demos.DesktopGL/Demos.DesktopGL.FNA.csproj +++ b/Demos.DesktopGL/Demos.DesktopGL.FNA.csproj @@ -7,6 +7,7 @@ MLEM Desktop Demos Demos.DesktopGL $(DefineConstants);FNA + MSB3270 DesktopGL diff --git a/Demos/Demos.FNA.csproj b/Demos/Demos.FNA.csproj index 6d088d2..0b64091 100644 --- a/Demos/Demos.FNA.csproj +++ b/Demos/Demos.FNA.csproj @@ -4,6 +4,7 @@ netstandard2.0 Demos $(DefineConstants);FNA + MSB3270 diff --git a/FontStashSharp b/FontStashSharp index 38849f3..6e6fc60 160000 --- a/FontStashSharp +++ b/FontStashSharp @@ -1 +1 @@ -Subproject commit 38849f3ac2887c14b8fa1c69c17468032e5233e1 +Subproject commit 6e6fc608bee0d4e7b2944f7c686ca0d28896e195 diff --git a/MLEM.Data/Json/JsonTypeSafeGenericDataHolder.cs b/MLEM.Data/Json/JsonTypeSafeGenericDataHolder.cs index d3cbbbe..39f2d1c 100644 --- a/MLEM.Data/Json/JsonTypeSafeGenericDataHolder.cs +++ b/MLEM.Data/Json/JsonTypeSafeGenericDataHolder.cs @@ -12,6 +12,8 @@ namespace MLEM.Data.Json { [DataContract] public class JsonTypeSafeGenericDataHolder : IGenericDataHolder { + private static readonly string[] EmptyStrings = new string[0]; + [DataMember(EmitDefaultValue = false)] private Dictionary data; @@ -35,9 +37,9 @@ namespace MLEM.Data.Json { } /// - public IReadOnlyCollection GetDataKeys() { + public IEnumerable GetDataKeys() { if (this.data == null) - return Array.Empty(); + return JsonTypeSafeGenericDataHolder.EmptyStrings; return this.data.Keys; } diff --git a/MLEM.Data/MLEM.Data.FNA.csproj b/MLEM.Data/MLEM.Data.FNA.csproj index b247ae1..aa7d556 100644 --- a/MLEM.Data/MLEM.Data.FNA.csproj +++ b/MLEM.Data/MLEM.Data.FNA.csproj @@ -1,11 +1,11 @@  - netstandard2.0;net6.0 + net452;netstandard2.0;net6.0 true true MLEM.Data $(DefineConstants);FNA - NU1701 + NU1701;MSB3270 @@ -33,7 +33,8 @@ all - + + all diff --git a/MLEM.Data/MLEM.Data.csproj b/MLEM.Data/MLEM.Data.csproj index 109e6ff..12fe703 100644 --- a/MLEM.Data/MLEM.Data.csproj +++ b/MLEM.Data/MLEM.Data.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net452;netstandard2.0;net6.0 true true NU1701 diff --git a/MLEM.Extended/MLEM.Extended.FNA.csproj b/MLEM.Extended/MLEM.Extended.FNA.csproj index 7357d76..fccb966 100644 --- a/MLEM.Extended/MLEM.Extended.FNA.csproj +++ b/MLEM.Extended/MLEM.Extended.FNA.csproj @@ -5,6 +5,7 @@ true MLEM.Extended $(DefineConstants);FNA + NU1702;MSB3270 @@ -22,10 +23,10 @@ - + all - + all diff --git a/MLEM.FNA.sln b/MLEM.FNA.sln index acdea53..07a328b 100644 --- a/MLEM.FNA.sln +++ b/MLEM.FNA.sln @@ -16,9 +16,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.FNA", "Tests\Tests.FN EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.Extended.FNA", "MLEM.Extended\MLEM.Extended.FNA.csproj", "{A5B22930-DF4B-4A62-93ED-A6549F7B666B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA.Core", "FNA\FNA.Core.csproj", "{06459F72-CEAA-4B45-B2B1-708FC28D04F8}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA", "FNA\FNA.csproj", "{35253CE1-C864-4CD3-8249-4D1319748E8F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FontStashSharp.FNA.Core", "FontStashSharp\src\XNA\FontStashSharp.FNA.Core.csproj", "{0B410591-3AED-4C82-A07A-516FF493709B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FontStashSharp.FNA", "FontStashSharp\src\XNA\FontStashSharp.FNA.csproj", "{39249E92-EBF2-4951-A086-AB4951C3CCE1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA.Core", "FNA\FNA.Core.csproj", "{E33D9B9B-DDC7-4488-BCCF-76625AF80B4D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -58,13 +60,17 @@ Global {A5B22930-DF4B-4A62-93ED-A6549F7B666B}.Debug|Any CPU.Build.0 = Debug|Any CPU {A5B22930-DF4B-4A62-93ED-A6549F7B666B}.Release|Any CPU.ActiveCfg = Release|Any CPU {A5B22930-DF4B-4A62-93ED-A6549F7B666B}.Release|Any CPU.Build.0 = Release|Any CPU - {06459F72-CEAA-4B45-B2B1-708FC28D04F8}.Debug|Any CPU.ActiveCfg = Debug|x64 - {06459F72-CEAA-4B45-B2B1-708FC28D04F8}.Debug|Any CPU.Build.0 = Debug|x64 - {06459F72-CEAA-4B45-B2B1-708FC28D04F8}.Release|Any CPU.ActiveCfg = Release|x64 - {06459F72-CEAA-4B45-B2B1-708FC28D04F8}.Release|Any CPU.Build.0 = Release|x64 - {0B410591-3AED-4C82-A07A-516FF493709B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0B410591-3AED-4C82-A07A-516FF493709B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0B410591-3AED-4C82-A07A-516FF493709B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0B410591-3AED-4C82-A07A-516FF493709B}.Release|Any CPU.Build.0 = Release|Any CPU + {35253CE1-C864-4CD3-8249-4D1319748E8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35253CE1-C864-4CD3-8249-4D1319748E8F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35253CE1-C864-4CD3-8249-4D1319748E8F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35253CE1-C864-4CD3-8249-4D1319748E8F}.Release|Any CPU.Build.0 = Release|Any CPU + {39249E92-EBF2-4951-A086-AB4951C3CCE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39249E92-EBF2-4951-A086-AB4951C3CCE1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39249E92-EBF2-4951-A086-AB4951C3CCE1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39249E92-EBF2-4951-A086-AB4951C3CCE1}.Release|Any CPU.Build.0 = Release|Any CPU + {E33D9B9B-DDC7-4488-BCCF-76625AF80B4D}.Debug|Any CPU.ActiveCfg = Debug|x64 + {E33D9B9B-DDC7-4488-BCCF-76625AF80B4D}.Debug|Any CPU.Build.0 = Debug|x64 + {E33D9B9B-DDC7-4488-BCCF-76625AF80B4D}.Release|Any CPU.ActiveCfg = Release|x64 + {E33D9B9B-DDC7-4488-BCCF-76625AF80B4D}.Release|Any CPU.Build.0 = Release|x64 EndGlobalSection EndGlobal diff --git a/MLEM.Startup/MLEM.Startup.FNA.csproj b/MLEM.Startup/MLEM.Startup.FNA.csproj index 2ff3745..898c256 100644 --- a/MLEM.Startup/MLEM.Startup.FNA.csproj +++ b/MLEM.Startup/MLEM.Startup.FNA.csproj @@ -1,11 +1,12 @@  - netstandard2.0;net6.0 + net452;netstandard2.0;net6.0 true true MLEM.Startup $(DefineConstants);FNA + MSB3270 @@ -21,11 +22,11 @@ - + - + all diff --git a/MLEM.Startup/MLEM.Startup.csproj b/MLEM.Startup/MLEM.Startup.csproj index 871862c..f7f863d 100644 --- a/MLEM.Startup/MLEM.Startup.csproj +++ b/MLEM.Startup/MLEM.Startup.csproj @@ -1,7 +1,7 @@  - netstandard2.0;net6.0 + net452;netstandard2.0;net6.0 true true @@ -19,7 +19,7 @@ - + diff --git a/MLEM.Templates/MLEM.Templates.csproj b/MLEM.Templates/MLEM.Templates.csproj index d3a5f13..f321339 100644 --- a/MLEM.Templates/MLEM.Templates.csproj +++ b/MLEM.Templates/MLEM.Templates.csproj @@ -1,7 +1,7 @@  - netstandard2.0;net6.0 + net452;netstandard2.0;net6.0 true false content diff --git a/MLEM.Ui/Elements/TextField.cs b/MLEM.Ui/Elements/TextField.cs index 9eafbd8..7b39c6f 100644 --- a/MLEM.Ui/Elements/TextField.cs +++ b/MLEM.Ui/Elements/TextField.cs @@ -6,7 +6,9 @@ using MLEM.Input; using MLEM.Misc; using MLEM.Textures; using MLEM.Ui.Style; +#if NETSTANDARD2_0_OR_GREATER || NET6_0_OR_GREATER using TextCopy; +#endif namespace MLEM.Ui.Elements { /// @@ -143,7 +145,11 @@ namespace MLEM.Ui.Elements { /// The text that the text field should contain by default /// Whether the text field should support multi-line editing public TextField(Anchor anchor, Vector2 size, Rule rule = null, GenericFont font = null, string text = null, bool multiline = false) : base(anchor, size) { - this.textInput = new TextInput(null, Vector2.Zero, 1, null, ClipboardService.SetText, ClipboardService.GetText) { + this.textInput = new TextInput(null, Vector2.Zero, 1 + #if NETSTANDARD2_0_OR_GREATER || NET6_0_OR_GREATER + , null, ClipboardService.SetText, ClipboardService.GetText + #endif + ) { OnTextChange = (i, s) => this.OnTextChange?.Invoke(this, s), InputRule = (i, s) => this.InputRule.Invoke(this, s) }; diff --git a/MLEM.Ui/MLEM.Ui.FNA.csproj b/MLEM.Ui/MLEM.Ui.FNA.csproj index b3d713a..21f359e 100644 --- a/MLEM.Ui/MLEM.Ui.FNA.csproj +++ b/MLEM.Ui/MLEM.Ui.FNA.csproj @@ -1,10 +1,11 @@  - netstandard2.0;net6.0 + net452;netstandard2.0;net6.0 true true MLEM.Ui $(DefineConstants);FNA + MSB3270 @@ -20,10 +21,10 @@ - + - + all diff --git a/MLEM.Ui/MLEM.Ui.csproj b/MLEM.Ui/MLEM.Ui.csproj index 7f7727b..301173c 100644 --- a/MLEM.Ui/MLEM.Ui.csproj +++ b/MLEM.Ui/MLEM.Ui.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net452;netstandard2.0;net6.0 true true @@ -18,7 +18,7 @@ - + diff --git a/MLEM.Ui/Parsers/UiParser.cs b/MLEM.Ui/Parsers/UiParser.cs index 570d8e6..f7024fb 100644 --- a/MLEM.Ui/Parsers/UiParser.cs +++ b/MLEM.Ui/Parsers/UiParser.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Net.Http; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using MLEM.Formatting; @@ -10,6 +9,12 @@ using MLEM.Textures; using MLEM.Ui.Elements; using MLEM.Ui.Style; +#if NETSTANDARD2_0_OR_GREATER || NET6_0_OR_GREATER +using System.Net.Http; +#else +using System.Net; +#endif + namespace MLEM.Ui.Parsers { /// /// A base class for parsing various types of formatted strings into a set of MLEM.Ui elements with styling for each individual . @@ -148,15 +153,16 @@ namespace MLEM.Ui.Parsers { try { Texture2D tex; if (path.StartsWith("http")) { - using (var client = new HttpClient()) { - using (var src = await client.GetStreamAsync(path)) { - using (var memory = new MemoryStream()) { - // download the full stream before passing it to texture - await src.CopyToAsync(memory); - tex = Texture2D.FromStream(this.GraphicsDevice, memory); - } - } - } + byte[] src; + #if NETSTANDARD2_0_OR_GREATER || NET6_0_OR_GREATER + using (var client = new HttpClient()) + src = await client.GetByteArrayAsync(path); + #else + using (var client = new WebClient()) + src = await client.DownloadDataTaskAsync(path); + #endif + using (var memory = new MemoryStream(src)) + tex = Texture2D.FromStream(this.GraphicsDevice, memory); } else { using (var stream = Path.IsPathRooted(path) ? File.OpenRead(path) : TitleContainer.OpenStream(path)) tex = Texture2D.FromStream(this.GraphicsDevice, stream); diff --git a/MLEM.Ui/UiControls.cs b/MLEM.Ui/UiControls.cs index 8f47cf4..c9b69a2 100644 --- a/MLEM.Ui/UiControls.cs +++ b/MLEM.Ui/UiControls.cs @@ -367,7 +367,7 @@ namespace MLEM.Ui { protected virtual Element GetTabNextElement(bool backward) { if (this.ActiveRoot == null) return null; - var children = this.ActiveRoot.Element.GetChildren(c => !c.IsHidden, true, true).Append(this.ActiveRoot.Element) + var children = this.ActiveRoot.Element.GetChildren(c => !c.IsHidden, true, true).Concat(Enumerable.Repeat(this.ActiveRoot.Element, 1)) // we can't add these checks to GetChildren because it ignores false grandchildren .Where(c => c.CanBeSelected && c.AutoNavGroup == this.SelectedElement?.AutoNavGroup); if (this.SelectedElement?.Root != this.ActiveRoot) { @@ -402,7 +402,7 @@ namespace MLEM.Ui { protected virtual Element GetGamepadNextElement(Direction2 direction) { if (this.ActiveRoot == null) return null; - var children = this.ActiveRoot.Element.GetChildren(c => !c.IsHidden, true, true).Append(this.ActiveRoot.Element) + var children = this.ActiveRoot.Element.GetChildren(c => !c.IsHidden, true, true).Concat(Enumerable.Repeat(this.ActiveRoot.Element, 1)) // we can't add these checks to GetChildren because it ignores false grandchildren .Where(c => c.CanBeSelected && c.AutoNavGroup == this.SelectedElement?.AutoNavGroup); if (this.SelectedElement?.Root != this.ActiveRoot) { diff --git a/MLEM/Extensions/CollectionExtensions.cs b/MLEM/Extensions/CollectionExtensions.cs index db1f238..0f92f0c 100644 --- a/MLEM/Extensions/CollectionExtensions.cs +++ b/MLEM/Extensions/CollectionExtensions.cs @@ -25,7 +25,7 @@ namespace MLEM.Extensions { public static IEnumerable> Combinations(this IEnumerable> things) { var combos = Enumerable.Repeat(Enumerable.Empty(), 1); foreach (var t in things) - combos = combos.SelectMany(c => t.Select(c.Append)); + combos = combos.SelectMany(c => t.Select(o => c.Concat(Enumerable.Repeat(o, 1)))); return combos; } @@ -47,7 +47,7 @@ namespace MLEM.Extensions { public static IEnumerable> IndexCombinations(this IEnumerable> things) { var combos = Enumerable.Repeat(Enumerable.Empty(), 1); foreach (var t in things) - combos = combos.SelectMany(c => t.Select((o, i) => c.Append(i))); + combos = combos.SelectMany(c => t.Select((o, i) => c.Concat(Enumerable.Repeat(i, 1)))); return combos; } diff --git a/MLEM/Input/InputHandler.cs b/MLEM/Input/InputHandler.cs index af2110c..20e2bc5 100644 --- a/MLEM/Input/InputHandler.cs +++ b/MLEM/Input/InputHandler.cs @@ -20,6 +20,8 @@ namespace MLEM.Input { #else private static readonly int MaximumGamePadCount = GamePad.MaximumGamePadCount; #endif + private static readonly TouchLocation[] EmptyTouchLocations = new TouchLocation[0]; + private static readonly GenericInput[] EmptyGenericInputs = new GenericInput[0]; /// /// Contains all of the gestures that have finished during the last update call. @@ -83,20 +85,20 @@ namespace MLEM.Input { /// An array of all , and values that are currently down. /// Additionally, or can be used to determine the amount of time that a given input has been down for. /// - public GenericInput[] InputsDown { get; private set; } = Array.Empty(); + public GenericInput[] InputsDown { get; private set; } = InputHandler.EmptyGenericInputs; /// /// An array of all , and that are currently considered pressed. /// An input is considered pressed if it was up in the last update, and is up in the current one. /// - public GenericInput[] InputsPressed { get; private set; } = Array.Empty(); + public GenericInput[] InputsPressed { get; private set; } = InputHandler.EmptyGenericInputs; /// /// Contains the touch state from the last update call /// - public TouchCollection LastTouchState { get; private set; } = new TouchCollection(Array.Empty()); + public TouchCollection LastTouchState { get; private set; } = new TouchCollection(InputHandler.EmptyTouchLocations); /// /// Contains the current touch state /// - public TouchCollection TouchState { get; private set; } = new TouchCollection(Array.Empty()); + public TouchCollection TouchState { get; private set; } = new TouchCollection(InputHandler.EmptyTouchLocations); /// /// Contains the , but with the taken into account. /// @@ -281,7 +283,7 @@ namespace MLEM.Input { this.LastTouchState = this.TouchState; this.LastViewportTouchState = this.ViewportTouchState; - this.TouchState = active ? TouchPanel.GetState() : new TouchCollection(Array.Empty()); + this.TouchState = active ? TouchPanel.GetState() : new TouchCollection(InputHandler.EmptyTouchLocations); if (this.TouchState.Count > 0 && this.ViewportOffset != Point.Zero) { this.ViewportTouchState = new List(); foreach (var touch in this.TouchState) { @@ -302,8 +304,8 @@ namespace MLEM.Input { } if (this.inputsDownAccum.Count <= 0 && this.inputsDown.Count <= 0) { - this.InputsPressed = Array.Empty(); - this.InputsDown = Array.Empty(); + this.InputsPressed = InputHandler.EmptyGenericInputs; + this.InputsDown = InputHandler.EmptyGenericInputs; } else { // handle pressed inputs var pressed = new List(); diff --git a/MLEM/Input/Keybind.cs b/MLEM/Input/Keybind.cs index e4f0b8c..b4c01e6 100644 --- a/MLEM/Input/Keybind.cs +++ b/MLEM/Input/Keybind.cs @@ -13,8 +13,10 @@ namespace MLEM.Input { [DataContract] public class Keybind : IComparable, IComparable { + private static readonly Combination[] EmptyCombinations = new Combination[0]; + [DataMember] - private Combination[] combinations = Array.Empty(); + private Combination[] combinations = Keybind.EmptyCombinations; /// /// Creates a new keybind and adds the given key and modifiers using @@ -42,7 +44,7 @@ namespace MLEM.Input { /// The modifier keys that have to be held down. /// This keybind, for chaining public Keybind Add(GenericInput key, params GenericInput[] modifiers) { - this.combinations = this.combinations.Append(new Combination(key, modifiers)).ToArray(); + this.combinations = this.combinations.Concat(Enumerable.Repeat(new Combination(key, modifiers), 1)).ToArray(); return this; } @@ -61,7 +63,7 @@ namespace MLEM.Input { /// The modifier keys that have to be held down. /// This keybind, for chaining. public Keybind Insert(int index, GenericInput key, params GenericInput[] modifiers) { - this.combinations = this.combinations.Take(index).Append(new Combination(key, modifiers)).Concat(this.combinations.Skip(index)).ToArray(); + this.combinations = this.combinations.Take(index).Concat(Enumerable.Repeat(new Combination(key, modifiers), 1)).Concat(this.combinations.Skip(index)).ToArray(); return this; } @@ -77,7 +79,7 @@ namespace MLEM.Input { /// /// This keybind, for chaining public Keybind Clear() { - this.combinations = Array.Empty(); + this.combinations = Keybind.EmptyCombinations; return this; } @@ -349,7 +351,7 @@ namespace MLEM.Input { /// The function to use for determining the display name of a . If this is null, the generic input's default method is used. /// A human-readable string representing this combination public string ToString(string joiner, Func inputName = null) { - return string.Join(joiner, this.Modifiers.Append(this.Key).Select(i => inputName?.Invoke(i) ?? i.ToString())); + return string.Join(joiner, this.Modifiers.Concat(Enumerable.Repeat(this.Key, 1)).Select(i => inputName?.Invoke(i) ?? i.ToString())); } /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. diff --git a/MLEM/MLEM.FNA.csproj b/MLEM/MLEM.FNA.csproj index 4f4ccdd..75b89a2 100644 --- a/MLEM/MLEM.FNA.csproj +++ b/MLEM/MLEM.FNA.csproj @@ -1,10 +1,11 @@  - netstandard2.0;net6.0 + net452;netstandard2.0;net6.0 true true MLEM $(DefineConstants);FNA + MSB3270 @@ -20,9 +21,11 @@ - + all + + diff --git a/MLEM/MLEM.csproj b/MLEM/MLEM.csproj index ac55ac3..ac0df57 100644 --- a/MLEM/MLEM.csproj +++ b/MLEM/MLEM.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net452;netstandard2.0;net6.0 true true @@ -21,6 +21,8 @@ all + + diff --git a/MLEM/Misc/GenericDataHolder.cs b/MLEM/Misc/GenericDataHolder.cs index 14decf2..b224b55 100644 --- a/MLEM/Misc/GenericDataHolder.cs +++ b/MLEM/Misc/GenericDataHolder.cs @@ -11,6 +11,8 @@ namespace MLEM.Misc { [DataContract] public class GenericDataHolder : IGenericDataHolder { + private static readonly string[] EmptyStrings = new string[0]; + [DataMember(EmitDefaultValue = false)] private Dictionary data; @@ -34,9 +36,9 @@ namespace MLEM.Misc { } /// - public IReadOnlyCollection GetDataKeys() { + public IEnumerable GetDataKeys() { if (this.data == null) - return Array.Empty(); + return GenericDataHolder.EmptyStrings; return this.data.Keys; } @@ -67,7 +69,7 @@ namespace MLEM.Misc { /// Returns all of the generic data that this object stores. /// /// The generic data on this object - IReadOnlyCollection GetDataKeys(); + IEnumerable GetDataKeys(); } } diff --git a/Tests/Tests.FNA.csproj b/Tests/Tests.FNA.csproj index e032b51..4f5001d 100644 --- a/Tests/Tests.FNA.csproj +++ b/Tests/Tests.FNA.csproj @@ -4,6 +4,7 @@ nunit Tests $(DefineConstants);FNA + MSB3270 From 53cda02ec45aa5fff632b95f5555b1f058d1df73 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 14 Sep 2022 21:53:25 +0200 Subject: [PATCH 22/33] fixed some issues with the FNA project --- Demos.DesktopGL/Demos.DesktopGL.FNA.csproj | 2 +- Demos/Demos.FNA.csproj | 2 +- MLEM.FNA.sln | 6 ------ Tests/Tests.FNA.csproj | 2 +- build.cake | 1 + 5 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Demos.DesktopGL/Demos.DesktopGL.FNA.csproj b/Demos.DesktopGL/Demos.DesktopGL.FNA.csproj index d2d77ce..2ddcbac 100644 --- a/Demos.DesktopGL/Demos.DesktopGL.FNA.csproj +++ b/Demos.DesktopGL/Demos.DesktopGL.FNA.csproj @@ -21,7 +21,7 @@ - + diff --git a/Demos/Demos.FNA.csproj b/Demos/Demos.FNA.csproj index 0b64091..3da211a 100644 --- a/Demos/Demos.FNA.csproj +++ b/Demos/Demos.FNA.csproj @@ -14,7 +14,7 @@ - + all diff --git a/MLEM.FNA.sln b/MLEM.FNA.sln index 07a328b..8fdc283 100644 --- a/MLEM.FNA.sln +++ b/MLEM.FNA.sln @@ -20,8 +20,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA", "FNA\FNA.csproj", "{3 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FontStashSharp.FNA", "FontStashSharp\src\XNA\FontStashSharp.FNA.csproj", "{39249E92-EBF2-4951-A086-AB4951C3CCE1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA.Core", "FNA\FNA.Core.csproj", "{E33D9B9B-DDC7-4488-BCCF-76625AF80B4D}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -68,9 +66,5 @@ Global {39249E92-EBF2-4951-A086-AB4951C3CCE1}.Debug|Any CPU.Build.0 = Debug|Any CPU {39249E92-EBF2-4951-A086-AB4951C3CCE1}.Release|Any CPU.ActiveCfg = Release|Any CPU {39249E92-EBF2-4951-A086-AB4951C3CCE1}.Release|Any CPU.Build.0 = Release|Any CPU - {E33D9B9B-DDC7-4488-BCCF-76625AF80B4D}.Debug|Any CPU.ActiveCfg = Debug|x64 - {E33D9B9B-DDC7-4488-BCCF-76625AF80B4D}.Debug|Any CPU.Build.0 = Debug|x64 - {E33D9B9B-DDC7-4488-BCCF-76625AF80B4D}.Release|Any CPU.ActiveCfg = Release|x64 - {E33D9B9B-DDC7-4488-BCCF-76625AF80B4D}.Release|Any CPU.Build.0 = Release|x64 EndGlobalSection EndGlobal diff --git a/Tests/Tests.FNA.csproj b/Tests/Tests.FNA.csproj index 4f5001d..d0fb5ca 100644 --- a/Tests/Tests.FNA.csproj +++ b/Tests/Tests.FNA.csproj @@ -15,7 +15,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/build.cake b/build.cake index e233eec..ce207fd 100644 --- a/build.cake +++ b/build.cake @@ -9,6 +9,7 @@ var config = Argument("configuration", "Release"); Task("Prepare").Does(() => { DotNetCoreRestore("MLEM.sln"); + DotNetCoreRestore("MLEM.FNA.sln"); if (branch != "release") { var buildNum = EnvironmentVariable("BUILD_NUMBER"); From 39a7dd3e97827ccbff06cfb1b501a67fa83b6484 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 14 Sep 2022 22:00:45 +0200 Subject: [PATCH 23/33] updated build script --- .config/dotnet-tools.json | 4 ++-- FNA.Settings.props | 6 ++++++ build.cake | 22 ++++++++++------------ 3 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 FNA.Settings.props diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 4903ba5..c8bb929 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,10 +3,10 @@ "isRoot": true, "tools": { "cake.tool": { - "version": "1.3.0", + "version": "2.2.0", "commands": [ "dotnet-cake" ] } } -} \ No newline at end of file +} diff --git a/FNA.Settings.props b/FNA.Settings.props new file mode 100644 index 0000000..4ff94ac --- /dev/null +++ b/FNA.Settings.props @@ -0,0 +1,6 @@ + + + + + + diff --git a/build.cake b/build.cake index ce207fd..a342f60 100644 --- a/build.cake +++ b/build.cake @@ -8,8 +8,8 @@ var branch = Argument("branch", "main"); var config = Argument("configuration", "Release"); Task("Prepare").Does(() => { - DotNetCoreRestore("MLEM.sln"); - DotNetCoreRestore("MLEM.FNA.sln"); + DotNetRestore("MLEM.sln"); + DotNetRestore("MLEM.FNA.sln"); if (branch != "release") { var buildNum = EnvironmentVariable("BUILD_NUMBER"); @@ -21,32 +21,30 @@ Task("Prepare").Does(() => { }); Task("Build").IsDependentOn("Prepare").Does(() =>{ - var settings = new DotNetCoreBuildSettings { + var settings = new DotNetBuildSettings { Configuration = config, ArgumentCustomization = args => args.Append($"/p:Version={version}") }; - foreach (var project in GetFiles("**/MLEM*.csproj")) - DotNetCoreBuild(project.FullPath, settings); - DotNetCoreBuild("Demos/Demos.csproj", settings); - DotNetCoreBuild("Demos/Demos.FNA.csproj", settings); + DotNetBuild("MLEM.sln", settings); + DotNetBuild("MLEM.FNA.sln", settings); }); Task("Test").IsDependentOn("Build").Does(() => { - var settings = new DotNetCoreTestSettings { + var settings = new DotNetTestSettings { Configuration = config, Collectors = {"XPlat Code Coverage"} }; - DotNetCoreTest("Tests/Tests.csproj", settings); - DotNetCoreTest("Tests/Tests.FNA.csproj", settings); + DotNetTest("Tests/Tests.csproj", settings); + DotNetTest("Tests/Tests.FNA.csproj", settings); }); Task("Pack").IsDependentOn("Test").Does(() => { - var settings = new DotNetCorePackSettings { + var settings = new DotNetPackSettings { Configuration = config, ArgumentCustomization = args => args.Append($"/p:Version={version}") }; foreach (var project in GetFiles("**/MLEM*.csproj")) - DotNetCorePack(project.FullPath, settings); + DotNetPack(project.FullPath, settings); }); Task("Push").WithCriteria(branch == "main" || branch == "release").IsDependentOn("Pack").Does(() => { From b6a626d96e391679cc926f850ee87bdb5bca64e4 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 14 Sep 2022 22:13:05 +0200 Subject: [PATCH 24/33] use FNA core for tests and demos --- Demos.DesktopGL/Demos.DesktopGL.FNA.csproj | 2 +- Demos/Demos.FNA.csproj | 2 +- MLEM.FNA.sln | 6 ++++++ Tests/Tests.FNA.csproj | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Demos.DesktopGL/Demos.DesktopGL.FNA.csproj b/Demos.DesktopGL/Demos.DesktopGL.FNA.csproj index 2ddcbac..d2d77ce 100644 --- a/Demos.DesktopGL/Demos.DesktopGL.FNA.csproj +++ b/Demos.DesktopGL/Demos.DesktopGL.FNA.csproj @@ -21,7 +21,7 @@ - + diff --git a/Demos/Demos.FNA.csproj b/Demos/Demos.FNA.csproj index 3da211a..0b64091 100644 --- a/Demos/Demos.FNA.csproj +++ b/Demos/Demos.FNA.csproj @@ -14,7 +14,7 @@ - + all diff --git a/MLEM.FNA.sln b/MLEM.FNA.sln index 8fdc283..d4e2539 100644 --- a/MLEM.FNA.sln +++ b/MLEM.FNA.sln @@ -20,6 +20,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA", "FNA\FNA.csproj", "{3 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FontStashSharp.FNA", "FontStashSharp\src\XNA\FontStashSharp.FNA.csproj", "{39249E92-EBF2-4951-A086-AB4951C3CCE1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA.Core", "FNA\FNA.Core.csproj", "{458FFA5E-A1C4-4B23-A5D8-259385FEECED}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -66,5 +68,9 @@ Global {39249E92-EBF2-4951-A086-AB4951C3CCE1}.Debug|Any CPU.Build.0 = Debug|Any CPU {39249E92-EBF2-4951-A086-AB4951C3CCE1}.Release|Any CPU.ActiveCfg = Release|Any CPU {39249E92-EBF2-4951-A086-AB4951C3CCE1}.Release|Any CPU.Build.0 = Release|Any CPU + {458FFA5E-A1C4-4B23-A5D8-259385FEECED}.Debug|Any CPU.ActiveCfg = Debug|x64 + {458FFA5E-A1C4-4B23-A5D8-259385FEECED}.Debug|Any CPU.Build.0 = Debug|x64 + {458FFA5E-A1C4-4B23-A5D8-259385FEECED}.Release|Any CPU.ActiveCfg = Release|x64 + {458FFA5E-A1C4-4B23-A5D8-259385FEECED}.Release|Any CPU.Build.0 = Release|x64 EndGlobalSection EndGlobal diff --git a/Tests/Tests.FNA.csproj b/Tests/Tests.FNA.csproj index d0fb5ca..4f5001d 100644 --- a/Tests/Tests.FNA.csproj +++ b/Tests/Tests.FNA.csproj @@ -15,7 +15,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From e6b4bc54ba4bcd1db4ec8ed017391f7ca30bce2f Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 14 Sep 2022 23:28:04 +0200 Subject: [PATCH 25/33] exclude the android project from build --- MLEM.sln | 6 ------ 1 file changed, 6 deletions(-) diff --git a/MLEM.sln b/MLEM.sln index 40fe144..13e208e 100644 --- a/MLEM.sln +++ b/MLEM.sln @@ -18,8 +18,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.Data", "MLEM.Data\MLEM EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.Templates", "MLEM.Templates\MLEM.Templates.csproj", "{C2A2CFED-C9E8-4675-BD66-EFC3DB210977}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demos.Android", "Demos.Android\Demos.Android.csproj", "{410C0262-131C-4D0E-910D-D01B4F7143E0}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{53D52C3F-67FB-4F32-A794-EAB140BBFC11}" EndProject Global @@ -64,10 +62,6 @@ Global {C2A2CFED-C9E8-4675-BD66-EFC3DB210977}.Debug|Any CPU.Build.0 = Debug|Any CPU {C2A2CFED-C9E8-4675-BD66-EFC3DB210977}.Release|Any CPU.ActiveCfg = Release|Any CPU {C2A2CFED-C9E8-4675-BD66-EFC3DB210977}.Release|Any CPU.Build.0 = Release|Any CPU - {410C0262-131C-4D0E-910D-D01B4F7143E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {410C0262-131C-4D0E-910D-D01B4F7143E0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {410C0262-131C-4D0E-910D-D01B4F7143E0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {410C0262-131C-4D0E-910D-D01B4F7143E0}.Release|Any CPU.Build.0 = Release|Any CPU {53D52C3F-67FB-4F32-A794-EAB140BBFC11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {53D52C3F-67FB-4F32-A794-EAB140BBFC11}.Debug|Any CPU.Build.0 = Debug|Any CPU {53D52C3F-67FB-4F32-A794-EAB140BBFC11}.Release|Any CPU.ActiveCfg = Release|Any CPU From d0500bf9810f24f5a67a61f879905c3623ae21e2 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 14 Sep 2022 23:44:07 +0200 Subject: [PATCH 26/33] reference solutions for tests in build.cake --- build.cake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.cake b/build.cake index a342f60..0007bb5 100644 --- a/build.cake +++ b/build.cake @@ -34,8 +34,8 @@ Task("Test").IsDependentOn("Build").Does(() => { Configuration = config, Collectors = {"XPlat Code Coverage"} }; - DotNetTest("Tests/Tests.csproj", settings); - DotNetTest("Tests/Tests.FNA.csproj", settings); + DotNetTest("MLEM.sln", settings); + DotNetTest("MLEM.FNA.sln", settings); }); Task("Pack").IsDependentOn("Test").Does(() => { From 9f60a59706aa15daf8751937bef4388ffaa7ca28 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Thu, 15 Sep 2022 10:44:50 +0200 Subject: [PATCH 27/33] resolved some build warnings --- Demos.Android/Demos.Android.csproj | 7 ++++--- Demos.DesktopGL/Demos.DesktopGL.FNA.csproj | 2 +- Demos.DesktopGL/Demos.DesktopGL.csproj | 13 +++++++------ Demos/Demos.FNA.csproj | 2 +- Demos/Demos.csproj | 1 + FNA.Settings.props | 9 +++++++++ MLEM.Data/MLEM.Data.FNA.csproj | 2 +- MLEM.Extended/MLEM.Extended.FNA.csproj | 2 +- MLEM.Startup/MLEM.Startup.FNA.csproj | 1 - MLEM.Ui/MLEM.Ui.FNA.csproj | 1 - MLEM/MLEM.FNA.csproj | 1 - Sandbox/Sandbox.csproj | 9 +++++---- Tests/Tests.FNA.csproj | 2 +- Tests/Tests.csproj | 1 + build.cake | 4 ++-- 15 files changed, 34 insertions(+), 23 deletions(-) diff --git a/Demos.Android/Demos.Android.csproj b/Demos.Android/Demos.Android.csproj index 771320e..87ec7ba 100644 --- a/Demos.Android/Demos.Android.csproj +++ b/Demos.Android/Demos.Android.csproj @@ -7,15 +7,16 @@ 1 1.0 true + false - + - + - + diff --git a/Demos.DesktopGL/Demos.DesktopGL.FNA.csproj b/Demos.DesktopGL/Demos.DesktopGL.FNA.csproj index d2d77ce..3835c88 100644 --- a/Demos.DesktopGL/Demos.DesktopGL.FNA.csproj +++ b/Demos.DesktopGL/Demos.DesktopGL.FNA.csproj @@ -7,7 +7,7 @@ MLEM Desktop Demos Demos.DesktopGL $(DefineConstants);FNA - MSB3270 + false DesktopGL diff --git a/Demos.DesktopGL/Demos.DesktopGL.csproj b/Demos.DesktopGL/Demos.DesktopGL.csproj index 81b788e..d53a9bc 100644 --- a/Demos.DesktopGL/Demos.DesktopGL.csproj +++ b/Demos.DesktopGL/Demos.DesktopGL.csproj @@ -1,31 +1,32 @@  - + Exe net6.0 Icon.ico MLEM Desktop Demos + false - + - + - + - + - + diff --git a/Demos/Demos.FNA.csproj b/Demos/Demos.FNA.csproj index 0b64091..a15422e 100644 --- a/Demos/Demos.FNA.csproj +++ b/Demos/Demos.FNA.csproj @@ -4,7 +4,7 @@ netstandard2.0 Demos $(DefineConstants);FNA - MSB3270 + false diff --git a/Demos/Demos.csproj b/Demos/Demos.csproj index e0742c1..31946ae 100644 --- a/Demos/Demos.csproj +++ b/Demos/Demos.csproj @@ -2,6 +2,7 @@ netstandard2.0 + false diff --git a/FNA.Settings.props b/FNA.Settings.props index 4ff94ac..0a0b3e8 100644 --- a/FNA.Settings.props +++ b/FNA.Settings.props @@ -1,6 +1,15 @@  + + AnyCPU + false + + + + + + diff --git a/MLEM.Data/MLEM.Data.FNA.csproj b/MLEM.Data/MLEM.Data.FNA.csproj index aa7d556..4c9ffa5 100644 --- a/MLEM.Data/MLEM.Data.FNA.csproj +++ b/MLEM.Data/MLEM.Data.FNA.csproj @@ -5,7 +5,7 @@ true MLEM.Data $(DefineConstants);FNA - NU1701;MSB3270 + NU1701 diff --git a/MLEM.Extended/MLEM.Extended.FNA.csproj b/MLEM.Extended/MLEM.Extended.FNA.csproj index fccb966..b671c22 100644 --- a/MLEM.Extended/MLEM.Extended.FNA.csproj +++ b/MLEM.Extended/MLEM.Extended.FNA.csproj @@ -5,7 +5,7 @@ true MLEM.Extended $(DefineConstants);FNA - NU1702;MSB3270 + NU1702 diff --git a/MLEM.Startup/MLEM.Startup.FNA.csproj b/MLEM.Startup/MLEM.Startup.FNA.csproj index 898c256..b5f3a64 100644 --- a/MLEM.Startup/MLEM.Startup.FNA.csproj +++ b/MLEM.Startup/MLEM.Startup.FNA.csproj @@ -6,7 +6,6 @@ true MLEM.Startup $(DefineConstants);FNA - MSB3270 diff --git a/MLEM.Ui/MLEM.Ui.FNA.csproj b/MLEM.Ui/MLEM.Ui.FNA.csproj index 21f359e..3482793 100644 --- a/MLEM.Ui/MLEM.Ui.FNA.csproj +++ b/MLEM.Ui/MLEM.Ui.FNA.csproj @@ -5,7 +5,6 @@ true MLEM.Ui $(DefineConstants);FNA - MSB3270 diff --git a/MLEM/MLEM.FNA.csproj b/MLEM/MLEM.FNA.csproj index 75b89a2..9a83102 100644 --- a/MLEM/MLEM.FNA.csproj +++ b/MLEM/MLEM.FNA.csproj @@ -5,7 +5,6 @@ true MLEM $(DefineConstants);FNA - MSB3270 diff --git a/Sandbox/Sandbox.csproj b/Sandbox/Sandbox.csproj index ae66e79..b7ba249 100644 --- a/Sandbox/Sandbox.csproj +++ b/Sandbox/Sandbox.csproj @@ -1,10 +1,11 @@  - + Exe net6.0 + false - + @@ -12,7 +13,7 @@ - + @@ -21,7 +22,7 @@ - + diff --git a/Tests/Tests.FNA.csproj b/Tests/Tests.FNA.csproj index 4f5001d..730a19e 100644 --- a/Tests/Tests.FNA.csproj +++ b/Tests/Tests.FNA.csproj @@ -4,7 +4,7 @@ nunit Tests $(DefineConstants);FNA - MSB3270 + false diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 1d34903..4fae9b9 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -2,6 +2,7 @@ net6.0 nunit + false diff --git a/build.cake b/build.cake index 0007bb5..68882dd 100644 --- a/build.cake +++ b/build.cake @@ -43,8 +43,8 @@ Task("Pack").IsDependentOn("Test").Does(() => { Configuration = config, ArgumentCustomization = args => args.Append($"/p:Version={version}") }; - foreach (var project in GetFiles("**/MLEM*.csproj")) - DotNetPack(project.FullPath, settings); + DotNetPack("MLEM.sln", settings); + DotNetPack("MLEM.FNA.sln", settings); }); Task("Push").WithCriteria(branch == "main" || branch == "release").IsDependentOn("Pack").Does(() => { From fe89b28031b4d1c64cd3e62cc5abb81b216f9e04 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Thu, 15 Sep 2022 12:50:10 +0200 Subject: [PATCH 28/33] publish MG and FNA test results separately --- .gitignore | 2 +- Tests/Tests.FNA.csproj | 1 + Tests/Tests.csproj | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 78ff2f1..a87c755 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ obj packages *.user tools -TestResults \ No newline at end of file +TestResults* diff --git a/Tests/Tests.FNA.csproj b/Tests/Tests.FNA.csproj index 730a19e..0f9cdad 100644 --- a/Tests/Tests.FNA.csproj +++ b/Tests/Tests.FNA.csproj @@ -2,6 +2,7 @@ net6.0 nunit + TestResults.FNA Tests $(DefineConstants);FNA false diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 4fae9b9..4386917 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -2,6 +2,7 @@ net6.0 nunit + TestResults false From 02cf01fcb74b7bf27fb3353dfe02fdd4bf79e107 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Thu, 15 Sep 2022 17:51:46 +0200 Subject: [PATCH 29/33] added Append and Prepend to the net452 version for better code compatibility --- MLEM.Ui/UiControls.cs | 8 +++++-- MLEM/Extensions/CollectionExtensions.cs | 28 +++++++++++++++++++++++-- MLEM/Input/Keybind.cs | 10 ++++++--- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/MLEM.Ui/UiControls.cs b/MLEM.Ui/UiControls.cs index c9b69a2..d02a413 100644 --- a/MLEM.Ui/UiControls.cs +++ b/MLEM.Ui/UiControls.cs @@ -9,6 +9,10 @@ using MLEM.Misc; using MLEM.Ui.Elements; using MLEM.Ui.Style; +#if NET452 +using MLEM.Extensions; +#endif + namespace MLEM.Ui { /// /// UiControls holds and manages all of the controls for a . @@ -367,7 +371,7 @@ namespace MLEM.Ui { protected virtual Element GetTabNextElement(bool backward) { if (this.ActiveRoot == null) return null; - var children = this.ActiveRoot.Element.GetChildren(c => !c.IsHidden, true, true).Concat(Enumerable.Repeat(this.ActiveRoot.Element, 1)) + var children = this.ActiveRoot.Element.GetChildren(c => !c.IsHidden, true, true).Append(this.ActiveRoot.Element) // we can't add these checks to GetChildren because it ignores false grandchildren .Where(c => c.CanBeSelected && c.AutoNavGroup == this.SelectedElement?.AutoNavGroup); if (this.SelectedElement?.Root != this.ActiveRoot) { @@ -402,7 +406,7 @@ namespace MLEM.Ui { protected virtual Element GetGamepadNextElement(Direction2 direction) { if (this.ActiveRoot == null) return null; - var children = this.ActiveRoot.Element.GetChildren(c => !c.IsHidden, true, true).Concat(Enumerable.Repeat(this.ActiveRoot.Element, 1)) + var children = this.ActiveRoot.Element.GetChildren(c => !c.IsHidden, true, true).Append(this.ActiveRoot.Element) // we can't add these checks to GetChildren because it ignores false grandchildren .Where(c => c.CanBeSelected && c.AutoNavGroup == this.SelectedElement?.AutoNavGroup); if (this.SelectedElement?.Root != this.ActiveRoot) { diff --git a/MLEM/Extensions/CollectionExtensions.cs b/MLEM/Extensions/CollectionExtensions.cs index 0f92f0c..f983751 100644 --- a/MLEM/Extensions/CollectionExtensions.cs +++ b/MLEM/Extensions/CollectionExtensions.cs @@ -25,7 +25,7 @@ namespace MLEM.Extensions { public static IEnumerable> Combinations(this IEnumerable> things) { var combos = Enumerable.Repeat(Enumerable.Empty(), 1); foreach (var t in things) - combos = combos.SelectMany(c => t.Select(o => c.Concat(Enumerable.Repeat(o, 1)))); + combos = combos.SelectMany(c => t.Select(c.Append)); return combos; } @@ -47,9 +47,33 @@ namespace MLEM.Extensions { public static IEnumerable> IndexCombinations(this IEnumerable> things) { var combos = Enumerable.Repeat(Enumerable.Empty(), 1); foreach (var t in things) - combos = combos.SelectMany(c => t.Select((o, i) => c.Concat(Enumerable.Repeat(i, 1)))); + combos = combos.SelectMany(c => t.Select((o, i) => c.Append(i))); return combos; } + #if NET452 + /// Appends a value to the end of the sequence. + /// A sequence of values. + /// The value to append to . + /// The type of the elements of . + /// A new sequence that ends with . + public static IEnumerable Append(this IEnumerable source, T element) { + foreach (var src in source) + yield return src; + yield return element; + } + + /// Prepends a value to the beginning of the sequence. + /// A sequence of values. + /// The value to prepend to . + /// The type of the elements of . + /// A new sequence that begins with . + public static IEnumerable Prepend(this IEnumerable source, T element) { + yield return element; + foreach (var src in source) + yield return src; + } + #endif + } } diff --git a/MLEM/Input/Keybind.cs b/MLEM/Input/Keybind.cs index b4c01e6..78ad1b4 100644 --- a/MLEM/Input/Keybind.cs +++ b/MLEM/Input/Keybind.cs @@ -3,6 +3,10 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; +#if NET452 +using MLEM.Extensions; +#endif + namespace MLEM.Input { /// /// A keybind represents a generic way to trigger input. @@ -44,7 +48,7 @@ namespace MLEM.Input { /// The modifier keys that have to be held down. /// This keybind, for chaining public Keybind Add(GenericInput key, params GenericInput[] modifiers) { - this.combinations = this.combinations.Concat(Enumerable.Repeat(new Combination(key, modifiers), 1)).ToArray(); + this.combinations = this.combinations.Append(new Combination(key, modifiers)).ToArray(); return this; } @@ -63,7 +67,7 @@ namespace MLEM.Input { /// The modifier keys that have to be held down. /// This keybind, for chaining. public Keybind Insert(int index, GenericInput key, params GenericInput[] modifiers) { - this.combinations = this.combinations.Take(index).Concat(Enumerable.Repeat(new Combination(key, modifiers), 1)).Concat(this.combinations.Skip(index)).ToArray(); + this.combinations = this.combinations.Take(index).Append(new Combination(key, modifiers)).Concat(this.combinations.Skip(index)).ToArray(); return this; } @@ -351,7 +355,7 @@ namespace MLEM.Input { /// The function to use for determining the display name of a . If this is null, the generic input's default method is used. /// A human-readable string representing this combination public string ToString(string joiner, Func inputName = null) { - return string.Join(joiner, this.Modifiers.Concat(Enumerable.Repeat(this.Key, 1)).Select(i => inputName?.Invoke(i) ?? i.ToString())); + return string.Join(joiner, this.Modifiers.Append(this.Key).Select(i => inputName?.Invoke(i) ?? i.ToString())); } /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. From e8710f69e99a49b7d9532d342615e5981e936df5 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Mon, 19 Sep 2022 15:02:36 +0200 Subject: [PATCH 30/33] Fixed an exception when trying to force-update the area of an element without a ui system --- CHANGELOG.md | 1 + MLEM.Ui/Elements/Element.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e2928a..1a9434d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ Fixes - Fixed panels sometimes not drawing children that came into view when their positions changed unexpectedly - Fixed UiMarkdownParser not parsing formatting in headings and blockquotes - Fixed Element.OnChildAdded and Element.OnChildRemoved being called for grandchildren when a child is added +- Fixed an exception when trying to force-update the area of an element without a ui system ### MLEM.Data Additions diff --git a/MLEM.Ui/Elements/Element.cs b/MLEM.Ui/Elements/Element.cs index e0d7e54..049ed6e 100644 --- a/MLEM.Ui/Elements/Element.cs +++ b/MLEM.Ui/Elements/Element.cs @@ -452,7 +452,7 @@ namespace MLEM.Ui.Elements { /// The of this element's , or the if this element has no parent. /// This value is the one that is passed to during . /// - protected RectangleF ParentArea => this.Parent?.ChildPaddedArea ?? (RectangleF) this.system.Viewport; + protected RectangleF ParentArea => this.Parent?.ChildPaddedArea ?? (RectangleF) this.System.Viewport; private readonly List children = new List(); private readonly Stopwatch stopwatch = new Stopwatch(); @@ -601,7 +601,7 @@ namespace MLEM.Ui.Elements { /// public virtual void ForceUpdateArea() { this.AreaDirty = false; - if (this.IsHidden) + if (this.IsHidden || this.System == null) return; // if the parent's area is dirty, it would get updated anyway when querying its ChildPaddedArea, // which would cause our ForceUpdateArea code to be run twice, so we only update our parent instead From 74b3c426b8f03906a6bc0cd2e754887ae08d771b Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Thu, 22 Sep 2022 17:19:22 +0200 Subject: [PATCH 31/33] readme updates --- Docs/index.md | 2 ++ README.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Docs/index.md b/Docs/index.md index 01de279..05b2caa 100644 --- a/Docs/index.md +++ b/Docs/index.md @@ -2,6 +2,8 @@ **MLEM Library for Extending MonoGame and FNA** is a set of multipurpose libraries for the game frameworks [MonoGame](https://www.monogame.net/) and [FNA](https://fna-xna.github.io/) that provides abstractions, quality of life improvements and additional features like an extensive ui system and easy input handling. +MLEM is platform-agnostic and multi-targets .NET Standard 2.0, .NET 6.0 and .NET Framework 4.5.2, which makes it compatible with MonoGame and FNA on Desktop, mobile devices and consoles. + # What next? - Get it on [NuGet](https://www.nuget.org/packages?q=mlem) - Get prerelease builds on [BaGet](https://nuget.ellpeck.de/?q=mlem) diff --git a/README.md b/README.md index 6cc3305..04152e1 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ **MLEM Library for Extending MonoGame and FNA** is a set of multipurpose libraries for the game frameworks [MonoGame](https://www.monogame.net/) and [FNA](https://fna-xna.github.io/) that provides abstractions, quality of life improvements and additional features like an extensive ui system and easy input handling. +MLEM is platform-agnostic and multi-targets .NET Standard 2.0, .NET 6.0 and .NET Framework 4.5.2, which makes it compatible with MonoGame and FNA on Desktop, mobile devices and consoles. + # What next? - Get it on [NuGet](https://www.nuget.org/packages?q=mlem) - Get prerelease builds on [BaGet](https://nuget.ellpeck.de/?q=mlem) From d6a51776e56b2da5b5da22f1ca6abed912dcf3f0 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Sat, 24 Sep 2022 11:04:23 +0200 Subject: [PATCH 32/33] Fixed the scroll bar of an empty panel being positioned incorrectly --- CHANGELOG.md | 1 + MLEM.Ui/Elements/Panel.cs | 37 ++++++++++++++++++++----------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a9434d..22fc6bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ Fixes - Fixed UiMarkdownParser not parsing formatting in headings and blockquotes - Fixed Element.OnChildAdded and Element.OnChildRemoved being called for grandchildren when a child is added - Fixed an exception when trying to force-update the area of an element without a ui system +- Fixed the scroll bar of an empty panel being positioned incorrectly ### MLEM.Data Additions diff --git a/MLEM.Ui/Elements/Panel.cs b/MLEM.Ui/Elements/Panel.cs index 4be979a..6ccf880 100644 --- a/MLEM.Ui/Elements/Panel.cs +++ b/MLEM.Ui/Elements/Panel.cs @@ -84,8 +84,9 @@ namespace MLEM.Ui.Elements { return; if (e == null || !e.GetParentTree().Contains(this)) return; - var firstChild = this.Children.First(c => c != this.ScrollBar); - this.ScrollBar.CurrentValue = (e.Area.Center.Y - this.Area.Height / 2 - firstChild.Area.Top) / e.Scale + this.ChildPadding.Value.Height / 2; + var firstChild = this.Children.FirstOrDefault(c => c != this.ScrollBar); + if (firstChild != null) + this.ScrollBar.CurrentValue = (e.Area.Center.Y - this.Area.Height / 2 - firstChild.Area.Top) / e.Scale + this.ChildPadding.Value.Height / 2; }; this.AddChild(this.ScrollBar); } @@ -231,28 +232,30 @@ namespace MLEM.Ui.Elements { protected virtual void ScrollSetup() { if (!this.scrollOverflow || this.IsHidden) return; - // if there is only one child, then we have just the scroll bar - if (this.Children.Count == 1) - return; - // the "real" first child is the scroll bar, which we want to ignore - var firstChild = this.Children.First(c => c != this.ScrollBar); - var lowestChild = this.GetLowestChild(c => c != this.ScrollBar && !c.IsHidden); - var childrenHeight = lowestChild.Area.Bottom - firstChild.Area.Top; + float childrenHeight; + if (this.Children.Count > 1) { + var firstChild = this.Children.FirstOrDefault(c => c != this.ScrollBar); + var lowestChild = this.GetLowestChild(c => c != this.ScrollBar && !c.IsHidden); + childrenHeight = lowestChild.Area.Bottom - firstChild.Area.Top; + } else { + // if we only have one child (the scroll bar), then the children take up no visual height + childrenHeight = 0; + } - // the max value of the scrollbar is the amount of non-scaled pixels taken up by overflowing components + // the max value of the scroll bar is the amount of non-scaled pixels taken up by overflowing components var scrollBarMax = (childrenHeight - this.ChildPaddedArea.Height) / this.Scale; if (!this.ScrollBar.MaxValue.Equals(scrollBarMax, Element.Epsilon)) { this.ScrollBar.MaxValue = scrollBarMax; this.relevantChildrenDirty = true; + } - // update child padding based on whether the scroll bar is visible - var childOffset = this.ScrollBar.IsHidden ? 0 : this.ScrollerSize.Value.X + this.ScrollBarOffset; - if (!this.scrollBarChildOffset.Equals(childOffset, Element.Epsilon)) { - this.ChildPadding += new Padding(0, -this.scrollBarChildOffset + childOffset, 0, 0); - this.scrollBarChildOffset = childOffset; - this.SetAreaDirty(); - } + // update child padding based on whether the scroll bar is visible + var childOffset = this.ScrollBar.IsHidden ? 0 : this.ScrollerSize.Value.X + this.ScrollBarOffset; + if (!this.scrollBarChildOffset.Equals(childOffset, Element.Epsilon)) { + this.ChildPadding += new Padding(0, -this.scrollBarChildOffset + childOffset, 0, 0); + this.scrollBarChildOffset = childOffset; + this.SetAreaDirty(); } // the scroller height has the same relation to the scroll bar height as the visible area has to the total height of the panel's content From 92f9164256064109ee36bbd60f569cd0e34ae283 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Sat, 24 Sep 2022 18:46:33 +0200 Subject: [PATCH 33/33] Added Panel.ScrollToElement --- CHANGELOG.md | 1 + MLEM.Ui/Elements/Panel.cs | 109 ++++++++++++++++++++++---------------- 2 files changed, 64 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22fc6bd..c75b094 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Additions - Added Element.AutoSizeAddedAbsolute to allow for more granular control of auto-sizing - Added Element.OnAddedToUi and Element.OnRemovedFromUi - Added ScrollBar.MouseDragScrolling +- Added Panel.ScrollToElement Improvements - Allow elements to auto-adjust their size even when their children are aligned oddly diff --git a/MLEM.Ui/Elements/Panel.cs b/MLEM.Ui/Elements/Panel.cs index 6ccf880..dafda63 100644 --- a/MLEM.Ui/Elements/Panel.cs +++ b/MLEM.Ui/Elements/Panel.cs @@ -84,9 +84,7 @@ namespace MLEM.Ui.Elements { return; if (e == null || !e.GetParentTree().Contains(this)) return; - var firstChild = this.Children.FirstOrDefault(c => c != this.ScrollBar); - if (firstChild != null) - this.ScrollBar.CurrentValue = (e.Area.Center.Y - this.Area.Height / 2 - firstChild.Area.Top) / e.Scale + this.ChildPadding.Value.Height / 2; + this.ScrollToElement(e); }; this.AddChild(this.ScrollBar); } @@ -116,15 +114,6 @@ namespace MLEM.Ui.Elements { this.ScrollSetup(); } - private void ScrollChildren() { - if (!this.scrollOverflow) - return; - // we ignore false grandchildren so that the children of the scroll bar stay in place - foreach (var child in this.GetChildren(c => c != this.ScrollBar, true, true)) - child.ScrollOffset.Y = -this.ScrollBar.CurrentValue; - this.relevantChildrenDirty = true; - } - /// public override void ForceUpdateSortedChildren() { base.ForceUpdateSortedChildren(); @@ -144,26 +133,6 @@ namespace MLEM.Ui.Elements { base.RemoveChildren(e => e != this.ScrollBar && (condition == null || condition(e))); } - /// - protected override IList GetRelevantChildren() { - var relevant = base.GetRelevantChildren(); - if (this.scrollOverflow) { - if (this.relevantChildrenDirty) - this.ForceUpdateRelevantChildren(); - relevant = this.relevantChildren; - } - return relevant; - } - - /// - protected override void OnChildAreaDirty(Element child, bool grandchild) { - base.OnChildAreaDirty(child, grandchild); - // we only need to scroll when a grandchild changes, since all of our children are forced - // to be auto-anchored and so will automatically propagate their changes up to us - if (grandchild) - this.ScrollChildren(); - } - /// public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) { // draw children onto the render target if we have one @@ -208,11 +177,32 @@ namespace MLEM.Ui.Elements { return base.GetElementUnderPos(position); } - private RectangleF GetRenderTargetArea() { - var area = this.ChildPaddedArea; - area.X = this.DisplayArea.X; - area.Width = this.DisplayArea.Width; - return area; + /// + public override void Dispose() { + if (this.renderTarget != null) { + this.renderTarget.Dispose(); + this.renderTarget = null; + } + base.Dispose(); + } + + /// + /// Scrolls this panel's to the given in such a way that its center is positioned in the center of this panel. + /// + /// The element to scroll to. + public void ScrollToElement(Element element) { + this.ScrollToElement(element.Area.Center.Y); + } + + /// + /// Scrolls this panel's to the given coordinate in such a way that the coordinate is positioned in the center of this panel. + /// + /// The y coordinate to scroll to, which should have this element's applied. + public void ScrollToElement(float elementY) { + var firstChild = this.Children.FirstOrDefault(c => c != this.ScrollBar); + if (firstChild == null) + return; + this.ScrollBar.CurrentValue = (elementY - this.Area.Height / 2 - firstChild.Area.Top) / this.Scale + this.ChildPadding.Value.Height / 2; } /// @@ -226,6 +216,26 @@ namespace MLEM.Ui.Elements { this.SetScrollBarStyle(); } + /// + protected override IList GetRelevantChildren() { + var relevant = base.GetRelevantChildren(); + if (this.scrollOverflow) { + if (this.relevantChildrenDirty) + this.ForceUpdateRelevantChildren(); + relevant = this.relevantChildren; + } + return relevant; + } + + /// + protected override void OnChildAreaDirty(Element child, bool grandchild) { + base.OnChildAreaDirty(child, grandchild); + // we only need to scroll when a grandchild changes, since all of our children are forced + // to be auto-anchored and so will automatically propagate their changes up to us + if (grandchild) + this.ScrollChildren(); + } + /// /// Prepares the panel for auto-scrolling, creating the render target and setting up the scroll bar's maximum value. /// @@ -274,15 +284,6 @@ namespace MLEM.Ui.Elements { } } - /// - public override void Dispose() { - if (this.renderTarget != null) { - this.renderTarget.Dispose(); - this.renderTarget = null; - } - base.Dispose(); - } - private void SetScrollBarStyle() { if (this.ScrollBar == null) return; @@ -309,5 +310,21 @@ namespace MLEM.Ui.Elements { } } + private RectangleF GetRenderTargetArea() { + var area = this.ChildPaddedArea; + area.X = this.DisplayArea.X; + area.Width = this.DisplayArea.Width; + return area; + } + + private void ScrollChildren() { + if (!this.scrollOverflow) + return; + // we ignore false grandchildren so that the children of the scroll bar stay in place + foreach (var child in this.GetChildren(c => c != this.ScrollBar, true, true)) + child.ScrollOffset.Y = -this.ScrollBar.CurrentValue; + this.relevantChildrenDirty = true; + } + } }