1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-27 23:08:35 +01:00

Compare commits

..

73 commits

Author SHA1 Message Date
Ell
8f2dc2e507 removed woodpecker
(cherry picked from commit 604ac7d825)
2024-04-04 23:34:55 +02:00
Ell
0fde5b7aa0 Merge branch 'main' into release
# Conflicts:
#	build.cake
2024-04-04 22:26:42 +02:00
Ell
11256b02f5 Release 6.3.0 2024-04-04 22:25:45 +02:00
Ell
3bb3ae20c0 further improved performance of panels when removing a large amount of children 2024-04-02 13:36:46 +02:00
Ell
7f4cc773ec Fixed scrolling panels calculating their height incorrectly when their first child is hidden 2024-03-30 12:24:57 +01:00
Ell
5d4f1293c7 fixed SetWidth/HeightBasedOnAspect behaving unexpectedly if an image has no texture 2024-03-30 12:10:50 +01:00
Ell
60af21aff4 Improved UiParser.ParseImage with locks and a callback action 2024-03-30 11:52:23 +01:00
Ell
23103613cd Allow formatting codes applied later to override settings of earlier ones 2024-03-17 21:54:06 +01:00
Ell
0c5369e687 Fixed bold and italic formatting code closing tags working on each other 2024-03-17 21:43:53 +01:00
Ell
62ef75441a Added ToHexStringRgba and ToHexStringRgb to ColorExtensions
Closes #19
2024-03-15 20:04:57 +01:00
Ell
0f8072d83e update copyright year 2024-02-24 22:16:38 +01:00
Ell
fd438f6b1d fixed pre-push hook 2024-02-04 14:55:00 +01:00
Ell
326462fb58 added changelog enforcer action 2024-02-04 14:05:59 +01:00
Ell
08d7af2b80 added pre-commit hook to ensure changelog was updated 2024-02-04 13:54:36 +01:00
Ell
3e76364c5d add changelog entry for ed5c4b4 2024-01-30 20:47:54 +01:00
Ell
2eaf0c0cee Expose character and line spacing in GenericStashFont
closes #16
2024-01-30 20:47:05 +01:00
Ell
ed5c4b44d4 fixed empty nine patch regions stalling when using tile mode
closes #17
2024-01-30 20:34:36 +01:00
Ell
17b6a3297a added discord link to the readme 2024-01-18 10:17:15 +01:00
Ell
cd8b10bd4d actions: fixed env variable names 2024-01-14 22:29:53 +01:00
Ell
6487ad3644 actions: potentially fixed passing ref name xvfb 2024-01-14 22:21:43 +01:00
Ell
dfc56611f2
Switch to GitHub Actions (#15)
* switch to github actions for workflows

* fixed action names

* setup java in build and restore in docs

* use java 17 for android sdk

* clean up restore step
2024-01-14 22:12:07 +01:00
Ell
bd0a723d86 Added some useful additional constructors to various elements 2023-12-28 17:16:31 +01:00
Ell
1436cdb987 Also use IsAotCompatible for FNA projects 2023-12-20 20:54:23 +01:00
Ell
7e345e7437 Merge remote-tracking branch 'origin/main' 2023-12-20 20:50:02 +01:00
Ell
236ecfa116 Use IsAotCompatible instead of IsTrimmable to enable more warnings 2023-12-20 20:49:52 +01:00
Ell
7d314a589e improved Element ToString 2023-12-16 21:37:49 +01:00
Ell
b935bd0a61 Added the ability to set a custom SamplerState for images 2023-12-13 22:57:23 +01:00
Ell
6a8e9639c1 updated dependencies 2023-12-13 22:11:31 +01:00
Ell
e191d4919b Fixed InputHandler touch states being initialized incorrectly when touch handling is disabled
Closes #10
2023-12-04 10:20:29 +01:00
Ell
764b29e120 Text input improvements:
- Allow using control and arrow keys to move the visible area of a text input
- Don't reset the caret position of a text field when selecting or deselecting it
2023-12-02 19:28:59 +01:00
Ell
294af052ae Added SetWidthBasedOnAspect and SetHeightBasedOnAspect to images 2023-11-23 22:16:31 +01:00
Ell
ae5d2b7a37 suppress android demo deprecation warning for SystemUiVisibility 2023-11-23 22:14:48 +01:00
Ell
6c4d241d91 use higher verbosity for ci tests 2023-11-23 10:26:58 +01:00
Ell
aca1ece870 fixed cake build 2023-11-22 21:56:27 +01:00
Ell
a892d2424e fix new trimmable warnings 2023-11-22 12:17:21 +01:00
Ell
3b22a8f228 don't restore dotnet tools in project files 2023-11-22 12:12:36 +01:00
Ell
b4fd0219cd avoid using local package directory 2023-11-22 12:01:30 +01:00
Ell
d7d768d0a9 dotnet install script requires a full version 2023-11-22 10:50:03 +01:00
Ell
b8f46ff5d2 updated to .net 8 and reorganized third party deps 2023-11-22 10:39:13 +01:00
Ell
56a4833a49 further panel performance improvements 2023-11-11 13:09:34 +01:00
Ell
5fcee515e2 this would never happen 2023-11-11 12:43:17 +01:00
Ell
1fa563be46 Improved Panel performance when adding and removing a lot of children 2023-11-11 12:40:11 +01:00
Ell
a233477b1e cleaned up element ui addition order (0fab7fe) 2023-11-11 12:15:21 +01:00
Ell
0fab7fe859 Don't unnecessarily set areas dirty when removing a root element from the ui 2023-11-11 12:06:28 +01:00
Ell
0293ea435e Added UiControls.NavType, which stores the most recently used type of ui navigation 2023-11-08 10:31:36 +01:00
Ell
476e1dd2a6 Improved text formatter tokenization performance 2023-10-14 18:58:32 +02:00
Ell
5e2f48db9d further improved auto-hiding loop detection 2023-10-14 17:53:23 +02:00
Ell
95b28c6039 improved 0571e8a implementation 2023-10-14 17:34:43 +02:00
Ell
281a6f7588 Revert "fixed 0571e8a not updating older panels correctly"
This reverts commit 7bfe44de07.
2023-10-14 17:22:05 +02:00
Ell
7bfe44de07 fixed 0571e8a not updating older panels correctly 2023-10-14 17:05:41 +02:00
Ell
0571e8a4e1 Fixed a stack overflow exception when a panel's scroll bar auto-hiding causes elements to gain height 2023-10-14 15:02:58 +02:00
Ell
6c07a7e900 Fixed panels updating their relevant children too much when the scroll bar is hidden 2023-10-14 14:28:27 +02:00
Ell
41a1a8aef1 dependency update 2023-10-04 15:54:05 +02:00
Ell
de1fc28376 Color parsing improvements
- Added ColorHelper.TryFromHexString, a non-throwing version of FromHexString
- Stopped the text formatter from throwing if a color can't be parsed
2023-09-30 22:50:18 +02:00
Ell
8eff529b9d Fixed various exception types not being wrapped by ContentLoadExceptions when loading raw or JSON content 2023-09-27 18:58:09 +02:00
Ell
ebeba463b4 updated docfx and improved docs navbar 2023-09-15 15:10:03 +02:00
Ell
1a06bcc7fd ci: don't push on pull request 2023-09-06 12:42:47 +02:00
Ell
b49ac1d053 Added the ability to draw single corners of AutoTiling's extended auto tiles 2023-08-28 01:51:25 +02:00
Ell
7720ab0ea5 fixed newly added Panel children not scrolling correctly since f6bc206 2023-08-15 10:30:49 +02:00
Ell
a119db553f fixed a potential stack overflow introduced by 237334b 2023-08-14 18:32:27 +02:00
Ell
7bf22fa8f3 changelog cleanup 2023-08-14 17:54:32 +02:00
Ell
4d7d628486 fixed FNA 2023-08-14 17:52:16 +02:00
Ell
237334b1c9 Allow dropdowns to have scrolling panels closes #8 2023-08-14 17:50:07 +02:00
Ell
f6bc206c1f Allow scrolling panels to contain other scrolling panels 2023-08-14 17:37:26 +02:00
Ell
550bf28320 Allow scrolling panels to contain other scrolling panels 2023-08-14 16:02:28 +02:00
Ell
7e64b8a990 Added GetRandomEntry and GetRandomWeightedEntry to SingleRandom 2023-08-07 19:00:34 +02:00
Ell
2c7ffee427 Added Zero, One, Linear and Clamp to Easings 2023-08-03 11:29:51 +02:00
Ell
62a7a89834 additional text input fixes for emoji 2023-07-17 15:56:25 +02:00
Ell
fda22de83d Fixed TextInput not working correctly when using surrogate pairs 2023-07-17 15:20:36 +02:00
Ell
50da081be9 Added WithRenderTargets, a multi-target version of WithRenderTarget 2023-07-11 12:11:12 +02:00
Ell
840c528f06 use dotnet nuget in cake rather than nuget.exe 2023-07-10 17:33:45 +02:00
Ell
053ad5967b added woodpecker 2023-07-10 17:18:09 +02:00
Ell
f8aae9f5c2 bump upcoming version 2023-06-28 13:57:05 +02:00
99 changed files with 1002 additions and 1140 deletions

8
.githooks/pre-push Normal file
View file

@ -0,0 +1,8 @@
#!/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

11
.github/workflows/enforce-changelog.yml vendored Normal file
View file

@ -0,0 +1,11 @@
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.

51
.github/workflows/main.yml vendored Normal file
View file

@ -0,0 +1,51 @@
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 = FNA path = ThirdParty/FNA
url = https://github.com/FNA-XNA/FNA url = https://github.com/FNA-XNA/FNA
[submodule "FontStashSharp"] [submodule "FontStashSharp"]
path = FontStashSharp path = ThirdParty/FontStashSharp
url = https://github.com/FontStashSharp/FontStashSharp url = https://github.com/FontStashSharp/FontStashSharp

View file

@ -1,16 +0,0 @@
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

View file

@ -1,16 +0,0 @@
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,6 +2,7 @@
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)
@ -10,6 +11,57 @@ 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 CA1422 #pragma warning disable CS0618
// 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 CA1422 #pragma warning restore CS0618
} }
} }

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0-android</TargetFramework> <TargetFramework>net8.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>net7.0</TargetFramework> <TargetFramework>net8.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="..\FNA\FNA.Core.csproj" /> <ProjectReference Include="..\ThirdParty\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="../FnaNative/**"> <Content Include="../ThirdParty/Native/**">
<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>net7.0</TargetFramework> <TargetFramework>net8.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,9 +26,4 @@
<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,16 @@ 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
function codeBlock() { function codeBlock() {
} }
``` ```

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>net8.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="..\FNA\FNA.Core.csproj"> <ProjectReference Include="..\ThirdParty\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>netstandard2.0</TargetFramework> <TargetFramework>net8.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</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 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 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,6 +223,15 @@ 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-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>", "_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>",
"_enableSearch": true "_enableSearch": true
}, },
"dest": "_site", "dest": "_site",

1
FNA

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

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.

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

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2019-2023 Ellpeck Copyright (c) 2019-2024 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 (FileNotFoundException) {} } catch (IOException) {}
} }
} }
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 (FileNotFoundException) {} } catch (IOException) {}
} }
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;net7.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable> <IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
<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.2"> <PackageReference Include="Newtonsoft.Json" Version="13.0.3">
<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="..\FNA\FNA.csproj"> <ProjectReference Include="..\ThirdParty\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;net7.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable> <IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
<NoWarn>NU1701</NoWarn> <NoWarn>NU1701</NoWarn>
</PropertyGroup> </PropertyGroup>

View file

@ -11,6 +11,7 @@ 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 />
@ -18,6 +19,15 @@ 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.
@ -33,12 +43,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)).X; return this.Font.MeasureString(CodePointSource.ToString(codePoint), null, this.CharacterSpacing, this.LineSpacing).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, scale, rotation, Vector2.Zero, layerDepth); this.Font.DrawText(batch, character, position, color, rotation, Vector2.Zero, scale, layerDepth, this.CharacterSpacing, this.LineSpacing);
} }
} }

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;net7.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable> <IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
<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="..\FontStashSharp\src\XNA\FontStashSharp.FNA.csproj"> <ProjectReference Include="..\ThirdParty\FontStashSharp\src\XNA\FontStashSharp.FNA.csproj">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\FNA\FNA.csproj"> <ProjectReference Include="..\ThirdParty\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;net7.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable> <IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
</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.2.8"> <PackageReference Include="FontStashSharp.MonoGame" Version="1.3.3">
<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,7 +44,8 @@ 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) {
return ColorHelper.FromHexString(properties.Get(key)); ColorHelper.TryFromHexString(properties.Get(key), out var val);
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", "FNA\FNA.csproj", "{35253CE1-C864-4CD3-8249-4D1319748E8F}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA", "ThirdParty\FNA\FNA.csproj", "{35253CE1-C864-4CD3-8249-4D1319748E8F}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FontStashSharp.FNA", "FontStashSharp\src\XNA\FontStashSharp.FNA.csproj", "{39249E92-EBF2-4951-A086-AB4951C3CCE1}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FontStashSharp.FNA", "ThirdParty\FontStashSharp\src\XNA\FontStashSharp.FNA.csproj", "{39249E92-EBF2-4951-A086-AB4951C3CCE1}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA.Core", "FNA\FNA.Core.csproj", "{458FFA5E-A1C4-4B23-A5D8-259385FEECED}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA.Core", "ThirdParty\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;net7.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable> <IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
<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.4" /> <PackageReference Include="Coroutine" Version="2.1.5" />
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.FNA.csproj" /> <ProjectReference Include="..\MLEM.Ui\MLEM.Ui.FNA.csproj" />
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" /> <ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
<ProjectReference Include="..\FNA\FNA.csproj"> <ProjectReference Include="..\ThirdParty\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;net7.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable> <IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View file

@ -1,12 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net8.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>net6.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<PublishReadyToRun>false</PublishReadyToRun> <PublishReadyToRun>false</PublishReadyToRun>
<TieredCompilation>false</TieredCompilation> <TieredCompilation>false</TieredCompilation>
<ApplicationIcon>Icon.ico</ApplicationIcon> <ApplicationIcon>Icon.ico</ApplicationIcon>
@ -19,9 +19,4 @@
<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,6 +87,13 @@ 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>
@ -94,7 +101,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) : base(anchor, size) { public Button(Anchor anchor, Vector2 size, string text = null, string tooltipText = null) : this(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);
@ -104,6 +111,23 @@ 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,8 +12,7 @@ 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 readonly Panel Panel; public Panel Panel { get; private set; }
/// <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>
@ -29,6 +28,18 @@ 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>
@ -36,31 +47,25 @@ 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>
public Dropdown(Anchor anchor, Vector2 size, string text = null, string tooltipText = null) : base(anchor, size, text, tooltipText) { /// <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>
this.Panel = this.AddChild(new Panel(Anchor.TopCenter, Vector2.Zero, Vector2.Zero, true) { /// <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>
IsHidden = 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>
}); 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.OnAreaUpdated += e => { this.Initialize(panelHeight, scrollPanel, autoHidePanelScrollbar);
this.Panel.Size = new Vector2(e.Area.Width / e.Scale, 0); }
this.Panel.PositionOffset = new Vector2(0, e.Area.Height / e.Scale);
}; /// <summary>
this.OnOpenedOrClosed += e => this.Priority = this.IsOpen ? 10000 : 0; /// Creates a new dropdown with the given settings
this.OnPressed += e => { /// </summary>
this.IsOpen = !this.IsOpen; /// <param name="anchor">The dropdown's anchor</param>
// close other dropdowns in the same root when we open /// <param name="size">The dropdown button's size</param>
if (this.IsOpen) { /// <param name="textCallback">The text displayed on the dropdown button</param>
this.Root.Element.AndChildren(o => { /// <param name="tooltipTextCallback">The text displayed as a tooltip when hovering over the dropdown button</param>
if (o != this && o is Dropdown d && d.IsOpen) /// <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>
d.IsOpen = false; /// <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, 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>
@ -116,5 +121,32 @@ 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,12 +1236,13 @@ namespace MLEM.Ui.Elements {
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() { public override string ToString() {
var ret = this.GetType().ToString(); var ret = this.GetType().Name;
// elements will contain their path up to the root (Paragraph@Panel@...@RootName) // elements will contain their path up to the root and their index in each parent
// eg Paragraph 2 @ Panel 3 @ ... @ Group RootName
if (this.Parent != null) { if (this.Parent != null) {
ret += $"@{this.Parent}"; ret += $" {this.Parent.Children.IndexOf(this)} @ {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,8 +14,18 @@ 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</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 setHeightBasedOnChildren = true) : base(anchor, size) { public Group(Anchor anchor, Vector2 size, bool setHeightBasedOnChildren = true) : this(anchor, size, false, setHeightBasedOnChildren) {}
/// <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,11 +69,45 @@ 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;
@ -86,7 +120,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;
} }
@ -95,14 +129,29 @@ 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) {
return this.Texture != null && this.scaleToImage ? this.Texture.Size.ToVector2() * this.Scale : base.CalcActualSize(parentArea); var ret = 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 />
@ -115,6 +164,14 @@ 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) {
@ -125,6 +182,12 @@ 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);
} }
@ -134,7 +197,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) if (nullChanged || this.ScaleToImage || this.SetWidthBasedOnAspect || this.SetHeightBasedOnAspect)
this.SetAreaDirty(); this.SetAreaDirty();
} }

View file

@ -55,12 +55,16 @@ 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.
@ -70,7 +74,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</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, 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;
@ -94,9 +98,23 @@ 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) {
@ -106,11 +124,15 @@ 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();
} }
@ -136,8 +158,16 @@ 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
if (this.System != null) this.childrenDirtyForScroll = true;
}
/// <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 />
@ -202,10 +232,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 firstChild = this.Children.FirstOrDefault(c => c != this.ScrollBar); var highestValidChild = this.Children.FirstOrDefault(c => c != this.ScrollBar && !c.IsHidden);
if (firstChild == null) if (highestValidChild == null)
return; return;
this.ScrollBar.CurrentValue = (elementY - this.Area.Height / 2 - firstChild.Area.Top) / this.Scale + this.ChildPadding.Value.Height / 2; this.ScrollBar.CurrentValue = (elementY - this.Area.Height / 2 - highestValidChild.Area.Top) / this.Scale + this.ChildPadding.Value.Height / 2;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -234,13 +264,12 @@ 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);
// we only need to scroll when a grandchild changes, since all of our children are forced if (grandchild && !this.AreaDirty) {
// to be auto-anchored and so will automatically propagate their changes up to us // we only need to scroll when a grandchild changes, since all of our children are forced
if (grandchild) { // to be auto-anchored and so will automatically propagate their changes up to us
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
if (!this.AreaDirty) this.ScrollSetup();
this.ScrollSetup();
} }
} }
@ -257,32 +286,41 @@ 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 firstChild = this.Children.FirstOrDefault(c => c != this.ScrollBar); var highestValidChild = this.Children.FirstOrDefault(c => c != this.ScrollBar && !c.IsHidden);
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 - firstChild.Area.Top; childrenHeight = lowestChild.GetTotalCoveredArea(false).Bottom - highestValidChild.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 = (childrenHeight - this.ChildPaddedArea.Height) / this.Scale; var scrollBarMax = Math.Max(0, (childrenHeight - this.ChildPaddedArea.Height) / this.Scale);
if (!this.ScrollBar.MaxValue.Equals(scrollBarMax, Element.Epsilon)) { if (!this.ScrollBar.MaxValue.Equals(scrollBarMax, Element.Epsilon)) {
this.ScrollBar.MaxValue = scrollBarMax; // 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.relevantChildrenDirty = true; if (!this.scrollBarMaxHistory[0].Equals(this.scrollBarMaxHistory[2], Element.Epsilon) || !this.scrollBarMaxHistory[1].Equals(scrollBarMax, Element.Epsilon)) {
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;
if (!this.scrollBarChildOffset.Equals(childOffset, Element.Epsilon)) { var childOffsetDelta = childOffset - this.scrollBarChildOffset;
this.ChildPadding += new Padding(0, -this.scrollBarChildOffset + childOffset, 0, 0); if (!childOffsetDelta.Equals(0, Element.Epsilon)) {
this.scrollBarChildOffset = childOffset; this.scrollBarChildOffset = childOffset;
this.SetAreaDirty(); this.ChildPadding += new Padding(0, childOffsetDelta, 0, 0);
} }
// 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
@ -290,15 +328,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 targetArea = (Rectangle) this.GetRenderTargetArea(); var area = (Rectangle) this.GetRenderTargetArea();
if (targetArea.Width <= 0 || targetArea.Height <= 0) { if (area.Width <= 0 || area.Height <= 0) {
this.renderTarget?.Dispose(); this.renderTarget?.Dispose();
this.renderTarget = null; this.renderTarget = null;
return; return;
} }
if (this.renderTarget == null || targetArea.Width != this.renderTarget.Width || targetArea.Height != this.renderTarget.Height) { if (this.renderTarget == null || area.Width != this.renderTarget.Width || area.Height != this.renderTarget.Height) {
this.renderTarget?.Dispose(); this.renderTarget?.Dispose();
this.renderTarget = targetArea.IsEmpty ? null : new RenderTarget2D(this.System.Game.GraphicsDevice, targetArea.Width, targetArea.Height); this.renderTarget = new RenderTarget2D(this.System.Game.GraphicsDevice, area.Width, area.Height, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents);
this.relevantChildrenDirty = true; this.relevantChildrenDirty = true;
} }
} }
@ -330,7 +368,7 @@ namespace MLEM.Ui.Elements {
} }
private RectangleF GetRenderTargetArea() { private RectangleF GetRenderTargetArea() {
var area = this.ChildPaddedArea; var area = this.ChildPaddedArea.OffsetCopy(this.ScaledScrollOffset);
area.X = this.DisplayArea.X; area.X = this.DisplayArea.X;
area.Width = this.DisplayArea.Width; area.Width = this.DisplayArea.Width;
return area; return area;
@ -339,9 +377,21 @@ 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)) {
child.ScrollOffset.Y = -this.ScrollBar.CurrentValue; // if a child was newly added later, the last scroll offset was never applied
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,6 +166,30 @@ 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>
@ -177,7 +201,13 @@ namespace MLEM.Ui.Elements {
this.GetTextCallback = textCallback; this.GetTextCallback = textCallback;
} }
/// <inheritdoc cref="Paragraph(Anchor,float,TextCallback,bool)"/> /// <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="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;
@ -232,7 +262,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.CalcActualSize(this.ParentArea).Equals(this.DisplayArea.Size, Element.Epsilon)) if (!this.AreaDirty && (this.System == null || !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 (moused != null && (moused == this.Parent || moused.GetParentTree().Contains(this.Parent))) { if (this.IsMousedForScrolling(moused)) {
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,6 +244,23 @@ 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,8 +204,6 @@ 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;net7.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable> <IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
<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="..\FNA\FNA.csproj"> <ProjectReference Include="..\ThirdParty\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;net7.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable> <IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View file

@ -139,21 +139,32 @@ 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) { protected Image ParseImage(string path, Action<TextureRegion> onImageFetched = null) {
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, new Vector2(1, -1), _ => image) { return new Image(Anchor.AutoLeft, Vector2.One, _ => {
lock (imageLock)
return image;
}) {
SetHeightBasedOnAspect = true,
OnAddedToUi = e => { OnAddedToUi = e => {
if (image == null) bool imageNull;
lock (imageLock)
imageNull = image == null;
if (imageNull)
LoadImageAsync(); LoadImageAsync();
}, },
OnRemovedFromUi = e => { OnRemovedFromUi = e => {
image?.Texture.Dispose(); lock (imageLock) {
image = null; image?.Texture.Dispose();
image = null;
}
} }
}; };
@ -178,7 +189,12 @@ 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);
} }
image = new TextureRegion(tex); lock (imageLock) {
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. /// 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"/>.
/// 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,12 +115,29 @@ 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.
@ -134,6 +151,7 @@ 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.
@ -167,6 +185,7 @@ 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) {
@ -175,6 +194,7 @@ 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);
@ -187,12 +207,14 @@ 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);
@ -208,12 +230,14 @@ 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)
@ -224,6 +248,7 @@ 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) {
@ -239,11 +264,13 @@ 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);
} }
@ -286,8 +313,9 @@ 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</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>
public void SelectElement(RootElement root, Element element, bool? autoNav = null) { /// <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, NavigationType? navType = null) {
if (root == null) if (root == null)
return; return;
if (element != null && !element.CanBeSelected) if (element != null && !element.CanBeSelected)
@ -308,6 +336,8 @@ 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>
@ -444,6 +474,7 @@ 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);
@ -454,5 +485,34 @@ 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,9 +608,10 @@ 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</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>
public void SelectElement(Element element, bool? autoNav = null) { /// <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>
this.System.Controls.SelectElement(this, element, autoNav); public void SelectElement(Element element, bool? autoNav = null, UiControls.NavigationType? navType = null) {
this.System.Controls.SelectElement(this, element, autoNav, navType);
} }
/// <summary> /// <summary>

View file

@ -1,3 +1,4 @@
using System;
using System.Globalization; using System.Globalization;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
@ -36,6 +37,27 @@ 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>
@ -64,16 +86,34 @@ namespace MLEM.Extensions {
} }
/// <summary> /// <summary>
/// Parses a hexadecimal string into a color. /// Parses a hexadecimal string into a color and throws a <see cref="FormatException"/> if parsing fails.
/// The string can either be formatted as RRGGBB or AARRGGBB and can optionally start with a <c>#</c>. /// The string can either be formatted as <c>RRGGBB</c> or <c>AARRGGBB</c> 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);
var val = int.Parse(value, NumberStyles.HexNumber); if (int.TryParse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var val)) {
return value.Length > 6 ? ColorHelper.FromHexRgba(val) : ColorHelper.FromHexRgb(val); color = value.Length > 6 ? ColorHelper.FromHexRgba(val) : ColorHelper.FromHexRgb(val);
return true;
}
color = default;
return false;
} }
} }

View file

@ -69,11 +69,22 @@ 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></returns> /// <returns>The render target context, to be used in a <c>using</c> statement</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"/>.
@ -88,7 +99,20 @@ 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) { public TargetContext(GraphicsDevice device, RenderTarget2D target) : this(device) {
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
@ -96,7 +120,6 @@ 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,8 +16,7 @@ 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) {
// ElementAt internally optimizes for IList access so we don't have to here return RandomExtensions.GetRandomEntry(entries, random.NextSingle());
return entries.ElementAt(random.Next(entries.Count));
} }
/// <summary> /// <summary>
@ -31,28 +30,12 @@ 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) {
var totalWeight = entries.Sum(weightFunc); return RandomExtensions.GetRandomWeightedEntry(entries, weightFunc, random.NextSingle());
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) {
var totalWeight = entries.Sum(weightFunc); return RandomExtensions.GetRandomWeightedEntry(entries, weightFunc, random.NextSingle());
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>
@ -87,5 +70,32 @@ 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,6 +63,18 @@ 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 array only has multiple entries if additional tokens have to be started while this code is still applied. /// Note that this collection only has multiple entries if additional tokens have to be started while this code is still applied.
/// </summary> /// </summary>
public IList<Token> Tokens { get; internal set; } public readonly List<Token> Tokens = new List<Token>();
/// <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,5 +18,11 @@ 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,
m.Groups[1].Success ? ColorHelper.FromHexString(m.Groups[1].Value) : this.DefaultShadowColor, ColorHelper.TryFromHexString(m.Groups[1].Value, out var color) ? color : 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,
m.Groups[1].Success ? ColorHelper.FromHexString(m.Groups[1].Value) : this.DefaultOutlineColor, ColorHelper.TryFromHexString(m.Groups[1].Value, out var color) ? color : 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,12 +124,13 @@ 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, ColorHelper.FromHexString(m.Groups[1].Value))); this.Codes.Add(new Regex(@"<c #([0-9\w]{6,8})>"), (f, m, r) => new ColorCode(m, r,
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));
} }
@ -155,11 +156,12 @@ 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 codes = new List<Code>(); var applied = 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)
codes.Add(firstCode); applied.Add(firstCode);
var index = 0; var index = 0;
var rawIndex = 0; var rawIndex = 0;
while (rawIndex < s.Length) { while (rawIndex < s.Length) {
@ -167,24 +169,25 @@ 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(codes.ToArray(), index, rawIndex, TextFormatter.StripFormatting(font, sub, codes), sub)); tokens.Add(new Token(applied.ToArray(), index, rawIndex, TextFormatter.StripFormatting(font, sub, applied), 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, codes); var strippedRet = TextFormatter.StripFormatting(font, ret, applied);
tokens.Add(new Token(codes.ToArray(), index, rawIndex, strippedRet, ret)); tokens.Add(new Token(applied.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
codes.RemoveAll(c => c.EndsHere(next) || next.EndsOther(c)); applied.RemoveAll(c => c.EndsHere(next) || next.EndsOther(c));
codes.Add(next); applied.Add(next);
} }
return new TokenizedString(font, alignment, s, TextFormatter.StripFormatting(font, s, tokens.SelectMany(t => t.AppliedCodes)), tokens.ToArray()); return new TokenizedString(font, alignment, s, TextFormatter.StripFormatting(font, s, allCodes), tokens.ToArray(), allCodes.ToArray());
} }
/// <summary> /// <summary>

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
@ -14,6 +15,7 @@ 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>
@ -45,11 +47,14 @@ 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,11 +1,9 @@
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;
@ -42,17 +40,11 @@ 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) { internal TokenizedString(GenericFont font, TextAlignment alignment, string rawString, string strg, Token[] tokens, Code[] allCodes) {
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,6 +2,7 @@ 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 {
@ -86,92 +87,94 @@ 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) {
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)
batch.Draw(backgroundTexture, pos, backgroundColor, 0, orig, sc, SpriteEffects.None, layerDepth); batch.Draw(backgroundTexture, pos, backgroundColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth);
if (r1 != Rectangle.Empty) var od = layerDepth + overlayDepthOffset;
batch.Draw(overlayTexture.Texture, pos, r1, overlayColor, 0, orig, sc, SpriteEffects.None, od); AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.UpLeft, origin, scale, od);
if (r2 != Rectangle.Empty) AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.UpRight, origin, scale, od);
batch.Draw(overlayTexture.Texture, pos, r2, overlayColor, 0, orig, sc, SpriteEffects.None, od); AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.DownLeft, origin, scale, od);
if (r3 != Rectangle.Empty) AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.DownRight, origin, scale, od);
batch.Draw(overlayTexture.Texture, pos, r3, overlayColor, 0, orig, sc, SpriteEffects.None, od); }
if (r4 != Rectangle.Empty)
batch.Draw(overlayTexture.Texture, pos, r4, overlayColor, 0, orig, sc, SpriteEffects.None, od); /// <summary>
/// This method allows for a single corner of a tiled texture to be drawn in an auto-tiling mode.
/// This allows, for example, a grass patch on a tilemap to have nice looking edges that transfer over into a path without any hard edges between tiles.
///
/// 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)"/>
/// </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) {
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)
batch.Draw(backgroundTexture, pos, backgroundColor, 0, orig, sc, SpriteEffects.None, layerDepth); batch.Draw(backgroundTexture, pos, backgroundColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth);
if (xUl >= 0) var od = layerDepth + overlayDepthOffset;
batch.Draw(overlayTextures(xUl), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od); AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.UpLeft, origin, scale, od);
if (xUr >= 0) AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.UpRight, origin, scale, od);
batch.Draw(overlayTextures(xUr), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od); AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.DownLeft, origin, scale, od);
if (xDl >= 0) AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.DownRight, origin, scale, od);
batch.Draw(overlayTextures(xDl), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od); }
if (xDr >= 0)
batch.Draw(overlayTextures(xDr), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od); /// <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 DrawExtendedAutoTileCorner(SpriteBatch batch, Vector2 pos, Func<int, TextureRegion> overlayTextures, ConnectsTo connectsTo, Color overlayColor, Direction2 corner, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0) {
var src = AutoTiling.CalculateExtendedAutoTileOffset(connectsTo, corner);
if (src >= 0)
batch.Draw(overlayTextures(src), pos, 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 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, orig, sc, SpriteEffects.None, layerDepth); var background = batch.Add(backgroundTexture, pos, backgroundColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth);
items?.Add(background); items?.Add(background);
} }
if (r1 != Rectangle.Empty) { var od = layerDepth + overlayDepthOffset;
var o1 = batch.Add(overlayTexture.Texture, pos, r1, overlayColor, 0, orig, sc, SpriteEffects.None, od); AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.UpLeft, origin, scale, od, items);
items?.Add(o1); AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.UpRight, origin, scale, od, items);
} AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.DownLeft, origin, scale, od, items);
if (r2 != Rectangle.Empty) { AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.DownRight, origin, scale, od, items);
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)"/>
if (r3 != Rectangle.Empty) { 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) {
var o3 = batch.Add(overlayTexture.Texture, pos, r3, overlayColor, 0, orig, sc, SpriteEffects.None, od); var src = AutoTiling.CalculateExtendedAutoTile(overlayTexture.Area, connectsTo, corner);
items?.Add(o3); if (src != Rectangle.Empty) {
} 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, orig, sc, SpriteEffects.None, layerDepth); var background = batch.Add(backgroundTexture, pos, backgroundColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth);
items?.Add(background); items?.Add(background);
} }
if (xUl >= 0) { var od = layerDepth + overlayDepthOffset;
var o1 = batch.Add(overlayTextures(xUl), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od); AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.UpLeft, origin, scale, od, items);
items?.Add(o1); AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.UpRight, origin, scale, od, items);
} AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.DownLeft, origin, scale, od, items);
if (xUr >= 0) { AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.DownRight, origin, scale, od, items);
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)"/>
if (xDl >= 0) { 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) {
var o3 = batch.Add(overlayTextures(xDl), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od); var src = AutoTiling.CalculateExtendedAutoTileOffset(connectsTo, corner);
items?.Add(o3); if (src >= 0) {
} 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);
} }
} }
@ -194,26 +197,36 @@ 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, int, int, int) CalculateExtendedAutoTileOffsets(ConnectsTo connectsTo) { private static int CalculateExtendedAutoTileOffset(ConnectsTo connectsTo, Direction2 corner) {
var up = connectsTo(0, -1); switch (corner) {
var down = connectsTo(0, 1); case Direction2.UpLeft: {
var left = connectsTo(-1, 0); var up = connectsTo(0, -1);
var right = connectsTo(1, 0); var left = connectsTo(-1, 0);
return ( return up && left ? connectsTo(-1, -1) ? -1 : 12 : left ? 0 : up ? 8 : 4;
up && left ? connectsTo(-1, -1) ? -1 : 12 : left ? 0 : up ? 8 : 4, }
up && right ? connectsTo(1, -1) ? -1 : 13 : right ? 1 : up ? 9 : 5, case Direction2.UpRight: {
down && left ? connectsTo(-1, 1) ? -1 : 14 : left ? 2 : down ? 10 : 6, var up = connectsTo(0, -1);
down && right ? connectsTo(1, 1) ? -1 : 15 : right ? 3 : down ? 11 : 7); var right = connectsTo(1, 0);
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, Rectangle, Rectangle, Rectangle) CalculateExtendedAutoTile(Rectangle textureRegion, ConnectsTo connectsTo) { private static Rectangle CalculateExtendedAutoTile(Rectangle textureRegion, ConnectsTo connectsTo, Direction2 corner) {
var (xUl, xUr, xDl, xDr) = AutoTiling.CalculateExtendedAutoTileOffsets(connectsTo); var off = AutoTiling.CalculateExtendedAutoTileOffset(connectsTo, corner);
var (w, h) = (textureRegion.Width, textureRegion.Height); return off < 0 ? Rectangle.Empty : new Rectangle(textureRegion.X + off * textureRegion.Width, textureRegion.Y, 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; } public IList<TouchLocation> LastViewportTouchState { get; private set; } = new List<TouchLocation>();
/// <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; } public IList<TouchLocation> ViewportTouchState { get; private set; } = new List<TouchLocation>();
/// <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,6 +342,7 @@ 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,7 +105,8 @@ 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) {
this.caretPos = val; // ensure that we don't move to a location that is between high and low surrogates
this.caretPos = new CodePointSource(this.text).EnsureSurrogateBoundary(val, val > this.caretPos);
this.caretBlinkTimer = 0; this.caretBlinkTimer = 0;
this.SetTextDataDirty(false); this.SetTextDataDirty(false);
} }
@ -203,6 +204,21 @@ 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>
@ -217,10 +233,9 @@ namespace MLEM.Input {
private char? maskingCharacter; private char? maskingCharacter;
private double caretBlinkTimer; private double caretBlinkTimer;
private string displayedText; private string visibleText;
private string[] splitText; private string[] multilineSplitText;
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;
@ -301,9 +316,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) && this.MoveCaretToLine(this.CaretLine - 1)) { } else if (this.Multiline && input.IsPressedAvailable(Keys.Up) && (input.IsModifierKeyDown(ModifierKey.Control) ? this.ShowLine(this.FirstVisibleLine - 1) : this.MoveCaretToLine(this.CaretLine - 1))) {
input.TryConsumePressed(Keys.Up); input.TryConsumePressed(Keys.Up);
} else if (this.Multiline && input.IsPressedAvailable(Keys.Down) && this.MoveCaretToLine(this.CaretLine + 1)) { } else if (this.Multiline && input.IsPressedAvailable(Keys.Down) && (input.IsModifierKeyDown(ModifierKey.Control) ? this.ShowLine(this.FirstVisibleLine + 1) : 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;
@ -339,12 +354,12 @@ namespace MLEM.Input {
this.UpdateTextDataIfDirty(); this.UpdateTextDataIfDirty();
var scale = this.TextScale * drawScale; var scale = this.TextScale * drawScale;
this.Font.DrawString(batch, this.displayedText, textPos, textColor, 0, Vector2.Zero, scale, SpriteEffects.None, 0); this.Font.DrawString(batch, this.visibleText, 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.lineOffset) * scale; caretDrawPos.Y += this.Font.LineHeight * (this.CaretLine - this.FirstVisibleLine) * 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);
} }
} }
@ -360,7 +375,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, this.MaximumCharacters.Value); strg = strg.Substring(0, new CodePointSource(strg).EnsureSurrogateBoundary(this.MaximumCharacters.Value, false));
this.text.Clear(); this.text.Clear();
this.text.Append(strg); this.text.Append(strg);
this.CaretPos = this.text.Length; this.CaretPos = this.text.Length;
@ -378,7 +393,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, this.MaximumCharacters.Value - this.text.Length); strg = strg.Substring(0, new CodePointSource(strg).EnsureSurrogateBoundary(this.MaximumCharacters.Value - this.text.Length, false));
this.text.Insert(this.CaretPos, strg); this.text.Insert(this.CaretPos, strg);
this.CaretPos += strg.Length; this.CaretPos += strg.Length;
this.SetTextDataDirty(); this.SetTextDataDirty();
@ -393,7 +408,8 @@ 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;
this.text.Remove(index, length); var source = new CodePointSource(this.text);
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();
@ -417,7 +433,7 @@ namespace MLEM.Input {
this.CaretPos = destStart + destAccum.Length; this.CaretPos = destStart + destAccum.Length;
return true; return true;
} }
destAccum += this.text[destStart + destAccum.Length]; destAccum += CodePointSource.ToString(new CodePointSource(this.text).GetCodePoint(destStart + destAccum.Length).CodePoint);
} }
// 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;
@ -426,6 +442,26 @@ 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)) {
@ -458,49 +494,50 @@ namespace MLEM.Input {
if (this.Multiline) { if (this.Multiline) {
// soft wrap if we're multiline // soft wrap if we're multiline
this.splitText = this.Font.SplitStringSeparate(visualText, this.Size.X, this.TextScale).ToArray(); this.multilineSplitText = this.Font.SplitStringSeparate(visualText, this.Size.X, this.TextScale).ToArray();
this.displayedText = string.Join("\n", this.splitText); this.visibleText = string.Join("\n", this.multilineSplitText);
this.Lines = this.visibleText.Count(c => c == '\n') + 1;
this.UpdateCaretData(); this.UpdateCaretData();
if (this.Font.MeasureString(this.displayedText).Y * this.TextScale > this.Size.Y) { if (this.Font.MeasureString(this.visibleText).Y * this.TextScale > this.Size.Y) {
var maxLines = (this.Size.Y / (this.Font.LineHeight * this.TextScale)).Floor(); if (this.FirstVisibleLine > this.CaretLine) {
if (this.lineOffset > this.CaretLine) {
// if we're moving up // if we're moving up
this.lineOffset = this.CaretLine; this.FirstVisibleLine = this.CaretLine;
} else if (this.CaretLine >= maxLines) { } else if (this.CaretLine >= this.MaxDisplayedLines) {
// if we're moving down // if we're moving down
var limit = this.CaretLine - (maxLines - 1); var limit = this.CaretLine - (this.MaxDisplayedLines - 1);
if (limit > this.lineOffset) if (limit > this.FirstVisibleLine)
this.lineOffset = limit; this.FirstVisibleLine = 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.displayedText.Length; i++) { for (var i = 0; i < this.visibleText.Length; i++) {
if (lines >= this.lineOffset) { if (lines >= this.FirstVisibleLine) {
if (ret.Length <= 0) if (ret.Length <= 0)
this.textOffset = originalIndex; this.textOffset = originalIndex;
ret.Append(this.displayedText[i]); ret.Append(this.visibleText[i]);
} }
if (this.displayedText[i] == '\n') { if (this.visibleText[i] == '\n') {
lines++; lines++;
if (visualText[originalIndex] == '\n') if (visualText[originalIndex] == '\n')
originalIndex++; originalIndex++;
} else { } else {
originalIndex++; originalIndex++;
} }
if (lines - this.lineOffset >= maxLines) if (lines - this.FirstVisibleLine >= this.MaxDisplayedLines)
break; break;
} }
this.displayedText = ret.ToString(); this.visibleText = ret.ToString();
} else { } else {
this.lineOffset = 0; this.FirstVisibleLine = 0;
this.textOffset = 0; this.textOffset = 0;
} }
} else { } else {
this.splitText = null; this.multilineSplitText = null;
this.lineOffset = 0; this.FirstVisibleLine = 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) {
@ -514,9 +551,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.displayedText = this.Font.TruncateString(visible, this.Size.X, this.TextScale); this.visibleText = this.Font.TruncateString(visible, this.Size.X, this.TextScale);
} else { } else {
this.displayedText = visualText.ToString(); this.visibleText = visualText.ToString();
this.textOffset = 0; this.textOffset = 0;
} }
this.UpdateCaretData(); this.UpdateCaretData();
@ -524,9 +561,9 @@ namespace MLEM.Input {
} }
private void UpdateCaretData() { private void UpdateCaretData() {
if (this.splitText != null) { if (this.multilineSplitText != 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.splitText.Length <= 0) { if (this.multilineSplitText.Length <= 0) {
this.caretLine = 0; this.caretLine = 0;
this.caretPosInLine = 0; this.caretPosInLine = 0;
this.caretDrawOffset = 0; this.caretDrawOffset = 0;
@ -535,9 +572,9 @@ namespace MLEM.Input {
var line = 0; var line = 0;
var index = 0; var index = 0;
for (var d = 0; d < this.splitText.Length; d++) { for (var d = 0; d < this.multilineSplitText.Length; d++) {
var startOfLine = 0; var startOfLine = 0;
var split = this.splitText[d]; var split = this.multilineSplitText[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;
@ -557,20 +594,20 @@ namespace MLEM.Input {
// max width splits // max width splits
line++; line++;
} }
} else if (this.displayedText != null) { } else if (this.visibleText != null) {
this.caretLine = 0; this.caretLine = 0;
this.caretPosInLine = this.CaretPos; this.caretPosInLine = this.CaretPos;
this.caretDrawOffset = this.Font.MeasureString(this.displayedText.Substring(0, this.CaretPos - this.textOffset)).X; this.caretDrawOffset = this.Font.MeasureString(this.visibleText.Substring(0, this.CaretPos - this.textOffset)).X;
} }
} }
private (int, int) GetLineBounds(int boundLine) { private (int, int) GetLineBounds(int boundLine) {
if (this.splitText != null) { if (this.multilineSplitText != 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.splitText.Length; d++) { for (var d = 0; d < this.multilineSplitText.Length; d++) {
var split = this.splitText[d]; var split = this.multilineSplitText[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;net7.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable> <IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
<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="..\FNA\FNA.csproj"> <ProjectReference Include="..\ThirdParty\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;net7.0</TargetFrameworks> <TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable> <IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View file

@ -1,4 +1,5 @@
using System; using System;
using Microsoft.Xna.Framework;
namespace MLEM.Misc { namespace MLEM.Misc {
/// <summary> /// <summary>
@ -8,6 +9,21 @@ 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>
@ -170,6 +186,17 @@ 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,4 +1,8 @@
namespace MLEM.Misc { using System;
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.
@ -138,5 +142,35 @@
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,11 +158,13 @@ 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;
for (var x = 0F; x < rect.Width; x += width) { if (width > 0 && height > 0) {
for (var y = 0F; y < rect.Height; y += height) { for (var x = 0F; x < rect.Width; x += width) {
var size = new Vector2(Math.Min(rect.Width - x, width), Math.Min(rect.Height - y, height)); for (var y = 0F; y < rect.Height; y += height) {
var srcSize = (size / patchScale).CeilCopy().ToPoint(); var size = new Vector2(Math.Min(rect.Width - x, width), Math.Min(rect.Height - y, height));
batch.Draw(texture.Region.Texture, new RectangleF(rect.Location + new Vector2(x, y), size), new Rectangle(src.X, src.Y, srcSize.X, srcSize.Y), color, rotation, origin, effects, layerDepth); var srcSize = (size / patchScale).CeilCopy().ToPoint();
batch.Draw(texture.Region.Texture, new RectangleF(rect.Location + new Vector2(x, y), size), new Rectangle(src.X, src.Y, srcSize.X, srcSize.Y), color, rotation, origin, effects, layerDepth);
}
} }
} }
break; break;

View file

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

View file

@ -11,6 +11,7 @@ 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,8 +10,6 @@
#-------------------------------- 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
@ -20,11 +18,6 @@
#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
@ -64,5 +57,3 @@
#begin Textures/Test.png #begin Textures/Test.png
/copy:Textures/Test.png /copy:Textures/Test.png

View file

@ -1,330 +0,0 @@
<?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

@ -1,274 +0,0 @@
<?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.

Before

Width:  |  Height:  |  Size: 46 KiB

View file

@ -1,123 +0,0 @@
<?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>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
@ -19,16 +19,11 @@
<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.2.8" /> <PackageReference Include="FontStashSharp.MonoGame" Version="1.3.3" />
<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>net7.0</TargetFramework> <TargetFramework>net8.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,24 +18,21 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\FNA\FNA.Core.csproj" /> <ProjectReference Include="..\ThirdParty\FNA\FNA.Core.csproj" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="coverlet.collector" Version="3.2.0"> <PackageReference Include="coverlet.collector" Version="6.0.0" />
<PrivateAssets>all</PrivateAssets> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PackageReference Include="NUnit" Version="3.14.0" />
</PackageReference> <PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" /> <PackageReference Include="NunitXml.TestLogger" Version="3.1.15" />
<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="../FnaNative/**"> <Content Include="../ThirdParty/Native/**">
<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>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<VSTestLogger>nunit</VSTestLogger> <VSTestLogger>nunit</VSTestLogger>
<VSTestResultsDirectory>TestResults</VSTestResultsDirectory> <VSTestResultsDirectory>TestResults</VSTestResultsDirectory>
<RunSettingsFilePath>Tests.runsettings</RunSettingsFilePath> <RunSettingsFilePath>Tests.runsettings</RunSettingsFilePath>
@ -20,14 +20,11 @@
<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="3.2.0"> <PackageReference Include="coverlet.collector" Version="6.0.0" />
<PrivateAssets>all</PrivateAssets> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PackageReference Include="NUnit" Version="3.14.0" />
</PackageReference> <PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="NunitXml.TestLogger" Version="3.1.15" />
<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 Submodule

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

1
ThirdParty/FontStashSharp vendored Submodule

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

BIN
ThirdParty/Native/FAudio.dll vendored Normal file

Binary file not shown.

BIN
ThirdParty/Native/FNA3D.dll vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
ThirdParty/Native/libFAudio.0.dylib vendored Normal file

Binary file not shown.

BIN
ThirdParty/Native/libFAudio.so.0 vendored Normal file

Binary file not shown.

BIN
ThirdParty/Native/libFNA3D.0.dylib vendored Normal file

Binary file not shown.

BIN
ThirdParty/Native/libFNA3D.so.0 vendored Normal file

Binary file not shown.

BIN
ThirdParty/Native/libSDL2-2.0.0.dylib vendored Normal file

Binary file not shown.

BIN
ThirdParty/Native/libSDL2-2.0.so.0 vendored Normal file

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.2.0"); var version = Argument("version", "6.3.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("CI_PIPELINE_NUMBER"); var buildNum = EnvironmentVariable("GITHUB_RUN_NUMBER");
if (buildNum != null) if (!string.IsNullOrEmpty(buildNum))
version += "-ci." + buildNum; version += "-ci." + buildNum;
} }
@ -25,7 +25,9 @@ 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);
@ -34,7 +36,8 @@ 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);
@ -49,7 +52,7 @@ Task("Pack").IsDependentOn("Test").Does(() => {
DotNetPack("MLEM.FNA.sln", settings); DotNetPack("MLEM.FNA.sln", settings);
}); });
Task("Push").WithCriteria(branch == "main" || branch == "release").IsDependentOn("Pack").Does(() => { Task("Push").WithCriteria(branch == "main" || branch == "release", "Not on main or release branch").IsDependentOn("Pack").Does(() => {
DotNetNuGetPushSettings settings; DotNetNuGetPushSettings settings;
if (branch == "release") { if (branch == "release") {
settings = new DotNetNuGetPushSettings { settings = new DotNetNuGetPushSettings {