1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-12-24 01:09:23 +01:00

added MLEM.FNA

This commit is contained in:
Ell 2022-06-24 14:01:26 +02:00
parent aff61508c4
commit 5d7d238630
90 changed files with 736 additions and 158 deletions

6
.gitmodules vendored Normal file
View file

@ -0,0 +1,6 @@
[submodule "FNA"]
path = FNA
url = https://github.com/FNA-XNA/FNA
[submodule "FontStashSharp"]
path = FontStashSharp
url = https://github.com/FontStashSharp/FontStashSharp

View file

@ -18,6 +18,7 @@ Additions
- Added an Enum constructor to GenericInput
- Added RandomPitchModifier and GetRandomPitch to SoundEffectInfo
- Added TextInput class, which is an isolated version of MLEM.Ui's TextField logic
- Added MLEM.FNA, which is fully compatible with FNA
Improvements
- Allow comparing Keybind and Combination based on the amount of modifiers they have
@ -31,6 +32,7 @@ Removals
Additions
- Added Element.AutoNavGroup which allows forming groups for auto-navigation
- Added UiMarkdownParser
- Added MLEM.Ui.FNA, which is fully compatible with FNA
Improvements
- Ensure that Element.IsMouseOver is always accurate by making it an auto-property
@ -59,6 +61,7 @@ Removals
### MLEM.Extended
Additions
- Added LayerPositionF
- Added MLEM.Extended.FNA, which is fully compatible with FNA
Improvements
- Allow using a StaticSpriteBatch to render an IndividualTiledMapRenderer
@ -67,6 +70,7 @@ Improvements
Additions
- Added the ability to add padding to RuntimeTexturePacker texture regions
- Added the ability to pack UniformTextureAtlas and DataTextureAtlas using RuntimeTexturePacker
- Added MLEM.Data.FNA, which is fully compatible with FNA
Improvements
- Premultiply textures when using RawContentManager
@ -77,6 +81,10 @@ Improvements
Fixes
- Fixed SoundEffectReader incorrectly claiming it could read ogg and mp3 files
### MLEM.Startup
Additions
- Added MLEM.Startup.FNA, which is fully compatible with FNA
## 5.3.0
### MLEM
Additions

View file

@ -80,7 +80,7 @@
<PackageReference Include="Coroutine" Version="2.1.3" />
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.0.1641" />
<PackageReference Include="MonoGame.Framework.Android" Version="3.8.0.1641" />
<PackageReference Include="TextCopy" Version="4.3.0" />
<PackageReference Include="TextCopy" Version="6.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Demos\Demos.csproj">

View file

@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<ApplicationIcon>Icon.ico</ApplicationIcon>
<AssemblyName>MLEM Desktop Demos</AssemblyName>
<RootNamespace>Demos.DesktopGL</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
<!-- We still use the MG content builder for ease of compatibility between the MG and FNA demo projects -->
<MonoGamePlatform>DesktopGL</MonoGamePlatform>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Demos\Demos.FNA.csproj" />
<ProjectReference Include="..\MLEM.Startup\MLEM.Startup.FNA.csproj" />
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.FNA.csproj" />
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.0.1641" />
<ProjectReference Include="..\FNA\FNA.Core.csproj" />
</ItemGroup>
<ItemGroup>
<MonoGameContentReference Include="..\Demos\Content\Content.mgcb" />
<Content Include="..\Demos\Content\*\**" />
<EmbeddedResource Include="Icon.ico" />
<EmbeddedResource Include="Icon.bmp" />
<Content Include="FNA/**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>%(Filename)%(Extension)</Link>
</Content>
</ItemGroup>
</Project>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,11 +1,17 @@
using Microsoft.Xna.Framework;
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MLEM.Misc;
namespace Demos.DesktopGL {
public static class Program {
public static void Main() {
#if FNA
MlemPlatform.Current = new MlemPlatform.DesktopFna(a => TextInputEXT.TextInput += a);
#else
MlemPlatform.Current = new MlemPlatform.DesktopGl<TextInputEventArgs>((w, c) => w.TextInput += c);
#endif
using var game = new GameImpl();
game.Run();
}

View file

@ -90,7 +90,7 @@ namespace Demos {
public override void DoDraw(GameTime gameTime) {
this.GraphicsDevice.Clear(Color.CornflowerBlue);
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, transformMatrix: Matrix.CreateScale(10));
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(10));
// draw the group's current region
// if not using a group, just draw the animation's CurrentRegion here
this.SpriteBatch.Draw(this.group.CurrentRegion, new Vector2(10, 10), Color.White);

View file

@ -37,7 +37,7 @@ namespace Demos {
this.GraphicsDevice.Clear(Color.Black);
// drawing the auto tiles
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, transformMatrix: Matrix.CreateScale(10));
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(10));
for (var x = 0; x < 6; x++) {
for (var y = 0; y < 5; y++) {
// don't draw non-grass tiles ( )

21
Demos/Demos.FNA.csproj Normal file
View file

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Demos</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MLEM.Startup\MLEM.Startup.FNA.csproj" />
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.FNA.csproj" />
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FNA\FNA.Core.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
</ItemGroup>
</Project>

View file

@ -43,7 +43,7 @@ namespace Demos {
this.GraphicsDevice.Clear(Color.CornflowerBlue);
base.DoDraw(time);
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp);
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null);
var view = this.GraphicsDevice.Viewport;
// graph the easing function

View file

@ -9,7 +9,9 @@ using MLEM.Textures;
using MLEM.Ui;
using MLEM.Ui.Elements;
using MLEM.Ui.Style;
#if !FNA
using MonoGame.Framework.Utilities;
#endif
namespace Demos {
public class GameImpl : MlemGame {
@ -47,11 +49,13 @@ namespace Demos {
protected override void LoadContent() {
// TODO remove with MonoGame 3.8.1 https://github.com/MonoGame/MonoGame/issues/7298
#if !FNA
if (PlatformInfo.MonoGamePlatform == MonoGamePlatform.DesktopGL) {
this.GraphicsDeviceManager.PreferredBackBufferWidth = 1280;
this.GraphicsDeviceManager.PreferredBackBufferHeight = 720;
this.GraphicsDeviceManager.ApplyChanges();
}
#endif
base.LoadContent();
this.UiSystem.AutoScaleReferenceSize = new Point(1280, 720);

View file

@ -35,9 +35,9 @@ namespace Demos {
// Create a cost function, which determines how expensive (or difficult) it should be to move from a given position
// to the next, adjacent position. In our case, the only restriction should be walls and out-of-bounds positions, which
// both have a cost of AStar2.InfiniteCost, meaning they are completely unwalkable.
// both have a cost of AStar2.InfiniteCost, meaning they are completely unwalkable.
// If your game contains harder-to-move-on areas like, say, a muddy pit, you can return a higher cost value for those
// locations. If you want to scale your cost function differently, you can specify a different default cost in your
// locations. If you want to scale your cost function differently, you can specify a different default cost in your
// pathfinder's constructor
float Cost(Point pos, Point nextPos) {
if (nextPos.X < 0 || nextPos.Y < 0 || nextPos.X >= 50 || nextPos.Y >= 50)
@ -73,7 +73,7 @@ namespace Demos {
public override void DoDraw(GameTime gameTime) {
this.GraphicsDevice.Clear(Color.White);
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, transformMatrix: Matrix.CreateScale(14));
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(14));
var tex = this.SpriteBatch.GetBlankTexture();
// draw the world with simple shapes
for (var x = 0; x < 50; x++) {
@ -87,9 +87,9 @@ namespace Demos {
// in a real game, you'd obviously make your characters walk along the path instead of drawing it
if (this.path != null) {
for (var i = 1; i < this.path.Count; i++) {
var (firstX, firstY) = this.path[i - 1];
var (secondX, secondY) = this.path[i];
this.SpriteBatch.Draw(tex, RectangleF.FromCorners(new Vector2(firstX + 0.25F, firstY + 0.25F), new Vector2(secondX + 0.75F, secondY + 0.75F)), Color.Blue);
var first = this.path[i - 1];
var second = this.path[i];
this.SpriteBatch.Draw(tex, RectangleF.FromCorners(new Vector2(first.X + 0.25F, first.Y + 0.25F), new Vector2(second.X + 0.75F, second.Y + 0.75F)), Color.Blue);
}
}
this.SpriteBatch.End();

View file

@ -50,7 +50,7 @@ namespace Demos {
public override void DoDraw(GameTime time) {
this.GraphicsDevice.Clear(Color.DarkSlateGray);
this.SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null);
// we draw the tokenized text in the center of the screen
// since the text is already center-aligned, we only need to align it on the y axis here

View file

@ -73,7 +73,7 @@ namespace Demos {
// add the root to the demos' ui
this.UiRoot.AddChild(this.root);
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "This is a small demo for MLEM.Ui, a user interface library that is part of the MLEM Library for Extending MonoGame."));
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "This is a small demo for MLEM.Ui, a user interface library that is part of the MLEM Library for Extending MonoGame and FNA."));
var image = this.root.AddChild(new Image(Anchor.AutoCenter, new Vector2(50, 50), new TextureRegion(this.testTexture, 0, 0, 8, 8)) {IsHidden = true, Padding = new Padding(3)});
// Setting the x or y coordinate of the size to 1 or a lower number causes the width or height to be a percentage of the parent's width or height
// (for example, setting the size's x to 0.75 would make the element's width be 0.75*parentWidth)

View file

@ -1,6 +1,6 @@
![The MLEM logo](https://raw.githubusercontent.com/Ellpeck/MLEM/release/Media/Banner.png)
**MLEM Library for Extending MonoGame** is an addition to the game framework [MonoGame](https://www.monogame.net/) that provides extension methods, quality of life improvements and additional features like a ui system and easy input handling.
**MLEM Library for Extending MonoGame and FNA** is an addition to the game frameworks [MonoGame](https://www.monogame.net/) and [FNA](https://fna-xna.github.io/) that provides extension methods, quality of life improvements and additional features like a ui system and easy input handling.
# What next?
- Get it on [NuGet](https://www.nuget.org/packages?q=mlem)

1
FNA Submodule

@ -0,0 +1 @@
Subproject commit e27bfb5a34112461a6bff960888c0cce2b0c5be2

1
FontStashSharp Submodule

@ -0,0 +1 @@
Subproject commit 460a60ca817e10b6c9bae3e3c05affd1b0bb4ba7

View file

@ -7,19 +7,22 @@ using Microsoft.Xna.Framework.Graphics;
namespace MLEM.Data.Content {
/// <summary>
/// Represents a version of <see cref="ContentManager"/> that doesn't load content binary <c>xnb</c> files, but rather as their regular formats.
/// Represents a version of <see cref="ContentManager"/> that doesn't load content binary <c>xnb</c> files, but rather as their regular formats.
/// </summary>
public class RawContentManager : ContentManager, IGameComponent {
private static List<RawContentReader> readers;
private readonly List<IDisposable> disposableAssets = new List<IDisposable>();
/// <summary>
/// The graphics device that this content manager uses
/// </summary>
public readonly GraphicsDevice GraphicsDevice;
private readonly List<IDisposable> disposableAssets = new List<IDisposable>();
#if FNA
private Dictionary<string, object> LoadedAssets { get; } = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
#endif
/// <summary>
/// Creates a new content manager with an optionally specified root directory.
/// </summary>
@ -50,7 +53,11 @@ namespace MLEM.Data.Content {
/// <param name="originalAssetName">The original name of the asset.</param>
/// <param name="currentAsset">The current asset instance.</param>
/// <typeparam name="T">The asset's type.</typeparam>
protected override void ReloadAsset<T>(string originalAssetName, T currentAsset) {
protected
#if !FNA
override
#endif
void ReloadAsset<T>(string originalAssetName, T currentAsset) {
this.Read(originalAssetName, currentAsset);
}

View file

@ -9,10 +9,13 @@ namespace MLEM.Data.Content {
/// <inheritdoc />
protected override Texture2D Read(RawContentManager manager, string assetPath, Stream stream, Texture2D existing) {
#if !FNA
if (existing != null) {
existing.Reload(stream);
return existing;
} else {
} else
#endif
{
// premultiply the texture's color to be in line with the pipeline's texture reader
// TODO this can be converted to use https://github.com/MonoGame/MonoGame/pull/7369 in the future
using (var texture = Texture2D.FromStream(manager.GraphicsDevice, stream)) {

View file

@ -5,12 +5,13 @@ using System.Text.RegularExpressions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using MLEM.Extensions;
using MLEM.Textures;
namespace MLEM.Data {
/// <summary>
/// <para>
/// This class represents an atlas of <see cref="TextureRegion"/> objects which are loaded from a special texture atlas file.
/// This class represents an atlas of <see cref="TextureRegion"/> objects which are loaded from a special texture atlas file.
/// To load a data texture atlas, you can use <see cref="DataTextureAtlasExtensions.LoadTextureAtlas"/>.
/// </para>
/// <para>
@ -95,7 +96,7 @@ namespace MLEM.Data {
var loc = new Rectangle(
int.Parse(match.Groups[2].Value), int.Parse(match.Groups[3].Value),
int.Parse(match.Groups[4].Value), int.Parse(match.Groups[5].Value));
loc.Offset(off);
loc.Offset(off.ToPoint());
// pivot
var piv = !match.Groups[6].Success ? Vector2.Zero : off + new Vector2(

View file

@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<RootNamespace>MLEM.Data</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
<NoWarn>NU1701</NoWarn>
</PropertyGroup>
<PropertyGroup>
<Authors>Ellpeck</Authors>
<Description>Simple loading and processing of textures and other data for FNA, including the ability to load non-XNB content files easily</Description>
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes>
<PackageTags>fna ellpeck mlem utility extensions data serialize</PackageTags>
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>Logo.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
<!--TODO remove lidgren support eventually (methods marked as obsolete since 5.2.0)-->
<PackageReference Include="Lidgren.Network" Version="1.0.2">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json.Bson" Version="1.0.2">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<ProjectReference Include="..\FNA\FNA.Core.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
</ItemGroup>
</Project>

View file

@ -3,8 +3,9 @@
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<NoWarn>NU1701</NoWarn>
</PropertyGroup>
<PropertyGroup>
<Authors>Ellpeck</Authors>
<Description>Simple loading and processing of textures and other data for MonoGame, including the ability to load non-XNB content files easily</Description>
@ -15,12 +16,11 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>Logo.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<NoWarn>NU1701</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MLEM\MLEM.csproj" />
<!--TODO remove lidgren support eventually (methods marked as obsolete since 5.2.0)-->
<PackageReference Include="Lidgren.Network" Version="1.0.2">
<PrivateAssets>all</PrivateAssets>
@ -35,9 +35,9 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
</ItemGroup>
</Project>
</Project>

View file

@ -170,7 +170,7 @@ namespace MLEM.Data {
foreach (var request in this.texturesToPack.OrderByDescending(t => t.Texture.Width * t.Texture.Height)) {
request.PackedArea = this.FindFreeArea(request);
// if this is the first position that this request fit in, no other requests of the same size will find a position before it
this.firstPossiblePosForSizeCache[request.PackedArea.Size] = request.PackedArea.Location;
this.firstPossiblePosForSizeCache[new Point(request.PackedArea.Width, request.PackedArea.Height)] = request.PackedArea.Location;
this.alreadyPackedTextures.Add(request);
}
stopwatch.Stop();
@ -198,7 +198,7 @@ namespace MLEM.Data {
// invoke callbacks
foreach (var request in this.alreadyPackedTextures) {
var packedArea = request.PackedArea.Shrink(new Point(request.Padding));
var packedArea = request.PackedArea.Shrink(new Point(request.Padding, request.Padding));
request.Result.Invoke(new TextureRegion(this.PackedTexture, packedArea));
if (this.disposeTextures)
request.Texture.Texture.Dispose();
@ -232,7 +232,7 @@ namespace MLEM.Data {
var lowestY = int.MaxValue;
while (true) {
var intersected = false;
var area = new Rectangle(pos, size);
var area = new Rectangle(pos.X, pos.Y, size.X, size.Y);
foreach (var tex in this.alreadyPackedTextures) {
if (tex.PackedArea.Intersects(area)) {
pos.X = tex.PackedArea.Right;
@ -255,7 +255,7 @@ namespace MLEM.Data {
private void CopyRegion(TextureData destination, Request request) {
var data = this.GetCachedTextureData(request.Texture.Texture);
var location = request.PackedArea.Location + new Point(request.Padding);
var location = request.PackedArea.Location + new Point(request.Padding, request.Padding);
for (var x = -request.Padding; x < request.Texture.Width + request.Padding; x++) {
for (var y = -request.Padding; y < request.Texture.Height + request.Padding; y++) {
Color srcColor;
@ -264,7 +264,7 @@ namespace MLEM.Data {
srcColor = Color.Transparent;
} else {
// otherwise, we just use the closest pixel that is actually in bounds, causing the border pixels to be doubled up
var src = new Point(MathHelper.Clamp(x, 0, request.Texture.Width - 1), MathHelper.Clamp(y, 0, request.Texture.Height - 1));
var src = new Point((int) MathHelper.Clamp(x, 0, request.Texture.Width - 1), (int) MathHelper.Clamp(y, 0, request.Texture.Height - 1));
srcColor = data[request.Texture.Position + src];
}
destination[location + new Point(x, y)] = srcColor;

View file

@ -0,0 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<RootNamespace>MLEM.Extended</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
<NoWarn>NU1702</NoWarn>
</PropertyGroup>
<PropertyGroup>
<Authors>Ellpeck</Authors>
<Description>MLEM Library for Extending FNA extension that ties in with other FNA libraries</Description>
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes>
<PackageTags>fna ellpeck mlem utility extensions extended</PackageTags>
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>Logo.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
<ProjectReference Include="..\FontStashSharp\src\XNA\FontStashSharp.FNA.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
<ProjectReference Include="..\FNA\FNA.Core.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
<Compile Remove="Tiled/**" />
<Compile Remove="Extensions/**" />
<Compile Remove="Font/GenericBitmapFont.cs" />
</ItemGroup>
<ItemGroup>
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
</ItemGroup>
</Project>

View file

@ -26,7 +26,7 @@
<PackageReference Include="MonoGame.Extended.Tiled" Version="3.8.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FontStashSharp.MonoGame" Version="1.0.4">
<PackageReference Include="FontStashSharp.MonoGame" Version="1.1.5">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">

76
MLEM.FNA.sln Normal file
View file

@ -0,0 +1,76 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.FNA", "MLEM\MLEM.FNA.csproj", "{C2C88AE6-6274-4395-8B03-52AE898BA070}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.Ui.FNA", "MLEM.Ui\MLEM.Ui.FNA.csproj", "{1B47A40B-3BF6-4933-A7DB-57EADEBDFB61}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.Startup.FNA", "MLEM.Startup\MLEM.Startup.FNA.csproj", "{FBE2C9B5-293D-47A7-9BA1-5A4BD1C4E816}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.Data.FNA", "MLEM.Data\MLEM.Data.FNA.csproj", "{6587BC91-0640-43FB-988A-4F545B8ACFC5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demos.FNA", "Demos\Demos.FNA.csproj", "{D83D7D24-14CB-4A7C-A80B-BA4FEC66AB55}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demos.DesktopGL.FNA", "Demos.DesktopGL\Demos.DesktopGL.FNA.csproj", "{AB08FEC7-3AC3-4FDE-B632-226FAAD7F73F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.FNA", "Tests\Tests.FNA.csproj", "{C74FC4C5-3BE0-42A7-8BA6-4A9AD438A9E2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.Extended.FNA", "MLEM.Extended\MLEM.Extended.FNA.csproj", "{A5B22930-DF4B-4A62-93ED-A6549F7B666B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FontStashSharp.FNA", "FontStashSharp\src\XNA\FontStashSharp.FNA.csproj", "{B805851C-9802-4239-AB16-AE77226771CA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA", "FNA\FNA.csproj", "{35253CE1-C864-4CD3-8249-4D1319748E8F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA.Core", "FNA\FNA.Core.csproj", "{06459F72-CEAA-4B45-B2B1-708FC28D04F8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C2C88AE6-6274-4395-8B03-52AE898BA070}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C2C88AE6-6274-4395-8B03-52AE898BA070}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C2C88AE6-6274-4395-8B03-52AE898BA070}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C2C88AE6-6274-4395-8B03-52AE898BA070}.Release|Any CPU.Build.0 = Release|Any CPU
{1B47A40B-3BF6-4933-A7DB-57EADEBDFB61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B47A40B-3BF6-4933-A7DB-57EADEBDFB61}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1B47A40B-3BF6-4933-A7DB-57EADEBDFB61}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B47A40B-3BF6-4933-A7DB-57EADEBDFB61}.Release|Any CPU.Build.0 = Release|Any CPU
{FBE2C9B5-293D-47A7-9BA1-5A4BD1C4E816}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FBE2C9B5-293D-47A7-9BA1-5A4BD1C4E816}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FBE2C9B5-293D-47A7-9BA1-5A4BD1C4E816}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FBE2C9B5-293D-47A7-9BA1-5A4BD1C4E816}.Release|Any CPU.Build.0 = Release|Any CPU
{6587BC91-0640-43FB-988A-4F545B8ACFC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6587BC91-0640-43FB-988A-4F545B8ACFC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6587BC91-0640-43FB-988A-4F545B8ACFC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6587BC91-0640-43FB-988A-4F545B8ACFC5}.Release|Any CPU.Build.0 = Release|Any CPU
{D83D7D24-14CB-4A7C-A80B-BA4FEC66AB55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D83D7D24-14CB-4A7C-A80B-BA4FEC66AB55}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D83D7D24-14CB-4A7C-A80B-BA4FEC66AB55}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D83D7D24-14CB-4A7C-A80B-BA4FEC66AB55}.Release|Any CPU.Build.0 = Release|Any CPU
{AB08FEC7-3AC3-4FDE-B632-226FAAD7F73F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AB08FEC7-3AC3-4FDE-B632-226FAAD7F73F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AB08FEC7-3AC3-4FDE-B632-226FAAD7F73F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AB08FEC7-3AC3-4FDE-B632-226FAAD7F73F}.Release|Any CPU.Build.0 = Release|Any CPU
{C74FC4C5-3BE0-42A7-8BA6-4A9AD438A9E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C74FC4C5-3BE0-42A7-8BA6-4A9AD438A9E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C74FC4C5-3BE0-42A7-8BA6-4A9AD438A9E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C74FC4C5-3BE0-42A7-8BA6-4A9AD438A9E2}.Release|Any CPU.Build.0 = Release|Any CPU
{A5B22930-DF4B-4A62-93ED-A6549F7B666B}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU
{B805851C-9802-4239-AB16-AE77226771CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B805851C-9802-4239-AB16-AE77226771CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B805851C-9802-4239-AB16-AE77226771CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B805851C-9802-4239-AB16-AE77226771CA}.Release|Any CPU.Build.0 = Release|Any CPU
{35253CE1-C864-4CD3-8249-4D1319748E8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{35253CE1-C864-4CD3-8249-4D1319748E8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{35253CE1-C864-4CD3-8249-4D1319748E8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{35253CE1-C864-4CD3-8249-4D1319748E8F}.Release|Any CPU.Build.0 = Release|Any CPU
{06459F72-CEAA-4B45-B2B1-708FC28D04F8}.Debug|Any CPU.ActiveCfg = Debug|x64
{06459F72-CEAA-4B45-B2B1-708FC28D04F8}.Debug|Any CPU.Build.0 = Debug|x64
{06459F72-CEAA-4B45-B2B1-708FC28D04F8}.Release|Any CPU.ActiveCfg = Release|x64
{06459F72-CEAA-4B45-B2B1-708FC28D04F8}.Release|Any CPU.Build.0 = Release|x64
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<RootNamespace>MLEM.Startup</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<Authors>Ellpeck</Authors>
<Description>MLEM Library for Extending FNA combined with some other useful libraries into a quick Game startup class</Description>
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes>
<PackageTags>fna ellpeck mlem utility extensions</PackageTags>
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>Logo.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Coroutine" Version="2.1.3" />
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.FNA.csproj" />
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
<ProjectReference Include="..\FNA\FNA.Core.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
</ItemGroup>
</Project>

View file

@ -69,7 +69,9 @@ namespace MLEM.Startup {
this.GraphicsDeviceManager = new GraphicsDeviceManager(this) {
PreferredBackBufferWidth = windowWidth,
PreferredBackBufferHeight = windowHeight,
#if !FNA
HardwareModeSwitch = false
#endif
};
this.Window.AllowUserResizing = true;
this.Content.RootDirectory = "Content";

View file

@ -70,7 +70,7 @@ namespace MLEM.Ui.Elements {
/// <summary>
/// The size of this element, where X represents the width and Y represents the height.
/// If the x or y value of the size is between 0 and 1, the size will be seen as a percentage of its parent's size rather than as an absolute value.
/// If the x (or y) value of the size is negative, the width (or height) is seen as a percentage of the element's resulting height (or width).
/// If the x (or y) value of the size is negative, the width (or height) is seen as a percentage of the element's resulting height (or width).
/// </summary>
/// <example>
/// The following example combines both types of percentage-based sizing.
@ -178,12 +178,12 @@ namespace MLEM.Ui.Elements {
/// <summary>
/// This element's transform matrix.
/// Can easily be scaled using <see cref="ScaleTransform"/>.
/// Note that, when this is non-null, a new <see cref="SpriteBatch.Begin"/> call is used for this element.
/// Note that, when this is non-null, a new <c>SpriteBatch.Begin</c> call is used for this element.
/// </summary>
public Matrix Transform = Matrix.Identity;
/// <summary>
/// The call that this element should make to <see cref="SpriteBatch"/> to begin drawing.
/// Note that, when this is non-null, a new <see cref="SpriteBatch.Begin"/> call is used for this element.
/// Note that, when this is non-null, a new <c>SpriteBatch.Begin</c> call is used for this element.
/// </summary>
#pragma warning disable CS0618
[Obsolete("BeginImpl is deprecated. You can create a custom element class and override Draw instead.")]
@ -927,7 +927,7 @@ namespace MLEM.Ui.Elements {
/// <summary>
/// Draws this element by calling <see cref="Draw(Microsoft.Xna.Framework.GameTime,Microsoft.Xna.Framework.Graphics.SpriteBatch,float,MLEM.Graphics.SpriteBatchContext)"/> internally.
/// If <see cref="Transform"/> or <see cref="BeginImpl"/> is set, a new <see cref="SpriteBatch.Begin"/> call is also started.
/// If <see cref="Transform"/> or <see cref="BeginImpl"/> is set, a new <c>SpriteBatch.Begin</c> call is also started.
/// </summary>
/// <param name="time">The game's time</param>
/// <param name="batch">The sprite batch to use for drawing</param>
@ -944,7 +944,7 @@ namespace MLEM.Ui.Elements {
/// <summary>
/// Draws this element by calling <see cref="Draw(Microsoft.Xna.Framework.GameTime,Microsoft.Xna.Framework.Graphics.SpriteBatch,float,MLEM.Graphics.SpriteBatchContext)"/> internally.
/// If <see cref="Transform"/> or <see cref="BeginImpl"/> is set, a new <see cref="SpriteBatch.Begin"/> call is also started.
/// If <see cref="Transform"/> or <see cref="BeginImpl"/> is set, a new <c>SpriteBatch.Begin</c> call is also started.
/// </summary>
/// <param name="time">The game's time</param>
/// <param name="batch">The sprite batch to use for drawing</param>
@ -981,7 +981,7 @@ namespace MLEM.Ui.Elements {
/// <summary>
/// Draws this element and all of its children. Override this method to draw the content of custom elements.
/// Note that, when this is called, <see cref="SpriteBatch.Begin"/> has already been called with custom <see cref="Transform"/> etc. applied.
/// Note that, when this is called, <c>SpriteBatch.Begin</c> has already been called with custom <see cref="Transform"/> etc. applied.
/// </summary>
/// <param name="time">The game's time</param>
/// <param name="batch">The sprite batch to use for drawing</param>
@ -998,7 +998,7 @@ namespace MLEM.Ui.Elements {
/// <summary>
/// Draws this element and all of its children. Override this method to draw the content of custom elements.
/// Note that, when this is called, <see cref="SpriteBatch.Begin"/> has already been called with custom <see cref="Transform"/> etc. applied.
/// Note that, when this is called, <c>SpriteBatch.Begin</c> has already been called with custom <see cref="Transform"/> etc. applied.
/// </summary>
/// <param name="time">The game's time</param>
/// <param name="batch">The sprite batch to use for drawing</param>
@ -1021,7 +1021,7 @@ namespace MLEM.Ui.Elements {
/// <summary>
/// Draws this element and all of its <see cref="GetRelevantChildren"/> early.
/// Drawing early involves drawing onto <see cref="RenderTarget2D"/> instances rather than onto the screen.
/// Note that, when this is called, <see cref="SpriteBatch.Begin"/> has not yet been called.
/// Note that, when this is called, <c>SpriteBatch.Begin</c> has not yet been called.
/// </summary>
/// <param name="time">The game's time</param>
/// <param name="batch">The sprite batch to use for drawing</param>

View file

@ -1,6 +1,7 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MLEM.Extensions;
using MLEM.Graphics;
using MLEM.Misc;
using MLEM.Textures;

View file

@ -135,8 +135,8 @@ namespace MLEM.Ui.Elements {
protected override Vector2 CalcActualSize(RectangleF parentArea) {
var size = base.CalcActualSize(parentArea);
this.ParseText(size);
var (w, h) = this.TokenizedText.Measure(this.RegularFont) * this.TextScale * this.TextScaleMultiplier * this.Scale;
return new Vector2(this.AutoAdjustWidth ? w + this.ScaledPadding.Width : size.X, h + this.ScaledPadding.Height);
var textSize = this.TokenizedText.Measure(this.RegularFont) * this.TextScale * this.TextScaleMultiplier * this.Scale;
return new Vector2(this.AutoAdjustWidth ? textSize.X + this.ScaledPadding.Width : size.X, textSize.Y + this.ScaledPadding.Height);
}
/// <inheritdoc />

View file

@ -3,6 +3,7 @@ using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input.Touch;
using MLEM.Extensions;
using MLEM.Graphics;
using MLEM.Input;
using MLEM.Misc;
@ -195,13 +196,13 @@ namespace MLEM.Ui.Elements {
}
private void ScrollToPos(Vector2 position) {
var (width, height) = this.ScrollerSize * this.Scale;
var size = this.ScrollerSize * this.Scale;
if (this.Horizontal) {
var offset = this.scrollStartOffset.X >= 0 && this.scrollStartOffset.X <= width ? this.scrollStartOffset.X : width / 2;
this.CurrentValue = (position.X - this.Area.X - offset) / (this.Area.Width - width) * this.MaxValue;
var offset = this.scrollStartOffset.X >= 0 && this.scrollStartOffset.X <= size.X ? this.scrollStartOffset.X : size.X / 2;
this.CurrentValue = (position.X - this.Area.X - offset) / (this.Area.Width - size.X) * this.MaxValue;
} else {
var offset = this.scrollStartOffset.Y >= 0 && this.scrollStartOffset.Y <= height ? this.scrollStartOffset.Y : height / 2;
this.CurrentValue = (position.Y - this.Area.Y - offset) / (this.Area.Height - height) * this.MaxValue;
var offset = this.scrollStartOffset.Y >= 0 && this.scrollStartOffset.Y <= size.Y ? this.scrollStartOffset.Y : size.Y / 2;
this.CurrentValue = (position.Y - this.Area.Y - offset) / (this.Area.Height - size.Y) * this.MaxValue;
}
}

View file

@ -1,3 +1,4 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MLEM.Font;

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using MLEM.Extensions;
using MLEM.Input;
using MLEM.Ui.Style;

View file

@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<RootNamespace>MLEM.Ui</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<Authors>Ellpeck</Authors>
<Description>A mouse, keyboard, gamepad and touch ready Ui system for FNA that features automatic anchoring, sizing and several ready-to-use element types</Description>
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes>
<PackageTags>fna ellpeck mlem ui user interface graphical gui system mouse keyboard gamepad touch</PackageTags>
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>Logo.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TextCopy" Version="6.1.0" />
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
<ProjectReference Include="..\FNA\FNA.Core.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
</ItemGroup>
</Project>

View file

@ -18,7 +18,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TextCopy" Version="4.3.1" />
<PackageReference Include="TextCopy" Version="6.1.0" />
<ProjectReference Include="..\MLEM\MLEM.csproj" />
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">

View file

@ -170,8 +170,13 @@ namespace MLEM.Ui.Parsers {
Texture2D tex;
if (loc.StartsWith("http")) {
using (var client = new HttpClient()) {
using (var src = await client.GetStreamAsync(loc))
tex = Texture2D.FromStream(this.GraphicsDevice, src);
using (var src = await client.GetStreamAsync(loc)) {
using (var memory = new MemoryStream()) {
// download the full stream before passing it to texture
await src.CopyToAsync(memory);
tex = Texture2D.FromStream(this.GraphicsDevice, memory);
}
}
}
} else {
using (var stream = Path.IsPathRooted(loc) ? File.OpenRead(loc) : TitleContainer.OpenStream(loc))

View file

@ -159,7 +159,7 @@ namespace MLEM.Ui {
// MOUSE INPUT
if (this.HandleMouse) {
var mousedNow = this.GetElementUnderPos(this.Input.ViewportMousePosition.ToVector2());
var mousedNow = this.GetElementUnderPos(new Vector2(this.Input.ViewportMousePosition.X, this.Input.ViewportMousePosition.Y));
this.SetMousedElement(mousedNow);
if (this.Input.IsMouseButtonPressedAvailable(MouseButton.Left)) {
@ -414,8 +414,8 @@ namespace MLEM.Ui {
foreach (var child in children) {
if (child == this.SelectedElement)
continue;
var (xOffset, yOffset) = child.Area.Center - this.SelectedElement.Area.Center;
var angle = Math.Abs(MathHelper.WrapAngle(direction.Angle() - (float) Math.Atan2(yOffset, xOffset)));
var offset = child.Area.Center - this.SelectedElement.Area.Center;
var angle = Math.Abs(MathHelper.WrapAngle(direction.Angle() - (float) Math.Atan2(offset.Y, offset.X)));
if (angle >= MathHelper.PiOver2 - Element.Epsilon)
continue;
var distSq = child.Area.DistanceSquared(this.SelectedElement.Area);

View file

@ -5,7 +5,6 @@ using MLEM.Ui.Elements;
namespace MLEM.Ui {
/// <summary>
/// A snapshot of update and rendering statistics from <see cref="UiSystem.Metrics"/> to be used for runtime debugging and profiling.
/// This metrics struct works similarly to <see cref="GraphicsMetrics"/>.
/// </summary>
public struct UiMetrics {

View file

@ -88,7 +88,7 @@ namespace MLEM.Ui {
public SamplerState SamplerState;
/// <summary>
/// The depth stencil state that this ui system and all of its elements draw with.
/// The default is <see cref="Microsoft.Xna.Framework.Graphics.DepthStencilState.None"/>, which is also the default for <see cref="SpriteBatch.Begin"/>.
/// The default is <see cref="Microsoft.Xna.Framework.Graphics.DepthStencilState.None"/>, which is also the default for <c>SpriteBatch.Begin</c>.
/// </summary>
[Obsolete("Set this through SpriteBatchContext instead")]
public DepthStencilState DepthStencilState;
@ -241,10 +241,10 @@ namespace MLEM.Ui {
MlemPlatform.Current?.AddTextInputListener(game.Window, (sender, key, character) => this.ApplyToAll(e => e.OnTextInput?.Invoke(e, key, character)));
if (automaticViewport) {
this.Viewport = new Rectangle(Point.Zero, game.Window.ClientBounds.Size);
this.AutoScaleReferenceSize = this.Viewport.Size;
this.Viewport = new Rectangle(0, 0, game.Window.ClientBounds.Width, game.Window.ClientBounds.Height);
this.AutoScaleReferenceSize = new Point(this.Viewport.X, this.Viewport.Y);
game.Window.ClientSizeChanged += (sender, args) => {
this.Viewport = new Rectangle(Point.Zero, game.Window.ClientBounds.Size);
this.Viewport = new Rectangle(0, 0, game.Window.ClientBounds.Width, game.Window.ClientBounds.Height);
};
}
@ -570,11 +570,11 @@ namespace MLEM.Ui {
/// </summary>
public event Element.GenericCallback OnElementRemoved;
/// <summary>
/// Event that is invoked when this <see cref="RootElement"/> gets added to a <see cref="UiSystem"/> in <see cref="UiSystem.Add"/>
/// Event that is invoked when this <see cref="RootElement"/> gets added to a <see cref="UiSystem"/> in <see cref="UiSystem.Add"/>
/// </summary>
public event Action<UiSystem> OnAddedToUi;
/// <summary>
/// Event that is invoked when this <see cref="RootElement"/> gets removed from a <see cref="UiSystem"/> in <see cref="UiSystem.Remove"/>
/// Event that is invoked when this <see cref="RootElement"/> gets removed from a <see cref="UiSystem"/> in <see cref="UiSystem.Remove"/>
/// </summary>
public event Action<UiSystem> OnRemovedFromUi;

View file

@ -58,7 +58,7 @@ namespace MLEM.Cameras {
}
/// <summary>
/// The matrix that this camera "sees", based on its position and scale.
/// Use this in your <see cref="SpriteBatch.Begin"/> calls to render based on the camera's viewport.
/// Use this in your <c>SpriteBatch.Begin</c> calls to render based on the camera's viewport.
/// </summary>
public Matrix ViewMatrix {
get {
@ -105,7 +105,7 @@ namespace MLEM.Cameras {
/// <param name="roundPosition">Whether the camera's <see cref="Position"/> should be rounded to full integers when calculating the <see cref="ViewMatrix"/></param>
public Camera(GraphicsDevice graphicsDevice, bool roundPosition = true) {
this.graphicsDevice = graphicsDevice;
this.AutoScaleReferenceSize = this.Viewport.Size;
this.AutoScaleReferenceSize = new Point(this.Viewport.Width, this.Viewport.Height);
this.RoundPosition = roundPosition;
}
@ -173,7 +173,7 @@ namespace MLEM.Cameras {
/// <param name="delta">The amount to zoom in or out by</param>
/// <param name="zoomCenter">The position that should be regarded as the zoom's center, in screen space. The default value is the center.</param>
public void Zoom(float delta, Vector2? zoomCenter = null) {
var center = (zoomCenter ?? this.Viewport.Size.ToVector2() / 2) / this.ActualScale;
var center = (zoomCenter ?? new Vector2(this.Viewport.Width, this.Viewport.Height) / 2) / this.ActualScale;
var lastScale = this.Scale;
this.Scale += delta;
this.Position += center * ((this.Scale - lastScale) / this.Scale);

View file

@ -57,7 +57,7 @@ namespace MLEM.Extensions {
/// <param name="manager">The graphics device manager</param>
/// <param name="window">The window whose bounds to use</param>
public static void ResetWidthAndHeight(this GraphicsDeviceManager manager, GameWindow window) {
var (_, _, width, height) = window.ClientBounds;
var (width, height) = (window.ClientBounds.Width, window.ClientBounds.Height);
manager.PreferredBackBufferWidth = Math.Max(height, width);
manager.PreferredBackBufferHeight = Math.Min(height, width);
manager.ApplyChanges();
@ -90,7 +90,12 @@ namespace MLEM.Extensions {
/// <param name="target">The target to apply</param>
public TargetContext(GraphicsDevice device, RenderTarget2D target) {
this.device = device;
#if FNA
// RenderTargetCount doesn't exist in FNA but we still want the optimization in MG
this.lastTargets = device.GetRenderTargets();
#else
this.lastTargets = device.RenderTargetCount <= 0 ? null : device.GetRenderTargets();
#endif
device.SetRenderTarget(target);
}

View file

@ -223,13 +223,13 @@ namespace MLEM.Extensions {
/// <param name="matrix">The matrix</param>
/// <returns>The rotation of the matrix</returns>
public static Quaternion Rotation(this Matrix matrix) {
var (scX, scY, scZ) = matrix.Scale();
if (scX == 0 || scY == 0 || scZ == 0)
var sc = matrix.Scale();
if (sc.X == 0 || sc.Y == 0 || sc.Z == 0)
return Quaternion.Identity;
return Quaternion.CreateFromRotationMatrix(new Matrix(
matrix.M11 / scX, matrix.M12 / scX, matrix.M13 / scX, 0,
matrix.M21 / scY, matrix.M22 / scY, matrix.M23 / scY, 0,
matrix.M31 / scZ, matrix.M32 / scZ, matrix.M33 / scZ, 0,
matrix.M11 / sc.X, matrix.M12 / sc.X, matrix.M13 / sc.X, 0,
matrix.M21 / sc.Y, matrix.M22 / sc.Y, matrix.M23 / sc.Y, 0,
matrix.M31 / sc.Z, matrix.M32 / sc.Z, matrix.M33 / sc.Z, 0,
0, 0, 0, 1));
}
@ -250,7 +250,7 @@ namespace MLEM.Extensions {
/// <param name="quaternion">The quaternion</param>
/// <returns>The rotation of the quaternion</returns>
public static Vector3 RotationVector(this Quaternion quaternion) {
var (x, y, z, w) = quaternion;
var (x, y, z, w) = (quaternion.X, quaternion.Y, quaternion.Z, quaternion.W);
return new Vector3(
(float) Math.Atan2(2 * (w * x + y * z), 1 - 2 * (x * x + y * y)),
(float) Math.Asin(MathHelper.Clamp(2 * (w * y - z * x), -1, 1)),
@ -268,16 +268,16 @@ namespace MLEM.Extensions {
/// <param name="penetration">The amount that the penetration occured by, in the direction of <paramref name="normal"/></param>
/// <returns>Whether or not a penetration occured</returns>
public static bool Penetrate(this RectangleF rect, RectangleF other, out Vector2 normal, out float penetration) {
var (offsetX, offsetY) = other.Center - rect.Center;
var overlapX = rect.Width / 2 + other.Width / 2 - Math.Abs(offsetX);
var offset = other.Center - rect.Center;
var overlapX = rect.Width / 2 + other.Width / 2 - Math.Abs(offset.X);
if (overlapX > 0) {
var overlapY = rect.Height / 2 + other.Height / 2 - Math.Abs(offsetY);
var overlapY = rect.Height / 2 + other.Height / 2 - Math.Abs(offset.Y);
if (overlapY > 0) {
if (overlapX < overlapY) {
normal = new Vector2(offsetX < 0 ? -1 : 1, 0);
normal = new Vector2(offset.X < 0 ? -1 : 1, 0);
penetration = overlapX;
} else {
normal = new Vector2(0, offsetY < 0 ? -1 : 1);
normal = new Vector2(0, offset.Y < 0 ? -1 : 1);
penetration = overlapY;
}
return true;
@ -288,5 +288,23 @@ namespace MLEM.Extensions {
return false;
}
#if FNA
/// <summary>
/// Gets a <see cref="Point"/> representation for this object.
/// </summary>
/// <returns>A <see cref="Point"/> representation for this object.</returns>
public static Point ToPoint(this Vector2 vector) {
return new Point((int) vector.X, (int) vector.Y);
}
/// <summary>
/// Gets a <see cref="Vector2"/> representation for this object.
/// </summary>
/// <returns>A <see cref="Vector2"/> representation for this object.</returns>
public static Vector2 ToVector2(this Point point) {
return new Vector2(point.X, point.Y);
}
#endif
}
}

View file

@ -285,18 +285,18 @@ namespace MLEM.Font {
}
private void DrawString(SpriteBatch batch, CharSource text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) {
var (flipX, flipY) = Vector2.Zero;
var (flipX, flipY) = (0F, 0F);
var flippedV = (effects & SpriteEffects.FlipVertically) != 0;
var flippedH = (effects & SpriteEffects.FlipHorizontally) != 0;
if (flippedV || flippedH) {
var (w, h) = this.MeasureString(text, false, null);
var size = this.MeasureString(text, false, null);
if (flippedH) {
origin.X *= -1;
flipX = -w;
flipX = -size.X;
}
if (flippedV) {
origin.Y *= -1;
flipY = this.LineHeight - h;
flipY = this.LineHeight - size.Y;
}
}
@ -327,17 +327,17 @@ namespace MLEM.Font {
}
var cString = c.ToCachedString();
var (cW, cH) = this.MeasureString(cString);
var cSize = this.MeasureString(cString);
var charPos = offset;
if (flippedH)
charPos.X += cW;
charPos.X += cSize.X;
if (flippedV)
charPos.Y += cH - this.LineHeight;
charPos.Y += cSize.Y - this.LineHeight;
Vector2.Transform(ref charPos, ref trans, out charPos);
this.DrawChar(batch, cString, charPos, color, rotation, scale, effects, layerDepth);
offset.X += cW;
offset.X += cSize.X;
}
}

View file

@ -42,6 +42,10 @@ namespace MLEM.Font {
}
private static SpriteFont SetDefaults(SpriteFont font) {
#if FNA
// none of the copying is available with FNA
return font;
#else
// we copy the font here to set the default character to a space
return new SpriteFont(
font.Texture,
@ -52,6 +56,7 @@ namespace MLEM.Font {
font.Spacing,
font.Glyphs.Select(g => new Vector3(g.LeftSideBearing, g.Width, g.RightSideBearing)).ToList(),
' ');
#endif
}
}

View file

@ -23,9 +23,9 @@ namespace MLEM.Formatting.Codes {
// don't underline spaces at the end of lines
if (c == ' ' && token.DisplayString.Length > indexInToken + 1 && token.DisplayString[indexInToken + 1] == '\n')
return false;
var (w, h) = font.MeasureString(cString) * scale;
var t = h * this.thickness;
batch.Draw(batch.GetBlankTexture(), new RectangleF(pos.X, pos.Y + this.yOffset * h - t, w, t), color);
var size = font.MeasureString(cString) * scale;
var t = size.Y * this.thickness;
batch.Draw(batch.GetBlankTexture(), new RectangleF(pos.X, pos.Y + this.yOffset * size.Y - t, size.X, t), color);
return false;
}

View file

@ -62,8 +62,8 @@ namespace MLEM.Graphics {
/// <summary>
/// This method allows for a tiled texture to be drawn in an auto-tiling mode.
/// This allows, for example, a grass patch on a tilemap to have nice looking edges that transfer over into a path without any hard edges between tiles.
///
/// This method is a more complex version of <see cref="DrawAutoTile"/> that overlays separate border textures on a background texture region, which also allows for non-rectangular texture areas to be used easily.
///
/// This method is a more complex version of <see cref="DrawAutoTile"/> that overlays separate border textures on a background texture region, which also allows for non-rectangular texture areas to be used easily.
/// For auto-tiling in this way to work, the overlay sections have to be laid out as follows: 16 sections aligned horizontally within the texture file, with the following information:
/// <list type="number">
/// <item><description>The texture used for straight, horizontal borders, with the borders facing away from the center, split up into four parts: top left, then top right, then bottom left, then bottom right</description></item>
@ -138,8 +138,8 @@ namespace MLEM.Graphics {
var xUr = up && right ? connectsTo(1, -1) ? 0 : 4 : right ? 1 : up ? 3 : 2;
var xDl = down && left ? connectsTo(-1, 1) ? 0 : 4 : left ? 1 : down ? 3 : 2;
var xDr = down && right ? connectsTo(1, 1) ? 0 : 4 : right ? 1 : down ? 3 : 2;
var (w, h) = textureRegion.Size;
var (w2, h2) = new Point(w / 2, h / 2);
var (w, h) = (textureRegion.Width, textureRegion.Height);
var (w2, h2) = (w / 2, h / 2);
return (
new Vector2(pos.X, pos.Y), new Rectangle(textureRegion.X + xUl * w, textureRegion.Y, w2, h2),
new Vector2(pos.X + w2 * scale.X, pos.Y), new Rectangle(textureRegion.X + w2 + xUr * w, textureRegion.Y, w2, h2),
@ -156,7 +156,7 @@ namespace MLEM.Graphics {
var xUr = up && right ? connectsTo(1, -1) ? -1 : 13 : right ? 1 : up ? 9 : 5;
var xDl = down && left ? connectsTo(-1, 1) ? -1 : 14 : left ? 2 : down ? 10 : 6;
var xDr = down && right ? connectsTo(1, 1) ? -1 : 15 : right ? 3 : down ? 11 : 7;
var (w, h) = textureRegion.Size;
var (w, h) = (textureRegion.Width, textureRegion.Height);
return (
xUl < 0 ? Rectangle.Empty : new Rectangle(textureRegion.X + xUl * w, textureRegion.Y, w, h),
xUr < 0 ? Rectangle.Empty : new Rectangle(textureRegion.X + xUr * w, textureRegion.Y, w, h),

View file

@ -3,7 +3,7 @@ using Microsoft.Xna.Framework.Graphics;
namespace MLEM.Graphics {
/// <summary>
/// A sprite batch context is a set of information for a <see cref="SpriteBatch"/> to use, which encapsulates all of the information usually passed directly to <see cref="SpriteBatch.Begin"/>.
/// A sprite batch context is a set of information for a <see cref="SpriteBatch"/> to use, which encapsulates all of the information usually passed directly to <c>SpriteBatch.Begin</c>.
/// To use a sprite batch context effectively, the extension methods in <see cref="SpriteBatchContextExtensions"/> should be used.
/// </summary>
public struct SpriteBatchContext {
@ -17,7 +17,7 @@ namespace MLEM.Graphics {
/// </summary>
public BlendState BlendState;
/// <summary>
/// State of the sampler.
/// State of the sampler.
/// </summary>
public SamplerState SamplerState;
/// <summary>

View file

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MLEM.Extensions;
namespace MLEM.Graphics {
/// <summary>
@ -174,18 +176,18 @@ namespace MLEM.Graphics {
for (var i = 0; i < this.FilledBuffers; i++) {
var buffer = this.vertexBuffers[i];
var texture = this.textures[i];
var tris = Math.Min(this.items.Count * 4 - totalIndex, buffer.VertexCount) / 4 * 2;
var verts = Math.Min(this.items.Count * 4 - totalIndex, buffer.VertexCount);
this.graphicsDevice.SetVertexBuffer(buffer);
if (effect != null) {
foreach (var pass in effect.CurrentTechnique.Passes) {
pass.Apply();
this.graphicsDevice.Textures[0] = texture;
this.graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, tris);
this.DrawPrimitives(verts);
}
} else {
this.graphicsDevice.Textures[0] = texture;
this.graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, tris);
this.DrawPrimitives(verts);
}
totalIndex += buffer.VertexCount;
@ -290,10 +292,11 @@ namespace MLEM.Graphics {
if ((effects & SpriteEffects.FlipHorizontally) != 0)
(texBr.X, texTl.X) = (texTl.X, texBr.X);
var destSize = new Vector2(destinationRectangle.Width, destinationRectangle.Height);
if (rotation == 0) {
return this.Add(texture, destinationRectangle.Location.ToVector2() - origin, destinationRectangle.Size.ToVector2(), color, texTl, texBr, layerDepth);
return this.Add(texture, destinationRectangle.Location.ToVector2() - origin, destSize, color, texTl, texBr, layerDepth);
} else {
return this.Add(texture, destinationRectangle.Location.ToVector2(), -origin, destinationRectangle.Size.ToVector2(), (float) Math.Sin(rotation), (float) Math.Cos(rotation), color, texTl, texBr, layerDepth);
return this.Add(texture, destinationRectangle.Location.ToVector2(), -origin, destSize, (float) Math.Sin(rotation), (float) Math.Cos(rotation), color, texTl, texBr, layerDepth);
}
}
@ -439,6 +442,14 @@ namespace MLEM.Graphics {
this.textures.Insert(index, texture);
}
private void DrawPrimitives(int vertices) {
#if FNA
this.graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices, 0, vertices / 4 * 2);
#else
this.graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices / 4 * 2);
#endif
}
/// <summary>
/// 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"/>.
@ -463,5 +474,58 @@ namespace MLEM.Graphics {
}
#if FNA
private class SpriteEffect : Effect {
private EffectParameter matrixParam;
private Viewport lastViewport;
private Matrix projection;
public Matrix? TransformMatrix { get; set; }
public SpriteEffect(GraphicsDevice device) : base(device, SpriteEffect.LoadEffectCode()) {
this.CacheEffectParameters();
}
private SpriteEffect(SpriteEffect cloneSource) : base(cloneSource) {
this.CacheEffectParameters();
}
public override Effect Clone() {
return new SpriteEffect(this);
}
private void CacheEffectParameters() {
this.matrixParam = this.Parameters["MatrixTransform"];
}
protected override void OnApply() {
var vp = this.GraphicsDevice.Viewport;
if (vp.Width != this.lastViewport.Width || vp.Height != this.lastViewport.Height) {
Matrix.CreateOrthographicOffCenter(0, vp.Width, vp.Height, 0, 0, -1, out this.projection);
this.projection.M41 += -0.5f * this.projection.M11;
this.projection.M42 += -0.5f * this.projection.M22;
this.lastViewport = vp;
}
if (this.TransformMatrix.HasValue) {
this.matrixParam.SetValue(this.TransformMatrix.GetValueOrDefault() * this.projection);
} else {
this.matrixParam.SetValue(this.projection);
}
}
private static byte[] LoadEffectCode() {
using (var stream = typeof(Effect).Assembly.GetManifestResourceStream("Microsoft.Xna.Framework.Graphics.Effect.Resources.SpriteEffect.fxb")) {
using (var memory = new MemoryStream()) {
stream.CopyTo(memory);
return memory.ToArray();
}
}
}
}
#endif
}
}

View file

@ -15,6 +15,12 @@ namespace MLEM.Input {
/// </summary>
public class InputHandler : GameComponent {
#if FNA
private const int MaximumGamePadCount = 4;
#else
private static readonly int MaximumGamePadCount = GamePad.MaximumGamePadCount;
#endif
/// <summary>
/// Contains all of the gestures that have finished during the last update call.
/// To easily query these gestures, use <see cref="GetGesture"/> or <see cref="GetViewportGesture"/>.
@ -80,11 +86,11 @@ namespace MLEM.Input {
/// <summary>
/// Contains the touch state from the last update call
/// </summary>
public TouchCollection LastTouchState { get; private set; }
public TouchCollection LastTouchState { get; private set; } = new TouchCollection(Array.Empty<TouchLocation>());
/// <summary>
/// Contains the current touch state
/// </summary>
public TouchCollection TouchState { get; private set; }
public TouchCollection TouchState { get; private set; } = new TouchCollection(Array.Empty<TouchLocation>());
/// <summary>
/// Contains the <see cref="LastTouchState"/>, but with the <see cref="GraphicsDevice.Viewport"/> taken into account.
/// </summary>
@ -109,7 +115,7 @@ namespace MLEM.Input {
/// <summary>
/// Contains the position of the mouse from the last update call, extracted from <see cref="LastMouseState"/>
/// </summary>
public Point LastMousePosition => this.LastMouseState.Position;
public Point LastMousePosition => new Point(this.LastMouseState.X, this.LastMouseState.Y);
/// <summary>
/// Contains the <see cref="LastMousePosition"/>, but with the <see cref="GraphicsDevice.Viewport"/> taken into account.
/// </summary>
@ -117,7 +123,7 @@ namespace MLEM.Input {
/// <summary>
/// Contains the current position of the mouse, extracted from <see cref="MouseState"/>
/// </summary>
public Point MousePosition => this.MouseState.Position;
public Point MousePosition => new Point(this.MouseState.X, this.MouseState.Y);
/// <summary>
/// Contains the <see cref="MousePosition"/>, but with the <see cref="GraphicsDevice.Viewport"/> taken into account.
/// </summary>
@ -139,11 +145,11 @@ namespace MLEM.Input {
/// </summary>
public KeyboardState KeyboardState { get; private set; }
private readonly GamePadState[] lastGamepads = new GamePadState[GamePad.MaximumGamePadCount];
private readonly GamePadState[] gamepads = new GamePadState[GamePad.MaximumGamePadCount];
private readonly DateTime[] lastGamepadButtonRepeats = new DateTime[GamePad.MaximumGamePadCount];
private readonly bool[] triggerGamepadButtonRepeat = new bool[GamePad.MaximumGamePadCount];
private readonly Buttons?[] heldGamepadButtons = new Buttons?[GamePad.MaximumGamePadCount];
private readonly GamePadState[] lastGamepads = new GamePadState[MaximumGamePadCount];
private readonly GamePadState[] gamepads = new GamePadState[MaximumGamePadCount];
private readonly DateTime[] lastGamepadButtonRepeats = new DateTime[MaximumGamePadCount];
private readonly bool[] triggerGamepadButtonRepeat = new bool[MaximumGamePadCount];
private readonly Buttons?[] heldGamepadButtons = new Buttons?[MaximumGamePadCount];
private readonly List<GestureSample> gestures = new List<GestureSample>();
private readonly HashSet<(GenericInput, int)> consumedPresses = new HashSet<(GenericInput, int)>();
@ -209,7 +215,7 @@ namespace MLEM.Input {
if (this.HandleMouse) {
this.LastMouseState = this.MouseState;
var state = Mouse.GetState();
if (active && this.Game.GraphicsDevice.Viewport.Bounds.Contains(state.Position)) {
if (active && this.Game.GraphicsDevice.Viewport.Bounds.Contains(state.X, state.Y)) {
this.MouseState = state;
foreach (var button in MouseExtensions.MouseButtons) {
if (state.GetState(button) == ButtonState.Pressed)
@ -217,18 +223,22 @@ namespace MLEM.Input {
}
} else {
// mouse position and scroll wheel value should be preserved when the mouse is out of bounds
#if FNA
this.MouseState = new MouseState(state.X, state.Y, state.ScrollWheelValue, 0, 0, 0, 0, 0);
#else
this.MouseState = new MouseState(state.X, state.Y, state.ScrollWheelValue, 0, 0, 0, 0, 0, state.HorizontalScrollWheelValue);
#endif
}
}
if (this.HandleGamepads) {
this.ConnectedGamepads = GamePad.MaximumGamePadCount;
for (var i = 0; i < GamePad.MaximumGamePadCount; i++) {
this.ConnectedGamepads = MaximumGamePadCount;
for (var i = 0; i < MaximumGamePadCount; i++) {
this.lastGamepads[i] = this.gamepads[i];
this.gamepads[i] = GamePadState.Default;
if (GamePad.GetCapabilities(i).IsConnected) {
this.gamepads[i] = default;
if (GamePad.GetCapabilities((PlayerIndex) i).IsConnected) {
if (active) {
this.gamepads[i] = GamePad.GetState(i);
this.gamepads[i] = GamePad.GetState((PlayerIndex) i);
foreach (var button in EnumHelper.Buttons) {
if (this.IsGamepadButtonDown(button, i))
this.AccumulateDown(button, i);
@ -263,12 +273,13 @@ namespace MLEM.Input {
this.LastTouchState = this.TouchState;
this.LastViewportTouchState = this.ViewportTouchState;
this.TouchState = active ? TouchPanel.GetState() : default;
this.TouchState = active ? TouchPanel.GetState() : new TouchCollection(Array.Empty<TouchLocation>());
if (this.TouchState.Count > 0 && this.ViewportOffset != Point.Zero) {
this.ViewportTouchState = new List<TouchLocation>();
foreach (var touch in this.TouchState) {
touch.TryGetPreviousLocation(out var previous);
this.ViewportTouchState.Add(new TouchLocation(touch.Id, touch.State, touch.Position + this.ViewportOffset.ToVector2(), previous.State, previous.Position + this.ViewportOffset.ToVector2()));
var offset = new Vector2(this.ViewportOffset.X, this.ViewportOffset.Y);
this.ViewportTouchState.Add(new TouchLocation(touch.Id, touch.State, touch.Position + offset, previous.State, previous.Position + offset));
}
} else {
this.ViewportTouchState = this.TouchState;
@ -548,7 +559,7 @@ namespace MLEM.Input {
/// <summary>
/// Returns whether the given key is considered pressed.
/// A gamepad button is considered pressed if it was down the last update call, and is up the current update call. If <see cref="InvertPressBehavior"/> is true, this behavior is inverted.
/// This has the same behavior as <see cref="IsGamepadButtonPressed"/>, but ignores gamepad repeat events.
/// This has the same behavior as <see cref="IsGamepadButtonPressed"/>, but ignores gamepad repeat events.
/// If <see cref="HandleGamepadRepeats"/> is false, this method does the same as <see cref="IsGamepadButtonPressed"/>.
/// </summary>
/// <param name="button">The button to query</param>
@ -613,7 +624,8 @@ namespace MLEM.Input {
/// <returns>True if a gesture of the type was found, otherwise false</returns>
public bool GetViewportGesture(GestureType type, out GestureSample sample) {
if (this.GetGesture(type, out var original)) {
sample = new GestureSample(original.GestureType, original.Timestamp, original.Position + this.ViewportOffset.ToVector2(), original.Position2 + this.ViewportOffset.ToVector2(), original.Delta, original.Delta2);
var offset = new Vector2(this.ViewportOffset.X, this.ViewportOffset.Y);
sample = new GestureSample(original.GestureType, original.Timestamp, original.Position + offset, original.Position2 + offset, original.Delta, original.Delta2);
return true;
}
sample = default;

View file

@ -91,7 +91,7 @@ namespace MLEM.Input {
public int CaretPos {
get => this.caretPos;
set {
var val = MathHelper.Clamp(value, 0, this.text.Length);
var val = (int) MathHelper.Clamp(value, 0F, this.text.Length);
if (this.caretPos != val) {
this.caretPos = val;
this.caretBlinkTimer = 0;
@ -226,12 +226,14 @@ namespace MLEM.Input {
/// <summary>
/// A method that should be called when the given text should be entered into this text input.
/// This method is designed to be used with the <see cref="GameWindow.TextInput"/> event.
/// This method is designed to be used with <see cref="MlemPlatform.AddTextInputListener"/> or the TextInput event provided by MonoGame and FNA.
/// </summary>
/// <param name="key">The key that was pressed.</param>
/// <param name="character">The character that the <paramref name="key"/> represents.</param>
/// <returns>Whether text was successfully input.</returns>
public bool OnTextInput(Keys key, char character) {
// FNA's text input event doesn't supply keys, so we handle this in Update
#if !FNA
if (key == Keys.Back) {
if (this.CaretPos > 0) {
this.CaretPos--;
@ -246,6 +248,9 @@ namespace MLEM.Input {
return this.InsertText(character);
}
return false;
#else
return this.InsertText(character);
#endif
}
/// <summary>
@ -254,26 +259,37 @@ namespace MLEM.Input {
/// <param name="time">The current game time.</param>
/// <param name="input">The input handler to use for input querying.</param>
public void Update(GameTime time, InputHandler input) {
// FNA's text input event doesn't supply keys, so we handle this here
#if FNA
if (this.CaretPos > 0 && input.TryConsumePressed(Keys.Back)) {
this.CaretPos--;
this.RemoveText(this.CaretPos, 1);
} else if (this.CaretPos < this.text.Length && input.TryConsumePressed(Keys.Delete)) {
this.RemoveText(this.CaretPos, 1);
} else if (this.Multiline && input.TryConsumePressed(Keys.Enter)) {
this.InsertText('\n');
} else
#endif
if (this.CaretPos > 0 && input.TryConsumePressed(Keys.Left)) {
this.CaretPos--;
} else if (this.CaretPos < this.text.Length && input.TryConsumePressed(Keys.Right)) {
this.CaretPos++;
} else if (this.Multiline && input.IsKeyPressedAvailable(Keys.Up) && this.MoveCaretToLine(this.CaretLine - 1)) {
input.TryConsumeKeyPressed(Keys.Up);
input.TryConsumePressed(Keys.Up);
} else if (this.Multiline && input.IsKeyPressedAvailable(Keys.Down) && this.MoveCaretToLine(this.CaretLine + 1)) {
input.TryConsumeKeyPressed(Keys.Down);
} else if (this.CaretPos != 0 && input.TryConsumeKeyPressed(Keys.Home)) {
input.TryConsumePressed(Keys.Down);
} else if (this.CaretPos != 0 && input.TryConsumePressed(Keys.Home)) {
this.CaretPos = 0;
} else if (this.CaretPos != this.text.Length && input.TryConsumeKeyPressed(Keys.End)) {
} else if (this.CaretPos != this.text.Length && input.TryConsumePressed(Keys.End)) {
this.CaretPos = this.text.Length;
} else if (input.IsModifierKeyDown(ModifierKey.Control)) {
if (input.IsKeyPressedAvailable(Keys.V)) {
var clip = this.PasteFromClipboardFunction?.Invoke();
if (clip != null) {
this.InsertText(clip, true);
input.TryConsumeKeyPressed(Keys.V);
input.TryConsumePressed(Keys.V);
}
} else if (input.TryConsumeKeyPressed(Keys.C)) {
} else if (input.TryConsumePressed(Keys.C)) {
// until there is text selection, just copy the whole content
this.CopyToClipboardFunction?.Invoke(this.Text);
}

32
MLEM/MLEM.FNA.csproj Normal file
View file

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<RootNamespace>MLEM</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<Authors>Ellpeck</Authors>
<Description>MLEM Library for Extending FNA provides extension methods and additional features for FNA</Description>
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes>
<PackageTags>fna ellpeck mlem utility extensions</PackageTags>
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>Logo.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\FNA\FNA.Core.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
</ItemGroup>
</Project>

View file

@ -182,8 +182,8 @@ namespace MLEM.Misc {
/// <param name="dir">The direction whose angle to get</param>
/// <returns>The direction's angle</returns>
public static float Angle(this Direction2 dir) {
var (x, y) = dir.Offset();
return (float) Math.Atan2(y, x);
var off = dir.Offset();
return (float) Math.Atan2(off.Y, off.X);
}
/// <summary>

View file

@ -57,9 +57,9 @@ namespace MLEM.Misc {
/// <summary>
/// A delegate method that can be used for <see cref="MlemPlatform.AddTextInputListener"/>
/// </summary>
/// <param name="sender">The object that sent the event. The <see cref="MlemPlatform"/> used in most cases.</param>
/// <param name="key">The key that was pressed</param>
/// <param name="character">The character that corresponds to that key</param>
/// <param name="sender">The object that sent the event. The <see cref="GameWindow"/> or <see cref="MlemPlatform"/> used in most cases.</param>
/// <param name="key">The key that was pressed. Note that this is always <see cref="Keys.None"/> on FNA.</param>
/// <param name="character">The character that corresponds to that key.</param>
public delegate void TextInputCallback(object sender, Keys key, char character);
/// <summary>
@ -78,10 +78,10 @@ namespace MLEM.Misc {
private readonly Action<GameWindow, EventHandler<T>> addListener;
/// <summary>
/// Creates a new DesktopGL-based platform
/// Creates a new DesktopGL-based platform.
/// See <see cref="MlemPlatform.DesktopGl{T}"/> class documentation for more detailed information.
/// </summary>
/// <param name="addListener">The function that is used to add a text input listener</param>
/// <param name="addListener">The function that is used to add a text input listener.</param>
public DesktopGl(Action<GameWindow, EventHandler<T>> addListener) {
this.addListener = addListener;
}
@ -109,6 +109,44 @@ namespace MLEM.Misc {
}
/// <summary>
/// The MLEM Desktop platform for FNA.
/// This platform uses the built-in FNA TextInputEXT event, which makes this listener work with any keyboard localization natively.
/// This platform is initialized as follows:
/// <code>
/// MlemPlatform.Current = new MlemPlatform.DesktopFna(a => TextInputEXT.TextInput += a);
/// </code>
/// </summary>
public class DesktopFna : MlemPlatform {
private readonly Action<Action<char>> addListener;
/// <summary>
/// Creates a new Desktop for FNA platform.
/// See <see cref="MlemPlatform.DesktopFna"/> class documentation for more detailed information.
/// </summary>
/// <param name="addListener">The function that is used to add a text input listener.</param>
public DesktopFna(Action<Action<char>> addListener) {
this.addListener = addListener;
}
/// <inheritdoc />
public override Task<string> OpenOnScreenKeyboard(string title, string description, string defaultText, bool usePasswordMode) {
return Task.FromResult<string>(null);
}
/// <inheritdoc />
public override void AddTextInputListener(GameWindow window, TextInputCallback callback) {
this.addListener(c => callback(this, Keys.None, c));
}
/// <inheritdoc />
public override void OpenLinkOrFile(string link) {
Process.Start(new ProcessStartInfo(link) {UseShellExecute = true});
}
}
/// <summary>
/// The MLEM platform for mobile platforms as well as consoles.
/// This platform opens an on-screen keyboard using the <see cref="Microsoft.Xna.Framework.Input"/> <c>KeyboardInput</c> class on mobile devices.

View file

@ -105,12 +105,12 @@ namespace MLEM.Misc {
this.Height = size.Y;
}
/// <inheritdoc cref="Rectangle.Contains(float, float)"/>
/// <inheritdoc cref="Rectangle.Contains(int, int)"/>
public bool Contains(float x, float y) {
return this.X <= x && x < this.X + this.Width && this.Y <= y && y < this.Y + this.Height;
}
/// <inheritdoc cref="Rectangle.Contains(Vector2)"/>
/// <inheritdoc cref="Rectangle.Contains(Point)"/>
public bool Contains(Vector2 value) {
return this.Contains(value.X, value.Y);
}
@ -140,7 +140,7 @@ namespace MLEM.Misc {
return (((17 * 23 + this.X.GetHashCode()) * 23 + this.Y.GetHashCode()) * 23 + this.Width.GetHashCode()) * 23 + this.Height.GetHashCode();
}
/// <inheritdoc cref="Rectangle.Inflate(float,float)"/>
/// <inheritdoc cref="Rectangle.Inflate(int,int)"/>
public void Inflate(float horizontalAmount, float verticalAmount) {
this.X -= horizontalAmount;
this.Y -= verticalAmount;
@ -153,13 +153,13 @@ namespace MLEM.Misc {
return value.Left < this.Right && this.Left < value.Right && value.Top < this.Bottom && this.Top < value.Bottom;
}
/// <inheritdoc cref="Rectangle.Offset(float, float)"/>
/// <inheritdoc cref="Rectangle.Offset(int, int)"/>
public void Offset(float offsetX, float offsetY) {
this.X += offsetX;
this.Y += offsetY;
}
/// <inheritdoc cref="Rectangle.Offset(Vector2)"/>
/// <inheritdoc cref="Rectangle.Offset(Point)"/>
public void Offset(Vector2 amount) {
this.X += amount.X;
this.Y += amount.Y;
@ -173,7 +173,7 @@ namespace MLEM.Misc {
/// <returns>The squared distance between the two rectangles.</returns>
public float DistanceSquared(RectangleF value) {
// we calculate the distance based on the quadrants that the other rectangle is in using 8 cases:
// 1 7 4
// 1 7 4
// 3 T 6
// 2 8 5
var valueIsAbove = value.Bottom < this.Top;
@ -215,7 +215,13 @@ namespace MLEM.Misc {
return "{X:" + this.X + " Y:" + this.Y + " Width:" + this.Width + " Height:" + this.Height + "}";
}
/// <inheritdoc cref="Rectangle.Deconstruct"/>
/// <summary>
/// Deconstruction method for <see cref="RectangleF"/>.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="width"></param>
/// <param name="height"></param>
public void Deconstruct(out float x, out float y, out float width, out float height) {
x = this.X;
y = this.Y;

View file

@ -158,7 +158,8 @@ namespace MLEM.Textures {
for (var x = 0F; x < rect.Width; x += width) {
for (var y = 0F; y < rect.Height; y += height) {
var size = new Vector2(Math.Min(rect.Width - x, width), Math.Min(rect.Height - y, height));
batch.Draw(texture.Region.Texture, new RectangleF(rect.Location + new Vector2(x, y), size), new Rectangle(src.Location, (size / patchScale).CeilCopy().ToPoint()), color, rotation, origin, effects, layerDepth);
var srcSize = (size / patchScale).CeilCopy().ToPoint();
batch.Draw(texture.Region.Texture, new RectangleF(rect.Location + new Vector2(x, y), size), new Rectangle(src.X, src.Y, srcSize.X, srcSize.Y), color, rotation, origin, effects, layerDepth);
}
}
break;

View file

@ -33,7 +33,7 @@ namespace MLEM.Textures {
/// <summary>
/// The size of this texture region
/// </summary>
public Point Size => this.Area.Size;
public Point Size => new Point(this.Area.Width, this.Area.Height);
/// <summary>
/// The width of this texture region
/// </summary>
@ -51,8 +51,8 @@ namespace MLEM.Textures {
/// The <see cref="Pivot"/> of this texture region, but in absolute pixels rather than percentage.
/// </summary>
public Vector2 PivotPixels {
get => this.Pivot * this.Size.ToVector2();
set => this.Pivot = value / this.Size.ToVector2();
get => this.Pivot * new Vector2(this.Size.X, this.Size.Y);
set => this.Pivot = value / new Vector2(this.Size.X, this.Size.Y);
}
/// <summary>
/// The name of this texture region. By default, this name is <see cref="string.Empty"/>.
@ -91,14 +91,14 @@ namespace MLEM.Textures {
/// <param name="texture">The texture to use</param>
/// <param name="uv">The top left corner of this area</param>
/// <param name="size">The size of this area</param>
public TextureRegion(Texture2D texture, Point uv, Point size) : this(texture, new Rectangle(uv, size)) {}
public TextureRegion(Texture2D texture, Point uv, Point size) : this(texture, new Rectangle(uv.X, uv.Y, size.X, size.Y)) {}
/// <summary>
/// Creates a new texture region which is a sub-region of the given texture region
/// </summary>
/// <param name="region">The texture region to create a sub-region of</param>
/// <param name="area">The new texture region area</param>
public TextureRegion(TextureRegion region, Rectangle area) : this(region, area.Location, area.Size) {}
public TextureRegion(TextureRegion region, Rectangle area) : this(region, area.Location, new Point(area.Width, area.Height)) {}
/// <summary>
/// Creates a new texture region which is a sub-region of the given texture region

View file

@ -47,7 +47,7 @@ namespace MLEM.Textures {
/// Returns the <see cref="TextureRegion"/> at this texture atlas' given region position
/// </summary>
/// <param name="point">The region's x and y location</param>
public TextureRegion this[Point point] => this[new Rectangle(point, new Point(1, 1))];
public TextureRegion this[Point point] => this[new Rectangle(point.X, point.Y, 1, 1)];
/// <inheritdoc cref="this[Point]"/>
public TextureRegion this[int x, int y] => this[new Point(x, y)];
/// <summary>

View file

@ -1,6 +1,6 @@
![The MLEM logo](https://raw.githubusercontent.com/Ellpeck/MLEM/main/Media/Banner.png)
**MLEM Library for Extending MonoGame** is an addition to the game framework [MonoGame](https://www.monogame.net/) that provides extension methods, quality of life improvements and additional features like a ui system and easy input handling.
**MLEM Library for Extending MonoGame and FNA** is an addition to the game frameworks [MonoGame](https://www.monogame.net/) and [FNA](https://fna-xna.github.io/) that provides extension methods, quality of life improvements and additional features like a ui system and easy input handling.
# What next?
- Get it on [NuGet](https://www.nuget.org/packages?q=mlem)

View file

@ -18,7 +18,7 @@
<PackageReference Include="MonoGame.Extended.Content.Pipeline" Version="3.8.0" />
<PackageReference Include="MonoGame.Extended.Tiled" Version="3.8.0" />
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641" />
<PackageReference Include="FontStashSharp.MonoGame" Version="1.0.4" />
<PackageReference Include="FontStashSharp.MonoGame" Version="1.1.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>

BIN
Tests/FNA/FAudio.dll Normal file

Binary file not shown.

BIN
Tests/FNA/FNA3D.dll Normal file

Binary file not shown.

BIN
Tests/FNA/SDL2.dll Normal file

Binary file not shown.

BIN
Tests/FNA/libFAudio.0.dylib Normal file

Binary file not shown.

BIN
Tests/FNA/libFAudio.so.0 Normal file

Binary file not shown.

BIN
Tests/FNA/libFNA3D.0.dylib Normal file

Binary file not shown.

BIN
Tests/FNA/libFNA3D.so.0 Normal file

Binary file not shown.

BIN
Tests/FNA/libMoltenVK.dylib Normal file

Binary file not shown.

Binary file not shown.

BIN
Tests/FNA/libSDL2-2.0.so.0 Normal file

Binary file not shown.

BIN
Tests/FNA/libtheorafile.dll Normal file

Binary file not shown.

Binary file not shown.

BIN
Tests/FNA/libtheorafile.so Normal file

Binary file not shown.

BIN
Tests/FNA/libvulkan.1.dylib Normal file

Binary file not shown.

38
Tests/Tests.FNA.csproj Normal file
View file

@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<VSTestLogger>nunit</VSTestLogger>
<RootNamespace>Tests</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MLEM.Startup\MLEM.Startup.FNA.csproj" />
<ProjectReference Include="..\MLEM.Data\MLEM.Data.FNA.csproj" />
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.FNA.csproj" />
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FNA\FNA.Core.csproj" />
<PackageReference Include="coverlet.collector" Version="3.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
</ItemGroup>
<ItemGroup>
<Content Include="Content/**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="FNA/**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>%(Filename)%(Extension)</Link>
</Content>
</ItemGroup>
</Project>

View file

@ -13,14 +13,14 @@
<ItemGroup>
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641" />
<PackageReference Include="coverlet.collector" Version="3.1.0">
<PackageReference Include="coverlet.collector" Version="3.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
</ItemGroup>

View file

@ -9,13 +9,13 @@ var config = Argument("configuration", "Release");
Task("Prepare").Does(() => {
DotNetCoreRestore("MLEM.sln");
if (branch != "release") {
var buildNum = EnvironmentVariable("BUILD_NUMBER");
if (buildNum != null)
version += "-" + buildNum;
}
DeleteFiles("**/*.nupkg");
});
@ -27,13 +27,16 @@ Task("Build").IsDependentOn("Prepare").Does(() =>{
foreach (var project in GetFiles("**/MLEM*.csproj"))
DotNetCoreBuild(project.FullPath, settings);
DotNetCoreBuild("Demos/Demos.csproj", settings);
DotNetCoreBuild("Demos/Demos.FNA.csproj", settings);
});
Task("Test").IsDependentOn("Build").Does(() => {
DotNetCoreTest("Tests/Tests.csproj", new DotNetCoreTestSettings {
var settings = new DotNetCoreTestSettings {
Configuration = config,
Collectors = {"XPlat Code Coverage"}
});
};
DotNetCoreTest("Tests/Tests.csproj", settings);
DotNetCoreTest("Tests/Tests.FNA.csproj", settings);
});
Task("Pack").IsDependentOn("Test").Does(() => {
@ -71,4 +74,4 @@ Task("Document").Does(() => {
Task("Default").IsDependentOn("Pack");
Task("Publish").IsDependentOn("Push");
RunTarget(target);
RunTarget(target);