mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-27 06:58:34 +01:00
Compare commits
75 commits
30160e8210
...
40a697a96c
Author | SHA1 | Date | |
---|---|---|---|
40a697a96c | |||
d85d6e8968 | |||
e4e7191d8d | |||
04050b9144 | |||
d81efe1d44 | |||
7b9b177453 | |||
f652854c1d | |||
d48b7e2e71 | |||
985dc74376 | |||
d69cd80b72 | |||
f5f925fab3 | |||
34cb5210b5 | |||
2627736283 | |||
5266d00796 | |||
2463c27a5d | |||
b2f457088d | |||
30432e43d4 | |||
3127ad5b74 | |||
f1740b7b32 | |||
0766220a8e | |||
a1064984ec | |||
a45a6adabd | |||
bccef1f7f4 | |||
e71450366b | |||
4863b5504b | |||
99b45b09d9 | |||
e623eff02d | |||
1d4a2ebdf7 | |||
e09484cbe7 | |||
230f2e954c | |||
b289bbd98e | |||
c77ec3765c | |||
a8e5c93fe4 | |||
39ade19d47 | |||
08931b49ac | |||
1f315def2d | |||
1e16c6fdc5 | |||
1b2cbb6afd | |||
5f02e701d9 | |||
2265af3fae | |||
1a7cb65cf2 | |||
4994bb3d5d | |||
6607a5f48c | |||
eda9531566 | |||
da2fab9b57 | |||
e0d4bb3472 | |||
db454ebd71 | |||
94a54c336e | |||
bef670c09b | |||
a4f00c9eed | |||
24a4c23be5 | |||
12af816a90 | |||
5086101bad | |||
8a4dc11072 | |||
5c8b535fe4 | |||
7a54e4aa2d | |||
a84fd764c5 | |||
3968f7dfae | |||
d8314877c8 | |||
b69a2c4755 | |||
fd642637a1 | |||
c4836eedd6 | |||
dbd7f66c89 | |||
695fba59a4 | |||
4029adb4bf | |||
a47d3f50cb | |||
f6beaff43a | |||
aef6f7bd58 | |||
7b2306f58f | |||
1d26cf017d | |||
6be4143331 | |||
7f7a9c6415 | |||
ca9c8e6cfd | |||
d450ec3082 | |||
4cfa105f75 |
130 changed files with 1556 additions and 1062 deletions
61
CHANGELOG.md
61
CHANGELOG.md
|
@ -2,6 +2,7 @@
|
|||
MLEM tries to adhere to [semantic versioning](https://semver.org/). Potentially breaking changes are written in **bold**.
|
||||
|
||||
Jump to version:
|
||||
- [6.2.0](#620)
|
||||
- [6.1.0](#610)
|
||||
- [6.0.0](#600)
|
||||
- [5.3.0](#530)
|
||||
|
@ -9,6 +10,62 @@ Jump to version:
|
|||
- [5.1.0](#510)
|
||||
- [5.0.0](#500)
|
||||
|
||||
## 6.2.0
|
||||
|
||||
### MLEM
|
||||
Additions
|
||||
- Added a simple outline formatting code
|
||||
- Added the ability to add inverse modifiers to a Keybind
|
||||
- Added GenericInput collections AllKeys, AllMouseButtons, AllButtons and AllInputs
|
||||
- Added TextFormatter.StripAllFormatting
|
||||
|
||||
Improvements
|
||||
- Increased TextFormatter macro recursion limit to 64
|
||||
- Allow changing the default values used by default TextFormatter codes
|
||||
- Allow setting ExternalGestureHandling through the InputHandler constructor
|
||||
- Allow specifying start and end indices when drawing a TokenizedString
|
||||
- Include control characters in TextInput FileNames and PathNames rules
|
||||
|
||||
Fixes
|
||||
- Fixed control characters being included in TextInput
|
||||
- Fixed TextInputs behaving incorrectly when switching between multiline and single-line modes
|
||||
- Fixed TextInput drawing characters with the wrong width if a masking character is used
|
||||
- Fixed a multiline TextInput's cursor not returning to the default position when the last character is removed
|
||||
- Fixed GetRandomWeightedEntry distribution not being equal for equal weights
|
||||
|
||||
Removals
|
||||
- Marked GetDownTime, GetUpTime and GetTimeSincePress in Keybind and Combination as obsolete
|
||||
|
||||
### MLEM.Ui
|
||||
Additions
|
||||
- Added AutoInlineCenter and AutoInlineBottom anchors
|
||||
- Added UiAnimation system
|
||||
- Added AddCustomStyle and ApplyCustomStyle to UiStyle to allow for easy custom styling of elements
|
||||
- Added UiControls.PressElement
|
||||
- Added TextField.EnterReceiver
|
||||
- Added a copy constructor to UiStyle
|
||||
|
||||
Improvements
|
||||
- Increased Element area calculation recursion limit to 64
|
||||
- Improved the SquishingGroup algorithm by prioritizing each element's final size
|
||||
- Allow specifying start and end indices when drawing a Paragraph
|
||||
- Allow elements with larger children to influence a panel's scrollable area
|
||||
- Remove all elements from a UiSystem when it is disposed
|
||||
- Made elements' ui styles be inherited by their children
|
||||
|
||||
Fixes
|
||||
- Fixed images not updating their hidden state properly when the displayed texture changes
|
||||
- Fixed AutoInline elements overflowing into their parent if it's taller
|
||||
- Fixed Paragraph and Checkbox not reacting to SquishingGroup sizing properly
|
||||
- Fixed TextInput and Slider still reacting to input when they are selected, but not part of the active root
|
||||
- Fixed dropdown menu panels not updating their width when the dropdown's width changes
|
||||
- Fixed removing and later adding children to a scrolling panel showing the scroll bar erroneously
|
||||
|
||||
### MLEM.Data
|
||||
Improvements
|
||||
- Improved RuntimeTexturePacker performance for differently sized textures
|
||||
- Allow querying the amount of RuntimeTexturePacker regions
|
||||
|
||||
## 6.1.0
|
||||
|
||||
### MLEM
|
||||
|
@ -114,7 +171,7 @@ Fixes
|
|||
Removals
|
||||
- Marked DynamicEnum as obsolete due to its reimplementation in [DynamicEnums](https://www.nuget.org/packages/DynamicEnums)
|
||||
|
||||
## MLEM.Extended
|
||||
### MLEM.Extended
|
||||
Additions
|
||||
- Added Range extension methods GetPercentage and FromPercentage
|
||||
|
||||
|
@ -123,7 +180,7 @@ Improvements
|
|||
- Added trimming and AOT annotations and made MLEM.Extended trimmable
|
||||
- **Made GenericBitmapFont and GenericStashFont support UTF-32 characters like emoji**
|
||||
|
||||
## MLEM.Startup
|
||||
### MLEM.Startup
|
||||
Improvements
|
||||
- Multi-target net452, making MLEM compatible with MonoGame for consoles
|
||||
- Added trimming and AOT annotations and made MLEM.Startup trimmable
|
||||
|
|
|
@ -3,31 +3,31 @@
|
|||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-mgcb": {
|
||||
"version": "3.8.1.263",
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor": {
|
||||
"version": "3.8.1.263",
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb-editor"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-linux": {
|
||||
"version": "3.8.1.263",
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb-editor-linux"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-windows": {
|
||||
"version": "3.8.1.263",
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb-editor-windows"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-mac": {
|
||||
"version": "3.8.1.263",
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb-editor-mac"
|
||||
]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
|
@ -47,10 +48,10 @@ public class Activity1 : AndroidGameActivity {
|
|||
base.OnWindowFocusChanged(hasFocus);
|
||||
// hide the status bar
|
||||
if (hasFocus) {
|
||||
#pragma warning disable CS0618
|
||||
#pragma warning disable CA1422
|
||||
// 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);
|
||||
#pragma warning restore CS0618
|
||||
#pragma warning restore CA1422
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="de.ellpeck.mlem.demos.android" android:versionCode="1" android:versionName="1.0">
|
||||
<uses-sdk android:minSdkVersion="23" android:targetSdkVersion="31" />
|
||||
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application android:label="MLEM Android Demos"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
</manifest>
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0-android</TargetFramework>
|
||||
<TargetFramework>net7.0-android</TargetFramework>
|
||||
<SupportedOSPlatformVersion>31</SupportedOSPlatformVersion>
|
||||
<OutputType>Exe</OutputType>
|
||||
<ApplicationId>de.ellpeck.mlem.demos.android</ApplicationId>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||
<ImplicitUsings>true</ImplicitUsings>
|
||||
<ApplicationVersion>100</ApplicationVersion>
|
||||
<ApplicationDisplayVersion>1.0.0</ApplicationDisplayVersion>
|
||||
<RunAOTCompilation>false</RunAOTCompilation>
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -3,31 +3,31 @@
|
|||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-mgcb": {
|
||||
"version": "3.8.1.263",
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor": {
|
||||
"version": "3.8.1.263",
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb-editor"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-linux": {
|
||||
"version": "3.8.1.263",
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb-editor-linux"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-windows": {
|
||||
"version": "3.8.1.263",
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb-editor-windows"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-mac": {
|
||||
"version": "3.8.1.263",
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb-editor-mac"
|
||||
]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ApplicationIcon>Icon.ico</ApplicationIcon>
|
||||
<AssemblyName>MLEM Desktop Demos</AssemblyName>
|
||||
<RootNamespace>Demos.DesktopGL</RootNamespace>
|
||||
|
@ -29,7 +29,7 @@
|
|||
<Content Include="..\Demos\Content\*\**" />
|
||||
<EmbeddedResource Include="Icon.ico" />
|
||||
<EmbeddedResource Include="Icon.bmp" />
|
||||
<Content Include="FnaNative/**">
|
||||
<Content Include="../FnaNative/**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Link>%(Filename)%(Extension)</Link>
|
||||
</Content>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ApplicationIcon>Icon.ico</ApplicationIcon>
|
||||
<AssemblyName>MLEM Desktop Demos</AssemblyName>
|
||||
<IsPackable>false</IsPackable>
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -41,6 +41,27 @@
|
|||
/processorParam:TextureFormat=Compressed
|
||||
/build:Fonts/TestFontItalic.spritefont
|
||||
|
||||
#begin Fonts/Roboto.spritefont
|
||||
/importer:FontDescriptionImporter
|
||||
/processor:FontDescriptionProcessor
|
||||
/processorParam:PremultiplyAlpha=True
|
||||
/processorParam:TextureFormat=Compressed
|
||||
/build:Fonts/Roboto.spritefont
|
||||
|
||||
#begin Fonts/RobotoBold.spritefont
|
||||
/importer:FontDescriptionImporter
|
||||
/processor:FontDescriptionProcessor
|
||||
/processorParam:PremultiplyAlpha=True
|
||||
/processorParam:TextureFormat=Compressed
|
||||
/build:Fonts/RobotoBold.spritefont
|
||||
|
||||
#begin Fonts/RobotoItalic.spritefont
|
||||
/importer:FontDescriptionImporter
|
||||
/processor:FontDescriptionProcessor
|
||||
/processorParam:PremultiplyAlpha=True
|
||||
/processorParam:TextureFormat=Compressed
|
||||
/build:Fonts/RobotoItalic.spritefont
|
||||
|
||||
#begin Markdown.md
|
||||
/copy:Markdown.md
|
||||
|
||||
|
|
60
Demos/Content/Fonts/Roboto.spritefont
Normal file
60
Demos/Content/Fonts/Roboto.spritefont
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
This file contains an xml description of a font, and will be read by the XNA
|
||||
Framework Content Pipeline. Follow the comments to customize the appearance
|
||||
of the font in your game, and to change the characters which are available to draw
|
||||
with.
|
||||
-->
|
||||
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
|
||||
<Asset Type="Graphics:FontDescription">
|
||||
|
||||
<!--
|
||||
Modify this string to change the font that will be imported.
|
||||
-->
|
||||
<FontName>RobotoRegular.ttf</FontName>
|
||||
|
||||
<!--
|
||||
Size is a float value, measured in points. Modify this value to change
|
||||
the size of the font.
|
||||
-->
|
||||
<Size>32</Size>
|
||||
|
||||
<!--
|
||||
Spacing is a float value, measured in pixels. Modify this value to change
|
||||
the amount of spacing in between characters.
|
||||
-->
|
||||
<Spacing>0</Spacing>
|
||||
|
||||
<!--
|
||||
UseKerning controls the layout of the font. If this value is true, kerning information
|
||||
will be used when placing characters.
|
||||
-->
|
||||
<UseKerning>true</UseKerning>
|
||||
|
||||
<!--
|
||||
Style controls the style of the font. Valid entries are "Regular", "Bold", "Italic",
|
||||
and "Bold, Italic", and are case sensitive.
|
||||
-->
|
||||
<Style>Regular</Style>
|
||||
|
||||
<!--
|
||||
If you uncomment this line, the default character will be substituted if you draw
|
||||
or measure text that contains characters which were not included in the font.
|
||||
-->
|
||||
<DefaultCharacter>*</DefaultCharacter>
|
||||
|
||||
<!--
|
||||
CharacterRegions control what letters are available in the font. Every
|
||||
character from Start to End will be built and made available for drawing. The
|
||||
default range is from 32, (ASCII space), to 126, ('~'), covering the basic Latin
|
||||
character set. The characters are ordered according to the Unicode standard.
|
||||
See the documentation for more information.
|
||||
-->
|
||||
<CharacterRegions>
|
||||
<CharacterRegion>
|
||||
<Start> </Start>
|
||||
<End>ɏ</End>
|
||||
</CharacterRegion>
|
||||
</CharacterRegions>
|
||||
</Asset>
|
||||
</XnaContent>
|
60
Demos/Content/Fonts/RobotoBold.spritefont
Normal file
60
Demos/Content/Fonts/RobotoBold.spritefont
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
This file contains an xml description of a font, and will be read by the XNA
|
||||
Framework Content Pipeline. Follow the comments to customize the appearance
|
||||
of the font in your game, and to change the characters which are available to draw
|
||||
with.
|
||||
-->
|
||||
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
|
||||
<Asset Type="Graphics:FontDescription">
|
||||
|
||||
<!--
|
||||
Modify this string to change the font that will be imported.
|
||||
-->
|
||||
<FontName>RobotoBold.ttf</FontName>
|
||||
|
||||
<!--
|
||||
Size is a float value, measured in points. Modify this value to change
|
||||
the size of the font.
|
||||
-->
|
||||
<Size>32</Size>
|
||||
|
||||
<!--
|
||||
Spacing is a float value, measured in pixels. Modify this value to change
|
||||
the amount of spacing in between characters.
|
||||
-->
|
||||
<Spacing>0</Spacing>
|
||||
|
||||
<!--
|
||||
UseKerning controls the layout of the font. If this value is true, kerning information
|
||||
will be used when placing characters.
|
||||
-->
|
||||
<UseKerning>true</UseKerning>
|
||||
|
||||
<!--
|
||||
Style controls the style of the font. Valid entries are "Regular", "Bold", "Italic",
|
||||
and "Bold, Italic", and are case sensitive.
|
||||
-->
|
||||
<Style>Regular</Style>
|
||||
|
||||
<!--
|
||||
If you uncomment this line, the default character will be substituted if you draw
|
||||
or measure text that contains characters which were not included in the font.
|
||||
-->
|
||||
<DefaultCharacter>*</DefaultCharacter>
|
||||
|
||||
<!--
|
||||
CharacterRegions control what letters are available in the font. Every
|
||||
character from Start to End will be built and made available for drawing. The
|
||||
default range is from 32, (ASCII space), to 126, ('~'), covering the basic Latin
|
||||
character set. The characters are ordered according to the Unicode standard.
|
||||
See the documentation for more information.
|
||||
-->
|
||||
<CharacterRegions>
|
||||
<CharacterRegion>
|
||||
<Start> </Start>
|
||||
<End>ɏ</End>
|
||||
</CharacterRegion>
|
||||
</CharacterRegions>
|
||||
</Asset>
|
||||
</XnaContent>
|
BIN
Demos/Content/Fonts/RobotoBold.ttf
Normal file
BIN
Demos/Content/Fonts/RobotoBold.ttf
Normal file
Binary file not shown.
60
Demos/Content/Fonts/RobotoItalic.spritefont
Normal file
60
Demos/Content/Fonts/RobotoItalic.spritefont
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
This file contains an xml description of a font, and will be read by the XNA
|
||||
Framework Content Pipeline. Follow the comments to customize the appearance
|
||||
of the font in your game, and to change the characters which are available to draw
|
||||
with.
|
||||
-->
|
||||
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
|
||||
<Asset Type="Graphics:FontDescription">
|
||||
|
||||
<!--
|
||||
Modify this string to change the font that will be imported.
|
||||
-->
|
||||
<FontName>RobotoItalic.ttf</FontName>
|
||||
|
||||
<!--
|
||||
Size is a float value, measured in points. Modify this value to change
|
||||
the size of the font.
|
||||
-->
|
||||
<Size>32</Size>
|
||||
|
||||
<!--
|
||||
Spacing is a float value, measured in pixels. Modify this value to change
|
||||
the amount of spacing in between characters.
|
||||
-->
|
||||
<Spacing>0</Spacing>
|
||||
|
||||
<!--
|
||||
UseKerning controls the layout of the font. If this value is true, kerning information
|
||||
will be used when placing characters.
|
||||
-->
|
||||
<UseKerning>true</UseKerning>
|
||||
|
||||
<!--
|
||||
Style controls the style of the font. Valid entries are "Regular", "Bold", "Italic",
|
||||
and "Bold, Italic", and are case sensitive.
|
||||
-->
|
||||
<Style>Regular</Style>
|
||||
|
||||
<!--
|
||||
If you uncomment this line, the default character will be substituted if you draw
|
||||
or measure text that contains characters which were not included in the font.
|
||||
-->
|
||||
<DefaultCharacter>*</DefaultCharacter>
|
||||
|
||||
<!--
|
||||
CharacterRegions control what letters are available in the font. Every
|
||||
character from Start to End will be built and made available for drawing. The
|
||||
default range is from 32, (ASCII space), to 126, ('~'), covering the basic Latin
|
||||
character set. The characters are ordered according to the Unicode standard.
|
||||
See the documentation for more information.
|
||||
-->
|
||||
<CharacterRegions>
|
||||
<CharacterRegion>
|
||||
<Start> </Start>
|
||||
<End>ɏ</End>
|
||||
</CharacterRegion>
|
||||
</CharacterRegions>
|
||||
</Asset>
|
||||
</XnaContent>
|
BIN
Demos/Content/Fonts/RobotoItalic.ttf
Normal file
BIN
Demos/Content/Fonts/RobotoItalic.ttf
Normal file
Binary file not shown.
BIN
Demos/Content/Fonts/RobotoRegular.ttf
Normal file
BIN
Demos/Content/Fonts/RobotoRegular.ttf
Normal file
Binary file not shown.
|
@ -15,18 +15,27 @@ namespace Demos {
|
|||
|
||||
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" +
|
||||
"You can write in <b>bold</i>, <i>italics</i>, <u>with an underline</u>, <st>strikethrough</st>, with a <s #000000 4>drop shadow</s> whose <s #ff0000 4>color</s> and <s #000000 10>offset</s> you can modify in each application of the code, or with various types of <b>combined <c Pink>formatting</c> codes</b>.\n\n" +
|
||||
"You can write in <b>bold</i>, <i>italics</i>, <u>with an underline</u>, <st>strikethrough</st>, with a <s>drop shadow</s> whose <s #ff0000 4>color</s> and <s #000000 10>offset</s> you can modify in each application of the code, with an <o>outline</o> that you can also <o #ff0000 4>modify</o> <o #ff00ff 2>dynamically</o>, or with various types of <b>combined <c Pink>formatting</c> codes</b>.\n\n" +
|
||||
"You can apply <c CornflowerBlue>custom</c> <c Yellow>colors</c> to text, including all default <c Orange>MonoGame colors</c> and <c #aabb00>inline custom colors</c>.\n\n" +
|
||||
"You can 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" +
|
||||
"Additionally, the text formatter has various methods for interacting with the text, like custom behaviors when hovering over certain parts, and more.";
|
||||
private const float Scale = 0.5F;
|
||||
private const float Width = 0.9F;
|
||||
private const float DefaultScale = 0.5F;
|
||||
private const float WidthMultiplier = 0.9F;
|
||||
|
||||
private TextFormatter formatter;
|
||||
private TokenizedString tokenizedText;
|
||||
private GenericFont font;
|
||||
private bool drawBounds;
|
||||
private float Scale {
|
||||
get {
|
||||
// calculate our scale based on how much larger the window is, so that the text scales with the window
|
||||
var viewport = new Rectangle(0, 0, this.Game.Window.ClientBounds.Width, this.Game.Window.ClientBounds.Height);
|
||||
return TextFormattingDemo.DefaultScale * Math.Min(viewport.Width / 1280F, viewport.Height / 720F);
|
||||
}
|
||||
}
|
||||
private int startIndex;
|
||||
private int endIndex;
|
||||
|
||||
public TextFormattingDemo(MlemGame game) : base(game) {}
|
||||
|
||||
|
@ -34,13 +43,16 @@ namespace Demos {
|
|||
this.Game.Window.ClientSizeChanged += this.OnResize;
|
||||
|
||||
// creating a new text formatter as well as a generic font to draw with
|
||||
this.formatter = new TextFormatter();
|
||||
this.formatter = new TextFormatter {
|
||||
DefaultShadowOffset = new Vector2(4),
|
||||
DefaultOutlineThickness = 4
|
||||
};
|
||||
// GenericFont and its subtypes are wrappers around various font classes, including SpriteFont, MonoGame.Extended's BitmapFont and FontStashSharp
|
||||
// supplying a bold and italic version of the font here allows for the bold and italic formatting codes to be used
|
||||
this.font = new GenericSpriteFont(
|
||||
Demo.LoadContent<SpriteFont>("Fonts/TestFont"),
|
||||
Demo.LoadContent<SpriteFont>("Fonts/TestFontBold"),
|
||||
Demo.LoadContent<SpriteFont>("Fonts/TestFontItalic"));
|
||||
Demo.LoadContent<SpriteFont>("Fonts/Roboto"),
|
||||
Demo.LoadContent<SpriteFont>("Fonts/RobotoBold"),
|
||||
Demo.LoadContent<SpriteFont>("Fonts/RobotoItalic"));
|
||||
|
||||
// adding the image code used in the example to it
|
||||
var testTexture = Demo.LoadContent<Texture2D>("Textures/Test");
|
||||
|
@ -49,7 +61,8 @@ namespace Demos {
|
|||
// tokenizing our text and splitting it to fit the screen
|
||||
// we specify our text alignment here too, so that all data is cached correctly for display
|
||||
this.tokenizedText = this.formatter.Tokenize(this.font, TextFormattingDemo.Text, TextAlignment.Center);
|
||||
this.tokenizedText.Split(this.font, this.GraphicsDevice.Viewport.Width * TextFormattingDemo.Width, TextFormattingDemo.Scale, TextAlignment.Center);
|
||||
this.tokenizedText.Split(this.font, this.GraphicsDevice.Viewport.Width * TextFormattingDemo.WidthMultiplier, this.Scale, TextAlignment.Center);
|
||||
this.endIndex = this.tokenizedText.String.Length;
|
||||
}
|
||||
|
||||
public override void DoDraw(GameTime time) {
|
||||
|
@ -58,7 +71,7 @@ namespace Demos {
|
|||
|
||||
// we draw the tokenized text in the center of the screen
|
||||
// since the text is already center-aligned, we only need to align it on the y axis here
|
||||
var size = this.tokenizedText.GetArea(Vector2.Zero, TextFormattingDemo.Scale).Size;
|
||||
var size = this.tokenizedText.GetArea(Vector2.Zero, this.Scale).Size;
|
||||
var pos = new Vector2(this.GraphicsDevice.Viewport.Width / 2, (this.GraphicsDevice.Viewport.Height - size.Y) / 2);
|
||||
|
||||
// draw bounds, which can be toggled with B in this demo
|
||||
|
@ -66,13 +79,13 @@ namespace Demos {
|
|||
var blank = this.SpriteBatch.GetBlankTexture();
|
||||
this.SpriteBatch.Draw(blank, new RectangleF(pos - new Vector2(size.X / 2, 0), size), Color.Red * 0.25F);
|
||||
foreach (var token in this.tokenizedText.Tokens) {
|
||||
foreach (var area in token.GetArea(pos, TextFormattingDemo.Scale))
|
||||
foreach (var area in token.GetArea(pos, this.Scale))
|
||||
this.SpriteBatch.Draw(blank, area, Color.Black * 0.25F);
|
||||
}
|
||||
}
|
||||
|
||||
// draw the text itself
|
||||
this.tokenizedText.Draw(time, this.SpriteBatch, pos, this.font, Color.White, TextFormattingDemo.Scale, 0);
|
||||
// draw the text itself (start and end indices are optional)
|
||||
this.tokenizedText.Draw(time, this.SpriteBatch, pos, this.font, Color.White, this.Scale, 0, this.startIndex, this.endIndex);
|
||||
|
||||
this.SpriteBatch.End();
|
||||
}
|
||||
|
@ -80,8 +93,18 @@ namespace Demos {
|
|||
public override void Update(GameTime time) {
|
||||
// update our tokenized string to animate the animation codes
|
||||
this.tokenizedText.Update(time);
|
||||
|
||||
// change some demo showcase info based on keybinds
|
||||
if (this.InputHandler.IsPressed(Keys.B))
|
||||
this.drawBounds = !this.drawBounds;
|
||||
if (this.startIndex > 0 && this.InputHandler.IsDown(Keys.Left))
|
||||
this.startIndex--;
|
||||
if (this.startIndex < this.tokenizedText.String.Length && this.InputHandler.IsDown(Keys.Right))
|
||||
this.startIndex++;
|
||||
if (this.endIndex > 0 && this.InputHandler.IsDown(Keys.Down))
|
||||
this.endIndex--;
|
||||
if (this.endIndex < this.tokenizedText.String.Length && this.InputHandler.IsDown(Keys.Up))
|
||||
this.endIndex++;
|
||||
}
|
||||
|
||||
public override void Clear() {
|
||||
|
@ -92,7 +115,7 @@ namespace Demos {
|
|||
private void OnResize(object sender, EventArgs e) {
|
||||
// re-split our text if the window resizes, since it depends on the window size
|
||||
// this doesn't require re-tokenization of the text, since TokenizedString also stores the un-split version
|
||||
this.tokenizedText.Split(this.font, this.GraphicsDevice.Viewport.Width * TextFormattingDemo.Width, TextFormattingDemo.Scale, TextAlignment.Center);
|
||||
this.tokenizedText.Split(this.font, this.GraphicsDevice.Viewport.Width * TextFormattingDemo.WidthMultiplier, this.Scale, TextAlignment.Center);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -164,21 +164,13 @@ namespace Demos {
|
|||
PositionOffset = new Vector2(0, 1)
|
||||
});
|
||||
// Another button that shows animations!
|
||||
var fancyHoverTimer = 0D;
|
||||
var fancyButton = this.root.AddChild(new Button(Anchor.AutoCenter, new Vector2(0.5F, 10), "Fancy Hover") {
|
||||
this.root.AddChild(new Button(Anchor.AutoCenter, new Vector2(0.5F, 10), "Fancy Hover") {
|
||||
PositionOffset = new Vector2(0, 1),
|
||||
OnUpdated = (e, time) => {
|
||||
if (e.IsMouseOver && fancyHoverTimer <= 0.5F)
|
||||
return;
|
||||
if (fancyHoverTimer > 0) {
|
||||
fancyHoverTimer -= time.ElapsedGameTime.TotalSeconds * 3;
|
||||
e.ScaleTransform(1 + (float) Math.Sin(fancyHoverTimer * MathHelper.Pi) * 0.05F);
|
||||
} else {
|
||||
e.Transform = Matrix.Identity;
|
||||
}
|
||||
MouseEnterAnimation = new UiAnimation(0.15, (a, e, p) => e.ScaleTransform(1 + Easings.OutSine(p) * 0.05F)),
|
||||
MouseExitAnimation = new UiAnimation(0.15, (a, e, p) => e.ScaleTransform(1 + Easings.OutSine.ReverseOutput()(p) * 0.05F)) {
|
||||
Finished = (a, e) => e.Transform = Matrix.Identity
|
||||
}
|
||||
});
|
||||
fancyButton.OnMouseEnter += e => fancyHoverTimer = 1;
|
||||
this.root.AddChild(new Button(Anchor.AutoCenter, new Vector2(0.5F, 10), "Transform Ui", "This button causes the entire ui to be transformed (both in positioning, rotation and scale)") {
|
||||
OnPressed = element => {
|
||||
if (element.Root.Transform == Matrix.Identity) {
|
||||
|
@ -242,7 +234,10 @@ namespace Demos {
|
|||
this.root.AddChild(new VerticalSpace(3));
|
||||
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "MLEM.Ui also contains a simple Markdown parser, which can be useful for displaying things like changelogs in your game."));
|
||||
this.root.AddChild(new VerticalSpace(3));
|
||||
var parser = new UiMarkdownParser {GraphicsDevice = this.GraphicsDevice};
|
||||
var parser = new UiMarkdownParser {
|
||||
GraphicsDevice = this.GraphicsDevice,
|
||||
ImageExceptionHandler = (s, e) => Console.Error.WriteLine($"Couldn't load image {s}: {e}")
|
||||
};
|
||||
using (var reader = new StreamReader(TitleContainer.OpenStream("Content/Markdown.md")))
|
||||
parser.ParseInto(reader.ReadToEnd(), this.root);
|
||||
|
||||
|
|
|
@ -23,6 +23,12 @@ var split = spriteFont.SplitString("This is a really long line of text [...]", w
|
|||
spriteFont.DrawString(this.SpriteBatch, split, new Vector2(10, 10), Color.White);
|
||||
```
|
||||
|
||||
Alternatively, the `SplitStringSeparate` method returns a collection of strings, where each entry represents a place where a split has been introduced. Using this method, you can differentiate between pre-existing newline characters and newly introduced ones.
|
||||
```cs
|
||||
var split = spriteFont.SplitStringSeparate("This is a line of text that contains\nnewline characters!", width: 10, scale: 1);
|
||||
// returns something like ["This is a line of ", "text that contains\nnewline characters!"]
|
||||
```
|
||||
|
||||
## Truncating
|
||||
Using generic fonts, a long line of text can also be truncated to fit a certain width in pixels. The remaining text that doesn't fit will simply be chopped off of the end (or start) of the string.
|
||||
```cs
|
||||
|
|
|
@ -4,10 +4,12 @@ The **MLEM** base package features an extended `InputHandler` class that allows
|
|||
|
||||
Rather than using an event-based structure, the MLEM input handler relies on the game's `Update` frames: To query input through the input handler, you have to query it every Update frame, and input information will only be available for a single update frame in most situations.
|
||||
|
||||
The input handler makes use of the `GenericInput` struct, which is a MLEM wrapper around the three main types of input that MonoGame and FNA provide: `Keys`, `Buttons` and `MouseButton` (the latter of which is a MLEM abstraction of mouse buttons). Values of all of these types can be converted into `GenericInput` implicitly, and a `GenericInput` can be converted back implicitly as well, so you will rarely ever have to interact with the `GenericInput` type manually.
|
||||
|
||||
## Setting it up
|
||||
To set it up, all you have to do is create a new instance. The constructor optionally accepts parameters to enable or disable certain kinds of input.
|
||||
```cs
|
||||
this.InputHandler = new InputHandler();
|
||||
this.InputHandler = new InputHandler(gameInstance);
|
||||
```
|
||||
Additionally, you will have to call the input handler's `Update` method each update call of your game:
|
||||
```cs
|
||||
|
@ -15,7 +17,7 @@ this.InputHandler.Update();
|
|||
```
|
||||
|
||||
## Querying pressed keys
|
||||
A *pressed* key is a key that wasn't down the last update but is held down the current update. This behavior can be useful for things like ui buttons, where holding down the mouse button shouldn't constantly keep triggering the button.
|
||||
A *pressed* key is a key that wasn't down the last update but is held down the current update, which is essentially a positive-edge-triggered press. This behavior can be useful for things like ui buttons, where holding down the mouse button shouldn't constantly keep triggering the button.
|
||||
|
||||
You can query if any key, mouse button or gamepad button is pressed as follows:
|
||||
```cs
|
||||
|
@ -28,20 +30,52 @@ var gamepad = this.InputHandler.IsPressed(Buttons.A);
|
|||
var gamepad2 = this.InputHandler.IsPressed(Buttons.A, 2);
|
||||
```
|
||||
|
||||
Using the `InvertPressBehavior` flag, you can invert this behavior: If it is set to `true`, a key is considered pressed if it was down the last update, but is up in the current update. This is essentially a negative-edge-triggered press.
|
||||
|
||||
### Repeat events
|
||||
Keyboard and gamepad repeat events can be enabled or disabled through the `HandleKeyboardRepeats` and `HandleGamepadRepeats` properties in the input handler. Additionally, you can configure the time that it takes until the first repeat is triggered through the `KeyRepeatDelay` property, and you can configure the delay between repeat events through the `KeyRepeatRate` property.
|
||||
|
||||
When enabled, repeat events for *pressing* are automatically triggered. This means that calling `IsPressed` every update call would return `true` for a control that is being held down every `KeyRepeatRate` seconds after `KeyRepeatDelay` seconds have passed once.
|
||||
|
||||
## Consuming inputs
|
||||
Due to the fact that the input handler is query-based (rather than event-based), multiple pieces of code might query the same input each update, causing a single press to be misconstrued as multiple distinct inputs.
|
||||
|
||||
The input handler provides the methods `IsPressConsumed`, `IsPressedAvailable`, and `TryConsumePressed`. Calling `TryConsumePressed` on an input means that subsequent calls to `IsPressConsumed` and `IsPressedAvailable` will not return `true` until the next update frame:
|
||||
```cs
|
||||
// is this update frame's Up press consumed yet?
|
||||
var consumed = this.InputHandler.IsPressConsumed(Keys.Up);
|
||||
// is the Up key pressed, and its press not consumed yet?
|
||||
var available = this.InputHandler.IsPressedAvailable(Keys.Up);
|
||||
|
||||
// check whether the Up key is pressed and its press is available, and consume it
|
||||
if (this.InputHandler.TryConsumePressed(Keys.Up)) {
|
||||
// the press has been consumed by us, now do something with the press!
|
||||
}
|
||||
```
|
||||
|
||||
## Input metrics
|
||||
The input handler tracks additional data related to keyboard, gamepad, and mouse inputs, such as the amount of times that they have been down for. These metrics can be useful for implementing short-press and long-press behavior.
|
||||
|
||||
```cs
|
||||
// how long has the A key been up (or down) for the last time it was up (or down)?
|
||||
var upTime = this.InputHandler.GetUpTime(Keys.A);
|
||||
var downTime = this.InputHandler.GetDownTime(Keys.A);
|
||||
|
||||
// how long has it been since the A key was pressed?
|
||||
var timeSincePress = this.InputHandler.TryGetTimeSincePress(Keys.A);
|
||||
```
|
||||
|
||||
## Gesture handling
|
||||
MonoGame's default touch handling can be a bit wonky to deal with, so the input handler also provides a much better user experience for touch gesture input.
|
||||
MonoGame's default gesture handling (which is inherited from XNA) can be a little difficult to deal with. This is mainly due to the fact that gestures stay in the queue until they are queried (so they might be very old), and the fact that they can't be queried without being permanently removed from the queue.
|
||||
|
||||
Because of this, MLEM's input handler also provides a much more streamlined user experience for touch gesture input.
|
||||
|
||||
To enable touch input, the gestures you want to use first have to be enabled:
|
||||
```cs
|
||||
InputHandler.EnableGestures(GestureType.Tap);
|
||||
```
|
||||
|
||||
When enabled, a `GestureSample` will be available for the requested gesture type *the frame it is finished*. It can be accessed like so:
|
||||
When enabled, a `GestureSample` will be available for the requested gesture type *the update frame it is finished*. It can be accessed like so:
|
||||
```cs
|
||||
if (this.InputHandler.GetGesture(GestureType.Tap, out var sample)) {
|
||||
// The gesture happened this frame
|
||||
|
@ -52,6 +86,21 @@ if (this.InputHandler.GetGesture(GestureType.Tap, out var sample)) {
|
|||
```
|
||||
|
||||
### External gesture handling
|
||||
If your game already handles gestures through some other means, you might notice that one of the gesture handling methods stops working correctly. This is due to the fact that MonoGame's gesture querying system only supports each gesture to be queried once before it is removed from the queue.
|
||||
If your game already handles gestures through some other means, you might notice that one of the gesture handling methods stops working correctly. This is due to the fact that MonoGame's gesture querying system only supports each gesture being queried once before it is removed from the queue, which causes any additional queries for that gesture to fail.
|
||||
|
||||
If you want to continue using your own gesture handling, but still allow the `InputHandler` to use gestures (for [MLEM.Ui](ui.md), for example), you can set `GesturesExternal` to true in your `InputHandler`. Then, you can use `AddExternalGesture` to make the input handler aware of a gesture for the duration of the update frame that you added it on.
|
||||
The input handler's gesture handling does not have this problem, since gestures are kept around for an entire update frame no matter how many times they are queried, and gestures can be queried from multiple sources based on the expected gesture type. Because of this, it's generally recommended that you use the input handler's gesture system instead of the default one.
|
||||
|
||||
However, if you want to continue using your own gesture handling, but still allow the `InputHandler` to have access to gestures (for [MLEM.Ui](ui.md), for example), you can set `ExternalGestureHandling` to true in your `InputHandler`. Then, you can use `AddExternalGesture` to make the input handler aware of a gesture for the duration of the update frame that you added it on. As an example, you could modify your game's existing gesture handling like this:
|
||||
```cs
|
||||
while (TouchPanel.IsGestureAvailable) {
|
||||
var gesture = TouchPanel.ReadGesture();
|
||||
|
||||
// your game's existing gesture handling ...
|
||||
bool gestureConsumed = this.HandleGestureSomeWay(gesture);
|
||||
if (gestureConsumed)
|
||||
continue;
|
||||
|
||||
// pass the gesture onto the input handler if we didn't make use of it
|
||||
this.InputHandler.AddExternalGesture(gesture);
|
||||
}
|
||||
```
|
||||
|
|
|
@ -57,3 +57,17 @@ namespace Test {
|
|||
}
|
||||
```
|
||||
As `RawContentManager` automatically collects all raw content readers in the loaded assemblies, you don't have to register your custom reader anywhere.
|
||||
|
||||
## Environments without reflection or with trimming
|
||||
By default, the `RawContentManager` finds all types that extend `RawContentReader` in all loaded assemblies, so they don't have to be added manually. This won't work in environments like NativeAOT, where reflection isn't as readily available, or in assemblies that get trimmed.
|
||||
|
||||
If you're in an environment with this restriction, you can manually collect all the content readers that you plan on using and call the constructor that accepts a list of content readers instead:
|
||||
```csharp
|
||||
protected override void LoadContent() {
|
||||
var neededReaders = new List<RawContentReader> {
|
||||
new Texture2DReader(), new JsonReader() // ...
|
||||
};
|
||||
this.rawContent = new RawContentManager(this.Services, neededReaders);
|
||||
this.Components.Add(this.rawContent);
|
||||
}
|
||||
```
|
||||
|
|
|
@ -2,24 +2,28 @@
|
|||
|
||||
The **MLEM** package contains a simple text formatting system that supports coloring, bold and italic font modifiers, in-text icons and text animations.
|
||||
|
||||
Text formatting makes use of [generic fonts](font_extensions.md).
|
||||
Text formatting makes use of [generic fonts](font_extensions.md), and [MLEM.Ui](ui.md)'s `Paragraph` supports text formatting out of the box, but using it for your own text rendering is very simple.
|
||||
|
||||
It should also be noted that [MLEM.Ui](ui.md)'s `Paragraph` supports text formatting out of the box.
|
||||
[The demo](https://github.com/Ellpeck/MLEM/blob/main/Demos/TextFormattingDemo.cs) features plenty of examples of the formatting codes that are available by default, as well as examples of the ability to add custom codes and interact with formatted text.
|
||||
|
||||
## Formatting codes
|
||||
To format your text, you can insert *formatting codes* into it. Almost all of these codes are single letters surrounded by `<>`, and some formatting codes can accept additional parameters after their letter representation.
|
||||
|
||||
By default, the following formatting options are available:
|
||||
- Colors using `<c ColorName>`. All default MonoGame colors are supported, for example `<c CornflowerBlue>`. Reset using `</c>`.
|
||||
- Bold and italic text using `<b>` and `<i>`, respectively. Reset using `</b>` and `</i>`.
|
||||
- Drop shadows using `<s>`. Optional parameters for the shadow's color and positional offset are accepted: `<s #AARRGGBB 2.5>`. Reset using `</s>`.
|
||||
- Underlined and strikethrough text using `<u>` and `<st>`, respectively. Reset using `</u>` and `</st>`.
|
||||
- A wobbly sine wave animation using `<a wobbly>`. Optional parameters for the wobble's intensity and height are accepted: `<a wobbly 10 0.25>`. Reset using `</a>`.
|
||||
- **Colors** using `<c ColorName>`. All default MonoGame colors are supported, for example `<c CornflowerBlue>`. Reset using `</c>`.
|
||||
- **Bold** and *italic* text using `<b>` and `<i>`, respectively. Reset using `</b>` and `</i>`.
|
||||
- **Drop shadows** using `<s>`. Optional parameters for the shadow's color and positional offset are accepted: `<s #AARRGGBB 2.5>`. Reset using `</s>`.
|
||||
- **Underlined** and **strikethrough** text using `<u>` and `<st>`, respectively. Reset using `</u>` and `</st>`.
|
||||
- **Subscript** and **superscript** text using `<sub>` and `<sup>`, respectively. Reset using `</sub>` and `</sup>`.
|
||||
- **Text outlines** using `<o>`. Optional parameters for the outlines' color and thickness are accepted as well: `<o #ff0000 4>`. Reset using `</o>`.
|
||||
- A wobbly sine wave **animation** using `<a wobbly>`. Optional parameters for the wobble's intensity and height are accepted: `<a wobbly 10 0.25>`. Reset using `</a>`.
|
||||
|
||||
When using [MLEM.Ui](ui.md)'s `Paragraph`, these additional formatting options are available by default:
|
||||
- Hoverable and clickable links using `<l Url>`. Note that this code does not automatically change the color of the text. Reset using `</l>`.
|
||||
- Hoverable and clickable links using `<l Url>`. Reset using `</l>`.
|
||||
- Inline font changes using `<f FontName>`, with custom fonts gathered from `UiStyle.AdditionalFonts`. Reset using `</f>`.
|
||||
|
||||
If you only want to use your own formatting codes in your text formatter, the constructor allows disabling some or all of the default ones.
|
||||
|
||||
## Getting your text ready
|
||||
To get your text ready for rendering with formatting codes, it has to be tokenized. For that, you need to create a new text formatter first. Additionally, you need to have a [generic font](font_extensions.md) ready:
|
||||
```cs
|
||||
|
@ -34,15 +38,29 @@ Additionally, if you want your tokenized string to be split based on a certain m
|
|||
```cs
|
||||
tokenizedString.Split(font, maxWidth, scale);
|
||||
```
|
||||
|
||||
## Drawing the formatted text
|
||||
To draw your tokenized text, all you have to do is call its `Draw` method like so:
|
||||
```cs
|
||||
tokenizedString.Draw(gameTime, spriteBatch, position, font, color, scale, depth);
|
||||
```
|
||||
Note that, if your tokenized text contains any animations, you have to updated the tokenized string every `Update` call like so:
|
||||
Note that, if your tokenized text contains any animations, you have to update the tokenized string every `Update` call like so:
|
||||
```cs
|
||||
tokenizedString.Update(gameTime);
|
||||
```
|
||||
|
||||
## Interacting with formatted text
|
||||
The `TokenizedString` class also features several methods for querying and interacting with the drawing of formatted text:
|
||||
```cs
|
||||
// the token that is under queryPosition if the string is drawn at position
|
||||
var tokenUnderPos = tokenizedString.GetTokenUnderPos(position, queryPosition, scale);
|
||||
|
||||
foreach (var token in tokenizedString.Tokens) {
|
||||
// the area that the given token takes up
|
||||
var area = token.GetArea(position, scale);
|
||||
}
|
||||
```
|
||||
|
||||
## Adding custom codes
|
||||
Adding custom formatting codes is easy! There are two things that a custom formatting code requires:
|
||||
- A class that extends `Code` that does what your formatting code should do (we'll use `MyCustomCode` in this case)
|
||||
|
@ -61,10 +79,11 @@ formatter.AddImage("ImageName", new TextureRegion(texture, 0, 0, 8, 8));
|
|||
After doing so, the image can be displayed using the code `<i ImageName>`.
|
||||
|
||||
## Macros
|
||||
The text formatting system additionally supports macros: Regular expressions that cause the matched text to expand into a different string. Macros are resolved recursively, meaning that you can have macros that resolve into other macros, and so on.
|
||||
The text formatting system additionally supports macros: Regular expressions that cause the matched text to expand into a different string. Macros are resolved recursively (up to 64 times), meaning that you can have macros that resolve into other macros as well.
|
||||
|
||||
By default, the following macros are available:
|
||||
- `~` expands into a non-breaking space, much like in LaTeX.
|
||||
- `<n>` expands into a newline character, if you like visual consistency with the other codes.
|
||||
|
||||
Adding custom macros is very similar to adding custom formatting codes:
|
||||
```cs
|
||||
|
|
|
@ -32,7 +32,7 @@ This means that, to check if the tile at tile coordinate `6, 10` contains any co
|
|||
```cs
|
||||
var tiles = collisions.GetCollidingTiles(new RectangleF(6, 10, 1, 1));
|
||||
```
|
||||
If the tile at that location is `16x16` pixels big and it has a single collision box at pixels `4, 4` that is `8x8` pixels big, then the following code prints out its percentaged coordinates: `X: 0.25, Y: 0.25, Width: 0.5, Height: 0.5`.
|
||||
If the tile at that location is `16x16` pixels big, and it has a single collision box at pixels `4, 4` that is `8x8` pixels big, then the following code prints out its percentaged coordinates: `X: 0.25, Y: 0.25, Width: 0.5, Height: 0.5`.
|
||||
```cs
|
||||
foreach (var tile in tiles)
|
||||
Console.WriteLine(tile.Collisions[0]);
|
||||
|
|
|
@ -1,16 +1,25 @@
|
|||
- name: MLEM.Ui
|
||||
href: ui.md
|
||||
- name: MLEM
|
||||
- name: Font Extensions
|
||||
href: font_extensions.md
|
||||
- name: Text Formatting
|
||||
href: text_formatting.md
|
||||
- name: Input Handler
|
||||
href: input.md
|
||||
- name: Raw Content Manager
|
||||
href: raw_content.md
|
||||
- name: Sprite Animations
|
||||
href: sprite_animations.md
|
||||
|
||||
- name: MLEM.Ui
|
||||
- name: MLEM.Ui
|
||||
href: ui.md
|
||||
|
||||
- name: MLEM.Extended
|
||||
- name: Tiled Extensions
|
||||
href: tiled_extensions.md
|
||||
|
||||
- name: MLEM.Data
|
||||
- name: Raw Content Manager
|
||||
href: raw_content.md
|
||||
|
||||
- name: MLEM.Startup
|
||||
- name: MLEM.Startup
|
||||
href: startup.md
|
|
@ -53,7 +53,7 @@ MlemPlatform.Current = new MlemPlatform.None();
|
|||
```
|
||||
Initializing the platform in this way also allows for links in paragraphs to be clickable, causing a browser or explorer window to be opened on desktop or mobile devices.
|
||||
|
||||
For more info on MLEM's platform-related code, you can also check out MlemPlatform's [documentation](https://mlem.ellpeck.de/api/MLEM.Misc.MlemPlatform).
|
||||
For more info on MLEM's platform-related code, you can also check out MlemPlatform's [documentation](xref:MLEM.Misc.MlemPlatform).
|
||||
|
||||
## Setting the style
|
||||
By default, MLEM.Ui's controls look pretty bland, since it doesn't ship with any fonts or textures for any of its controls. To change the style of your ui, simply expand your `new UntexturedStyle(this.SpriteBatch)` call to include fonts and textures of your choosing, for example:
|
||||
|
@ -92,5 +92,7 @@ this.UiSystem.Add("InfoBox", box);
|
|||
### About sizing
|
||||
Note that, when setting the width and height of any element, there are some things to note:
|
||||
- Each element has a `SetWidthBasedOnChildren` and a `SetHeightBasedOnChildren` property, which allow them to change their size automatically based on their content
|
||||
- When specifying a width or height *lower than or equal to 1*, it is seen as a percentage based on the parent's size instead. For example, a paragraph with a width of `0.5F` inside of a panel width a width of `200` will be `100` units wide.
|
||||
- When specifying a width or height *lower than or equal to 1*, it is seen as a percentage based on the parent's size instead. For example, a paragraph with a width of `0.5F` inside a panel width a width of `200` will be `100` units wide.
|
||||
- When specifying a width *lower than 0*, it is seen as a percentage based on the element's height, and vice versa. For example, a panel with a width of `200` and a height of `-2` will be `400` units tall.
|
||||
|
||||
A lot of other ways to modify the size of an object are available as well, including `TreatSizeAsMaximum`, `TreatSizeAsMinimum`, `PreventParentSpill` and more. For more information, the `Element` [documentation](xref:MLEM.Ui.Elements.Element) contains descriptions of all fields, properties, and methods.
|
||||
|
|
|
@ -1,14 +1,23 @@
|
|||
{
|
||||
"metadata": [{
|
||||
"src": [{
|
||||
"metadata": [
|
||||
{
|
||||
"src": [
|
||||
{
|
||||
"src": "../",
|
||||
"files": ["**/MLEM**.csproj"],
|
||||
"exclude": ["**.FNA.**"]
|
||||
}],
|
||||
"files": [
|
||||
"**/MLEM**.csproj"
|
||||
],
|
||||
"exclude": [
|
||||
"**.FNA.**"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dest": "api"
|
||||
}],
|
||||
}
|
||||
],
|
||||
"build": {
|
||||
"content": [{
|
||||
"content": [
|
||||
{
|
||||
"files": [
|
||||
"articles/**.md",
|
||||
"articles/**/toc.yml",
|
||||
|
@ -24,31 +33,30 @@
|
|||
"src": ".."
|
||||
}
|
||||
],
|
||||
"resource": [{
|
||||
"resource": [
|
||||
{
|
||||
"files": [
|
||||
"favicon.ico"
|
||||
]
|
||||
},
|
||||
{
|
||||
"files": ["*"],
|
||||
"files": [
|
||||
"*"
|
||||
],
|
||||
"src": "../Media"
|
||||
}
|
||||
],
|
||||
"globalMetadata": {
|
||||
"_appTitle": "MLEM Documentation",
|
||||
"_appLogoPath": "Logo.svg",
|
||||
"_appFooter": "<a href=\"https://github.com/Ellpeck/MLEM\">© 2019-2021 Ellpeck</a> – <a href=\"https://ellpeck.de/impressum\">Impressum</a> – <a href=\"https://ellpeck.de/privacy\">Privacy</a> – <a href=\"https://status.ellpeck.de\">Status</a>",
|
||||
"_appFooter": "<a href=\"https://github.com/Ellpeck/MLEM\">© 2019-2023 Ellpeck</a> – <a href=\"https://ellpeck.de/impressum\">Impressum</a> – <a href=\"https://ellpeck.de/privacy\">Privacy</a> – <a href=\"https://status.ellpeck.de\">Status</a>",
|
||||
"_enableSearch": true
|
||||
},
|
||||
"dest": "_site",
|
||||
"globalMetadataFiles": [],
|
||||
"fileMetadataFiles": [],
|
||||
"template": [
|
||||
"default",
|
||||
"templates/darkfx"
|
||||
],
|
||||
"postProcessors": [],
|
||||
"markdownEngineName": "markdig",
|
||||
"noLangKeyword": false
|
||||
"modern",
|
||||
"overrides"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,49 +1 @@
|
|||
![The MLEM logo](https://raw.githubusercontent.com/Ellpeck/MLEM/release/Media/Banner.png)
|
||||
|
||||
**MLEM Library for Extending MonoGame and FNA** is a set of multipurpose libraries for the game frameworks [MonoGame](https://www.monogame.net/) and [FNA](https://fna-xna.github.io/) that provides abstractions, quality of life improvements and additional features like an extensive ui system and easy input handling.
|
||||
|
||||
MLEM is platform-agnostic and multi-targets .NET Standard 2.0, .NET 6.0 and .NET Framework 4.5.2, which makes it compatible with MonoGame and FNA on Desktop, mobile devices and consoles.
|
||||
|
||||
# What next?
|
||||
- Get it on [NuGet](https://www.nuget.org/packages?q=mlem)
|
||||
- Get prerelease builds on [BaGet](https://nuget.ellpeck.de/?q=mlem)
|
||||
- See the source code on [GitHub](https://github.com/Ellpeck/MLEM)
|
||||
- See tutorials and API documentation on [the website](https://mlem.ellpeck.de/)
|
||||
- Check out [the demos](https://github.com/Ellpeck/MLEM/tree/release/Demos) on [Desktop](https://github.com/Ellpeck/MLEM/tree/release/Demos.DesktopGL) or [Android](https://github.com/Ellpeck/MLEM/tree/release/Demos.Android)
|
||||
- See [the changelog](https://mlem.ellpeck.de/CHANGELOG.html) for information on updates
|
||||
|
||||
# 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.Ui** provides a mouse, keyboard, gamepad and touch ready Ui system that features automatic anchoring, sizing and several ready-to-use element types
|
||||
- **MLEM.Extended** ties in with MonoGame.Extended and other MonoGame and FNA libraries
|
||||
- **MLEM.Data** provides simple loading and processing of textures and other data, including the ability to load non-XNB content files easily
|
||||
- **MLEM.Startup** combines MLEM with some other useful libraries into a quick Game startup class
|
||||
- **MLEM.Templates** contains cross-platform project templates
|
||||
|
||||
# Made with MLEM
|
||||
- [A Breath of Spring Air](https://ellpeck.itch.io/a-breath-of-spring-air), a short platformer ([Source](https://git.ellpeck.de/Ellpeck/GreatSpringGameJam))
|
||||
- [Don't Wake Up](https://ellpeck.itch.io/dont-wake-up), a short puzzle game ([Source](https://github.com/Ellpeck/DontLetGo))
|
||||
- [Pong Clone](https://github.com/luanfagu/pong), a very simple pong clone ([Source](https://github.com/luanfagu/pong))
|
||||
- [Tiny Life](https://tinylifegame.com), an isometric life simulation game ([Modding API](https://github.com/Ellpeck/TinyLifeExampleMod))
|
||||
|
||||
If you created a game with the help of MLEM, you can get it added to this list by submitting it on the [issue tracker](https://github.com/Ellpeck/MLEM/issues). If its source is public, other people will be able to use your project as an example, too!
|
||||
|
||||
# Gallery
|
||||
Here are some images that show a couple of MLEM's features.
|
||||
|
||||
The [MLEM.Ui](https://mlem.ellpeck.de/articles/ui) demo in action:
|
||||
|
||||
![A gif showing various user interface elements from the MLEM.Ui demo](https://raw.githubusercontent.com/Ellpeck/MLEM/release/Media/Ui.gif)
|
||||
|
||||
MLEM's [text formatting system](https://mlem.ellpeck.de/articles/text_formatting), which is compatible with both MLEM.Ui and regular sprite batch rendering:
|
||||
|
||||
![An image showing text with various colors and other formatting](https://raw.githubusercontent.com/Ellpeck/MLEM/release/Media/Formatting.png)
|
||||
|
||||
# Friends of MLEM
|
||||
There are several other libraries and tools that work well in combination with MonoGame, FNA and MLEM. Here are some of them:
|
||||
- [Contentless](https://github.com/Ellpeck/Contentless), a tool that removes the need to add assets to the MonoGame Content Pipeline manually
|
||||
- [GameBundle](https://github.com/Ellpeck/GameBundle), a tool that packages MonoGame and other .NET applications into several distributable formats
|
||||
- [MonoGame.Extended](https://github.com/craftworkgames/MonoGame.Extended), a package that also provides several additional features for MonoGame
|
||||
- [Coroutine](https://github.com/Ellpeck/Coroutine), a package that implements Unity-style coroutines for any project
|
||||
- [Illumilib](https://github.com/Ellpeck/Illumilib), a simple keyboard and mouse lighting library with support for Razer, Logitech and Corsair devices
|
||||
- [DynamicEnums](https://github.com/Ellpeck/DynamicEnums), which provides enum-like single-instance values with additional capabilities, including dynamic addition of new arbitrary values and flags
|
||||
[!INCLUDE [](../README.md)]
|
||||
|
|
11
Docs/overrides/conceptual.extension.js
Normal file
11
Docs/overrides/conceptual.extension.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
exports.preTransform = function (model) {
|
||||
if (model._path.includes("index")) {
|
||||
// point to the release branch in the readme
|
||||
model.conceptual = model.conceptual.replaceAll(/\/MLEM(\/[^/]+)?\/main\//g, "/MLEM$1/release/");
|
||||
|
||||
// reduce header levels by 1 to allow for TOC navigation
|
||||
for (let i = 5; i >= 1; i--)
|
||||
model.conceptual = model.conceptual.replaceAll(`<h${i}`, `<h${i + 1}`).replaceAll(`</h${i}`, `</h${i + 1}`);
|
||||
}
|
||||
return model;
|
||||
};
|
|
@ -1,40 +0,0 @@
|
|||
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
|
||||
|
||||
<div class="hidden-sm col-md-2" role="complementary">
|
||||
<div class="sideaffix">
|
||||
{{^_disableContribution}}
|
||||
<div class="contribution">
|
||||
<ul class="nav">
|
||||
{{#docurl}}
|
||||
<li>
|
||||
<a href="{{docurl}}" class="contribution-link">{{__global.improveThisDoc}}</a>
|
||||
</li>
|
||||
{{/docurl}}
|
||||
{{#sourceurl}}
|
||||
<li>
|
||||
<a href="{{sourceurl}}" class="contribution-link">{{__global.viewSource}}</a>
|
||||
</li>
|
||||
{{/sourceurl}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/_disableContribution}}
|
||||
<div class="toggle-mode">
|
||||
<div class="icon">
|
||||
<i aria-hidden="true">☀</i>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="switch-style">
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
<div class="icon">
|
||||
<i aria-hidden="true">☾</i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="bs-docs-sidebar hidden-print hidden-xs hidden-sm affix" id="affix">
|
||||
<h5>{{__global.inThisArticle}}</h5>
|
||||
<div></div>
|
||||
<!-- <p><a class="back-to-top" href="#top">Back to top</a><p> -->
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
|
@ -1,29 +0,0 @@
|
|||
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
|
||||
|
||||
<footer>
|
||||
<div class="grad-bottom"></div>
|
||||
<div class="footer">
|
||||
<div class="container">
|
||||
<span class="pull-right">
|
||||
<a href="#top">Back to top</a>
|
||||
</span>
|
||||
<div class="pull-left">
|
||||
{{{_appFooter}}}
|
||||
{{^_appFooter}}<span>Generated by <strong>DocFX</strong></span>{{/_appFooter}}
|
||||
</div>
|
||||
<div class="toggle-mode pull-right visible-sm visible-xs">
|
||||
<div class="icon">
|
||||
<i aria-hidden="true">☀</i>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="switch-style-m">
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
<div class="icon">
|
||||
<i aria-hidden="true">☾</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="{{_rel}}styles/toggle-theme.js"></script>
|
||||
</footer>
|
|
@ -1,20 +0,0 @@
|
|||
{{!Copyright (c) Oscar Vasquez. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}</title>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta name="title" content="{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}">
|
||||
<meta name="generator" content="docfx {{_docfxVersion}}">
|
||||
{{#_description}}<meta name="description" content="{{_description}}">{{/_description}}
|
||||
<link rel="shortcut icon" href="{{_rel}}{{{_appFaviconPath}}}{{^_appFaviconPath}}favicon.ico{{/_appFaviconPath}}">
|
||||
<link rel="stylesheet" href="{{_rel}}styles/docfx.vendor.css">
|
||||
<link rel="stylesheet" href="{{_rel}}styles/docfx.css">
|
||||
<link rel="stylesheet" href="{{_rel}}styles/main.css">
|
||||
<meta property="docfx:navrel" content="{{_navRel}}">
|
||||
<meta property="docfx:tocrel" content="{{_tocRel}}">
|
||||
{{#_noindex}}<meta name="searchOption" content="noindex">{{/_noindex}}
|
||||
{{#_enableSearch}}<meta property="docfx:rel" content="{{_rel}}">{{/_enableSearch}}
|
||||
{{#_enableNewTab}}<meta property="docfx:newtab" content="true">{{/_enableNewTab}}
|
||||
</head>
|
|
@ -1,470 +0,0 @@
|
|||
:root, body.dark-theme {
|
||||
--color-foreground: #ccd5dc;
|
||||
--color-navbar: #66666d;
|
||||
--color-breadcrumb: #999;
|
||||
--color-underline: #ddd;
|
||||
--color-toc-hover: #fff;
|
||||
--color-background: #2d2d30;
|
||||
--color-background-subnav: #333337;
|
||||
--color-background-dark: #1e1e1e;
|
||||
--color-background-table-alt: #212123;
|
||||
--color-background-quote: #69696e;
|
||||
}
|
||||
|
||||
body.light-theme {
|
||||
--color-foreground: #171717;
|
||||
--color-breadcrumb: #4a4a4a;
|
||||
--color-toc-hover: #4c4c4c;
|
||||
--color-background: #ffffff;
|
||||
--color-background-subnav: #f5f5f5;
|
||||
--color-background-dark: #ddd;
|
||||
--color-background-table-alt: #f9f9f9;
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--color-foreground);
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
word-wrap: break-word;
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
.btn.focus, .btn:focus, .btn:hover {
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 600;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 14px;
|
||||
padding: 10px 0px;
|
||||
}
|
||||
|
||||
article h1, article h2, article h3, article h4 {
|
||||
margin-top: 35px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
article h4 {
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--color-underline);
|
||||
}
|
||||
|
||||
.navbar-brand>img {
|
||||
color: var(--color-background);
|
||||
}
|
||||
|
||||
.navbar {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.subnav {
|
||||
border-top: 1px solid var(--color-underline);
|
||||
background-color: var(--color-background-subnav);
|
||||
}
|
||||
|
||||
.sidenav, .fixed_header, .toc {
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
.navbar-inverse {
|
||||
background-color: var(--color-background-dark);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav>li>a, .navbar-inverse .navbar-text {
|
||||
color: var(--color-navbar);
|
||||
background-color: var(--color-background-dark);
|
||||
border-bottom: 3px solid transparent;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav>li>a:focus, .navbar-inverse .navbar-nav>li>a:hover {
|
||||
color: var(--color-foreground);
|
||||
background-color: var(--color-background-dark);
|
||||
border-bottom: 3px solid var(--color-background-subnav);
|
||||
transition: all ease 0.25s;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav>.active>a, .navbar-inverse .navbar-nav>.active>a:focus, .navbar-inverse .navbar-nav>.active>a:hover {
|
||||
color: var(--color-foreground);
|
||||
background-color: var(--color-background-dark);
|
||||
border-bottom: 3px solid var(--color-foreground);
|
||||
transition: all ease 0.25s;
|
||||
}
|
||||
|
||||
.navbar-form .form-control {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.light-theme .navbar-brand svg {
|
||||
filter: brightness(20%);
|
||||
}
|
||||
|
||||
.toc .level1>li {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.toc .nav>li>a {
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
.sidefilter {
|
||||
background-color: var(--color-background);
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.sidefilter {
|
||||
background-color: var(--color-background);
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.toc-filter {
|
||||
padding: 10px;
|
||||
margin: 0;
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
.toc-filter>input {
|
||||
border: none;
|
||||
border-radius: unset;
|
||||
background-color: var(--color-background-subnav);
|
||||
padding: 5px 0 5px 20px;
|
||||
font-size: 90%
|
||||
}
|
||||
|
||||
.toc-filter>.clear-icon {
|
||||
position: absolute;
|
||||
top: 17px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
.toc-filter>input:focus {
|
||||
color: var(--color-foreground);
|
||||
transition: all ease 0.25s;
|
||||
}
|
||||
|
||||
.toc-filter>.filter-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidetoc>.toc {
|
||||
background-color: var(--color-background);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.sidetoc {
|
||||
background-color: var(--color-background);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.alert {
|
||||
background-color: inherit;
|
||||
border: none;
|
||||
padding: 10px 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.alert>p {
|
||||
margin-bottom: 0;
|
||||
padding: 5px 10px;
|
||||
border-bottom: 1px solid;
|
||||
background-color: var(--color-background-dark);
|
||||
}
|
||||
|
||||
.alert>h5 {
|
||||
padding: 10px 15px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
border-top: 2px solid;
|
||||
background-color: var(--color-background-dark);
|
||||
border-radius: none;
|
||||
}
|
||||
|
||||
.alert>ul {
|
||||
margin-bottom: 0;
|
||||
padding: 5px 40px;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
color: #f57f17;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
color: #d32f2f;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 9.5px;
|
||||
margin: 0 0 10px;
|
||||
font-size: 13px;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
background-color: var(--color-background-dark);
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
code {
|
||||
background: var(--color-background-dark) !important;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
.toc .nav>li.active>.expand-stub::before, .toc .nav>li.in>.expand-stub::before, .toc .nav>li.in.active>.expand-stub::before, .toc .nav>li.filtered>.expand-stub::before {
|
||||
content: "▾";
|
||||
}
|
||||
|
||||
.toc .nav>li>.expand-stub::before, .toc .nav>li.active>.expand-stub::before {
|
||||
content: "▸";
|
||||
}
|
||||
|
||||
.affix ul ul>li>a:before {
|
||||
content: "|";
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
background-color: var(--color-background-subnav);
|
||||
}
|
||||
|
||||
.breadcrumb .label.label-primary {
|
||||
background: #444;
|
||||
border-radius: 0;
|
||||
font-weight: normal;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
#breadcrumb .breadcrumb>li a {
|
||||
border-radius: 0;
|
||||
font-weight: normal;
|
||||
font-size: 85%;
|
||||
display: inline;
|
||||
padding: 0 .6em 0;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
color: var(--color-breadcrumb);
|
||||
}
|
||||
|
||||
#breadcrumb .breadcrumb>li a:hover {
|
||||
color: var(--color-foreground);
|
||||
transition: all ease 0.25s;
|
||||
}
|
||||
|
||||
.breadcrumb>li+li:before {
|
||||
content: "⯈";
|
||||
font-size: 75%;
|
||||
color: var(--color-background-dark);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.light-theme .breadcrumb>li+li:before {
|
||||
color: var(--color-foreground)
|
||||
}
|
||||
|
||||
.toc .level1>li {
|
||||
font-weight: 600;
|
||||
font-size: 130%;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
border-top: none;
|
||||
background-color: var(--color-background-dark);
|
||||
padding: 15px 0;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.toc .nav>li>a:hover, .toc .nav>li>a:focus {
|
||||
color: var(--color-toc-hover);
|
||||
transition: all ease 0.1s;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
background-color: var(--color-background-subnav);
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: #66afe9;
|
||||
outline: 0;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
input#search-query:focus {
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
.table-bordered, .table-bordered>tbody>tr>td, .table-bordered>tbody>tr>th, .table-bordered>tfoot>tr>td, .table-bordered>tfoot>tr>th, .table-bordered>thead>tr>td, .table-bordered>thead>tr>th {
|
||||
border: 1px solid var(--color-background-dark);
|
||||
}
|
||||
|
||||
.table-striped>tbody>tr:nth-of-type(odd) {
|
||||
background-color: var(--color-background-table-alt);
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding: 10px 20px;
|
||||
margin: 0 0 10px;
|
||||
font-size: 110%;
|
||||
border-left: 5px solid var(--color-background-quote);
|
||||
color: var(--color-background-quote);
|
||||
}
|
||||
|
||||
.pagination>.disabled>a, .pagination>.disabled>a:focus, .pagination>.disabled>a:hover, .pagination>.disabled>span, .pagination>.disabled>span:focus, .pagination>.disabled>span:hover {
|
||||
background-color: var(--color-background-subnav);
|
||||
border-color: var(--color-background-subnav);
|
||||
}
|
||||
|
||||
.breadcrumb>li, .pagination {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.tabGroup a[role="tab"] {
|
||||
border-bottom: 2px solid var(--color-background-dark);
|
||||
}
|
||||
|
||||
.tabGroup a[role="tab"][aria-selected="true"] {
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
.tabGroup section[role="tabpanel"] {
|
||||
border: 1px solid var(--color-background-dark);
|
||||
}
|
||||
|
||||
.sideaffix > div.contribution > ul > li > a.contribution-link:hover {
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
left: 4px;
|
||||
bottom: 3px;
|
||||
background-color: white;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #337ab7;
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 1px #337ab7;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
-webkit-transform: translateX(19px);
|
||||
-ms-transform: translateX(19px);
|
||||
transform: translateX(19px);
|
||||
}
|
||||
|
||||
/* Rounded sliders */
|
||||
.slider.round {
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.toggle-mode .icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.toggle-mode .icon i {
|
||||
font-style: normal;
|
||||
font-size: 17px;
|
||||
display: inline-block;
|
||||
padding-right: 7px;
|
||||
padding-left: 7px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@media (min-width: 1600px) {
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
.sidefilter {
|
||||
width: 18%;
|
||||
}
|
||||
.sidetoc {
|
||||
width: 18%;
|
||||
}
|
||||
.article.grid-right {
|
||||
margin-left: 19%;
|
||||
}
|
||||
.sideaffix {
|
||||
width: 11.5%;
|
||||
}
|
||||
.affix ul>li.active>a {
|
||||
white-space: initial;
|
||||
}
|
||||
.affix ul>li>a {
|
||||
width: 99%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
const sw = document.getElementById("switch-style"), sw_mobile = document.getElementById("switch-style-m"), b = document.body;
|
||||
if (b) {
|
||||
function toggleTheme(target, dark) {
|
||||
target.classList.toggle("dark-theme", dark)
|
||||
target.classList.toggle("light-theme", !dark)
|
||||
}
|
||||
|
||||
function switchEventListener() {
|
||||
toggleTheme(b, this.checked);
|
||||
if (window.localStorage) {
|
||||
this.checked ? localStorage.setItem("theme", "dark-theme") : localStorage.setItem("theme", "light-theme")
|
||||
}
|
||||
}
|
||||
|
||||
var isDarkTheme = !window.localStorage || !window.localStorage.getItem("theme") || window.localStorage && localStorage.getItem("theme") === "dark-theme";
|
||||
|
||||
if(sw && sw_mobile){
|
||||
sw.checked = isDarkTheme;
|
||||
sw_mobile.checked = isDarkTheme;
|
||||
|
||||
sw.addEventListener("change", switchEventListener);
|
||||
sw_mobile.addEventListener("change", switchEventListener);
|
||||
|
||||
// sync state between switches
|
||||
sw.addEventListener("change", function() {
|
||||
sw_mobile.checked = this.checked;
|
||||
});
|
||||
|
||||
sw_mobile.addEventListener("change", function() {
|
||||
sw.checked = this.checked;
|
||||
});
|
||||
}
|
||||
|
||||
toggleTheme(b, isDarkTheme);
|
||||
}
|
2
FNA
2
FNA
|
@ -1 +1 @@
|
|||
Subproject commit 9029e149358197612509e2ee4893870a3b5d590e
|
||||
Subproject commit 697cc63662914c0dc26c500bc9b8498b5ca8a68f
|
BIN
FnaNative/FAudio.dll
Normal file
BIN
FnaNative/FAudio.dll
Normal file
Binary file not shown.
BIN
FnaNative/FNA3D.dll
Normal file
BIN
FnaNative/FNA3D.dll
Normal file
Binary file not shown.
BIN
FnaNative/SDL2.dll
Normal file
BIN
FnaNative/SDL2.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
FnaNative/libFAudio.so.0
Normal file
BIN
FnaNative/libFAudio.so.0
Normal file
Binary file not shown.
BIN
FnaNative/libFNA3D.0.dylib
Normal file
BIN
FnaNative/libFNA3D.0.dylib
Normal file
Binary file not shown.
BIN
FnaNative/libFNA3D.so.0
Normal file
BIN
FnaNative/libFNA3D.so.0
Normal file
Binary file not shown.
Binary file not shown.
BIN
FnaNative/libSDL2-2.0.0.dylib
Normal file
BIN
FnaNative/libSDL2-2.0.0.dylib
Normal file
Binary file not shown.
BIN
FnaNative/libSDL2-2.0.so.0
Normal file
BIN
FnaNative/libSDL2-2.0.so.0
Normal file
Binary file not shown.
BIN
FnaNative/libvulkan.1.dylib
Normal file
BIN
FnaNative/libvulkan.1.dylib
Normal file
Binary file not shown.
|
@ -1 +1 @@
|
|||
Subproject commit c50bf544bcfb217b518727a0a38eb71fc5725092
|
||||
Subproject commit f11f97b709e50960dd8ce1f727974744c4f8a0dd
|
27
Jenkinsfile
vendored
27
Jenkinsfile
vendored
|
@ -1,4 +1,7 @@
|
|||
pipeline {
|
||||
agent none
|
||||
stages {
|
||||
stage('Cake') {
|
||||
agent any
|
||||
stages {
|
||||
stage('Submodules') {
|
||||
|
@ -6,7 +9,7 @@ pipeline {
|
|||
sh 'git submodule update --init --recursive --force'
|
||||
}
|
||||
}
|
||||
stage('Cake Build') {
|
||||
stage('Build') {
|
||||
steps {
|
||||
sh 'dotnet tool restore'
|
||||
// we use xvfb to allow for graphics-dependent tests
|
||||
|
@ -16,15 +19,7 @@ pipeline {
|
|||
stage('Document') {
|
||||
steps {
|
||||
sh 'dotnet cake --target Document --branch ' + env.BRANCH_NAME
|
||||
}
|
||||
}
|
||||
stage('Publish Docs') {
|
||||
when {
|
||||
branch 'release'
|
||||
}
|
||||
steps {
|
||||
sh 'rm -rf /var/www/MLEM/*'
|
||||
sh 'cp Docs/_site/** /var/www/MLEM/ -r'
|
||||
stash includes: 'Docs/_site/**', name: 'site'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +29,18 @@ pipeline {
|
|||
cobertura coberturaReportFile: '**/coverage.cobertura.xml'
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Publish Docs') {
|
||||
when { branch 'release' }
|
||||
agent { label 'web' }
|
||||
options { skipDefaultCheckout() }
|
||||
steps {
|
||||
unstash 'site'
|
||||
sh 'rm -rf /var/www/MLEM/*'
|
||||
sh 'cp Docs/_site/** /var/www/MLEM/ -r'
|
||||
}
|
||||
}
|
||||
}
|
||||
environment {
|
||||
BAGET = credentials('3db850d0-e6b5-43d5-b607-d180f4eab676')
|
||||
NUGET = credentials('e1bf7f6c-6047-4f7e-b639-15240a8f8351')
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019-2021 Ellpeck
|
||||
Copyright (c) 2019-2023 Ellpeck
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net452;netstandard2.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
<IsTrimmable>true</IsTrimmable>
|
||||
|
@ -42,6 +42,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
|
||||
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
|
||||
<None Include="../README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net452;netstandard2.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
<IsTrimmable>true</IsTrimmable>
|
||||
|
@ -26,7 +26,7 @@
|
|||
<PackageReference Include="Lidgren.Network" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2">
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json.Bson" Version="1.0.2">
|
||||
|
@ -39,6 +39,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
|
||||
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
|
||||
<None Include="../README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -33,10 +33,15 @@ namespace MLEM.Data {
|
|||
/// The time that <see cref="Pack"/> took the last time it was called
|
||||
/// </summary>
|
||||
public TimeSpan LastTotalTime => this.LastCalculationTime + this.LastPackTime;
|
||||
/// <summary>
|
||||
/// The amount of currently packed texture regions.
|
||||
/// </summary>
|
||||
public int PackedTextures => this.packedTextures.Count;
|
||||
|
||||
private readonly List<Request> texturesToPack = new List<Request>();
|
||||
private readonly List<Request> packedTextures = new List<Request>();
|
||||
private readonly Dictionary<Point, Point> firstPossiblePosForSize = new Dictionary<Point, Point>();
|
||||
private readonly Dictionary<Point, Request> occupiedPositions = new Dictionary<Point, Request>();
|
||||
private readonly Dictionary<Point, Point> initialPositions = new Dictionary<Point, Point>();
|
||||
private readonly Dictionary<Texture2D, TextureData> dataCache = new Dictionary<Texture2D, TextureData>();
|
||||
private readonly bool autoIncreaseMaxWidth;
|
||||
private readonly bool forcePowerOfTwo;
|
||||
|
@ -49,7 +54,7 @@ namespace MLEM.Data {
|
|||
/// Creates a new runtime texture packer with the given settings.
|
||||
/// </summary>
|
||||
/// <param name="maxWidth">The maximum width that the packed texture can have. Defaults to 2048.</param>
|
||||
/// <param name="autoIncreaseMaxWidth">Whether the maximum width should be increased if there is a texture to be packed that is wider than <see cref="maxWidth"/>. Defaults to false.</param>
|
||||
/// <param name="autoIncreaseMaxWidth">Whether the maximum width should be increased if there is a texture to be packed that is wider than the maximum width specified in the constructor. Defaults to false.</param>
|
||||
/// <param name="forcePowerOfTwo">Whether the resulting <see cref="PackedTexture"/> should have a width and height that is a power of two.</param>
|
||||
/// <param name="forceSquare">Whether the resulting <see cref="PackedTexture"/> should be square regardless of required size.</param>
|
||||
/// <param name="disposeTextures">Whether the original textures submitted to this texture packer should be disposed after packing.</param>
|
||||
|
@ -162,9 +167,7 @@ namespace MLEM.Data {
|
|||
// we pack larger textures first, so that smaller textures can fit in the gaps that larger ones leave
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
foreach (var request in this.texturesToPack.OrderByDescending(t => t.Texture.Width * t.Texture.Height)) {
|
||||
request.PackedArea = this.FindFreeArea(request);
|
||||
// if this is the first position that this request fit in, no other requests of the same size will find a position before it
|
||||
this.firstPossiblePosForSize[new Point(request.PackedArea.Width, request.PackedArea.Height)] = request.PackedArea.Location;
|
||||
request.PackedArea = this.OccupyFreeArea(request);
|
||||
this.packedTextures.Add(request);
|
||||
}
|
||||
stopwatch.Stop();
|
||||
|
@ -224,7 +227,8 @@ namespace MLEM.Data {
|
|||
this.LastPackTime = TimeSpan.Zero;
|
||||
this.texturesToPack.Clear();
|
||||
this.packedTextures.Clear();
|
||||
this.firstPossiblePosForSize.Clear();
|
||||
this.initialPositions.Clear();
|
||||
this.occupiedPositions.Clear();
|
||||
this.dataCache.Clear();
|
||||
}
|
||||
|
||||
|
@ -233,31 +237,45 @@ namespace MLEM.Data {
|
|||
this.Reset();
|
||||
}
|
||||
|
||||
private Rectangle FindFreeArea(Request request) {
|
||||
private Rectangle OccupyFreeArea(Request request) {
|
||||
var size = new Point(request.Texture.Width, request.Texture.Height);
|
||||
size.X += request.Padding * 2;
|
||||
size.Y += request.Padding * 2;
|
||||
|
||||
var pos = this.firstPossiblePosForSize.TryGetValue(size, out var first) ? first : Point.Zero;
|
||||
// exit early if the texture doesn't need to find a free location
|
||||
if (size.X <= 0 || size.Y <= 0)
|
||||
return Rectangle.Empty;
|
||||
|
||||
var pos = this.initialPositions.TryGetValue(size, out var first) ? first : Point.Zero;
|
||||
var area = new Rectangle(pos.X, pos.Y, size.X, size.Y);
|
||||
var lowestY = int.MaxValue;
|
||||
while (true) {
|
||||
var intersected = false;
|
||||
var area = new Rectangle(pos.X, pos.Y, size.X, size.Y);
|
||||
foreach (var tex in this.packedTextures) {
|
||||
if (tex.PackedArea.Intersects(area)) {
|
||||
pos.X = tex.PackedArea.Right;
|
||||
// when we move down, we want to move down by the smallest intersecting texture's height
|
||||
if (lowestY > tex.PackedArea.Bottom)
|
||||
lowestY = tex.PackedArea.Bottom;
|
||||
intersected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!intersected)
|
||||
// check if the current area is already occupied
|
||||
if (!this.occupiedPositions.TryGetValue(area.Location, out var existing)) {
|
||||
existing = this.packedTextures.FirstOrDefault(t => t.PackedArea.Intersects(area));
|
||||
// if no texture is occupying this space, we have found a free area
|
||||
if (existing == null) {
|
||||
// if this is the first position that this request fit in, no other requests of the same size will find a position before it
|
||||
this.initialPositions[new Point(area.Width, area.Height)] = area.Location;
|
||||
this.occupiedPositions.Add(area.Location, request);
|
||||
return area;
|
||||
if (pos.X + size.X > this.maxWidth) {
|
||||
pos.X = 0;
|
||||
pos.Y = lowestY;
|
||||
}
|
||||
|
||||
// also cache the existing texture for this position, in case we check it again in the future
|
||||
this.occupiedPositions.Add(area.Location, existing);
|
||||
}
|
||||
|
||||
// move to the right by the existing texture's width
|
||||
area.X = existing.PackedArea.Right;
|
||||
|
||||
// remember the smallest intersecting texture's height for when we move down
|
||||
if (lowestY > existing.PackedArea.Bottom)
|
||||
lowestY = existing.PackedArea.Bottom;
|
||||
|
||||
// move down a row if we exceed our maximum width
|
||||
if (area.Right > this.maxWidth) {
|
||||
area.X = 0;
|
||||
area.Y = lowestY;
|
||||
lowestY = int.MaxValue;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;net7.0</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
<IsTrimmable>true</IsTrimmable>
|
||||
|
@ -38,6 +38,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
|
||||
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
|
||||
<None Include="../README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;net7.0</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
<IsTrimmable>true</IsTrimmable>
|
||||
|
@ -37,6 +37,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
|
||||
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
|
||||
<None Include="../README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -190,7 +190,7 @@ namespace MLEM.Extended.Tiled {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate method used for <see cref="IndividualTiledMapRenderer.depthFunction"/>.
|
||||
/// A delegate method used for an <see cref="IndividualTiledMapRenderer"/>'s depth function.
|
||||
/// The idea is to return a depth (between 0 and 1) for the given tile that determines where in the sprite batch it should be rendererd.
|
||||
/// Note that, for this depth function to take effect, the sprite batch needs to begin with <see cref="SpriteSortMode.FrontToBack"/> or <see cref="SpriteSortMode.BackToFront"/>.
|
||||
/// </summary>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net452;netstandard2.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
<IsTrimmable>true</IsTrimmable>
|
||||
|
@ -33,6 +33,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
|
||||
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
|
||||
<None Include="../README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net452;netstandard2.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
<IsTrimmable>true</IsTrimmable>
|
||||
|
@ -20,7 +20,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Coroutine" Version="2.1.4" />
|
||||
<PackageReference Include="Coroutine" Version="2.1.5" />
|
||||
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.csproj" />
|
||||
<ProjectReference Include="..\MLEM\MLEM.csproj" />
|
||||
|
||||
|
@ -31,6 +31,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
|
||||
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
|
||||
<None Include="../README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net452;netstandard2.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
|
||||
<IncludeContentInPack>true</IncludeContentInPack>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
<ContentTargetFolders>content</ContentTargetFolders>
|
||||
|
@ -27,7 +27,7 @@
|
|||
<Content Include="content\**\*" Exclude="content\**\.DS_Store;content\**\bin;content\**\obj" />
|
||||
<Compile Remove="**\*" />
|
||||
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
|
||||
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
|
||||
<None Include="../README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -3,31 +3,31 @@
|
|||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-mgcb": {
|
||||
"version": "3.8.1.263",
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor": {
|
||||
"version": "3.8.1.263",
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb-editor"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-linux": {
|
||||
"version": "3.8.1.263",
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb-editor-linux"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-windows": {
|
||||
"version": "3.8.1.263",
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb-editor-windows"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-mac": {
|
||||
"version": "3.8.1.263",
|
||||
"version": "3.8.1.303",
|
||||
"commands": [
|
||||
"mgcb-editor-mac"
|
||||
]
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Contentless" Version="3.*" />
|
||||
<PackageReference Include="MLEM.Startup" Version="6.*" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.263" />
|
||||
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.1.263" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
|
||||
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.1.303" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MLEM.Startup" Version="6.*" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.263">
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
|
|
@ -60,7 +60,7 @@ namespace MLEM.Ui {
|
|||
AutoRight,
|
||||
/// <summary>
|
||||
/// This is an auto-anchoring value.
|
||||
/// This anchor will cause an element to be placed in the same line as its older sibling, or at the start of the next line if there is no space to the right of its older sibling.
|
||||
/// This anchor will cause an element to be placed at the top right of its older sibling, or at the start of the next line if there is no space to the right of its older sibling.
|
||||
/// </summary>
|
||||
AutoInline,
|
||||
/// <summary>
|
||||
|
@ -68,7 +68,29 @@ namespace MLEM.Ui {
|
|||
/// This anchor is an overflow-ignoring version of <see cref="AutoInline"/>, meaning that the element will never be forced into the next line.
|
||||
/// Note that, when using this property, it is very easy to cause an element to overflow out of its parent container.
|
||||
/// </summary>
|
||||
AutoInlineIgnoreOverflow
|
||||
AutoInlineIgnoreOverflow,
|
||||
/// <summary>
|
||||
/// This is an auto-anchoring value.
|
||||
/// This anchor will cause an element to be placed at the center right of its older sibling, or at the start of the next line if there is no space to the right of its older sibling.
|
||||
/// </summary>
|
||||
AutoInlineCenter,
|
||||
/// <summary>
|
||||
/// This is an auto-anchoring value.
|
||||
/// This anchor is an overflow-ignoring version of <see cref="AutoInlineCenter"/>, meaning that the element will never be forced into the next line.
|
||||
/// Note that, when using this property, it is very easy to cause an element to overflow out of its parent container.
|
||||
/// </summary>
|
||||
AutoInlineCenterIgnoreOverflow,
|
||||
/// <summary>
|
||||
/// This is an auto-anchoring value.
|
||||
/// This anchor will cause an element to be placed at the bottom right of its older sibling, or at the start of the next line if there is no space to the right of its older sibling.
|
||||
/// </summary>
|
||||
AutoInlineBottom,
|
||||
/// <summary>
|
||||
/// This is an auto-anchoring value.
|
||||
/// This anchor is an overflow-ignoring version of <see cref="AutoInlineBottom"/>, meaning that the element will never be forced into the next line.
|
||||
/// Note that, when using this property, it is very easy to cause an element to overflow out of its parent container.
|
||||
/// </summary>
|
||||
AutoInlineBottomIgnoreOverflow
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ namespace MLEM.Ui.Elements {
|
|||
public bool CanSelectDisabled;
|
||||
/// <summary>
|
||||
/// An optional function that can be used to modify the result of <see cref="IsDisabled"/> automatically based on a user-defined condition. This removes the need to disable a button based on a condition in <see cref="Element.OnUpdated"/> or manually.
|
||||
/// Note that, if <see cref="IsDisabled"/>'s underlying value is set to <see langword="true"/> using <see cref="set_IsDisabled"/>, this function's result will be ignored.
|
||||
/// Note that, if <see cref="IsDisabled"/>'s underlying value is set to <see langword="true"/> using <see cref="IsDisabled"/>, this function's result will be ignored.
|
||||
/// </summary>
|
||||
public Func<Button, bool> AutoDisableCondition;
|
||||
|
||||
|
|
|
@ -106,13 +106,12 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Vector2 CalcActualSize(RectangleF parentArea) {
|
||||
var size = base.CalcActualSize(parentArea);
|
||||
public override void SetAreaAndUpdateChildren(RectangleF area) {
|
||||
base.SetAreaAndUpdateChildren(area);
|
||||
if (this.Label != null) {
|
||||
this.Label.Size = new Vector2((size.X - size.Y) / this.Scale - this.TextOffsetX, 1);
|
||||
this.Label.PositionOffset = new Vector2(size.Y / this.Scale + this.TextOffsetX, 0);
|
||||
this.Label.Size = new Vector2((area.Width - area.Height) / this.Scale - this.TextOffsetX, 1);
|
||||
this.Label.PositionOffset = new Vector2(area.Height / this.Scale + this.TextOffsetX, 0);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -37,10 +37,13 @@ namespace MLEM.Ui.Elements {
|
|||
/// <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>
|
||||
public Dropdown(Anchor anchor, Vector2 size, string text = null, string tooltipText = null) : base(anchor, size, text, tooltipText) {
|
||||
this.Panel = this.AddChild(new Panel(Anchor.TopCenter, size, Vector2.Zero, true) {
|
||||
this.Panel = this.AddChild(new Panel(Anchor.TopCenter, Vector2.Zero, Vector2.Zero, true) {
|
||||
IsHidden = true
|
||||
});
|
||||
this.OnAreaUpdated += e => this.Panel.PositionOffset = new Vector2(0, e.Area.Height / this.Scale);
|
||||
this.OnAreaUpdated += e => {
|
||||
this.Panel.Size = new Vector2(e.Area.Width / e.Scale, 0);
|
||||
this.Panel.PositionOffset = new Vector2(0, e.Area.Height / e.Scale);
|
||||
};
|
||||
this.OnOpenedOrClosed += e => this.Priority = this.IsOpen ? 10000 : 0;
|
||||
this.OnPressed += e => {
|
||||
this.IsOpen = !this.IsOpen;
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace MLEM.Ui.Elements {
|
|||
private set {
|
||||
this.system = value;
|
||||
this.Controls = value?.Controls;
|
||||
this.Style = this.Style.OrStyle(value?.Style);
|
||||
this.AndChildren(e => e.Style = e.Style.OrStyle(value?.Style));
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
|
@ -320,9 +320,15 @@ namespace MLEM.Ui.Elements {
|
|||
public bool IsMouseOver => this.Controls.MousedElement == this || this.Controls.TouchedElement == this;
|
||||
/// <summary>
|
||||
/// Returns whether this element is its <see cref="Root"/>'s <see cref="RootElement.SelectedElement"/>.
|
||||
/// Note that, unlike <see cref="IsSelectedActive"/>, this property will be <see langword="true"/> even if this element's <see cref="Root"/> is not the <see cref="UiControls.ActiveRoot"/>.
|
||||
/// </summary>
|
||||
public bool IsSelected => this.Root.SelectedElement == this;
|
||||
/// <summary>
|
||||
/// Returns whether this element is its <see cref="Controls"/>'s <see cref="UiControls.SelectedElement"/>.
|
||||
/// Note that <see cref="IsSelected"/> can be used to query whether this element is its <see cref="Root"/>'s <see cref="RootElement.SelectedElement"/> instead.
|
||||
/// </summary>
|
||||
public bool IsSelectedActive => this.Controls.SelectedElement == this;
|
||||
/// <summary>
|
||||
/// Returns whether this element's <see cref="SetAreaDirty"/> method has been recently called and its area has not been updated since then using <see cref="UpdateAreaIfDirty"/> or <see cref="ForceUpdateArea"/>.
|
||||
/// </summary>
|
||||
public bool AreaDirty { get; private set; }
|
||||
|
@ -373,6 +379,14 @@ namespace MLEM.Ui.Elements {
|
|||
this.SetAreaDirty();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// A <see cref="UiAnimation"/> that is played when the mouse enters this element, in <see cref="OnMouseEnter"/>.
|
||||
/// </summary>
|
||||
public StyleProp<UiAnimation> MouseEnterAnimation;
|
||||
/// <summary>
|
||||
/// A <see cref="UiAnimation"/> that is played when the mouse exits this element, in <see cref="OnMouseExit"/>.
|
||||
/// </summary>
|
||||
public StyleProp<UiAnimation> MouseExitAnimation;
|
||||
|
||||
/// <summary>
|
||||
/// Event that is called after this element is drawn, but before its children are drawn
|
||||
|
@ -484,6 +498,12 @@ namespace MLEM.Ui.Elements {
|
|||
/// Use <see cref="AddChild{T}"/> or <see cref="RemoveChild"/> to manipulate this list while calling all of the necessary callbacks.
|
||||
/// </summary>
|
||||
protected readonly IList<Element> Children;
|
||||
/// <summary>
|
||||
/// A list of all of the <see cref="UiAnimation"/> instances that are currently playing.
|
||||
/// You can modify this collection through <see cref="PlayAnimation"/> and <see cref="StopAnimation"/>.
|
||||
/// </summary>
|
||||
protected readonly List<UiAnimation> PlayingAnimations = new List<UiAnimation>();
|
||||
|
||||
/// <summary>
|
||||
/// A sorted version of <see cref="Children"/>. The children are sorted by their <see cref="Priority"/>.
|
||||
/// </summary>
|
||||
|
@ -535,8 +555,16 @@ namespace MLEM.Ui.Elements {
|
|||
this.size = size;
|
||||
|
||||
this.Children = new ReadOnlyCollection<Element>(this.children);
|
||||
this.GetTabNextElement = (backward, next) => next;
|
||||
this.GetGamepadNextElement = (dir, next) => next;
|
||||
this.GetTabNextElement += (backward, next) => next;
|
||||
this.GetGamepadNextElement += (dir, next) => next;
|
||||
this.OnMouseEnter += e => {
|
||||
if (e.MouseEnterAnimation.HasValue())
|
||||
e.PlayAnimation(e.MouseEnterAnimation);
|
||||
};
|
||||
this.OnMouseExit += e => {
|
||||
if (e.MouseExitAnimation.HasValue())
|
||||
e.PlayAnimation(e.MouseExitAnimation);
|
||||
};
|
||||
|
||||
this.SetAreaDirty();
|
||||
this.SetSortedChildrenDirty();
|
||||
|
@ -673,7 +701,11 @@ namespace MLEM.Ui.Elements {
|
|||
case Anchor.TopLeft:
|
||||
case Anchor.AutoLeft:
|
||||
case Anchor.AutoInline:
|
||||
case Anchor.AutoInlineCenter:
|
||||
case Anchor.AutoInlineBottom:
|
||||
case Anchor.AutoInlineIgnoreOverflow:
|
||||
case Anchor.AutoInlineCenterIgnoreOverflow:
|
||||
case Anchor.AutoInlineBottomIgnoreOverflow:
|
||||
pos.X = parentArea.X + this.ScaledOffset.X;
|
||||
pos.Y = parentArea.Y + this.ScaledOffset.Y;
|
||||
break;
|
||||
|
@ -714,36 +746,33 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
if (this.Anchor.IsAuto()) {
|
||||
Element previousChild;
|
||||
if (this.Anchor == Anchor.AutoInline || this.Anchor == Anchor.AutoInlineIgnoreOverflow) {
|
||||
previousChild = this.GetOlderSibling(e => !e.IsHidden && e.CanAutoAnchorsAttach);
|
||||
} else {
|
||||
previousChild = this.GetLowestOlderSibling(e => !e.IsHidden && e.CanAutoAnchorsAttach);
|
||||
}
|
||||
if (previousChild != null) {
|
||||
var prevArea = previousChild.GetAreaForAutoAnchors();
|
||||
switch (this.Anchor) {
|
||||
case Anchor.AutoLeft:
|
||||
case Anchor.AutoCenter:
|
||||
case Anchor.AutoRight:
|
||||
pos.Y = prevArea.Bottom + this.ScaledOffset.Y;
|
||||
break;
|
||||
case Anchor.AutoInline:
|
||||
var newX = prevArea.Right + this.ScaledOffset.X;
|
||||
if (this.Anchor.IsInline()) {
|
||||
var anchorEl = this.GetOlderSibling(e => !e.IsHidden && e.CanAutoAnchorsAttach);
|
||||
if (anchorEl != null) {
|
||||
var anchorElArea = anchorEl.GetAreaForAutoAnchors();
|
||||
var newX = anchorElArea.Right + this.ScaledOffset.X;
|
||||
// with awkward ui scale values, floating point rounding can cause an element that would usually
|
||||
// be positioned correctly to be pushed into the next line due to a very small deviation
|
||||
if (newX + newSize.X <= parentArea.Right + Element.Epsilon) {
|
||||
if (this.Anchor.IsIgnoreOverflow() || newX + newSize.X <= parentArea.Right + Element.Epsilon) {
|
||||
pos.X = newX;
|
||||
pos.Y = prevArea.Y + this.ScaledOffset.Y;
|
||||
pos.Y = anchorElArea.Y + this.ScaledOffset.Y;
|
||||
if (this.Anchor == Anchor.AutoInlineCenter || this.Anchor == Anchor.AutoInlineCenterIgnoreOverflow) {
|
||||
pos.Y += (anchorElArea.Height - newSize.Y) / 2;
|
||||
} else if (this.Anchor == Anchor.AutoInlineBottom || this.Anchor == Anchor.AutoInlineBottomIgnoreOverflow) {
|
||||
pos.Y += anchorElArea.Height - newSize.Y;
|
||||
}
|
||||
} else {
|
||||
pos.Y = prevArea.Bottom + this.ScaledOffset.Y;
|
||||
// inline anchors that overflow into the next line act like AutoLeft
|
||||
var newlineAnchorEl = this.GetLowestOlderSibling(e => !e.IsHidden && e.CanAutoAnchorsAttach);
|
||||
if (newlineAnchorEl != null)
|
||||
pos.Y = newlineAnchorEl.GetAreaForAutoAnchors().Bottom + this.ScaledOffset.Y;
|
||||
}
|
||||
break;
|
||||
case Anchor.AutoInlineIgnoreOverflow:
|
||||
pos.X = prevArea.Right + this.ScaledOffset.X;
|
||||
pos.Y = prevArea.Y + this.ScaledOffset.Y;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// auto anchors keep their x coordinates from the switch above
|
||||
var anchorEl = this.GetLowestOlderSibling(e => !e.IsHidden && e.CanAutoAnchorsAttach);
|
||||
if (anchorEl != null)
|
||||
pos.Y = anchorEl.GetAreaForAutoAnchors().Bottom + this.ScaledOffset.Y;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -801,8 +830,8 @@ namespace MLEM.Ui.Elements {
|
|||
// we want to leave some leeway to prevent float rounding causing an infinite loop
|
||||
if (!autoSize.Equals(this.UnscrolledArea.Size, Element.Epsilon)) {
|
||||
recursion++;
|
||||
if (recursion >= 16)
|
||||
throw new ArithmeticException($"The area of {this} with root {this.Root.Name} has recursively updated too often. Does its child {foundChild} contain any conflicting auto-sizing settings?");
|
||||
if (recursion >= 64)
|
||||
throw new ArithmeticException($"The area of {this} has recursively updated too often. Does its child {foundChild} contain any conflicting auto-sizing settings?");
|
||||
UpdateDisplayArea(autoSize);
|
||||
}
|
||||
}
|
||||
|
@ -852,14 +881,16 @@ namespace MLEM.Ui.Elements {
|
|||
/// Returns this element's lowest child element (in terms of y position) that matches the given condition.
|
||||
/// </summary>
|
||||
/// <param name="condition">The condition to match</param>
|
||||
/// <param name="total">Whether to evaluate based on the child's <see cref="GetTotalCoveredArea"/>, rather than its <see cref="UnscrolledArea"/>.</param>
|
||||
/// <returns>The lowest element, or null if no such element exists</returns>
|
||||
public Element GetLowestChild(Func<Element, bool> condition = null) {
|
||||
public Element GetLowestChild(Func<Element, bool> condition = null, bool total = false) {
|
||||
Element lowest = null;
|
||||
var lowestX = float.MinValue;
|
||||
foreach (var child in this.Children) {
|
||||
if (condition != null && !condition(child))
|
||||
continue;
|
||||
var x = !child.Anchor.IsTopAligned() ? child.UnscrolledArea.Height : child.UnscrolledArea.Bottom;
|
||||
var covered = total ? child.GetTotalCoveredArea(true) : child.UnscrolledArea;
|
||||
var x = !child.Anchor.IsTopAligned() ? covered.Height : covered.Bottom;
|
||||
if (x >= lowestX) {
|
||||
lowest = child;
|
||||
lowestX = x;
|
||||
|
@ -872,14 +903,16 @@ namespace MLEM.Ui.Elements {
|
|||
/// Returns this element's rightmost child (in terms of x position) that matches the given condition.
|
||||
/// </summary>
|
||||
/// <param name="condition">The condition to match</param>
|
||||
/// <param name="total">Whether to evaluate based on the child's <see cref="GetTotalCoveredArea"/>, rather than its <see cref="UnscrolledArea"/>.</param>
|
||||
/// <returns>The rightmost element, or null if no such element exists</returns>
|
||||
public Element GetRightmostChild(Func<Element, bool> condition = null) {
|
||||
public Element GetRightmostChild(Func<Element, bool> condition = null, bool total = false) {
|
||||
Element rightmost = null;
|
||||
var rightmostX = float.MinValue;
|
||||
foreach (var child in this.Children) {
|
||||
if (condition != null && !condition(child))
|
||||
continue;
|
||||
var x = !child.Anchor.IsLeftAligned() ? child.UnscrolledArea.Width : child.UnscrolledArea.Right;
|
||||
var covered = total ? child.GetTotalCoveredArea(true) : child.UnscrolledArea;
|
||||
var x = !child.Anchor.IsLeftAligned() ? covered.Width : covered.Right;
|
||||
if (x >= rightmostX) {
|
||||
rightmost = child;
|
||||
rightmostX = x;
|
||||
|
@ -893,8 +926,9 @@ namespace MLEM.Ui.Elements {
|
|||
/// The returned element's <see cref="Parent"/> will always be equal to this element's <see cref="Parent"/>.
|
||||
/// </summary>
|
||||
/// <param name="condition">The condition to match</param>
|
||||
/// <param name="total">Whether to evaluate based on the child's <see cref="GetTotalCoveredArea"/>, rather than its <see cref="UnscrolledArea"/>.</param>
|
||||
/// <returns>The lowest older sibling of this element, or null if no such element exists</returns>
|
||||
public Element GetLowestOlderSibling(Func<Element, bool> condition = null) {
|
||||
public Element GetLowestOlderSibling(Func<Element, bool> condition = null, bool total = false) {
|
||||
if (this.Parent == null)
|
||||
return null;
|
||||
Element lowest = null;
|
||||
|
@ -903,7 +937,7 @@ namespace MLEM.Ui.Elements {
|
|||
break;
|
||||
if (condition != null && !condition(child))
|
||||
continue;
|
||||
if (lowest == null || child.UnscrolledArea.Bottom >= lowest.UnscrolledArea.Bottom)
|
||||
if (lowest == null || (total ? child.GetTotalCoveredArea(true) : child.UnscrolledArea).Bottom >= lowest.UnscrolledArea.Bottom)
|
||||
lowest = child;
|
||||
}
|
||||
return lowest;
|
||||
|
@ -985,6 +1019,21 @@ namespace MLEM.Ui.Elements {
|
|||
yield return parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the total covered area of this element, which is its <see cref="Area"/> (or <see cref="UnscrolledArea"/>), unioned with all of the total covered areas of its <see cref="Children"/>.
|
||||
/// The returned area is only different from this element's <see cref="Area"/> (or <see cref="UnscrolledArea"/>) if it has any <see cref="Children"/> that are outside of this element's area, or are bigger than this element.
|
||||
/// </summary>
|
||||
/// <param name="unscrolled">Whether to use elements' <see cref="UnscrolledArea"/> (instead of their <see cref="Area"/>).</param>
|
||||
/// <returns>This element's total covered area.</returns>
|
||||
public RectangleF GetTotalCoveredArea(bool unscrolled) {
|
||||
var ret = unscrolled ? this.UnscrolledArea : this.Area;
|
||||
foreach (var child in this.Children) {
|
||||
if (!child.IsHidden)
|
||||
ret = RectangleF.Union(ret, child.GetTotalCoveredArea(unscrolled));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a subset of <see cref="Children"/> that are currently relevant in terms of drawing and input querying.
|
||||
/// A <see cref="Panel"/> only returns elements that are currently in view here.
|
||||
|
@ -1001,6 +1050,14 @@ namespace MLEM.Ui.Elements {
|
|||
public virtual void Update(GameTime time) {
|
||||
this.System.InvokeOnElementUpdated(this, time);
|
||||
|
||||
for (var i = this.PlayingAnimations.Count - 1; i >= 0; i--) {
|
||||
var anim = this.PlayingAnimations[i];
|
||||
if (anim.Update(this, time)) {
|
||||
anim.OnFinished(this);
|
||||
this.PlayingAnimations.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
// update all sorted children, not just relevant ones, because they might become relevant or irrelevant through updates
|
||||
foreach (var child in this.SortedChildren) {
|
||||
if (child.System != null)
|
||||
|
@ -1143,6 +1200,33 @@ namespace MLEM.Ui.Elements {
|
|||
return this.CanBeMoused && this.DisplayArea.Contains(position) ? this : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays the given <see cref="UiAnimation"/> on this element, causing it to be added to the <see cref="PlayingAnimations"/> and updated in <see cref="Update"/>.
|
||||
/// If the given <paramref name="animation"/> is already playing on this element, it will be restarted.
|
||||
/// </summary>
|
||||
/// <param name="animation">The animation to play.</param>
|
||||
public virtual void PlayAnimation(UiAnimation animation) {
|
||||
if (this.PlayingAnimations.Contains(animation)) {
|
||||
// if we're already playing this animation, just restart it
|
||||
animation.OnFinished(this);
|
||||
} else {
|
||||
this.PlayingAnimations.Add(animation);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the given <see cref="UiAnimation"/> on this element, causing it to be removed from the <see cref="PlayingAnimations"/> and <see cref="UiAnimation.OnFinished"/> to be invoked.
|
||||
/// </summary>
|
||||
/// <param name="animation">The animation to stop.</param>
|
||||
/// <returns>Whether the animation was present in this element's <see cref="PlayingAnimations"/>.</returns>
|
||||
public virtual bool StopAnimation(UiAnimation animation) {
|
||||
if (this.PlayingAnimations.Remove(animation)) {
|
||||
animation.OnFinished(this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
|
||||
[Obsolete("Dispose will be removed in a future update. To unregister custom event handlers, use OnRemovedFromUi instead.")]
|
||||
public virtual void Dispose() {
|
||||
|
@ -1150,6 +1234,18 @@ namespace MLEM.Ui.Elements {
|
|||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() {
|
||||
var ret = this.GetType().ToString();
|
||||
// elements will contain their path up to the root (Paragraph@Panel@...@RootName)
|
||||
if (this.Parent != null) {
|
||||
ret += $"@{this.Parent}";
|
||||
} else if (this.Root?.Element == this) {
|
||||
ret += $"@{this.Root.Name}";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the specified action on this element and all of its <see cref="Children"/>
|
||||
/// </summary>
|
||||
|
@ -1194,8 +1290,11 @@ namespace MLEM.Ui.Elements {
|
|||
this.SelectionIndicator = this.SelectionIndicator.OrStyle(style.SelectionIndicator);
|
||||
this.ActionSound = this.ActionSound.OrStyle(style.ActionSound);
|
||||
this.SecondActionSound = this.SecondActionSound.OrStyle(style.ActionSound);
|
||||
this.MouseEnterAnimation = this.MouseEnterAnimation.OrStyle(style.MouseEnterAnimation);
|
||||
this.MouseExitAnimation = this.MouseExitAnimation.OrStyle(style.MouseExitAnimation);
|
||||
|
||||
this.System?.InvokeOnElementStyleInit(this);
|
||||
style.ApplyCustomStyle(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -234,21 +234,39 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the given <see cref="Anchor"/> is automatic. The anchors <see cref="Anchor.AutoLeft"/>, <see cref="Anchor.AutoCenter"/>, <see cref="Anchor.AutoRight"/>, <see cref="Anchor.AutoInline"/> and <see cref="Anchor.AutoInlineIgnoreOverflow"/> will return true.
|
||||
/// Returns whether the given <see cref="Anchor"/> is automatic. The anchors <see cref="Anchor.AutoLeft"/>, <see cref="Anchor.AutoCenter"/>, <see cref="Anchor.AutoRight"/>, and any anchor that <see cref="IsInline"/> will return true.
|
||||
/// </summary>
|
||||
/// <param name="anchor">The anchor to query.</param>
|
||||
/// <returns>Whether the given anchor is automatic.</returns>
|
||||
public static bool IsAuto(this Anchor anchor) {
|
||||
return anchor == Anchor.AutoLeft || anchor == Anchor.AutoCenter || anchor == Anchor.AutoRight || anchor == Anchor.AutoInline || anchor == Anchor.AutoInlineIgnoreOverflow;
|
||||
return anchor == Anchor.AutoLeft || anchor == Anchor.AutoCenter || anchor == Anchor.AutoRight || anchor.IsInline();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the given <see cref="Anchor"/> is left-aligned for the purpose of <see cref="Element.GetRightmostChild"/>. The anchors <see cref="Anchor.TopLeft"/>, <see cref="Anchor.CenterLeft"/>, <see cref="Anchor.BottomLeft"/>, <see cref="Anchor.AutoLeft"/>, <see cref="Anchor.AutoInline"/> and <see cref="Anchor.AutoInlineIgnoreOverflow"/> will return true.
|
||||
/// Returns whether the given <see cref="Anchor"/> is inline. The anchors <see cref="Anchor.AutoInline"/>, <see cref="Anchor.AutoInlineCenter"/>, <see cref="Anchor.AutoInlineBottom"/>, and any anchor that <see cref="IsIgnoreOverflow"/> will return true.
|
||||
/// </summary>
|
||||
/// <param name="anchor">The anchor to query.</param>
|
||||
/// <returns>Whether the given anchor is inline.</returns>
|
||||
public static bool IsInline(this Anchor anchor) {
|
||||
return anchor == Anchor.AutoInline || anchor == Anchor.AutoInlineCenter || anchor == Anchor.AutoInlineBottom || anchor.IsIgnoreOverflow();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the given <see cref="Anchor"/> ignores overflow. The anchors <see cref="Anchor.AutoInlineIgnoreOverflow"/>, <see cref="Anchor.AutoInlineCenterIgnoreOverflow"/>, and <see cref="Anchor.AutoInlineBottomIgnoreOverflow"/> will return true.
|
||||
/// </summary>
|
||||
/// <param name="anchor">The anchor to query.</param>
|
||||
/// <returns>Whether the given anchor ignores overflow.</returns>
|
||||
public static bool IsIgnoreOverflow(this Anchor anchor) {
|
||||
return anchor == Anchor.AutoInlineIgnoreOverflow || anchor == Anchor.AutoInlineCenterIgnoreOverflow || anchor == Anchor.AutoInlineBottomIgnoreOverflow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the given <see cref="Anchor"/> is left-aligned for the purpose of <see cref="Element.GetRightmostChild"/>. The anchors <see cref="Anchor.TopLeft"/>, <see cref="Anchor.CenterLeft"/>, <see cref="Anchor.BottomLeft"/>, <see cref="Anchor.AutoLeft"/>, and any anchor that <see cref="IsInline"/> will return true.
|
||||
/// </summary>
|
||||
/// <param name="anchor">The anchor to query.</param>
|
||||
/// <returns>Whether the given anchor is left-aligned.</returns>
|
||||
public static bool IsLeftAligned(this Anchor anchor) {
|
||||
return anchor == Anchor.TopLeft || anchor == Anchor.CenterLeft || anchor == Anchor.BottomLeft || anchor == Anchor.AutoLeft || anchor == Anchor.AutoInline || anchor == Anchor.AutoInlineIgnoreOverflow;
|
||||
return anchor == Anchor.TopLeft || anchor == Anchor.CenterLeft || anchor == Anchor.BottomLeft || anchor == Anchor.AutoLeft || anchor.IsInline();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -30,13 +30,12 @@ namespace MLEM.Ui.Elements {
|
|||
/// </summary>
|
||||
public TextureRegion Texture {
|
||||
get {
|
||||
var ret = this.GetTextureCallback?.Invoke(this) ?? this.texture;
|
||||
this.CheckTextureChange(ret);
|
||||
return ret;
|
||||
this.CheckTextureChange();
|
||||
return this.displayedTexture;
|
||||
}
|
||||
set {
|
||||
this.texture = value;
|
||||
this.CheckTextureChange(value);
|
||||
this.explicitlySetTexture = value;
|
||||
this.CheckTextureChange();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
|
@ -75,8 +74,8 @@ namespace MLEM.Ui.Elements {
|
|||
public override bool IsHidden => base.IsHidden || this.Texture == null;
|
||||
|
||||
private bool scaleToImage;
|
||||
private TextureRegion texture;
|
||||
private TextureRegion lastTexture;
|
||||
private TextureRegion explicitlySetTexture;
|
||||
private TextureRegion displayedTexture;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new image with the given settings
|
||||
|
@ -106,6 +105,12 @@ namespace MLEM.Ui.Elements {
|
|||
return this.Texture != null && this.scaleToImage ? this.Texture.Size.ToVector2() * this.Scale : base.CalcActualSize(parentArea);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(GameTime time) {
|
||||
this.CheckTextureChange();
|
||||
base.Update(time);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
if (this.Texture == null)
|
||||
|
@ -123,11 +128,12 @@ namespace MLEM.Ui.Elements {
|
|||
base.Draw(time, batch, alpha, context);
|
||||
}
|
||||
|
||||
private void CheckTextureChange(TextureRegion newTexture) {
|
||||
if (this.lastTexture == newTexture)
|
||||
private void CheckTextureChange() {
|
||||
var newTexture = this.GetTextureCallback?.Invoke(this) ?? this.explicitlySetTexture;
|
||||
if (this.displayedTexture == newTexture)
|
||||
return;
|
||||
var nullChanged = this.lastTexture == null != (newTexture == null);
|
||||
this.lastTexture = newTexture;
|
||||
var nullChanged = this.displayedTexture == null != (newTexture == null);
|
||||
this.displayedTexture = newTexture;
|
||||
if (nullChanged || this.scaleToImage)
|
||||
this.SetAreaDirty();
|
||||
}
|
||||
|
|
|
@ -13,13 +13,13 @@ namespace MLEM.Ui.Elements {
|
|||
/// <summary>
|
||||
/// A panel element to be used inside of a <see cref="UiSystem"/>.
|
||||
/// The panel is a complex element that displays a box as a background to all of its child elements.
|
||||
/// Additionally, a panel can be set to <see cref="scrollOverflow"/> on construction, which causes all elements that don't fit into the panel to be hidden until scrolled to using a <see cref="ScrollBar"/>.
|
||||
/// Additionally, a panel can be set to scroll overflowing elements on construction, which causes all elements that don't fit into the panel to be hidden until scrolled to using a <see cref="ScrollBar"/>.
|
||||
/// </summary>
|
||||
public class Panel : Element {
|
||||
|
||||
/// <summary>
|
||||
/// The scroll bar that this panel contains.
|
||||
/// This is only nonnull if <see cref="scrollOverflow"/> is true.
|
||||
/// This is only nonnull if scrolling overflow was enabled in the constructor.
|
||||
/// Note that some scroll bar styling is controlled by this panel, namely <see cref="StepPerScroll"/> and <see cref="ScrollerSize"/>.
|
||||
/// </summary>
|
||||
public readonly ScrollBar ScrollBar;
|
||||
|
@ -133,6 +133,11 @@ namespace MLEM.Ui.Elements {
|
|||
if (element == this.ScrollBar)
|
||||
throw new NotSupportedException("A panel that scrolls overflow cannot have its scroll bar removed from its list of children");
|
||||
base.RemoveChild(element);
|
||||
|
||||
// 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 (this.System != null)
|
||||
this.ScrollSetup();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -207,6 +212,7 @@ namespace MLEM.Ui.Elements {
|
|||
protected override void InitStyle(UiStyle style) {
|
||||
base.InitStyle(style);
|
||||
this.Texture = this.Texture.OrStyle(style.PanelTexture);
|
||||
this.DrawColor = this.DrawColor.OrStyle(style.PanelColor);
|
||||
this.StepPerScroll = this.StepPerScroll.OrStyle(style.PanelStepPerScroll);
|
||||
this.ScrollerSize = this.ScrollerSize.OrStyle(style.PanelScrollerSize);
|
||||
this.ScrollBarOffset = this.ScrollBarOffset.OrStyle(style.PanelScrollBarOffset);
|
||||
|
@ -230,8 +236,12 @@ namespace MLEM.Ui.Elements {
|
|||
base.OnChildAreaDirty(child, grandchild);
|
||||
// we only need to scroll when a grandchild changes, since all of our children are forced
|
||||
// to be auto-anchored and so will automatically propagate their changes up to us
|
||||
if (grandchild)
|
||||
if (grandchild) {
|
||||
this.ScrollChildren();
|
||||
// we also need to re-setup here in case the child is involved in a special GetTotalCoveredArea
|
||||
if (!this.AreaDirty)
|
||||
this.ScrollSetup();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -253,8 +263,8 @@ namespace MLEM.Ui.Elements {
|
|||
float childrenHeight;
|
||||
if (this.Children.Count > 1) {
|
||||
var firstChild = this.Children.FirstOrDefault(c => c != this.ScrollBar);
|
||||
var lowestChild = this.GetLowestChild(c => c != this.ScrollBar && !c.IsHidden);
|
||||
childrenHeight = lowestChild.Area.Bottom - firstChild.Area.Top;
|
||||
var lowestChild = this.GetLowestChild(c => c != this.ScrollBar && !c.IsHidden, true);
|
||||
childrenHeight = lowestChild.GetTotalCoveredArea(false).Bottom - firstChild.Area.Top;
|
||||
} else {
|
||||
// if we only have one child (the scroll bar), then the children take up no visual height
|
||||
childrenHeight = 0;
|
||||
|
|
|
@ -139,6 +139,16 @@ namespace MLEM.Ui.Elements {
|
|||
this.SetTextDirty();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The inclusive index in this paragraph's <see cref="Text"/> to start drawing at.
|
||||
/// This value is passed to <see cref="TokenizedString.Draw"/>.
|
||||
/// </summary>
|
||||
public int? DrawStartIndex;
|
||||
/// <summary>
|
||||
/// The exclusive index in this paragraph's <see cref="Text"/> to stop drawing at.
|
||||
/// This value is passed to <see cref="TokenizedString.Draw"/>.
|
||||
/// </summary>
|
||||
public int? DrawEndIndex;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsHidden => base.IsHidden || string.IsNullOrWhiteSpace(this.Text);
|
||||
|
@ -175,6 +185,13 @@ namespace MLEM.Ui.Elements {
|
|||
this.CanBeMoused = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetAreaAndUpdateChildren(RectangleF area) {
|
||||
base.SetAreaAndUpdateChildren(area);
|
||||
// in case an outside source sets our area, we still want to display our text correctly
|
||||
this.AlignAndSplitIfNecessary(area.Size);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Vector2 CalcActualSize(RectangleF parentArea) {
|
||||
var size = base.CalcActualSize(parentArea);
|
||||
|
@ -182,6 +199,9 @@ namespace MLEM.Ui.Elements {
|
|||
this.TokenizeIfNecessary();
|
||||
this.AlignAndSplitIfNecessary(size);
|
||||
var textSize = this.tokenizedText.GetArea(Vector2.Zero, this.TextScale * this.TextScaleMultiplier * this.Scale).Size;
|
||||
// if we auto-adjust our width, then we would also split the same way with our adjusted width, so cache that
|
||||
if (this.AutoAdjustWidth)
|
||||
this.lastAlignSplitWidth = textSize.X;
|
||||
return new Vector2(this.AutoAdjustWidth ? textSize.X + this.ScaledPadding.Width : size.X, textSize.Y + this.ScaledPadding.Height);
|
||||
}
|
||||
|
||||
|
@ -196,7 +216,7 @@ namespace MLEM.Ui.Elements {
|
|||
var pos = this.DisplayArea.Location + new Vector2(this.GetAlignmentOffset(), 0);
|
||||
var sc = this.TextScale * this.TextScaleMultiplier * this.Scale;
|
||||
var color = this.TextColor.OrDefault(Color.White) * alpha;
|
||||
this.TokenizedText.Draw(time, batch, pos, this.RegularFont, color, sc, 0);
|
||||
this.TokenizedText.Draw(time, batch, pos, this.RegularFont, color, sc, 0, this.DrawStartIndex, this.DrawEndIndex);
|
||||
base.Draw(time, batch, alpha, context);
|
||||
}
|
||||
|
||||
|
@ -255,7 +275,7 @@ namespace MLEM.Ui.Elements {
|
|||
var width = size.X - this.ScaledPadding.Width;
|
||||
var scale = this.TextScale * this.TextScaleMultiplier * this.Scale;
|
||||
|
||||
if (this.lastAlignSplitWidth == width && this.lastAlignSplitScale == scale)
|
||||
if (this.lastAlignSplitWidth?.Equals(width, Element.Epsilon) == true && this.lastAlignSplitScale?.Equals(scale, Element.Epsilon) == true)
|
||||
return;
|
||||
this.lastAlignSplitWidth = width;
|
||||
this.lastAlignSplitScale = scale;
|
||||
|
|
|
@ -10,7 +10,7 @@ using MLEM.Ui.Style;
|
|||
namespace MLEM.Ui.Elements {
|
||||
/// <summary>
|
||||
/// A progress bar element to use inside of a <see cref="UiSystem"/>.
|
||||
/// A progress bar is an element that fills up a bar based on a given <see cref="currentValue"/> percentage.
|
||||
/// A progress bar is an element that fills up a bar based on a given <see cref="CurrentValue"/> percentage.
|
||||
/// </summary>
|
||||
public class ProgressBar : Element {
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace MLEM.Ui.Elements {
|
|||
public override void Update(GameTime time) {
|
||||
base.Update(time);
|
||||
|
||||
if (this.IsSelected) {
|
||||
if (this.IsSelectedActive) {
|
||||
if (this.CurrentValue > 0 && this.Controls.LeftButtons.TryConsumePressed(this.Input, this.Controls.GamepadIndex)) {
|
||||
this.CurrentValue -= this.StepPerScroll;
|
||||
} else if (this.CurrentValue < this.MaxValue && this.Controls.RightButtons.TryConsumePressed(this.Input, this.Controls.GamepadIndex)) {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MLEM.Extensions;
|
||||
using MLEM.Misc;
|
||||
|
||||
namespace MLEM.Ui.Elements {
|
||||
/// <summary>
|
||||
/// A squishing group is a <see cref="Group"/> whose <see cref="Element.Children"/> automatically get resized so that they do not overlap each other.
|
||||
/// The order in which elements are squished depends on their <see cref="Element.Priority"/>, where elements with a lower priority will move out of the way of elements with a higher priority.
|
||||
/// If all elements have the same priority, their addition order (their order in <see cref="Element.Children"/>) determines squish order.
|
||||
/// A squishing group is a <see cref="Group"/> whose <see cref="Element.Children"/> automatically get resized so that they do not overlap each other. Elements are squished in a way that maximizes the final area that each element retains compared to its original area.
|
||||
/// The order in which elements are squished depends on their <see cref="Element.Priority"/>, where elements with a lower priority will move out of the way of elements with a higher priority. If all elements have the same priority, their addition order (their order in <see cref="Element.Children"/>) determines squish order.
|
||||
/// </summary>
|
||||
public class SquishingGroup : Group {
|
||||
|
||||
|
@ -43,27 +43,17 @@ namespace MLEM.Ui.Elements {
|
|||
var pos = element.Area.Location;
|
||||
var size = element.Area.Size;
|
||||
foreach (var sibling in element.GetSiblings(e => !e.IsHidden)) {
|
||||
var siblingArea = sibling.Area;
|
||||
var leftIntersect = siblingArea.Right - pos.X;
|
||||
var rightIntersect = pos.X + size.X - siblingArea.Left;
|
||||
var bottomIntersect = siblingArea.Bottom - pos.Y;
|
||||
var topIntersect = pos.Y + size.Y - siblingArea.Top;
|
||||
if (leftIntersect > 0 && rightIntersect > 0 && bottomIntersect > 0 && topIntersect > 0) {
|
||||
if (rightIntersect + leftIntersect < topIntersect + bottomIntersect) {
|
||||
if (rightIntersect > leftIntersect) {
|
||||
size.X -= siblingArea.Right - pos.X;
|
||||
pos.X = Math.Max(pos.X, siblingArea.Right);
|
||||
} else {
|
||||
size.X = Math.Min(pos.X + size.X, siblingArea.Left) - pos.X;
|
||||
}
|
||||
} else {
|
||||
if (topIntersect > bottomIntersect) {
|
||||
size.Y -= siblingArea.Bottom - pos.Y;
|
||||
pos.Y = Math.Max(pos.Y, siblingArea.Bottom);
|
||||
} else {
|
||||
size.Y = Math.Min(pos.Y + size.Y, siblingArea.Top) - pos.Y;
|
||||
}
|
||||
}
|
||||
var sibArea = sibling.Area;
|
||||
if (pos.X < sibArea.Right && sibArea.Left < pos.X + size.X && pos.Y < sibArea.Bottom && sibArea.Top < pos.Y + size.Y) {
|
||||
var possible = new[] {
|
||||
new RectangleF(Math.Max(pos.X, sibArea.Right), pos.Y, size.X - (sibArea.Right - pos.X), size.Y),
|
||||
new RectangleF(pos.X, pos.Y, Math.Min(pos.X + size.X, sibArea.Left) - pos.X, size.Y),
|
||||
new RectangleF(pos.X, Math.Max(pos.Y, sibArea.Bottom), size.X, size.Y - (sibArea.Bottom - pos.Y)),
|
||||
new RectangleF(pos.X, pos.Y, size.X, Math.Min(pos.Y + size.Y, sibArea.Top) - pos.Y)
|
||||
};
|
||||
var biggest = possible.OrderByDescending(r => r.Width * r.Height).First();
|
||||
pos = biggest.Location;
|
||||
size = biggest.Size;
|
||||
}
|
||||
}
|
||||
if (!pos.Equals(element.Area.Location, Element.Epsilon) || !size.Equals(element.Area.Size, Element.Epsilon)) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using MLEM.Font;
|
||||
using MLEM.Graphics;
|
||||
using MLEM.Input;
|
||||
|
@ -139,6 +140,12 @@ namespace MLEM.Ui.Elements {
|
|||
/// The description of the <c>KeyboardInput</c> field on mobile devices and consoles
|
||||
/// </summary>
|
||||
public string MobileDescription;
|
||||
/// <summary>
|
||||
/// An element that should be pressed (using <see cref="UiControls.PressElement"/>) if <see cref="Keys.Enter"/> is pressed while this text field is active.
|
||||
/// Note that, for text fields that are <see cref="Multiline"/>, this is ignored.
|
||||
/// This also occurs once the text input window is successfully closed on a mobile device.
|
||||
/// </summary>
|
||||
public Element EnterReceiver;
|
||||
|
||||
private readonly TextInput textInput;
|
||||
private StyleProp<GenericFont> font;
|
||||
|
@ -188,12 +195,14 @@ namespace MLEM.Ui.Elements {
|
|||
this.OnPressed += async e => {
|
||||
var title = this.MobileTitle ?? this.PlaceholderText;
|
||||
var result = await MlemPlatform.Current.OpenOnScreenKeyboard(title, this.MobileDescription, this.Text, false);
|
||||
if (result != null)
|
||||
if (result != null) {
|
||||
this.SetText(this.Multiline ? result : result.Replace('\n', ' '), true);
|
||||
this.EnterReceiver?.Controls?.PressElement(this.EnterReceiver);
|
||||
}
|
||||
};
|
||||
this.OnTextInput += (element, key, character) => {
|
||||
if (this.IsSelected && !this.IsHidden)
|
||||
this.textInput.OnTextInput(key, character);
|
||||
if (this.IsSelectedActive && !this.IsHidden && !this.textInput.OnTextInput(key, character) && key == Keys.Enter && !this.Multiline)
|
||||
this.EnterReceiver?.Controls?.PressElement(this.EnterReceiver);
|
||||
};
|
||||
this.OnDeselected += e => this.CaretPos = 0;
|
||||
this.OnSelected += e => this.CaretPos = this.textInput.Length;
|
||||
|
@ -209,8 +218,14 @@ namespace MLEM.Ui.Elements {
|
|||
/// <inheritdoc />
|
||||
public override void Update(GameTime time) {
|
||||
base.Update(time);
|
||||
if (this.IsSelected && !this.IsHidden)
|
||||
if (this.IsSelectedActive && !this.IsHidden) {
|
||||
this.textInput.Update(time, this.Input);
|
||||
#if FNA
|
||||
// this occurs in OnTextInput outside FNA, where special keys are also counted as text input
|
||||
if (this.EnterReceiver != null && !this.Multiline && this.Input.TryConsumePressed(Keys.Enter))
|
||||
this.EnterReceiver.Controls?.PressElement(this.EnterReceiver);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -196,7 +196,7 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Causes this tooltip's position to be snapped to the mouse position, or the <see cref="snapElement"/> if <see cref="DisplayInAutoNavMode"/> is true, or the <see cref="SnapPosition"/> if set.
|
||||
/// Causes this tooltip's position to be snapped to the mouse position, or the element to snap to if <see cref="DisplayInAutoNavMode"/> is true, or the <see cref="SnapPosition"/> if set.
|
||||
/// </summary>
|
||||
public void SnapPositionToMouse() {
|
||||
Vector2 snapPosition;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net452;netstandard2.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
<IsTrimmable>true</IsTrimmable>
|
||||
|
@ -31,6 +31,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
|
||||
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
|
||||
<None Include="../README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net452;netstandard2.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
<IsTrimmable>true</IsTrimmable>
|
||||
|
@ -29,6 +29,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
|
||||
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
|
||||
<None Include="../README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -109,11 +109,11 @@ namespace MLEM.Ui.Parsers {
|
|||
|
||||
/// <summary>
|
||||
/// Specifies an action to be invoked when a new element with the given <see cref="ElementType"/> is parsed in <see cref="Parse"/> or <see cref="ParseInto"/>.
|
||||
/// These actions can be used to modify the style properties of the created elements.
|
||||
/// These actions can be used to modify the style properties of the created elements similarly to <see cref="UiStyle.AddCustomStyle{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="types">The element types that should be styled. Can be a combined flag.</param>
|
||||
/// <param name="style">The action that styles the elements with the given element type.</param>
|
||||
/// <param name="add">Whether the <paramref name="style"/> function should be added to the existing style settings, or replace them.</param>
|
||||
/// <param name="add">Whether the <paramref name="style"/> function should be added to the existing style settings rather than replacing them.</param>
|
||||
/// <typeparam name="T">The type of elements that the given <see cref="ElementType"/> flags are expected to be.</typeparam>
|
||||
/// <returns>This parser, for chaining.</returns>
|
||||
public UiParser Style<T>(ElementType types, Action<T> style, bool add = false) where T : Element {
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace MLEM.Ui.Style {
|
|||
/// <summary>
|
||||
/// The style settings for a <see cref="UiSystem"/>.
|
||||
/// Each <see cref="Element"/> uses these style settings by default, however you can also change these settings per element using the elements' individual style settings.
|
||||
/// Note that this class is a <see cref="GenericDataHolder"/>, meaning additional styles for custom components can easily be added using <see cref="GenericDataHolder.SetData"/>
|
||||
/// Additional styles for built-in or custom element types can easily be added using <see cref="AddCustomStyle{T}"/>.
|
||||
/// </summary>
|
||||
public class UiStyle : GenericDataHolder {
|
||||
|
||||
|
@ -22,6 +22,14 @@ namespace MLEM.Ui.Style {
|
|||
/// </summary>
|
||||
public NinePatch SelectionIndicator;
|
||||
/// <summary>
|
||||
/// A <see cref="UiAnimation"/> that is played when the mouse enters an element.
|
||||
/// </summary>
|
||||
public UiAnimation MouseEnterAnimation;
|
||||
/// <summary>
|
||||
/// A <see cref="UiAnimation"/> that is played when the mouse exists an element.
|
||||
/// </summary>
|
||||
public UiAnimation MouseExitAnimation;
|
||||
/// <summary>
|
||||
/// The texture that the <see cref="Button"/> element uses
|
||||
/// </summary>
|
||||
public NinePatch ButtonTexture;
|
||||
|
@ -47,6 +55,10 @@ namespace MLEM.Ui.Style {
|
|||
/// </summary>
|
||||
public NinePatch PanelTexture;
|
||||
/// <summary>
|
||||
/// The color that the <see cref="Panel"/> element draws with.
|
||||
/// </summary>
|
||||
public Color PanelColor = Color.White;
|
||||
/// <summary>
|
||||
/// The <see cref="Element.ChildPadding"/> to apply to a <see cref="Panel"/> by default
|
||||
/// </summary>
|
||||
public Padding PanelChildPadding = new Vector2(5);
|
||||
|
@ -222,5 +234,106 @@ namespace MLEM.Ui.Style {
|
|||
/// </summary>
|
||||
public Dictionary<string, GenericFont> AdditionalFonts = new Dictionary<string, GenericFont>();
|
||||
|
||||
private readonly Dictionary<Type, Action<Element>> elementStyles = new Dictionary<Type, Action<Element>>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new set of style settings with the default values.
|
||||
/// </summary>
|
||||
public UiStyle() {}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new set of style settings with values inherited from the given <paramref name="original"/> style settings.
|
||||
/// </summary>
|
||||
/// <param name="original">The original style settings, to copy into the new instance.</param>
|
||||
public UiStyle(UiStyle original) {
|
||||
this.SelectionIndicator = original.SelectionIndicator;
|
||||
this.MouseEnterAnimation = original.MouseEnterAnimation;
|
||||
this.MouseExitAnimation = original.MouseExitAnimation;
|
||||
this.ButtonTexture = original.ButtonTexture;
|
||||
this.ButtonHoveredTexture = original.ButtonHoveredTexture;
|
||||
this.ButtonHoveredColor = original.ButtonHoveredColor;
|
||||
this.ButtonDisabledTexture = original.ButtonDisabledTexture;
|
||||
this.ButtonDisabledColor = original.ButtonDisabledColor;
|
||||
this.PanelTexture = original.PanelTexture;
|
||||
this.PanelColor = original.PanelColor;
|
||||
this.PanelChildPadding = original.PanelChildPadding;
|
||||
this.PanelStepPerScroll = original.PanelStepPerScroll;
|
||||
this.PanelScrollerSize = original.PanelScrollerSize;
|
||||
this.PanelScrollBarOffset = original.PanelScrollBarOffset;
|
||||
this.TextFieldTexture = original.TextFieldTexture;
|
||||
this.TextFieldHoveredTexture = original.TextFieldHoveredTexture;
|
||||
this.TextFieldHoveredColor = original.TextFieldHoveredColor;
|
||||
this.TextFieldTextOffsetX = original.TextFieldTextOffsetX;
|
||||
this.TextFieldCaretWidth = original.TextFieldCaretWidth;
|
||||
this.ScrollBarBackground = original.ScrollBarBackground;
|
||||
this.ScrollBarScrollerTexture = original.ScrollBarScrollerTexture;
|
||||
this.ScrollBarSmoothScrolling = original.ScrollBarSmoothScrolling;
|
||||
this.ScrollBarSmoothScrollFactor = original.ScrollBarSmoothScrollFactor;
|
||||
this.CheckboxTexture = original.CheckboxTexture;
|
||||
this.CheckboxHoveredTexture = original.CheckboxHoveredTexture;
|
||||
this.CheckboxHoveredColor = original.CheckboxHoveredColor;
|
||||
this.CheckboxDisabledTexture = original.CheckboxDisabledTexture;
|
||||
this.CheckboxDisabledColor = original.CheckboxDisabledColor;
|
||||
this.CheckboxCheckmark = original.CheckboxCheckmark;
|
||||
this.CheckboxTextOffsetX = original.CheckboxTextOffsetX;
|
||||
this.RadioTexture = original.RadioTexture;
|
||||
this.RadioHoveredTexture = original.RadioHoveredTexture;
|
||||
this.RadioHoveredColor = original.RadioHoveredColor;
|
||||
this.RadioCheckmark = original.RadioCheckmark;
|
||||
this.TooltipBackground = original.TooltipBackground;
|
||||
this.TooltipOffset = original.TooltipOffset;
|
||||
this.TooltipAutoNavOffset = original.TooltipAutoNavOffset;
|
||||
this.TooltipTextColor = original.TooltipTextColor;
|
||||
this.TooltipDelay = original.TooltipDelay;
|
||||
this.TooltipTextWidth = original.TooltipTextWidth;
|
||||
this.TooltipChildPadding = original.TooltipChildPadding;
|
||||
this.ProgressBarTexture = original.ProgressBarTexture;
|
||||
this.ProgressBarColor = original.ProgressBarColor;
|
||||
this.ProgressBarProgressPadding = original.ProgressBarProgressPadding;
|
||||
this.ProgressBarProgressTexture = original.ProgressBarProgressTexture;
|
||||
this.ProgressBarProgressColor = original.ProgressBarProgressColor;
|
||||
this.Font = original.Font;
|
||||
this.TextScale = original.TextScale;
|
||||
this.TextColor = original.TextColor;
|
||||
this.TextAlignment = original.TextAlignment;
|
||||
this.ActionSound = original.ActionSound;
|
||||
this.LinkColor = original.LinkColor;
|
||||
this.AdditionalFonts = new Dictionary<string, GenericFont>(original.AdditionalFonts);
|
||||
this.elementStyles = new Dictionary<Type, Action<Element>>(original.elementStyles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an action to the given <see cref="Element"/> type <typeparamref name="T"/> that allows applying any kind of custom styling or behavior to it.
|
||||
/// Custom styles added in this manner can be applied to an element using <see cref="ApplyCustomStyle"/>.
|
||||
/// </summary>
|
||||
/// <param name="style">The style action to add.</param>
|
||||
/// <param name="add">Whether the <paramref name="style"/> function should be added to the existing style settings rather than replacing them.</param>
|
||||
/// <typeparam name="T">The <see cref="Element"/> type that the <paramref name="style"/> should apply to.</typeparam>
|
||||
public void AddCustomStyle<T>(Action<T> style, bool add = false) where T : Element {
|
||||
if (add && this.elementStyles.ContainsKey(typeof(T))) {
|
||||
this.elementStyles[typeof(T)] += Action;
|
||||
} else {
|
||||
this.elementStyles[typeof(T)] = Action;
|
||||
}
|
||||
|
||||
void Action(Element e) {
|
||||
style.Invoke((T) e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a set of custom styling actions to the given <paramref name="element"/> which were added through <see cref="AddCustomStyle{T}"/>.
|
||||
/// This method is automatically invoked in <see cref="Element.InitStyle"/>.
|
||||
/// </summary>
|
||||
/// <param name="element">The element to apply custom styling to.</param>
|
||||
/// <returns>Whether any custom styling exists for the given <paramref name="element"/>.</returns>
|
||||
public bool ApplyCustomStyle(Element element) {
|
||||
if (this.elementStyles.TryGetValue(element.GetType(), out var style)) {
|
||||
style?.Invoke(element);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
86
MLEM.Ui/UiAnimation.cs
Normal file
86
MLEM.Ui/UiAnimation.cs
Normal file
|
@ -0,0 +1,86 @@
|
|||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MLEM.Misc;
|
||||
using MLEM.Ui.Elements;
|
||||
|
||||
namespace MLEM.Ui {
|
||||
/// <summary>
|
||||
/// A ui animation is a simple timed event that an <see cref="Element"/> in a <see cref="UiSystem"/> can use to play a visual or other type of animation.
|
||||
/// To use ui animations, you can use <see cref="Element.PlayAnimation"/>, or one of the built-in style properties like <see cref="Element.MouseEnterAnimation"/> or <see cref="Element.MouseExitAnimation"/>.
|
||||
/// </summary>
|
||||
public class UiAnimation : GenericDataHolder {
|
||||
|
||||
/// <summary>
|
||||
/// The total time that this ui animation plays for.
|
||||
/// </summary>
|
||||
public readonly TimeSpan TotalTime;
|
||||
/// <summary>
|
||||
/// The <see cref="AnimationFunction"/> that is invoked every <see cref="Update"/>.
|
||||
/// </summary>
|
||||
public readonly AnimationFunction Function;
|
||||
|
||||
/// <summary>
|
||||
/// An event that is raised when this ui animation is (re)started in <see cref="Update"/>.
|
||||
/// </summary>
|
||||
public Action<UiAnimation, Element> Started;
|
||||
/// <summary>
|
||||
/// An event that is raised when this ui animation is stopped or finished through <see cref="OnFinished"/>.
|
||||
/// </summary>
|
||||
public Action<UiAnimation, Element> Finished;
|
||||
/// <summary>
|
||||
/// The current time that this ui animation has been playing for, out of the <see cref="TotalTime"/>.
|
||||
/// </summary>
|
||||
public TimeSpan CurrentTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ui animation with the given settings.
|
||||
/// </summary>
|
||||
/// <param name="seconds">The amount of seconds that this ui animation should play for.</param>
|
||||
/// <param name="function">The <see cref="AnimationFunction"/> that is invoked every <see cref="Update"/>.</param>
|
||||
public UiAnimation(double seconds, AnimationFunction function) : this(TimeSpan.FromSeconds(seconds), function) {}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ui animation with the given settings.
|
||||
/// </summary>
|
||||
/// <param name="totalTime">The <see cref="TotalTime"/> that this ui animation should play for.</param>
|
||||
/// <param name="function">The <see cref="AnimationFunction"/> that is invoked every <see cref="Update"/>.</param>
|
||||
public UiAnimation(TimeSpan totalTime, AnimationFunction function) {
|
||||
this.TotalTime = totalTime;
|
||||
this.Function = function;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates this ui animation, invoking its <see cref="Started"/> event if necessary, increasing its <see cref="CurrentTime"/> and invoking its <see cref="Function"/>.
|
||||
/// This method is called by an <see cref="Element"/> in <see cref="Element.Update"/>.
|
||||
/// </summary>
|
||||
/// <param name="element">The element that this ui animation is attached to.</param>
|
||||
/// <param name="time">The game's current time.</param>
|
||||
/// <returns>Whether this animation is ready to finish, that is, if its <see cref="CurrentTime"/> is greater than or equal to its <see cref="TotalTime"/>.</returns>
|
||||
public virtual bool Update(Element element, GameTime time) {
|
||||
if (this.CurrentTime <= TimeSpan.Zero)
|
||||
this.Started?.Invoke(this, element);
|
||||
|
||||
this.CurrentTime += time.ElapsedGameTime;
|
||||
this.Function?.Invoke(this, element, this.CurrentTime.Ticks / (float) this.TotalTime.Ticks);
|
||||
|
||||
return this.CurrentTime >= this.TotalTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Causes this ui animation's <see cref="Finished"/> event to be raised, and sets the <see cref="CurrentTime"/> to <see cref="TimeSpan.Zero"/>.
|
||||
/// This allows the animation to play from the start again.
|
||||
/// This method is invoked automatically when <see cref="Update"/> returns <see langword="true"/> in <see cref="Element.Update"/>, as well as in <see cref="Element.StopAnimation"/>.
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
public virtual void OnFinished(Element element) {
|
||||
this.Finished?.Invoke(this, element);
|
||||
this.CurrentTime = TimeSpan.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate method used by <see cref="UiAnimation.Function"/>.
|
||||
/// </summary>
|
||||
public delegate void AnimationFunction(UiAnimation animation, Element element, float timePercentage);
|
||||
|
||||
}
|
||||
}
|
|
@ -170,13 +170,13 @@ namespace MLEM.Ui {
|
|||
var selectedNow = mousedNow != null && mousedNow.CanBeSelected ? mousedNow : null;
|
||||
this.SelectElement(this.ActiveRoot, selectedNow);
|
||||
if (mousedNow != null && mousedNow.CanBePressed) {
|
||||
this.System.InvokeOnElementPressed(mousedNow);
|
||||
this.PressElement(mousedNow);
|
||||
this.Input.TryConsumePressed(MouseButton.Left);
|
||||
}
|
||||
} else if (this.Input.IsPressedAvailable(MouseButton.Right)) {
|
||||
this.IsAutoNavMode = false;
|
||||
if (mousedNow != null && mousedNow.CanBePressed) {
|
||||
this.System.InvokeOnElementSecondaryPressed(mousedNow);
|
||||
this.PressElement(mousedNow, true);
|
||||
this.Input.TryConsumePressed(MouseButton.Right);
|
||||
}
|
||||
}
|
||||
|
@ -187,13 +187,8 @@ namespace MLEM.Ui {
|
|||
if (this.HandleKeyboard) {
|
||||
if (this.KeyboardButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
|
||||
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) {
|
||||
if (this.Input.IsModifierKeyDown(ModifierKey.Shift)) {
|
||||
// secondary action on element using space or enter
|
||||
this.System.InvokeOnElementSecondaryPressed(this.SelectedElement);
|
||||
} else {
|
||||
// first action on element using space or enter
|
||||
this.System.InvokeOnElementPressed(this.SelectedElement);
|
||||
}
|
||||
// primary or secondary action on element using space or enter
|
||||
this.PressElement(this.SelectedElement, this.Input.IsModifierKeyDown(ModifierKey.Shift));
|
||||
this.KeyboardButtons.TryConsumePressed(this.Input, this.GamepadIndex);
|
||||
}
|
||||
} else if (this.Input.IsPressedAvailable(Keys.Tab)) {
|
||||
|
@ -216,13 +211,13 @@ namespace MLEM.Ui {
|
|||
var tapped = this.GetElementUnderPos(tap.Position);
|
||||
this.SelectElement(this.ActiveRoot, tapped);
|
||||
if (tapped != null && tapped.CanBePressed)
|
||||
this.System.InvokeOnElementPressed(tapped);
|
||||
this.PressElement(tapped);
|
||||
} else if (this.Input.GetViewportGesture(GestureType.Hold, out var hold)) {
|
||||
this.IsAutoNavMode = false;
|
||||
var held = this.GetElementUnderPos(hold.Position);
|
||||
this.SelectElement(this.ActiveRoot, held);
|
||||
if (held != null && held.CanBePressed)
|
||||
this.System.InvokeOnElementSecondaryPressed(held);
|
||||
this.PressElement(held, true);
|
||||
} else if (this.Input.ViewportTouchState.Count <= 0) {
|
||||
this.SetTouchedElement(null);
|
||||
} else {
|
||||
|
@ -244,12 +239,12 @@ namespace MLEM.Ui {
|
|||
if (this.HandleGamepad) {
|
||||
if (this.GamepadButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
|
||||
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) {
|
||||
this.System.InvokeOnElementPressed(this.SelectedElement);
|
||||
this.PressElement(this.SelectedElement);
|
||||
this.GamepadButtons.TryConsumePressed(this.Input, this.GamepadIndex);
|
||||
}
|
||||
} else if (this.SecondaryGamepadButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
|
||||
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) {
|
||||
this.System.InvokeOnElementSecondaryPressed(this.SelectedElement);
|
||||
this.PressElement(this.SelectedElement, true);
|
||||
this.SecondaryGamepadButtons.TryConsumePressed(this.Input, this.GamepadIndex);
|
||||
}
|
||||
} else if (this.DownButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
|
||||
|
@ -362,6 +357,19 @@ namespace MLEM.Ui {
|
|||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Causes the passed element to be pressed, invoking its <see cref="Element.OnPressed"/> or <see cref="Element.OnSecondaryPressed"/> through <see cref="UiSystem.OnElementPressed"/> or <see cref="UiSystem.OnElementSecondaryPressed"/>.
|
||||
/// </summary>
|
||||
/// <param name="element">The element to press.</param>
|
||||
/// <param name="secondary">Whether the secondary action should be invoked, rather than the primary one.</param>
|
||||
public void PressElement(Element element, bool secondary = false) {
|
||||
if (secondary) {
|
||||
this.System.InvokeOnElementSecondaryPressed(element);
|
||||
} else {
|
||||
this.System.InvokeOnElementPressed(element);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next element to select when pressing the <see cref="Keys.Tab"/> key during keyboard navigation.
|
||||
/// If the <c>backward</c> boolean is true, the previous element should be returned instead.
|
||||
|
|
|
@ -419,6 +419,14 @@ namespace MLEM.Ui {
|
|||
root.Element.AndChildren(action);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
while (this.rootElements.Count > 0)
|
||||
this.Remove(this.rootElements[0].Name);
|
||||
}
|
||||
}
|
||||
|
||||
internal void InvokeOnElementDrawn(Element element, GameTime time, SpriteBatch batch, float alpha) {
|
||||
this.OnElementDrawn?.Invoke(element, time, batch, alpha);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace MLEM.Cameras {
|
|||
|
||||
/// <summary>
|
||||
/// This field holds an epsilon value used in some camera calculations to mitigate floating point rounding inaccuracies.
|
||||
/// If camera <see cref="Position"/> or <see cref="Viewport"/> size are extremely small or extremely big, this value can be reduced or increased.
|
||||
/// If camera <see cref="Position"/> or <see cref="ScaledViewport"/> size are extremely small or extremely big, this value can be reduced or increased.
|
||||
/// </summary>
|
||||
public static float Epsilon = 0.01F;
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace MLEM.Extensions {
|
|||
var currWeight = 0;
|
||||
foreach (var entry in entries) {
|
||||
currWeight += weightFunc(entry);
|
||||
if (currWeight >= goalWeight)
|
||||
if (currWeight > goalWeight)
|
||||
return entry;
|
||||
}
|
||||
throw new IndexOutOfRangeException();
|
||||
|
@ -49,7 +49,7 @@ namespace MLEM.Extensions {
|
|||
var currWeight = 0F;
|
||||
foreach (var entry in entries) {
|
||||
currWeight += weightFunc(entry);
|
||||
if (currWeight >= goalWeight)
|
||||
if (currWeight > goalWeight)
|
||||
return entry;
|
||||
}
|
||||
throw new IndexOutOfRangeException();
|
||||
|
|
34
MLEM/Formatting/Codes/OutlineCode.cs
Normal file
34
MLEM/Formatting/Codes/OutlineCode.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Extensions;
|
||||
using MLEM.Font;
|
||||
using MLEM.Misc;
|
||||
|
||||
namespace MLEM.Formatting.Codes {
|
||||
/// <inheritdoc />
|
||||
public class OutlineCode : Code {
|
||||
|
||||
private readonly Color color;
|
||||
private readonly float thickness;
|
||||
private readonly bool diagonals;
|
||||
|
||||
/// <inheritdoc />
|
||||
public OutlineCode(Match match, Regex regex, Color color, float thickness, bool diagonals) : base(match, regex) {
|
||||
this.color = color;
|
||||
this.thickness = thickness;
|
||||
this.diagonals = diagonals;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, Token token, int indexInToken, ref Vector2 pos, GenericFont font, ref Color color, ref float scale, float depth) {
|
||||
foreach (var dir in this.diagonals ? Direction2Helper.AllExceptNone : Direction2Helper.Adjacent) {
|
||||
var offset = Vector2.Normalize(dir.Offset().ToVector2()) * (this.thickness * scale);
|
||||
font.DrawString(batch, character, pos + offset, this.color.CopyAlpha(color), 0, Vector2.Zero, scale, SpriteEffects.None, depth);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -28,6 +28,67 @@ namespace MLEM.Formatting {
|
|||
/// </summary>
|
||||
public readonly Dictionary<Regex, Macro> Macros = new Dictionary<Regex, Macro>();
|
||||
|
||||
/// <summary>
|
||||
/// The line thickness used by this text formatter, which determines how the default <see cref="UnderlineCode"/>-based formatting codes are drawn.
|
||||
/// Note that this value only has an effect on the default formatting codes created through the <see cref="TextFormatter(bool, bool, bool, bool)"/> constructor.
|
||||
/// </summary>
|
||||
public float LineThickness = 1 / 16F;
|
||||
/// <summary>
|
||||
/// The underline offset used by this text formatter, which determines how the default <see cref="UnderlineCode"/> is drawn.
|
||||
/// Note that this value only has an effect on the default formatting codes created through the <see cref="TextFormatter(bool, bool, bool, bool)"/> constructor.
|
||||
/// </summary>
|
||||
public float UnderlineOffset = 0.85F;
|
||||
/// <summary>
|
||||
/// The strikethrough offset used by this text formatter, which determines how the default <see cref="UnderlineCode"/>'s strikethrough variant is drawn.
|
||||
/// Note that this value only has an effect on the default formatting codes created through the <see cref="TextFormatter(bool, bool, bool, bool)"/> constructor.
|
||||
/// </summary>
|
||||
public float StrikethroughOffset = 0.55F;
|
||||
/// <summary>
|
||||
/// The default subscript offset used by this text formatter, which determines how the default <see cref="SubSupCode"/> is drawn if no custom value is used.
|
||||
/// Note that this value only has an effect on the default formatting codes created through the <see cref="TextFormatter(bool, bool, bool, bool)"/> constructor.
|
||||
/// </summary>
|
||||
public float DefaultSubOffset = 0.15F;
|
||||
/// <summary>
|
||||
/// The default superscript offset used by this text formatter, which determines how the default <see cref="SubSupCode"/> is drawn if no custom value is used.
|
||||
/// Note that this value only has an effect on the default formatting codes created through the <see cref="TextFormatter(bool, bool, bool, bool)"/> constructor.
|
||||
/// </summary>
|
||||
public float DefaultSupOffset = -0.25F;
|
||||
/// <summary>
|
||||
/// The default shadow color used by this text formatter, which determines how the default <see cref="ShadowCode"/> is drawn if no custom value is used.
|
||||
/// Note that this value only has an effect on the default formatting codes created through the <see cref="TextFormatter(bool, bool, bool, bool)"/> constructor.
|
||||
/// </summary>
|
||||
public Color DefaultShadowColor = Color.Black;
|
||||
/// <summary>
|
||||
/// The default shadow offset used by this text formatter, which determines how the default <see cref="ShadowCode"/> is drawn if no custom value is used.
|
||||
/// Note that this value only has an effect on the default formatting codes created through the <see cref="TextFormatter(bool, bool, bool, bool)"/> constructor.
|
||||
/// </summary>
|
||||
public Vector2 DefaultShadowOffset = new Vector2(2);
|
||||
/// <summary>
|
||||
/// The default wobbly modifier used by this text formatter, which determines how the default <see cref="WobblyCode"/> is drawn if no custom value is used.
|
||||
/// Note that this value only has an effect on the default formatting codes created through the <see cref="TextFormatter(bool, bool, bool, bool)"/> constructor.
|
||||
/// </summary>
|
||||
public float DefaultWobblyModifier = 5;
|
||||
/// <summary>
|
||||
/// The default wobbly modifier used by this text formatter, which determines how the default <see cref="WobblyCode"/> is drawn if no custom value is used.
|
||||
/// Note that this value only has an effect on the default formatting codes created through the <see cref="TextFormatter(bool, bool, bool, bool)"/> constructor.
|
||||
/// </summary>
|
||||
public float DefaultWobblyHeight = 1 / 8F;
|
||||
/// <summary>
|
||||
/// The default outline thickness used by this text formatter, which determines how the default <see cref="OutlineCode"/> is drawn if no custom value is used.
|
||||
/// Note that this value only has an effect on the default formatting codes created through the <see cref="TextFormatter(bool, bool, bool, bool)"/> constructor.
|
||||
/// </summary>
|
||||
public float DefaultOutlineThickness = 2;
|
||||
/// <summary>
|
||||
/// The default outline color used by this text formatter, which determines how the default <see cref="OutlineCode"/> is drawn if no custom value is used.
|
||||
/// Note that this value only has an effect on the default formatting codes created through the <see cref="TextFormatter(bool, bool, bool, bool)"/> constructor.
|
||||
/// </summary>
|
||||
public Color DefaultOutlineColor = Color.Black;
|
||||
/// <summary>
|
||||
/// Whether the default outline used by this text formatter should also draw outlines diagonally, which determines how the default <see cref="OutlineCode"/> is drawn if no custom value is used. Non-diagonally drawn outlines might generally look better when using a pixelart font.
|
||||
/// Note that this value only has an effect on the default formatting codes created through the <see cref="TextFormatter(bool, bool, bool, bool)"/> constructor.
|
||||
/// </summary>
|
||||
public bool OutlineDiagonals = true;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new text formatter with an optional set of default formatting codes.
|
||||
/// </summary>
|
||||
|
@ -41,14 +102,18 @@ namespace MLEM.Formatting {
|
|||
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(@"<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) : Color.Black,
|
||||
new Vector2(float.TryParse(m.Groups[2].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var offset) ? offset : 2)));
|
||||
this.Codes.Add(new Regex("<u>"), (f, m, r) => new UnderlineCode(m, r, 1 / 16F, 0.85F));
|
||||
this.Codes.Add(new Regex("<st>"), (f, m, r) => new UnderlineCode(m, r, 1 / 16F, 0.55F));
|
||||
m.Groups[1].Success ? ColorHelper.FromHexString(m.Groups[1].Value) : this.DefaultShadowColor,
|
||||
float.TryParse(m.Groups[2].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var offset) ? new Vector2(offset) : this.DefaultShadowOffset));
|
||||
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(@"<sub(?: ([+-.0-9]+))?>"), (f, m, r) => new SubSupCode(m, r,
|
||||
float.TryParse(m.Groups[1].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var off) ? off : 0.15F));
|
||||
float.TryParse(m.Groups[1].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var off) ? off : this.DefaultSubOffset));
|
||||
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 : -0.25F));
|
||||
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,
|
||||
m.Groups[1].Success ? ColorHelper.FromHexString(m.Groups[1].Value) : this.DefaultOutlineColor,
|
||||
float.TryParse(m.Groups[2].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var thickness) ? thickness : this.DefaultOutlineThickness,
|
||||
this.OutlineDiagonals));
|
||||
}
|
||||
|
||||
// color codes
|
||||
|
@ -65,8 +130,8 @@ namespace MLEM.Formatting {
|
|||
// animation codes
|
||||
if (hasAnimations) {
|
||||
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 : 5,
|
||||
float.TryParse(m.Groups[2].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var heightMod) ? heightMod : 1 / 8F));
|
||||
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));
|
||||
}
|
||||
|
||||
// control codes
|
||||
|
@ -131,35 +196,50 @@ namespace MLEM.Formatting {
|
|||
public string ResolveMacros(string s) {
|
||||
// resolve macros that resolve into macros
|
||||
var rec = 0;
|
||||
var ret = s;
|
||||
bool matched;
|
||||
do {
|
||||
matched = false;
|
||||
foreach (var macro in this.Macros) {
|
||||
s = macro.Key.Replace(s, m => {
|
||||
ret = macro.Key.Replace(ret, m => {
|
||||
// if the match evaluator was queried, then we know we matched something
|
||||
matched = true;
|
||||
return macro.Value(this, m, macro.Key);
|
||||
});
|
||||
}
|
||||
rec++;
|
||||
if (rec >= 16)
|
||||
throw new ArithmeticException($"A string resolved macros recursively too many times. Does it contain any conflicting macros?\n{s}");
|
||||
if (rec >= 64)
|
||||
throw new ArithmeticException($"A string resolved macros recursively too many times. Does it contain any conflicting macros?\nOriginal: {s}\nCurrent: {ret}");
|
||||
} while (matched);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Strips all formatting codes from the given string, causing a string without any formatting codes to be returned.
|
||||
/// Note that, if a <see cref="TokenizedString"/> has already been created using <see cref="Tokenize"/>, it is more efficient to use <see cref="TokenizedString.String"/> or <see cref="TokenizedString.DisplayString"/>.
|
||||
/// </summary>
|
||||
/// <param name="s">The string to strip formatting codes from.</param>
|
||||
/// <returns>The stripped string.</returns>
|
||||
public string StripAllFormatting(string s) {
|
||||
foreach (var regex in this.Codes.Keys)
|
||||
s = regex.Replace(s, string.Empty);
|
||||
return s;
|
||||
}
|
||||
|
||||
private Code GetNextCode(string s, int index, int maxIndex = int.MaxValue) {
|
||||
var (c, m, r) = this.Codes
|
||||
.Select(kv => (c: kv.Value, m: kv.Key.Match(s, index), r: kv.Key))
|
||||
.Where(kv => kv.m.Success && kv.m.Index <= maxIndex)
|
||||
.OrderBy(kv => kv.m.Index)
|
||||
var (constructor, match, regex) = this.Codes
|
||||
.Select(kv => (Constructor: kv.Value, Match: kv.Key.Match(s, index), Regex: kv.Key))
|
||||
.Where(kv => kv.Match.Success && kv.Match.Index <= maxIndex)
|
||||
.OrderBy(kv => kv.Match.Index)
|
||||
.FirstOrDefault();
|
||||
return c?.Invoke(this, m, r);
|
||||
return constructor?.Invoke(this, match, regex);
|
||||
}
|
||||
|
||||
private static string StripFormatting(GenericFont font, string s, IEnumerable<Code> codes) {
|
||||
foreach (var code in codes) {
|
||||
#pragma warning disable CS0618
|
||||
// this can be combined with StripAllFormatting (which was added after GetReplacementString was deprecated) once GetReplacementString is removed
|
||||
// (just make this method accept a set of regular expressions, and then call it with all code keys in StripAllFormatting, and the applied codes' regexes in Tokenize)
|
||||
s = code.Regex.Replace(s, code.GetReplacementString(font));
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
|
|
@ -189,29 +189,37 @@ namespace MLEM.Formatting {
|
|||
}
|
||||
|
||||
/// <inheritdoc cref="GenericFont.DrawString(SpriteBatch,string,Vector2,Color,float,Vector2,float,SpriteEffects,float)"/>
|
||||
public void Draw(GameTime time, SpriteBatch batch, Vector2 pos, GenericFont font, Color color, float scale, float depth) {
|
||||
public void Draw(GameTime time, SpriteBatch batch, Vector2 pos, GenericFont font, Color color, float scale, float depth, int? startIndex = null, int? endIndex = null) {
|
||||
var innerOffset = new Vector2(this.initialInnerOffset * scale, 0);
|
||||
for (var t = 0; t < this.Tokens.Length; t++) {
|
||||
var token = this.Tokens[t];
|
||||
if (endIndex != null && token.Index >= endIndex)
|
||||
return;
|
||||
|
||||
var drawFont = token.GetFont(font);
|
||||
var drawColor = token.GetColor(color);
|
||||
|
||||
if (startIndex == null || token.Index >= startIndex)
|
||||
token.DrawSelf(time, batch, pos + innerOffset, drawFont, drawColor, scale, depth);
|
||||
innerOffset.X += token.GetSelfWidth(drawFont) * scale;
|
||||
|
||||
var indexInToken = 0;
|
||||
for (var l = 0; l < token.SplitDisplayString.Length; l++) {
|
||||
var charIndex = 0;
|
||||
var cpsIndex = 0;
|
||||
var line = new CodePointSource(token.SplitDisplayString[l]);
|
||||
while (charIndex < line.Length) {
|
||||
var (codePoint, length) = line.GetCodePoint(charIndex);
|
||||
while (cpsIndex < line.Length) {
|
||||
if (endIndex != null && token.Index + indexInToken >= endIndex)
|
||||
return;
|
||||
|
||||
var (codePoint, length) = line.GetCodePoint(cpsIndex);
|
||||
var character = CodePointSource.ToString(codePoint);
|
||||
|
||||
if (startIndex == null || token.Index + indexInToken >= startIndex)
|
||||
token.DrawCharacter(time, batch, codePoint, character, indexInToken, pos + innerOffset, drawFont, drawColor, scale, depth);
|
||||
|
||||
innerOffset.X += drawFont.MeasureString(character).X * scale;
|
||||
charIndex += length;
|
||||
indexInToken++;
|
||||
indexInToken += length;
|
||||
cpsIndex += length;
|
||||
}
|
||||
|
||||
// only split at a new line, not between tokens!
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
|
@ -13,7 +14,25 @@ namespace MLEM.Input {
|
|||
public readonly struct GenericInput : IEquatable<GenericInput> {
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InputType"/> of this generic input's current <see cref="value"/>.
|
||||
/// All <see cref="GenericInput"/> values created from all values of the <see cref="Keys"/> enum.
|
||||
/// </summary>
|
||||
public static readonly GenericInput[] AllKeys = InputHandler.AllKeys.Select(k => (GenericInput) k).ToArray();
|
||||
/// <summary>
|
||||
/// All <see cref="GenericInput"/> values created from all values of the <see cref="Input.MouseButton"/> enum.
|
||||
/// </summary>
|
||||
public static readonly GenericInput[] AllMouseButtons = MouseExtensions.MouseButtons.Select(k => (GenericInput) k).ToArray();
|
||||
/// <summary>
|
||||
/// All <see cref="GenericInput"/> values created from all values of the <see cref="Buttons"/> enum.
|
||||
/// </summary>
|
||||
public static readonly GenericInput[] AllButtons = InputHandler.AllButtons.Select(k => (GenericInput) k).ToArray();
|
||||
/// <summary>
|
||||
/// All <see cref="GenericInput"/> values created from all values of the <see cref="Keys"/>, <see cref="Input.MouseButton"/> and <see cref="Buttons"/> enums.
|
||||
/// This collection represents all possible valid, non-default <see cref="GenericInput"/> values.
|
||||
/// </summary>
|
||||
public static readonly GenericInput[] AllInputs = GenericInput.AllKeys.Concat(GenericInput.AllMouseButtons).Concat(GenericInput.AllButtons).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InputType"/> of this generic input's current value.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public readonly InputType Type;
|
||||
|
|
|
@ -193,16 +193,18 @@ namespace MLEM.Input {
|
|||
/// <summary>
|
||||
/// Creates a new input handler with optional initial values.
|
||||
/// </summary>
|
||||
/// <param name="game">The game instance that this input handler belongs to</param>
|
||||
/// <param name="handleKeyboard">If keyboard input should be handled</param>
|
||||
/// <param name="handleMouse">If mouse input should be handled</param>
|
||||
/// <param name="handleGamepads">If gamepad input should be handled</param>
|
||||
/// <param name="handleTouch">If touch input should be handled</param>
|
||||
public InputHandler(Game game, bool handleKeyboard = true, bool handleMouse = true, bool handleGamepads = true, bool handleTouch = true) : base(game) {
|
||||
/// <param name="game">The game instance that this input handler belongs to.</param>
|
||||
/// <param name="handleKeyboard">The initial value for <see cref="HandleKeyboard"/>, which determines whether this input handler handles keyboard inputs.</param>
|
||||
/// <param name="handleMouse">The initial value for <see cref="HandleMouse"/>, which determines whether this input handler handles mouse inputs.</param>
|
||||
/// <param name="handleGamepads">The initial value for <see cref="HandleGamepads"/>, which determines whether this input handler handles gamepad inputs.</param>
|
||||
/// <param name="handleTouch">The initial value for <see cref="HandleTouch"/>, which determines whether this input handler handles touch inputs.</param>
|
||||
/// <param name="externalGestureHandling">The initial value for <see cref="ExternalGestureHandling"/>, which determines whether gestures will be supplied using <see cref="AddExternalGesture"/> (or this input handler should handle gestures itself).</param>
|
||||
public InputHandler(Game game, bool handleKeyboard = true, bool handleMouse = true, bool handleGamepads = true, bool handleTouch = true, bool externalGestureHandling = false) : base(game) {
|
||||
this.HandleKeyboard = handleKeyboard;
|
||||
this.HandleMouse = handleMouse;
|
||||
this.HandleGamepads = handleGamepads;
|
||||
this.HandleTouch = handleTouch;
|
||||
this.ExternalGestureHandling = externalGestureHandling;
|
||||
this.Gestures = this.gestures.AsReadOnly();
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue