1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-28 07:18:35 +01:00

Compare commits

..

No commits in common. "8f2dc2e50774838d8ff3816740b54a701934927b" and "289e35fc91edf223af442322703c3e01c3dd1b1f" have entirely different histories.

99 changed files with 1140 additions and 1002 deletions

View file

@ -1,8 +0,0 @@
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'pre-push' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; }
git lfs pre-push "$@"
if ! git diff origin --name-status | grep -E -q "M\s+CHANGELOG.md"; then
echo "The changelog was not updated. Please document your changes in CHANGELOG.md before pushing."
exit 1
fi

View file

@ -1,11 +0,0 @@
on: pull_request
jobs:
enforce-changelog:
runs-on: ubuntu-latest
steps:
- uses: dangoslen/changelog-enforcer@v3
with:
changeLogPath: CHANGELOG.md
missingUpdateErrorMessage: |
The changelog was not updated. Please document your changes in CHANGELOG.md.
Run `git config core.hooksPath .githooks` to enable a git hook that ensures you updated the changelog before pushing.

View file

@ -1,51 +0,0 @@
on: [push, pull_request]
jobs:
build-publish:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
- name: Setup Java
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Restore tools
run: dotnet tool restore
- name: Run cake
uses: coactions/setup-xvfb@v1
with:
run: dotnet cake --target Publish --branch ${{ github.ref_name }}
env:
NUGET_KEY: ${{ secrets.NUGET_KEY }}
BAGET_KEY: ${{ secrets.BAGET_KEY }}
docs:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
- name: Restore tools
run: dotnet tool restore
- name: Run cake
run: dotnet cake --target Document --branch $GITHUB_REF_NAME
- name: Deploy
if: github.event_name == 'push' && github.ref_name == 'release'
# this is a beautiful way to deploy a website and i will not take any criticism
run: |
curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb && sudo dpkg -i cloudflared.deb
mkdir ~/.ssh && echo "${{ secrets.ELLBOT_KEY }}" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa
rsync -rv --delete -e 'ssh -o "ProxyCommand cloudflared access ssh --hostname %h" -o "StrictHostKeyChecking=no"' Docs/_site/. ellbot@ssh.ellpeck.de:/var/www/MLEM

4
.gitmodules vendored
View file

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

16
.woodpecker/build.yml Normal file
View file

@ -0,0 +1,16 @@
steps:
build:
image: runmymind/docker-android-sdk:ubuntu-standalone
commands:
# install xvfb to allow for graphics-dependent tests
- apt-get update && apt-get install -y --no-install-recommends xauth xvfb openjdk-11-jdk
# install dotnet
- curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --version 7.0.305
- export DOTNET_ROOT=$HOME/.dotnet
- export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools
# run cake
- dotnet tool restore
- xvfb-run -a dotnet cake --target Publish --branch $CI_COMMIT_BRANCH
secrets:
- nuget_key
- baget_key

16
.woodpecker/docs.yml Normal file
View file

@ -0,0 +1,16 @@
steps:
document:
image: mcr.microsoft.com/dotnet/sdk:7.0.305
commands:
- dotnet tool restore
- dotnet cake --target Document --branch $CI_COMMIT_BRANCH
deploy:
image: debian:latest
when:
- event: [push, manual]
branch: release
commands:
- rm -rfv /var/www/MLEM/*
- cp -rv Docs/_site/. /var/www/MLEM/
volumes:
- /var/www/MLEM:/var/www/MLEM

View file

@ -2,7 +2,6 @@
MLEM tries to adhere to [semantic versioning](https://semver.org/). Potentially breaking changes are written in **bold**. MLEM tries to adhere to [semantic versioning](https://semver.org/). Potentially breaking changes are written in **bold**.
Jump to version: Jump to version:
- [6.3.0](#630)
- [6.2.0](#620) - [6.2.0](#620)
- [6.1.0](#610) - [6.1.0](#610)
- [6.0.0](#600) - [6.0.0](#600)
@ -11,57 +10,6 @@ Jump to version:
- [5.1.0](#510) - [5.1.0](#510)
- [5.0.0](#500) - [5.0.0](#500)
## 6.3.0
### MLEM
Additions
- Added GraphicsExtensions.WithRenderTargets, a multi-target version of WithRenderTarget
- Added Zero, One, Linear and Clamp to Easings
- Added GetRandomEntry and GetRandomWeightedEntry to SingleRandom
- Added the ability to draw single corners of AutoTiling's extended auto tiles
- Added ColorHelper.TryFromHexString, a non-throwing version of FromHexString
- Added ToHexStringRgba and ToHexStringRgb to ColorExtensions
Improvements
- Stopped the text formatter throwing if a color can't be parsed
- Improved text formatter tokenization performance
- Allow using control and arrow keys to move the visible area of a text input
- Allow formatting codes applied later to override settings of earlier ones
Fixes
- Fixed TextInput not working correctly when using surrogate pairs
- Fixed InputHandler touch states being initialized incorrectly when touch handling is disabled
- Fixed empty NinePatch regions stalling when using tile mode
- Fixed bold and italic formatting code closing tags working on each other
### MLEM.Ui
Additions
- Added UiControls.NavType, which stores the most recently used type of ui navigation
- Added SetWidthBasedOnAspect and SetHeightBasedOnAspect to images
- Added the ability to set a custom SamplerState for images
- Added some useful additional constructors to various elements
Improvements
- Allow scrolling panels to contain other scrolling panels
- Allow dropdowns to have scrolling panels
- Improved Panel performance when adding and removing a lot of children
- Don't reset the caret position of a text field when selecting or deselecting it
- Improved UiParser.ParseImage with locks and a callback action
Fixes
- Fixed panels updating their relevant children too much when the scroll bar is hidden
- Fixed a stack overflow exception when a panel's scroll bar auto-hiding causes elements to gain height
- Fixed scrolling panels calculating their height incorrectly when their first child is hidden
### MLEM.Extended
Improvements
- Updated to FontStashSharp 1.3.0's API
- Expose character and line spacing in GenericStashFont
### MLEM.Data
Fixes
- Fixed various exception types not being wrapped by ContentLoadExceptions when loading raw or JSON content
## 6.2.0 ## 6.2.0
### MLEM ### MLEM

View file

@ -48,10 +48,10 @@ public class Activity1 : AndroidGameActivity {
base.OnWindowFocusChanged(hasFocus); base.OnWindowFocusChanged(hasFocus);
// hide the status bar // hide the status bar
if (hasFocus) { if (hasFocus) {
#pragma warning disable CS0618 #pragma warning disable CA1422
// TODO this is deprecated, find out how to replace it // TODO this is deprecated, find out how to replace it
this.Window.DecorView.SystemUiVisibility = (StatusBarVisibility) (SystemUiFlags.ImmersiveSticky | SystemUiFlags.LayoutStable | SystemUiFlags.LayoutHideNavigation | SystemUiFlags.LayoutFullscreen | SystemUiFlags.HideNavigation | SystemUiFlags.Fullscreen); this.Window.DecorView.SystemUiVisibility = (StatusBarVisibility) (SystemUiFlags.ImmersiveSticky | SystemUiFlags.LayoutStable | SystemUiFlags.LayoutHideNavigation | SystemUiFlags.LayoutFullscreen | SystemUiFlags.HideNavigation | SystemUiFlags.Fullscreen);
#pragma warning restore CS0618 #pragma warning restore CA1422
} }
} }

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0-android</TargetFramework> <TargetFramework>net7.0-android</TargetFramework>
<SupportedOSPlatformVersion>31</SupportedOSPlatformVersion> <SupportedOSPlatformVersion>31</SupportedOSPlatformVersion>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<ApplicationId>de.ellpeck.mlem.demos.android</ApplicationId> <ApplicationId>de.ellpeck.mlem.demos.android</ApplicationId>

View file

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<ApplicationIcon>Icon.ico</ApplicationIcon> <ApplicationIcon>Icon.ico</ApplicationIcon>
<AssemblyName>MLEM Desktop Demos</AssemblyName> <AssemblyName>MLEM Desktop Demos</AssemblyName>
<RootNamespace>Demos.DesktopGL</RootNamespace> <RootNamespace>Demos.DesktopGL</RootNamespace>
@ -21,7 +21,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.1.303" /> <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.1.303" />
<ProjectReference Include="..\ThirdParty\FNA\FNA.Core.csproj" /> <ProjectReference Include="..\FNA\FNA.Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -29,7 +29,7 @@
<Content Include="..\Demos\Content\*\**" /> <Content Include="..\Demos\Content\*\**" />
<EmbeddedResource Include="Icon.ico" /> <EmbeddedResource Include="Icon.ico" />
<EmbeddedResource Include="Icon.bmp" /> <EmbeddedResource Include="Icon.bmp" />
<Content Include="../ThirdParty/Native/**"> <Content Include="../FnaNative/**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>%(Filename)%(Extension)</Link> <Link>%(Filename)%(Extension)</Link>
</Content> </Content>

View file

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<ApplicationIcon>Icon.ico</ApplicationIcon> <ApplicationIcon>Icon.ico</ApplicationIcon>
<AssemblyName>MLEM Desktop Demos</AssemblyName> <AssemblyName>MLEM Desktop Demos</AssemblyName>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
@ -26,4 +26,9 @@
<EmbeddedResource Include="Icon.ico" /> <EmbeddedResource Include="Icon.ico" />
<EmbeddedResource Include="Icon.bmp" /> <EmbeddedResource Include="Icon.bmp" />
</ItemGroup> </ItemGroup>
<Target Name="RestoreDotnetTools" BeforeTargets="Restore">
<Message Text="Restoring dotnet tools" Importance="High" />
<Exec Command="dotnet tool restore" />
</Target>
</Project> </Project>

View file

@ -12,12 +12,8 @@ Strikethrough with ~~two tildes~~.
[I'm an inline-style link](https://www.google.com) [I'm an inline-style link](https://www.google.com)
<http://www.example.com> <http://www.example.com>
Logo:
![](https://raw.githubusercontent.com/Ellpeck/MLEM/main/Media/Logo.png) ![](https://raw.githubusercontent.com/Ellpeck/MLEM/main/Media/Logo.png)
Wide logo:
![](https://raw.githubusercontent.com/Ellpeck/MLEM/main/Media/Banner.png)
Some `inline code` right here Some `inline code` right here
```js ```js

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Demos</RootNamespace> <RootNamespace>Demos</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants> <DefineConstants>$(DefineConstants);FNA</DefineConstants>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
@ -14,7 +14,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\ThirdParty\FNA\FNA.Core.csproj"> <ProjectReference Include="..\FNA\FNA.Core.csproj">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>

View file

@ -15,7 +15,7 @@ namespace Demos {
private const string Text = private const string Text =
"MLEM's text formatting system allows for various <b>formatting codes</b> to be applied in the middle of a string. Here's a demonstration of some of them.\n\n" + "MLEM's text formatting system allows for various <b>formatting codes</b> to be applied in the middle of a string. Here's a demonstration of some of them.\n\n" +
"You can write in <b>bold</b>, <i>italics</i>, <u>with an underline</u>, <st>strikethrough</st>, with a <s>drop shadow</s> whose <s #ff0000 4>color</s> and <s #000000 10>offset</s> you can modify in each application of the code, with an <o>outline</o> that you can also <o #ff0000 4>modify</o> <o #ff00ff 2>dynamically</o>, or with various types of <b>combined <c Pink>formatting</c> codes</b>.\n\n" + "You can write in <b>bold</i>, <i>italics</i>, <u>with an underline</u>, <st>strikethrough</st>, with a <s>drop shadow</s> whose <s #ff0000 4>color</s> and <s #000000 10>offset</s> you can modify in each application of the code, with an <o>outline</o> that you can also <o #ff0000 4>modify</o> <o #ff00ff 2>dynamically</o>, or with various types of <b>combined <c Pink>formatting</c> codes</b>.\n\n" +
"You can apply <c CornflowerBlue>custom</c> <c Yellow>colors</c> to text, including all default <c Orange>MonoGame colors</c> and <c #aabb00>inline custom colors</c>.\n\n" + "You can apply <c CornflowerBlue>custom</c> <c Yellow>colors</c> to text, including all default <c Orange>MonoGame colors</c> and <c #aabb00>inline custom colors</c>.\n\n" +
"You can also use animations like <a wobbly>a wobbly one</a>, as well as create custom ones using the <a wobbly>Code class</a>.\n\n" + "You can also use animations like <a wobbly>a wobbly one</a>, as well as create custom ones using the <a wobbly>Code class</a>.\n\n" +
"You can also display <i grass> icons in your text, and use super<sup>script</sup> or sub<sub>script</sub> formatting!\n\n" + "You can also display <i grass> icons in your text, and use super<sup>script</sup> or sub<sub>script</sub> formatting!\n\n" +

View file

@ -223,15 +223,6 @@ namespace Demos {
PositionOffset = new Vector2(0, 1) PositionOffset = new Vector2(0, 1)
}); });
var subPanel = this.root.AddChild(new Panel(Anchor.AutoLeft, new Vector2(1, 25), Vector2.Zero, false, true) {
PositionOffset = new Vector2(0, 1),
Texture = null,
ChildPadding = Padding.Empty
});
subPanel.AddChild(new Paragraph(Anchor.AutoLeft, 1, "This is a nested scrolling panel!"));
for (var i = 1; i <= 5; i++)
subPanel.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 10), $"Button {i}") {PositionOffset = new Vector2(0, 1)});
const string alignText = "Paragraphs can have <l Left>left</l> aligned text, <l Right>right</l> aligned text and <l Center>center</l> aligned text."; const string alignText = "Paragraphs can have <l Left>left</l> aligned text, <l Right>right</l> aligned text and <l Center>center</l> aligned text.";
this.root.AddChild(new VerticalSpace(3)); this.root.AddChild(new VerticalSpace(3));
var alignPar = this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, alignText)); var alignPar = this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, alignText));

View file

@ -49,7 +49,7 @@
"globalMetadata": { "globalMetadata": {
"_appTitle": "MLEM Documentation", "_appTitle": "MLEM Documentation",
"_appLogoPath": "Logo.svg", "_appLogoPath": "Logo.svg",
"_appFooter": "<a href=\"https://github.com/Ellpeck/MLEM\">&copy; 2019-2024 Ellpeck</a> &ndash; <a href=\"https://ellpeck.de/impressum\">Impressum</a> &ndash; <a href=\"https://ellpeck.de/privacy\">Privacy</a> &ndash; <a href=\"https://status.ellpeck.de\">Status</a>", "_appFooter": "<a href=\"https://github.com/Ellpeck/MLEM\">&copy; 2019-2023 Ellpeck</a> &ndash; <a href=\"https://ellpeck.de/impressum\">Impressum</a> &ndash; <a href=\"https://ellpeck.de/privacy\">Privacy</a> &ndash; <a href=\"https://status.ellpeck.de\">Status</a>",
"_enableSearch": true "_enableSearch": true
}, },
"dest": "_site", "dest": "_site",

1
FNA Submodule

@ -0,0 +1 @@
Subproject commit 697cc63662914c0dc26c500bc9b8498b5ca8a68f

BIN
FnaNative/FAudio.dll Normal file

Binary file not shown.

BIN
FnaNative/FNA3D.dll Normal file

Binary file not shown.

BIN
FnaNative/libFAudio.0.dylib Normal file

Binary file not shown.

BIN
FnaNative/libFAudio.so.0 Normal file

Binary file not shown.

BIN
FnaNative/libFNA3D.0.dylib Normal file

Binary file not shown.

BIN
FnaNative/libFNA3D.so.0 Normal file

Binary file not shown.

Binary file not shown.

BIN
FnaNative/libSDL2-2.0.so.0 Normal file

Binary file not shown.

1
FontStashSharp Submodule

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

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2019-2024 Ellpeck Copyright (c) 2019-2023 Ellpeck
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -100,7 +100,7 @@ namespace MLEM.Data.Content {
r.Name = assetName; r.Name = assetName;
return t; return t;
} }
} catch (IOException) {} } catch (FileNotFoundException) {}
} }
} }
throw new ContentLoadException($"Asset {assetName} not found. Tried files {string.Join(", ", triedFiles)}"); throw new ContentLoadException($"Asset {assetName} not found. Tried files {string.Join(", ", triedFiles)}");

View file

@ -68,7 +68,7 @@ namespace MLEM.Data {
using (var reader = new JsonTextReader(stream)) using (var reader = new JsonTextReader(stream))
return serializerToUse.Deserialize<T>(reader); return serializerToUse.Deserialize<T>(reader);
} }
} catch (IOException) {} } catch (FileNotFoundException) {}
} }
throw new ContentLoadException($"Asset {name} not found. Tried files {string.Join(", ", triedFiles)}"); throw new ContentLoadException($"Asset {name} not found. Tried files {string.Join(", ", triedFiles)}");
} }

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible> <IsTrimmable>true</IsTrimmable>
<RootNamespace>MLEM.Data</RootNamespace> <RootNamespace>MLEM.Data</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants> <DefineConstants>$(DefineConstants);FNA</DefineConstants>
<NoWarn>NU1701</NoWarn> <NoWarn>NU1701</NoWarn>
@ -28,14 +28,14 @@
<PackageReference Include="Lidgren.Network" Version="1.0.2"> <PackageReference Include="Lidgren.Network" Version="1.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"> <PackageReference Include="Newtonsoft.Json" Version="13.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<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="..\ThirdParty\FNA\FNA.csproj"> <ProjectReference Include="..\FNA\FNA.csproj">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible> <IsTrimmable>true</IsTrimmable>
<NoWarn>NU1701</NoWarn> <NoWarn>NU1701</NoWarn>
</PropertyGroup> </PropertyGroup>

View file

@ -11,7 +11,6 @@ namespace MLEM.Extended.Font {
/// The <see cref="SpriteFontBase"/> that is being wrapped by this generic font /// The <see cref="SpriteFontBase"/> that is being wrapped by this generic font
/// </summary> /// </summary>
public readonly SpriteFontBase Font; public readonly SpriteFontBase Font;
/// <inheritdoc /> /// <inheritdoc />
public override GenericFont Bold { get; } public override GenericFont Bold { get; }
/// <inheritdoc /> /// <inheritdoc />
@ -19,15 +18,6 @@ namespace MLEM.Extended.Font {
/// <inheritdoc /> /// <inheritdoc />
public override float LineHeight => this.Font.LineHeight; public override float LineHeight => this.Font.LineHeight;
/// <summary>
/// The character spacing that will be passed to the underlying <see cref="Font"/>.
/// </summary>
public float CharacterSpacing { get; set; }
/// <summary>
/// The line spacing that will be passed to the underlying <see cref="Font"/>.
/// </summary>
public float LineSpacing { get; set; }
/// <summary> /// <summary>
/// Creates a new generic font using <see cref="SpriteFontBase"/>. /// Creates a new generic font using <see cref="SpriteFontBase"/>.
/// Optionally, a bold and italic version of the font can be supplied. /// Optionally, a bold and italic version of the font can be supplied.
@ -43,12 +33,12 @@ namespace MLEM.Extended.Font {
/// <inheritdoc /> /// <inheritdoc />
protected override float MeasureCharacter(int codePoint) { protected override float MeasureCharacter(int codePoint) {
return this.Font.MeasureString(CodePointSource.ToString(codePoint), null, this.CharacterSpacing, this.LineSpacing).X; return this.Font.MeasureString(CodePointSource.ToString(codePoint)).X;
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void DrawCharacter(SpriteBatch batch, int codePoint, string character, Vector2 position, Color color, float rotation, Vector2 scale, SpriteEffects effects, float layerDepth) { protected override void DrawCharacter(SpriteBatch batch, int codePoint, string character, Vector2 position, Color color, float rotation, Vector2 scale, SpriteEffects effects, float layerDepth) {
this.Font.DrawText(batch, character, position, color, rotation, Vector2.Zero, scale, layerDepth, this.CharacterSpacing, this.LineSpacing); this.Font.DrawText(batch, character, position, color, scale, rotation, Vector2.Zero, layerDepth);
} }
} }

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;net7.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible> <IsTrimmable>true</IsTrimmable>
<RootNamespace>MLEM.Extended</RootNamespace> <RootNamespace>MLEM.Extended</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants> <DefineConstants>$(DefineConstants);FNA</DefineConstants>
<NoWarn>NU1702</NoWarn> <NoWarn>NU1702</NoWarn>
@ -24,10 +24,10 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" /> <ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
<ProjectReference Include="..\ThirdParty\FontStashSharp\src\XNA\FontStashSharp.FNA.csproj"> <ProjectReference Include="..\FontStashSharp\src\XNA\FontStashSharp.FNA.csproj">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\ThirdParty\FNA\FNA.csproj"> <ProjectReference Include="..\FNA\FNA.csproj">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</ProjectReference> </ProjectReference>

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;net7.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible> <IsTrimmable>true</IsTrimmable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
@ -27,7 +27,7 @@
<PackageReference Include="MonoGame.Extended.Tiled" Version="3.8.0"> <PackageReference Include="MonoGame.Extended.Tiled" Version="3.8.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="FontStashSharp.MonoGame" Version="1.3.3"> <PackageReference Include="FontStashSharp.MonoGame" Version="1.2.8">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641"> <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">

View file

@ -44,8 +44,7 @@ namespace MLEM.Extended.Tiled {
/// <param name="key">The key by which to get a property</param> /// <param name="key">The key by which to get a property</param>
/// <returns>The color property</returns> /// <returns>The color property</returns>
public static Color GetColor(this TiledMapProperties properties, string key) { public static Color GetColor(this TiledMapProperties properties, string key) {
ColorHelper.TryFromHexString(properties.Get(key), out var val); return ColorHelper.FromHexString(properties.Get(key));
return val;
} }
/// <summary> /// <summary>

View file

@ -16,11 +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", "ThirdParty\FNA\FNA.csproj", "{35253CE1-C864-4CD3-8249-4D1319748E8F}" 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", "ThirdParty\FontStashSharp\src\XNA\FontStashSharp.FNA.csproj", "{39249E92-EBF2-4951-A086-AB4951C3CCE1}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FontStashSharp.FNA", "FontStashSharp\src\XNA\FontStashSharp.FNA.csproj", "{39249E92-EBF2-4951-A086-AB4951C3CCE1}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA.Core", "ThirdParty\FNA\FNA.Core.csproj", "{458FFA5E-A1C4-4B23-A5D8-259385FEECED}" 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

View file

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible> <IsTrimmable>true</IsTrimmable>
<RootNamespace>MLEM.Startup</RootNamespace> <RootNamespace>MLEM.Startup</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants> <DefineConstants>$(DefineConstants);FNA</DefineConstants>
</PropertyGroup> </PropertyGroup>
@ -22,11 +22,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Coroutine" Version="2.1.5" /> <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="..\ThirdParty\FNA\FNA.csproj"> <ProjectReference Include="..\FNA\FNA.csproj">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>

View file

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible> <IsTrimmable>true</IsTrimmable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View file

@ -1,11 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<IncludeContentInPack>true</IncludeContentInPack> <IncludeContentInPack>true</IncludeContentInPack>
<IncludeBuildOutput>false</IncludeBuildOutput> <IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>content</ContentTargetFolders> <ContentTargetFolders>content</ContentTargetFolders>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<IsTrimmable>true</IsTrimmable>
<NoWarn>NU5128</NoWarn> <NoWarn>NU5128</NoWarn>
</PropertyGroup> </PropertyGroup>

View file

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<PublishReadyToRun>false</PublishReadyToRun> <PublishReadyToRun>false</PublishReadyToRun>
<TieredCompilation>false</TieredCompilation> <TieredCompilation>false</TieredCompilation>
<ApplicationIcon>Icon.ico</ApplicationIcon> <ApplicationIcon>Icon.ico</ApplicationIcon>
@ -19,4 +19,9 @@
<EmbeddedResource Include="Icon.ico" /> <EmbeddedResource Include="Icon.ico" />
<EmbeddedResource Include="Icon.bmp" /> <EmbeddedResource Include="Icon.bmp" />
</ItemGroup> </ItemGroup>
<Target Name="RestoreDotnetTools" BeforeTargets="Restore">
<Message Text="Restoring dotnet tools" Importance="High" />
<Exec Command="dotnet tool restore" />
</Target>
</Project> </Project>

View file

@ -87,13 +87,6 @@ namespace MLEM.Ui.Elements {
private bool isDisabled; private bool isDisabled;
/// <summary>
/// Creates a new button with the given settings and no text or tooltip.
/// </summary>
/// <param name="anchor">The button's anchor</param>
/// <param name="size">The button's size</param>
public Button(Anchor anchor, Vector2 size) : base(anchor, size) {}
/// <summary> /// <summary>
/// Creates a new button with the given settings /// Creates a new button with the given settings
/// </summary> /// </summary>
@ -101,7 +94,7 @@ namespace MLEM.Ui.Elements {
/// <param name="size">The button's size</param> /// <param name="size">The button's size</param>
/// <param name="text">The text that should be displayed on the button</param> /// <param name="text">The text that should be displayed on the button</param>
/// <param name="tooltipText">The text that should be displayed in a <see cref="Tooltip"/> when hovering over this button</param> /// <param name="tooltipText">The text that should be displayed in a <see cref="Tooltip"/> when hovering over this button</param>
public Button(Anchor anchor, Vector2 size, string text = null, string tooltipText = null) : this(anchor, size) { public Button(Anchor anchor, Vector2 size, string text = null, string tooltipText = null) : base(anchor, size) {
if (text != null) { if (text != null) {
this.Text = new Paragraph(Anchor.Center, 1, text, true); this.Text = new Paragraph(Anchor.Center, 1, text, true);
this.Text.Padding = this.Text.Padding.OrStyle(new Padding(1), 1); this.Text.Padding = this.Text.Padding.OrStyle(new Padding(1), 1);
@ -111,23 +104,6 @@ namespace MLEM.Ui.Elements {
this.Tooltip = this.AddTooltip(tooltipText); this.Tooltip = this.AddTooltip(tooltipText);
} }
/// <summary>
/// Creates a new button with the given settings
/// </summary>
/// <param name="anchor">The button's anchor</param>
/// <param name="size">The button's size</param>
/// <param name="textCallback">The text that should be displayed on the button</param>
/// <param name="tooltipTextCallback">The text that should be displayed in a <see cref="Tooltip"/> when hovering over this button</param>
public Button(Anchor anchor, Vector2 size, Paragraph.TextCallback textCallback = null, Paragraph.TextCallback tooltipTextCallback = null) : this(anchor, size) {
if (textCallback != null) {
this.Text = new Paragraph(Anchor.Center, 1, textCallback, true);
this.Text.Padding = this.Text.Padding.OrStyle(new Padding(1), 1);
this.AddChild(this.Text);
}
if (tooltipTextCallback != null)
this.Tooltip = this.AddTooltip(tooltipTextCallback);
}
/// <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) {
var tex = this.Texture; var tex = this.Texture;

View file

@ -12,7 +12,8 @@ namespace MLEM.Ui.Elements {
/// <summary> /// <summary>
/// The panel that this dropdown contains. It will be displayed upon pressing the dropdown button. /// The panel that this dropdown contains. It will be displayed upon pressing the dropdown button.
/// </summary> /// </summary>
public Panel Panel { get; private set; } public readonly Panel Panel;
/// <summary> /// <summary>
/// This property stores whether the dropdown is currently opened or not /// This property stores whether the dropdown is currently opened or not
/// </summary> /// </summary>
@ -28,18 +29,6 @@ namespace MLEM.Ui.Elements {
/// </summary> /// </summary>
public GenericCallback OnOpenedOrClosed; public GenericCallback OnOpenedOrClosed;
/// <summary>
/// Creates a new dropdown with the given settings and no text or tooltip.
/// </summary>
/// <param name="anchor">The dropdown's anchor</param>
/// <param name="size">The dropdown button's size</param>
/// <param name="panelHeight">The height of the <see cref="Panel"/>. If this is 0, the panel will be set to <see cref="Element.SetHeightBasedOnChildren"/>.</param>
/// <param name="scrollPanel">Whether this dropdown's <see cref="Panel"/> should automatically add a scroll bar to scroll towards elements that are beyond the area it covers.</param>
/// <param name="autoHidePanelScrollbar">Whether this dropdown's <see cref="Panel"/>'s scroll bar should be hidden automatically if the panel does not contain enough children to allow for scrolling. This only has an effect if <paramref name="scrollPanel"/> is <see langword="true"/>.</param>
public Dropdown(Anchor anchor, Vector2 size, float panelHeight = 0, bool scrollPanel = false, bool autoHidePanelScrollbar = true) : base(anchor, size) {
this.Initialize(panelHeight, scrollPanel, autoHidePanelScrollbar);
}
/// <summary> /// <summary>
/// Creates a new dropdown with the given settings /// Creates a new dropdown with the given settings
/// </summary> /// </summary>
@ -47,25 +36,31 @@ namespace MLEM.Ui.Elements {
/// <param name="size">The dropdown button's size</param> /// <param name="size">The dropdown button's size</param>
/// <param name="text">The text displayed on the dropdown button</param> /// <param name="text">The text displayed on the dropdown button</param>
/// <param name="tooltipText">The text displayed as a tooltip when hovering over the dropdown button</param> /// <param name="tooltipText">The text displayed as a tooltip when hovering over the dropdown button</param>
/// <param name="panelHeight">The height of the <see cref="Panel"/>. If this is 0, the panel will be set to <see cref="Element.SetHeightBasedOnChildren"/>.</param> public Dropdown(Anchor anchor, Vector2 size, string text = null, string tooltipText = null) : base(anchor, size, text, tooltipText) {
/// <param name="scrollPanel">Whether this dropdown's <see cref="Panel"/> should automatically add a scroll bar to scroll towards elements that are beyond the area it covers.</param> this.Panel = this.AddChild(new Panel(Anchor.TopCenter, Vector2.Zero, Vector2.Zero, true) {
/// <param name="autoHidePanelScrollbar">Whether this dropdown's <see cref="Panel"/>'s scroll bar should be hidden automatically if the panel does not contain enough children to allow for scrolling. This only has an effect if <paramref name="scrollPanel"/> is <see langword="true"/>.</param> IsHidden = true
public Dropdown(Anchor anchor, Vector2 size, string text = null, string tooltipText = null, float panelHeight = 0, bool scrollPanel = false, bool autoHidePanelScrollbar = true) : base(anchor, size, text, tooltipText) { });
this.Initialize(panelHeight, scrollPanel, autoHidePanelScrollbar); this.OnAreaUpdated += e => {
} this.Panel.Size = new Vector2(e.Area.Width / e.Scale, 0);
this.Panel.PositionOffset = new Vector2(0, e.Area.Height / e.Scale);
/// <summary> };
/// Creates a new dropdown with the given settings this.OnOpenedOrClosed += e => this.Priority = this.IsOpen ? 10000 : 0;
/// </summary> this.OnPressed += e => {
/// <param name="anchor">The dropdown's anchor</param> this.IsOpen = !this.IsOpen;
/// <param name="size">The dropdown button's size</param> // close other dropdowns in the same root when we open
/// <param name="textCallback">The text displayed on the dropdown button</param> if (this.IsOpen) {
/// <param name="tooltipTextCallback">The text displayed as a tooltip when hovering over the dropdown button</param> this.Root.Element.AndChildren(o => {
/// <param name="panelHeight">The height of the <see cref="Panel"/>. If this is 0, the panel will be set to <see cref="Element.SetHeightBasedOnChildren"/>.</param> if (o != this && o is Dropdown d && d.IsOpen)
/// <param name="scrollPanel">Whether this dropdown's <see cref="Panel"/> should automatically add a scroll bar to scroll towards elements that are beyond the area it covers.</param> d.IsOpen = false;
/// <param name="autoHidePanelScrollbar">Whether this dropdown's <see cref="Panel"/>'s scroll bar should be hidden automatically if the panel does not contain enough children to allow for scrolling. This only has an effect if <paramref name="scrollPanel"/> is <see langword="true"/>.</param> });
public Dropdown(Anchor anchor, Vector2 size, Paragraph.TextCallback textCallback = null, Paragraph.TextCallback tooltipTextCallback = null, float panelHeight = 0, bool scrollPanel = false, bool autoHidePanelScrollbar = true) : base(anchor, size, textCallback, tooltipTextCallback) { }
this.Initialize(panelHeight, scrollPanel, autoHidePanelScrollbar); };
this.GetGamepadNextElement = (dir, usualNext) => {
// Force navigate down to our first child if we're open
if (this.IsOpen && dir == Direction2.Down)
return this.Panel.GetChildren().FirstOrDefault(c => c.CanBeSelected) ?? usualNext;
return usualNext;
};
} }
/// <summary> /// <summary>
@ -121,32 +116,5 @@ namespace MLEM.Ui.Elements {
return paragraph; return paragraph;
} }
private void Initialize(float panelHeight, bool scrollPanel, bool autoHidePanelScrollbar) {
this.Panel = this.AddChild(new Panel(Anchor.TopCenter, Vector2.Zero, panelHeight == 0, scrollPanel, autoHidePanelScrollbar) {
IsHidden = true
});
this.OnAreaUpdated += e => {
this.Panel.Size = new Vector2(e.Area.Width / e.Scale, panelHeight);
this.Panel.PositionOffset = new Vector2(0, e.Area.Height / e.Scale);
};
this.OnOpenedOrClosed += e => this.Priority = this.IsOpen ? 10000 : 0;
this.OnPressed += e => {
this.IsOpen = !this.IsOpen;
// close other dropdowns in the same root when we open
if (this.IsOpen) {
this.Root.Element.AndChildren(o => {
if (o != this && o is Dropdown d && d.IsOpen)
d.IsOpen = false;
});
}
};
this.GetGamepadNextElement = (dir, usualNext) => {
// Force navigate down to our first child if we're open
if (this.IsOpen && dir == Direction2.Down)
return this.Panel.GetChildren().FirstOrDefault(c => c.CanBeSelected) ?? usualNext;
return usualNext;
};
}
} }
} }

View file

@ -1236,13 +1236,12 @@ namespace MLEM.Ui.Elements {
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() { public override string ToString() {
var ret = this.GetType().Name; var ret = this.GetType().ToString();
// elements will contain their path up to the root and their index in each parent // elements will contain their path up to the root (Paragraph@Panel@...@RootName)
// eg Paragraph 2 @ Panel 3 @ ... @ Group RootName
if (this.Parent != null) { if (this.Parent != null) {
ret += $" {this.Parent.Children.IndexOf(this)} @ {this.Parent}"; ret += $"@{this.Parent}";
} else if (this.Root?.Element == this) { } else if (this.Root?.Element == this) {
ret += $" {this.Root.Name}"; ret += $"@{this.Root.Name}";
} }
return ret; return ret;
} }

View file

@ -14,18 +14,8 @@ namespace MLEM.Ui.Elements {
/// </summary> /// </summary>
/// <param name="anchor">The group's anchor</param> /// <param name="anchor">The group's anchor</param>
/// <param name="size">The group's size</param> /// <param name="size">The group's size</param>
/// <param name="setHeightBasedOnChildren">Whether the group's height should be based on its children's height, see <see cref="Element.SetHeightBasedOnChildren"/>.</param> /// <param name="setHeightBasedOnChildren">Whether the group's height should be based on its children's height</param>
public Group(Anchor anchor, Vector2 size, bool setHeightBasedOnChildren = true) : this(anchor, size, false, setHeightBasedOnChildren) {} public Group(Anchor anchor, Vector2 size, bool setHeightBasedOnChildren = true) : base(anchor, size) {
/// <summary>
/// Creates a new group with the given settings
/// </summary>
/// <param name="anchor">The group's anchor</param>
/// <param name="size">The group's size</param>
/// <param name="setWidthBasedOnChildren">Whether the group's width should be based on its children's width, see <see cref="Element.SetWidthBasedOnChildren"/>.</param>
/// <param name="setHeightBasedOnChildren">Whether the group's height should be based on its children's height, see <see cref="Element.SetHeightBasedOnChildren"/>.</param>
public Group(Anchor anchor, Vector2 size, bool setWidthBasedOnChildren, bool setHeightBasedOnChildren) : base(anchor, size) {
this.SetWidthBasedOnChildren = setWidthBasedOnChildren;
this.SetHeightBasedOnChildren = setHeightBasedOnChildren; this.SetHeightBasedOnChildren = setHeightBasedOnChildren;
this.CanBeSelected = false; this.CanBeSelected = false;
} }

View file

@ -69,45 +69,11 @@ namespace MLEM.Ui.Elements {
/// Note that increased rotation does not increase this component's size, even if the rotated texture would go out of bounds of this component. /// Note that increased rotation does not increase this component's size, even if the rotated texture would go out of bounds of this component.
/// </summary> /// </summary>
public float ImageRotation; public float ImageRotation;
/// <summary>
/// Whether this image's width should automatically be calculated based on this image's calculated height in relation to its <see cref="Texture"/>'s aspect ratio.
/// Note that, if this is <see langword="true"/>, the <see cref="Element.AutoSizeAddedAbsolute"/> value will still be applied to this image's width.
/// </summary>
public bool SetWidthBasedOnAspect {
get => this.setWidthBasedOnAspect;
set {
if (this.setWidthBasedOnAspect != value) {
this.setWidthBasedOnAspect = value;
this.SetAreaDirty();
}
}
}
/// <summary>
/// Whether this image's height should automatically be calculated based on this image's calculated width in relation to its <see cref="Texture"/>'s aspect ratio.
/// This behavior is useful if an image should take up a certain width, but the aspect ratio of its texture can vary and the image should not take up more height than is necessary.
/// Note that, if this is <see langword="true"/>, the <see cref="Element.AutoSizeAddedAbsolute"/> value will still be applied to this image's height.
/// </summary>
public bool SetHeightBasedOnAspect {
get => this.setHeightBasedOnAspect;
set {
if (this.setHeightBasedOnAspect != value) {
this.setHeightBasedOnAspect = value;
this.SetAreaDirty();
}
}
}
/// <summary>
/// The sampler state that this image's <see cref="Texture"/> should be drawn with.
/// If this is <see langword="null"/>, the current <see cref="SpriteBatchContext"/>'s <see cref="SpriteBatchContext.SamplerState"/> will be used, which will likely be the same as <see cref="UiSystem.SpriteBatchContext"/>.
/// </summary>
public SamplerState SamplerState;
/// <inheritdoc /> /// <inheritdoc />
public override bool IsHidden => base.IsHidden || this.Texture == null; public override bool IsHidden => base.IsHidden || this.Texture == null;
private bool scaleToImage; private bool scaleToImage;
private bool setWidthBasedOnAspect;
private bool setHeightBasedOnAspect;
private TextureRegion explicitlySetTexture; private TextureRegion explicitlySetTexture;
private TextureRegion displayedTexture; private TextureRegion displayedTexture;
@ -120,7 +86,7 @@ namespace MLEM.Ui.Elements {
/// <param name="scaleToImage">Whether this image's size should be based on the texture's size</param> /// <param name="scaleToImage">Whether this image's size should be based on the texture's size</param>
public Image(Anchor anchor, Vector2 size, TextureRegion texture, bool scaleToImage = false) : base(anchor, size) { public Image(Anchor anchor, Vector2 size, TextureRegion texture, bool scaleToImage = false) : base(anchor, size) {
this.Texture = texture; this.Texture = texture;
this.ScaleToImage = scaleToImage; this.scaleToImage = scaleToImage;
this.CanBeSelected = false; this.CanBeSelected = false;
this.CanBeMoused = false; this.CanBeMoused = false;
} }
@ -129,29 +95,14 @@ namespace MLEM.Ui.Elements {
public Image(Anchor anchor, Vector2 size, TextureCallback getTextureCallback, bool scaleToImage = false) : base(anchor, size) { public Image(Anchor anchor, Vector2 size, TextureCallback getTextureCallback, bool scaleToImage = false) : base(anchor, size) {
this.GetTextureCallback = getTextureCallback; this.GetTextureCallback = getTextureCallback;
this.Texture = getTextureCallback(this); this.Texture = getTextureCallback(this);
this.ScaleToImage = scaleToImage; this.scaleToImage = scaleToImage;
this.CanBeSelected = false; this.CanBeSelected = false;
this.CanBeMoused = false; this.CanBeMoused = false;
} }
/// <inheritdoc /> /// <inheritdoc />
protected override Vector2 CalcActualSize(RectangleF parentArea) { protected override Vector2 CalcActualSize(RectangleF parentArea) {
var ret = base.CalcActualSize(parentArea); return this.Texture != null && this.scaleToImage ? this.Texture.Size.ToVector2() * this.Scale : base.CalcActualSize(parentArea);
if (this.Texture != null) {
if (this.ScaleToImage)
ret = this.Texture.Size.ToVector2() * this.Scale;
if (this.SetWidthBasedOnAspect)
ret.X = ret.Y * this.Texture.Width / this.Texture.Height + this.ScaledAutoSizeAddedAbsolute.X;
if (this.SetHeightBasedOnAspect)
ret.Y = ret.X * this.Texture.Height / this.Texture.Width + this.ScaledAutoSizeAddedAbsolute.Y;
} else {
// if we don't have a texture and we auto-set width or height, calculate as if we had a texture with a size of 0
if (this.SetWidthBasedOnAspect)
ret.X = this.ScaledAutoSizeAddedAbsolute.X;
if (this.SetHeightBasedOnAspect)
ret.Y = this.ScaledAutoSizeAddedAbsolute.Y;
}
return ret;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -164,14 +115,6 @@ namespace MLEM.Ui.Elements {
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) { public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
if (this.Texture == null) if (this.Texture == null)
return; return;
if (this.SamplerState != null) {
batch.End();
var localContext = context;
localContext.SamplerState = this.SamplerState;
batch.Begin(localContext);
}
var center = new Vector2(this.Texture.Width / 2F, this.Texture.Height / 2F); var center = new Vector2(this.Texture.Width / 2F, this.Texture.Height / 2F);
var color = this.Color.OrDefault(Microsoft.Xna.Framework.Color.White) * alpha; var color = this.Color.OrDefault(Microsoft.Xna.Framework.Color.White) * alpha;
if (this.MaintainImageAspect) { if (this.MaintainImageAspect) {
@ -182,12 +125,6 @@ namespace MLEM.Ui.Elements {
var scale = new Vector2(1F / this.Texture.Width, 1F / this.Texture.Height) * this.DisplayArea.Size; var scale = new Vector2(1F / this.Texture.Width, 1F / this.Texture.Height) * this.DisplayArea.Size;
batch.Draw(this.Texture, this.DisplayArea.Location + center * scale, color, this.ImageRotation, center, scale * this.ImageScale, this.ImageEffects, 0); batch.Draw(this.Texture, this.DisplayArea.Location + center * scale, color, this.ImageRotation, center, scale * this.ImageScale, this.ImageEffects, 0);
} }
if (this.SamplerState != null) {
batch.End();
batch.Begin(context);
}
base.Draw(time, batch, alpha, context); base.Draw(time, batch, alpha, context);
} }
@ -197,7 +134,7 @@ namespace MLEM.Ui.Elements {
return; return;
var nullChanged = this.displayedTexture == null != (newTexture == null); var nullChanged = this.displayedTexture == null != (newTexture == null);
this.displayedTexture = newTexture; this.displayedTexture = newTexture;
if (nullChanged || this.ScaleToImage || this.SetWidthBasedOnAspect || this.SetHeightBasedOnAspect) if (nullChanged || this.scaleToImage)
this.SetAreaDirty(); this.SetAreaDirty();
} }

View file

@ -55,16 +55,12 @@ namespace MLEM.Ui.Elements {
} }
private readonly List<Element> relevantChildren = new List<Element>(); private readonly List<Element> relevantChildren = new List<Element>();
private readonly HashSet<Element> scrolledChildren = new HashSet<Element>();
private readonly float[] scrollBarMaxHistory;
private readonly bool scrollOverflow; private readonly bool scrollOverflow;
private RenderTarget2D renderTarget; private RenderTarget2D renderTarget;
private bool relevantChildrenDirty; private bool relevantChildrenDirty;
private float scrollBarChildOffset; private float scrollBarChildOffset;
private StyleProp<float> scrollBarOffset; private StyleProp<float> scrollBarOffset;
private float lastScrollOffset;
private bool childrenDirtyForScroll;
/// <summary> /// <summary>
/// Creates a new panel with the given settings. /// Creates a new panel with the given settings.
@ -74,7 +70,7 @@ namespace MLEM.Ui.Elements {
/// <param name="positionOffset">The panel's offset from its anchor point</param> /// <param name="positionOffset">The panel's offset from its anchor point</param>
/// <param name="setHeightBasedOnChildren">Whether the panel should automatically calculate its height based on its children's size</param> /// <param name="setHeightBasedOnChildren">Whether the panel should automatically calculate its height based on its children's size</param>
/// <param name="scrollOverflow">Whether this panel should automatically add a scroll bar to scroll towards elements that are beyond the area this panel covers</param> /// <param name="scrollOverflow">Whether this panel should automatically add a scroll bar to scroll towards elements that are beyond the area this panel covers</param>
/// <param name="autoHideScrollbar">Whether the scroll bar should be hidden automatically if the panel does not contain enough children to allow for scrolling. This only has an effect if <paramref name="scrollOverflow"/> is <see langword="true"/>.</param> /// <param name="autoHideScrollbar">Whether the scroll bar should be hidden automatically if the panel does not contain enough children to allow for scrolling</param>
public Panel(Anchor anchor, Vector2 size, Vector2 positionOffset, bool setHeightBasedOnChildren = false, bool scrollOverflow = false, bool autoHideScrollbar = true) : base(anchor, size) { public Panel(Anchor anchor, Vector2 size, Vector2 positionOffset, bool setHeightBasedOnChildren = false, bool scrollOverflow = false, bool autoHideScrollbar = true) : base(anchor, size) {
this.PositionOffset = positionOffset; this.PositionOffset = positionOffset;
this.SetHeightBasedOnChildren = setHeightBasedOnChildren; this.SetHeightBasedOnChildren = setHeightBasedOnChildren;
@ -98,23 +94,9 @@ namespace MLEM.Ui.Elements {
this.ScrollToElement(e); this.ScrollToElement(e);
}; };
this.AddChild(this.ScrollBar); this.AddChild(this.ScrollBar);
this.scrollBarMaxHistory = new float[3];
for (var i = 0; i < this.scrollBarMaxHistory.Length; i++)
this.scrollBarMaxHistory[i] = -1;
} }
} }
/// <summary>
/// Creates a new panel with the given settings.
/// </summary>
/// <param name="anchor">The panel's anchor</param>
/// <param name="size">The panel's default size</param>
/// <param name="setHeightBasedOnChildren">Whether the panel should automatically calculate its height based on its children's size</param>
/// <param name="scrollOverflow">Whether this panel should automatically add a scroll bar to scroll towards elements that are beyond the area this panel covers</param>
/// <param name="autoHideScrollbar">Whether the scroll bar should be hidden automatically if the panel does not contain enough children to allow for scrolling. This only has an effect if <paramref name="scrollOverflow"/> is <see langword="true"/>.</param>
public Panel(Anchor anchor, Vector2 size, bool setHeightBasedOnChildren = false, bool scrollOverflow = false, bool autoHideScrollbar = true) : this(anchor, size, Vector2.Zero, setHeightBasedOnChildren, scrollOverflow, autoHideScrollbar) {}
/// <inheritdoc /> /// <inheritdoc />
public override void ForceUpdateArea() { public override void ForceUpdateArea() {
if (this.scrollOverflow) { if (this.scrollOverflow) {
@ -124,15 +106,11 @@ namespace MLEM.Ui.Elements {
foreach (var child in this.Children) { foreach (var child in this.Children) {
if (child != this.ScrollBar && !child.Anchor.IsAuto()) if (child != this.ScrollBar && !child.Anchor.IsAuto())
throw new NotSupportedException($"A panel that handles overflow can't contain non-automatic anchors ({child})"); throw new NotSupportedException($"A panel that handles overflow can't contain non-automatic anchors ({child})");
if (child is Panel panel && panel.scrollOverflow)
throw new NotSupportedException($"A panel that scrolls overflow cannot contain another panel that scrolls overflow ({child})");
} }
} }
base.ForceUpdateArea(); base.ForceUpdateArea();
if (this.scrollOverflow) {
for (var i = 0; i < this.scrollBarMaxHistory.Length; i++)
this.scrollBarMaxHistory[i] = -1;
}
this.SetScrollBarStyle(); this.SetScrollBarStyle();
} }
@ -158,16 +136,8 @@ namespace MLEM.Ui.Elements {
// when removing children, our scroll bar might have to be hidden // when removing children, our scroll bar might have to be hidden
// if we don't do this before adding children again, they might incorrectly assume that the scroll bar will still be visible and adjust their size accordingly // if we don't do this before adding children again, they might incorrectly assume that the scroll bar will still be visible and adjust their size accordingly
this.childrenDirtyForScroll = true; if (this.System != null)
}
/// <inheritdoc />
public override T AddChild<T>(T element, int index = -1) {
// if children were recently removed, make sure to update the scroll bar before adding new ones so that they can't incorrectly assume the scroll bar will be visible
if (this.childrenDirtyForScroll && this.System != null)
this.ScrollSetup(); this.ScrollSetup();
return base.AddChild(element, index);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -232,10 +202,10 @@ namespace MLEM.Ui.Elements {
/// </summary> /// </summary>
/// <param name="elementY">The y coordinate to scroll to, which should have this element's <see cref="Element.Scale"/> applied.</param> /// <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) { public void ScrollToElement(float elementY) {
var highestValidChild = this.Children.FirstOrDefault(c => c != this.ScrollBar && !c.IsHidden); var firstChild = this.Children.FirstOrDefault(c => c != this.ScrollBar);
if (highestValidChild == null) if (firstChild == null)
return; return;
this.ScrollBar.CurrentValue = (elementY - this.Area.Height / 2 - highestValidChild.Area.Top) / this.Scale + this.ChildPadding.Value.Height / 2; this.ScrollBar.CurrentValue = (elementY - this.Area.Height / 2 - firstChild.Area.Top) / this.Scale + this.ChildPadding.Value.Height / 2;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -264,12 +234,13 @@ namespace MLEM.Ui.Elements {
/// <inheritdoc /> /// <inheritdoc />
protected override void OnChildAreaDirty(Element child, bool grandchild) { protected override void OnChildAreaDirty(Element child, bool grandchild) {
base.OnChildAreaDirty(child, grandchild); base.OnChildAreaDirty(child, grandchild);
if (grandchild && !this.AreaDirty) { // we only need to scroll when a grandchild changes, since all of our children are forced
// 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
// to be auto-anchored and so will automatically propagate their changes up to us if (grandchild) {
this.ScrollChildren(); this.ScrollChildren();
// we also need to re-setup here in case the child is involved in a special GetTotalCoveredArea // we also need to re-setup here in case the child is involved in a special GetTotalCoveredArea
this.ScrollSetup(); if (!this.AreaDirty)
this.ScrollSetup();
} }
} }
@ -286,41 +257,32 @@ namespace MLEM.Ui.Elements {
/// 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() {
this.childrenDirtyForScroll = false;
if (!this.scrollOverflow || this.IsHidden) if (!this.scrollOverflow || this.IsHidden)
return; return;
float childrenHeight; float childrenHeight;
if (this.Children.Count > 1) { if (this.Children.Count > 1) {
var highestValidChild = this.Children.FirstOrDefault(c => c != this.ScrollBar && !c.IsHidden); var firstChild = this.Children.FirstOrDefault(c => c != this.ScrollBar);
var lowestChild = this.GetLowestChild(c => c != this.ScrollBar && !c.IsHidden, true); var lowestChild = this.GetLowestChild(c => c != this.ScrollBar && !c.IsHidden, true);
childrenHeight = lowestChild.GetTotalCoveredArea(false).Bottom - highestValidChild.Area.Top; childrenHeight = lowestChild.GetTotalCoveredArea(false).Bottom - firstChild.Area.Top;
} else { } else {
// if we only have one child (the scroll bar), then the children take up no visual height // if we only have one child (the scroll bar), then the children take up no visual height
childrenHeight = 0; childrenHeight = 0;
} }
// the max value of the scroll bar 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 = Math.Max(0, (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)) {
// avoid a show/hide oscillation that occurs while updating our area with children that can lose height when the scroll bar is shown (like long paragraphs) this.ScrollBar.MaxValue = scrollBarMax;
if (!this.scrollBarMaxHistory[0].Equals(this.scrollBarMaxHistory[2], Element.Epsilon) || !this.scrollBarMaxHistory[1].Equals(scrollBarMax, Element.Epsilon)) { this.relevantChildrenDirty = true;
this.scrollBarMaxHistory[0] = this.scrollBarMaxHistory[1];
this.scrollBarMaxHistory[1] = this.scrollBarMaxHistory[2];
this.scrollBarMaxHistory[2] = scrollBarMax;
this.ScrollBar.MaxValue = scrollBarMax;
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;
var childOffsetDelta = childOffset - this.scrollBarChildOffset; if (!this.scrollBarChildOffset.Equals(childOffset, Element.Epsilon)) {
if (!childOffsetDelta.Equals(0, Element.Epsilon)) { this.ChildPadding += new Padding(0, -this.scrollBarChildOffset + childOffset, 0, 0);
this.scrollBarChildOffset = childOffset; this.scrollBarChildOffset = childOffset;
this.ChildPadding += new Padding(0, childOffsetDelta, 0, 0); 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
@ -328,15 +290,15 @@ namespace MLEM.Ui.Elements {
this.ScrollBar.ScrollerSize = new Vector2(this.ScrollerSize.Value.X, Math.Max(this.ScrollerSize.Value.Y, scrollerHeight)); this.ScrollBar.ScrollerSize = new Vector2(this.ScrollerSize.Value.X, Math.Max(this.ScrollerSize.Value.Y, scrollerHeight));
// update the render target // update the render target
var area = (Rectangle) this.GetRenderTargetArea(); var targetArea = (Rectangle) this.GetRenderTargetArea();
if (area.Width <= 0 || area.Height <= 0) { if (targetArea.Width <= 0 || targetArea.Height <= 0) {
this.renderTarget?.Dispose(); this.renderTarget?.Dispose();
this.renderTarget = null; this.renderTarget = null;
return; return;
} }
if (this.renderTarget == null || area.Width != this.renderTarget.Width || area.Height != this.renderTarget.Height) { if (this.renderTarget == null || targetArea.Width != this.renderTarget.Width || targetArea.Height != this.renderTarget.Height) {
this.renderTarget?.Dispose(); this.renderTarget?.Dispose();
this.renderTarget = new RenderTarget2D(this.System.Game.GraphicsDevice, area.Width, area.Height, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); this.renderTarget = targetArea.IsEmpty ? null : new RenderTarget2D(this.System.Game.GraphicsDevice, targetArea.Width, targetArea.Height);
this.relevantChildrenDirty = true; this.relevantChildrenDirty = true;
} }
} }
@ -368,7 +330,7 @@ namespace MLEM.Ui.Elements {
} }
private RectangleF GetRenderTargetArea() { private RectangleF GetRenderTargetArea() {
var area = this.ChildPaddedArea.OffsetCopy(this.ScaledScrollOffset); var area = this.ChildPaddedArea;
area.X = this.DisplayArea.X; area.X = this.DisplayArea.X;
area.Width = this.DisplayArea.Width; area.Width = this.DisplayArea.Width;
return area; return area;
@ -377,21 +339,9 @@ namespace MLEM.Ui.Elements {
private void ScrollChildren() { private void ScrollChildren() {
if (!this.scrollOverflow) if (!this.scrollOverflow)
return; return;
var currentChildren = new HashSet<Element>();
// scroll all our children (and cache newly added ones)
// we ignore false grandchildren so that the children of the scroll bar stay in place // 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)) { foreach (var child in this.GetChildren(c => c != this.ScrollBar, true, true))
// if a child was newly added later, the last scroll offset was never applied child.ScrollOffset.Y = -this.ScrollBar.CurrentValue;
if (this.scrolledChildren.Add(child))
child.ScrollOffset.Y -= this.lastScrollOffset;
child.ScrollOffset.Y += (this.lastScrollOffset - this.ScrollBar.CurrentValue);
currentChildren.Add(child);
}
// remove cached scrolled children that aren't our children anymore
this.scrolledChildren.IntersectWith(currentChildren);
this.lastScrollOffset = this.ScrollBar.CurrentValue;
this.relevantChildrenDirty = true; this.relevantChildrenDirty = true;
} }

View file

@ -166,30 +166,6 @@ namespace MLEM.Ui.Elements {
private float textScaleMultiplier = 1; private float textScaleMultiplier = 1;
private bool autoAdjustWidth; private bool autoAdjustWidth;
/// <summary>
/// Creates a new paragraph with the given settings.
/// </summary>
/// <param name="anchor">The paragraph's anchor</param>
/// <param name="width">The paragraph's width. Note that its height is automatically calculated.</param>
/// <param name="textCallback">The paragraph's text</param>
/// <param name="alignment">The paragraph's text alignment.</param>
/// <param name="autoAdjustWidth">Whether the paragraph's width should automatically be calculated based on the text within it.</param>
public Paragraph(Anchor anchor, float width, TextCallback textCallback, TextAlignment alignment, bool autoAdjustWidth = false) : this(anchor, width, textCallback, autoAdjustWidth) {
this.Alignment = alignment;
}
/// <summary>
/// Creates a new paragraph with the given settings.
/// </summary>
/// <param name="anchor">The paragraph's anchor</param>
/// <param name="width">The paragraph's width. Note that its height is automatically calculated.</param>
/// <param name="text">The paragraph's text</param>
/// <param name="alignment">The paragraph's text alignment.</param>
/// <param name="autoAdjustWidth">Whether the paragraph's width should automatically be calculated based on the text within it.</param>
public Paragraph(Anchor anchor, float width, string text, TextAlignment alignment, bool autoAdjustWidth = false) : this(anchor, width, text, autoAdjustWidth) {
this.Alignment = alignment;
}
/// <summary> /// <summary>
/// Creates a new paragraph with the given settings. /// Creates a new paragraph with the given settings.
/// </summary> /// </summary>
@ -201,13 +177,7 @@ namespace MLEM.Ui.Elements {
this.GetTextCallback = textCallback; this.GetTextCallback = textCallback;
} }
/// <summary> /// <inheritdoc cref="Paragraph(Anchor,float,TextCallback,bool)"/>
/// Creates a new paragraph with the given settings.
/// </summary>
/// <param name="anchor">The paragraph's anchor</param>
/// <param name="width">The paragraph's width. Note that its height is automatically calculated.</param>
/// <param name="text">The paragraph's text</param>
/// <param name="autoAdjustWidth">Whether the paragraph's width should automatically be calculated based on the text within it.</param>
public Paragraph(Anchor anchor, float width, string text, bool autoAdjustWidth = false) : base(anchor, new Vector2(width, 0)) { public Paragraph(Anchor anchor, float width, string text, bool autoAdjustWidth = false) : base(anchor, new Vector2(width, 0)) {
this.Text = text; this.Text = text;
this.AutoAdjustWidth = autoAdjustWidth; this.AutoAdjustWidth = autoAdjustWidth;
@ -262,7 +232,7 @@ namespace MLEM.Ui.Elements {
private void SetTextDirty() { private void SetTextDirty() {
this.tokenizedText = null; this.tokenizedText = null;
// only set our area dirty if our size changed as a result of this action // only set our area dirty if our size changed as a result of this action
if (!this.AreaDirty && (this.System == null || !this.CalcActualSize(this.ParentArea).Equals(this.DisplayArea.Size, Element.Epsilon))) if (!this.AreaDirty && !this.CalcActualSize(this.ParentArea).Equals(this.DisplayArea.Size, Element.Epsilon))
this.SetAreaDirty(); this.SetAreaDirty();
} }

View file

@ -158,7 +158,7 @@ namespace MLEM.Ui.Elements {
if (this.isMouseScrolling) if (this.isMouseScrolling)
this.ScrollToPos(this.TransformInverseAll(this.Input.ViewportMousePosition.ToVector2())); this.ScrollToPos(this.TransformInverseAll(this.Input.ViewportMousePosition.ToVector2()));
if (!this.Horizontal) { if (!this.Horizontal) {
if (this.IsMousedForScrolling(moused)) { if (moused != null && (moused == this.Parent || moused.GetParentTree().Contains(this.Parent))) {
var scroll = this.Input.LastScrollWheel - this.Input.ScrollWheel; var scroll = this.Input.LastScrollWheel - this.Input.ScrollWheel;
if (scroll != 0) if (scroll != 0)
this.CurrentValue += this.StepPerScroll * Math.Sign(scroll); this.CurrentValue += this.StepPerScroll * Math.Sign(scroll);
@ -244,23 +244,6 @@ namespace MLEM.Ui.Elements {
this.SmoothScrollFactor = this.SmoothScrollFactor.OrStyle(style.ScrollBarSmoothScrollFactor); this.SmoothScrollFactor = this.SmoothScrollFactor.OrStyle(style.ScrollBarSmoothScrollFactor);
} }
private bool IsMousedForScrolling(Element moused) {
if (moused == null || (moused != this.Parent && !moused.GetParentTree().Contains(this.Parent)))
return false;
// if we're moused, check if there are any scroll bars deeper than us that should take precedence
var foundMe = false;
foreach (var child in this.Parent.GetChildren(regardGrandchildren: true)) {
if (foundMe) {
if (child is ScrollBar b && !b.Horizontal && b.IsMousedForScrolling(moused))
return false;
} else if (child == this) {
// once we found ourselves, all subsequent children are deeper/older!
foundMe = true;
}
}
return true;
}
/// <summary> /// <summary>
/// A delegate method used for <see cref="ScrollBar.OnValueChanged"/> /// A delegate method used for <see cref="ScrollBar.OnValueChanged"/>
/// </summary> /// </summary>

View file

@ -204,6 +204,8 @@ namespace MLEM.Ui.Elements {
if (this.IsSelectedActive && !this.IsHidden && !this.textInput.OnTextInput(key, character) && key == Keys.Enter && !this.Multiline) if (this.IsSelectedActive && !this.IsHidden && !this.textInput.OnTextInput(key, character) && key == Keys.Enter && !this.Multiline)
this.EnterReceiver?.Controls?.PressElement(this.EnterReceiver); this.EnterReceiver?.Controls?.PressElement(this.EnterReceiver);
}; };
this.OnDeselected += e => this.CaretPos = 0;
this.OnSelected += e => this.CaretPos = this.textInput.Length;
} }
/// <inheritdoc /> /// <inheritdoc />

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible> <IsTrimmable>true</IsTrimmable>
<RootNamespace>MLEM.Ui</RootNamespace> <RootNamespace>MLEM.Ui</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants> <DefineConstants>$(DefineConstants);FNA</DefineConstants>
</PropertyGroup> </PropertyGroup>
@ -24,7 +24,7 @@
<PackageReference Include="TextCopy" Version="6.2.0" Condition="'$(TargetFramework)'!='net452'" /> <PackageReference Include="TextCopy" Version="6.2.0" Condition="'$(TargetFramework)'!='net452'" />
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" /> <ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
<ProjectReference Include="..\ThirdParty\FNA\FNA.csproj"> <ProjectReference Include="..\FNA\FNA.csproj">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible> <IsTrimmable>true</IsTrimmable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View file

@ -139,32 +139,21 @@ namespace MLEM.Ui.Parsers {
/// This method invokes an asynchronouns action, meaning the <see cref="Image"/>'s <see cref="Image.Texture"/> will likely not have loaded in when this method returns. /// This method invokes an asynchronouns action, meaning the <see cref="Image"/>'s <see cref="Image.Texture"/> will likely not have loaded in when this method returns.
/// </summary> /// </summary>
/// <param name="path">The absolute, relative or web path to the image.</param> /// <param name="path">The absolute, relative or web path to the image.</param>
/// <param name="onImageFetched">An action that is invoked with the loaded image once it is fetched. Note that this action will be invoked asynchronously.</param>
/// <returns>The loaded image.</returns> /// <returns>The loaded image.</returns>
/// <exception cref="NullReferenceException">Thrown if <see cref="GraphicsDevice"/> is null, or if there is an <see cref="Exception"/> loading the image and <see cref="ImageExceptionHandler"/> is unset.</exception> /// <exception cref="NullReferenceException">Thrown if <see cref="GraphicsDevice"/> is null, or if there is an <see cref="Exception"/> loading the image and <see cref="ImageExceptionHandler"/> is unset.</exception>
protected Image ParseImage(string path, Action<TextureRegion> onImageFetched = null) { protected Image ParseImage(string path) {
if (this.GraphicsDevice == null) if (this.GraphicsDevice == null)
throw new NullReferenceException("A UI parser requires a GraphicsDevice for parsing images"); throw new NullReferenceException("A UI parser requires a GraphicsDevice for parsing images");
var imageLock = new object();
TextureRegion image = null; TextureRegion image = null;
return new Image(Anchor.AutoLeft, Vector2.One, _ => { return new Image(Anchor.AutoLeft, new Vector2(1, -1), _ => image) {
lock (imageLock)
return image;
}) {
SetHeightBasedOnAspect = true,
OnAddedToUi = e => { OnAddedToUi = e => {
bool imageNull; if (image == null)
lock (imageLock)
imageNull = image == null;
if (imageNull)
LoadImageAsync(); LoadImageAsync();
}, },
OnRemovedFromUi = e => { OnRemovedFromUi = e => {
lock (imageLock) { image?.Texture.Dispose();
image?.Texture.Dispose(); image = null;
image = null;
}
} }
}; };
@ -189,12 +178,7 @@ namespace MLEM.Ui.Parsers {
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);
} }
lock (imageLock) { image = new TextureRegion(tex);
if (image == null) {
image = new TextureRegion(tex);
onImageFetched?.Invoke(image);
}
}
} catch (Exception e) { } catch (Exception e) {
if (this.ImageExceptionHandler != null) { if (this.ImageExceptionHandler != null) {
this.ImageExceptionHandler.Invoke(path, e); this.ImageExceptionHandler.Invoke(path, e);

View file

@ -103,7 +103,7 @@ namespace MLEM.Ui {
/// </summary> /// </summary>
public bool HandleGamepad = true; public bool HandleGamepad = true;
/// <summary> /// <summary>
/// If this value is true, the ui controls are in automatic navigation mode. The state of automatic navigation is usually based on the current <see cref="NavType"/>. /// If this value is true, the ui controls are in automatic navigation mode.
/// This means that the <see cref="UiStyle.SelectionIndicator"/> will be drawn around the <see cref="SelectedElement"/>. /// This means that the <see cref="UiStyle.SelectionIndicator"/> will be drawn around the <see cref="SelectedElement"/>.
/// </summary> /// </summary>
public bool IsAutoNavMode { public bool IsAutoNavMode {
@ -115,29 +115,12 @@ namespace MLEM.Ui {
} }
} }
} }
/// <summary>
/// The current <see cref="NavigationType"/> of these ui controls, which represents the last type of interaction that was used to interact with the underlying <see cref="UiSystem"/>.
/// </summary>
public NavigationType NavType {
get => this.navType;
set {
if (this.navType != value) {
var last = this.navType;
this.navType = value;
this.NavTypeChanged?.Invoke(last, value);
}
}
}
/// <summary> /// <summary>
/// An event that is raised when <see cref="IsAutoNavMode"/> is changed. /// An event that is raised when <see cref="IsAutoNavMode"/> is changed.
/// This can be used for custom actions like hiding the mouse cursor when automatic navigation is enabled. /// This can be used for custom actions like hiding the mouse cursor when automatic navigation is enabled.
/// </summary> /// </summary>
public event Action<bool> AutoNavModeChanged; public event Action<bool> AutoNavModeChanged;
/// <summary>
/// An event that is raised when <see cref="NavType"/> is changed. It receives the previous navigation type, as well as the newly set navigation type.
/// </summary>
public event Action<NavigationType, NavigationType> NavTypeChanged;
/// <summary> /// <summary>
/// This value ist true if the <see cref="InputHandler"/> was created by this ui controls instance, or if it was passed in. /// This value ist true if the <see cref="InputHandler"/> was created by this ui controls instance, or if it was passed in.
@ -151,7 +134,6 @@ namespace MLEM.Ui {
private readonly Dictionary<string, Element> selectedElements = new Dictionary<string, Element>(); private readonly Dictionary<string, Element> selectedElements = new Dictionary<string, Element>();
private bool isAutoNavMode; private bool isAutoNavMode;
private NavigationType navType;
/// <summary> /// <summary>
/// Creates a new instance of the ui controls. /// Creates a new instance of the ui controls.
@ -185,7 +167,6 @@ namespace MLEM.Ui {
if (this.Input.IsPressedAvailable(MouseButton.Left)) { if (this.Input.IsPressedAvailable(MouseButton.Left)) {
this.IsAutoNavMode = false; this.IsAutoNavMode = false;
this.NavType = NavigationType.Mouse;
var selectedNow = mousedNow != null && mousedNow.CanBeSelected ? mousedNow : null; var selectedNow = mousedNow != null && mousedNow.CanBeSelected ? mousedNow : null;
this.SelectElement(this.ActiveRoot, selectedNow); this.SelectElement(this.ActiveRoot, selectedNow);
if (mousedNow != null && mousedNow.CanBePressed) { if (mousedNow != null && mousedNow.CanBePressed) {
@ -194,7 +175,6 @@ namespace MLEM.Ui {
} }
} else if (this.Input.IsPressedAvailable(MouseButton.Right)) { } else if (this.Input.IsPressedAvailable(MouseButton.Right)) {
this.IsAutoNavMode = false; this.IsAutoNavMode = false;
this.NavType = NavigationType.Mouse;
if (mousedNow != null && mousedNow.CanBePressed) { if (mousedNow != null && mousedNow.CanBePressed) {
this.PressElement(mousedNow, true); this.PressElement(mousedNow, true);
this.Input.TryConsumePressed(MouseButton.Right); this.Input.TryConsumePressed(MouseButton.Right);
@ -207,14 +187,12 @@ namespace MLEM.Ui {
if (this.HandleKeyboard) { if (this.HandleKeyboard) {
if (this.KeyboardButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) { if (this.KeyboardButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) { if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) {
this.NavType = NavigationType.Keyboard;
// primary or secondary action on element using space or enter // primary or secondary action on element using space or enter
this.PressElement(this.SelectedElement, this.Input.IsModifierKeyDown(ModifierKey.Shift)); this.PressElement(this.SelectedElement, this.Input.IsModifierKeyDown(ModifierKey.Shift));
this.KeyboardButtons.TryConsumePressed(this.Input, this.GamepadIndex); this.KeyboardButtons.TryConsumePressed(this.Input, this.GamepadIndex);
} }
} else if (this.Input.IsPressedAvailable(Keys.Tab)) { } else if (this.Input.IsPressedAvailable(Keys.Tab)) {
this.IsAutoNavMode = true; this.IsAutoNavMode = true;
this.NavType = NavigationType.Keyboard;
// tab or shift-tab to next or previous element // tab or shift-tab to next or previous element
var backward = this.Input.IsModifierKeyDown(ModifierKey.Shift); var backward = this.Input.IsModifierKeyDown(ModifierKey.Shift);
var next = this.GetTabNextElement(backward); var next = this.GetTabNextElement(backward);
@ -230,14 +208,12 @@ namespace MLEM.Ui {
if (this.HandleTouch) { if (this.HandleTouch) {
if (this.Input.GetViewportGesture(GestureType.Tap, out var tap)) { if (this.Input.GetViewportGesture(GestureType.Tap, out var tap)) {
this.IsAutoNavMode = false; this.IsAutoNavMode = false;
this.NavType = NavigationType.Touch;
var tapped = this.GetElementUnderPos(tap.Position); var tapped = this.GetElementUnderPos(tap.Position);
this.SelectElement(this.ActiveRoot, tapped); this.SelectElement(this.ActiveRoot, tapped);
if (tapped != null && tapped.CanBePressed) if (tapped != null && tapped.CanBePressed)
this.PressElement(tapped); this.PressElement(tapped);
} else if (this.Input.GetViewportGesture(GestureType.Hold, out var hold)) { } else if (this.Input.GetViewportGesture(GestureType.Hold, out var hold)) {
this.IsAutoNavMode = false; this.IsAutoNavMode = false;
this.NavType = NavigationType.Touch;
var held = this.GetElementUnderPos(hold.Position); var held = this.GetElementUnderPos(hold.Position);
this.SelectElement(this.ActiveRoot, held); this.SelectElement(this.ActiveRoot, held);
if (held != null && held.CanBePressed) if (held != null && held.CanBePressed)
@ -248,7 +224,6 @@ namespace MLEM.Ui {
foreach (var location in this.Input.ViewportTouchState) { foreach (var location in this.Input.ViewportTouchState) {
var element = this.GetElementUnderPos(location.Position); var element = this.GetElementUnderPos(location.Position);
if (location.State == TouchLocationState.Pressed) { if (location.State == TouchLocationState.Pressed) {
this.NavType = NavigationType.Touch;
// start touching an element if we just touched down on it // start touching an element if we just touched down on it
this.SetTouchedElement(element); this.SetTouchedElement(element);
} else if (element != this.TouchedElement) { } else if (element != this.TouchedElement) {
@ -264,13 +239,11 @@ namespace MLEM.Ui {
if (this.HandleGamepad) { if (this.HandleGamepad) {
if (this.GamepadButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) { if (this.GamepadButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) { if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) {
this.NavType = NavigationType.Gamepad;
this.PressElement(this.SelectedElement); this.PressElement(this.SelectedElement);
this.GamepadButtons.TryConsumePressed(this.Input, this.GamepadIndex); this.GamepadButtons.TryConsumePressed(this.Input, this.GamepadIndex);
} }
} else if (this.SecondaryGamepadButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) { } else if (this.SecondaryGamepadButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) { if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) {
this.NavType = NavigationType.Gamepad;
this.PressElement(this.SelectedElement, true); this.PressElement(this.SelectedElement, true);
this.SecondaryGamepadButtons.TryConsumePressed(this.Input, this.GamepadIndex); this.SecondaryGamepadButtons.TryConsumePressed(this.Input, this.GamepadIndex);
} }
@ -313,9 +286,8 @@ namespace MLEM.Ui {
/// </summary> /// </summary>
/// <param name="root">The root element of the <see cref="Element"/></param> /// <param name="root">The root element of the <see cref="Element"/></param>
/// <param name="element">The element to select, or null to deselect the selected element.</param> /// <param name="element">The element to select, or null to deselect the selected element.</param>
/// <param name="autoNav">Whether automatic navigation should be forced on. If this is <see langword="null"/>, the automatic navigation state will stay the same.</param> /// <param name="autoNav">Whether automatic navigation should be forced on</param>
/// <param name="navType">An optional <see cref="NavigationType"/> to set. If this is <see langword="null"/>, the navigation type will stay the same.</param> public void SelectElement(RootElement root, Element element, bool? autoNav = null) {
public void SelectElement(RootElement root, Element element, bool? autoNav = null, NavigationType? navType = null) {
if (root == null) if (root == null)
return; return;
if (element != null && !element.CanBeSelected) if (element != null && !element.CanBeSelected)
@ -336,8 +308,6 @@ namespace MLEM.Ui {
if (autoNav != null) if (autoNav != null)
this.IsAutoNavMode = autoNav.Value; this.IsAutoNavMode = autoNav.Value;
if (navType != null)
this.NavType = navType.Value;
} }
/// <summary> /// <summary>
@ -474,7 +444,6 @@ namespace MLEM.Ui {
private bool HandleGamepadNextElement(Direction2 dir) { private bool HandleGamepadNextElement(Direction2 dir) {
this.IsAutoNavMode = true; this.IsAutoNavMode = true;
this.NavType = NavigationType.Gamepad;
var next = this.GetGamepadNextElement(dir); var next = this.GetGamepadNextElement(dir);
if (this.SelectedElement != null) if (this.SelectedElement != null)
next = this.SelectedElement.GetGamepadNextElement(dir, next); next = this.SelectedElement.GetGamepadNextElement(dir, next);
@ -485,34 +454,5 @@ namespace MLEM.Ui {
return false; return false;
} }
/// <summary>
/// An enumeration type that represents the possible types of navigation that a <see cref="UiControls"/> instance supports.
/// This is used by <see cref="UiControls.NavType"/>, which stores the most recently used navigation type for a <see cref="UiSystem"/>.
/// </summary>
public enum NavigationType {
/// <summary>
/// An unknown navigation type, which usually means there has not been any ui navigation of any type yet.
/// </summary>
Unknown = 0,
/// <summary>
/// Mouse cursor and mouse button navigation.
/// </summary>
Mouse,
/// <summary>
/// Keyboard navigation.
/// </summary>
Keyboard,
/// <summary>
/// Touch and gesture navigation.
/// </summary>
Touch,
/// <summary>
/// Gamepad-style navigation, which may also include arrow key-based navigation based on current <see cref="UiControls"/> settings.
/// </summary>
Gamepad
}
} }
} }

View file

@ -608,10 +608,9 @@ namespace MLEM.Ui {
/// Optionally, automatic navigation can be forced on, causing the <see cref="UiStyle.SelectionIndicator"/> to be drawn around the element. /// Optionally, automatic navigation can be forced on, causing the <see cref="UiStyle.SelectionIndicator"/> to be drawn around the element.
/// </summary> /// </summary>
/// <param name="element">The element to select, or null to deselect the selected element.</param> /// <param name="element">The element to select, or null to deselect the selected element.</param>
/// <param name="autoNav">Whether automatic navigation should be forced on. If this is <see langword="null"/>, the automatic navigation state will stay the same.</param> /// <param name="autoNav">Whether automatic navigation should be forced on</param>
/// <param name="navType">An optional <see cref="UiControls.NavigationType"/> to set. If this is <see langword="null"/>, the navigation type will stay the same.</param> public void SelectElement(Element element, bool? autoNav = null) {
public void SelectElement(Element element, bool? autoNav = null, UiControls.NavigationType? navType = null) { this.System.Controls.SelectElement(this, element, autoNav);
this.System.Controls.SelectElement(this, element, autoNav, navType);
} }
/// <summary> /// <summary>

View file

@ -1,4 +1,3 @@
using System;
using System.Globalization; using System.Globalization;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
@ -37,27 +36,6 @@ namespace MLEM.Extensions {
return new Color(color.ToVector4() * other.ToVector4()); return new Color(color.ToVector4() * other.ToVector4());
} }
/// <summary>
/// Returns the hexadecimal representation of this color as a string in the format <c>#AARRGGBB</c>, or optionally <c>AARRGGBB</c>, without the pound symbol.
/// </summary>
/// <param name="color">The color to convert.</param>
/// <param name="hash">Whether a # should prepend the string.</param>
/// <returns>The resulting hex string.</returns>
public static string ToHexStringRgba(this Color color, bool hash = true) {
return $"{(hash ? "#" : string.Empty)}{color.A:X2}{color.R:X2}{color.G:X2}{color.B:X2}";
}
/// <summary>
/// Returns the hexadecimal representation of this color as a string in the format <c>#RRGGBB</c>, or optionally <c>RRGGBB</c>, without the pound symbol.
/// The alpha channel is ignored.
/// </summary>
/// <param name="color">The color to convert.</param>
/// <param name="hash">Whether a # should prepend the string.</param>
/// <returns>The resulting hex string.</returns>
public static string ToHexStringRgb(this Color color, bool hash = true) {
return $"{(hash ? "#" : string.Empty)}{color.R:X2}{color.G:X2}{color.B:X2}";
}
} }
/// <summary> /// <summary>
@ -86,34 +64,16 @@ namespace MLEM.Extensions {
} }
/// <summary> /// <summary>
/// Parses a hexadecimal string into a color and throws a <see cref="FormatException"/> if parsing fails. /// Parses a hexadecimal string into a color.
/// The string can either be formatted as <c>RRGGBB</c> or <c>AARRGGBB</c> and can optionally start with a <c>#</c>. /// The string can either be formatted as RRGGBB or AARRGGBB and can optionally start with a <c>#</c>.
/// </summary> /// </summary>
/// <param name="value">The string to parse.</param> /// <param name="value">The string to parse.</param>
/// <returns>The resulting color.</returns> /// <returns>The resulting color.</returns>
/// <exception cref="FormatException">Thrown if parsing fails.</exception>
public static Color FromHexString(string value) { public static Color FromHexString(string value) {
if (!ColorHelper.TryFromHexString(value, out var val))
throw new FormatException($"Cannot parse hex string {value}");
return val;
}
/// <summary>
/// Tries to parse a hexadecimal string into a color and returns whether a color was successfully parsed.
/// The string can either be formatted as <c>RRGGBB</c> or <c>AARRGGBB</c> and can optionally start with a <c>#</c>.
/// </summary>
/// <param name="value">The string to parse.</param>
/// <param name="color">The resulting color.</param>
/// <returns>Whether parsing was successful.</returns>
public static bool TryFromHexString(string value, out Color color) {
if (value.StartsWith("#")) if (value.StartsWith("#"))
value = value.Substring(1); value = value.Substring(1);
if (int.TryParse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var val)) { var val = int.Parse(value, NumberStyles.HexNumber);
color = value.Length > 6 ? ColorHelper.FromHexRgba(val) : ColorHelper.FromHexRgb(val); return value.Length > 6 ? ColorHelper.FromHexRgba(val) : ColorHelper.FromHexRgb(val);
return true;
}
color = default;
return false;
} }
} }

View file

@ -69,22 +69,11 @@ namespace MLEM.Extensions {
/// </summary> /// </summary>
/// <param name="device">The graphics device</param> /// <param name="device">The graphics device</param>
/// <param name="target">The render target to apply</param> /// <param name="target">The render target to apply</param>
/// <returns>The render target context, to be used in a <c>using</c> statement</returns> /// <returns></returns>
public static TargetContext WithRenderTarget(this GraphicsDevice device, RenderTarget2D target) { public static TargetContext WithRenderTarget(this GraphicsDevice device, RenderTarget2D target) {
return new TargetContext(device, target); return new TargetContext(device, target);
} }
/// <summary>
/// Starts a new <see cref="TargetContext"/> using the specified render target bindings.
/// The returned context automatically disposes when used in a <c>using</c> statement, which causes any previously applied render targets to be reapplied automatically.
/// </summary>
/// <param name="device">The graphics device</param>
/// <param name="targets">The render targets to apply</param>
/// <returns>The render target context, to be used in a <c>using</c> statement</returns>
public static TargetContext WithRenderTargets(this GraphicsDevice device, params RenderTargetBinding[] targets) {
return new TargetContext(device, targets);
}
/// <summary> /// <summary>
/// Represents a context in which a <see cref="RenderTarget2D"/> is applied. /// Represents a context in which a <see cref="RenderTarget2D"/> is applied.
/// This class should be used with <see cref="GraphicsExtensions.WithRenderTarget"/>. /// This class should be used with <see cref="GraphicsExtensions.WithRenderTarget"/>.
@ -99,20 +88,7 @@ namespace MLEM.Extensions {
/// </summary> /// </summary>
/// <param name="device">The graphics device to apply the target on</param> /// <param name="device">The graphics device to apply the target on</param>
/// <param name="target">The target to apply</param> /// <param name="target">The target to apply</param>
public TargetContext(GraphicsDevice device, RenderTarget2D target) : this(device) { public TargetContext(GraphicsDevice device, RenderTarget2D target) {
device.SetRenderTarget(target);
}
/// <summary>
/// Creates a new target context with the given settings.
/// </summary>
/// <param name="device">The graphics device to apply the target on</param>
/// <param name="targets">The targets to apply</param>
public TargetContext(GraphicsDevice device, RenderTargetBinding[] targets) : this(device) {
device.SetRenderTargets(targets);
}
private TargetContext(GraphicsDevice device) {
this.device = device; this.device = device;
#if FNA #if FNA
// RenderTargetCount doesn't exist in FNA but we still want the optimization in MG // RenderTargetCount doesn't exist in FNA but we still want the optimization in MG
@ -120,6 +96,7 @@ namespace MLEM.Extensions {
#else #else
this.lastTargets = device.RenderTargetCount <= 0 ? null : device.GetRenderTargets(); this.lastTargets = device.RenderTargetCount <= 0 ? null : device.GetRenderTargets();
#endif #endif
device.SetRenderTarget(target);
} }
/// <summary> /// <summary>

View file

@ -16,7 +16,8 @@ namespace MLEM.Extensions {
/// <typeparam name="T">The entries' type</typeparam> /// <typeparam name="T">The entries' type</typeparam>
/// <returns>A random entry</returns> /// <returns>A random entry</returns>
public static T GetRandomEntry<T>(this Random random, ICollection<T> entries) { public static T GetRandomEntry<T>(this Random random, ICollection<T> entries) {
return RandomExtensions.GetRandomEntry(entries, random.NextSingle()); // ElementAt internally optimizes for IList access so we don't have to here
return entries.ElementAt(random.Next(entries.Count));
} }
/// <summary> /// <summary>
@ -30,12 +31,28 @@ namespace MLEM.Extensions {
/// <returns>A random entry, based on the entries' weight</returns> /// <returns>A random entry, based on the entries' weight</returns>
/// <exception cref="IndexOutOfRangeException">If the weight function returns different weights for the same entry</exception> /// <exception cref="IndexOutOfRangeException">If the weight function returns different weights for the same entry</exception>
public static T GetRandomWeightedEntry<T>(this Random random, ICollection<T> entries, Func<T, int> weightFunc) { public static T GetRandomWeightedEntry<T>(this Random random, ICollection<T> entries, Func<T, int> weightFunc) {
return RandomExtensions.GetRandomWeightedEntry(entries, weightFunc, random.NextSingle()); var totalWeight = entries.Sum(weightFunc);
var goalWeight = random.Next(totalWeight);
var currWeight = 0;
foreach (var entry in entries) {
currWeight += weightFunc(entry);
if (currWeight > goalWeight)
return entry;
}
throw new IndexOutOfRangeException();
} }
/// <inheritdoc cref="GetRandomWeightedEntry{T}(System.Random,System.Collections.Generic.ICollection{T},System.Func{T,int})"/> /// <inheritdoc cref="GetRandomWeightedEntry{T}(System.Random,System.Collections.Generic.ICollection{T},System.Func{T,int})"/>
public static T GetRandomWeightedEntry<T>(this Random random, ICollection<T> entries, Func<T, float> weightFunc) { public static T GetRandomWeightedEntry<T>(this Random random, ICollection<T> entries, Func<T, float> weightFunc) {
return RandomExtensions.GetRandomWeightedEntry(entries, weightFunc, random.NextSingle()); var totalWeight = entries.Sum(weightFunc);
var goalWeight = random.NextDouble() * totalWeight;
var currWeight = 0F;
foreach (var entry in entries) {
currWeight += weightFunc(entry);
if (currWeight > goalWeight)
return entry;
}
throw new IndexOutOfRangeException();
} }
/// <summary> /// <summary>
@ -70,32 +87,5 @@ namespace MLEM.Extensions {
} }
#endif #endif
internal static T GetRandomEntry<T>(ICollection<T> entries, float randomValue) {
// ElementAt internally optimizes for IList access so we don't have to here
return entries.ElementAt((int) (randomValue * entries.Count));
}
internal static T GetRandomWeightedEntry<T>(ICollection<T> entries, Func<T, int> weightFunc, float randomValue) {
var goalWeight = randomValue * entries.Sum(weightFunc);
var currWeight = 0;
foreach (var entry in entries) {
currWeight += weightFunc(entry);
if (currWeight > goalWeight)
return entry;
}
throw new IndexOutOfRangeException();
}
internal static T GetRandomWeightedEntry<T>(ICollection<T> entries, Func<T, float> weightFunc, float randomValue) {
var goalWeight = randomValue * entries.Sum(weightFunc);
var currWeight = 0F;
foreach (var entry in entries) {
currWeight += weightFunc(entry);
if (currWeight > goalWeight)
return entry;
}
throw new IndexOutOfRangeException();
}
} }
} }

View file

@ -63,18 +63,6 @@ namespace MLEM.Font {
return (curr, 1); return (curr, 1);
} }
/// <summary>
/// Returns an index in this code point source that is as close to <paramref name="index"/> as possible, but not between two members of a surrogate pair. If the <paramref name="index"/> is already not between surrogate pairs, it is returned unchanged.
/// </summary>
/// <param name="index">The index to ensure is not between surrogates.</param>
/// <param name="increase">Whether the returned index should be increased by 1 (instead of decreased by 1) when it is between surrogates.</param>
/// <returns>An index close to <paramref name="index"/>, but not between surrogates.</returns>
public int EnsureSurrogateBoundary(int index, bool increase) {
if (index < this.Length && char.IsLowSurrogate(this[index]))
return increase || index <= 0 ? index + 1 : index - 1;
return index;
}
/// <summary>Returns an enumerator that iterates through the collection.</summary> /// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.</returns> /// <returns>A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.</returns>
/// <filterpriority>1</filterpriority> /// <filterpriority>1</filterpriority>

View file

@ -23,9 +23,9 @@ namespace MLEM.Formatting.Codes {
public readonly Match Match; public readonly Match Match;
/// <summary> /// <summary>
/// The tokens that this formatting code is a part of. /// The tokens that this formatting code is a part of.
/// Note that this collection only has multiple entries if additional tokens have to be started while this code is still applied. /// Note that this array only has multiple entries if additional tokens have to be started while this code is still applied.
/// </summary> /// </summary>
public readonly List<Token> Tokens = new List<Token>(); public IList<Token> Tokens { get; internal set; }
/// <summary> /// <summary>
/// Creates a new formatting code based on a formatting code regex and its match. /// Creates a new formatting code based on a formatting code regex and its match.

View file

@ -18,11 +18,5 @@ namespace MLEM.Formatting.Codes {
return this.font?.Invoke(defaultPick); return this.font?.Invoke(defaultPick);
} }
/// <inheritdoc />
public override bool EndsHere(Code other) {
// turning a string bold/italic should only end when that specific code is ended using SimpleEndCode
return false;
}
} }
} }

View file

@ -102,7 +102,7 @@ namespace MLEM.Formatting {
this.Codes.Add(new Regex("<b>"), (f, m, r) => new FontCode(m, r, fnt => fnt.Bold)); this.Codes.Add(new Regex("<b>"), (f, m, r) => new FontCode(m, r, fnt => fnt.Bold));
this.Codes.Add(new Regex("<i>"), (f, m, r) => new FontCode(m, r, fnt => fnt.Italic)); this.Codes.Add(new Regex("<i>"), (f, m, r) => new FontCode(m, r, fnt => fnt.Italic));
this.Codes.Add(new Regex(@"<s(?: #([0-9\w]{6,8}) (([+-.0-9]*)))?>"), (f, m, r) => new ShadowCode(m, r, this.Codes.Add(new Regex(@"<s(?: #([0-9\w]{6,8}) (([+-.0-9]*)))?>"), (f, m, r) => new ShadowCode(m, r,
ColorHelper.TryFromHexString(m.Groups[1].Value, out var color) ? color : this.DefaultShadowColor, m.Groups[1].Success ? ColorHelper.FromHexString(m.Groups[1].Value) : this.DefaultShadowColor,
float.TryParse(m.Groups[2].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var offset) ? new Vector2(offset) : this.DefaultShadowOffset)); float.TryParse(m.Groups[2].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var offset) ? new Vector2(offset) : this.DefaultShadowOffset));
this.Codes.Add(new Regex("<u>"), (f, m, r) => new UnderlineCode(m, r, this.LineThickness, this.UnderlineOffset)); this.Codes.Add(new Regex("<u>"), (f, m, r) => new UnderlineCode(m, r, this.LineThickness, this.UnderlineOffset));
this.Codes.Add(new Regex("<st>"), (f, m, r) => new UnderlineCode(m, r, this.LineThickness, this.StrikethroughOffset)); this.Codes.Add(new Regex("<st>"), (f, m, r) => new UnderlineCode(m, r, this.LineThickness, this.StrikethroughOffset));
@ -111,7 +111,7 @@ namespace MLEM.Formatting {
this.Codes.Add(new Regex(@"<sup(?: ([+-.0-9]+))?>"), (f, m, r) => new SubSupCode(m, r, this.Codes.Add(new Regex(@"<sup(?: ([+-.0-9]+))?>"), (f, m, r) => new SubSupCode(m, r,
float.TryParse(m.Groups[1].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var off) ? -off : this.DefaultSupOffset)); float.TryParse(m.Groups[1].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var off) ? -off : this.DefaultSupOffset));
this.Codes.Add(new Regex(@"<o(?: #([0-9\w]{6,8}) (([+-.0-9]*)))?>"), (f, m, r) => new OutlineCode(m, r, this.Codes.Add(new Regex(@"<o(?: #([0-9\w]{6,8}) (([+-.0-9]*)))?>"), (f, m, r) => new OutlineCode(m, r,
ColorHelper.TryFromHexString(m.Groups[1].Value, out var color) ? color : this.DefaultOutlineColor, m.Groups[1].Success ? ColorHelper.FromHexString(m.Groups[1].Value) : this.DefaultOutlineColor,
float.TryParse(m.Groups[2].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var thickness) ? thickness : this.DefaultOutlineThickness, float.TryParse(m.Groups[2].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var thickness) ? thickness : this.DefaultOutlineThickness,
this.OutlineDiagonals)); this.OutlineDiagonals));
} }
@ -124,13 +124,12 @@ namespace MLEM.Formatting {
this.Codes.Add(new Regex($"<c {c.Name}>"), (f, m, r) => new ColorCode(m, r, value)); this.Codes.Add(new Regex($"<c {c.Name}>"), (f, m, r) => new ColorCode(m, r, value));
} }
} }
this.Codes.Add(new Regex(@"<c #([0-9\w]{6,8})>"), (f, m, r) => new ColorCode(m, r, this.Codes.Add(new Regex(@"<c #([0-9\w]{6,8})>"), (f, m, r) => new ColorCode(m, r, ColorHelper.FromHexString(m.Groups[1].Value)));
ColorHelper.TryFromHexString(m.Groups[1].Value, out var color) ? color : Color.Red));
} }
// animation codes // animation codes
if (hasAnimations) { if (hasAnimations) {
this.Codes.Add(new Regex("<a wobbly(?: ([+-.0-9]*) ([+-.0-9]*))?>"), (f, m, r) => new WobblyCode(m, r, this.Codes.Add(new Regex(@"<a wobbly(?: ([+-.0-9]*) ([+-.0-9]*))?>"), (f, m, r) => new WobblyCode(m, r,
float.TryParse(m.Groups[1].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var mod) ? mod : this.DefaultWobblyModifier, float.TryParse(m.Groups[1].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var mod) ? mod : this.DefaultWobblyModifier,
float.TryParse(m.Groups[2].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var heightMod) ? heightMod : this.DefaultWobblyHeight)); float.TryParse(m.Groups[2].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var heightMod) ? heightMod : this.DefaultWobblyHeight));
} }
@ -156,12 +155,11 @@ namespace MLEM.Formatting {
// resolve macros // resolve macros
s = this.ResolveMacros(s); s = this.ResolveMacros(s);
var tokens = new List<Token>(); var tokens = new List<Token>();
var applied = new List<Code>(); var codes = new List<Code>();
var allCodes = new List<Code>();
// add the formatting code right at the start of the string // add the formatting code right at the start of the string
var firstCode = this.GetNextCode(s, 0, 0); var firstCode = this.GetNextCode(s, 0, 0);
if (firstCode != null) if (firstCode != null)
applied.Add(firstCode); codes.Add(firstCode);
var index = 0; var index = 0;
var rawIndex = 0; var rawIndex = 0;
while (rawIndex < s.Length) { while (rawIndex < s.Length) {
@ -169,25 +167,24 @@ namespace MLEM.Formatting {
// if we've reached the end of the string // if we've reached the end of the string
if (next == null) { if (next == null) {
var sub = s.Substring(rawIndex, s.Length - rawIndex); var sub = s.Substring(rawIndex, s.Length - rawIndex);
tokens.Add(new Token(applied.ToArray(), index, rawIndex, TextFormatter.StripFormatting(font, sub, applied), sub)); tokens.Add(new Token(codes.ToArray(), index, rawIndex, TextFormatter.StripFormatting(font, sub, codes), sub));
break; break;
} }
allCodes.Add(next);
// create a new token for the content up to the next code // create a new token for the content up to the next code
var ret = s.Substring(rawIndex, next.Match.Index - rawIndex); var ret = s.Substring(rawIndex, next.Match.Index - rawIndex);
var strippedRet = TextFormatter.StripFormatting(font, ret, applied); var strippedRet = TextFormatter.StripFormatting(font, ret, codes);
tokens.Add(new Token(applied.ToArray(), index, rawIndex, strippedRet, ret)); tokens.Add(new Token(codes.ToArray(), index, rawIndex, strippedRet, ret));
// move to the start of the next code // move to the start of the next code
rawIndex = next.Match.Index; rawIndex = next.Match.Index;
index += strippedRet.Length; index += strippedRet.Length;
// remove all codes that are incompatible with the next one and apply it // remove all codes that are incompatible with the next one and apply it
applied.RemoveAll(c => c.EndsHere(next) || next.EndsOther(c)); codes.RemoveAll(c => c.EndsHere(next) || next.EndsOther(c));
applied.Add(next); codes.Add(next);
} }
return new TokenizedString(font, alignment, s, TextFormatter.StripFormatting(font, s, allCodes), tokens.ToArray(), allCodes.ToArray()); return new TokenizedString(font, alignment, s, TextFormatter.StripFormatting(font, s, tokens.SelectMany(t => t.AppliedCodes)), tokens.ToArray());
} }
/// <summary> /// <summary>

View file

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
@ -15,7 +14,6 @@ namespace MLEM.Formatting {
/// <summary> /// <summary>
/// The formatting codes that are applied on this token. /// The formatting codes that are applied on this token.
/// Codes are stored application order, with the first entry in the array being the code that was most recently applied.
/// </summary> /// </summary>
public readonly Code[] AppliedCodes; public readonly Code[] AppliedCodes;
/// <summary> /// <summary>
@ -47,14 +45,11 @@ namespace MLEM.Formatting {
internal float[] InnerOffsets; internal float[] InnerOffsets;
internal Token(Code[] appliedCodes, int index, int rawIndex, string substring, string rawSubstring) { internal Token(Code[] appliedCodes, int index, int rawIndex, string substring, string rawSubstring) {
Array.Reverse(appliedCodes);
this.AppliedCodes = appliedCodes; this.AppliedCodes = appliedCodes;
this.Index = index; this.Index = index;
this.RawIndex = rawIndex; this.RawIndex = rawIndex;
this.Substring = substring; this.Substring = substring;
this.RawSubstring = rawSubstring; this.RawSubstring = rawSubstring;
foreach (var code in appliedCodes)
code.Tokens.Add(this);
} }
/// <summary> /// <summary>

View file

@ -1,9 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using MLEM.Extensions;
using MLEM.Font; using MLEM.Font;
using MLEM.Formatting.Codes; using MLEM.Formatting.Codes;
using MLEM.Misc; using MLEM.Misc;
@ -40,11 +42,17 @@ namespace MLEM.Formatting {
private float initialInnerOffset; private float initialInnerOffset;
private RectangleF area; private RectangleF area;
internal TokenizedString(GenericFont font, TextAlignment alignment, string rawString, string strg, Token[] tokens, Code[] allCodes) { internal TokenizedString(GenericFont font, TextAlignment alignment, string rawString, string strg, Token[] tokens) {
this.RawString = rawString; this.RawString = rawString;
this.String = strg; this.String = strg;
this.Tokens = tokens; this.Tokens = tokens;
this.AllCodes = allCodes;
// since a code can be present in multiple tokens, we use Distinct here
this.AllCodes = tokens.SelectMany(t => t.AppliedCodes).Distinct().ToArray();
// TODO this can probably be optimized by keeping track of a code's tokens while tokenizing
foreach (var code in this.AllCodes)
code.Tokens = new ReadOnlyCollection<Token>(this.Tokens.Where(t => t.AppliedCodes.Contains(code)).ToList());
this.Realign(font, alignment); this.Realign(font, alignment);
} }

View file

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using MLEM.Misc;
using MLEM.Textures; using MLEM.Textures;
namespace MLEM.Graphics { namespace MLEM.Graphics {
@ -87,94 +86,92 @@ namespace MLEM.Graphics {
/// <param name="layerDepth">The layer depth to draw with.</param> /// <param name="layerDepth">The layer depth to draw with.</param>
/// <param name="overlayDepthOffset">An optional depth offset from <paramref name="layerDepth"/> that the overlay should be drawn with</param> /// <param name="overlayDepthOffset">An optional depth offset from <paramref name="layerDepth"/> that the overlay should be drawn with</param>
public static void DrawExtendedAutoTile(SpriteBatch batch, Vector2 pos, TextureRegion backgroundTexture, TextureRegion overlayTexture, ConnectsTo connectsTo, Color backgroundColor, Color overlayColor, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0, float overlayDepthOffset = 0) { public static void DrawExtendedAutoTile(SpriteBatch batch, Vector2 pos, TextureRegion backgroundTexture, TextureRegion overlayTexture, ConnectsTo connectsTo, Color backgroundColor, Color overlayColor, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0, float overlayDepthOffset = 0) {
if (backgroundTexture != null) var orig = origin ?? Vector2.Zero;
batch.Draw(backgroundTexture, pos, backgroundColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth); var sc = scale ?? Vector2.One;
var od = layerDepth + overlayDepthOffset; var od = layerDepth + overlayDepthOffset;
AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.UpLeft, origin, scale, od); var (r1, r2, r3, r4) = AutoTiling.CalculateExtendedAutoTile(overlayTexture.Area, connectsTo);
AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.UpRight, origin, scale, od); if (backgroundTexture != null)
AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.DownLeft, origin, scale, od); batch.Draw(backgroundTexture, pos, backgroundColor, 0, orig, sc, SpriteEffects.None, layerDepth);
AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.DownRight, origin, scale, od); if (r1 != Rectangle.Empty)
} batch.Draw(overlayTexture.Texture, pos, r1, overlayColor, 0, orig, sc, SpriteEffects.None, od);
if (r2 != Rectangle.Empty)
/// <summary> batch.Draw(overlayTexture.Texture, pos, r2, overlayColor, 0, orig, sc, SpriteEffects.None, od);
/// This method allows for a single corner of a tiled texture to be drawn in an auto-tiling mode. if (r3 != Rectangle.Empty)
/// 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. batch.Draw(overlayTexture.Texture, pos, r3, overlayColor, 0, orig, sc, SpriteEffects.None, od);
/// if (r4 != Rectangle.Empty)
/// For more information, and to draw all four corners at once, see <see cref="DrawExtendedAutoTile(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,Microsoft.Xna.Framework.Color,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float,float)"/> batch.Draw(overlayTexture.Texture, pos, r4, overlayColor, 0, orig, sc, SpriteEffects.None, od);
/// </summary>
/// <param name="batch">The sprite batch to use for drawing.</param>
/// <param name="pos">The position to draw at.</param>
/// <param name="overlayTexture">The first overlay region, as described in the summary.</param>
/// <param name="connectsTo">A function that determines whether two positions should connect.</param>
/// <param name="overlayColor">The color to draw border and corner textures with.</param>
/// <param name="corner">The corner of the auto-tile to draw. Can be <see cref="Direction2.UpLeft"/>, <see cref="Direction2.UpRight"/>, <see cref="Direction2.DownLeft"/> or <see cref="Direction2.DownRight"/>.</param>
/// <param name="origin">The origin to draw from.</param>
/// <param name="scale">The scale to draw with.</param>
/// <param name="layerDepth">The layer depth to draw with.</param>
public static void DrawExtendedAutoTileCorner(SpriteBatch batch, Vector2 pos, TextureRegion overlayTexture, ConnectsTo connectsTo, Color overlayColor, Direction2 corner, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0) {
var src = AutoTiling.CalculateExtendedAutoTile(overlayTexture.Area, connectsTo, corner);
if (src != Rectangle.Empty)
batch.Draw(overlayTexture.Texture, pos, src, overlayColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth);
} }
/// <inheritdoc cref="DrawExtendedAutoTile(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,Microsoft.Xna.Framework.Color,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float,float)"/> /// <inheritdoc cref="DrawExtendedAutoTile(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,Microsoft.Xna.Framework.Color,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float,float)"/>
public static void DrawExtendedAutoTile(SpriteBatch batch, Vector2 pos, TextureRegion backgroundTexture, Func<int, TextureRegion> overlayTextures, ConnectsTo connectsTo, Color backgroundColor, Color overlayColor, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0, float overlayDepthOffset = 0) { public static void DrawExtendedAutoTile(SpriteBatch batch, Vector2 pos, TextureRegion backgroundTexture, Func<int, TextureRegion> overlayTextures, ConnectsTo connectsTo, Color backgroundColor, Color overlayColor, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0, float overlayDepthOffset = 0) {
if (backgroundTexture != null) var orig = origin ?? Vector2.Zero;
batch.Draw(backgroundTexture, pos, backgroundColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth); var sc = scale ?? Vector2.One;
var od = layerDepth + overlayDepthOffset; var od = layerDepth + overlayDepthOffset;
AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.UpLeft, origin, scale, od); var (xUl, xUr, xDl, xDr) = AutoTiling.CalculateExtendedAutoTileOffsets(connectsTo);
AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.UpRight, origin, scale, od); if (backgroundTexture != null)
AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.DownLeft, origin, scale, od); batch.Draw(backgroundTexture, pos, backgroundColor, 0, orig, sc, SpriteEffects.None, layerDepth);
AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.DownRight, origin, scale, od); if (xUl >= 0)
} batch.Draw(overlayTextures(xUl), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od);
if (xUr >= 0)
/// <inheritdoc cref="DrawExtendedAutoTileCorner(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,MLEM.Misc.Direction2,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float)"/> batch.Draw(overlayTextures(xUr), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od);
public static void DrawExtendedAutoTileCorner(SpriteBatch batch, Vector2 pos, Func<int, TextureRegion> overlayTextures, ConnectsTo connectsTo, Color overlayColor, Direction2 corner, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0) { if (xDl >= 0)
var src = AutoTiling.CalculateExtendedAutoTileOffset(connectsTo, corner); batch.Draw(overlayTextures(xDl), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od);
if (src >= 0) if (xDr >= 0)
batch.Draw(overlayTextures(src), pos, overlayColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth); batch.Draw(overlayTextures(xDr), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od);
} }
/// <inheritdoc cref="DrawExtendedAutoTile(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,Microsoft.Xna.Framework.Color,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float,float)"/> /// <inheritdoc cref="DrawExtendedAutoTile(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,Microsoft.Xna.Framework.Color,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float,float)"/>
public static void AddExtendedAutoTile(StaticSpriteBatch batch, Vector2 pos, TextureRegion backgroundTexture, TextureRegion overlayTexture, ConnectsTo connectsTo, Color backgroundColor, Color overlayColor, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0, float overlayDepthOffset = 0, ICollection<StaticSpriteBatch.Item> items = null) { public static void AddExtendedAutoTile(StaticSpriteBatch batch, Vector2 pos, TextureRegion backgroundTexture, TextureRegion overlayTexture, ConnectsTo connectsTo, Color backgroundColor, Color overlayColor, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0, float overlayDepthOffset = 0, ICollection<StaticSpriteBatch.Item> items = null) {
var orig = origin ?? Vector2.Zero;
var sc = scale ?? Vector2.One;
var od = layerDepth + overlayDepthOffset;
var (r1, r2, r3, r4) = AutoTiling.CalculateExtendedAutoTile(overlayTexture.Area, connectsTo);
if (backgroundTexture != null) { if (backgroundTexture != null) {
var background = batch.Add(backgroundTexture, pos, backgroundColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth); var background = batch.Add(backgroundTexture, pos, backgroundColor, 0, orig, sc, SpriteEffects.None, layerDepth);
items?.Add(background); items?.Add(background);
} }
var od = layerDepth + overlayDepthOffset; if (r1 != Rectangle.Empty) {
AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.UpLeft, origin, scale, od, items); var o1 = batch.Add(overlayTexture.Texture, pos, r1, overlayColor, 0, orig, sc, SpriteEffects.None, od);
AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.UpRight, origin, scale, od, items); items?.Add(o1);
AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.DownLeft, origin, scale, od, items); }
AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.DownRight, origin, scale, od, items); if (r2 != Rectangle.Empty) {
} var o2 = batch.Add(overlayTexture.Texture, pos, r2, overlayColor, 0, orig, sc, SpriteEffects.None, od);
items?.Add(o2);
/// <inheritdoc cref="DrawExtendedAutoTileCorner(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,MLEM.Misc.Direction2,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float)"/> }
public static void AddExtendedAutoTileCorner(StaticSpriteBatch batch, Vector2 pos, TextureRegion overlayTexture, ConnectsTo connectsTo, Color overlayColor, Direction2 corner, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0, ICollection<StaticSpriteBatch.Item> items = null) { if (r3 != Rectangle.Empty) {
var src = AutoTiling.CalculateExtendedAutoTile(overlayTexture.Area, connectsTo, corner); var o3 = batch.Add(overlayTexture.Texture, pos, r3, overlayColor, 0, orig, sc, SpriteEffects.None, od);
if (src != Rectangle.Empty) { items?.Add(o3);
var o4 = batch.Add(overlayTexture.Texture, pos, src, overlayColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth); }
if (r4 != Rectangle.Empty) {
var o4 = batch.Add(overlayTexture.Texture, pos, r4, overlayColor, 0, orig, sc, SpriteEffects.None, od);
items?.Add(o4); items?.Add(o4);
} }
} }
/// <inheritdoc cref="DrawExtendedAutoTile(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,Func{int,MLEM.Textures.TextureRegion},MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,Microsoft.Xna.Framework.Color,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float,float)"/> /// <inheritdoc cref="DrawExtendedAutoTile(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,Func{int,MLEM.Textures.TextureRegion},MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,Microsoft.Xna.Framework.Color,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float,float)"/>
public static void AddExtendedAutoTile(StaticSpriteBatch batch, Vector2 pos, TextureRegion backgroundTexture, Func<int, TextureRegion> overlayTextures, ConnectsTo connectsTo, Color backgroundColor, Color overlayColor, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0, float overlayDepthOffset = 0, ICollection<StaticSpriteBatch.Item> items = null) { public static void AddExtendedAutoTile(StaticSpriteBatch batch, Vector2 pos, TextureRegion backgroundTexture, Func<int, TextureRegion> overlayTextures, ConnectsTo connectsTo, Color backgroundColor, Color overlayColor, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0, float overlayDepthOffset = 0, ICollection<StaticSpriteBatch.Item> items = null) {
var orig = origin ?? Vector2.Zero;
var sc = scale ?? Vector2.One;
var od = layerDepth + overlayDepthOffset;
var (xUl, xUr, xDl, xDr) = AutoTiling.CalculateExtendedAutoTileOffsets(connectsTo);
if (backgroundTexture != null) { if (backgroundTexture != null) {
var background = batch.Add(backgroundTexture, pos, backgroundColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth); var background = batch.Add(backgroundTexture, pos, backgroundColor, 0, orig, sc, SpriteEffects.None, layerDepth);
items?.Add(background); items?.Add(background);
} }
var od = layerDepth + overlayDepthOffset; if (xUl >= 0) {
AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.UpLeft, origin, scale, od, items); var o1 = batch.Add(overlayTextures(xUl), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od);
AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.UpRight, origin, scale, od, items); items?.Add(o1);
AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.DownLeft, origin, scale, od, items); }
AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.DownRight, origin, scale, od, items); if (xUr >= 0) {
} var o2 = batch.Add(overlayTextures(xUr), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od);
items?.Add(o2);
/// <inheritdoc cref="DrawExtendedAutoTileCorner(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,MLEM.Misc.Direction2,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float)"/> }
public static void AddExtendedAutoTileCorner(StaticSpriteBatch batch, Vector2 pos, Func<int, TextureRegion> overlayTextures, ConnectsTo connectsTo, Color overlayColor, Direction2 corner, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0, ICollection<StaticSpriteBatch.Item> items = null) { if (xDl >= 0) {
var src = AutoTiling.CalculateExtendedAutoTileOffset(connectsTo, corner); var o3 = batch.Add(overlayTextures(xDl), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od);
if (src >= 0) { items?.Add(o3);
var o4 = batch.Add(overlayTextures(src), pos, overlayColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth); }
if (xDr >= 0) {
var o4 = batch.Add(overlayTextures(xDr), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od);
items?.Add(o4); items?.Add(o4);
} }
} }
@ -197,36 +194,26 @@ namespace MLEM.Graphics {
new Vector2(pos.X + w2 * scale.X, pos.Y + h2 * scale.Y), new Rectangle(textureRegion.X + w2 + xDr * w, textureRegion.Y + h2, w2, h2)); new Vector2(pos.X + w2 * scale.X, pos.Y + h2 * scale.Y), new Rectangle(textureRegion.X + w2 + xDr * w, textureRegion.Y + h2, w2, h2));
} }
private static int CalculateExtendedAutoTileOffset(ConnectsTo connectsTo, Direction2 corner) { private static (int, int, int, int) CalculateExtendedAutoTileOffsets(ConnectsTo connectsTo) {
switch (corner) { var up = connectsTo(0, -1);
case Direction2.UpLeft: { var down = connectsTo(0, 1);
var up = connectsTo(0, -1); var left = connectsTo(-1, 0);
var left = connectsTo(-1, 0); var right = connectsTo(1, 0);
return up && left ? connectsTo(-1, -1) ? -1 : 12 : left ? 0 : up ? 8 : 4; return (
} up && left ? connectsTo(-1, -1) ? -1 : 12 : left ? 0 : up ? 8 : 4,
case Direction2.UpRight: { up && right ? connectsTo(1, -1) ? -1 : 13 : right ? 1 : up ? 9 : 5,
var up = connectsTo(0, -1); down && left ? connectsTo(-1, 1) ? -1 : 14 : left ? 2 : down ? 10 : 6,
var right = connectsTo(1, 0); down && right ? connectsTo(1, 1) ? -1 : 15 : right ? 3 : down ? 11 : 7);
return up && right ? connectsTo(1, -1) ? -1 : 13 : right ? 1 : up ? 9 : 5;
}
case Direction2.DownLeft: {
var down = connectsTo(0, 1);
var left = connectsTo(-1, 0);
return down && left ? connectsTo(-1, 1) ? -1 : 14 : left ? 2 : down ? 10 : 6;
}
case Direction2.DownRight: {
var down = connectsTo(0, 1);
var right = connectsTo(1, 0);
return down && right ? connectsTo(1, 1) ? -1 : 15 : right ? 3 : down ? 11 : 7;
}
default:
throw new ArgumentOutOfRangeException(nameof(corner), corner, null);
}
} }
private static Rectangle CalculateExtendedAutoTile(Rectangle textureRegion, ConnectsTo connectsTo, Direction2 corner) { private static (Rectangle, Rectangle, Rectangle, Rectangle) CalculateExtendedAutoTile(Rectangle textureRegion, ConnectsTo connectsTo) {
var off = AutoTiling.CalculateExtendedAutoTileOffset(connectsTo, corner); var (xUl, xUr, xDl, xDr) = AutoTiling.CalculateExtendedAutoTileOffsets(connectsTo);
return off < 0 ? Rectangle.Empty : new Rectangle(textureRegion.X + off * textureRegion.Width, textureRegion.Y, textureRegion.Width, textureRegion.Height); 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),
xDl < 0 ? Rectangle.Empty : new Rectangle(textureRegion.X + xDl * w, textureRegion.Y, w, h),
xDr < 0 ? Rectangle.Empty : new Rectangle(textureRegion.X + xDr * w, textureRegion.Y, w, h));
} }
/// <summary> /// <summary>

View file

@ -121,11 +121,11 @@ namespace MLEM.Input {
/// <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>
public IList<TouchLocation> LastViewportTouchState { get; private set; } = new List<TouchLocation>(); public IList<TouchLocation> LastViewportTouchState { get; private set; }
/// <summary> /// <summary>
/// Contains the <see cref="TouchState"/>, but with the <see cref="GraphicsDevice.Viewport"/> taken into account. /// Contains the <see cref="TouchState"/>, but with the <see cref="GraphicsDevice.Viewport"/> taken into account.
/// </summary> /// </summary>
public IList<TouchLocation> ViewportTouchState { get; private set; } = new List<TouchLocation>(); public IList<TouchLocation> ViewportTouchState { get; private set; }
/// <summary> /// <summary>
/// Contains the amount of gamepads that are currently connected. Note that this value will be set to 0 if <see cref="HandleGamepads"/> is false. /// Contains the amount of gamepads that are currently connected. Note that this value will be set to 0 if <see cref="HandleGamepads"/> is false.
/// This field is automatically updated in <see cref="Update()"/>. /// This field is automatically updated in <see cref="Update()"/>.
@ -342,7 +342,6 @@ namespace MLEM.Input {
} }
} else { } else {
this.TouchState = new TouchCollection(InputHandler.EmptyTouchLocations); this.TouchState = new TouchCollection(InputHandler.EmptyTouchLocations);
this.ViewportTouchState = this.TouchState;
this.gestures.Clear(); this.gestures.Clear();
} }

View file

@ -105,8 +105,7 @@ namespace MLEM.Input {
set { set {
var val = (int) MathHelper.Clamp(value, 0F, this.text.Length); var val = (int) MathHelper.Clamp(value, 0F, this.text.Length);
if (this.caretPos != val) { if (this.caretPos != val) {
// ensure that we don't move to a location that is between high and low surrogates this.caretPos = val;
this.caretPos = new CodePointSource(this.text).EnsureSurrogateBoundary(val, val > this.caretPos);
this.caretBlinkTimer = 0; this.caretBlinkTimer = 0;
this.SetTextDataDirty(false); this.SetTextDataDirty(false);
} }
@ -204,21 +203,6 @@ namespace MLEM.Input {
} }
} }
/// <summary> /// <summary>
/// The maximum amount of lines that can be visible in this text input, based on its <see cref="Size"/>, the used <see cref="Font"/> and its <see cref="TextScale"/>.
/// Note that this may return a number higher than 1 even if this is not a <see cref="Multiline"/> text input.
/// </summary>
public int MaxDisplayedLines => (this.Size.Y / (this.Font.LineHeight * this.TextScale)).Floor();
/// <summary>
/// The index of the first line that is currently visible.
/// This value can be changed using <see cref="ShowLine"/>.
/// </summary>
public int FirstVisibleLine { get; private set; }
/// <summary>
/// The total amount of lines of text that this text input currently has, including additional lines added by automatic wrapping.
/// If this is not a <see cref="Multiline"/> text input, this value is always 1.
/// </summary>
public int Lines { get; private set; }
/// <summary>
/// A function that is invoked when a string of text should be copied to the clipboard. /// A function that is invoked when a string of text should be copied to the clipboard.
/// MLEM.Ui uses the TextCopy package for this, but other options are available. /// MLEM.Ui uses the TextCopy package for this, but other options are available.
/// </summary> /// </summary>
@ -233,9 +217,10 @@ namespace MLEM.Input {
private char? maskingCharacter; private char? maskingCharacter;
private double caretBlinkTimer; private double caretBlinkTimer;
private string visibleText; private string displayedText;
private string[] multilineSplitText; private string[] splitText;
private int textOffset; private int textOffset;
private int lineOffset;
private int caretPos; private int caretPos;
private int caretLine; private int caretLine;
private int caretPosInLine; private int caretPosInLine;
@ -316,9 +301,9 @@ namespace MLEM.Input {
this.CaretPos--; this.CaretPos--;
} else if (this.CaretPos < this.text.Length && input.TryConsumePressed(Keys.Right)) { } else if (this.CaretPos < this.text.Length && input.TryConsumePressed(Keys.Right)) {
this.CaretPos++; this.CaretPos++;
} else if (this.Multiline && input.IsPressedAvailable(Keys.Up) && (input.IsModifierKeyDown(ModifierKey.Control) ? this.ShowLine(this.FirstVisibleLine - 1) : this.MoveCaretToLine(this.CaretLine - 1))) { } else if (this.Multiline && input.IsPressedAvailable(Keys.Up) && this.MoveCaretToLine(this.CaretLine - 1)) {
input.TryConsumePressed(Keys.Up); input.TryConsumePressed(Keys.Up);
} else if (this.Multiline && input.IsPressedAvailable(Keys.Down) && (input.IsModifierKeyDown(ModifierKey.Control) ? this.ShowLine(this.FirstVisibleLine + 1) : this.MoveCaretToLine(this.CaretLine + 1))) { } else if (this.Multiline && input.IsPressedAvailable(Keys.Down) && this.MoveCaretToLine(this.CaretLine + 1)) {
input.TryConsumePressed(Keys.Down); input.TryConsumePressed(Keys.Down);
} else if (this.CaretPos != 0 && input.TryConsumePressed(Keys.Home)) { } else if (this.CaretPos != 0 && input.TryConsumePressed(Keys.Home)) {
this.CaretPos = 0; this.CaretPos = 0;
@ -354,12 +339,12 @@ namespace MLEM.Input {
this.UpdateTextDataIfDirty(); this.UpdateTextDataIfDirty();
var scale = this.TextScale * drawScale; var scale = this.TextScale * drawScale;
this.Font.DrawString(batch, this.visibleText, textPos, textColor, 0, Vector2.Zero, scale, SpriteEffects.None, 0); this.Font.DrawString(batch, this.displayedText, textPos, textColor, 0, Vector2.Zero, scale, SpriteEffects.None, 0);
if (caretWidth > 0 && this.caretBlinkTimer < 0.5F) { if (caretWidth > 0 && this.caretBlinkTimer < 0.5F) {
var caretDrawPos = textPos + new Vector2(this.caretDrawOffset * scale, 0); var caretDrawPos = textPos + new Vector2(this.caretDrawOffset * scale, 0);
if (this.Multiline) if (this.Multiline)
caretDrawPos.Y += this.Font.LineHeight * (this.CaretLine - this.FirstVisibleLine) * scale; caretDrawPos.Y += this.Font.LineHeight * (this.CaretLine - this.lineOffset) * scale;
batch.Draw(batch.GetBlankTexture(), new RectangleF(caretDrawPos, new Vector2(caretWidth * drawScale, this.Font.LineHeight * scale)), null, textColor); batch.Draw(batch.GetBlankTexture(), new RectangleF(caretDrawPos, new Vector2(caretWidth * drawScale, this.Font.LineHeight * scale)), null, textColor);
} }
} }
@ -375,7 +360,7 @@ namespace MLEM.Input {
if (!this.FilterText(ref strg, removeMismatching)) if (!this.FilterText(ref strg, removeMismatching))
return; return;
if (this.MaximumCharacters != null && strg.Length > this.MaximumCharacters) if (this.MaximumCharacters != null && strg.Length > this.MaximumCharacters)
strg = strg.Substring(0, new CodePointSource(strg).EnsureSurrogateBoundary(this.MaximumCharacters.Value, false)); strg = strg.Substring(0, this.MaximumCharacters.Value);
this.text.Clear(); this.text.Clear();
this.text.Append(strg); this.text.Append(strg);
this.CaretPos = this.text.Length; this.CaretPos = this.text.Length;
@ -393,7 +378,7 @@ namespace MLEM.Input {
if (!this.FilterText(ref strg, removeMismatching)) if (!this.FilterText(ref strg, removeMismatching))
return false; return false;
if (this.MaximumCharacters != null && this.text.Length + strg.Length > this.MaximumCharacters) if (this.MaximumCharacters != null && this.text.Length + strg.Length > this.MaximumCharacters)
strg = strg.Substring(0, new CodePointSource(strg).EnsureSurrogateBoundary(this.MaximumCharacters.Value - this.text.Length, false)); strg = strg.Substring(0, this.MaximumCharacters.Value - this.text.Length);
this.text.Insert(this.CaretPos, strg); this.text.Insert(this.CaretPos, strg);
this.CaretPos += strg.Length; this.CaretPos += strg.Length;
this.SetTextDataDirty(); this.SetTextDataDirty();
@ -408,8 +393,7 @@ namespace MLEM.Input {
public bool RemoveText(int index, int length) { public bool RemoveText(int index, int length) {
if (index < 0 || index >= this.text.Length) if (index < 0 || index >= this.text.Length)
return false; return false;
var source = new CodePointSource(this.text); this.text.Remove(index, length);
this.text.Remove(source.EnsureSurrogateBoundary(index, false), source.EnsureSurrogateBoundary(index + length, true) - index);
// ensure that caret pos is still in bounds // ensure that caret pos is still in bounds
this.CaretPos = this.CaretPos; this.CaretPos = this.CaretPos;
this.SetTextDataDirty(); this.SetTextDataDirty();
@ -433,7 +417,7 @@ namespace MLEM.Input {
this.CaretPos = destStart + destAccum.Length; this.CaretPos = destStart + destAccum.Length;
return true; return true;
} }
destAccum += CodePointSource.ToString(new CodePointSource(this.text).GetCodePoint(destStart + destAccum.Length).CodePoint); destAccum += this.text[destStart + destAccum.Length];
} }
// if we don't find a proper position, just move to the end of the destination line // if we don't find a proper position, just move to the end of the destination line
this.CaretPos = destEnd; this.CaretPos = destEnd;
@ -442,26 +426,6 @@ namespace MLEM.Input {
return false; return false;
} }
/// <summary>
/// Moves visual focus into such bounds that the given line will be the first visible line of this text input.
/// </summary>
/// <param name="line">The first line that should be visible.</param>
/// <returns>Whether the line can be the fist visible line, and wasn't already the first visible line.</returns>
public bool ShowLine(int line) {
if (this.FirstVisibleLine != line && line >= 0 && line < this.Lines - (this.MaxDisplayedLines - 1)) {
this.FirstVisibleLine = line;
// move the caret into visible bounds if necessary
var clampedCaretLine = (int) MathHelper.Clamp(this.CaretLine, line, line + this.MaxDisplayedLines - 1F);
if (clampedCaretLine != this.CaretLine)
this.MoveCaretToLine(clampedCaretLine);
this.SetTextDataDirty(false);
return true;
}
return false;
}
private bool FilterText(ref string text, bool removeMismatching) { private bool FilterText(ref string text, bool removeMismatching) {
var result = new StringBuilder(); var result = new StringBuilder();
foreach (var codePoint in new CodePointSource(text)) { foreach (var codePoint in new CodePointSource(text)) {
@ -494,50 +458,49 @@ namespace MLEM.Input {
if (this.Multiline) { if (this.Multiline) {
// soft wrap if we're multiline // soft wrap if we're multiline
this.multilineSplitText = this.Font.SplitStringSeparate(visualText, this.Size.X, this.TextScale).ToArray(); this.splitText = this.Font.SplitStringSeparate(visualText, this.Size.X, this.TextScale).ToArray();
this.visibleText = string.Join("\n", this.multilineSplitText); this.displayedText = string.Join("\n", this.splitText);
this.Lines = this.visibleText.Count(c => c == '\n') + 1;
this.UpdateCaretData(); this.UpdateCaretData();
if (this.Font.MeasureString(this.visibleText).Y * this.TextScale > this.Size.Y) { if (this.Font.MeasureString(this.displayedText).Y * this.TextScale > this.Size.Y) {
if (this.FirstVisibleLine > this.CaretLine) { var maxLines = (this.Size.Y / (this.Font.LineHeight * this.TextScale)).Floor();
if (this.lineOffset > this.CaretLine) {
// if we're moving up // if we're moving up
this.FirstVisibleLine = this.CaretLine; this.lineOffset = this.CaretLine;
} else if (this.CaretLine >= this.MaxDisplayedLines) { } else if (this.CaretLine >= maxLines) {
// if we're moving down // if we're moving down
var limit = this.CaretLine - (this.MaxDisplayedLines - 1); var limit = this.CaretLine - (maxLines - 1);
if (limit > this.FirstVisibleLine) if (limit > this.lineOffset)
this.FirstVisibleLine = limit; this.lineOffset = limit;
} }
// calculate resulting string // calculate resulting string
var ret = new StringBuilder(); var ret = new StringBuilder();
var lines = 0; var lines = 0;
var originalIndex = 0; var originalIndex = 0;
for (var i = 0; i < this.visibleText.Length; i++) { for (var i = 0; i < this.displayedText.Length; i++) {
if (lines >= this.FirstVisibleLine) { if (lines >= this.lineOffset) {
if (ret.Length <= 0) if (ret.Length <= 0)
this.textOffset = originalIndex; this.textOffset = originalIndex;
ret.Append(this.visibleText[i]); ret.Append(this.displayedText[i]);
} }
if (this.visibleText[i] == '\n') { if (this.displayedText[i] == '\n') {
lines++; lines++;
if (visualText[originalIndex] == '\n') if (visualText[originalIndex] == '\n')
originalIndex++; originalIndex++;
} else { } else {
originalIndex++; originalIndex++;
} }
if (lines - this.FirstVisibleLine >= this.MaxDisplayedLines) if (lines - this.lineOffset >= maxLines)
break; break;
} }
this.visibleText = ret.ToString(); this.displayedText = ret.ToString();
} else { } else {
this.FirstVisibleLine = 0; this.lineOffset = 0;
this.textOffset = 0; this.textOffset = 0;
} }
} else { } else {
this.multilineSplitText = null; this.splitText = null;
this.FirstVisibleLine = 0; this.lineOffset = 0;
this.Lines = 1;
// not multiline, so scroll horizontally based on caret position // not multiline, so scroll horizontally based on caret position
if (this.Font.MeasureString(visualText).X * this.TextScale > this.Size.X) { if (this.Font.MeasureString(visualText).X * this.TextScale > this.Size.X) {
if (this.textOffset > this.CaretPos) { if (this.textOffset > this.CaretPos) {
@ -551,9 +514,9 @@ namespace MLEM.Input {
this.textOffset = bound; this.textOffset = bound;
} }
var visible = visualText.ToString(this.textOffset, visualText.Length - this.textOffset); var visible = visualText.ToString(this.textOffset, visualText.Length - this.textOffset);
this.visibleText = this.Font.TruncateString(visible, this.Size.X, this.TextScale); this.displayedText = this.Font.TruncateString(visible, this.Size.X, this.TextScale);
} else { } else {
this.visibleText = visualText.ToString(); this.displayedText = visualText.ToString();
this.textOffset = 0; this.textOffset = 0;
} }
this.UpdateCaretData(); this.UpdateCaretData();
@ -561,9 +524,9 @@ namespace MLEM.Input {
} }
private void UpdateCaretData() { private void UpdateCaretData() {
if (this.multilineSplitText != null) { if (this.splitText != null) {
// the code below will never execute if our text is empty, so reset our caret position fully // the code below will never execute if our text is empty, so reset our caret position fully
if (this.multilineSplitText.Length <= 0) { if (this.splitText.Length <= 0) {
this.caretLine = 0; this.caretLine = 0;
this.caretPosInLine = 0; this.caretPosInLine = 0;
this.caretDrawOffset = 0; this.caretDrawOffset = 0;
@ -572,9 +535,9 @@ namespace MLEM.Input {
var line = 0; var line = 0;
var index = 0; var index = 0;
for (var d = 0; d < this.multilineSplitText.Length; d++) { for (var d = 0; d < this.splitText.Length; d++) {
var startOfLine = 0; var startOfLine = 0;
var split = this.multilineSplitText[d]; var split = this.splitText[d];
for (var i = 0; i <= split.Length; i++) { for (var i = 0; i <= split.Length; i++) {
if (index == this.CaretPos) { if (index == this.CaretPos) {
this.caretLine = line; this.caretLine = line;
@ -594,20 +557,20 @@ namespace MLEM.Input {
// max width splits // max width splits
line++; line++;
} }
} else if (this.visibleText != null) { } else if (this.displayedText != null) {
this.caretLine = 0; this.caretLine = 0;
this.caretPosInLine = this.CaretPos; this.caretPosInLine = this.CaretPos;
this.caretDrawOffset = this.Font.MeasureString(this.visibleText.Substring(0, this.CaretPos - this.textOffset)).X; this.caretDrawOffset = this.Font.MeasureString(this.displayedText.Substring(0, this.CaretPos - this.textOffset)).X;
} }
} }
private (int, int) GetLineBounds(int boundLine) { private (int, int) GetLineBounds(int boundLine) {
if (this.multilineSplitText != null) { if (this.splitText != null) {
var line = 0; var line = 0;
var index = 0; var index = 0;
var startOfLineIndex = 0; var startOfLineIndex = 0;
for (var d = 0; d < this.multilineSplitText.Length; d++) { for (var d = 0; d < this.splitText.Length; d++) {
var split = this.multilineSplitText[d]; var split = this.splitText[d];
for (var i = 0; i < split.Length; i++) { for (var i = 0; i < split.Length; i++) {
index++; index++;
if (split[i] == '\n') { if (split[i] == '\n') {

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible> <IsTrimmable>true</IsTrimmable>
<RootNamespace>MLEM</RootNamespace> <RootNamespace>MLEM</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants> <DefineConstants>$(DefineConstants);FNA</DefineConstants>
</PropertyGroup> </PropertyGroup>
@ -21,7 +21,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\ThirdParty\FNA\FNA.csproj"> <ProjectReference Include="..\FNA\FNA.csproj">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</ProjectReference> </ProjectReference>

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible> <IsTrimmable>true</IsTrimmable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View file

@ -1,5 +1,4 @@
using System; using System;
using Microsoft.Xna.Framework;
namespace MLEM.Misc { namespace MLEM.Misc {
/// <summary> /// <summary>
@ -9,21 +8,6 @@ namespace MLEM.Misc {
/// </summary> /// </summary>
public static class Easings { public static class Easings {
/// <summary>
/// An easing function that constantly returns 0, regardless of the input percentage.
/// This is useful for chaining using <see cref="AndThen(MLEM.Misc.Easings.Easing,MLEM.Misc.Easings.Easing)"/>.
/// </summary>
public static readonly Easing Zero = p => 0;
/// <summary>
/// An easing function that constantly returns 1, regardless of the input percentage.
/// This is useful for chaining using <see cref="AndThen(MLEM.Misc.Easings.Easing,MLEM.Misc.Easings.Easing)"/>.
/// </summary>
public static readonly Easing One = p => 1;
/// <summary>
/// A linear easing function that returns the input percentage without modifying it.
/// </summary>
public static readonly Easing Linear = p => p;
/// <summary>https://easings.net/#easeInSine</summary> /// <summary>https://easings.net/#easeInSine</summary>
public static readonly Easing InSine = p => 1 - (float) Math.Cos(p * Math.PI / 2); public static readonly Easing InSine = p => 1 - (float) Math.Cos(p * Math.PI / 2);
/// <summary>https://easings.net/#easeOutSine</summary> /// <summary>https://easings.net/#easeOutSine</summary>
@ -186,17 +170,6 @@ namespace MLEM.Misc {
}; };
} }
/// <summary>
/// Causes output from the easing function to be clamped between the <paramref name="min"/> and <paramref name="max"/> values passed.
/// </summary>
/// <param name="easing">The easing function to clamp.</param>
/// <param name="min">The minimum output value to clamp to, defaults to 0.</param>
/// <param name="max">The maximum output value to clamp to, defaults to 1.</param>
/// <returns>A clamped easing function.</returns>
public static Easing Clamp(this Easing easing, float min = 0, float max = 1) {
return p => MathHelper.Clamp(easing(p), min, max);
}
/// <summary> /// <summary>
/// A delegate method used by <see cref="Easings"/>. /// A delegate method used by <see cref="Easings"/>.
/// </summary> /// </summary>

View file

@ -1,8 +1,4 @@
using System; namespace MLEM.Misc {
using System.Collections.Generic;
using MLEM.Extensions;
namespace MLEM.Misc {
/// <summary> /// <summary>
/// The SingleRandom class allows generating single, one-off pseudorandom numbers based on a seed or a <see cref="SeedSource"/>. /// The SingleRandom class allows generating single, one-off pseudorandom numbers based on a seed or a <see cref="SeedSource"/>.
/// The types of numbers that can be generated are <see cref="int"/> and <see cref="float"/>, both of which can be generated with specific minimum and maximum values if desired. /// The types of numbers that can be generated are <see cref="int"/> and <see cref="float"/>, both of which can be generated with specific minimum and maximum values if desired.
@ -142,35 +138,5 @@ namespace MLEM.Misc {
return (maxValue - minValue) * SingleRandom.Single(source) + minValue; return (maxValue - minValue) * SingleRandom.Single(source) + minValue;
} }
/// <summary>
/// Gets a random entry from the given collection with uniform chance.
/// </summary>
/// <param name="entries">The entries to choose from</param>
/// <param name="source">The <see cref="SeedSource"/> to use.</param>
/// <typeparam name="T">The entries' type</typeparam>
/// <returns>A random entry</returns>
public static T GetRandomEntry<T>(ICollection<T> entries, SeedSource source) {
return RandomExtensions.GetRandomEntry(entries, SingleRandom.Single(source));
}
/// <summary>
/// Returns a random entry from the given collection based on the specified weight function.
/// A higher weight for an entry increases its likeliness of being picked.
/// </summary>
/// <param name="entries">The entries to choose from</param>
/// <param name="weightFunc">A function that applies weight to each entry</param>
/// <param name="source">The <see cref="SeedSource"/> to use.</param>
/// <typeparam name="T">The entries' type</typeparam>
/// <returns>A random entry, based on the entries' weight</returns>
/// <exception cref="IndexOutOfRangeException">If the weight function returns different weights for the same entry</exception>
public static T GetRandomWeightedEntry<T>(ICollection<T> entries, Func<T, int> weightFunc, SeedSource source) {
return RandomExtensions.GetRandomWeightedEntry(entries, weightFunc, SingleRandom.Single(source));
}
/// <inheritdoc cref="GetRandomWeightedEntry{T}(System.Collections.Generic.ICollection{T},System.Func{T,int},MLEM.Misc.SeedSource)"/>
public static T GetRandomWeightedEntry<T>(ICollection<T> entries, Func<T, float> weightFunc, SeedSource source) {
return RandomExtensions.GetRandomWeightedEntry(entries, weightFunc, SingleRandom.Single(source));
}
} }
} }

View file

@ -158,13 +158,11 @@ namespace MLEM.Textures {
case NinePatchMode.Tile: case NinePatchMode.Tile:
var width = src.Width * patchScale; var width = src.Width * patchScale;
var height = src.Height * patchScale; var height = src.Height * patchScale;
if (width > 0 && height > 0) { for (var x = 0F; x < rect.Width; x += width) {
for (var x = 0F; x < rect.Width; x += width) { for (var y = 0F; y < rect.Height; y += height) {
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));
var size = new Vector2(Math.Min(rect.Width - x, width), Math.Min(rect.Height - y, height)); var srcSize = (size / patchScale).CeilCopy().ToPoint();
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);
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; break;

5
NuGet.config Normal file
View file

@ -0,0 +1,5 @@
<configuration>
<config>
<add key="globalPackagesFolder" value="./packages" />
</config>
</configuration>

View file

@ -11,7 +11,6 @@ MLEM is platform-agnostic and multi-targets .NET Standard 2.0, .NET 6.0 and .NET
- See tutorials and API documentation on [the website](https://mlem.ellpeck.de/) - See tutorials and API documentation on [the website](https://mlem.ellpeck.de/)
- Check out [the demos](https://github.com/Ellpeck/MLEM/tree/main/Demos) on [Desktop](https://github.com/Ellpeck/MLEM/tree/main/Demos.DesktopGL) or [Android](https://github.com/Ellpeck/MLEM/tree/main/Demos.Android) - Check out [the demos](https://github.com/Ellpeck/MLEM/tree/main/Demos) on [Desktop](https://github.com/Ellpeck/MLEM/tree/main/Demos.DesktopGL) or [Android](https://github.com/Ellpeck/MLEM/tree/main/Demos.Android)
- See [the changelog](https://github.com/Ellpeck/MLEM/blob/main/CHANGELOG.md) for information on updates - See [the changelog](https://github.com/Ellpeck/MLEM/blob/main/CHANGELOG.md) for information on updates
- Join [the Discord server](https://link.ellpeck.de/discordweb) to ask questions
# Packages # Packages
- **MLEM** is the base package, which provides various small addons and abstractions for MonoGame and FNA, including a text formatting system and simple input handling - **MLEM** is the base package, which provides various small addons and abstractions for MonoGame and FNA, including a text formatting system and simple input handling

View file

@ -10,6 +10,8 @@
#-------------------------------- References --------------------------------# #-------------------------------- References --------------------------------#
/reference:..\..\packages\monogame.extended.content.pipeline\3.8.0\tools\MonoGame.Extended.Content.Pipeline.dll
#---------------------------------- Content ---------------------------------# #---------------------------------- Content ---------------------------------#
#begin Fonts/Cadman_Roman.otf #begin Fonts/Cadman_Roman.otf
@ -18,6 +20,11 @@
#begin Fonts/Symbola-Emoji.ttf #begin Fonts/Symbola-Emoji.ttf
/copy:Fonts/Symbola-Emoji.ttf /copy:Fonts/Symbola-Emoji.ttf
#begin Fonts/Regular.fnt
/importer:BitmapFontImporter
/processor:BitmapFontProcessor
/build:Fonts/Regular.fnt
#begin Fonts/RegularTexture.png #begin Fonts/RegularTexture.png
/importer:TextureImporter /importer:TextureImporter
/processor:TextureProcessor /processor:TextureProcessor
@ -57,3 +64,5 @@
#begin Textures/Test.png #begin Textures/Test.png
/copy:Textures/Test.png /copy:Textures/Test.png

View file

@ -0,0 +1,330 @@
<?xml version="1.0"?>
<font>
<info face="BitPotionExt" size="-16" bold="0" italic="0" charset="" unicode="1" stretchH="100" smooth="0" aa="1" padding="0,0,0,0" spacing="1,1" outline="0"/>
<common lineHeight="14" base="11" scaleW="128" scaleH="128" pages="1" packed="0" alphaChnl="0" redChnl="4" greenChnl="4" blueChnl="4"/>
<pages>
<page id="0" file="RegularTexture.png" />
</pages>
<chars count="320">
<char id="32" x="41" y="106" width="3" height="1" xoffset="-1" yoffset="13" xadvance="5" page="0" chnl="15" />
<char id="33" x="10" y="95" width="1" height="7" xoffset="0" yoffset="4" xadvance="2" page="0" chnl="15" />
<char id="34" x="124" y="98" width="3" height="2" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="35" x="12" y="95" width="7" height="6" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
<char id="36" x="35" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="37" x="45" y="93" width="4" height="6" xoffset="0" yoffset="5" xadvance="5" page="0" chnl="15" />
<char id="38" x="121" y="51" width="6" height="7" xoffset="0" yoffset="4" xadvance="7" page="0" chnl="15" />
<char id="39" x="13" y="109" width="1" height="2" xoffset="0" yoffset="4" xadvance="2" page="0" chnl="15" />
<char id="40" x="125" y="83" width="2" height="7" xoffset="0" yoffset="4" xadvance="3" page="0" chnl="15" />
<char id="41" x="125" y="75" width="2" height="7" xoffset="0" yoffset="4" xadvance="3" page="0" chnl="15" />
<char id="42" x="39" y="93" width="5" height="6" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="43" x="92" y="92" width="5" height="5" xoffset="0" yoffset="6" xadvance="6" page="0" chnl="15" />
<char id="44" x="109" y="98" width="2" height="3" xoffset="0" yoffset="10" xadvance="3" page="0" chnl="15" />
<char id="45" x="27" y="106" width="4" height="1" xoffset="0" yoffset="8" xadvance="5" page="0" chnl="15" />
<char id="46" x="49" y="106" width="1" height="1" xoffset="0" yoffset="10" xadvance="2" page="0" chnl="15" />
<char id="47" x="97" y="60" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="48" x="40" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="49" x="109" y="84" width="3" height="7" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="50" x="45" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="51" x="50" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="52" x="55" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="53" x="65" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="54" x="70" y="84" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="55" x="80" y="84" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="56" x="33" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="57" x="43" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="58" x="81" y="98" width="1" height="5" xoffset="0" yoffset="6" xadvance="2" page="0" chnl="15" />
<char id="59" x="0" y="95" width="2" height="7" xoffset="0" yoffset="6" xadvance="3" page="0" chnl="15" />
<char id="60" x="30" y="100" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="61" x="104" y="98" width="4" height="3" xoffset="0" yoffset="7" xadvance="5" page="0" chnl="15" />
<char id="62" x="50" y="100" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="63" x="48" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="64" x="41" y="61" width="6" height="7" xoffset="0" yoffset="4" xadvance="7" page="0" chnl="15" />
<char id="65" x="53" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="66" x="58" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="67" x="63" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="68" x="68" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="69" x="73" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="70" x="78" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="71" x="83" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="72" x="30" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="73" x="6" y="95" width="1" height="7" xoffset="0" yoffset="4" xadvance="2" page="0" chnl="15" />
<char id="74" x="88" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="75" x="98" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="76" x="103" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="77" x="6" y="71" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="78" x="108" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="79" x="113" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="80" x="118" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="81" x="123" y="67" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="82" x="0" y="79" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="83" x="5" y="79" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="84" x="93" y="84" width="3" height="7" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="85" x="10" y="79" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="86" x="15" y="78" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="87" x="91" y="60" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="88" x="20" y="77" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="89" x="97" y="84" width="3" height="7" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="90" x="25" y="77" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="91" x="121" y="32" width="2" height="9" xoffset="0" yoffset="4" xadvance="3" page="0" chnl="15" />
<char id="92" x="61" y="61" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="93" x="109" y="32" width="2" height="9" xoffset="0" yoffset="4" xadvance="3" page="0" chnl="15" />
<char id="94" x="0" y="109" width="3" height="2" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="95" x="32" y="106" width="4" height="1" xoffset="0" yoffset="12" xadvance="5" page="0" chnl="15" />
<char id="96" x="4" y="109" width="2" height="2" xoffset="0" yoffset="4" xadvance="3" page="0" chnl="15" />
<char id="97" x="40" y="100" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="98" x="30" y="77" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="99" x="77" y="98" width="3" height="5" xoffset="0" yoffset="6" xadvance="4" page="0" chnl="15" />
<char id="100" x="35" y="77" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="101" x="35" y="100" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="102" x="113" y="84" width="3" height="7" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="103" x="40" y="77" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="104" x="50" y="77" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="105" x="8" y="95" width="1" height="7" xoffset="0" yoffset="4" xadvance="2" page="0" chnl="15" />
<char id="106" x="115" y="32" width="2" height="9" xoffset="-1" yoffset="4" xadvance="2" page="0" chnl="15" />
<char id="107" x="73" y="98" width="3" height="5" xoffset="0" yoffset="6" xadvance="4" page="0" chnl="15" />
<char id="108" x="3" y="95" width="2" height="7" xoffset="0" yoffset="4" xadvance="3" page="0" chnl="15" />
<char id="109" x="73" y="92" width="6" height="5" xoffset="0" yoffset="6" xadvance="7" page="0" chnl="15" />
<char id="110" x="86" y="92" width="5" height="5" xoffset="0" yoffset="6" xadvance="6" page="0" chnl="15" />
<char id="111" x="20" y="100" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="112" x="105" y="76" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="113" x="55" y="77" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="114" x="0" y="103" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="115" x="121" y="92" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="116" x="116" y="92" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="117" x="10" y="103" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="118" x="5" y="103" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="119" x="80" y="92" width="5" height="5" xoffset="0" yoffset="6" xadvance="6" page="0" chnl="15" />
<char id="120" x="69" y="99" width="3" height="5" xoffset="0" yoffset="6" xadvance="4" page="0" chnl="15" />
<char id="121" x="60" y="77" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="122" x="25" y="100" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="123" x="30" y="33" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="124" x="0" y="44" width="1" height="9" xoffset="0" yoffset="4" xadvance="2" page="0" chnl="15" />
<char id="125" x="80" y="32" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="126" x="112" y="98" width="6" height="2" xoffset="0" yoffset="7" xadvance="7" page="0" chnl="15" />
<char id="160" x="37" y="106" width="3" height="1" xoffset="-1" yoffset="13" xadvance="5" page="0" chnl="15" />
<char id="161" x="124" y="32" width="1" height="9" xoffset="0" yoffset="4" xadvance="2" page="0" chnl="15" />
<char id="162" x="70" y="76" width="4" height="7" xoffset="0" yoffset="5" xadvance="5" page="0" chnl="15" />
<char id="163" x="75" y="76" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="164" x="88" y="98" width="4" height="4" xoffset="0" yoffset="5" xadvance="5" page="0" chnl="15" />
<char id="165" x="109" y="60" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="166" x="126" y="32" width="1" height="9" xoffset="0" yoffset="4" xadvance="2" page="0" chnl="15" />
<char id="167" x="85" y="76" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="168" x="45" y="106" width="3" height="1" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="169" x="2" y="44" width="7" height="8" xoffset="0" yoffset="3" xadvance="8" page="0" chnl="15" />
<char id="170" x="45" y="100" width="4" height="5" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="171" x="104" y="92" width="5" height="5" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
<char id="172" x="119" y="98" width="4" height="2" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="174" x="10" y="43" width="7" height="8" xoffset="0" yoffset="3" xadvance="8" page="0" chnl="15" />
<char id="175" x="22" y="106" width="4" height="1" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="176" x="83" y="98" width="4" height="4" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="177" x="27" y="93" width="5" height="6" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
<char id="178" x="55" y="99" width="4" height="5" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="179" x="60" y="99" width="4" height="5" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="180" x="10" y="109" width="2" height="2" xoffset="1" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="181" x="90" y="76" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="182" x="82" y="22" width="5" height="9" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="183" x="7" y="109" width="2" height="2" xoffset="1" yoffset="8" xadvance="4" page="0" chnl="15" />
<char id="184" x="101" y="98" width="2" height="4" xoffset="1" yoffset="10" xadvance="4" page="0" chnl="15" />
<char id="185" x="97" y="98" width="3" height="4" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="186" x="15" y="102" width="4" height="5" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="187" x="110" y="92" width="5" height="5" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
<char id="188" x="68" y="22" width="7" height="9" xoffset="0" yoffset="2" xadvance="8" page="0" chnl="15" />
<char id="189" x="60" y="22" width="7" height="9" xoffset="0" yoffset="2" xadvance="8" page="0" chnl="15" />
<char id="190" x="8" y="0" width="7" height="10" xoffset="0" yoffset="1" xadvance="8" page="0" chnl="15" />
<char id="191" x="95" y="76" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="192" x="10" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="193" x="15" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="194" x="45" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="195" x="55" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="196" x="5" y="34" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="197" x="65" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="198" x="0" y="63" width="8" height="7" xoffset="0" yoffset="4" xadvance="9" page="0" chnl="15" />
<char id="199" x="15" y="33" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="200" x="80" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="201" x="85" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="202" x="100" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="203" x="20" y="33" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="204" x="125" y="11" width="2" height="10" xoffset="0" yoffset="1" xadvance="3" page="0" chnl="15" />
<char id="205" x="57" y="22" width="2" height="10" xoffset="1" yoffset="1" xadvance="4" page="0" chnl="15" />
<char id="206" x="42" y="22" width="3" height="10" xoffset="0" yoffset="1" xadvance="4" page="0" chnl="15" />
<char id="207" x="120" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="208" x="0" y="71" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="209" x="0" y="23" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="210" x="5" y="23" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="211" x="15" y="22" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="212" x="20" y="22" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="213" x="28" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="214" x="60" y="32" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="215" x="65" y="99" width="3" height="5" xoffset="1" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="216" x="18" y="61" width="7" height="7" xoffset="0" yoffset="4" xadvance="8" page="0" chnl="15" />
<char id="217" x="53" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="218" x="58" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="219" x="118" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="220" x="25" y="33" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="221" x="38" y="22" width="3" height="10" xoffset="0" yoffset="1" xadvance="4" page="0" chnl="15" />
<char id="222" x="50" y="93" width="4" height="6" xoffset="0" yoffset="5" xadvance="5" page="0" chnl="15" />
<char id="223" x="60" y="52" width="4" height="8" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="224" x="108" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="225" x="93" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="226" x="78" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="227" x="55" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="228" x="110" y="76" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="229" x="80" y="51" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="230" x="64" y="93" width="8" height="5" xoffset="0" yoffset="6" xadvance="9" page="0" chnl="15" />
<char id="231" x="117" y="84" width="3" height="7" xoffset="0" yoffset="6" xadvance="4" page="0" chnl="15" />
<char id="232" x="98" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="233" x="103" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="234" x="35" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="235" x="115" y="76" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="236" x="118" y="51" width="2" height="8" xoffset="0" yoffset="3" xadvance="3" page="0" chnl="15" />
<char id="237" x="115" y="51" width="2" height="8" xoffset="1" yoffset="3" xadvance="4" page="0" chnl="15" />
<char id="238" x="107" y="51" width="3" height="8" xoffset="0" yoffset="3" xadvance="4" page="0" chnl="15" />
<char id="239" x="85" y="84" width="3" height="7" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="240" x="90" y="51" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="241" x="67" y="42" width="5" height="8" xoffset="0" yoffset="3" xadvance="6" page="0" chnl="15" />
<char id="242" x="88" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="243" x="123" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="244" x="0" y="54" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="245" x="20" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="246" x="120" y="76" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="247" x="98" y="92" width="5" height="5" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
<char id="248" x="20" y="93" width="6" height="6" xoffset="0" yoffset="5" xadvance="7" page="0" chnl="15" />
<char id="249" x="25" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="250" x="50" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="251" x="75" y="51" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="252" x="0" y="87" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="253" x="63" y="0" width="4" height="10" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="254" x="68" y="0" width="4" height="10" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="255" x="108" y="22" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="256" x="50" y="33" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="257" x="5" y="87" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="258" x="83" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="259" x="118" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="260" x="35" y="33" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="261" x="10" y="87" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="262" x="0" y="12" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="263" x="99" y="51" width="3" height="8" xoffset="0" yoffset="3" xadvance="4" page="0" chnl="15" />
<char id="264" x="123" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="265" x="103" y="51" width="3" height="8" xoffset="0" yoffset="3" xadvance="4" page="0" chnl="15" />
<char id="266" x="98" y="22" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="267" x="121" y="84" width="3" height="7" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="268" x="113" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="269" x="95" y="51" width="3" height="8" xoffset="0" yoffset="3" xadvance="4" page="0" chnl="15" />
<char id="270" x="103" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="271" x="26" y="61" width="7" height="7" xoffset="0" yoffset="4" xadvance="8" page="0" chnl="15" />
<char id="272" x="121" y="59" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="273" x="61" y="42" width="5" height="8" xoffset="0" yoffset="3" xadvance="6" page="0" chnl="15" />
<char id="274" x="40" y="33" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="275" x="15" y="86" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="276" x="48" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="277" x="83" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="278" x="85" y="32" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="279" x="20" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="280" x="70" y="32" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="281" x="25" y="85" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="282" x="38" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="283" x="70" y="51" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="284" x="33" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="285" x="10" y="22" width="4" height="10" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="286" x="95" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="287" x="40" y="11" width="4" height="10" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="288" x="0" y="34" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="289" x="123" y="22" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="290" x="118" y="22" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="291" x="88" y="0" width="4" height="10" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="292" x="108" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="293" x="93" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="294" x="34" y="61" width="6" height="7" xoffset="0" yoffset="4" xadvance="7" page="0" chnl="15" />
<char id="295" x="49" y="43" width="5" height="8" xoffset="0" yoffset="3" xadvance="6" page="0" chnl="15" />
<char id="296" x="78" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="297" x="5" y="53" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="298" x="94" y="32" width="3" height="9" xoffset="0" yoffset="2" xadvance="4" page="0" chnl="15" />
<char id="299" x="101" y="84" width="3" height="7" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="300" x="30" y="22" width="3" height="10" xoffset="0" yoffset="1" xadvance="4" page="0" chnl="15" />
<char id="301" x="111" y="51" width="3" height="8" xoffset="0" yoffset="3" xadvance="4" page="0" chnl="15" />
<char id="302" x="112" y="32" width="2" height="9" xoffset="0" yoffset="4" xadvance="3" page="0" chnl="15" />
<char id="303" x="118" y="32" width="2" height="9" xoffset="0" yoffset="4" xadvance="3" page="0" chnl="15" />
<char id="304" x="106" y="32" width="2" height="9" xoffset="1" yoffset="2" xadvance="4" page="0" chnl="15" />
<char id="305" x="126" y="91" width="1" height="5" xoffset="1" yoffset="6" xadvance="3" page="0" chnl="15" />
<char id="306" x="48" y="61" width="6" height="7" xoffset="1" yoffset="4" xadvance="8" page="0" chnl="15" />
<char id="307" x="90" y="32" width="3" height="9" xoffset="1" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="308" x="16" y="0" width="5" height="10" xoffset="0" yoffset="1" xadvance="6" page="0" chnl="15" />
<char id="309" x="50" y="22" width="3" height="10" xoffset="0" yoffset="3" xadvance="4" page="0" chnl="15" />
<char id="310" x="10" y="33" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="311" x="89" y="84" width="3" height="7" xoffset="0" yoffset="6" xadvance="4" page="0" chnl="15" />
<char id="312" x="93" y="98" width="3" height="4" xoffset="0" yoffset="7" xadvance="4" page="0" chnl="15" />
<char id="313" x="50" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="314" x="5" y="0" width="2" height="11" xoffset="0" yoffset="0" xadvance="3" page="0" chnl="15" />
<char id="315" x="103" y="22" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="316" x="54" y="22" width="2" height="10" xoffset="0" yoffset="3" xadvance="3" page="0" chnl="15" />
<char id="317" x="18" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="318" x="23" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="319" x="28" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="320" x="105" y="84" width="3" height="7" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="321" x="79" y="60" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="322" x="38" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="323" x="98" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="324" x="25" y="43" width="5" height="8" xoffset="0" yoffset="3" xadvance="6" page="0" chnl="15" />
<char id="325" x="113" y="22" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="326" x="85" y="60" width="5" height="7" xoffset="0" yoffset="6" xadvance="6" page="0" chnl="15" />
<char id="327" x="73" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="328" x="37" y="43" width="5" height="8" xoffset="0" yoffset="3" xadvance="6" page="0" chnl="15" />
<char id="329" x="18" y="43" width="6" height="8" xoffset="0" yoffset="3" xadvance="7" page="0" chnl="15" />
<char id="330" x="73" y="60" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="331" x="67" y="60" width="5" height="7" xoffset="0" yoffset="6" xadvance="6" page="0" chnl="15" />
<char id="332" x="93" y="22" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="333" x="93" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="334" x="43" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="335" x="40" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="336" x="5" y="12" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="337" x="45" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="338" x="9" y="62" width="8" height="7" xoffset="0" yoffset="4" xadvance="9" page="0" chnl="15" />
<char id="339" x="55" y="93" width="8" height="5" xoffset="0" yoffset="6" xadvance="9" page="0" chnl="15" />
<char id="340" x="20" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="341" x="55" y="43" width="5" height="8" xoffset="0" yoffset="3" xadvance="6" page="0" chnl="15" />
<char id="342" x="45" y="33" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="343" x="115" y="60" width="5" height="7" xoffset="0" yoffset="6" xadvance="6" page="0" chnl="15" />
<char id="344" x="25" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="345" x="43" y="43" width="5" height="8" xoffset="0" yoffset="3" xadvance="6" page="0" chnl="15" />
<char id="346" x="30" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="347" x="85" y="51" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="348" x="35" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="349" x="60" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="350" x="55" y="33" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="351" x="45" y="77" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="352" x="70" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="353" x="65" y="51" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="354" x="102" y="32" width="3" height="9" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="355" x="65" y="77" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="356" x="46" y="22" width="3" height="10" xoffset="0" yoffset="1" xadvance="4" page="0" chnl="15" />
<char id="357" x="33" y="93" width="5" height="6" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
<char id="358" x="55" y="61" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="359" x="80" y="76" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="360" x="25" y="22" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="361" x="73" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="362" x="75" y="32" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="363" x="100" y="76" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="364" x="75" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="365" x="15" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="366" x="0" y="0" width="4" height="11" xoffset="0" yoffset="0" xadvance="5" page="0" chnl="15" />
<char id="367" x="65" y="32" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="368" x="90" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="369" x="113" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="370" x="76" y="22" width="5" height="9" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="371" x="103" y="60" width="5" height="7" xoffset="0" yoffset="6" xadvance="6" page="0" chnl="15" />
<char id="372" x="22" y="0" width="5" height="10" xoffset="0" yoffset="1" xadvance="6" page="0" chnl="15" />
<char id="373" x="31" y="43" width="5" height="8" xoffset="0" yoffset="3" xadvance="6" page="0" chnl="15" />
<char id="374" x="34" y="22" width="3" height="10" xoffset="0" yoffset="1" xadvance="4" page="0" chnl="15" />
<char id="375" x="105" y="11" width="4" height="10" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="376" x="98" y="32" width="3" height="9" xoffset="0" yoffset="2" xadvance="4" page="0" chnl="15" />
<char id="377" x="110" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="378" x="10" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="379" x="88" y="22" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="380" x="60" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="381" x="115" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="382" x="30" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="956" x="75" y="84" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="8364" x="12" y="70" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="12288" x="15" y="108" width="6" height="1" xoffset="-2" yoffset="13" xadvance="16" page="0" chnl="15" />
</chars>
</font>

View file

@ -0,0 +1,274 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.2" tiledversion="1.3.1" orientation="orthogonal" renderorder="right-down" compressionlevel="0" width="50" height="50" tilewidth="16" tileheight="16" infinite="0" nextlayerid="7" nextobjectid="32">
<tileset firstgid="1" source="Tileset.tsx" />
<layer id="1" name="Ground" width="50" height="50">
<data encoding="csv">
450,450,450,450,450,451,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,71,102,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,450,450,451,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,450,420,483,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,164,40,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,450,451,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,450,451,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,450,451,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,420,483,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,100,72,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,451,3,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,100,72,133,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,451,3,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,100,72,133,133,39,166,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,420,483,3,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,100,72,133,133,39,166,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,451,3,3,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,133,39,166,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,452,419,3,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,451,3,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,452,419,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,39,166,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,450,451,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,450,451,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,450,451,3,3,36,66,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
450,450,450,420,483,3,3,68,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,35,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,
450,450,450,451,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,34,69,69,69,69,69,69,69,69,69,69,69,69,69,
450,450,450,452,419,3,3,3,3,3,100,72,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,417,418,419,3,3,3,3,36,37,38,3,3,3,3,3,3,3,3,3,3,3,417,418,
450,450,450,450,451,3,3,3,3,3,132,133,133,134,3,3,3,3,3,3,3,3,3,3,3,3,417,453,450,451,3,3,3,3,36,37,38,3,3,3,3,3,3,3,3,3,3,417,453,2684355010,
450,450,450,450,451,3,3,3,3,3,132,133,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,3,3,3,3,3,3,3,3,3,417,453,2684355010,2684355010,
450,450,450,450,451,3,3,3,3,3,164,40,133,71,102,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,3,3,3,3,3,3,3,3,3,449,2684355010,2684355010,2684355010,
450,450,450,420,483,3,3,3,3,3,3,132,133,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,3,3,3,3,3,3,3,3,417,453,2684355010,2684355010,2684355010,
450,450,450,451,3,3,3,3,3,3,3,164,40,133,71,102,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,3,3,3,3,3,3,3,417,453,2684355010,2684355010,2684355010,2684355010,
450,450,420,483,3,3,3,3,3,3,3,3,132,133,133,71,102,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,3,3,3,3,3,3,3,449,2684355010,2684355010,2684355010,2684355010,2684355010,
450,420,483,3,3,3,3,3,3,3,3,3,164,40,133,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,3,417,418,418,419,3,3,449,2684355010,2684355010,2684355010,2684355010,2684355010,
450,451,3,3,3,3,3,3,3,3,3,3,3,164,40,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,3,449,2684355010,2684355010,452,419,3,449,2684355010,2684355010,2684355010,2684355010,2684355010,
450,451,3,3,3,3,3,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,417,453,2684355010,2684355010,2684355010,451,3,449,2684355010,2684355010,2684355010,2684355010,2684355010,
450,451,3,3,3,3,3,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,449,2684355010,2684355010,2684355010,2684355010,451,3,449,2684355010,2684355010,2684355010,2684355010,2684355010,
450,452,419,3,3,3,3,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,36,37,38,3,3,3,3,3,3,3,36,37,38,449,2684355010,2684355010,2684355010,2684355010,451,3,481,482,421,2684355010,2684355010,2684355010,
450,450,451,3,3,3,3,3,3,3,3,3,3,3,132,133,71,102,3,100,101,101,101,101,36,37,66,5,5,5,5,5,5,5,67,37,38,449,2684355010,2684355010,2684355010,2684355010,451,3,3,3,481,482,482,482,
450,450,451,3,3,3,3,3,3,3,3,3,3,100,72,133,133,71,101,72,133,133,133,133,36,37,37,37,37,37,37,37,37,37,37,37,38,481,421,2684355010,2684355010,420,483,3,3,3,3,16,16,16,
450,450,451,3,3,3,3,3,3,3,3,3,100,72,133,133,133,133,133,133,133,39,165,165,68,69,69,69,69,69,69,69,69,69,69,69,70,3,481,482,482,483,3,3,16,16,16,16,16,16,
450,450,451,3,3,3,132,133,71,101,101,101,72,133,133,133,39,165,165,165,165,166,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,16,16,16,16,16,16,16,
450,450,451,3,3,3,132,133,133,133,133,133,133,133,39,165,166,3,3,3,3,3,3,3,417,418,418,418,418,418,418,418,418,418,418,419,3,3,3,3,3,16,16,16,16,16,16,16,16,16,
450,450,452,419,3,3,164,165,165,165,165,165,165,165,166,3,3,3,3,3,3,3,3,417,453,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,451,3,3,3,3,16,16,16,16,16,16,16,16,16,16,
450,450,450,451,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,481,421,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,420,483,3,16,16,16,16,16,16,16,16,16,16,16,16,16,
450,450,450,451,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,481,482,482,482,482,482,482,482,482,482,483,3,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
450,450,450,451,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
450,450,450,451,3,3,3,3,3,3,3,3,3,3,3,3,3,3,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,
450,450,450,451,3,3,3,3,3,3,3,3,3,3,3,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,
2684355010,450,420,483,3,3,3,3,3,3,3,3,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,3,3,
2684355010,420,483,3,3,3,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,3,3,3,
482,483,3,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,3,3,3,3,3,
3,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,3,3,3,3,3,3,3,3,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,3,3,3,3,3,3,3,3,3,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3
</data>
</layer>
<layer id="2" name="Ground1" width="50" height="50">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,17,17,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,17,17,18,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,17,18,19,19,54,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,18,19,19,54,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,17,18,54,0,0,0,21,83,83,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,18,19,54,0,0,21,83,84,17,17,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,17,17,18,54,0,0,21,83,84,17,17,17,17,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,18,19,54,0,0,21,84,17,17,17,17,17,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,18,19,54,0,0,0,21,84,17,17,17,17,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,17,17,17,18,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,54,0,21,83,83,83,84,17,17,17,0,0,0,0,0,
0,0,0,0,0,0,17,17,17,17,17,17,17,17,17,17,17,18,19,54,0,0,0,0,0,0,0,0,0,0,0,0,0,21,83,83,83,84,17,17,17,17,17,17,0,0,0,0,0,0,
0,0,0,17,17,17,17,17,17,17,17,17,17,17,18,19,19,54,0,0,0,0,0,0,0,0,21,83,83,83,83,83,83,84,17,17,17,17,17,17,17,17,0,0,0,0,0,0,0,0,
0,17,17,17,17,17,17,17,17,17,17,18,19,19,54,0,0,0,21,83,83,83,83,83,83,83,84,17,17,17,17,17,17,17,17,17,17,17,17,0,0,0,0,0,0,0,0,0,0,0,
17,17,17,17,17,17,18,19,19,19,19,54,0,0,21,83,83,83,84,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,0,0,0,0,0,0,0,0,0,0,0,0,
17,17,18,19,19,19,54,0,0,0,21,83,83,83,84,17,17,17,17,17,17,17,17,17,17,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
19,19,54,0,0,0,21,83,83,83,84,17,17,17,17,17,17,17,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,21,84,17,17,17,17,17,17,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<layer id="3" name="Objects" width="50" height="50">
<data encoding="csv">
0,0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,365,430,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,237,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,368,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,368,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,237,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,237,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,303,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,366,366,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,301,302,0,0,0,0,0,0,0,0,0,0,0,0,0,0,397,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,397,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,303,0,0,0,0,199,198,198,198,198,198,199,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,368,0,0,
0,365,366,366,430,0,0,0,0,0,0,0,0,0,0,0,0,0,197,198,198,198,199,198,198,198,198,198,199,198,198,198,200,0,0,0,0,0,0,0,303,0,0,0,0,0,0,397,0,0,
0,397,0,301,302,0,0,0,0,0,0,0,0,0,0,0,0,0,197,198,198,198,199,198,198,198,198,198,199,198,198,198,200,0,0,0,0,0,237,0,0,0,0,0,0,0,0,397,0,0,
0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,265,266,266,266,267,266,266,266,266,266,267,266,266,266,268,0,0,0,0,0,111,0,0,0,0,0,0,0,0,397,0,0,
0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,197,198,198,198,199,198,198,198,198,198,199,198,198,198,200,0,0,0,0,0,77,0,0,0,0,0,0,365,366,430,0,0,
0,397,0,0,0,0,0,0,0,0,0,301,302,0,0,0,0,0,197,198,198,198,199,198,198,198,198,198,199,198,198,198,200,0,0,0,0,0,77,0,0,0,0,0,0,397,0,0,303,0,
303,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,265,266,266,266,267,266,266,290,266,266,267,266,266,266,268,0,0,0,0,0,77,0,0,0,0,0,0,397,0,0,0,0,
0,368,0,193,194,195,194,194,194,195,194,196,0,0,0,0,0,0,197,198,198,198,199,198,198,322,198,198,199,198,198,198,200,0,0,0,0,0,77,0,0,0,0,0,0,397,0,0,0,0,
0,397,0,193,194,195,194,194,194,195,194,196,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,77,0,301,302,0,0,0,397,0,0,0,0,
0,397,0,193,194,195,194,194,194,195,194,196,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,77,0,0,0,0,0,0,397,0,0,0,0,
0,397,0,193,194,195,194,290,194,195,194,196,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,77,0,0,0,0,0,0,399,0,97,98,98,
0,397,0,193,194,195,194,322,194,195,194,196,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,77,0,0,0,0,0,97,98,98,74,0,0,
0,397,301,225,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,77,0,0,0,0,97,74,0,0,0,0,0,
0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,237,109,46,46,46,46,46,46,46,46,46,46,46,46,46,110,0,0,97,98,74,0,0,0,0,0,0,
0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,303,0,0,0,0,0,0,301,302,0,0,0,0,0,97,74,0,0,0,0,0,0,0,0,
0,429,366,367,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,98,98,74,0,0,0,0,0,0,0,0,0,
0,0,0,397,0,0,0,0,0,0,0,0,301,302,0,0,0,0,0,0,0,301,302,0,0,0,0,0,0,0,0,0,0,0,0,0,97,74,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,98,98,74,0,0,0,0,0,0,0,0,0,0,0,41,162,
0,301,302,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,98,98,98,98,98,98,98,98,98,98,98,98,98,98,74,0,0,0,0,0,0,0,0,0,0,0,0,41,162,163,0,
0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,97,98,98,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,162,163,0,0,0,
0,0,0,397,0,0,0,0,0,0,0,0,97,98,98,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,163,0,0,0,0,0,
0,0,0,399,0,303,97,98,98,98,98,98,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,162,163,0,0,0,301,302,0,
0,0,0,97,98,98,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,162,162,163,0,0,0,0,0,0,0,0,
0,97,98,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,163,0,0,0,0,0,0,0,0,0,0,0,
98,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,162,162,162,162,162,162,162,162,162,162,162,163,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,162,162,162,162,162,162,163,0,0,301,302,0,0,0,0,0,0,0,0,303,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,41,162,162,162,162,163,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,303,0,0,0,0,
0,0,0,0,0,0,0,0,41,162,162,162,162,163,0,0,0,0,0,0,0,303,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<layer id="4" name="Objects1" width="50" height="50">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,237,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,7,7,7,7,7,7,365,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,298,299,300,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,330,331,332,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,362,363,364,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,385,386,387,0,354,0,0,0,354,0,385,386,387,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,104,105,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,385,386,387,0,0,135,0,137,0,0,385,386,387,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,135,0,137,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,385,386,387,0,0,0,0,0,0,0,0,0,229,301,302,230,231,230,230,230,230,230,231,303,230,230,232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,257,258,259,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,354,0,289,0,291,0,354,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,289,0,291,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,302,226,227,226,226,226,227,226,228,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<layer id="6" name="Above" width="50" height="50">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,333,335,173,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,205,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,336,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,336,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,173,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,205,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,173,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,173,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,205,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,205,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,271,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,333,334,334,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,261,263,264,0,0,0,0,269,270,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,261,262,262,262,262,293,327,296,262,262,262,262,264,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,271,261,293,326,326,326,326,325,327,328,326,326,326,326,296,264,0,0,0,0,0,0,0,0,0,0,0,0,0,0,336,0,0,
0,333,334,334,335,0,0,0,0,0,0,0,0,0,0,0,0,292,293,325,326,326,326,326,325,327,328,326,326,326,326,328,296,297,0,0,0,0,173,0,271,0,0,0,0,0,0,0,0,0,
0,0,0,269,270,0,0,0,0,0,0,0,0,0,0,0,0,356,325,325,326,326,326,326,357,359,360,326,326,326,326,328,328,361,0,0,0,0,205,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,356,325,357,358,358,358,358,389,0,392,358,358,358,358,360,328,361,0,0,0,0,79,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,356,357,389,0,0,0,0,0,0,0,0,0,0,0,392,360,361,0,0,0,0,0,0,0,0,0,0,0,333,334,335,0,0,
0,0,0,0,0,261,262,262,262,264,0,269,270,0,0,0,0,388,389,0,0,0,0,0,0,0,0,0,0,0,0,0,392,393,0,0,0,0,0,0,0,0,0,0,0,0,0,0,271,0,
271,0,0,261,262,293,326,326,326,296,262,264,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,336,292,293,326,325,326,326,326,328,326,296,297,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,356,325,326,357,358,358,358,360,326,328,361,0,0,0,0,0,0,269,270,0,0,0,0,0,0,0,0,271,0,0,0,0,0,0,0,0,0,0,269,270,0,0,0,0,0,0,0,0,
0,0,356,357,358,389,0,0,0,392,358,360,361,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,388,389,0,0,0,0,0,0,0,392,393,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,43,43,43,43,43,43,0,0,0,0,0,0,0,0,336,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,8,2147483656,9,1073741833,3221225545,43,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,269,270,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,173,0,0,0,0,0,0,43,43,43,43,43,43,43,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,205,13,14,14,14,14,14,14,14,14,14,14,14,14,14,15,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,271,0,0,0,0,0,0,269,270,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,333,334,335,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,269,270,0,0,0,0,0,0,0,269,270,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,269,270,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,336,0,271,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,269,270,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,269,270,0,0,0,0,0,0,0,0,271,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,271,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,271,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
</map>

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View file

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.2" tiledversion="1.3.1" name="TownTiles" tilewidth="16" tileheight="16" tilecount="512" columns="32">
<image source="Tiles.png" width="512" height="256" />
<tile id="3">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="4">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="5">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="7">
<objectgroup draworder="index" id="5">
<object id="4" x="8" y="3" width="5" height="10" />
</objectgroup>
</tile>
<tile id="8">
<objectgroup draworder="index" id="2">
<object id="1" x="3" y="2" width="10" height="5" />
</objectgroup>
</tile>
<tile id="15">
<animation>
<frame tileid="15" duration="1000" />
<frame tileid="47" duration="1000" />
</animation>
</tile>
<tile id="33">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="34">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="35">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="36">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="37">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="65">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="66">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="67">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="68">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="69">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="72">
<objectgroup draworder="index" id="2">
<object id="1" x="12" y="0" width="4" height="4" />
</objectgroup>
</tile>
<tile id="132">
<properties>
<property name="Walkability" type="int" value="60" />
</properties>
</tile>
<tile id="172">
<properties>
<property name="LightColor" type="color" value="#ffaa6300" />
<property name="LightRadius" type="float" value="5.5" />
<property name="LightType" value="Lamp" />
</properties>
</tile>
<tile id="289">
<properties>
<property name="LightColor" type="color" value="#ff676e73" />
<property name="LightRadius" type="float" value="1.5" />
<property name="LightType" value="Window" />
</properties>
</tile>
<tile id="353">
<properties>
<property name="LightColor" type="color" value="#ff676e73" />
<property name="LightRadius" type="float" value="2.5" />
<property name="LightType" value="Window" />
</properties>
</tile>
<tile id="385">
<properties>
<property name="LightColor" type="color" value="#ff676e73" />
<property name="LightRadius" type="float" value="2.5" />
<property name="LightType" value="Window" />
</properties>
</tile>
</tileset>

View file

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
@ -19,11 +19,16 @@
<PackageReference Include="MonoGame.Extended.Content.Pipeline" Version="3.8.0" /> <PackageReference Include="MonoGame.Extended.Content.Pipeline" Version="3.8.0" />
<PackageReference Include="MonoGame.Extended.Tiled" Version="3.8.0" /> <PackageReference Include="MonoGame.Extended.Tiled" Version="3.8.0" />
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" /> <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
<PackageReference Include="FontStashSharp.MonoGame" Version="1.3.3" /> <PackageReference Include="FontStashSharp.MonoGame" Version="1.2.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Content\Fonts\Symbola-Emoji.ttf" /> <Content Include="Content\Fonts\Symbola-Emoji.ttf" />
</ItemGroup> </ItemGroup>
<Target Name="RestoreDotnetTools" BeforeTargets="Restore">
<Message Text="Restoring dotnet tools" Importance="High" />
<Exec Command="dotnet tool restore" />
</Target>
</Project> </Project>

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<VSTestLogger>nunit</VSTestLogger> <VSTestLogger>nunit</VSTestLogger>
<VSTestResultsDirectory>TestResults.FNA</VSTestResultsDirectory> <VSTestResultsDirectory>TestResults.FNA</VSTestResultsDirectory>
<RunSettingsFilePath>Tests.FNA.runsettings</RunSettingsFilePath> <RunSettingsFilePath>Tests.FNA.runsettings</RunSettingsFilePath>
@ -18,21 +18,24 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\ThirdParty\FNA\FNA.Core.csproj" /> <ProjectReference Include="..\FNA\FNA.Core.csproj" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="coverlet.collector" Version="6.0.0" /> <PackageReference Include="coverlet.collector" Version="3.2.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> <PrivateAssets>all</PrivateAssets>
<PackageReference Include="NUnit" Version="3.14.0" /> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" /> </PackageReference>
<PackageReference Include="NunitXml.TestLogger" Version="3.1.15" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
<PackageReference Include="NunitXml.TestLogger" Version="3.0.127" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Content/**"> <Content Include="Content/**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="../ThirdParty/Native/**"> <Content Include="../FnaNative/**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>%(Filename)%(Extension)</Link> <Link>%(Filename)%(Extension)</Link>
</Content> </Content>

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<VSTestLogger>nunit</VSTestLogger> <VSTestLogger>nunit</VSTestLogger>
<VSTestResultsDirectory>TestResults</VSTestResultsDirectory> <VSTestResultsDirectory>TestResults</VSTestResultsDirectory>
<RunSettingsFilePath>Tests.runsettings</RunSettingsFilePath> <RunSettingsFilePath>Tests.runsettings</RunSettingsFilePath>
@ -20,11 +20,14 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="MonoGame.Extended" Version="3.8.0" /> <PackageReference Include="MonoGame.Extended" Version="3.8.0" />
<PackageReference Include="coverlet.collector" Version="6.0.0" /> <PackageReference Include="coverlet.collector" Version="3.2.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> <PrivateAssets>all</PrivateAssets>
<PackageReference Include="NUnit" Version="3.14.0" /> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" /> </PackageReference>
<PackageReference Include="NunitXml.TestLogger" Version="3.1.15" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" />
<PackageReference Include="NunitXml.TestLogger" Version="3.0.131" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

1
ThirdParty/FNA vendored

@ -1 +0,0 @@
Subproject commit 354e2161b759fa052b25e94209d6ea463aaf098f

@ -1 +0,0 @@
Subproject commit 2d40e9f0f681595dbd4341a3e5a64ed6e31f9556

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

@ -2,7 +2,7 @@
#tool dotnet:?package=docfx&version=2.70.3 #tool dotnet:?package=docfx&version=2.70.3
// this is the upcoming version, for prereleases // this is the upcoming version, for prereleases
var version = Argument("version", "6.3.0"); var version = Argument("version", "6.2.0");
var target = Argument("target", "Default"); var target = Argument("target", "Default");
var branch = Argument("branch", "main"); var branch = Argument("branch", "main");
var config = Argument("configuration", "Release"); var config = Argument("configuration", "Release");
@ -14,8 +14,8 @@ Task("Prepare").Does(() => {
DotNetRestore("MLEM.FNA.sln"); DotNetRestore("MLEM.FNA.sln");
if (branch != "release") { if (branch != "release") {
var buildNum = EnvironmentVariable("GITHUB_RUN_NUMBER"); var buildNum = EnvironmentVariable("CI_PIPELINE_NUMBER");
if (!string.IsNullOrEmpty(buildNum)) if (buildNum != null)
version += "-ci." + buildNum; version += "-ci." + buildNum;
} }
@ -25,9 +25,7 @@ Task("Prepare").Does(() => {
Task("Build").IsDependentOn("Prepare").Does(() =>{ Task("Build").IsDependentOn("Prepare").Does(() =>{
var settings = new DotNetBuildSettings { var settings = new DotNetBuildSettings {
Configuration = config, Configuration = config,
ArgumentCustomization = args => args.Append($"/p:Version={version}"), ArgumentCustomization = args => args.Append($"/p:Version={version}")
// .net 8 has an issue that causes simultaneous tool restores during build to fail
MSBuildSettings = new DotNetMSBuildSettings { MaxCpuCount = 1 }
}; };
DotNetBuild("MLEM.sln", settings); DotNetBuild("MLEM.sln", settings);
DotNetBuild("MLEM.FNA.sln", settings); DotNetBuild("MLEM.FNA.sln", settings);
@ -36,8 +34,7 @@ Task("Build").IsDependentOn("Prepare").Does(() =>{
Task("Test").IsDependentOn("Build").Does(() => { Task("Test").IsDependentOn("Build").Does(() => {
var settings = new DotNetTestSettings { var settings = new DotNetTestSettings {
Configuration = config, Configuration = config,
Collectors = {"XPlat Code Coverage"}, Collectors = {"XPlat Code Coverage"}
Loggers = {"console;verbosity=normal"}
}; };
DotNetTest("MLEM.sln", settings); DotNetTest("MLEM.sln", settings);
DotNetTest("MLEM.FNA.sln", settings); DotNetTest("MLEM.FNA.sln", settings);
@ -52,7 +49,7 @@ Task("Pack").IsDependentOn("Test").Does(() => {
DotNetPack("MLEM.FNA.sln", settings); DotNetPack("MLEM.FNA.sln", settings);
}); });
Task("Push").WithCriteria(branch == "main" || branch == "release", "Not on main or release branch").IsDependentOn("Pack").Does(() => { Task("Push").WithCriteria(branch == "main" || branch == "release").IsDependentOn("Pack").Does(() => {
DotNetNuGetPushSettings settings; DotNetNuGetPushSettings settings;
if (branch == "release") { if (branch == "release") {
settings = new DotNetNuGetPushSettings { settings = new DotNetNuGetPushSettings {