mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-22 12:58:33 +01:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
3e4c4e566d
48 changed files with 715 additions and 308 deletions
|
@ -3,7 +3,7 @@
|
||||||
"isRoot": true,
|
"isRoot": true,
|
||||||
"tools": {
|
"tools": {
|
||||||
"cake.tool": {
|
"cake.tool": {
|
||||||
"version": "1.3.0",
|
"version": "2.2.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"dotnet-cake"
|
"dotnet-cake"
|
||||||
]
|
]
|
||||||
|
|
|
@ -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_indent = align_by_first_attribute
|
||||||
resharper_xmldoc_attribute_style = on_single_line
|
resharper_xmldoc_attribute_style = on_single_line
|
||||||
resharper_xmldoc_pi_attribute_style = on_single_line
|
resharper_xmldoc_pi_attribute_style = on_single_line
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
indent_size = 2
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -4,4 +4,4 @@ obj
|
||||||
packages
|
packages
|
||||||
*.user
|
*.user
|
||||||
tools
|
tools
|
||||||
TestResults
|
TestResults*
|
||||||
|
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -19,6 +19,9 @@ Additions
|
||||||
Improvements
|
Improvements
|
||||||
- Improved EnumHelper.GetValues signature to return an array
|
- Improved EnumHelper.GetValues signature to return an array
|
||||||
- Allow using external gesture handling alongside InputHandler through ExternalGestureHandling
|
- 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
|
Fixes
|
||||||
- Fixed TokenizedString handling trailing spaces incorrectly in the last line of non-left aligned text
|
- Fixed TokenizedString handling trailing spaces incorrectly in the last line of non-left aligned text
|
||||||
|
@ -28,26 +31,45 @@ Fixes
|
||||||
Additions
|
Additions
|
||||||
- Added some extension methods for querying Anchor types
|
- Added some extension methods for querying Anchor types
|
||||||
- Added Element.AutoSizeAddedAbsolute to allow for more granular control of auto-sizing
|
- 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
|
Improvements
|
||||||
- Allow elements to auto-adjust their size even when their children are aligned oddly
|
- Allow elements to auto-adjust their size even when their children are aligned oddly
|
||||||
- Close other dropdowns when opening a dropdown
|
- Close other dropdowns when opening a dropdown
|
||||||
- Generified UiMarkdownParser by adding abstract UiParser
|
- Generified UiMarkdownParser by adding abstract UiParser
|
||||||
|
- Multi-target net452, making MLEM compatible with MonoGame for consoles
|
||||||
|
|
||||||
Fixes
|
Fixes
|
||||||
- Fixed parents of elements that prevent spill not being notified properly
|
- Fixed parents of elements that prevent spill not being notified properly
|
||||||
- Fixed paragraphs sometimes not updating their position properly when hidden because they're empty
|
- 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 panels sometimes not drawing children that came into view when their positions changed unexpectedly
|
||||||
- Fixed UiMarkdownParser not parsing formatting in headings and blockquotes
|
- 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
|
### MLEM.Data
|
||||||
|
Additions
|
||||||
|
- Added data, from, and copy instructions to DataTextureAtlas
|
||||||
|
|
||||||
Improvements
|
Improvements
|
||||||
- Allow data texture atlas pivots and offsets to be negative
|
- Allow data texture atlas pivots and offsets to be negative
|
||||||
- Made RuntimeTexturePacker restore texture region name and pivot when packing
|
- Made RuntimeTexturePacker restore texture region name and pivot when packing
|
||||||
|
- Multi-target net452, making MLEM compatible with MonoGame for consoles
|
||||||
|
|
||||||
Fixes
|
Fixes
|
||||||
- Fixed data texture atlases not allowing most characters in their region names
|
- 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
|
## 6.0.0
|
||||||
|
|
||||||
### MLEM
|
### MLEM
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
<ApplicationVersion>1</ApplicationVersion>
|
<ApplicationVersion>1</ApplicationVersion>
|
||||||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||||
<ImplicitUsings>true</ImplicitUsings>
|
<ImplicitUsings>true</ImplicitUsings>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
<AssemblyName>MLEM Desktop Demos</AssemblyName>
|
<AssemblyName>MLEM Desktop Demos</AssemblyName>
|
||||||
<RootNamespace>Demos.DesktopGL</RootNamespace>
|
<RootNamespace>Demos.DesktopGL</RootNamespace>
|
||||||
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
<!-- We still use the MG content builder for ease of compatibility between the MG and FNA demo projects -->
|
<!-- We still use the MG content builder for ease of compatibility between the MG and FNA demo projects -->
|
||||||
<MonoGamePlatform>DesktopGL</MonoGamePlatform>
|
<MonoGamePlatform>DesktopGL</MonoGamePlatform>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<ApplicationIcon>Icon.ico</ApplicationIcon>
|
<ApplicationIcon>Icon.ico</ApplicationIcon>
|
||||||
<AssemblyName>MLEM Desktop Demos</AssemblyName>
|
<AssemblyName>MLEM Desktop Demos</AssemblyName>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<RootNamespace>Demos</RootNamespace>
|
<RootNamespace>Demos</RootNamespace>
|
||||||
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -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 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?
|
# What next?
|
||||||
- Get it on [NuGet](https://www.nuget.org/packages?q=mlem)
|
- Get it on [NuGet](https://www.nuget.org/packages?q=mlem)
|
||||||
- Get prerelease builds on [BaGet](https://nuget.ellpeck.de/?q=mlem)
|
- Get prerelease builds on [BaGet](https://nuget.ellpeck.de/?q=mlem)
|
||||||
|
|
15
FNA.Settings.props
Normal file
15
FNA.Settings.props
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- include reference assemblies so we can build for old framework versions without having to install them -->
|
||||||
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- dummy pack target to allow for this (non-SDK-style) project to be included in dotnet pack -->
|
||||||
|
<Target Name="Pack" />
|
||||||
|
</Project>
|
|
@ -1 +1 @@
|
||||||
Subproject commit 38849f3ac2887c14b8fa1c69c17468032e5233e1
|
Subproject commit 6e6fc608bee0d4e7b2944f7c686ca0d28896e195
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -5,10 +6,9 @@ using System.Text.RegularExpressions;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Content;
|
using Microsoft.Xna.Framework.Content;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
using MLEM.Textures;
|
|
||||||
#if FNA
|
|
||||||
using MLEM.Extensions;
|
using MLEM.Extensions;
|
||||||
#endif
|
using MLEM.Misc;
|
||||||
|
using MLEM.Textures;
|
||||||
|
|
||||||
namespace MLEM.Data {
|
namespace MLEM.Data {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -18,15 +18,20 @@ namespace MLEM.Data {
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Data texture atlases are designed to be easy to write by hand. Because of this, their structure is very simple.
|
/// 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.
|
/// 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 <c>loc</c> keyword defines the <see cref="TextureRegion.Area"/> 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.
|
/// <list type="bullet">
|
||||||
/// The (optional) <c>piv</c> keyword defines the <see cref="TextureRegion.PivotPixels"/> 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.
|
/// <item><description>The <c>loc</c> (or <c>location</c>) instruction defines the <see cref="TextureRegion.Area"/> 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.</description></item>
|
||||||
/// The (optional) <c>off</c> 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.
|
/// <item><description>The (optional) <c>piv</c> (or <c>pivot</c>) instruction defines the <see cref="TextureRegion.PivotPixels"/> 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.</description></item>
|
||||||
|
/// <item><description>The (optional) <c>off</c> (of <c>offset</c>) 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.</description></item>
|
||||||
|
/// <item><description>The (optional and repeatable) <c>cpy</c> (or <c>copy</c>) 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.</description></item>
|
||||||
|
/// <item><description>The (optional and repeatable) <c>dat</c> (or <c>data</c>) instruction defines a custom data point that can be added to the resulting <see cref="TextureRegion"/>'s <see cref="GenericDataHolder"/> 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.</description></item>
|
||||||
|
/// <item><description>The (optional) <c>frm</c> (or <c>from</c>) 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 <c>loc</c> instruction is not required.</description></item>
|
||||||
|
/// </list>
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <example>
|
/// <example>
|
||||||
/// The following entry defines a texture region with the name <c>LongTableRight</c>, whose <see cref="TextureRegion.Area"/> will be a rectangle with X=32, Y=30, Width=64, Height=48, and whose <see cref="TextureRegion.PivotPixels"/> will be a vector with X=80, Y=46.
|
/// The following entry defines a texture region with the names <c>LongTableRight</c> and <c>LongTableUp</c>, whose <see cref="TextureRegion.Area"/> will be a rectangle with X=32, Y=30, Width=64, Height=48, and whose <see cref="TextureRegion.PivotPixels"/> will be a vector with X=80, Y=46.
|
||||||
/// <code>
|
/// <code>
|
||||||
/// LongTableRight
|
/// LongTableRight LongTableUp
|
||||||
/// loc 32 30 64 48
|
/// loc 32 30 64 48
|
||||||
/// piv 80 46
|
/// piv 80 46
|
||||||
/// </code>
|
/// </code>
|
||||||
|
@ -69,6 +74,7 @@ namespace MLEM.Data {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads a <see cref="DataTextureAtlas"/> from the given loaded texture and texture data file.
|
/// Loads a <see cref="DataTextureAtlas"/> from the given loaded texture and texture data file.
|
||||||
|
/// For more information on data texture atlases, see the <see cref="DataTextureAtlas"/> type documentation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="texture">The texture to use for this data texture atlas</param>
|
/// <param name="texture">The texture to use for this data texture atlas</param>
|
||||||
/// <param name="content">The content manager to use for loading</param>
|
/// <param name="content">The content manager to use for loading</param>
|
||||||
|
@ -78,45 +84,124 @@ namespace MLEM.Data {
|
||||||
public static DataTextureAtlas LoadAtlasData(TextureRegion texture, ContentManager content, string infoPath, bool pivotRelative = false) {
|
public static DataTextureAtlas LoadAtlasData(TextureRegion texture, ContentManager content, string infoPath, bool pivotRelative = false) {
|
||||||
var info = Path.Combine(content.RootDirectory, infoPath);
|
var info = Path.Combine(content.RootDirectory, infoPath);
|
||||||
string text;
|
string text;
|
||||||
if (Path.IsPathRooted(info)) {
|
try {
|
||||||
text = File.ReadAllText(info);
|
if (Path.IsPathRooted(info)) {
|
||||||
} else {
|
text = File.ReadAllText(info);
|
||||||
using (var reader = new StreamReader(TitleContainer.OpenStream(info)))
|
} else {
|
||||||
text = reader.ReadToEnd();
|
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 atlas = new DataTextureAtlas(texture);
|
||||||
|
var words = Regex.Split(text, @"\s+");
|
||||||
|
|
||||||
// parse each texture region: "<names> loc <u> <v> <w> <h> [piv <px> <py>] [off <ox> <oy>]"
|
var namesOffsets = new List<(string, Vector2)>();
|
||||||
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.+-]+))?")) {
|
var customData = new Dictionary<string, string>();
|
||||||
// offset
|
var location = Rectangle.Empty;
|
||||||
var off = !match.Groups[8].Success ? Vector2.Zero : new Vector2(
|
var pivot = Vector2.Zero;
|
||||||
float.Parse(match.Groups[8].Value, CultureInfo.InvariantCulture),
|
var offset = Vector2.Zero;
|
||||||
float.Parse(match.Groups[9].Value, CultureInfo.InvariantCulture));
|
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;
|
||||||
|
case "frm":
|
||||||
|
case "from":
|
||||||
|
var fromRegion = atlas[words[i + 1]];
|
||||||
|
customData.Clear();
|
||||||
|
foreach (var key in fromRegion.GetDataKeys())
|
||||||
|
customData.Add(key, fromRegion.GetData<string>(key));
|
||||||
|
// 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();
|
||||||
|
offset = Vector2.Zero;
|
||||||
|
i += 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// if we have data for the previous regions, they're valid so we add them
|
||||||
|
AddCurrentRegions();
|
||||||
|
|
||||||
// location
|
// we're starting a new region (or adding another name for a new region), so clear old data
|
||||||
var loc = new Rectangle(
|
namesOffsets.Add((word.Trim(), Vector2.Zero));
|
||||||
int.Parse(match.Groups[2].Value), int.Parse(match.Groups[3].Value),
|
customData.Clear();
|
||||||
int.Parse(match.Groups[4].Value), int.Parse(match.Groups[5].Value));
|
location = Rectangle.Empty;
|
||||||
loc.Offset(off.ToPoint());
|
pivot = Vector2.Zero;
|
||||||
|
offset = Vector2.Zero;
|
||||||
// pivot
|
break;
|
||||||
var piv = !match.Groups[6].Success ? Vector2.Zero : off + new Vector2(
|
}
|
||||||
float.Parse(match.Groups[6].Value, CultureInfo.InvariantCulture) - (pivotRelative ? 0 : loc.X),
|
} catch (Exception e) {
|
||||||
float.Parse(match.Groups[7].Value, CultureInfo.InvariantCulture) - (pivotRelative ? 0 : loc.Y));
|
throw new ContentLoadException($"Couldn't parse data texture atlas instruction {word} for region(s) {string.Join(", ", namesOffsets)}", e);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add the last region that was started on
|
||||||
|
AddCurrentRegions();
|
||||||
return atlas;
|
return atlas;
|
||||||
|
|
||||||
|
void AddCurrentRegions() {
|
||||||
|
// 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) {
|
||||||
|
var loc = location;
|
||||||
|
var piv = pivot;
|
||||||
|
var off = offset + addedOff;
|
||||||
|
|
||||||
|
loc.Offset(off.ToPoint());
|
||||||
|
if (piv != Vector2.Zero) {
|
||||||
|
piv += off;
|
||||||
|
if (!pivotRelative)
|
||||||
|
piv -= loc.Location.ToVector2();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -128,6 +213,7 @@ namespace MLEM.Data {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads a <see cref="DataTextureAtlas"/> from the given texture and texture data file.
|
/// Loads a <see cref="DataTextureAtlas"/> from the given texture and texture data file.
|
||||||
|
/// For more information on data texture atlases, see the <see cref="DataTextureAtlas"/> type documentation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="content">The content manager to use for loading</param>
|
/// <param name="content">The content manager to use for loading</param>
|
||||||
/// <param name="texturePath">The path to the texture file</param>
|
/// <param name="texturePath">The path to the texture file</param>
|
||||||
|
|
|
@ -12,6 +12,8 @@ namespace MLEM.Data.Json {
|
||||||
[DataContract]
|
[DataContract]
|
||||||
public class JsonTypeSafeGenericDataHolder : IGenericDataHolder {
|
public class JsonTypeSafeGenericDataHolder : IGenericDataHolder {
|
||||||
|
|
||||||
|
private static readonly string[] EmptyStrings = new string[0];
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
[DataMember(EmitDefaultValue = false)]
|
||||||
private Dictionary<string, JsonTypeSafeWrapper> data;
|
private Dictionary<string, JsonTypeSafeWrapper> data;
|
||||||
|
|
||||||
|
@ -35,9 +37,9 @@ namespace MLEM.Data.Json {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IReadOnlyCollection<string> GetDataKeys() {
|
public IEnumerable<string> GetDataKeys() {
|
||||||
if (this.data == null)
|
if (this.data == null)
|
||||||
return Array.Empty<string>();
|
return JsonTypeSafeGenericDataHolder.EmptyStrings;
|
||||||
return this.data.Keys;
|
return this.data.Keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFrameworks>net452;netstandard2.0;net6.0</TargetFrameworks>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||||
<RootNamespace>MLEM.Data</RootNamespace>
|
<RootNamespace>MLEM.Data</RootNamespace>
|
||||||
|
@ -33,7 +33,8 @@
|
||||||
<PackageReference Include="Newtonsoft.Json.Bson" Version="1.0.2">
|
<PackageReference Include="Newtonsoft.Json.Bson" Version="1.0.2">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<ProjectReference Include="..\FNA\FNA.Core.csproj">
|
|
||||||
|
<ProjectReference Include="..\FNA\FNA.csproj">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFrameworks>net452;netstandard2.0;net6.0</TargetFrameworks>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||||
<NoWarn>NU1701</NoWarn>
|
<NoWarn>NU1701</NoWarn>
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||||
<RootNamespace>MLEM.Extended</RootNamespace>
|
<RootNamespace>MLEM.Extended</RootNamespace>
|
||||||
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
||||||
|
<NoWarn>NU1702</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
@ -22,10 +23,10 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
|
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
|
||||||
|
|
||||||
<ProjectReference Include="..\FontStashSharp\src\XNA\FontStashSharp.FNA.Core.csproj">
|
<ProjectReference Include="..\FontStashSharp\src\XNA\FontStashSharp.FNA.csproj">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
<ProjectReference Include="..\FNA\FNA.Core.csproj">
|
<ProjectReference Include="..\FNA\FNA.csproj">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
26
MLEM.FNA.sln
26
MLEM.FNA.sln
|
@ -16,9 +16,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.FNA", "Tests\Tests.FN
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.Extended.FNA", "MLEM.Extended\MLEM.Extended.FNA.csproj", "{A5B22930-DF4B-4A62-93ED-A6549F7B666B}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.Extended.FNA", "MLEM.Extended\MLEM.Extended.FNA.csproj", "{A5B22930-DF4B-4A62-93ED-A6549F7B666B}"
|
||||||
EndProject
|
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
|
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", "{458FFA5E-A1C4-4B23-A5D8-259385FEECED}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{A5B22930-DF4B-4A62-93ED-A6549F7B666B}.Release|Any CPU.Build.0 = 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
|
{35253CE1-C864-4CD3-8249-4D1319748E8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{06459F72-CEAA-4B45-B2B1-708FC28D04F8}.Debug|Any CPU.Build.0 = Debug|x64
|
{35253CE1-C864-4CD3-8249-4D1319748E8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{06459F72-CEAA-4B45-B2B1-708FC28D04F8}.Release|Any CPU.ActiveCfg = Release|x64
|
{35253CE1-C864-4CD3-8249-4D1319748E8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{06459F72-CEAA-4B45-B2B1-708FC28D04F8}.Release|Any CPU.Build.0 = Release|x64
|
{35253CE1-C864-4CD3-8249-4D1319748E8F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{0B410591-3AED-4C82-A07A-516FF493709B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{39249E92-EBF2-4951-A086-AB4951C3CCE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{0B410591-3AED-4C82-A07A-516FF493709B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{39249E92-EBF2-4951-A086-AB4951C3CCE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{0B410591-3AED-4C82-A07A-516FF493709B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{39249E92-EBF2-4951-A086-AB4951C3CCE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{0B410591-3AED-4C82-A07A-516FF493709B}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFrameworks>net452;netstandard2.0;net6.0</TargetFrameworks>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||||
<RootNamespace>MLEM.Startup</RootNamespace>
|
<RootNamespace>MLEM.Startup</RootNamespace>
|
||||||
|
@ -21,11 +21,11 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Coroutine" Version="2.1.3" />
|
<PackageReference Include="Coroutine" Version="2.1.4" />
|
||||||
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.FNA.csproj" />
|
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.FNA.csproj" />
|
||||||
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
|
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
|
||||||
|
|
||||||
<ProjectReference Include="..\FNA\FNA.Core.csproj">
|
<ProjectReference Include="..\FNA\FNA.csproj">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFrameworks>net452;netstandard2.0;net6.0</TargetFrameworks>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Coroutine" Version="2.1.3" />
|
<PackageReference Include="Coroutine" Version="2.1.4" />
|
||||||
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.csproj" />
|
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.csproj" />
|
||||||
<ProjectReference Include="..\MLEM\MLEM.csproj" />
|
<ProjectReference Include="..\MLEM\MLEM.csproj" />
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFrameworks>net452;netstandard2.0;net6.0</TargetFrameworks>
|
||||||
<IncludeContentInPack>true</IncludeContentInPack>
|
<IncludeContentInPack>true</IncludeContentInPack>
|
||||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||||
<ContentTargetFolders>content</ContentTargetFolders>
|
<ContentTargetFolders>content</ContentTargetFolders>
|
||||||
|
|
|
@ -408,13 +408,23 @@ namespace MLEM.Ui.Elements {
|
||||||
public GamepadNextElementCallback GetGamepadNextElement;
|
public GamepadNextElementCallback GetGamepadNextElement;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that is called when a child is added to this element using <see cref="AddChild{T}"/>
|
/// Event that is called when a child is added to this element using <see cref="AddChild{T}"/>
|
||||||
|
/// Note that, while this event is only called for immediate children of this element, <see cref="RootElement.OnElementAdded"/> is called for all children and grandchildren.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public OtherElementCallback OnChildAdded;
|
public OtherElementCallback OnChildAdded;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that is called when a child is removed from this element using <see cref="RemoveChild"/>
|
/// Event that is called when a child is removed from this element using <see cref="RemoveChild"/>.
|
||||||
|
/// Note that, while this event is only called for immediate children of this element, <see cref="RootElement.OnElementRemoved"/> is called for all children and grandchildren.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public OtherElementCallback OnChildRemoved;
|
public OtherElementCallback OnChildRemoved;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// Event that is called when this element is added to a <see cref="UiSystem"/>, that is, when this element's <see cref="System"/> is set to a non-<see langword="null"/> value.
|
||||||
|
/// </summary>
|
||||||
|
public GenericCallback OnAddedToUi;
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is called when this element is removed from a <see cref="UiSystem"/>, that is, when this element's <see cref="System"/> is set to <see langword="null"/>.
|
||||||
|
/// </summary>
|
||||||
|
public GenericCallback OnRemovedFromUi;
|
||||||
|
/// <summary>
|
||||||
/// Event that is called when this element's <see cref="Dispose"/> method is called, which also happens in <see cref="Finalize"/>.
|
/// Event that is called when this element's <see cref="Dispose"/> method is called, which also happens in <see cref="Finalize"/>.
|
||||||
/// This event is useful for unregistering global event handlers when this object should be destroyed.
|
/// This event is useful for unregistering global event handlers when this object should be destroyed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -442,7 +452,7 @@ namespace MLEM.Ui.Elements {
|
||||||
/// The <see cref="ChildPaddedArea"/> of this element's <see cref="Parent"/>, or the <see cref="UiSystem.Viewport"/> if this element has no parent.
|
/// The <see cref="ChildPaddedArea"/> of this element's <see cref="Parent"/>, or the <see cref="UiSystem.Viewport"/> if this element has no parent.
|
||||||
/// This value is the one that is passed to <see cref="CalcActualSize"/> during <see cref="ForceUpdateArea"/>.
|
/// This value is the one that is passed to <see cref="CalcActualSize"/> during <see cref="ForceUpdateArea"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected RectangleF ParentArea => this.Parent?.ChildPaddedArea ?? (RectangleF) this.system.Viewport;
|
protected RectangleF ParentArea => this.Parent?.ChildPaddedArea ?? (RectangleF) this.System.Viewport;
|
||||||
|
|
||||||
private readonly List<Element> children = new List<Element>();
|
private readonly List<Element> children = new List<Element>();
|
||||||
private readonly Stopwatch stopwatch = new Stopwatch();
|
private readonly Stopwatch stopwatch = new Stopwatch();
|
||||||
|
@ -497,9 +507,10 @@ namespace MLEM.Ui.Elements {
|
||||||
element.AndChildren(e => {
|
element.AndChildren(e => {
|
||||||
e.Root = this.Root;
|
e.Root = this.Root;
|
||||||
e.System = this.System;
|
e.System = this.System;
|
||||||
|
e.OnAddedToUi?.Invoke(e);
|
||||||
this.Root?.InvokeOnElementAdded(e);
|
this.Root?.InvokeOnElementAdded(e);
|
||||||
this.OnChildAdded?.Invoke(this, e);
|
|
||||||
});
|
});
|
||||||
|
this.OnChildAdded?.Invoke(this, element);
|
||||||
this.SetSortedChildrenDirty();
|
this.SetSortedChildrenDirty();
|
||||||
element.SetAreaDirty();
|
element.SetAreaDirty();
|
||||||
return element;
|
return element;
|
||||||
|
@ -520,9 +531,10 @@ namespace MLEM.Ui.Elements {
|
||||||
element.AndChildren(e => {
|
element.AndChildren(e => {
|
||||||
e.Root = null;
|
e.Root = null;
|
||||||
e.System = null;
|
e.System = null;
|
||||||
|
e.OnRemovedFromUi?.Invoke(e);
|
||||||
this.Root?.InvokeOnElementRemoved(e);
|
this.Root?.InvokeOnElementRemoved(e);
|
||||||
this.OnChildRemoved?.Invoke(this, e);
|
|
||||||
});
|
});
|
||||||
|
this.OnChildRemoved?.Invoke(this, element);
|
||||||
this.SetSortedChildrenDirty();
|
this.SetSortedChildrenDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -589,7 +601,7 @@ namespace MLEM.Ui.Elements {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void ForceUpdateArea() {
|
public virtual void ForceUpdateArea() {
|
||||||
this.AreaDirty = false;
|
this.AreaDirty = false;
|
||||||
if (this.IsHidden)
|
if (this.IsHidden || this.System == null)
|
||||||
return;
|
return;
|
||||||
// if the parent's area is dirty, it would get updated anyway when querying its ChildPaddedArea,
|
// 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
|
// which would cause our ForceUpdateArea code to be run twice, so we only update our parent instead
|
||||||
|
|
|
@ -84,8 +84,7 @@ namespace MLEM.Ui.Elements {
|
||||||
return;
|
return;
|
||||||
if (e == null || !e.GetParentTree().Contains(this))
|
if (e == null || !e.GetParentTree().Contains(this))
|
||||||
return;
|
return;
|
||||||
var firstChild = this.Children.First(c => c != this.ScrollBar);
|
this.ScrollToElement(e);
|
||||||
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);
|
this.AddChild(this.ScrollBar);
|
||||||
}
|
}
|
||||||
|
@ -115,15 +114,6 @@ namespace MLEM.Ui.Elements {
|
||||||
this.ScrollSetup();
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void ForceUpdateSortedChildren() {
|
public override void ForceUpdateSortedChildren() {
|
||||||
base.ForceUpdateSortedChildren();
|
base.ForceUpdateSortedChildren();
|
||||||
|
@ -143,26 +133,6 @@ namespace MLEM.Ui.Elements {
|
||||||
base.RemoveChildren(e => e != this.ScrollBar && (condition == null || condition(e)));
|
base.RemoveChildren(e => e != this.ScrollBar && (condition == null || condition(e)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override IList<Element> GetRelevantChildren() {
|
|
||||||
var relevant = base.GetRelevantChildren();
|
|
||||||
if (this.scrollOverflow) {
|
|
||||||
if (this.relevantChildrenDirty)
|
|
||||||
this.ForceUpdateRelevantChildren();
|
|
||||||
relevant = this.relevantChildren;
|
|
||||||
}
|
|
||||||
return relevant;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||||
// draw children onto the render target if we have one
|
// draw children onto the render target if we have one
|
||||||
|
@ -207,11 +177,32 @@ namespace MLEM.Ui.Elements {
|
||||||
return base.GetElementUnderPos(position);
|
return base.GetElementUnderPos(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RectangleF GetRenderTargetArea() {
|
/// <inheritdoc />
|
||||||
var area = this.ChildPaddedArea;
|
public override void Dispose() {
|
||||||
area.X = this.DisplayArea.X;
|
if (this.renderTarget != null) {
|
||||||
area.Width = this.DisplayArea.Width;
|
this.renderTarget.Dispose();
|
||||||
return area;
|
this.renderTarget = null;
|
||||||
|
}
|
||||||
|
base.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scrolls this panel's <see cref="ScrollBar"/> to the given <see cref="Element"/> in such a way that its center is positioned in the center of this panel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="element">The element to scroll to.</param>
|
||||||
|
public void ScrollToElement(Element element) {
|
||||||
|
this.ScrollToElement(element.Area.Center.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scrolls this panel's <see cref="ScrollBar"/> to the given <paramref name="elementY"/> coordinate in such a way that the coordinate is positioned in the center of this panel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="elementY">The y coordinate to scroll to, which should have this element's <see cref="Element.Scale"/> applied.</param>
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -225,34 +216,56 @@ namespace MLEM.Ui.Elements {
|
||||||
this.SetScrollBarStyle();
|
this.SetScrollBarStyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override IList<Element> GetRelevantChildren() {
|
||||||
|
var relevant = base.GetRelevantChildren();
|
||||||
|
if (this.scrollOverflow) {
|
||||||
|
if (this.relevantChildrenDirty)
|
||||||
|
this.ForceUpdateRelevantChildren();
|
||||||
|
relevant = this.relevantChildren;
|
||||||
|
}
|
||||||
|
return relevant;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prepares the panel for auto-scrolling, creating the render target and setting up the scroll bar's maximum value.
|
/// Prepares the panel for auto-scrolling, creating the render target and setting up the scroll bar's maximum value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void ScrollSetup() {
|
protected virtual void ScrollSetup() {
|
||||||
if (!this.scrollOverflow || this.IsHidden)
|
if (!this.scrollOverflow || this.IsHidden)
|
||||||
return;
|
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
|
float childrenHeight;
|
||||||
var firstChild = this.Children.First(c => c != this.ScrollBar);
|
if (this.Children.Count > 1) {
|
||||||
var lowestChild = this.GetLowestChild(c => c != this.ScrollBar && !c.IsHidden);
|
var firstChild = this.Children.FirstOrDefault(c => c != this.ScrollBar);
|
||||||
var childrenHeight = lowestChild.Area.Bottom - firstChild.Area.Top;
|
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;
|
var scrollBarMax = (childrenHeight - this.ChildPaddedArea.Height) / this.Scale;
|
||||||
if (!this.ScrollBar.MaxValue.Equals(scrollBarMax, Element.Epsilon)) {
|
if (!this.ScrollBar.MaxValue.Equals(scrollBarMax, Element.Epsilon)) {
|
||||||
this.ScrollBar.MaxValue = scrollBarMax;
|
this.ScrollBar.MaxValue = scrollBarMax;
|
||||||
this.relevantChildrenDirty = true;
|
this.relevantChildrenDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
// update child padding based on whether the scroll bar is visible
|
// update child padding based on whether the scroll bar is visible
|
||||||
var childOffset = this.ScrollBar.IsHidden ? 0 : this.ScrollerSize.Value.X + this.ScrollBarOffset;
|
var childOffset = this.ScrollBar.IsHidden ? 0 : this.ScrollerSize.Value.X + this.ScrollBarOffset;
|
||||||
if (!this.scrollBarChildOffset.Equals(childOffset, Element.Epsilon)) {
|
if (!this.scrollBarChildOffset.Equals(childOffset, Element.Epsilon)) {
|
||||||
this.ChildPadding += new Padding(0, -this.scrollBarChildOffset + childOffset, 0, 0);
|
this.ChildPadding += new Padding(0, -this.scrollBarChildOffset + childOffset, 0, 0);
|
||||||
this.scrollBarChildOffset = childOffset;
|
this.scrollBarChildOffset = childOffset;
|
||||||
this.SetAreaDirty();
|
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
|
// 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
|
||||||
|
@ -271,15 +284,6 @@ namespace MLEM.Ui.Elements {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Dispose() {
|
|
||||||
if (this.renderTarget != null) {
|
|
||||||
this.renderTarget.Dispose();
|
|
||||||
this.renderTarget = null;
|
|
||||||
}
|
|
||||||
base.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetScrollBarStyle() {
|
private void SetScrollBarStyle() {
|
||||||
if (this.ScrollBar == null)
|
if (this.ScrollBar == null)
|
||||||
return;
|
return;
|
||||||
|
@ -306,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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,16 @@ namespace MLEM.Ui.Elements {
|
||||||
/// The texture of this scroll bar's scroller indicator
|
/// The texture of this scroll bar's scroller indicator
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StyleProp<NinePatch> ScrollerTexture;
|
public StyleProp<NinePatch> ScrollerTexture;
|
||||||
|
/// <summary>
|
||||||
|
/// Whether smooth scrolling should be enabled for this scroll bar.
|
||||||
|
/// Smooth scrolling causes the <see cref="CurrentValue"/> to change gradually rather than instantly when scrolling.
|
||||||
|
/// </summary>
|
||||||
|
public StyleProp<bool> SmoothScrolling;
|
||||||
|
/// <summary>
|
||||||
|
/// The factor with which <see cref="SmoothScrolling"/> happens.
|
||||||
|
/// </summary>
|
||||||
|
public StyleProp<float> SmoothScrollFactor;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The scroller's width and height
|
/// The scroller's width and height
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -86,7 +96,7 @@ namespace MLEM.Ui.Elements {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This property is true while the user scrolls on the scroll bar using the mouse or touch input
|
/// This property is true while the user scrolls on the scroll bar using the mouse or touch input
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsBeingScrolled => this.isMouseHeld || this.isDragging || this.isTouchHeld;
|
public bool IsBeingScrolled => this.isMouseScrolling || this.isMouseDragging || this.isTouchDragging || this.isTouchScrolling;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This field determines if this scroll bar should automatically be hidden from a <see cref="Panel"/> if there aren't enough children to allow for scrolling.
|
/// This field determines if this scroll bar should automatically be hidden from a <see cref="Panel"/> if there aren't enough children to allow for scrolling.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -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.Width - this.ScrollerSize.X * this.Scale),
|
||||||
this.Horizontal ? 0 : this.CurrentValue / this.maxValue * (this.DisplayArea.Height - this.ScrollerSize.Y * this.Scale));
|
this.Horizontal ? 0 : this.CurrentValue / this.maxValue * (this.DisplayArea.Height - this.ScrollerSize.Y * this.Scale));
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether smooth scrolling should be enabled for this scroll bar.
|
/// Whether this scroll bar should allow dragging the mouse over its attached <see cref="Panel"/>'s content while holding the left mouse button to scroll, similarly to how scrolling using touch input works.
|
||||||
/// Smooth scrolling causes the <see cref="CurrentValue"/> to change gradually rather than instantly when scrolling.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StyleProp<bool> SmoothScrolling;
|
public bool MouseDragScrolling;
|
||||||
/// <summary>
|
|
||||||
/// The factor with which <see cref="SmoothScrolling"/> happens.
|
|
||||||
/// </summary>
|
|
||||||
public StyleProp<float> SmoothScrollFactor;
|
|
||||||
|
|
||||||
private bool isMouseHeld;
|
private bool isMouseScrolling;
|
||||||
private bool isDragging;
|
private bool isMouseDragging;
|
||||||
private bool isTouchHeld;
|
private bool isTouchScrolling;
|
||||||
|
private bool isTouchDragging;
|
||||||
private float maxValue;
|
private float maxValue;
|
||||||
private float scrollAdded;
|
private float scrollAdded;
|
||||||
private float currValue;
|
private float currValue;
|
||||||
|
@ -141,18 +147,29 @@ namespace MLEM.Ui.Elements {
|
||||||
|
|
||||||
// MOUSE INPUT
|
// MOUSE INPUT
|
||||||
var moused = this.Controls.MousedElement;
|
var moused = this.Controls.MousedElement;
|
||||||
if (moused == this && this.Input.WasMouseButtonUp(MouseButton.Left) && this.Input.IsMouseButtonDown(MouseButton.Left)) {
|
var wasMouseUp = this.Input.WasMouseButtonUp(MouseButton.Left);
|
||||||
this.isMouseHeld = true;
|
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;
|
this.scrollStartOffset = this.TransformInverseAll(this.Input.ViewportMousePosition.ToVector2()) - this.ScrollerPosition;
|
||||||
} else if (this.isMouseHeld && !this.Input.IsMouseButtonDown(MouseButton.Left)) {
|
} else if (!isMouseDown) {
|
||||||
this.isMouseHeld = false;
|
this.isMouseScrolling = false;
|
||||||
}
|
}
|
||||||
if (this.isMouseHeld)
|
if (this.isMouseScrolling)
|
||||||
this.ScrollToPos(this.TransformInverseAll(this.Input.ViewportMousePosition.ToVector2()));
|
this.ScrollToPos(this.TransformInverseAll(this.Input.ViewportMousePosition.ToVector2()));
|
||||||
if (!this.Horizontal && moused != null && (moused == this.Parent || moused.GetParentTree().Contains(this.Parent))) {
|
if (!this.Horizontal) {
|
||||||
var scroll = this.Input.LastScrollWheel - this.Input.ScrollWheel;
|
if (moused != null && (moused == this.Parent || moused.GetParentTree().Contains(this.Parent))) {
|
||||||
if (scroll != 0)
|
var scroll = this.Input.LastScrollWheel - this.Input.ScrollWheel;
|
||||||
this.CurrentValue += this.StepPerScroll * Math.Sign(scroll);
|
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
|
// 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
|
// 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));
|
var touched = this.Parent.GetElementUnderPos(this.TransformInverseAll(drag.Position));
|
||||||
if (touched != null && touched != this)
|
if (touched != null && touched != this)
|
||||||
this.isDragging = true;
|
this.isTouchDragging = true;
|
||||||
|
|
||||||
// if we're dragging at all, then move the scroller
|
// if we're dragging at all, then move the scroller
|
||||||
if (this.isDragging)
|
if (this.isTouchDragging)
|
||||||
this.CurrentValue -= drag.Delta.Y / this.Scale;
|
this.CurrentValue -= drag.Delta.Y / this.Scale;
|
||||||
} else {
|
} else {
|
||||||
this.isDragging = false;
|
this.isTouchDragging = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.Input.ViewportTouchState.Count <= 0) {
|
if (this.Input.ViewportTouchState.Count <= 0) {
|
||||||
// if no touch has occured this tick, then reset the variable
|
// if no touch has occured this tick, then reset the variable
|
||||||
this.isTouchHeld = false;
|
this.isTouchScrolling = false;
|
||||||
} else {
|
} else {
|
||||||
foreach (var loc in this.Input.ViewportTouchState) {
|
foreach (var loc in this.Input.ViewportTouchState) {
|
||||||
var pos = this.TransformInverseAll(loc.Position);
|
var pos = this.TransformInverseAll(loc.Position);
|
||||||
// if we just started touching and are on top of the scroller, then we should start scrolling
|
// 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 _)) {
|
if (this.DisplayArea.Contains(pos) && !loc.TryGetPreviousLocation(out _)) {
|
||||||
this.isTouchHeld = true;
|
this.isTouchScrolling = true;
|
||||||
this.scrollStartOffset = pos - this.ScrollerPosition;
|
this.scrollStartOffset = pos - this.ScrollerPosition;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// scroll no matter if we're on the scroller right now
|
// scroll no matter if we're on the scroller right now
|
||||||
if (this.isTouchHeld)
|
if (this.isTouchScrolling)
|
||||||
this.ScrollToPos(pos);
|
this.ScrollToPos(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,9 @@ using MLEM.Input;
|
||||||
using MLEM.Misc;
|
using MLEM.Misc;
|
||||||
using MLEM.Textures;
|
using MLEM.Textures;
|
||||||
using MLEM.Ui.Style;
|
using MLEM.Ui.Style;
|
||||||
|
#if NETSTANDARD2_0_OR_GREATER || NET6_0_OR_GREATER
|
||||||
using TextCopy;
|
using TextCopy;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace MLEM.Ui.Elements {
|
namespace MLEM.Ui.Elements {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -143,7 +145,11 @@ namespace MLEM.Ui.Elements {
|
||||||
/// <param name="text">The text that the text field should contain by default</param>
|
/// <param name="text">The text that the text field should contain by default</param>
|
||||||
/// <param name="multiline">Whether the text field should support multi-line editing</param>
|
/// <param name="multiline">Whether the text field should support multi-line editing</param>
|
||||||
public TextField(Anchor anchor, Vector2 size, Rule rule = null, GenericFont font = null, string text = null, bool multiline = false) : base(anchor, size) {
|
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),
|
OnTextChange = (i, s) => this.OnTextChange?.Invoke(this, s),
|
||||||
InputRule = (i, s) => this.InputRule.Invoke(this, s)
|
InputRule = (i, s) => this.InputRule.Invoke(this, s)
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFrameworks>net452;netstandard2.0;net6.0</TargetFrameworks>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||||
<RootNamespace>MLEM.Ui</RootNamespace>
|
<RootNamespace>MLEM.Ui</RootNamespace>
|
||||||
|
@ -20,10 +20,10 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="TextCopy" Version="6.1.0" />
|
<PackageReference Include="TextCopy" Version="6.1.0" Condition="'$(TargetFramework)'!='net452'" />
|
||||||
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
|
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
|
||||||
|
|
||||||
<ProjectReference Include="..\FNA\FNA.Core.csproj">
|
<ProjectReference Include="..\FNA\FNA.csproj">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFrameworks>net452;netstandard2.0;net6.0</TargetFrameworks>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="TextCopy" Version="6.1.0" />
|
<PackageReference Include="TextCopy" Version="6.1.0" Condition="'$(TargetFramework)'!='net452'" />
|
||||||
<ProjectReference Include="..\MLEM\MLEM.csproj" />
|
<ProjectReference Include="..\MLEM\MLEM.csproj" />
|
||||||
|
|
||||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
|
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net.Http;
|
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
using MLEM.Formatting;
|
using MLEM.Formatting;
|
||||||
|
@ -10,6 +9,12 @@ using MLEM.Textures;
|
||||||
using MLEM.Ui.Elements;
|
using MLEM.Ui.Elements;
|
||||||
using MLEM.Ui.Style;
|
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 {
|
namespace MLEM.Ui.Parsers {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A base class for parsing various types of formatted strings into a set of MLEM.Ui elements with styling for each individual <see cref="ElementType"/>.
|
/// A base class for parsing various types of formatted strings into a set of MLEM.Ui elements with styling for each individual <see cref="ElementType"/>.
|
||||||
|
@ -148,15 +153,16 @@ namespace MLEM.Ui.Parsers {
|
||||||
try {
|
try {
|
||||||
Texture2D tex;
|
Texture2D tex;
|
||||||
if (path.StartsWith("http")) {
|
if (path.StartsWith("http")) {
|
||||||
using (var client = new HttpClient()) {
|
byte[] src;
|
||||||
using (var src = await client.GetStreamAsync(path)) {
|
#if NETSTANDARD2_0_OR_GREATER || NET6_0_OR_GREATER
|
||||||
using (var memory = new MemoryStream()) {
|
using (var client = new HttpClient())
|
||||||
// download the full stream before passing it to texture
|
src = await client.GetByteArrayAsync(path);
|
||||||
await src.CopyToAsync(memory);
|
#else
|
||||||
tex = Texture2D.FromStream(this.GraphicsDevice, memory);
|
using (var client = new WebClient())
|
||||||
}
|
src = await client.DownloadDataTaskAsync(path);
|
||||||
}
|
#endif
|
||||||
}
|
using (var memory = new MemoryStream(src))
|
||||||
|
tex = Texture2D.FromStream(this.GraphicsDevice, memory);
|
||||||
} else {
|
} else {
|
||||||
using (var stream = Path.IsPathRooted(path) ? File.OpenRead(path) : TitleContainer.OpenStream(path))
|
using (var stream = Path.IsPathRooted(path) ? File.OpenRead(path) : TitleContainer.OpenStream(path))
|
||||||
tex = Texture2D.FromStream(this.GraphicsDevice, stream);
|
tex = Texture2D.FromStream(this.GraphicsDevice, stream);
|
||||||
|
|
|
@ -9,6 +9,10 @@ using MLEM.Misc;
|
||||||
using MLEM.Ui.Elements;
|
using MLEM.Ui.Elements;
|
||||||
using MLEM.Ui.Style;
|
using MLEM.Ui.Style;
|
||||||
|
|
||||||
|
#if NET452
|
||||||
|
using MLEM.Extensions;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace MLEM.Ui {
|
namespace MLEM.Ui {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// UiControls holds and manages all of the controls for a <see cref="UiSystem"/>.
|
/// UiControls holds and manages all of the controls for a <see cref="UiSystem"/>.
|
||||||
|
|
|
@ -347,6 +347,7 @@ namespace MLEM.Ui {
|
||||||
root.Element.AndChildren(e => {
|
root.Element.AndChildren(e => {
|
||||||
e.Root = root;
|
e.Root = root;
|
||||||
e.System = this;
|
e.System = this;
|
||||||
|
e.OnAddedToUi?.Invoke(e);
|
||||||
root.InvokeOnElementAdded(e);
|
root.InvokeOnElementAdded(e);
|
||||||
e.SetAreaDirty();
|
e.SetAreaDirty();
|
||||||
});
|
});
|
||||||
|
@ -369,6 +370,7 @@ namespace MLEM.Ui {
|
||||||
root.Element.AndChildren(e => {
|
root.Element.AndChildren(e => {
|
||||||
e.Root = null;
|
e.Root = null;
|
||||||
e.System = null;
|
e.System = null;
|
||||||
|
e.OnRemovedFromUi?.Invoke(e);
|
||||||
root.InvokeOnElementRemoved(e);
|
root.InvokeOnElementRemoved(e);
|
||||||
e.SetAreaDirty();
|
e.SetAreaDirty();
|
||||||
});
|
});
|
||||||
|
@ -570,7 +572,7 @@ namespace MLEM.Ui {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Element.GenericCallback OnElementAdded;
|
public event Element.GenericCallback OnElementAdded;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that is invoked when a <see cref="Element"/> is removed rom this root element of any of its children.
|
/// Event that is invoked when a <see cref="Element"/> is removed rom this root element or any of its children.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Element.GenericCallback OnElementRemoved;
|
public event Element.GenericCallback OnElementRemoved;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
6
MLEM.sln
6
MLEM.sln
|
@ -18,8 +18,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.Data", "MLEM.Data\MLEM
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.Templates", "MLEM.Templates\MLEM.Templates.csproj", "{C2A2CFED-C9E8-4675-BD66-EFC3DB210977}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.Templates", "MLEM.Templates\MLEM.Templates.csproj", "{C2A2CFED-C9E8-4675-BD66-EFC3DB210977}"
|
||||||
EndProject
|
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}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{53D52C3F-67FB-4F32-A794-EAB140BBFC11}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
|
@ -64,10 +62,6 @@ Global
|
||||||
{C2A2CFED-C9E8-4675-BD66-EFC3DB210977}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{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.ActiveCfg = Release|Any CPU
|
||||||
{C2A2CFED-C9E8-4675-BD66-EFC3DB210977}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
|
||||||
{53D52C3F-67FB-4F32-A794-EAB140BBFC11}.Debug|Any CPU.Build.0 = 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
|
{53D52C3F-67FB-4F32-A794-EAB140BBFC11}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
|
|
@ -51,5 +51,29 @@ namespace MLEM.Extensions {
|
||||||
return combos;
|
return combos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NET452
|
||||||
|
/// <summary>Appends a value to the end of the sequence.</summary>
|
||||||
|
/// <param name="source">A sequence of values.</param>
|
||||||
|
/// <param name="element">The value to append to <paramref name="source"/>.</param>
|
||||||
|
/// <typeparam name="T">The type of the elements of <paramref name="source"/>.</typeparam>
|
||||||
|
/// <returns>A new sequence that ends with <paramref name="element"/>.</returns>
|
||||||
|
public static IEnumerable<T> Append<T>(this IEnumerable<T> source, T element) {
|
||||||
|
foreach (var src in source)
|
||||||
|
yield return src;
|
||||||
|
yield return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Prepends a value to the beginning of the sequence.</summary>
|
||||||
|
/// <param name="source">A sequence of values.</param>
|
||||||
|
/// <param name="element">The value to prepend to <paramref name="source"/>.</param>
|
||||||
|
/// <typeparam name="T">The type of the elements of <paramref name="source"/>.</typeparam>
|
||||||
|
/// <returns>A new sequence that begins with <paramref name="element"/>.</returns>
|
||||||
|
public static IEnumerable<T> Prepend<T>(this IEnumerable<T> source, T element) {
|
||||||
|
yield return element;
|
||||||
|
foreach (var src in source)
|
||||||
|
yield return src;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ using System.IO;
|
||||||
|
|
||||||
namespace MLEM.Graphics {
|
namespace MLEM.Graphics {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A static sprite batch is a variation of <see cref="SpriteBatch"/> that keeps all batched items in a <see cref="VertexBuffer"/>, allowing for them to be drawn multiple times.
|
/// A static sprite batch is a highly optimized variation of <see cref="SpriteBatch"/> that keeps all batched items in a <see cref="VertexBuffer"/>, allowing for them to be drawn multiple times.
|
||||||
/// To add items to a static sprite batch, use <see cref="BeginBatch"/> to begin batching, <see cref="ClearBatch"/> to clear currently batched items, <c>Add</c> and its various overloads to add batch items, <see cref="Remove"/> to remove them again, and <see cref="EndBatch"/> to end batching.
|
/// To add items to a static sprite batch, use <see cref="BeginBatch"/> to begin batching, <see cref="ClearBatch"/> to clear currently batched items, <c>Add</c> and its various overloads to add batch items, <see cref="Remove"/> to remove them again, and <see cref="EndBatch"/> to end batching.
|
||||||
/// To draw the batched items, call <see cref="Draw"/>.
|
/// To draw the batched items, call <see cref="Draw"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -23,7 +23,7 @@ namespace MLEM.Graphics {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount of vertices that are currently batched.
|
/// The amount of vertices that are currently batched.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Vertices => this.items.Count * 4;
|
public int Vertices => this.itemAmount * 4;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount of vertex buffers that this static sprite batch has.
|
/// The amount of vertex buffers that this static sprite batch has.
|
||||||
/// To see the amount of buffers that are actually in use, see <see cref="FilledBuffers"/>.
|
/// To see the amount of buffers that are actually in use, see <see cref="FilledBuffers"/>.
|
||||||
|
@ -41,13 +41,15 @@ namespace MLEM.Graphics {
|
||||||
|
|
||||||
private readonly GraphicsDevice graphicsDevice;
|
private readonly GraphicsDevice graphicsDevice;
|
||||||
private readonly SpriteEffect spriteEffect;
|
private readonly SpriteEffect spriteEffect;
|
||||||
|
private readonly List<DynamicVertexBuffer> vertexBuffers = new List<DynamicVertexBuffer>();
|
||||||
private readonly List<VertexBuffer> vertexBuffers = new List<VertexBuffer>();
|
|
||||||
private readonly List<Texture2D> textures = new List<Texture2D>();
|
private readonly List<Texture2D> textures = new List<Texture2D>();
|
||||||
private readonly ISet<Item> items = new HashSet<Item>();
|
private readonly SortedDictionary<float, ItemSet> items = new SortedDictionary<float, ItemSet>();
|
||||||
|
|
||||||
|
private SpriteSortMode sortMode = SpriteSortMode.Texture;
|
||||||
private IndexBuffer indices;
|
private IndexBuffer indices;
|
||||||
private bool batching;
|
private bool batching;
|
||||||
private bool batchChanged;
|
private bool batchChanged;
|
||||||
|
private int itemAmount;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new static sprite batch with the given <see cref="GraphicsDevice"/>
|
/// Creates a new static sprite batch with the given <see cref="GraphicsDevice"/>
|
||||||
|
@ -62,10 +64,27 @@ namespace MLEM.Graphics {
|
||||||
/// Begins batching.
|
/// Begins batching.
|
||||||
/// Call this method before calling <c>Add</c> or any of its overloads.
|
/// Call this method before calling <c>Add</c> or any of its overloads.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="sortMode">The drawing order for sprite drawing. When <see langword="null"/> is passed, the last used sort mode will be used again. The initial sort mode is <see cref="SpriteSortMode.Texture"/>. Note that <see cref="SpriteSortMode.Immediate"/> is not supported.</param>
|
||||||
/// <exception cref="InvalidOperationException">Thrown if this batch is currently batching already</exception>
|
/// <exception cref="InvalidOperationException">Thrown if this batch is currently batching already</exception>
|
||||||
public void BeginBatch() {
|
/// <exception cref="ArgumentOutOfRangeException">Thrown if the <paramref name="sortMode"/> is <see cref="SpriteSortMode.Immediate"/>, which is not supported.</exception>
|
||||||
|
public void BeginBatch(SpriteSortMode? sortMode = null) {
|
||||||
if (this.batching)
|
if (this.batching)
|
||||||
throw new InvalidOperationException("Already 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 (sortMode != null && this.sortMode != sortMode) {
|
||||||
|
this.sortMode = sortMode.Value;
|
||||||
|
if (this.items.Count > 0) {
|
||||||
|
var tempItems = this.items.Values.SelectMany(s => s.Items).ToArray();
|
||||||
|
this.items.Clear();
|
||||||
|
foreach (var item in tempItems)
|
||||||
|
this.AddItemToSet(item);
|
||||||
|
this.batchChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.batching = true;
|
this.batching = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,14 +92,10 @@ namespace MLEM.Graphics {
|
||||||
/// Ends batching.
|
/// Ends batching.
|
||||||
/// Call this method after calling <c>Add</c> or any of its overloads the desired number of times to add batched items.
|
/// Call this method after calling <c>Add</c> or any of its overloads the desired number of times to add batched items.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sortMode">The drawing order for sprite drawing. <see cref="SpriteSortMode.Texture" /> by default, since it is the best in terms of rendering performance. Note that <see cref="SpriteSortMode.Immediate"/> is not supported.</param>
|
|
||||||
/// <exception cref="InvalidOperationException">Thrown if this method is called before <see cref="BeginBatch"/> was called.</exception>
|
/// <exception cref="InvalidOperationException">Thrown if this method is called before <see cref="BeginBatch"/> was called.</exception>
|
||||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if the <paramref name="sortMode"/> is <see cref="SpriteSortMode.Immediate"/>, which is not supported.</exception>
|
public void EndBatch() {
|
||||||
public void EndBatch(SpriteSortMode sortMode = SpriteSortMode.Texture) {
|
|
||||||
if (!this.batching)
|
if (!this.batching)
|
||||||
throw new InvalidOperationException("Not 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;
|
this.batching = false;
|
||||||
|
|
||||||
// if we didn't add or remove any batch items, we don't have to recalculate anything
|
// if we didn't add or remove any batch items, we don't have to recalculate anything
|
||||||
|
@ -90,41 +105,28 @@ namespace MLEM.Graphics {
|
||||||
this.FilledBuffers = 0;
|
this.FilledBuffers = 0;
|
||||||
this.textures.Clear();
|
this.textures.Clear();
|
||||||
|
|
||||||
// order items according to the sort mode
|
|
||||||
IEnumerable<Item> 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
|
// fill vertex buffers
|
||||||
var dataIndex = 0;
|
var dataIndex = 0;
|
||||||
Texture2D texture = null;
|
Texture2D texture = null;
|
||||||
foreach (var item in ordered) {
|
foreach (var itemSet in this.items.Values) {
|
||||||
// if the texture changes, we also have to start a new buffer!
|
foreach (var item in itemSet.Items) {
|
||||||
if (dataIndex > 0 && (item.Texture != texture || dataIndex >= StaticSpriteBatch.Data.Length)) {
|
// if the texture changes, we also have to start a new buffer!
|
||||||
this.FillBuffer(this.FilledBuffers++, texture, StaticSpriteBatch.Data);
|
if (dataIndex > 0 && (item.Texture != texture || dataIndex >= StaticSpriteBatch.Data.Length)) {
|
||||||
dataIndex = 0;
|
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++] = item.TopLeft;
|
|
||||||
StaticSpriteBatch.Data[dataIndex++] = item.TopRight;
|
|
||||||
StaticSpriteBatch.Data[dataIndex++] = item.BottomLeft;
|
|
||||||
StaticSpriteBatch.Data[dataIndex++] = item.BottomRight;
|
|
||||||
texture = item.Texture;
|
|
||||||
}
|
}
|
||||||
if (dataIndex > 0)
|
if (dataIndex > 0)
|
||||||
this.FillBuffer(this.FilledBuffers++, texture, StaticSpriteBatch.Data);
|
this.FillBuffer(this.FilledBuffers++, texture, StaticSpriteBatch.Data);
|
||||||
|
|
||||||
// ensure we have enough indices
|
// ensure we have enough indices
|
||||||
var maxItems = Math.Min(this.items.Count, StaticSpriteBatch.MaxBatchItems);
|
var maxItems = Math.Min(this.itemAmount, StaticSpriteBatch.MaxBatchItems);
|
||||||
// each item has 2 triangles which each have 3 indices
|
// each item has 2 triangles which each have 3 indices
|
||||||
if (this.indices == null || this.indices.IndexCount < 6 * maxItems) {
|
if (this.indices == null || this.indices.IndexCount < 6 * maxItems) {
|
||||||
var newIndices = new short[6 * maxItems];
|
var newIndices = new short[6 * maxItems];
|
||||||
|
@ -178,7 +180,7 @@ namespace MLEM.Graphics {
|
||||||
for (var i = 0; i < this.FilledBuffers; i++) {
|
for (var i = 0; i < this.FilledBuffers; i++) {
|
||||||
var buffer = this.vertexBuffers[i];
|
var buffer = this.vertexBuffers[i];
|
||||||
var texture = this.textures[i];
|
var texture = this.textures[i];
|
||||||
var verts = Math.Min(this.items.Count * 4 - totalIndex, buffer.VertexCount);
|
var verts = Math.Min(this.itemAmount * 4 - totalIndex, buffer.VertexCount);
|
||||||
|
|
||||||
this.graphicsDevice.SetVertexBuffer(buffer);
|
this.graphicsDevice.SetVertexBuffer(buffer);
|
||||||
if (effect != null) {
|
if (effect != null) {
|
||||||
|
@ -352,6 +354,21 @@ namespace MLEM.Graphics {
|
||||||
return this.Add(texture, destinationRectangle, null, color);
|
return this.Add(texture, destinationRectangle, null, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an item to this batch.
|
||||||
|
/// Note that this batch needs to currently be batching, meaning <see cref="BeginBatch"/> has to have been called previously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item to add.</param>
|
||||||
|
/// <returns>The added <paramref name="item"/>, for chaining.</returns>
|
||||||
|
public Item Add(Item item) {
|
||||||
|
if (!this.batching)
|
||||||
|
throw new InvalidOperationException("Not batching");
|
||||||
|
this.AddItemToSet(item);
|
||||||
|
this.itemAmount++;
|
||||||
|
this.batchChanged = true;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes the given item from this batch.
|
/// Removes the given item from this batch.
|
||||||
/// Note that this batch needs to currently be batching, meaning <see cref="BeginBatch"/> has to have been called previously.
|
/// Note that this batch needs to currently be batching, meaning <see cref="BeginBatch"/> has to have been called previously.
|
||||||
|
@ -362,7 +379,11 @@ namespace MLEM.Graphics {
|
||||||
public bool Remove(Item item) {
|
public bool Remove(Item item) {
|
||||||
if (!this.batching)
|
if (!this.batching)
|
||||||
throw new InvalidOperationException("Not batching");
|
throw new InvalidOperationException("Not batching");
|
||||||
if (this.items.Remove(item)) {
|
var key = item.GetSortKey(this.sortMode);
|
||||||
|
if (this.items.TryGetValue(key, out var itemSet) && itemSet.Remove(item)) {
|
||||||
|
if (itemSet.IsEmpty)
|
||||||
|
this.items.Remove(key);
|
||||||
|
this.itemAmount--;
|
||||||
this.batchChanged = true;
|
this.batchChanged = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -380,6 +401,7 @@ namespace MLEM.Graphics {
|
||||||
this.items.Clear();
|
this.items.Clear();
|
||||||
this.textures.Clear();
|
this.textures.Clear();
|
||||||
this.FilledBuffers = 0;
|
this.FilledBuffers = 0;
|
||||||
|
this.itemAmount = 0;
|
||||||
this.batchChanged = true;
|
this.batchChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,18 +451,13 @@ namespace MLEM.Graphics {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Item Add(Texture2D texture, float depth, VertexPositionColorTexture tl, VertexPositionColorTexture tr, VertexPositionColorTexture bl, VertexPositionColorTexture br) {
|
private Item Add(Texture2D texture, float depth, VertexPositionColorTexture tl, VertexPositionColorTexture tr, VertexPositionColorTexture bl, VertexPositionColorTexture br) {
|
||||||
if (!this.batching)
|
return this.Add(new Item(texture, depth, tl, tr, bl, br));
|
||||||
throw new InvalidOperationException("Not batching");
|
|
||||||
var item = new Item(texture, depth, tl, tr, bl, br);
|
|
||||||
this.items.Add(item);
|
|
||||||
this.batchChanged = true;
|
|
||||||
return item;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FillBuffer(int index, Texture2D texture, VertexPositionColorTexture[] data) {
|
private void FillBuffer(int index, Texture2D texture, VertexPositionColorTexture[] data) {
|
||||||
if (this.vertexBuffers.Count <= index)
|
if (this.vertexBuffers.Count <= index)
|
||||||
this.vertexBuffers.Add(new VertexBuffer(this.graphicsDevice, VertexPositionColorTexture.VertexDeclaration, StaticSpriteBatch.MaxBatchItems * 4, BufferUsage.WriteOnly));
|
this.vertexBuffers.Add(new DynamicVertexBuffer(this.graphicsDevice, VertexPositionColorTexture.VertexDeclaration, StaticSpriteBatch.MaxBatchItems * 4, BufferUsage.WriteOnly));
|
||||||
this.vertexBuffers[index].SetData(data);
|
this.vertexBuffers[index].SetData(data, 0, data.Length, SetDataOptions.Discard);
|
||||||
this.textures.Insert(index, texture);
|
this.textures.Insert(index, texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,6 +469,15 @@ namespace MLEM.Graphics {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddItemToSet(Item item) {
|
||||||
|
var sortKey = item.GetSortKey(this.sortMode);
|
||||||
|
if (!this.items.TryGetValue(sortKey, out var itemSet)) {
|
||||||
|
itemSet = new ItemSet();
|
||||||
|
this.items.Add(sortKey, itemSet);
|
||||||
|
}
|
||||||
|
itemSet.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A struct that represents an item added to a <see cref="StaticSpriteBatch"/> using <c>Add</c> or any of its overloads.
|
/// A struct that represents an item added to a <see cref="StaticSpriteBatch"/> using <c>Add</c> or any of its overloads.
|
||||||
/// An item returned after adding can be removed using <see cref="Remove"/>.
|
/// An item returned after adding can be removed using <see cref="Remove"/>.
|
||||||
|
@ -474,6 +500,65 @@ namespace MLEM.Graphics {
|
||||||
this.BottomRight = bottomRight;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ItemSet {
|
||||||
|
|
||||||
|
public IEnumerable<Item> Items {
|
||||||
|
get {
|
||||||
|
if (this.items != null)
|
||||||
|
return this.items;
|
||||||
|
if (this.single != null)
|
||||||
|
return Enumerable.Repeat(this.single, 1);
|
||||||
|
return Enumerable.Empty<Item>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool IsEmpty => this.items == null && this.single == null;
|
||||||
|
|
||||||
|
private HashSet<Item> 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<Item>();
|
||||||
|
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
|
#if FNA
|
||||||
|
|
|
@ -20,6 +20,8 @@ namespace MLEM.Input {
|
||||||
#else
|
#else
|
||||||
private static readonly int MaximumGamePadCount = GamePad.MaximumGamePadCount;
|
private static readonly int MaximumGamePadCount = GamePad.MaximumGamePadCount;
|
||||||
#endif
|
#endif
|
||||||
|
private static readonly TouchLocation[] EmptyTouchLocations = new TouchLocation[0];
|
||||||
|
private static readonly GenericInput[] EmptyGenericInputs = new GenericInput[0];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains all of the gestures that have finished during the last update call.
|
/// Contains all of the gestures that have finished during the last update call.
|
||||||
|
@ -83,20 +85,20 @@ namespace MLEM.Input {
|
||||||
/// An array of all <see cref="Keys"/>, <see cref="Buttons"/> and <see cref="MouseButton"/> values that are currently down.
|
/// An array of all <see cref="Keys"/>, <see cref="Buttons"/> and <see cref="MouseButton"/> values that are currently down.
|
||||||
/// Additionally, <see cref="TryGetDownTime"/> or <see cref="GetDownTime"/> can be used to determine the amount of time that a given input has been down for.
|
/// Additionally, <see cref="TryGetDownTime"/> or <see cref="GetDownTime"/> can be used to determine the amount of time that a given input has been down for.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GenericInput[] InputsDown { get; private set; } = Array.Empty<GenericInput>();
|
public GenericInput[] InputsDown { get; private set; } = InputHandler.EmptyGenericInputs;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An array of all <see cref="Keys"/>, <see cref="Buttons"/> and <see cref="MouseButton"/> that are currently considered pressed.
|
/// An array of all <see cref="Keys"/>, <see cref="Buttons"/> and <see cref="MouseButton"/> 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.
|
/// An input is considered pressed if it was up in the last update, and is up in the current one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GenericInput[] InputsPressed { get; private set; } = Array.Empty<GenericInput>();
|
public GenericInput[] InputsPressed { get; private set; } = InputHandler.EmptyGenericInputs;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains the touch state from the last update call
|
/// Contains the touch state from the last update call
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TouchCollection LastTouchState { get; private set; } = new TouchCollection(Array.Empty<TouchLocation>());
|
public TouchCollection LastTouchState { get; private set; } = new TouchCollection(InputHandler.EmptyTouchLocations);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains the current touch state
|
/// Contains the current touch state
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TouchCollection TouchState { get; private set; } = new TouchCollection(Array.Empty<TouchLocation>());
|
public TouchCollection TouchState { get; private set; } = new TouchCollection(InputHandler.EmptyTouchLocations);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains the <see cref="LastTouchState"/>, but with the <see cref="GraphicsDevice.Viewport"/> taken into account.
|
/// Contains the <see cref="LastTouchState"/>, but with the <see cref="GraphicsDevice.Viewport"/> taken into account.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -281,7 +283,7 @@ namespace MLEM.Input {
|
||||||
this.LastTouchState = this.TouchState;
|
this.LastTouchState = this.TouchState;
|
||||||
this.LastViewportTouchState = this.ViewportTouchState;
|
this.LastViewportTouchState = this.ViewportTouchState;
|
||||||
|
|
||||||
this.TouchState = active ? TouchPanel.GetState() : new TouchCollection(Array.Empty<TouchLocation>());
|
this.TouchState = active ? TouchPanel.GetState() : new TouchCollection(InputHandler.EmptyTouchLocations);
|
||||||
if (this.TouchState.Count > 0 && this.ViewportOffset != Point.Zero) {
|
if (this.TouchState.Count > 0 && this.ViewportOffset != Point.Zero) {
|
||||||
this.ViewportTouchState = new List<TouchLocation>();
|
this.ViewportTouchState = new List<TouchLocation>();
|
||||||
foreach (var touch in this.TouchState) {
|
foreach (var touch in this.TouchState) {
|
||||||
|
@ -302,8 +304,8 @@ namespace MLEM.Input {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.inputsDownAccum.Count <= 0 && this.inputsDown.Count <= 0) {
|
if (this.inputsDownAccum.Count <= 0 && this.inputsDown.Count <= 0) {
|
||||||
this.InputsPressed = Array.Empty<GenericInput>();
|
this.InputsPressed = InputHandler.EmptyGenericInputs;
|
||||||
this.InputsDown = Array.Empty<GenericInput>();
|
this.InputsDown = InputHandler.EmptyGenericInputs;
|
||||||
} else {
|
} else {
|
||||||
// handle pressed inputs
|
// handle pressed inputs
|
||||||
var pressed = new List<GenericInput>();
|
var pressed = new List<GenericInput>();
|
||||||
|
@ -823,6 +825,7 @@ namespace MLEM.Input {
|
||||||
downTime = DateTime.UtcNow - start;
|
downTime = DateTime.UtcNow - start;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
downTime = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -851,6 +854,7 @@ namespace MLEM.Input {
|
||||||
upTime = DateTime.UtcNow - start;
|
upTime = DateTime.UtcNow - start;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
upTime = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -879,6 +883,7 @@ namespace MLEM.Input {
|
||||||
lastPressTime = DateTime.UtcNow - start;
|
lastPressTime = DateTime.UtcNow - start;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
lastPressTime = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,10 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
#if NET452
|
||||||
|
using MLEM.Extensions;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace MLEM.Input {
|
namespace MLEM.Input {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A keybind represents a generic way to trigger input.
|
/// A keybind represents a generic way to trigger input.
|
||||||
|
@ -13,8 +17,10 @@ namespace MLEM.Input {
|
||||||
[DataContract]
|
[DataContract]
|
||||||
public class Keybind : IComparable<Keybind>, IComparable {
|
public class Keybind : IComparable<Keybind>, IComparable {
|
||||||
|
|
||||||
|
private static readonly Combination[] EmptyCombinations = new Combination[0];
|
||||||
|
|
||||||
[DataMember]
|
[DataMember]
|
||||||
private Combination[] combinations = Array.Empty<Combination>();
|
private Combination[] combinations = Keybind.EmptyCombinations;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new keybind and adds the given key and modifiers using <see cref="Add(MLEM.Input.GenericInput,MLEM.Input.GenericInput[])"/>
|
/// Creates a new keybind and adds the given key and modifiers using <see cref="Add(MLEM.Input.GenericInput,MLEM.Input.GenericInput[])"/>
|
||||||
|
@ -77,7 +83,7 @@ namespace MLEM.Input {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>This keybind, for chaining</returns>
|
/// <returns>This keybind, for chaining</returns>
|
||||||
public Keybind Clear() {
|
public Keybind Clear() {
|
||||||
this.combinations = Array.Empty<Combination>();
|
this.combinations = Keybind.EmptyCombinations;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFrameworks>net452;netstandard2.0;net6.0</TargetFrameworks>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||||
<RootNamespace>MLEM</RootNamespace>
|
<RootNamespace>MLEM</RootNamespace>
|
||||||
|
@ -20,9 +20,11 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\FNA\FNA.Core.csproj">
|
<ProjectReference Include="..\FNA\FNA.csproj">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
|
||||||
|
<PackageReference Include="System.ValueTuple" Version="4.5.0" Condition="'$(TargetFramework)'=='net452'" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFrameworks>net452;netstandard2.0;net6.0</TargetFrameworks>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
@ -21,6 +21,8 @@
|
||||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
|
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
||||||
|
<PackageReference Include="System.ValueTuple" Version="4.5.0" Condition="'$(TargetFramework)'=='net452'" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -22,8 +22,12 @@ namespace MLEM.Misc {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type whose enum to get</typeparam>
|
/// <typeparam name="T">The type whose enum to get</typeparam>
|
||||||
/// <returns>An enumerable of the values of the enum, in declaration order.</returns>
|
/// <returns>An enumerable of the values of the enum, in declaration order.</returns>
|
||||||
public static T[] GetValues<T>() {
|
public static T[] GetValues<T>() where T : struct, Enum {
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
return Enum.GetValues<T>();
|
||||||
|
#else
|
||||||
return (T[]) Enum.GetValues(typeof(T));
|
return (T[]) Enum.GetValues(typeof(T));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ namespace MLEM.Misc {
|
||||||
[DataContract]
|
[DataContract]
|
||||||
public class GenericDataHolder : IGenericDataHolder {
|
public class GenericDataHolder : IGenericDataHolder {
|
||||||
|
|
||||||
|
private static readonly string[] EmptyStrings = new string[0];
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
[DataMember(EmitDefaultValue = false)]
|
||||||
private Dictionary<string, object> data;
|
private Dictionary<string, object> data;
|
||||||
|
|
||||||
|
@ -34,9 +36,9 @@ namespace MLEM.Misc {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IReadOnlyCollection<string> GetDataKeys() {
|
public IEnumerable<string> GetDataKeys() {
|
||||||
if (this.data == null)
|
if (this.data == null)
|
||||||
return Array.Empty<string>();
|
return GenericDataHolder.EmptyStrings;
|
||||||
return this.data.Keys;
|
return this.data.Keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +69,7 @@ namespace MLEM.Misc {
|
||||||
/// Returns all of the generic data that this object stores.
|
/// Returns all of the generic data that this object stores.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The generic data on this object</returns>
|
/// <returns>The generic data on this object</returns>
|
||||||
IReadOnlyCollection<string> GetDataKeys();
|
IEnumerable<string> GetDataKeys();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 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?
|
# What next?
|
||||||
- Get it on [NuGet](https://www.nuget.org/packages?q=mlem)
|
- Get it on [NuGet](https://www.nuget.org/packages?q=mlem)
|
||||||
- Get prerelease builds on [BaGet](https://nuget.ellpeck.de/?q=mlem)
|
- Get prerelease builds on [BaGet](https://nuget.ellpeck.de/?q=mlem)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using FontStashSharp;
|
using FontStashSharp;
|
||||||
|
@ -13,6 +14,7 @@ using MLEM.Extensions;
|
||||||
using MLEM.Font;
|
using MLEM.Font;
|
||||||
using MLEM.Formatting;
|
using MLEM.Formatting;
|
||||||
using MLEM.Formatting.Codes;
|
using MLEM.Formatting.Codes;
|
||||||
|
using MLEM.Graphics;
|
||||||
using MLEM.Input;
|
using MLEM.Input;
|
||||||
using MLEM.Misc;
|
using MLEM.Misc;
|
||||||
using MLEM.Startup;
|
using MLEM.Startup;
|
||||||
|
@ -351,6 +353,35 @@ public class GameImpl : MlemGame {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.UiSystem.Add("WidthTest", widthPanel);
|
this.UiSystem.Add("WidthTest", widthPanel);
|
||||||
|
|
||||||
|
var batch = new StaticSpriteBatch(this.GraphicsDevice);
|
||||||
|
batch.BeginBatch();
|
||||||
|
var depth = 0F;
|
||||||
|
var items = new List<StaticSpriteBatch.Item>();
|
||||||
|
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.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();
|
||||||
|
} 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DoUpdate(GameTime gameTime) {
|
protected override void DoUpdate(GameTime gameTime) {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -13,11 +13,21 @@ TestRegionNegativePivot
|
||||||
loc 0 32 +16 16
|
loc 0 32 +16 16
|
||||||
piv -32 +46
|
piv -32 +46
|
||||||
|
|
||||||
|
DataTest
|
||||||
|
loc 0 0 16 16
|
||||||
|
dat DataPoint1 ThisIsSomeData
|
||||||
|
dat DataPoint2 3.5
|
||||||
|
dat DataPoint3 ---
|
||||||
|
|
||||||
LongTableUp
|
LongTableUp
|
||||||
loc 0 32 64 48
|
piv 16 48 loc 0 32 64 48
|
||||||
piv 16 48
|
copy Copy1 16 0
|
||||||
|
cpy Copy2 32 4
|
||||||
|
|
||||||
|
Copy3 from
|
||||||
|
LongTableUp off 2 4
|
||||||
|
|
||||||
LongTableRight LongTableDown LongTableLeft
|
LongTableRight LongTableDown LongTableLeft
|
||||||
loc 32 30 64 48
|
location 32 30 64 48
|
||||||
piv 80 46
|
piv 80 46
|
||||||
off 32 2
|
offset 32 2
|
||||||
|
|
|
@ -9,26 +9,51 @@ namespace Tests {
|
||||||
public class TestDataTextureAtlas {
|
public class TestDataTextureAtlas {
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Test() {
|
public void Test([Values(0, 4)] int regionX, [Values(0, 4)] int regionY) {
|
||||||
using var game = TestGame.Create();
|
using var game = TestGame.Create();
|
||||||
using var texture = new Texture2D(game.GraphicsDevice, 1, 1);
|
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);
|
||||||
Assert.AreEqual(atlas.Regions.Count(), 8);
|
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 + regionX, 0 + regionY, 16, 32));
|
||||||
|
Assert.AreEqual(plant.PivotPixels, Vector2.Zero);
|
||||||
|
|
||||||
// no added offset
|
// no added offset
|
||||||
var table = atlas["LongTableUp"];
|
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));
|
Assert.AreEqual(table.PivotPixels, new Vector2(16, 48 - 32));
|
||||||
|
|
||||||
// added offset
|
// added offset
|
||||||
var table2 = atlas["LongTableLeft"];
|
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));
|
Assert.AreEqual(table2.PivotPixels, new Vector2(112 - 64, 48 - 32));
|
||||||
|
|
||||||
// negative pivot
|
// negative pivot
|
||||||
var negativePivot = atlas["TestRegionNegativePivot"];
|
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));
|
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 + 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 + 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 + regionX, 32 + 4 + regionY, 64, 48));
|
||||||
|
Assert.AreEqual(copy3.PivotPixels, new Vector2(16, 48 - 32));
|
||||||
|
|
||||||
|
// data
|
||||||
|
var data = atlas["DataTest"];
|
||||||
|
Assert.AreEqual("ThisIsSomeData", data.GetData<string>("DataPoint1"));
|
||||||
|
Assert.AreEqual("3.5", data.GetData<string>("DataPoint2"));
|
||||||
|
Assert.AreEqual("---", data.GetData<string>("DataPoint3"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<VSTestLogger>nunit</VSTestLogger>
|
<VSTestLogger>nunit</VSTestLogger>
|
||||||
|
<VSTestResultsDirectory>TestResults.FNA</VSTestResultsDirectory>
|
||||||
<RootNamespace>Tests</RootNamespace>
|
<RootNamespace>Tests</RootNamespace>
|
||||||
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<VSTestLogger>nunit</VSTestLogger>
|
<VSTestLogger>nunit</VSTestLogger>
|
||||||
|
<VSTestResultsDirectory>TestResults</VSTestResultsDirectory>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
27
build.cake
27
build.cake
|
@ -8,7 +8,8 @@ var branch = Argument("branch", "main");
|
||||||
var config = Argument("configuration", "Release");
|
var config = Argument("configuration", "Release");
|
||||||
|
|
||||||
Task("Prepare").Does(() => {
|
Task("Prepare").Does(() => {
|
||||||
DotNetCoreRestore("MLEM.sln");
|
DotNetRestore("MLEM.sln");
|
||||||
|
DotNetRestore("MLEM.FNA.sln");
|
||||||
|
|
||||||
if (branch != "release") {
|
if (branch != "release") {
|
||||||
var buildNum = EnvironmentVariable("BUILD_NUMBER");
|
var buildNum = EnvironmentVariable("BUILD_NUMBER");
|
||||||
|
@ -16,36 +17,34 @@ Task("Prepare").Does(() => {
|
||||||
version += "-" + buildNum;
|
version += "-" + buildNum;
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteFiles("**/*.nupkg");
|
DeleteFiles("**/MLEM*.nupkg");
|
||||||
});
|
});
|
||||||
|
|
||||||
Task("Build").IsDependentOn("Prepare").Does(() =>{
|
Task("Build").IsDependentOn("Prepare").Does(() =>{
|
||||||
var settings = new DotNetCoreBuildSettings {
|
var settings = new DotNetBuildSettings {
|
||||||
Configuration = config,
|
Configuration = config,
|
||||||
ArgumentCustomization = args => args.Append($"/p:Version={version}")
|
ArgumentCustomization = args => args.Append($"/p:Version={version}")
|
||||||
};
|
};
|
||||||
foreach (var project in GetFiles("**/MLEM*.csproj"))
|
DotNetBuild("MLEM.sln", settings);
|
||||||
DotNetCoreBuild(project.FullPath, settings);
|
DotNetBuild("MLEM.FNA.sln", settings);
|
||||||
DotNetCoreBuild("Demos/Demos.csproj", settings);
|
|
||||||
DotNetCoreBuild("Demos/Demos.FNA.csproj", settings);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Task("Test").IsDependentOn("Build").Does(() => {
|
Task("Test").IsDependentOn("Build").Does(() => {
|
||||||
var settings = new DotNetCoreTestSettings {
|
var settings = new DotNetTestSettings {
|
||||||
Configuration = config,
|
Configuration = config,
|
||||||
Collectors = {"XPlat Code Coverage"}
|
Collectors = {"XPlat Code Coverage"}
|
||||||
};
|
};
|
||||||
DotNetCoreTest("Tests/Tests.csproj", settings);
|
DotNetTest("MLEM.sln", settings);
|
||||||
DotNetCoreTest("Tests/Tests.FNA.csproj", settings);
|
DotNetTest("MLEM.FNA.sln", settings);
|
||||||
});
|
});
|
||||||
|
|
||||||
Task("Pack").IsDependentOn("Test").Does(() => {
|
Task("Pack").IsDependentOn("Test").Does(() => {
|
||||||
var settings = new DotNetCorePackSettings {
|
var settings = new DotNetPackSettings {
|
||||||
Configuration = config,
|
Configuration = config,
|
||||||
ArgumentCustomization = args => args.Append($"/p:Version={version}")
|
ArgumentCustomization = args => args.Append($"/p:Version={version}")
|
||||||
};
|
};
|
||||||
foreach (var project in GetFiles("**/MLEM*.csproj"))
|
DotNetPack("MLEM.sln", settings);
|
||||||
DotNetCorePack(project.FullPath, settings);
|
DotNetPack("MLEM.FNA.sln", settings);
|
||||||
});
|
});
|
||||||
|
|
||||||
Task("Push").WithCriteria(branch == "main" || branch == "release").IsDependentOn("Pack").Does(() => {
|
Task("Push").WithCriteria(branch == "main" || branch == "release").IsDependentOn("Pack").Does(() => {
|
||||||
|
@ -62,7 +61,7 @@ Task("Push").WithCriteria(branch == "main" || branch == "release").IsDependentOn
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
settings.SkipDuplicate = true;
|
settings.SkipDuplicate = true;
|
||||||
NuGetPush(GetFiles("**/*.nupkg"), settings);
|
NuGetPush(GetFiles("**/MLEM*.nupkg"), settings);
|
||||||
});
|
});
|
||||||
|
|
||||||
Task("Document").Does(() => {
|
Task("Document").Does(() => {
|
||||||
|
|
Loading…
Reference in a new issue