mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-25 22:18:34 +01:00
Compare commits
No commits in common. "9b090c954f0b26b6270ecf64c59907b047ba5d18" and "a1c5b8e2d6a25c63ac11e47f67ba8e8cfa0ef43f" have entirely different histories.
9b090c954f
...
a1c5b8e2d6
225 changed files with 2336 additions and 4893 deletions
113
.editorconfig
113
.editorconfig
|
@ -1,113 +0,0 @@
|
|||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# Microsoft .NET properties
|
||||
csharp_new_line_before_catch = false
|
||||
csharp_new_line_before_else = false
|
||||
csharp_new_line_before_finally = false
|
||||
csharp_new_line_before_members_in_object_initializers = false
|
||||
csharp_new_line_before_open_brace = none
|
||||
csharp_new_line_between_query_expression_clauses = false
|
||||
csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async:suggestion
|
||||
csharp_space_after_cast = true
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
dotnet_naming_rule.private_constants_rule.severity = warning
|
||||
dotnet_naming_rule.private_constants_rule.style = upper_camel_case_style
|
||||
dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols
|
||||
dotnet_naming_rule.private_instance_fields_rule.severity = warning
|
||||
dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style
|
||||
dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols
|
||||
dotnet_naming_rule.private_static_fields_rule.severity = warning
|
||||
dotnet_naming_rule.private_static_fields_rule.style = lower_camel_case_style
|
||||
dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols
|
||||
dotnet_naming_rule.private_static_readonly_rule.severity = warning
|
||||
dotnet_naming_rule.private_static_readonly_rule.style = upper_camel_case_style
|
||||
dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols
|
||||
dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
|
||||
dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case
|
||||
dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_constants_symbols.required_modifiers = const
|
||||
dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
|
||||
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none
|
||||
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
dotnet_style_qualification_for_event = true:suggestion
|
||||
dotnet_style_qualification_for_field = true:suggestion
|
||||
dotnet_style_qualification_for_method = true:suggestion
|
||||
dotnet_style_qualification_for_property = true:suggestion
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||
|
||||
# ReSharper properties
|
||||
resharper_blank_lines_after_block_statements = 0
|
||||
resharper_blank_lines_around_auto_property = 0
|
||||
resharper_blank_lines_around_local_method = 0
|
||||
resharper_blank_lines_around_property = 0
|
||||
resharper_blank_lines_inside_type = 1
|
||||
resharper_braces_for_dowhile = required_for_multiline
|
||||
resharper_braces_for_fixed = required_for_multiline
|
||||
resharper_braces_for_for = required_for_multiline
|
||||
resharper_braces_for_foreach = required_for_multiline
|
||||
resharper_braces_for_ifelse = required_for_multiline
|
||||
resharper_braces_for_lock = required_for_multiline
|
||||
resharper_braces_for_using = required_for_multiline
|
||||
resharper_braces_for_while = required_for_multiline
|
||||
resharper_braces_redundant = false
|
||||
resharper_csharp_blank_lines_around_field = 0
|
||||
resharper_csharp_blank_lines_around_single_line_invocable = 1
|
||||
resharper_csharp_empty_block_style = together
|
||||
resharper_csharp_keep_blank_lines_in_code = 1
|
||||
resharper_csharp_keep_blank_lines_in_declarations = 1
|
||||
resharper_csharp_max_line_length = unset
|
||||
resharper_csharp_stick_comment = false
|
||||
resharper_csharp_wrap_before_ternary_opsigns = false
|
||||
resharper_csharp_wrap_multiple_declaration_style = wrap_if_long
|
||||
resharper_csharp_wrap_multiple_type_parameter_constraints_style = wrap_if_long
|
||||
resharper_csharp_wrap_ternary_expr_style = wrap_if_long
|
||||
resharper_force_attribute_style = join
|
||||
resharper_indent_nested_fixed_stmt = true
|
||||
resharper_indent_nested_foreach_stmt = true
|
||||
resharper_indent_nested_for_stmt = true
|
||||
resharper_indent_nested_lock_stmt = true
|
||||
resharper_indent_nested_usings_stmt = true
|
||||
resharper_indent_nested_while_stmt = true
|
||||
resharper_indent_preprocessor_if = usual_indent
|
||||
resharper_indent_preprocessor_other = usual_indent
|
||||
resharper_keep_existing_declaration_parens_arrangement = false
|
||||
resharper_keep_existing_embedded_arrangement = false
|
||||
resharper_keep_existing_expr_member_arrangement = false
|
||||
resharper_keep_existing_property_patterns_arrangement = false
|
||||
resharper_keep_existing_switch_expression_arrangement = false
|
||||
resharper_parentheses_redundancy_style = remove
|
||||
resharper_place_attribute_on_same_line = false
|
||||
resharper_place_expr_accessor_on_single_line = true
|
||||
resharper_place_expr_method_on_single_line = true
|
||||
resharper_place_expr_property_on_single_line = true
|
||||
resharper_place_simple_accessor_on_single_line = false
|
||||
resharper_place_simple_anonymousmethod_on_single_line = false
|
||||
resharper_place_simple_embedded_statement_on_same_line = false
|
||||
resharper_space_around_arrow_op = true
|
||||
resharper_space_within_empty_braces = false
|
||||
resharper_space_within_single_line_array_initializer_braces = false
|
||||
resharper_static_members_qualify_members = field, property, event, method
|
||||
resharper_wrap_for_stmt_header_style = wrap_if_long
|
||||
resharper_wrap_object_and_collection_initializer_style = wrap_if_long
|
||||
resharper_xmldoc_attribute_indent = align_by_first_attribute
|
||||
resharper_xmldoc_attribute_style = on_single_line
|
||||
resharper_xmldoc_pi_attribute_style = on_single_line
|
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -1,6 +0,0 @@
|
|||
[submodule "FNA"]
|
||||
path = FNA
|
||||
url = https://github.com/FNA-XNA/FNA
|
||||
[submodule "FontStashSharp"]
|
||||
path = FontStashSharp
|
||||
url = https://github.com/FontStashSharp/FontStashSharp
|
87
CHANGELOG.md
87
CHANGELOG.md
|
@ -2,98 +2,11 @@
|
|||
MLEM tries to adhere to [semantic versioning](https://semver.org/). Breaking changes are written in **bold**.
|
||||
|
||||
Jump to version:
|
||||
- [6.0.0](#600)
|
||||
- [5.3.0](#530)
|
||||
- [5.2.0](#520)
|
||||
- [5.1.0](#510)
|
||||
- [5.0.0](#500)
|
||||
|
||||
## 6.0.0
|
||||
### MLEM
|
||||
Additions
|
||||
- Added consuming variants of IsPressed methods to InputHandler and Keybind
|
||||
- Added SpriteBatchContext struct and extensions
|
||||
- Added InputHandler.InvertPressBehavior
|
||||
- Added ReverseInput, ReverseOutput and AndThen to Easings
|
||||
- Added an Enum constructor to GenericInput
|
||||
- Added RandomPitchModifier and GetRandomPitch to SoundEffectInfo
|
||||
- Added TextInput class, which is an isolated version of MLEM.Ui's TextField logic
|
||||
- Added MLEM.FNA, which is fully compatible with FNA
|
||||
- Added TryGetUpTime, GetUpTime, TryGetTimeSincePress and GetTimeSincePress to InputHandler
|
||||
|
||||
Improvements
|
||||
- Allow comparing Keybind and Combination based on the amount of modifiers they have
|
||||
- Allow using multiple textures in a StaticSpriteBatch
|
||||
- Added GenericInput support for Buttons.None
|
||||
- Improved the way terminating formatting codes work by introducing SimpleEndCode
|
||||
- Allow RandomExtensions to operate on any ICollection
|
||||
|
||||
Removals
|
||||
- Marked AStar.InfiniteCost as obsolete
|
||||
|
||||
### MLEM.Ui
|
||||
Additions
|
||||
- Added Element.AutoNavGroup which allows forming groups for auto-navigation
|
||||
- Added UiMarkdownParser
|
||||
- Added MLEM.Ui.FNA, which is fully compatible with FNA
|
||||
|
||||
Improvements
|
||||
- Ensure that Element.IsMouseOver is always accurate by making it an auto-property
|
||||
- Started using SpriteBatchContext for Draw and DrawTransformed methods
|
||||
- Make use of the new consuming variants in InputHandler and Keybind to consume UiControls inputs
|
||||
- Allow Tooltip to manage more than one paragraph and make it easier to add new lines
|
||||
- Allow adding dropdown elements at a specified index
|
||||
- Turned Tooltip paragraph styling into style properties
|
||||
- Improved ElementHelper.AddTooltip overloads
|
||||
- Don't query a paragraph's text callback in the constructor
|
||||
- Allow manually hiding a paragraph without its text overriding the hidden state
|
||||
- Added optional isKeybindAllowed parameter to KeybindButton
|
||||
- Allow manually setting a RootElement as CanBeActive
|
||||
|
||||
Fixes
|
||||
- Fixed auto-nav tooltip displaying on the selected element even when not in auto-nav mode
|
||||
- Fixed radio buttons not unchecking all other radio buttons with the same root element
|
||||
- Fixed elements not being deselected when removed through RemoveChild
|
||||
- Fixed elements sometimes staying hidden when they shouldn't in scrolling panels
|
||||
- Fixed elements' OnDeselected events not being raised when CanBeSelected is set to false while selected
|
||||
- Fixed gamepad auto-nav angle being incorrect for some elements
|
||||
|
||||
Removals
|
||||
- Marked old Draw and DrawTransformed overloads as obsolete in favor of SpriteBatchContext ones
|
||||
- Marked Tooltip.Paragraph as obsolete in favor of new Paragraphs collection
|
||||
|
||||
### MLEM.Extended
|
||||
Additions
|
||||
- Added LayerPositionF
|
||||
- Added MLEM.Extended.FNA, which is fully compatible with FNA
|
||||
|
||||
Improvements
|
||||
- Allow using a StaticSpriteBatch to render an IndividualTiledMapRenderer
|
||||
|
||||
### MLEM.Data
|
||||
Additions
|
||||
- Added the ability to add padding to RuntimeTexturePacker texture regions
|
||||
- Added the ability to pack UniformTextureAtlas and DataTextureAtlas using RuntimeTexturePacker
|
||||
- Added MLEM.Data.FNA, which is fully compatible with FNA
|
||||
|
||||
Improvements
|
||||
- Premultiply textures when using RawContentManager
|
||||
- Allow enumerating all region names of a DataTextureAtlas
|
||||
- Cache RuntimeTexturePacker texture data while packing to improve performance
|
||||
- Greatly improved RuntimeTexturePacker performance
|
||||
- Allow specifying multiple names for a DataTextureAtlas region
|
||||
|
||||
Fixes
|
||||
- Fixed SoundEffectReader incorrectly claiming it could read ogg and mp3 files
|
||||
|
||||
### MLEM.Startup
|
||||
Additions
|
||||
- Added MLEM.Startup.FNA, which is fully compatible with FNA
|
||||
|
||||
### MLEM.Templates
|
||||
Improvements
|
||||
- Updated to MonoGame 3.8.1
|
||||
|
||||
## 5.3.0
|
||||
### MLEM
|
||||
Additions
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-mgcb": {
|
||||
"version": "3.8.1.263",
|
||||
"commands": [
|
||||
"mgcb"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor": {
|
||||
"version": "3.8.1.263",
|
||||
"commands": [
|
||||
"mgcb-editor"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-linux": {
|
||||
"version": "3.8.1.263",
|
||||
"commands": [
|
||||
"mgcb-editor-linux"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-windows": {
|
||||
"version": "3.8.1.263",
|
||||
"commands": [
|
||||
"mgcb-editor-windows"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-mac": {
|
||||
"version": "3.8.1.263",
|
||||
"commands": [
|
||||
"mgcb-editor-mac"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,17 @@
|
|||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Content.PM;
|
||||
using Android.Net;
|
||||
using Android.OS;
|
||||
using Android.Views;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using MLEM.Extensions;
|
||||
using MLEM.Misc;
|
||||
using Uri = Android.Net.Uri;
|
||||
using static Android.Views.SystemUiFlags;
|
||||
|
||||
namespace Demos.Android;
|
||||
|
||||
[Activity(
|
||||
namespace Demos.Android {
|
||||
[Activity(
|
||||
Label = "@string/app_name",
|
||||
MainLauncher = true,
|
||||
Icon = "@drawable/icon",
|
||||
|
@ -16,8 +19,8 @@ namespace Demos.Android;
|
|||
LaunchMode = LaunchMode.SingleInstance,
|
||||
ScreenOrientation = ScreenOrientation.UserLandscape,
|
||||
ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden | ConfigChanges.ScreenSize
|
||||
)]
|
||||
public class Activity1 : AndroidGameActivity {
|
||||
)]
|
||||
public class Activity1 : AndroidGameActivity {
|
||||
|
||||
private GameImpl game;
|
||||
private View view;
|
||||
|
@ -44,12 +47,9 @@ public class Activity1 : AndroidGameActivity {
|
|||
public override void OnWindowFocusChanged(bool hasFocus) {
|
||||
base.OnWindowFocusChanged(hasFocus);
|
||||
// hide the status bar
|
||||
if (hasFocus) {
|
||||
#pragma warning disable CS0618
|
||||
// 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
|
||||
}
|
||||
if (hasFocus)
|
||||
this.Window.DecorView.SystemUiVisibility = (StatusBarVisibility) (ImmersiveSticky | LayoutStable | LayoutHideNavigation | LayoutFullscreen | HideNavigation | Fullscreen);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
<?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" />
|
||||
<application android:label="MLEM Android Demos" />
|
||||
</manifest>
|
|
@ -1,22 +1,104 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.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>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{410C0262-131C-4D0E-910D-D01B4F7143E0}</ProjectGuid>
|
||||
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Demos.Android</RootNamespace>
|
||||
<AssemblyName>Demos.Android</AssemblyName>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AndroidApplication>true</AndroidApplication>
|
||||
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
|
||||
<AndroidResgenClass>Resource</AndroidResgenClass>
|
||||
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
|
||||
<AndroidStoreUncompressedFileExtensions>.m4a</AndroidStoreUncompressedFileExtensions>
|
||||
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
|
||||
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
|
||||
<AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk>
|
||||
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
|
||||
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
|
||||
<AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent>
|
||||
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>portable</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\$(MonoGamePlatform)\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;ANDROID</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
|
||||
<AndroidLinkMode>None</AndroidLinkMode>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>portable</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\$(MonoGamePlatform)\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DefineConstants>TRACE;ANDROID</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
|
||||
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
|
||||
<AotAssemblies>false</AotAssemblies>
|
||||
<EnableLLVM>false</EnableLLVM>
|
||||
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
|
||||
<BundleAssemblies>false</BundleAssemblies>
|
||||
<MandroidI18n />
|
||||
<AndroidPackageFormat>aab</AndroidPackageFormat>
|
||||
<AndroidUseAapt2>true</AndroidUseAapt2>
|
||||
<AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.1.263" />
|
||||
<PackageReference Include="MonoGame.Framework.Android" Version="3.8.1.263" />
|
||||
|
||||
<ProjectReference Include="..\Demos\Demos.csproj" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="Mono.Android" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Activity1.cs" />
|
||||
<Compile Include="Resources\Resource.Designer.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\Drawable\Icon.png" />
|
||||
<AndroidResource Include="Resources\Values\Strings.xml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MonoGameContentReference Include="..\Demos\Content\Content.mgcb" />
|
||||
<None Include="..\Demos\Content\*\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Properties\AndroidManifest.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Coroutine" Version="2.1.3" />
|
||||
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.0.1641" />
|
||||
<PackageReference Include="MonoGame.Framework.Android" Version="3.8.0.1641" />
|
||||
<PackageReference Include="TextCopy" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Demos\Demos.csproj">
|
||||
<Project>{1bc4682b-aa14-4937-b5c7-707e20fe88ff}</Project>
|
||||
<Name>Demos</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\MLEM.Startup\MLEM.Startup.csproj">
|
||||
<Project>{997f4739-7bec-4621-b9ca-68deb2d74412}</Project>
|
||||
<Name>MLEM.Startup</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.csproj">
|
||||
<Project>{6f00629a-8b87-4264-8896-19983285e32f}</Project>
|
||||
<Name>MLEM.Ui</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\MLEM\MLEM.csproj">
|
||||
<Project>{1d6ab762-43c4-4775-8924-707c7ec3f142}</Project>
|
||||
<Name>MLEM</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
||||
</Project>
|
||||
|
|
5
Demos.Android/Properties/AndroidManifest.xml
Normal file
5
Demos.Android/Properties/AndroidManifest.xml
Normal file
|
@ -0,0 +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:installLocation="auto" android:versionCode="1" android:versionName="1.0">
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" />
|
||||
<application android:label="MLEM Android Demos" android:resizeableActivity="true" />
|
||||
</manifest>
|
15
Demos.Android/Properties/AssemblyInfo.cs
Normal file
15
Demos.Android/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("MLEM Android Demos")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("MLEM Android Demos")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2018")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
76
Demos.Android/Resources/Resource.Designer.cs
generated
Normal file
76
Demos.Android/Resources/Resource.Designer.cs
generated
Normal file
|
@ -0,0 +1,76 @@
|
|||
#pragma warning disable 1591
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[assembly: global::Android.Runtime.ResourceDesignerAttribute("Demos.Android.Resource", IsApplication=true)]
|
||||
|
||||
namespace Demos.Android
|
||||
{
|
||||
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
|
||||
public partial class Resource
|
||||
{
|
||||
|
||||
static Resource()
|
||||
{
|
||||
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
|
||||
}
|
||||
|
||||
public static void UpdateIdValues()
|
||||
{
|
||||
}
|
||||
|
||||
public partial class Attribute
|
||||
{
|
||||
|
||||
static Attribute()
|
||||
{
|
||||
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
|
||||
}
|
||||
|
||||
private Attribute()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public partial class Drawable
|
||||
{
|
||||
|
||||
// aapt resource value: 0x7F010000
|
||||
public const int Icon = 2130771968;
|
||||
|
||||
static Drawable()
|
||||
{
|
||||
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
|
||||
}
|
||||
|
||||
private Drawable()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public partial class String
|
||||
{
|
||||
|
||||
// aapt resource value: 0x7F020000
|
||||
public const int app_name = 2130837504;
|
||||
|
||||
static String()
|
||||
{
|
||||
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
|
||||
}
|
||||
|
||||
private String()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
|
@ -1,36 +0,0 @@
|
|||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-mgcb": {
|
||||
"version": "3.8.1.263",
|
||||
"commands": [
|
||||
"mgcb"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor": {
|
||||
"version": "3.8.1.263",
|
||||
"commands": [
|
||||
"mgcb-editor"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-linux": {
|
||||
"version": "3.8.1.263",
|
||||
"commands": [
|
||||
"mgcb-editor-linux"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-windows": {
|
||||
"version": "3.8.1.263",
|
||||
"commands": [
|
||||
"mgcb-editor-windows"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-mac": {
|
||||
"version": "3.8.1.263",
|
||||
"commands": [
|
||||
"mgcb-editor-mac"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<ApplicationIcon>Icon.ico</ApplicationIcon>
|
||||
<AssemblyName>MLEM Desktop Demos</AssemblyName>
|
||||
<RootNamespace>Demos.DesktopGL</RootNamespace>
|
||||
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
||||
<!-- We still use the MG content builder for ease of compatibility between the MG and FNA demo projects -->
|
||||
<MonoGamePlatform>DesktopGL</MonoGamePlatform>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Demos\Demos.FNA.csproj" />
|
||||
<ProjectReference Include="..\MLEM.Startup\MLEM.Startup.FNA.csproj" />
|
||||
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.FNA.csproj" />
|
||||
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.0.1641" />
|
||||
<ProjectReference Include="..\FNA\FNA.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MonoGameContentReference Include="..\Demos\Content\Content.mgcb" />
|
||||
<Content Include="..\Demos\Content\*\**" />
|
||||
<EmbeddedResource Include="Icon.ico" />
|
||||
<EmbeddedResource Include="Icon.bmp" />
|
||||
<Content Include="FnaNative/**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Link>%(Filename)%(Extension)</Link>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -2,32 +2,28 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<ApplicationIcon>Icon.ico</ApplicationIcon>
|
||||
<AssemblyName>MLEM Desktop Demos</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.1.263" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.263" />
|
||||
|
||||
<ProjectReference Include="..\Demos\Demos.csproj" />
|
||||
<ProjectReference Include="..\MLEM.Startup\MLEM.Startup.csproj" />
|
||||
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.csproj" />
|
||||
<ProjectReference Include="..\MLEM\MLEM.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.0.1641" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MonoGameContentReference Include="..\Demos\Content\Content.mgcb" />
|
||||
<Content Include="..\Demos\Content\*\**" />
|
||||
<EmbeddedResource Include="Icon.ico" />
|
||||
<EmbeddedResource Include="Icon.bmp" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="RestoreDotnetTools" BeforeTargets="Restore">
|
||||
<Message Text="Restoring dotnet tools" Importance="High" />
|
||||
<Exec Command="dotnet tool restore" />
|
||||
</Target>
|
||||
</Project>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,17 +1,11 @@
|
|||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MLEM.Misc;
|
||||
|
||||
namespace Demos.DesktopGL {
|
||||
public static class Program {
|
||||
|
||||
public static void Main() {
|
||||
#if FNA
|
||||
MlemPlatform.Current = new MlemPlatform.DesktopFna(a => TextInputEXT.TextInput += a);
|
||||
#else
|
||||
MlemPlatform.Current = new MlemPlatform.DesktopGl<TextInputEventArgs>((w, c) => w.TextInput += c);
|
||||
#endif
|
||||
using var game = new GameImpl();
|
||||
game.Run();
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace Demos {
|
|||
// using it having wrong coordinates and/or sizes
|
||||
// the regions that are part of the atlas are then referenced by region coordinates rather than texture coordinates
|
||||
// (as seen below)
|
||||
var atlas = new UniformTextureAtlas(Demo.LoadContent<Texture2D>("Textures/Anim"), 4, 4);
|
||||
var atlas = new UniformTextureAtlas(LoadContent<Texture2D>("Textures/Anim"), 4, 4);
|
||||
|
||||
// create the four animations by supplying the time per frame, and the four regions used
|
||||
// note that you don't need to use a texture atlas for this, you can also simply supply the texture and the regions manually here
|
||||
|
@ -90,7 +90,7 @@ namespace Demos {
|
|||
public override void DoDraw(GameTime gameTime) {
|
||||
this.GraphicsDevice.Clear(Color.CornflowerBlue);
|
||||
|
||||
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(10));
|
||||
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, transformMatrix: Matrix.CreateScale(10));
|
||||
// draw the group's current region
|
||||
// if not using a group, just draw the animation's CurrentRegion here
|
||||
this.SpriteBatch.Draw(this.group.CurrentRegion, new Vector2(10, 10), Color.White);
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace Demos {
|
|||
public override void LoadContent() {
|
||||
base.LoadContent();
|
||||
// The layout of the texture is important for auto tiling to work correctly, and is explained in the XML docs for the methods used
|
||||
this.texture = Demo.LoadContent<Texture2D>("Textures/AutoTiling");
|
||||
this.texture = LoadContent<Texture2D>("Textures/AutoTiling");
|
||||
|
||||
// in this example, a simple string array is used for layout purposes. As the AutoTiling method allows any kind of
|
||||
// comparison, the actual implementation can vary (for instance, based on a more in-depth tile map)
|
||||
|
@ -37,7 +37,7 @@ namespace Demos {
|
|||
this.GraphicsDevice.Clear(Color.Black);
|
||||
|
||||
// drawing the auto tiles
|
||||
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(10));
|
||||
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, transformMatrix: Matrix.CreateScale(10));
|
||||
for (var x = 0; x < 6; x++) {
|
||||
for (var y = 0; y < 5; y++) {
|
||||
// don't draw non-grass tiles ( )
|
||||
|
@ -59,12 +59,12 @@ namespace Demos {
|
|||
}
|
||||
|
||||
// the texture region supplied to the AutoTiling method should only encompass the first filler tile's location and size
|
||||
AutoTiling.DrawAutoTile(this.SpriteBatch, new Vector2(x + 1, y + 1) * AutoTilingDemo.TileSize, new TextureRegion(this.texture, 0, 0, AutoTilingDemo.TileSize, AutoTilingDemo.TileSize), ConnectsTo, Color.White);
|
||||
AutoTiling.DrawAutoTile(this.SpriteBatch, new Vector2(x + 1, y + 1) * TileSize, new TextureRegion(this.texture, 0, 0, TileSize, TileSize), ConnectsTo, Color.White);
|
||||
|
||||
// when drawing extended auto-tiles, the same rules apply, but the source texture layout is different
|
||||
var background = new TextureRegion(this.texture, 0, AutoTilingDemo.TileSize * 2, AutoTilingDemo.TileSize, AutoTilingDemo.TileSize);
|
||||
var overlay = new TextureRegion(this.texture, background.Area.OffsetCopy(new Point(AutoTilingDemo.TileSize, 0)));
|
||||
AutoTiling.DrawExtendedAutoTile(this.SpriteBatch, new Vector2(x + 8, y + 1) * AutoTilingDemo.TileSize, background, overlay, ConnectsTo, Color.White, Color.White);
|
||||
var background = new TextureRegion(this.texture, 0, TileSize * 2, TileSize, TileSize);
|
||||
var overlay = new TextureRegion(this.texture, background.Area.OffsetCopy(new Point(TileSize, 0)));
|
||||
AutoTiling.DrawExtendedAutoTile(this.SpriteBatch, new Vector2(x + 8, y + 1) * TileSize, background, overlay, ConnectsTo, Color.White, Color.White);
|
||||
}
|
||||
}
|
||||
this.SpriteBatch.End();
|
||||
|
|
|
@ -13,13 +13,6 @@
|
|||
|
||||
#---------------------------------- Content ---------------------------------#
|
||||
|
||||
#begin Fonts/MonospacedFont.spritefont
|
||||
/importer:FontDescriptionImporter
|
||||
/processor:FontDescriptionProcessor
|
||||
/processorParam:PremultiplyAlpha=True
|
||||
/processorParam:TextureFormat=Compressed
|
||||
/build:Fonts/MonospacedFont.spritefont
|
||||
|
||||
#begin Fonts/TestFont.spritefont
|
||||
/importer:FontDescriptionImporter
|
||||
/processor:FontDescriptionProcessor
|
||||
|
@ -41,8 +34,12 @@
|
|||
/processorParam:TextureFormat=Compressed
|
||||
/build:Fonts/TestFontItalic.spritefont
|
||||
|
||||
#begin Markdown.md
|
||||
/copy:Markdown.md
|
||||
#begin Fonts/MonospacedFont.spritefont
|
||||
/importer:FontDescriptionImporter
|
||||
/processor:FontDescriptionProcessor
|
||||
/processorParam:PremultiplyAlpha=True
|
||||
/processorParam:TextureFormat=Compressed
|
||||
/build:Fonts/MonospacedFont.spritefont
|
||||
|
||||
#begin Textures/Anim.png
|
||||
/importer:TextureImporter
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
# H1
|
||||
## H2
|
||||
### H3
|
||||
#### H4
|
||||
##### H5
|
||||
###### H6
|
||||
|
||||
Italics with *asterisks* or _underscores_.
|
||||
Bold with **asterisks** or __underscores__.
|
||||
Strikethrough with ~~two tildes~~.
|
||||
|
||||
[I'm an inline-style link](https://www.google.com)
|
||||
<http://www.example.com>
|
||||
|
||||
![](https://raw.githubusercontent.com/Ellpeck/MLEM/main/Media/Logo.png)
|
||||
|
||||
Some `inline code` right here
|
||||
|
||||
```js
|
||||
function codeBlock() {
|
||||
|
||||
}
|
||||
```
|
|
@ -1,21 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<RootNamespace>Demos</RootNamespace>
|
||||
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MLEM.Startup\MLEM.Startup.FNA.csproj" />
|
||||
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.FNA.csproj" />
|
||||
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FNA\FNA.Core.csproj">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -11,7 +11,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.263">
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace Demos {
|
|||
|
||||
private static readonly FieldInfo[] EasingFields = typeof(Easings)
|
||||
.GetFields(BindingFlags.Public | BindingFlags.Static).ToArray();
|
||||
private static readonly Easings.Easing[] Easings = EasingsDemo.EasingFields
|
||||
private static readonly Easings.Easing[] Easings = EasingFields
|
||||
.Select(f => (Easings.Easing) f.GetValue(null)).ToArray();
|
||||
private Group group;
|
||||
private int current;
|
||||
|
@ -27,11 +27,11 @@ namespace Demos {
|
|||
this.group = new Group(Anchor.TopCenter, Vector2.One) {CanBeMoused = false};
|
||||
this.group.AddChild(new Button(Anchor.AutoCenter, new Vector2(30, 10), "Next") {
|
||||
OnPressed = e => {
|
||||
this.current = (this.current + 1) % EasingsDemo.Easings.Length;
|
||||
this.current = (this.current + 1) % Easings.Length;
|
||||
this.progress = 0;
|
||||
}
|
||||
});
|
||||
this.group.AddChild(new Paragraph(Anchor.AutoCenter, 1, p => EasingsDemo.EasingFields[this.current].Name, true));
|
||||
this.group.AddChild(new Paragraph(Anchor.AutoCenter, 1, p => EasingFields[this.current].Name, true));
|
||||
this.UiRoot.AddChild(this.group);
|
||||
}
|
||||
|
||||
|
@ -43,11 +43,11 @@ namespace Demos {
|
|||
this.GraphicsDevice.Clear(Color.CornflowerBlue);
|
||||
base.DoDraw(time);
|
||||
|
||||
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null);
|
||||
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp);
|
||||
var view = this.GraphicsDevice.Viewport;
|
||||
|
||||
// graph the easing function
|
||||
var graphEase = EasingsDemo.Easings[this.current].ScaleInput(0, view.Width).ScaleOutput(-view.Height / 3, view.Height / 3);
|
||||
var graphEase = Easings[this.current].ScaleInput(0, view.Width).ScaleOutput(-view.Height / 3, view.Height / 3);
|
||||
for (var x = 0; x < view.Width; x++) {
|
||||
var area = new RectangleF(x - 2, view.Height / 2 - graphEase(x) - 2, 4, 4);
|
||||
this.SpriteBatch.Draw(this.SpriteBatch.GetBlankTexture(), area, Color.Green);
|
||||
|
@ -55,7 +55,7 @@ namespace Demos {
|
|||
|
||||
// draw a little dot to show what it would look like
|
||||
this.progress = (this.progress + (float) time.ElapsedGameTime.TotalSeconds / 2) % 1;
|
||||
var dotEase = EasingsDemo.Easings[this.current].AndReverse().ScaleOutput(0, view.Height / 4);
|
||||
var dotEase = Easings[this.current].AndReverse().ScaleOutput(0, view.Height / 4);
|
||||
var pos = new RectangleF(view.Width / 2 - 4, view.Height - 20 - dotEase(this.progress), 8, 8);
|
||||
this.SpriteBatch.Draw(this.SpriteBatch.GetBlankTexture(), pos, Color.Red);
|
||||
|
||||
|
|
|
@ -9,9 +9,7 @@ using MLEM.Textures;
|
|||
using MLEM.Ui;
|
||||
using MLEM.Ui.Elements;
|
||||
using MLEM.Ui.Style;
|
||||
#if !FNA
|
||||
using MonoGame.Framework.Utilities;
|
||||
#endif
|
||||
|
||||
namespace Demos {
|
||||
public class GameImpl : MlemGame {
|
||||
|
@ -25,12 +23,11 @@ namespace Demos {
|
|||
private TimeSpan secondCounter;
|
||||
|
||||
static GameImpl() {
|
||||
GameImpl.Demos.Add("Ui", ("An in-depth demonstration of the MLEM.Ui package and its abilities", game => new UiDemo(game)));
|
||||
GameImpl.Demos.Add("Text Formatting", ("A demonstration of MLEM's text formatting system", game => new TextFormattingDemo(game)));
|
||||
GameImpl.Demos.Add("Easings", ("An example of MLEM's Easings class, an adaptation of easings.net", game => new EasingsDemo(game)));
|
||||
GameImpl.Demos.Add("Pathfinding", ("An example of MLEM's A* pathfinding implementation in 2D", game => new PathfindingDemo(game)));
|
||||
GameImpl.Demos.Add("Animation and Texture Atlas", ("An example of UniformTextureAtlases, SpriteAnimations and SpriteAnimationGroups", game => new AnimationDemo(game)));
|
||||
GameImpl.Demos.Add("Auto Tiling", ("A demonstration of the AutoTiling class that MLEM provides", game => new AutoTilingDemo(game)));
|
||||
Demos.Add("Ui", ("An in-depth demonstration of the MLEM.Ui package and its abilities", game => new UiDemo(game)));
|
||||
Demos.Add("Easings", ("An example of MLEM's Easings class, an adaptation of easings.net", game => new EasingsDemo(game)));
|
||||
Demos.Add("Pathfinding", ("An example of MLEM's A* pathfinding implementation in 2D", game => new PathfindingDemo(game)));
|
||||
Demos.Add("Animation and Texture Atlas", ("An example of UniformTextureAtlases, SpriteAnimations and SpriteAnimationGroups", game => new AnimationDemo(game)));
|
||||
Demos.Add("Auto Tiling", ("A demonstration of the AutoTiling class that MLEM provides", game => new AutoTilingDemo(game)));
|
||||
}
|
||||
|
||||
public GameImpl() {
|
||||
|
@ -48,6 +45,13 @@ namespace Demos {
|
|||
}
|
||||
|
||||
protected override void LoadContent() {
|
||||
// TODO remove with MonoGame 3.8.1 https://github.com/MonoGame/MonoGame/issues/7298
|
||||
if (PlatformInfo.MonoGamePlatform == MonoGamePlatform.DesktopGL) {
|
||||
this.GraphicsDeviceManager.PreferredBackBufferWidth = 1280;
|
||||
this.GraphicsDeviceManager.PreferredBackBufferHeight = 720;
|
||||
this.GraphicsDeviceManager.ApplyChanges();
|
||||
}
|
||||
|
||||
base.LoadContent();
|
||||
this.UiSystem.AutoScaleReferenceSize = new Point(1280, 720);
|
||||
this.UiSystem.AutoScaleWithScreen = true;
|
||||
|
@ -70,7 +74,7 @@ namespace Demos {
|
|||
|
||||
selection.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Select the demo you want to see below using your mouse, touch input, your keyboard or a controller. Check the demos' <l https://github.com/Ellpeck/MLEM/tree/main/Demos>source code</l> for more in-depth explanations of their functionality or the <l https://mlem.ellpeck.de/>website</l> for tutorials and API documentation."));
|
||||
selection.AddChild(new VerticalSpace(5));
|
||||
foreach (var demo in GameImpl.Demos) {
|
||||
foreach (var demo in Demos) {
|
||||
selection.AddChild(new Button(Anchor.AutoCenter, new Vector2(1, 10), demo.Key, demo.Value.Item1) {
|
||||
OnPressed = e => {
|
||||
selection.IsHidden = true;
|
||||
|
@ -88,9 +92,9 @@ namespace Demos {
|
|||
}
|
||||
|
||||
protected override UiStyle InitializeDefaultUiStyle(SpriteBatch batch) {
|
||||
var tex = MlemGame.LoadContent<Texture2D>("Textures/Test");
|
||||
var tex = LoadContent<Texture2D>("Textures/Test");
|
||||
return new UntexturedStyle(this.SpriteBatch) {
|
||||
Font = new GenericSpriteFont(MlemGame.LoadContent<SpriteFont>("Fonts/TestFont")),
|
||||
Font = new GenericSpriteFont(LoadContent<SpriteFont>("Fonts/TestFont")),
|
||||
TextScale = 0.1F,
|
||||
PanelTexture = new NinePatch(new TextureRegion(tex, 0, 8, 24, 24), 8),
|
||||
ButtonTexture = new NinePatch(new TextureRegion(tex, 24, 8, 16, 16), 4),
|
||||
|
|
|
@ -41,8 +41,8 @@ namespace Demos {
|
|||
// pathfinder's constructor
|
||||
float Cost(Point pos, Point nextPos) {
|
||||
if (nextPos.X < 0 || nextPos.Y < 0 || nextPos.X >= 50 || nextPos.Y >= 50)
|
||||
return float.PositiveInfinity;
|
||||
return this.world[nextPos.X, nextPos.Y] ? 1 : float.PositiveInfinity;
|
||||
return AStar2.InfiniteCost;
|
||||
return this.world[nextPos.X, nextPos.Y] ? 1 : AStar2.InfiniteCost;
|
||||
}
|
||||
|
||||
// Actually initialize the pathfinder with the cost function, as well as specify if moving diagonally between tiles should be
|
||||
|
@ -73,7 +73,7 @@ namespace Demos {
|
|||
public override void DoDraw(GameTime gameTime) {
|
||||
this.GraphicsDevice.Clear(Color.White);
|
||||
|
||||
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(14));
|
||||
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, transformMatrix: Matrix.CreateScale(14));
|
||||
var tex = this.SpriteBatch.GetBlankTexture();
|
||||
// draw the world with simple shapes
|
||||
for (var x = 0; x < 50; x++) {
|
||||
|
@ -87,9 +87,9 @@ namespace Demos {
|
|||
// in a real game, you'd obviously make your characters walk along the path instead of drawing it
|
||||
if (this.path != null) {
|
||||
for (var i = 1; i < this.path.Count; i++) {
|
||||
var first = this.path[i - 1];
|
||||
var second = this.path[i];
|
||||
this.SpriteBatch.Draw(tex, RectangleF.FromCorners(new Vector2(first.X + 0.25F, first.Y + 0.25F), new Vector2(second.X + 0.75F, second.Y + 0.75F)), Color.Blue);
|
||||
var (firstX, firstY) = this.path[i - 1];
|
||||
var (secondX, secondY) = this.path[i];
|
||||
this.SpriteBatch.Draw(tex, RectangleF.FromCorners(new Vector2(firstX + 0.25F, firstY + 0.25F), new Vector2(secondX + 0.75F, secondY + 0.75F)), Color.Blue);
|
||||
}
|
||||
}
|
||||
this.SpriteBatch.End();
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Font;
|
||||
using MLEM.Formatting;
|
||||
using MLEM.Formatting.Codes;
|
||||
using MLEM.Startup;
|
||||
using MLEM.Textures;
|
||||
|
||||
namespace Demos {
|
||||
public class TextFormattingDemo : Demo {
|
||||
|
||||
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 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!\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 TextFormatter formatter;
|
||||
private TokenizedString tokenizedText;
|
||||
private GenericFont font;
|
||||
|
||||
public TextFormattingDemo(MlemGame game) : base(game) {}
|
||||
|
||||
public override void LoadContent() {
|
||||
this.Game.Window.ClientSizeChanged += this.OnResize;
|
||||
|
||||
// creating a new text formatter as well as a generic font to draw with
|
||||
this.formatter = new TextFormatter();
|
||||
// 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"));
|
||||
|
||||
// adding the image code used in the example to it
|
||||
var testTexture = Demo.LoadContent<Texture2D>("Textures/Test");
|
||||
this.formatter.AddImage("grass", new TextureRegion(testTexture, 0, 0, 8, 8));
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
public override void DoDraw(GameTime time) {
|
||||
this.GraphicsDevice.Clear(Color.DarkSlateGray);
|
||||
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null);
|
||||
|
||||
// we draw the tokenized text in the center of the screen
|
||||
// since the text is already center-aligned, we only need to align it on the y axis here
|
||||
var size = this.tokenizedText.Measure(this.font) * TextFormattingDemo.Scale;
|
||||
var pos = new Vector2(this.GraphicsDevice.Viewport.Width / 2, (this.GraphicsDevice.Viewport.Height - size.Y) / 2);
|
||||
this.tokenizedText.Draw(time, this.SpriteBatch, pos, this.font, Color.White, TextFormattingDemo.Scale, 0);
|
||||
|
||||
this.SpriteBatch.End();
|
||||
}
|
||||
|
||||
public override void Update(GameTime time) {
|
||||
// update our tokenized string to animate the animation codes
|
||||
this.tokenizedText.Update(time);
|
||||
}
|
||||
|
||||
public override void Clear() {
|
||||
base.Clear();
|
||||
this.Game.Window.ClientSizeChanged -= this.OnResize;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Coroutine;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
@ -8,13 +7,13 @@ using Microsoft.Xna.Framework.Graphics;
|
|||
using MLEM.Extensions;
|
||||
using MLEM.Font;
|
||||
using MLEM.Formatting;
|
||||
using MLEM.Formatting.Codes;
|
||||
using MLEM.Input;
|
||||
using MLEM.Misc;
|
||||
using MLEM.Startup;
|
||||
using MLEM.Textures;
|
||||
using MLEM.Ui;
|
||||
using MLEM.Ui.Elements;
|
||||
using MLEM.Ui.Parsers;
|
||||
using MLEM.Ui.Style;
|
||||
|
||||
namespace Demos {
|
||||
|
@ -27,7 +26,7 @@ namespace Demos {
|
|||
public UiDemo(MlemGame game) : base(game) {}
|
||||
|
||||
public override void LoadContent() {
|
||||
this.testTexture = Demo.LoadContent<Texture2D>("Textures/Test");
|
||||
this.testTexture = LoadContent<Texture2D>("Textures/Test");
|
||||
this.testPatch = new NinePatch(new TextureRegion(this.testTexture, 0, 8, 24, 24), 8);
|
||||
base.LoadContent();
|
||||
|
||||
|
@ -37,10 +36,7 @@ namespace Demos {
|
|||
// when using a SpriteFont, use GenericSpriteFont. When using a MonoGame.Extended BitmapFont, use GenericBitmapFont.
|
||||
// Wrapping fonts like this allows for both types to be usable within MLEM.Ui easily
|
||||
// Supplying a bold and an italic version is optional
|
||||
Font = new GenericSpriteFont(
|
||||
Demo.LoadContent<SpriteFont>("Fonts/TestFont"),
|
||||
Demo.LoadContent<SpriteFont>("Fonts/TestFontBold"),
|
||||
Demo.LoadContent<SpriteFont>("Fonts/TestFontItalic")),
|
||||
Font = new GenericSpriteFont(LoadContent<SpriteFont>("Fonts/TestFont"), LoadContent<SpriteFont>("Fonts/TestFontBold"), LoadContent<SpriteFont>("Fonts/TestFontItalic")),
|
||||
TextScale = 0.1F,
|
||||
PanelTexture = this.testPatch,
|
||||
ButtonTexture = new NinePatch(new TextureRegion(this.testTexture, 24, 8, 16, 16), 4),
|
||||
|
@ -51,7 +47,7 @@ namespace Demos {
|
|||
CheckboxCheckmark = new TextureRegion(this.testTexture, 24, 0, 8, 8),
|
||||
RadioTexture = new NinePatch(new TextureRegion(this.testTexture, 16, 0, 8, 8), 3),
|
||||
RadioCheckmark = new TextureRegion(this.testTexture, 32, 0, 8, 8),
|
||||
AdditionalFonts = {{"Monospaced", new GenericSpriteFont(Demo.LoadContent<SpriteFont>("Fonts/MonospacedFont"))}},
|
||||
AdditionalFonts = {{"Monospaced", new GenericSpriteFont(LoadContent<SpriteFont>("Fonts/MonospacedFont"))}},
|
||||
LinkColor = Color.CornflowerBlue
|
||||
};
|
||||
var untexturedStyle = new UntexturedStyle(this.SpriteBatch) {
|
||||
|
@ -73,7 +69,7 @@ namespace Demos {
|
|||
// add the root to the demos' ui
|
||||
this.UiRoot.AddChild(this.root);
|
||||
|
||||
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "This is a small demo for MLEM.Ui, a user interface library that is part of the MLEM Library for Extending MonoGame and FNA."));
|
||||
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "This is a small demo for MLEM.Ui, a user interface library that is part of the MLEM Library for Extending MonoGame."));
|
||||
var image = this.root.AddChild(new Image(Anchor.AutoCenter, new Vector2(50, 50), new TextureRegion(this.testTexture, 0, 0, 8, 8)) {IsHidden = true, Padding = new Padding(3)});
|
||||
// Setting the x or y coordinate of the size to 1 or a lower number causes the width or height to be a percentage of the parent's width or height
|
||||
// (for example, setting the size's x to 0.75 would make the element's width be 0.75*parentWidth)
|
||||
|
@ -92,7 +88,12 @@ namespace Demos {
|
|||
this.root.AddChild(new VerticalSpace(3));
|
||||
|
||||
// a paragraph with formatting codes. To see them all or to add more, check the TextFormatting class
|
||||
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Paragraphs can also contain <c Blue>formatting codes</c>, including colors and <i>text styles</i>. For more info, check out the <b>text formatting demo</b>!"));
|
||||
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Paragraphs can also contain <c Blue>formatting codes</c>, including colors and <i>text styles</i>. The names of all <c Orange>MonoGame Colors</c> can be used, as well as the codes <i>Italic</i>, <b>Bold</b>, <s>Drop Shadow'd</s> and <s><c Pink>mixed formatting</s></c>. You can also add additional fonts for things like\n<f Monospaced>void Code() {\n // Code\n}</f>\n<i>Even <c #ff611f82>inline custom colors</c> work!</i>"));
|
||||
|
||||
// adding some custom image formatting codes
|
||||
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Additionally, you can create custom formatting codes that contain <i Grass> images and more!"));
|
||||
this.UiSystem.TextFormatter.AddImage("Grass", image.Texture);
|
||||
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Defining text animations as formatting codes is also possible, including <a wobbly>wobbly text</a> at <a wobbly 8 0.25>different intensities</a>. Of course, more animations can be added though."));
|
||||
|
||||
this.root.AddChild(new VerticalSpace(3));
|
||||
this.root.AddChild(new Paragraph(Anchor.AutoCenter, 1, "Multiline text input:", true));
|
||||
|
@ -155,7 +156,7 @@ namespace Demos {
|
|||
|
||||
// Check the WobbleButton method for an explanation of how this button works
|
||||
this.root.AddChild(new Button(Anchor.AutoCenter, new Vector2(0.5F, 10), "Wobble Me", "This button wobbles around visually when clicked, but this doesn't affect its actual size and positioning") {
|
||||
OnPressed = element => CoroutineHandler.Start(UiDemo.WobbleButton(element)),
|
||||
OnPressed = element => CoroutineHandler.Start(WobbleButton(element)),
|
||||
PositionOffset = new Vector2(0, 1)
|
||||
});
|
||||
// Another button that shows animations!
|
||||
|
@ -188,13 +189,13 @@ namespace Demos {
|
|||
this.root.AddChild(new VerticalSpace(3));
|
||||
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Progress bars!"));
|
||||
var bar1 = this.root.AddChild(new ProgressBar(Anchor.AutoLeft, new Vector2(1, 8), Direction2.Right, 10) {PositionOffset = new Vector2(0, 1)});
|
||||
CoroutineHandler.Start(UiDemo.WobbleProgressBar(bar1));
|
||||
CoroutineHandler.Start(WobbleProgressBar(bar1));
|
||||
var bar2 = this.root.AddChild(new ProgressBar(Anchor.AutoLeft, new Vector2(1, 8), Direction2.Left, 10) {PositionOffset = new Vector2(0, 1)});
|
||||
CoroutineHandler.Start(UiDemo.WobbleProgressBar(bar2));
|
||||
CoroutineHandler.Start(WobbleProgressBar(bar2));
|
||||
var bar3 = this.root.AddChild(new ProgressBar(Anchor.AutoLeft, new Vector2(8, 30), Direction2.Down, 10) {PositionOffset = new Vector2(0, 1)});
|
||||
CoroutineHandler.Start(UiDemo.WobbleProgressBar(bar3));
|
||||
CoroutineHandler.Start(WobbleProgressBar(bar3));
|
||||
var bar4 = this.root.AddChild(new ProgressBar(Anchor.AutoInline, new Vector2(8, 30), Direction2.Up, 10) {PositionOffset = new Vector2(1, 0)});
|
||||
CoroutineHandler.Start(UiDemo.WobbleProgressBar(bar4));
|
||||
CoroutineHandler.Start(WobbleProgressBar(bar4));
|
||||
|
||||
this.root.AddChild(new VerticalSpace(3));
|
||||
var dropdown = this.root.AddChild(new Dropdown(Anchor.AutoLeft, new Vector2(1, 10), "Dropdown Menu"));
|
||||
|
@ -222,13 +223,6 @@ namespace Demos {
|
|||
alignPar.Alignment = alignment;
|
||||
};
|
||||
|
||||
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};
|
||||
using (var reader = new StreamReader(TitleContainer.OpenStream("Content/Markdown.md")))
|
||||
parser.ParseInto(reader.ReadToEnd(), this.root);
|
||||
|
||||
this.root.AddChild(new VerticalSpace(3));
|
||||
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "The code for this demo contains some examples for how to query element data. This is the output of that:"));
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# MLEM.Ui
|
||||
|
||||
**MLEM.Ui** is a Ui framework for MonoGame and FNA that features elements with automatic positioning and sizing. It contains various ready-made element types like buttons, paragraphs, text fields and more, along with the ability to easily create custom controls. It supports **mouse**, **keyboard**, **gamepad** and **touch** input with little to no additional setup work required.
|
||||
**MLEM.Ui** is a Ui framework for MonoGame that features elements with automatic positioning and sizing. It contains various ready-made element types like buttons, paragraphs, text fields and more, along with the ability to easily create custom controls. It supports **mouse**, **keyboard**, **gamepad** and **touch** input with little to no additional setup work required.
|
||||
|
||||
To see some of what MLEM.Ui can do, you can check out [the demo](https://github.com/Ellpeck/MLEM/blob/main/Demos/UiDemo.cs) as well.
|
||||
|
||||
|
@ -35,19 +35,15 @@ On desktop devices, MonoGame provides the `Window.TextInput` event that gets cal
|
|||
|
||||
To make MLEM compatible with all devices without publishing a separate version for each MonoGame platform, you have to set up the `MlemPlatform` class based on the system you're using MLEM.Ui with. This has to be done *before* initializing your `UiSystem`.
|
||||
|
||||
DesktopGL and WindowsDX using MonoGame:
|
||||
DesktopGL:
|
||||
```cs
|
||||
MlemPlatform.Current = new MlemPlatform.DesktopGl<TextInputEventArgs>((w, c) => w.TextInput += c);
|
||||
```
|
||||
Desktop using FNA:
|
||||
```cs
|
||||
MlemPlatform.Current = new MlemPlatform.DesktopFna(a => TextInputEXT.TextInput += a);
|
||||
```
|
||||
Mobile devices and consoles:
|
||||
```cs
|
||||
MlemPlatform.Current = new MlemPlatform.Mobile(KeyboardInput.Show, l => this.StartActivity(new Intent(Intent.ActionView, Uri.Parse(l))));
|
||||
```
|
||||
If you're not using text input, you can leave the platform uninitialized or just set it to a stub one like so:
|
||||
If you're not using text input, you can just set the platform to a stub one like so:
|
||||
```cs
|
||||
MlemPlatform.Current = new MlemPlatform.None();
|
||||
```
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
![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 Library for Extending MonoGame** is an addition to the game framework [MonoGame](https://www.monogame.net/) that provides extension methods, quality of life improvements and additional features like a ui system and easy input handling.
|
||||
|
||||
# What next?
|
||||
- Get it on [NuGet](https://www.nuget.org/packages?q=mlem)
|
||||
- Get prerelease builds on [BaGet](https://nuget.ellpeck.de/?q=mlem)
|
||||
- Get prerelease builds on [BaGet](https://nuget.ellpeck.de)
|
||||
- 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** is the base package, which provides extension methods and additional features for MonoGame
|
||||
- **MLEM.Ui** features 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 libraries
|
||||
- **MLEM.Data** provides simple loading and processing of textures and data
|
||||
- **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))
|
||||
- [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!
|
||||
|
@ -38,9 +38,9 @@ MLEM's [text formatting system](https://mlem.ellpeck.de/articles/text_formatting
|
|||
![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:
|
||||
There are several other libraries and tools that work well in combination with MonoGame 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
|
||||
- [GameBundle](https://github.com/Ellpeck/GameBundle), a tool that packages MonoGame and other .NET Core 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
|
||||
|
|
1
FNA
1
FNA
|
@ -1 +0,0 @@
|
|||
Subproject commit 102990f514f1e5bfac07d33f7c33e2e712946da4
|
|
@ -1 +0,0 @@
|
|||
Subproject commit f0774130cad6cec0b790a58bc7c811a186443fb3
|
5
Jenkinsfile
vendored
5
Jenkinsfile
vendored
|
@ -1,11 +1,6 @@
|
|||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
stage('Submodules') {
|
||||
steps {
|
||||
sh 'git submodule update --init --recursive --force'
|
||||
}
|
||||
}
|
||||
stage('Cake Build') {
|
||||
steps {
|
||||
sh 'dotnet tool restore'
|
||||
|
|
|
@ -13,16 +13,13 @@ namespace MLEM.Data.Content {
|
|||
|
||||
private static List<RawContentReader> readers;
|
||||
|
||||
private readonly List<IDisposable> disposableAssets = new List<IDisposable>();
|
||||
|
||||
/// <summary>
|
||||
/// The graphics device that this content manager uses
|
||||
/// </summary>
|
||||
public readonly GraphicsDevice GraphicsDevice;
|
||||
|
||||
private readonly List<IDisposable> disposableAssets = new List<IDisposable>();
|
||||
#if FNA
|
||||
private Dictionary<string, object> LoadedAssets { get; } = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new content manager with an optionally specified root directory.
|
||||
/// </summary>
|
||||
|
@ -53,19 +50,15 @@ namespace MLEM.Data.Content {
|
|||
/// <param name="originalAssetName">The original name of the asset.</param>
|
||||
/// <param name="currentAsset">The current asset instance.</param>
|
||||
/// <typeparam name="T">The asset's type.</typeparam>
|
||||
protected
|
||||
#if !FNA
|
||||
override
|
||||
#endif
|
||||
void ReloadAsset<T>(string originalAssetName, T currentAsset) {
|
||||
protected override void ReloadAsset<T>(string originalAssetName, T currentAsset) {
|
||||
this.Read(originalAssetName, currentAsset);
|
||||
}
|
||||
|
||||
private T Read<T>(string assetName, T existing) {
|
||||
var triedFiles = new List<string>();
|
||||
if (RawContentManager.readers == null)
|
||||
RawContentManager.readers = RawContentManager.CollectContentReaders();
|
||||
foreach (var reader in RawContentManager.readers) {
|
||||
if (readers == null)
|
||||
readers = CollectContentReaders();
|
||||
foreach (var reader in readers) {
|
||||
if (!reader.CanRead(typeof(T)))
|
||||
continue;
|
||||
foreach (var ext in reader.GetFileExtensions()) {
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace MLEM.Data.Content {
|
|||
|
||||
/// <inheritdoc />
|
||||
public override string[] GetFileExtensions() {
|
||||
return new[] {"wav"};
|
||||
return new[] {"ogg", "wav", "mp3"};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
using System.IO;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Extensions;
|
||||
|
||||
namespace MLEM.Data.Content {
|
||||
/// <inheritdoc />
|
||||
|
@ -9,26 +7,11 @@ namespace MLEM.Data.Content {
|
|||
|
||||
/// <inheritdoc />
|
||||
protected override Texture2D Read(RawContentManager manager, string assetPath, Stream stream, Texture2D existing) {
|
||||
#if !FNA
|
||||
if (existing != null) {
|
||||
existing.Reload(stream);
|
||||
return existing;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
// premultiply the texture's color to be in line with the pipeline's texture reader
|
||||
using (var texture = Texture2D.FromStream(manager.GraphicsDevice, stream)) {
|
||||
var ret = new Texture2D(manager.GraphicsDevice, texture.Width, texture.Height);
|
||||
using (var textureData = texture.GetTextureData()) {
|
||||
using (var retData = ret.GetTextureData()) {
|
||||
for (var x = 0; x < ret.Width; x++) {
|
||||
for (var y = 0; y < ret.Height; y++)
|
||||
retData[x, y] = Color.FromNonPremultiplied(textureData[x, y].ToVector4());
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
return Texture2D.FromStream(manager.GraphicsDevice, stream);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace MLEM.Data {
|
|||
/// <param name="content">The content manager to add the json serializer to</param>
|
||||
/// <param name="serializer">The json serializer to add</param>
|
||||
public static void SetJsonSerializer(this ContentManager content, JsonSerializer serializer) {
|
||||
ContentExtensions.Serializers[content] = serializer;
|
||||
Serializers[content] = serializer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -31,7 +31,7 @@ namespace MLEM.Data {
|
|||
/// <param name="content">The content manager whose serializer to get</param>
|
||||
/// <returns>The content manager's serializer</returns>
|
||||
public static JsonSerializer GetJsonSerializer(this ContentManager content) {
|
||||
if (!ContentExtensions.Serializers.TryGetValue(content, out var serializer)) {
|
||||
if (!Serializers.TryGetValue(content, out var serializer)) {
|
||||
serializer = JsonConverters.AddAll(new JsonSerializer());
|
||||
content.SetJsonSerializer(serializer);
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ namespace MLEM.Data {
|
|||
/// <param name="content">The content manager to add the converter to</param>
|
||||
/// <param name="converter">The converter to add</param>
|
||||
public static void AddJsonConverter(this ContentManager content, JsonConverter converter) {
|
||||
var serializer = content.GetJsonSerializer();
|
||||
var serializer = GetJsonSerializer(content);
|
||||
serializer.Converters.Add(converter);
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,7 @@ namespace MLEM.Data {
|
|||
public static T LoadJson<T>(this ContentManager content, string name, string[] extensions = null, JsonSerializer serializer = null) {
|
||||
var triedFiles = new List<string>();
|
||||
var serializerToUse = serializer ?? content.GetJsonSerializer();
|
||||
foreach (var extension in extensions ?? ContentExtensions.JsonExtensions) {
|
||||
foreach (var extension in extensions ?? JsonExtensions) {
|
||||
var file = Path.Combine(content.RootDirectory, name + extension);
|
||||
triedFiles.Add(file);
|
||||
try {
|
||||
|
|
|
@ -23,8 +23,8 @@ namespace MLEM.Data {
|
|||
/// <typeparam name="T">The type of the object to copy</typeparam>
|
||||
/// <returns>A shallow copy of the object</returns>
|
||||
[Obsolete("CopyExtensions has major flaws and insufficient speed compared to other libraries specifically designed for copying objects.")]
|
||||
public static T Copy<T>(this T obj, BindingFlags flags = CopyExtensions.DefaultFlags, Predicate<FieldInfo> fieldInclusion = null) {
|
||||
var copy = (T) CopyExtensions.Construct(typeof(T), flags);
|
||||
public static T Copy<T>(this T obj, BindingFlags flags = DefaultFlags, Predicate<FieldInfo> fieldInclusion = null) {
|
||||
var copy = (T) Construct(typeof(T), flags);
|
||||
obj.CopyInto(copy, flags, fieldInclusion);
|
||||
return copy;
|
||||
}
|
||||
|
@ -39,8 +39,8 @@ namespace MLEM.Data {
|
|||
/// <typeparam name="T">The type of the object to copy</typeparam>
|
||||
/// <returns>A deep copy of the object</returns>
|
||||
[Obsolete("CopyExtensions has major flaws and insufficient speed compared to other libraries specifically designed for copying objects.")]
|
||||
public static T DeepCopy<T>(this T obj, BindingFlags flags = CopyExtensions.DefaultFlags, Predicate<FieldInfo> fieldInclusion = null) {
|
||||
var copy = (T) CopyExtensions.Construct(typeof(T), flags);
|
||||
public static T DeepCopy<T>(this T obj, BindingFlags flags = DefaultFlags, Predicate<FieldInfo> fieldInclusion = null) {
|
||||
var copy = (T) Construct(typeof(T), flags);
|
||||
obj.DeepCopyInto(copy, flags, fieldInclusion);
|
||||
return copy;
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ namespace MLEM.Data {
|
|||
/// <param name="fieldInclusion">A predicate that determines whether or not the given field should be copied. If null, all fields will be copied.</param>
|
||||
/// <typeparam name="T">The type of the object to copy</typeparam>
|
||||
[Obsolete("CopyExtensions has major flaws and insufficient speed compared to other libraries specifically designed for copying objects.")]
|
||||
public static void CopyInto<T>(this T obj, T otherObj, BindingFlags flags = CopyExtensions.DefaultFlags, Predicate<FieldInfo> fieldInclusion = null) {
|
||||
public static void CopyInto<T>(this T obj, T otherObj, BindingFlags flags = DefaultFlags, Predicate<FieldInfo> fieldInclusion = null) {
|
||||
foreach (var field in typeof(T).GetFields(flags)) {
|
||||
if (fieldInclusion == null || fieldInclusion(field))
|
||||
field.SetValue(otherObj, field.GetValue(obj));
|
||||
|
@ -71,7 +71,7 @@ namespace MLEM.Data {
|
|||
/// <param name="fieldInclusion">A predicate that determines whether or not the given field should be copied. If null, all fields will be copied.</param>
|
||||
/// <typeparam name="T">The type of the object to copy</typeparam>
|
||||
[Obsolete("CopyExtensions has major flaws and insufficient speed compared to other libraries specifically designed for copying objects.")]
|
||||
public static void DeepCopyInto<T>(this T obj, T otherObj, BindingFlags flags = CopyExtensions.DefaultFlags, Predicate<FieldInfo> fieldInclusion = null) {
|
||||
public static void DeepCopyInto<T>(this T obj, T otherObj, BindingFlags flags = DefaultFlags, Predicate<FieldInfo> fieldInclusion = null) {
|
||||
foreach (var field in obj.GetType().GetFields(flags)) {
|
||||
if (fieldInclusion != null && !fieldInclusion(field))
|
||||
continue;
|
||||
|
@ -83,7 +83,7 @@ namespace MLEM.Data {
|
|||
var otherVal = field.GetValue(otherObj);
|
||||
// if the object we want to copy into doesn't have a value yet, we create one
|
||||
if (otherVal == null) {
|
||||
otherVal = CopyExtensions.Construct(field.FieldType, flags);
|
||||
otherVal = Construct(field.FieldType, flags);
|
||||
field.SetValue(otherObj, otherVal);
|
||||
}
|
||||
val.DeepCopyInto(otherVal, flags);
|
||||
|
@ -92,7 +92,7 @@ namespace MLEM.Data {
|
|||
}
|
||||
|
||||
private static object Construct(Type t, BindingFlags flags) {
|
||||
if (!CopyExtensions.ConstructorCache.TryGetValue(t, out var constructor)) {
|
||||
if (!ConstructorCache.TryGetValue(t, out var constructor)) {
|
||||
var constructors = t.GetConstructors(flags);
|
||||
// find a contructor with the correct attribute
|
||||
constructor = constructors.FirstOrDefault(c => c.GetCustomAttribute<CopyConstructorAttribute>() != null);
|
||||
|
@ -104,7 +104,7 @@ namespace MLEM.Data {
|
|||
constructor = constructors.FirstOrDefault();
|
||||
if (constructor == null)
|
||||
throw new NullReferenceException($"Type {t} does not have a constructor with the required visibility");
|
||||
CopyExtensions.ConstructorCache.Add(t, constructor);
|
||||
ConstructorCache.Add(t, constructor);
|
||||
}
|
||||
return constructor.Invoke(new object[constructor.GetParameters().Length]);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
@ -6,7 +5,6 @@ using System.Text.RegularExpressions;
|
|||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Content;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Extensions;
|
||||
using MLEM.Textures;
|
||||
|
||||
namespace MLEM.Data {
|
||||
|
@ -52,13 +50,9 @@ namespace MLEM.Data {
|
|||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns an enumerable of all of the <see cref="TextureRegion"/> values in this atlas.
|
||||
/// Returns an enumerable of all of the <see cref="TextureRegion"/>s in this atlas.
|
||||
/// </summary>
|
||||
public IEnumerable<TextureRegion> Regions => this.regions.Values;
|
||||
/// <summary>
|
||||
/// Returns an enumerable of all of the <see cref="TextureRegion"/> names in this atlas.
|
||||
/// </summary>
|
||||
public IEnumerable<string> RegionNames => this.regions.Keys;
|
||||
|
||||
private readonly Dictionary<string, TextureRegion> regions = new Dictionary<string, TextureRegion>();
|
||||
|
||||
|
@ -85,8 +79,9 @@ namespace MLEM.Data {
|
|||
}
|
||||
var atlas = new DataTextureAtlas(texture);
|
||||
|
||||
// parse each texture region: "<names> loc <u> <v> <w> <h> [piv <px> <py>] [off <ox> <oy>]"
|
||||
// parse each texture region: "<name> loc <u> <v> <w> <h> [piv <px> <py>] [off <ox> <oy>]"
|
||||
foreach (Match match in Regex.Matches(text, @"(.+)\W+loc\W+([0-9]+)\W+([0-9]+)\W+([0-9]+)\W+([0-9]+)\W*(?:piv\W+([0-9.]+)\W+([0-9.]+))?\W*(?:off\W+([0-9.]+)\W+([0-9.]+))?")) {
|
||||
var name = match.Groups[1].Value.Trim();
|
||||
// offset
|
||||
var off = !match.Groups[8].Success ? Vector2.Zero : new Vector2(
|
||||
float.Parse(match.Groups[8].Value, CultureInfo.InvariantCulture),
|
||||
|
@ -96,23 +91,18 @@ namespace MLEM.Data {
|
|||
var loc = new Rectangle(
|
||||
int.Parse(match.Groups[2].Value), int.Parse(match.Groups[3].Value),
|
||||
int.Parse(match.Groups[4].Value), int.Parse(match.Groups[5].Value));
|
||||
loc.Offset(off.ToPoint());
|
||||
loc.Offset(off);
|
||||
|
||||
// pivot
|
||||
var piv = !match.Groups[6].Success ? Vector2.Zero : off + new Vector2(
|
||||
float.Parse(match.Groups[6].Value, CultureInfo.InvariantCulture) - (pivotRelative ? 0 : loc.X),
|
||||
float.Parse(match.Groups[7].Value, CultureInfo.InvariantCulture) - (pivotRelative ? 0 : loc.Y));
|
||||
|
||||
foreach (var name in Regex.Split(match.Groups[1].Value, @"\W")) {
|
||||
var trimmed = name.Trim();
|
||||
if (trimmed.Length <= 0)
|
||||
continue;
|
||||
var region = new TextureRegion(texture, loc) {
|
||||
PivotPixels = piv,
|
||||
Name = trimmed
|
||||
Name = name
|
||||
};
|
||||
atlas.regions.Add(trimmed, region);
|
||||
}
|
||||
atlas.regions.Add(name, region);
|
||||
}
|
||||
|
||||
return atlas;
|
||||
|
|
|
@ -60,7 +60,7 @@ namespace MLEM.Data {
|
|||
if (this.allFlagsCache == null)
|
||||
this.allFlagsCache = new Dictionary<DynamicEnum, bool>();
|
||||
if (!this.allFlagsCache.TryGetValue(flags, out var ret)) {
|
||||
ret = (DynamicEnum.GetValue(this) & DynamicEnum.GetValue(flags)) == DynamicEnum.GetValue(flags);
|
||||
ret = (GetValue(this) & GetValue(flags)) == GetValue(flags);
|
||||
this.allFlagsCache.Add(flags, ret);
|
||||
}
|
||||
return ret;
|
||||
|
@ -76,7 +76,7 @@ namespace MLEM.Data {
|
|||
if (this.anyFlagsCache == null)
|
||||
this.anyFlagsCache = new Dictionary<DynamicEnum, bool>();
|
||||
if (!this.anyFlagsCache.TryGetValue(flags, out var ret)) {
|
||||
ret = (DynamicEnum.GetValue(this) & DynamicEnum.GetValue(flags)) != 0;
|
||||
ret = (GetValue(this) & GetValue(flags)) != 0;
|
||||
this.anyFlagsCache.Add(flags, ret);
|
||||
}
|
||||
return ret;
|
||||
|
@ -87,13 +87,13 @@ namespace MLEM.Data {
|
|||
public override string ToString() {
|
||||
if (this.name == null) {
|
||||
var included = new List<DynamicEnum>();
|
||||
if (DynamicEnum.GetValue(this) != 0) {
|
||||
foreach (var v in DynamicEnum.GetValues(this.GetType())) {
|
||||
if (this.HasFlag(v) && DynamicEnum.GetValue(v) != 0)
|
||||
if (GetValue(this) != 0) {
|
||||
foreach (var v in GetValues(this.GetType())) {
|
||||
if (this.HasFlag(v) && GetValue(v) != 0)
|
||||
included.Add(v);
|
||||
}
|
||||
}
|
||||
this.name = included.Count > 0 ? string.Join(" | ", included) : DynamicEnum.GetValue(this).ToString();
|
||||
this.name = included.Count > 0 ? string.Join(" | ", included) : GetValue(this).ToString();
|
||||
}
|
||||
return this.name;
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ namespace MLEM.Data {
|
|||
/// <returns>The newly created enum value</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if the name or value passed are already present</exception>
|
||||
public static T Add<T>(string name, BigInteger value) where T : DynamicEnum {
|
||||
var storage = DynamicEnum.GetStorage(typeof(T));
|
||||
var storage = GetStorage(typeof(T));
|
||||
|
||||
// cached parsed values and names might be incomplete with new values
|
||||
storage.ClearCaches();
|
||||
|
@ -119,7 +119,7 @@ namespace MLEM.Data {
|
|||
throw new ArgumentException($"Duplicate name {name}", nameof(name));
|
||||
}
|
||||
|
||||
var ret = DynamicEnum.Construct(typeof(T), name, value);
|
||||
var ret = Construct(typeof(T), name, value);
|
||||
storage.Values.Add(value, ret);
|
||||
return (T) ret;
|
||||
}
|
||||
|
@ -134,9 +134,9 @@ namespace MLEM.Data {
|
|||
/// <returns>The newly created enum value</returns>
|
||||
public static T AddValue<T>(string name) where T : DynamicEnum {
|
||||
BigInteger value = 0;
|
||||
while (DynamicEnum.GetStorage(typeof(T)).Values.ContainsKey(value))
|
||||
while (GetStorage(typeof(T)).Values.ContainsKey(value))
|
||||
value++;
|
||||
return DynamicEnum.Add<T>(name, value);
|
||||
return Add<T>(name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -149,9 +149,9 @@ namespace MLEM.Data {
|
|||
/// <returns>The newly created enum value</returns>
|
||||
public static T AddFlag<T>(string name) where T : DynamicEnum {
|
||||
BigInteger value = 1;
|
||||
while (DynamicEnum.GetStorage(typeof(T)).Values.ContainsKey(value))
|
||||
while (GetStorage(typeof(T)).Values.ContainsKey(value))
|
||||
value <<= 1;
|
||||
return DynamicEnum.Add<T>(name, value);
|
||||
return Add<T>(name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -161,7 +161,7 @@ namespace MLEM.Data {
|
|||
/// <typeparam name="T">The type whose values to get</typeparam>
|
||||
/// <returns>The defined values for the given type</returns>
|
||||
public static IEnumerable<T> GetValues<T>() where T : DynamicEnum {
|
||||
return DynamicEnum.GetValues(typeof(T)).Cast<T>();
|
||||
return GetValues(typeof(T)).Cast<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -171,7 +171,7 @@ namespace MLEM.Data {
|
|||
/// <param name="type">The type whose values to get</param>
|
||||
/// <returns>The defined values for the given type</returns>
|
||||
public static IEnumerable<DynamicEnum> GetValues(Type type) {
|
||||
return DynamicEnum.GetStorage(type).Values.Values;
|
||||
return GetStorage(type).Values.Values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -182,9 +182,9 @@ namespace MLEM.Data {
|
|||
/// <typeparam name="T">The type of the values</typeparam>
|
||||
/// <returns>The bitwise OR (|) combination</returns>
|
||||
public static T Or<T>(T left, T right) where T : DynamicEnum {
|
||||
var cache = DynamicEnum.GetStorage(typeof(T)).OrCache;
|
||||
var cache = GetStorage(typeof(T)).OrCache;
|
||||
if (!cache.TryGetValue((left, right), out var ret)) {
|
||||
ret = DynamicEnum.GetEnumValue<T>(DynamicEnum.GetValue(left) | DynamicEnum.GetValue(right));
|
||||
ret = GetEnumValue<T>(GetValue(left) | GetValue(right));
|
||||
cache.Add((left, right), ret);
|
||||
}
|
||||
return (T) ret;
|
||||
|
@ -198,9 +198,9 @@ namespace MLEM.Data {
|
|||
/// <typeparam name="T">The type of the values</typeparam>
|
||||
/// <returns>The bitwise AND (&) combination</returns>
|
||||
public static T And<T>(T left, T right) where T : DynamicEnum {
|
||||
var cache = DynamicEnum.GetStorage(typeof(T)).AndCache;
|
||||
var cache = GetStorage(typeof(T)).AndCache;
|
||||
if (!cache.TryGetValue((left, right), out var ret)) {
|
||||
ret = DynamicEnum.GetEnumValue<T>(DynamicEnum.GetValue(left) & DynamicEnum.GetValue(right));
|
||||
ret = GetEnumValue<T>(GetValue(left) & GetValue(right));
|
||||
cache.Add((left, right), ret);
|
||||
}
|
||||
return (T) ret;
|
||||
|
@ -214,9 +214,9 @@ namespace MLEM.Data {
|
|||
/// <typeparam name="T">The type of the values</typeparam>
|
||||
/// <returns>The bitwise XOR (^) combination</returns>
|
||||
public static T Xor<T>(T left, T right) where T : DynamicEnum {
|
||||
var cache = DynamicEnum.GetStorage(typeof(T)).XorCache;
|
||||
var cache = GetStorage(typeof(T)).XorCache;
|
||||
if (!cache.TryGetValue((left, right), out var ret)) {
|
||||
ret = DynamicEnum.GetEnumValue<T>(DynamicEnum.GetValue(left) ^ DynamicEnum.GetValue(right));
|
||||
ret = GetEnumValue<T>(GetValue(left) ^ GetValue(right));
|
||||
cache.Add((left, right), ret);
|
||||
}
|
||||
return (T) ret;
|
||||
|
@ -229,9 +229,9 @@ namespace MLEM.Data {
|
|||
/// <typeparam name="T">The type of the values</typeparam>
|
||||
/// <returns>The bitwise NEG (~) value</returns>
|
||||
public static T Neg<T>(T value) where T : DynamicEnum {
|
||||
var cache = DynamicEnum.GetStorage(typeof(T)).NegCache;
|
||||
var cache = GetStorage(typeof(T)).NegCache;
|
||||
if (!cache.TryGetValue(value, out var ret)) {
|
||||
ret = DynamicEnum.GetEnumValue<T>(~DynamicEnum.GetValue(value));
|
||||
ret = GetEnumValue<T>(~GetValue(value));
|
||||
cache.Add(value, ret);
|
||||
}
|
||||
return (T) ret;
|
||||
|
@ -253,7 +253,7 @@ namespace MLEM.Data {
|
|||
/// <typeparam name="T">The type that the returned dynamic enum should have</typeparam>
|
||||
/// <returns>The defined or combined dynamic enum value</returns>
|
||||
public static T GetEnumValue<T>(BigInteger value) where T : DynamicEnum {
|
||||
return (T) DynamicEnum.GetEnumValue(typeof(T), value);
|
||||
return (T) GetEnumValue(typeof(T), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -263,7 +263,7 @@ namespace MLEM.Data {
|
|||
/// <param name="value">The value whose dynamic enum value to get</param>
|
||||
/// <returns>The defined or combined dynamic enum value</returns>
|
||||
public static DynamicEnum GetEnumValue(Type type, BigInteger value) {
|
||||
var storage = DynamicEnum.GetStorage(type);
|
||||
var storage = GetStorage(type);
|
||||
|
||||
// get the defined value if it exists
|
||||
if (storage.Values.TryGetValue(value, out var defined))
|
||||
|
@ -271,7 +271,7 @@ namespace MLEM.Data {
|
|||
|
||||
// otherwise, cache the combined value
|
||||
if (!storage.FlagCache.TryGetValue(value, out var combined)) {
|
||||
combined = DynamicEnum.Construct(type, null, value);
|
||||
combined = Construct(type, null, value);
|
||||
storage.FlagCache.Add(value, combined);
|
||||
}
|
||||
return combined;
|
||||
|
@ -286,7 +286,7 @@ namespace MLEM.Data {
|
|||
/// <typeparam name="T">The type of the dynamic enum value to parse</typeparam>
|
||||
/// <returns>The parsed enum value, or null if parsing fails</returns>
|
||||
public static T Parse<T>(string strg) where T : DynamicEnum {
|
||||
return (T) DynamicEnum.Parse(typeof(T), strg);
|
||||
return (T) Parse(typeof(T), strg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -297,28 +297,28 @@ namespace MLEM.Data {
|
|||
/// <param name="strg">The string to parse into a dynamic enum value</param>
|
||||
/// <returns>The parsed enum value, or null if parsing fails</returns>
|
||||
public static DynamicEnum Parse(Type type, string strg) {
|
||||
var cache = DynamicEnum.GetStorage(type).ParseCache;
|
||||
var cache = GetStorage(type).ParseCache;
|
||||
if (!cache.TryGetValue(strg, out var cached)) {
|
||||
BigInteger? accum = null;
|
||||
foreach (var val in strg.Split('|')) {
|
||||
foreach (var defined in DynamicEnum.GetValues(type)) {
|
||||
foreach (var defined in GetValues(type)) {
|
||||
if (defined.name == val.Trim()) {
|
||||
accum = (accum ?? 0) | DynamicEnum.GetValue(defined);
|
||||
accum = (accum ?? 0) | GetValue(defined);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (accum != null)
|
||||
cached = DynamicEnum.GetEnumValue(type, accum.Value);
|
||||
cached = GetEnumValue(type, accum.Value);
|
||||
cache.Add(strg, cached);
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
|
||||
private static Storage GetStorage(Type type) {
|
||||
if (!DynamicEnum.Storages.TryGetValue(type, out var storage)) {
|
||||
if (!Storages.TryGetValue(type, out var storage)) {
|
||||
storage = new Storage();
|
||||
DynamicEnum.Storages.Add(type, storage);
|
||||
Storages.Add(type, storage);
|
||||
}
|
||||
return storage;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace MLEM.Data.Json {
|
|||
/// <param name="serializer">The serializer to add the converters to</param>
|
||||
/// <returns>The given serializer, for chaining</returns>
|
||||
public static JsonSerializer AddAll(JsonSerializer serializer) {
|
||||
foreach (var converter in JsonConverters.Converters)
|
||||
foreach (var converter in Converters)
|
||||
serializer.Converters.Add(converter);
|
||||
return serializer;
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace MLEM.Data.Json {
|
|||
/// </summary>
|
||||
/// <param name="type">The type that the dictionary is declared in</param>
|
||||
/// <param name="memberName">The name of the dictionary itself</param>
|
||||
public StaticJsonConverter(Type type, string memberName) : this(StaticJsonConverter<T>.GetEntries(type, memberName)) {}
|
||||
public StaticJsonConverter(Type type, string memberName) : this(GetEntries(type, memberName)) {}
|
||||
|
||||
/// <summary>Writes the JSON representation of the object.</summary>
|
||||
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter" /> to write to.</param>
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
<RootNamespace>MLEM.Data</RootNamespace>
|
||||
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Authors>Ellpeck</Authors>
|
||||
<Description>Simple loading and processing of textures and other data for FNA, including the ability to load non-XNB content files easily</Description>
|
||||
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes>
|
||||
<PackageTags>fna ellpeck mlem utility extensions data serialize</PackageTags>
|
||||
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
|
||||
|
||||
<!--TODO remove lidgren support eventually (methods marked as obsolete since 5.2.0)-->
|
||||
<PackageReference Include="Lidgren.Network" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json.Bson" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<ProjectReference Include="..\FNA\FNA.Core.csproj">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
|
||||
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -3,12 +3,11 @@
|
|||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Authors>Ellpeck</Authors>
|
||||
<Description>Simple loading and processing of textures and other data for MonoGame, including the ability to load non-XNB content files easily</Description>
|
||||
<Description>Simple loading and processing of textures and data for MLEM Library for Extending MonoGame</Description>
|
||||
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes>
|
||||
<PackageTags>monogame ellpeck mlem utility extensions data serialize</PackageTags>
|
||||
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
|
||||
|
@ -16,6 +15,7 @@
|
|||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -6,7 +6,6 @@ using Microsoft.Xna.Framework;
|
|||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Extensions;
|
||||
using MLEM.Textures;
|
||||
using static MLEM.Extensions.TextureExtensions;
|
||||
|
||||
namespace MLEM.Data {
|
||||
/// <summary>
|
||||
|
@ -34,10 +33,7 @@ namespace MLEM.Data {
|
|||
/// </summary>
|
||||
public TimeSpan LastTotalTime => this.LastCalculationTime + this.LastPackTime;
|
||||
|
||||
private readonly List<Request> texturesToPack = new List<Request>();
|
||||
private readonly List<Request> alreadyPackedTextures = new List<Request>();
|
||||
private readonly Dictionary<Point, Point> firstPossiblePosForSizeCache = new Dictionary<Point, Point>();
|
||||
private readonly Dictionary<Texture2D, TextureData> dataCache = new Dictionary<Texture2D, TextureData>();
|
||||
private readonly List<Request> textures = new List<Request>();
|
||||
private readonly bool autoIncreaseMaxWidth;
|
||||
private readonly bool forcePowerOfTwo;
|
||||
private readonly bool forceSquare;
|
||||
|
@ -46,13 +42,13 @@ namespace MLEM.Data {
|
|||
private int maxWidth;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new runtime texture packer with the given settings.
|
||||
/// 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="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>
|
||||
/// <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>
|
||||
public RuntimeTexturePacker(int maxWidth = 2048, bool autoIncreaseMaxWidth = false, bool forcePowerOfTwo = false, bool forceSquare = false, bool disposeTextures = false) {
|
||||
this.maxWidth = maxWidth;
|
||||
this.autoIncreaseMaxWidth = autoIncreaseMaxWidth;
|
||||
|
@ -62,99 +58,37 @@ namespace MLEM.Data {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new <see cref="UniformTextureAtlas"/> to this texture packer to be packed.
|
||||
/// The passed <see cref="Action{T}"/> is invoked in <see cref="Pack"/> and provides the caller with the resulting dictionary of texture regions on the <see cref="PackedTexture"/>, mapped to their x and y positions on the original <see cref="UniformTextureAtlas"/>.
|
||||
/// Note that the resulting data cannot be converted back into a <see cref="UniformTextureAtlas"/>, since the resulting texture regions might be scattered throughout the <see cref="PackedTexture"/>.
|
||||
/// </summary>
|
||||
/// <param name="atlas">The texture atlas to pack.</param>
|
||||
/// <param name="result">The result callback which will receive the resulting texture regions.</param>
|
||||
/// <param name="padding">The padding that the texture should have around itself. This can be useful if texture bleeding issues occur due to texture coordinate rounding.</param>
|
||||
/// <param name="padWithPixels">Whether the texture's padding should be filled with a copy of the texture's border, rather than transparent pixels. This value only has an effect if <paramref name="padding"/> is greater than 0.</param>
|
||||
/// <param name="ignoreTransparent">Whether completely transparent texture regions in the <paramref name="atlas"/> should be ignored. If this is true, they will not be part of the <paramref name="result"/> collection either.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to add data to a packer that has already been packed, or when trying to add a texture width a width greater than the defined max width.</exception>
|
||||
public void Add(UniformTextureAtlas atlas, Action<Dictionary<Point, TextureRegion>> result, int padding = 0, bool padWithPixels = false, bool ignoreTransparent = false) {
|
||||
var addedRegions = new List<TextureRegion>();
|
||||
var resultRegions = new Dictionary<Point, TextureRegion>();
|
||||
for (var x = 0; x < atlas.RegionAmountX; x++) {
|
||||
for (var y = 0; y < atlas.RegionAmountY; y++) {
|
||||
var pos = new Point(x, y);
|
||||
var region = atlas[pos];
|
||||
|
||||
if (ignoreTransparent) {
|
||||
if (this.IsTransparent(region))
|
||||
continue;
|
||||
}
|
||||
|
||||
this.Add(region, r => {
|
||||
resultRegions.Add(pos, r);
|
||||
if (resultRegions.Count >= addedRegions.Count)
|
||||
result.Invoke(resultRegions);
|
||||
}, padding, padWithPixels);
|
||||
addedRegions.Add(region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new <see cref="DataTextureAtlas"/> to this texture packer to be packed.
|
||||
/// The passed <see cref="Action{T}"/> is invoked in <see cref="Pack"/> and provides the caller with the resulting dictionary of texture regions on the <see cref="PackedTexture"/>, mapped to their name on the original <see cref="DataTextureAtlas"/>.
|
||||
/// Note that the resulting data cannot be converted back into a <see cref="DataTextureAtlas"/>, since the resulting texture regions might be scattered throughout the <see cref="PackedTexture"/>.
|
||||
/// </summary>
|
||||
/// <param name="atlas">The texture atlas to pack.</param>
|
||||
/// <param name="result">The result callback which will receive the resulting texture regions.</param>
|
||||
/// <param name="padding">The padding that the texture should have around itself. This can be useful if texture bleeding issues occur due to texture coordinate rounding.</param>
|
||||
/// <param name="padWithPixels">Whether the texture's padding should be filled with a copy of the texture's border, rather than transparent pixels. This value only has an effect if <paramref name="padding"/> is greater than 0.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to add data to a packer that has already been packed, or when trying to add a texture width a width greater than the defined max width.</exception>
|
||||
public void Add(DataTextureAtlas atlas, Action<Dictionary<string, TextureRegion>> result, int padding = 0, bool padWithPixels = false) {
|
||||
var atlasRegions = atlas.RegionNames.ToArray();
|
||||
var resultRegions = new Dictionary<string, TextureRegion>();
|
||||
foreach (var region in atlasRegions) {
|
||||
this.Add(atlas[region], r => {
|
||||
resultRegions.Add(region, r);
|
||||
if (resultRegions.Count >= atlasRegions.Length)
|
||||
result.Invoke(resultRegions);
|
||||
}, padding, padWithPixels);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new <see cref="Texture2D"/> to this texture packer to be packed.
|
||||
/// Adds a new texture to this texture packer to be packed.
|
||||
/// The passed <see cref="Action{T}"/> is invoked in <see cref="Pack"/> and provides the caller with the resulting texture region on the <see cref="PackedTexture"/>.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture to pack.</param>
|
||||
/// <param name="result">The result callback which will receive the resulting texture region.</param>
|
||||
/// <param name="padding">The padding that the texture should have around itself. This can be useful if texture bleeding issues occur due to texture coordinate rounding.</param>
|
||||
/// <param name="padWithPixels">Whether the texture's padding should be filled with a copy of the texture's border, rather than transparent pixels. This value only has an effect if <paramref name="padding"/> is greater than 0.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to add data to a packer that has already been packed, or when trying to add a texture width a width greater than the defined max width.</exception>
|
||||
public void Add(Texture2D texture, Action<TextureRegion> result, int padding = 0, bool padWithPixels = false) {
|
||||
this.Add(new TextureRegion(texture), result, padding, padWithPixels);
|
||||
/// <param name="texture">The texture to pack</param>
|
||||
/// <param name="result">The result callback which will receive the resulting texture region</param>
|
||||
public void Add(Texture2D texture, Action<TextureRegion> result) {
|
||||
this.Add(new TextureRegion(texture), result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new <see cref="TextureRegion"/> to this texture packer to be packed.
|
||||
/// The passed <see cref="Action{T}"/> is invoked in <see cref="Pack"/> and provides the caller with the resulting texture region on the <see cref="PackedTexture"/>.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture region to pack.</param>
|
||||
/// <param name="result">The result callback which will receive the resulting texture region.</param>
|
||||
/// <param name="padding">The padding that the texture should have around itself. This can be useful if texture bleeding issues occur due to texture coordinate rounding.</param>
|
||||
/// <param name="padWithPixels">Whether the texture's padding should be filled with a copy of the texture's border, rather than transparent pixels. This value only has an effect if <paramref name="padding"/> is greater than 0.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to add data to a packer that has already been packed, or when trying to add a texture width a width greater than the defined max width.</exception>
|
||||
public void Add(TextureRegion texture, Action<TextureRegion> result, int padding = 0, bool padWithPixels = false) {
|
||||
/// <param name="texture">The texture to pack</param>
|
||||
/// <param name="result">The result callback which will receive the resulting texture region</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to add data to a packer that has already been packed, or when trying to add a texture width a width greater than the defined max width</exception>
|
||||
public void Add(TextureRegion texture, Action<TextureRegion> result) {
|
||||
if (this.PackedTexture != null)
|
||||
throw new InvalidOperationException("Cannot add texture to a texture packer that is already packed");
|
||||
var paddedWidth = texture.Width + 2 * padding;
|
||||
if (paddedWidth > this.maxWidth) {
|
||||
if (texture.Width > this.maxWidth) {
|
||||
if (this.autoIncreaseMaxWidth) {
|
||||
this.maxWidth = paddedWidth;
|
||||
this.maxWidth = texture.Width;
|
||||
} else {
|
||||
throw new InvalidOperationException($"Cannot add texture with width {texture.Width} to a texture packer with max width {this.maxWidth}");
|
||||
}
|
||||
}
|
||||
this.texturesToPack.Add(new Request(texture, result, padding, padWithPixels));
|
||||
this.textures.Add(new Request(texture, result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packs all of the textures and texture regions added using <see cref="Add(Microsoft.Xna.Framework.Graphics.Texture2D,System.Action{MLEM.Textures.TextureRegion},int,bool)"/> into one texture.
|
||||
/// Packs all of the textures and texture regions added using <see cref="Add(Microsoft.Xna.Framework.Graphics.Texture2D,System.Action{MLEM.Textures.TextureRegion})"/> into one texture.
|
||||
/// The resulting texture will be stored in <see cref="PackedTexture"/>.
|
||||
/// All of the result callbacks that were added will also be invoked.
|
||||
/// </summary>
|
||||
|
@ -165,23 +99,20 @@ namespace MLEM.Data {
|
|||
throw new InvalidOperationException("Cannot pack a texture packer that is already packed");
|
||||
|
||||
// set pack areas for each request
|
||||
// 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.firstPossiblePosForSizeCache[new Point(request.PackedArea.Width, request.PackedArea.Height)] = request.PackedArea.Location;
|
||||
this.alreadyPackedTextures.Add(request);
|
||||
foreach (var request in this.textures.OrderByDescending(t => t.Texture.Width * t.Texture.Height)) {
|
||||
var area = this.FindFreeArea(new Point(request.Texture.Width, request.Texture.Height));
|
||||
request.PackedArea = area;
|
||||
}
|
||||
stopwatch.Stop();
|
||||
this.LastCalculationTime = stopwatch.Elapsed;
|
||||
|
||||
// figure out texture size and generate texture
|
||||
var width = this.alreadyPackedTextures.Max(t => t.PackedArea.Right);
|
||||
var height = this.alreadyPackedTextures.Max(t => t.PackedArea.Bottom);
|
||||
var width = this.textures.Max(t => t.PackedArea.Right);
|
||||
var height = this.textures.Max(t => t.PackedArea.Bottom);
|
||||
if (this.forcePowerOfTwo) {
|
||||
width = RuntimeTexturePacker.ToPowerOfTwo(width);
|
||||
height = RuntimeTexturePacker.ToPowerOfTwo(height);
|
||||
width = ToPowerOfTwo(width);
|
||||
height = ToPowerOfTwo(height);
|
||||
}
|
||||
if (this.forceSquare)
|
||||
width = height = Math.Max(width, height);
|
||||
|
@ -190,21 +121,20 @@ namespace MLEM.Data {
|
|||
// copy texture data onto the packed texture
|
||||
stopwatch.Restart();
|
||||
using (var data = this.PackedTexture.GetTextureData()) {
|
||||
foreach (var request in this.alreadyPackedTextures)
|
||||
this.CopyRegion(data, request);
|
||||
foreach (var request in this.textures)
|
||||
CopyRegion(data, request);
|
||||
}
|
||||
stopwatch.Stop();
|
||||
this.LastPackTime = stopwatch.Elapsed;
|
||||
|
||||
// invoke callbacks
|
||||
foreach (var request in this.alreadyPackedTextures) {
|
||||
var packedArea = request.PackedArea.Shrink(new Point(request.Padding, request.Padding));
|
||||
request.Result.Invoke(new TextureRegion(this.PackedTexture, packedArea));
|
||||
foreach (var request in this.textures) {
|
||||
request.Result.Invoke(new TextureRegion(this.PackedTexture, request.PackedArea));
|
||||
if (this.disposeTextures)
|
||||
request.Texture.Texture.Dispose();
|
||||
}
|
||||
|
||||
this.ClearTempCollections();
|
||||
this.textures.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -213,9 +143,9 @@ namespace MLEM.Data {
|
|||
public void Reset() {
|
||||
this.PackedTexture?.Dispose();
|
||||
this.PackedTexture = null;
|
||||
this.textures.Clear();
|
||||
this.LastCalculationTime = TimeSpan.Zero;
|
||||
this.LastPackTime = TimeSpan.Zero;
|
||||
this.ClearTempCollections();
|
||||
}
|
||||
|
||||
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
|
||||
|
@ -223,17 +153,13 @@ namespace MLEM.Data {
|
|||
this.Reset();
|
||||
}
|
||||
|
||||
private Rectangle FindFreeArea(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.firstPossiblePosForSizeCache.TryGetValue(size, out var first) ? first : Point.Zero;
|
||||
private Rectangle FindFreeArea(Point size) {
|
||||
var pos = new Point(0, 0);
|
||||
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.alreadyPackedTextures) {
|
||||
var area = new Rectangle(pos, size);
|
||||
foreach (var tex in this.textures) {
|
||||
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
|
||||
|
@ -253,51 +179,16 @@ namespace MLEM.Data {
|
|||
}
|
||||
}
|
||||
|
||||
private void CopyRegion(TextureData destination, Request request) {
|
||||
var data = this.GetCachedTextureData(request.Texture.Texture);
|
||||
var location = request.PackedArea.Location + new Point(request.Padding, request.Padding);
|
||||
for (var x = -request.Padding; x < request.Texture.Width + request.Padding; x++) {
|
||||
for (var y = -request.Padding; y < request.Texture.Height + request.Padding; y++) {
|
||||
Color srcColor;
|
||||
if (!request.PadWithPixels && (x < 0 || y < 0 || x >= request.Texture.Width || y >= request.Texture.Height)) {
|
||||
// if we're out of bounds and not padding with pixels, we make it transparent
|
||||
srcColor = Color.Transparent;
|
||||
} else {
|
||||
// otherwise, we just use the closest pixel that is actually in bounds, causing the border pixels to be doubled up
|
||||
var src = new Point((int) MathHelper.Clamp(x, 0, request.Texture.Width - 1), (int) MathHelper.Clamp(y, 0, request.Texture.Height - 1));
|
||||
srcColor = data[request.Texture.Position + src];
|
||||
}
|
||||
destination[location + new Point(x, y)] = srcColor;
|
||||
private static void CopyRegion(TextureExtensions.TextureData destination, Request request) {
|
||||
using (var data = request.Texture.Texture.GetTextureData()) {
|
||||
for (var x = 0; x < request.Texture.Width; x++) {
|
||||
for (var y = 0; y < request.Texture.Height; y++) {
|
||||
var dest = request.PackedArea.Location + new Point(x, y);
|
||||
var src = request.Texture.Position + new Point(x, y);
|
||||
destination[dest] = data[src];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TextureData GetCachedTextureData(Texture2D texture) {
|
||||
// we cache texture data in case multiple requests use the same underlying texture
|
||||
// this collection doesn't need to be disposed since we don't actually edit these textures
|
||||
if (!this.dataCache.TryGetValue(texture, out var data)) {
|
||||
data = texture.GetTextureData();
|
||||
this.dataCache.Add(texture, data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private bool IsTransparent(TextureRegion region) {
|
||||
var data = this.GetCachedTextureData(region.Texture);
|
||||
for (var rX = 0; rX < region.Width; rX++) {
|
||||
for (var rY = 0; rY < region.Height; rY++) {
|
||||
if (data[region.U + rX, region.V + rY] != Color.Transparent)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ClearTempCollections() {
|
||||
this.texturesToPack.Clear();
|
||||
this.alreadyPackedTextures.Clear();
|
||||
this.firstPossiblePosForSizeCache.Clear();
|
||||
this.dataCache.Clear();
|
||||
}
|
||||
|
||||
private static int ToPowerOfTwo(int value) {
|
||||
|
@ -311,15 +202,11 @@ namespace MLEM.Data {
|
|||
|
||||
public readonly TextureRegion Texture;
|
||||
public readonly Action<TextureRegion> Result;
|
||||
public readonly int Padding;
|
||||
public readonly bool PadWithPixels;
|
||||
public Rectangle PackedArea;
|
||||
|
||||
public Request(TextureRegion texture, Action<TextureRegion> result, int padding, bool padWithPixels) {
|
||||
public Request(TextureRegion texture, Action<TextureRegion> result) {
|
||||
this.Texture = texture;
|
||||
this.Result = result;
|
||||
this.Padding = padding;
|
||||
this.PadWithPixels = padWithPixels;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
<RootNamespace>MLEM.Extended</RootNamespace>
|
||||
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Authors>Ellpeck</Authors>
|
||||
<Description>MLEM Library for Extending FNA extension that ties in with other FNA libraries</Description>
|
||||
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes>
|
||||
<PackageTags>fna ellpeck mlem utility extensions extended</PackageTags>
|
||||
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
|
||||
|
||||
<ProjectReference Include="..\FontStashSharp\src\XNA\FontStashSharp.FNA.Core.csproj">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\FNA\FNA.Core.csproj">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</ProjectReference>
|
||||
|
||||
<Compile Remove="Tiled/**" />
|
||||
<Compile Remove="Extensions/**" />
|
||||
<Compile Remove="Font/GenericBitmapFont.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
|
||||
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -26,7 +26,7 @@
|
|||
<PackageReference Include="MonoGame.Extended.Tiled" Version="3.8.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FontStashSharp.MonoGame" Version="1.1.6">
|
||||
<PackageReference Include="FontStashSharp.MonoGame" Version="1.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
|
||||
|
|
|
@ -4,7 +4,6 @@ using Microsoft.Xna.Framework;
|
|||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Cameras;
|
||||
using MLEM.Extensions;
|
||||
using MLEM.Graphics;
|
||||
using MLEM.Misc;
|
||||
using MonoGame.Extended.Tiled;
|
||||
using RectangleF = MonoGame.Extended.RectangleF;
|
||||
|
@ -93,20 +92,6 @@ namespace MLEM.Extended.Tiled {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds this individual tiled map renderer to the given <see cref="StaticSpriteBatch"/>.
|
||||
/// Optionally, a frustum can be supplied that determines which positions, in pixel space, are visible at this time. <see cref="Camera"/> provides <see cref="Camera.GetVisibleRectangle"/> for this purpose.
|
||||
/// </summary>
|
||||
/// <param name="batch">The static sprite batch to use for drawing.</param>
|
||||
/// <param name="frustum">The area that is visible, in pixel space.</param>
|
||||
/// <param name="addFunction">The add function to use, or null to use <see cref="DefaultAdd"/>.</param>
|
||||
public void Add(StaticSpriteBatch batch, RectangleF? frustum = null, AddDelegate addFunction = null) {
|
||||
for (var i = 0; i < this.map.TileLayers.Count; i++) {
|
||||
if (this.map.TileLayers[i].IsVisible)
|
||||
this.AddLayer(batch, i, frustum, addFunction);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the given layer of this individual tiled map renderer.
|
||||
/// Optionally, a frustum can be supplied that determines which positions, in pixel space, are visible at this time. <see cref="Camera"/> provides <see cref="Camera.GetVisibleRectangle"/> for this purpose.
|
||||
|
@ -116,8 +101,12 @@ namespace MLEM.Extended.Tiled {
|
|||
/// <param name="frustum">The area that is visible, in pixel space.</param>
|
||||
/// <param name="drawFunction">The draw function to use, or null to use <see cref="DefaultDraw"/></param>
|
||||
public void DrawLayer(SpriteBatch batch, int layerIndex, RectangleF? frustum = null, DrawDelegate drawFunction = null) {
|
||||
var draw = drawFunction ?? IndividualTiledMapRenderer.DefaultDraw;
|
||||
var (minX, minY, maxX, maxY) = this.GetFrustum(frustum);
|
||||
var draw = drawFunction ?? DefaultDraw;
|
||||
var frust = frustum ?? new RectangleF(0, 0, float.MaxValue, float.MaxValue);
|
||||
var minX = Math.Max(0, frust.Left / this.map.TileWidth).Floor();
|
||||
var minY = Math.Max(0, frust.Top / this.map.TileHeight).Floor();
|
||||
var maxX = Math.Min(this.map.Width, frust.Right / this.map.TileWidth).Ceil();
|
||||
var maxY = Math.Min(this.map.Height, frust.Bottom / this.map.TileHeight).Ceil();
|
||||
for (var x = minX; x < maxX; x++) {
|
||||
for (var y = minY; y < maxY; y++) {
|
||||
var info = this.drawInfos[layerIndex, x, y];
|
||||
|
@ -127,26 +116,6 @@ namespace MLEM.Extended.Tiled {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given layer of this individual tiled map renderer to the given <see cref="StaticSpriteBatch"/>.
|
||||
/// Optionally, a frustum can be supplied that determines which positions, in pixel space, are visible at this time. <see cref="Camera"/> provides <see cref="Camera.GetVisibleRectangle"/> for this purpose.
|
||||
/// </summary>
|
||||
/// <param name="batch">The static sprite batch to use for drawing.</param>
|
||||
/// <param name="layerIndex">The index of the layer in <see cref="TiledMap.TileLayers"/>.</param>
|
||||
/// <param name="frustum">The area that is visible, in pixel space.</param>
|
||||
/// <param name="addFunction">The add function to use, or null to use <see cref="DefaultAdd"/>.</param>
|
||||
public void AddLayer(StaticSpriteBatch batch, int layerIndex, RectangleF? frustum = null, AddDelegate addFunction = null) {
|
||||
var add = addFunction ?? IndividualTiledMapRenderer.DefaultAdd;
|
||||
var (minX, minY, maxX, maxY) = this.GetFrustum(frustum);
|
||||
for (var x = minX; x < maxX; x++) {
|
||||
for (var y = minY; y < maxY; y++) {
|
||||
var info = this.drawInfos[layerIndex, x, y];
|
||||
if (info != null)
|
||||
add(batch, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update all of the animated tiles in this individual tiled map renderer
|
||||
/// </summary>
|
||||
|
@ -156,17 +125,8 @@ namespace MLEM.Extended.Tiled {
|
|||
animation.Update(time);
|
||||
}
|
||||
|
||||
private (int MinX, int MinY, int MaxX, int MaxY) GetFrustum(RectangleF? frustum) {
|
||||
var frust = frustum ?? new RectangleF(0, 0, float.MaxValue, float.MaxValue);
|
||||
var minX = Math.Max(0, frust.Left / this.map.TileWidth).Floor();
|
||||
var minY = Math.Max(0, frust.Top / this.map.TileHeight).Floor();
|
||||
var maxX = Math.Min(this.map.Width, frust.Right / this.map.TileWidth).Ceil();
|
||||
var maxY = Math.Min(this.map.Height, frust.Bottom / this.map.TileHeight).Ceil();
|
||||
return (minX, minY, maxX, maxY);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of <see cref="DrawDelegate"/> that is used by <see cref="Draw"/> if no custom draw function is passed.
|
||||
/// The default implementation of <see cref="DrawDelegate"/> that is used by <see cref="SetMap"/> if no custom draw function is passed
|
||||
/// </summary>
|
||||
/// <param name="batch">The sprite batch to use for drawing</param>
|
||||
/// <param name="info">The <see cref="TileDrawInfo"/> to draw</param>
|
||||
|
@ -177,18 +137,6 @@ namespace MLEM.Extended.Tiled {
|
|||
batch.Draw(info.Tileset.Texture, drawPos, region, Color.White, 0, Vector2.Zero, 1, effects, info.Depth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation of <see cref="AddDelegate"/> that is used by <see cref="Add"/> if no custom add function is passed.
|
||||
/// </summary>
|
||||
/// <param name="batch">The static sprite batch to use for drawing.</param>
|
||||
/// <param name="info">The <see cref="TileDrawInfo"/> to add.</param>
|
||||
public static void DefaultAdd(StaticSpriteBatch batch, TileDrawInfo info) {
|
||||
var region = info.Tileset.GetTextureRegion(info.TilesetTile);
|
||||
var effects = info.Tile.GetSpriteEffects();
|
||||
var drawPos = new Vector2(info.Position.X * info.Renderer.map.TileWidth, info.Position.Y * info.Renderer.map.TileHeight);
|
||||
batch.Add(info.Tileset.Texture, drawPos, region, Color.White, 0, Vector2.Zero, 1, effects, info.Depth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate method used for <see cref="IndividualTiledMapRenderer.depthFunction"/>.
|
||||
/// 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.
|
||||
|
@ -207,13 +155,6 @@ namespace MLEM.Extended.Tiled {
|
|||
/// <param name="info">The <see cref="TileDrawInfo"/> to draw</param>
|
||||
public delegate void DrawDelegate(SpriteBatch batch, TileDrawInfo info);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate method used for adding an <see cref="IndividualTiledMapRenderer"/> to a <see cref="StaticSpriteBatch"/>.
|
||||
/// </summary>
|
||||
/// <param name="batch">The static sprite batch to use for drawing.</param>
|
||||
/// <param name="info">The <see cref="TileDrawInfo"/> to add.</param>
|
||||
public delegate void AddDelegate(StaticSpriteBatch batch, TileDrawInfo info);
|
||||
|
||||
/// <summary>
|
||||
/// A tile draw info contains information about a tile at a given map location.
|
||||
/// It caches a lot of data that is required for drawing a tile efficiently.
|
||||
|
|
|
@ -4,8 +4,7 @@ using MonoGame.Extended.Tiled;
|
|||
|
||||
namespace MLEM.Extended.Tiled {
|
||||
/// <summary>
|
||||
/// A struct that represents a position on a <see cref="TiledMap"/> with multiple layers, where the <see cref="X"/> and <see cref="Y"/> coordinates are 32-bit integer numbers.
|
||||
/// See <see cref="LayerPositionF"/> for a floating point position.
|
||||
/// A struct that represents a position on a <see cref="TiledMap"/> with multiple layers.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public struct LayerPosition : IEquatable<LayerPosition> {
|
||||
|
@ -54,8 +53,8 @@ namespace MLEM.Extended.Tiled {
|
|||
/// <returns>A 32-bit signed integer that is the hash code for this instance.</returns>
|
||||
public override int GetHashCode() {
|
||||
var hashCode = this.Layer.GetHashCode();
|
||||
hashCode = hashCode * 397 ^ this.X;
|
||||
hashCode = hashCode * 397 ^ this.Y;
|
||||
hashCode = (hashCode * 397) ^ this.X;
|
||||
hashCode = (hashCode * 397) ^ this.Y;
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
|
@ -105,7 +104,7 @@ namespace MLEM.Extended.Tiled {
|
|||
/// <param name="right">The right position.</param>
|
||||
/// <returns>The sum of the positions.</returns>
|
||||
public static LayerPosition operator +(LayerPosition left, LayerPosition right) {
|
||||
return LayerPosition.Add(left, right);
|
||||
return Add(left, right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -115,16 +114,7 @@ namespace MLEM.Extended.Tiled {
|
|||
/// <param name="right">The right position.</param>
|
||||
/// <returns>The difference of the positions.</returns>
|
||||
public static LayerPosition operator -(LayerPosition left, LayerPosition right) {
|
||||
return LayerPosition.Add(left, -right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a <see cref="LayerPosition"/> to a <see cref="LayerPositionF"/>.
|
||||
/// </summary>
|
||||
/// <param name="position">The position to convert.</param>
|
||||
/// <returns>The converted position.</returns>
|
||||
public static implicit operator LayerPositionF(LayerPosition position) {
|
||||
return new LayerPositionF(position.Layer, position.X, position.Y);
|
||||
return Add(left, -right);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using MonoGame.Extended.Tiled;
|
||||
|
||||
namespace MLEM.Extended.Tiled {
|
||||
/// <summary>
|
||||
/// A struct that represents a position on a <see cref="TiledMap"/> with multiple layers, where the <see cref="X"/> and <see cref="Y"/> coordinates are 32-bit floating point numbers.
|
||||
/// See <see cref="LayerPosition"/> for an integer position.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public struct LayerPositionF : IEquatable<LayerPositionF> {
|
||||
|
||||
/// <summary>
|
||||
/// The name of the layer that this position is on
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string Layer;
|
||||
/// <summary>
|
||||
/// The x coordinate of this position
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public float X;
|
||||
/// <summary>
|
||||
/// The y coordinate of this position
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public float Y;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new layer position with the given settings
|
||||
/// </summary>
|
||||
/// <param name="layerName">The layer name</param>
|
||||
/// <param name="x">The x coordinate</param>
|
||||
/// <param name="y">The y coordinate</param>
|
||||
public LayerPositionF(string layerName, float x, float y) {
|
||||
this.Layer = layerName;
|
||||
this.X = x;
|
||||
this.Y = y;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Equals(object)"/>
|
||||
public bool Equals(LayerPositionF other) {
|
||||
return this.Layer == other.Layer && this.X == other.X && this.Y == other.Y;
|
||||
}
|
||||
|
||||
/// <summary>Indicates whether this instance and a specified object are equal.</summary>
|
||||
/// <param name="obj">The object to compare with the current instance.</param>
|
||||
/// <returns>true if <paramref name="obj">obj</paramref> and this instance are the same type and represent the same value; otherwise, false.</returns>
|
||||
public override bool Equals(object obj) {
|
||||
return obj is LayerPositionF other && this.Equals(other);
|
||||
}
|
||||
|
||||
/// <summary>Returns the hash code for this instance.</summary>
|
||||
/// <returns>A 32-bit signed integer that is the hash code for this instance.</returns>
|
||||
public override int GetHashCode() {
|
||||
var hashCode = this.Layer.GetHashCode();
|
||||
hashCode = hashCode * 397 ^ this.X.GetHashCode();
|
||||
hashCode = hashCode * 397 ^ this.Y.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/// <summary>Returns the fully qualified type name of this instance.</summary>
|
||||
/// <returns>The fully qualified type name.</returns>
|
||||
public override string ToString() {
|
||||
return $"{nameof(this.Layer)}: {this.Layer}, {nameof(this.X)}: {this.X}, {nameof(this.Y)}: {this.Y}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given layer positions together, returning a new layer position with the sum of their coordinates.
|
||||
/// If the two layer positions' <see cref="Layer"/> differ, an <see cref="ArgumentException"/> is thrown.
|
||||
/// </summary>
|
||||
/// <param name="left">The left position.</param>
|
||||
/// <param name="right">The right position.</param>
|
||||
/// <returns>The sum of the positions.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if the two positions' <see cref="Layer"/> are not the same.</exception>
|
||||
public static LayerPositionF Add(LayerPositionF left, LayerPositionF right) {
|
||||
if (left.Layer != right.Layer)
|
||||
throw new ArgumentException("Cannot add layer positions on different layers");
|
||||
return new LayerPositionF(left.Layer, left.X + right.X, left.Y + right.Y);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Equals(LayerPositionF)"/>
|
||||
public static bool operator ==(LayerPositionF left, LayerPositionF right) {
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Equals(LayerPositionF)"/>
|
||||
public static bool operator !=(LayerPositionF left, LayerPositionF right) {
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the negative of the given layer position.
|
||||
/// </summary>
|
||||
/// <param name="position">The position to negate.</param>
|
||||
/// <returns>The negative position.</returns>
|
||||
public static LayerPositionF operator -(LayerPositionF position) {
|
||||
return new LayerPositionF(position.Layer, -position.X, -position.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the sum of the two layer positions using <see cref="Add"/>.
|
||||
/// </summary>
|
||||
/// <param name="left">The left position.</param>
|
||||
/// <param name="right">The right position.</param>
|
||||
/// <returns>The sum of the positions.</returns>
|
||||
public static LayerPositionF operator +(LayerPositionF left, LayerPositionF right) {
|
||||
return LayerPositionF.Add(left, right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts the second from the first position using <see cref="Add"/>.
|
||||
/// </summary>
|
||||
/// <param name="left">The left position.</param>
|
||||
/// <param name="right">The right position.</param>
|
||||
/// <returns>The difference of the positions.</returns>
|
||||
public static LayerPositionF operator -(LayerPositionF left, LayerPositionF right) {
|
||||
return LayerPositionF.Add(left, -right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a <see cref="LayerPositionF"/> to a <see cref="LayerPosition"/>.
|
||||
/// The coordinates are typecast to 32-bit integers in the process.
|
||||
/// </summary>
|
||||
/// <param name="position">The position to convert.</param>
|
||||
/// <returns>The converted position.</returns>
|
||||
public static implicit operator LayerPosition(LayerPositionF position) {
|
||||
return new LayerPosition(position.Layer, (int) position.X, (int) position.Y);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ using Microsoft.Xna.Framework;
|
|||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MonoGame.Extended;
|
||||
using MonoGame.Extended.Tiled;
|
||||
using static MonoGame.Extended.Tiled.TiledMapTileFlipFlags;
|
||||
using ColorHelper = MLEM.Extensions.ColorHelper;
|
||||
|
||||
namespace MLEM.Extended.Tiled {
|
||||
|
@ -143,9 +144,9 @@ namespace MLEM.Extended.Tiled {
|
|||
public static TiledMapTilesetTile GetTilesetTile(this TiledMapTileset tileset, int localId, bool createStub = true) {
|
||||
var tilesetTile = tileset.Tiles.FirstOrDefault(t => t.LocalTileIdentifier == localId);
|
||||
if (tilesetTile == null && createStub) {
|
||||
if (!TiledExtensions.StubTilesetTiles.TryGetValue(localId, out tilesetTile)) {
|
||||
if (!StubTilesetTiles.TryGetValue(localId, out tilesetTile)) {
|
||||
tilesetTile = new TiledMapTilesetTile(localId);
|
||||
TiledExtensions.StubTilesetTiles.Add(localId, tilesetTile);
|
||||
StubTilesetTiles.Add(localId, tilesetTile);
|
||||
}
|
||||
}
|
||||
return tilesetTile;
|
||||
|
@ -270,12 +271,12 @@ namespace MLEM.Extended.Tiled {
|
|||
/// <param name="position">The position to add to the object's position</param>
|
||||
/// <param name="flipFlags">The flipping of the tile that this object belongs to. If set, the returned area will be "flipped" in the tile's space so that it matches the flip flags.</param>
|
||||
/// <returns>The area that the tile covers</returns>
|
||||
public static RectangleF GetArea(this TiledMapObject obj, TiledMap map, Vector2? position = null, TiledMapTileFlipFlags flipFlags = TiledMapTileFlipFlags.None) {
|
||||
public static RectangleF GetArea(this TiledMapObject obj, TiledMap map, Vector2? position = null, TiledMapTileFlipFlags flipFlags = None) {
|
||||
var tileSize = map.GetTileSize();
|
||||
var area = new RectangleF(obj.Position / tileSize, obj.Size / tileSize);
|
||||
if (flipFlags.HasFlag(TiledMapTileFlipFlags.FlipHorizontally))
|
||||
if (flipFlags.HasFlag(FlipHorizontally))
|
||||
area.X = 1 - area.X - area.Width;
|
||||
if (flipFlags.HasFlag(TiledMapTileFlipFlags.FlipVertically))
|
||||
if (flipFlags.HasFlag(FlipVertically))
|
||||
area.Y = 1 - area.Y - area.Height;
|
||||
if (position != null)
|
||||
area.Offset(position.Value);
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace MLEM.Extended.Tiled {
|
|||
/// <param name="collisionFunction">The function used to collect the collision info of a tile, or null to use <see cref="DefaultCollectCollisions"/></param>
|
||||
public void SetMap(TiledMap map, CollectCollisions collisionFunction = null) {
|
||||
this.map = map;
|
||||
this.collisionFunction = collisionFunction ?? TiledMapCollisions.DefaultCollectCollisions;
|
||||
this.collisionFunction = collisionFunction ?? DefaultCollectCollisions;
|
||||
this.collisionInfos = new TileCollisionInfo[map.Layers.Count, map.Width, map.Height];
|
||||
for (var i = 0; i < map.TileLayers.Count; i++) {
|
||||
for (var x = 0; x < map.Width; x++) {
|
||||
|
|
70
MLEM.FNA.sln
70
MLEM.FNA.sln
|
@ -1,70 +0,0 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.FNA", "MLEM\MLEM.FNA.csproj", "{C2C88AE6-6274-4395-8B03-52AE898BA070}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.Ui.FNA", "MLEM.Ui\MLEM.Ui.FNA.csproj", "{1B47A40B-3BF6-4933-A7DB-57EADEBDFB61}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.Startup.FNA", "MLEM.Startup\MLEM.Startup.FNA.csproj", "{FBE2C9B5-293D-47A7-9BA1-5A4BD1C4E816}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.Data.FNA", "MLEM.Data\MLEM.Data.FNA.csproj", "{6587BC91-0640-43FB-988A-4F545B8ACFC5}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demos.FNA", "Demos\Demos.FNA.csproj", "{D83D7D24-14CB-4A7C-A80B-BA4FEC66AB55}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demos.DesktopGL.FNA", "Demos.DesktopGL\Demos.DesktopGL.FNA.csproj", "{AB08FEC7-3AC3-4FDE-B632-226FAAD7F73F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.FNA", "Tests\Tests.FNA.csproj", "{C74FC4C5-3BE0-42A7-8BA6-4A9AD438A9E2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.Extended.FNA", "MLEM.Extended\MLEM.Extended.FNA.csproj", "{A5B22930-DF4B-4A62-93ED-A6549F7B666B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA.Core", "FNA\FNA.Core.csproj", "{06459F72-CEAA-4B45-B2B1-708FC28D04F8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FontStashSharp.FNA.Core", "FontStashSharp\src\XNA\FontStashSharp.FNA.Core.csproj", "{0B410591-3AED-4C82-A07A-516FF493709B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{C2C88AE6-6274-4395-8B03-52AE898BA070}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C2C88AE6-6274-4395-8B03-52AE898BA070}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C2C88AE6-6274-4395-8B03-52AE898BA070}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C2C88AE6-6274-4395-8B03-52AE898BA070}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1B47A40B-3BF6-4933-A7DB-57EADEBDFB61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1B47A40B-3BF6-4933-A7DB-57EADEBDFB61}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1B47A40B-3BF6-4933-A7DB-57EADEBDFB61}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1B47A40B-3BF6-4933-A7DB-57EADEBDFB61}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FBE2C9B5-293D-47A7-9BA1-5A4BD1C4E816}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FBE2C9B5-293D-47A7-9BA1-5A4BD1C4E816}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FBE2C9B5-293D-47A7-9BA1-5A4BD1C4E816}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FBE2C9B5-293D-47A7-9BA1-5A4BD1C4E816}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6587BC91-0640-43FB-988A-4F545B8ACFC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6587BC91-0640-43FB-988A-4F545B8ACFC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6587BC91-0640-43FB-988A-4F545B8ACFC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6587BC91-0640-43FB-988A-4F545B8ACFC5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D83D7D24-14CB-4A7C-A80B-BA4FEC66AB55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D83D7D24-14CB-4A7C-A80B-BA4FEC66AB55}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D83D7D24-14CB-4A7C-A80B-BA4FEC66AB55}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D83D7D24-14CB-4A7C-A80B-BA4FEC66AB55}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AB08FEC7-3AC3-4FDE-B632-226FAAD7F73F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AB08FEC7-3AC3-4FDE-B632-226FAAD7F73F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AB08FEC7-3AC3-4FDE-B632-226FAAD7F73F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AB08FEC7-3AC3-4FDE-B632-226FAAD7F73F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C74FC4C5-3BE0-42A7-8BA6-4A9AD438A9E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C74FC4C5-3BE0-42A7-8BA6-4A9AD438A9E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C74FC4C5-3BE0-42A7-8BA6-4A9AD438A9E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C74FC4C5-3BE0-42A7-8BA6-4A9AD438A9E2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A5B22930-DF4B-4A62-93ED-A6549F7B666B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A5B22930-DF4B-4A62-93ED-A6549F7B666B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A5B22930-DF4B-4A62-93ED-A6549F7B666B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A5B22930-DF4B-4A62-93ED-A6549F7B666B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{06459F72-CEAA-4B45-B2B1-708FC28D04F8}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{06459F72-CEAA-4B45-B2B1-708FC28D04F8}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{06459F72-CEAA-4B45-B2B1-708FC28D04F8}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{06459F72-CEAA-4B45-B2B1-708FC28D04F8}.Release|Any CPU.Build.0 = Release|x64
|
||||
{0B410591-3AED-4C82-A07A-516FF493709B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0B410591-3AED-4C82-A07A-516FF493709B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0B410591-3AED-4C82-A07A-516FF493709B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0B410591-3AED-4C82-A07A-516FF493709B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -1,37 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
<RootNamespace>MLEM.Startup</RootNamespace>
|
||||
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Authors>Ellpeck</Authors>
|
||||
<Description>MLEM Library for Extending FNA combined with some other useful libraries into a quick Game startup class</Description>
|
||||
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes>
|
||||
<PackageTags>fna ellpeck mlem utility extensions</PackageTags>
|
||||
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Coroutine" Version="2.1.3" />
|
||||
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.FNA.csproj" />
|
||||
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
|
||||
|
||||
<ProjectReference Include="..\FNA\FNA.Core.csproj">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
|
||||
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -18,7 +18,7 @@ namespace MLEM.Startup {
|
|||
/// <summary>
|
||||
/// The static game instance's input handler
|
||||
/// </summary>
|
||||
public static InputHandler Input => MlemGame.instance.InputHandler;
|
||||
public static InputHandler Input => instance.InputHandler;
|
||||
|
||||
/// <summary>
|
||||
/// This game's graphics device manager, initialized in the constructor
|
||||
|
@ -64,14 +64,12 @@ namespace MLEM.Startup {
|
|||
/// <param name="windowWidth">The default window width</param>
|
||||
/// <param name="windowHeight">The default window height</param>
|
||||
public MlemGame(int windowWidth = 1280, int windowHeight = 720) {
|
||||
MlemGame.instance = this;
|
||||
instance = this;
|
||||
|
||||
this.GraphicsDeviceManager = new GraphicsDeviceManager(this) {
|
||||
PreferredBackBufferWidth = windowWidth,
|
||||
PreferredBackBufferHeight = windowHeight,
|
||||
#if !FNA
|
||||
HardwareModeSwitch = false
|
||||
#endif
|
||||
};
|
||||
this.Window.AllowUserResizing = true;
|
||||
this.Content.RootDirectory = "Content";
|
||||
|
@ -162,7 +160,7 @@ namespace MLEM.Startup {
|
|||
/// <typeparam name="T">The type of content to load</typeparam>
|
||||
/// <returns>The loaded content</returns>
|
||||
public static T LoadContent<T>(string name) {
|
||||
return MlemGame.instance.Content.Load<T>(name);
|
||||
return instance.Content.Load<T>(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-mgcb": {
|
||||
"version": "3.8.1.263",
|
||||
"commands": [
|
||||
"mgcb"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor": {
|
||||
"version": "3.8.1.263",
|
||||
"commands": [
|
||||
"mgcb-editor"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-linux": {
|
||||
"version": "3.8.1.263",
|
||||
"commands": [
|
||||
"mgcb-editor-linux"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-windows": {
|
||||
"version": "3.8.1.263",
|
||||
"commands": [
|
||||
"mgcb-editor-windows"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-mac": {
|
||||
"version": "3.8.1.263",
|
||||
"commands": [
|
||||
"mgcb-editor-mac"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
using MLEM.Startup;
|
||||
|
||||
namespace TemplateNamespace;
|
||||
|
||||
public class GameImpl : MlemGame {
|
||||
namespace TemplateNamespace {
|
||||
public class GameImpl : MlemGame {
|
||||
|
||||
public static GameImpl Instance { get; private set; }
|
||||
|
||||
|
@ -10,4 +9,5 @@ public class GameImpl : MlemGame {
|
|||
Instance = this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using MLEM.Misc;
|
||||
|
||||
namespace TemplateNamespace;
|
||||
|
||||
public static class Program {
|
||||
namespace TemplateNamespace {
|
||||
public static class Program {
|
||||
|
||||
public static void Main() {
|
||||
MlemPlatform.Current = new MlemPlatform.DesktopGl<TextInputEventArgs>((w, c) => w.TextInput += c);
|
||||
|
@ -11,4 +10,5 @@ public static class Program {
|
|||
game.Run();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<PublishReadyToRun>false</PublishReadyToRun>
|
||||
<TieredCompilation>false</TieredCompilation>
|
||||
<ApplicationIcon>Icon.ico</ApplicationIcon>
|
||||
|
@ -10,18 +10,15 @@
|
|||
|
||||
<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="MLEM.Startup" Version="5.*" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.*" />
|
||||
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MonoGameContentReference Include="Content\Content.mgcb" />
|
||||
<Content Include="Content\*\**" />
|
||||
<EmbeddedResource Include="Icon.ico" />
|
||||
<EmbeddedResource Include="Icon.bmp" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="RestoreDotnetTools" BeforeTargets="Restore">
|
||||
<Message Text="Restoring dotnet tools" Importance="High" />
|
||||
<Exec Command="dotnet tool restore" />
|
||||
</Target>
|
||||
</Project>
|
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
using MLEM.Startup;
|
||||
|
||||
namespace TemplateNamespace;
|
||||
|
||||
public class GameImpl : MlemGame {
|
||||
namespace TemplateNamespace {
|
||||
public class GameImpl : MlemGame {
|
||||
|
||||
public static GameImpl Instance { get; private set; }
|
||||
|
||||
|
@ -10,4 +9,5 @@ public class GameImpl : MlemGame {
|
|||
Instance = this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -5,8 +5,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MLEM.Startup" Version="6.*" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.263">
|
||||
<PackageReference Include="MLEM.Startup" Version="5.*" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.*">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Graphics;
|
||||
using MLEM.Misc;
|
||||
using MLEM.Textures;
|
||||
using MLEM.Ui.Style;
|
||||
|
@ -93,7 +92,7 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
var tex = this.Texture;
|
||||
var color = (Color) this.NormalColor * alpha;
|
||||
if (this.IsDisabled) {
|
||||
|
@ -104,7 +103,7 @@ namespace MLEM.Ui.Elements {
|
|||
color = (Color) this.HoveredColor * alpha;
|
||||
}
|
||||
batch.Draw(tex, this.DisplayArea, color, this.Scale);
|
||||
base.Draw(time, batch, alpha, context);
|
||||
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Graphics;
|
||||
using MLEM.Misc;
|
||||
using MLEM.Textures;
|
||||
using MLEM.Ui.Style;
|
||||
|
@ -109,7 +108,7 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
var tex = this.Texture;
|
||||
var color = Color.White * alpha;
|
||||
if (this.IsDisabled) {
|
||||
|
@ -124,7 +123,7 @@ namespace MLEM.Ui.Elements {
|
|||
batch.Draw(tex, boxDisplayArea, color, this.Scale);
|
||||
if (this.Checked)
|
||||
batch.Draw(this.Checkmark, boxDisplayArea, Color.White * alpha);
|
||||
base.Draw(time, batch, alpha, context);
|
||||
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -55,9 +55,8 @@ namespace MLEM.Ui.Elements {
|
|||
/// Adds an element to this dropdown's <see cref="Panel"/>
|
||||
/// </summary>
|
||||
/// <param name="element">The element to add</param>
|
||||
/// <param name="index">The index to add the child at, or -1 to add it to the end of the <see cref="Element.Children"/> list</param>
|
||||
public void AddElement(Element element, int index = -1) {
|
||||
this.Panel.AddChild(element, index);
|
||||
public void AddElement(Element element) {
|
||||
this.Panel.AddChild(element);
|
||||
// Since the dropdown causes elements to be over each other,
|
||||
// usual gamepad code doesn't apply
|
||||
element.GetGamepadNextElement = (dir, usualNext) => {
|
||||
|
@ -78,9 +77,8 @@ namespace MLEM.Ui.Elements {
|
|||
/// </summary>
|
||||
/// <param name="text">The text to display</param>
|
||||
/// <param name="pressed">The resulting paragraph's <see cref="Element.OnPressed"/> event</param>
|
||||
/// <param name="index">The index to add the child at, or -1 to add it to the end of the <see cref="Element.Children"/> list</param>
|
||||
public Element AddElement(string text, GenericCallback pressed = null, int index = -1) {
|
||||
return this.AddElement(p => text, pressed, index);
|
||||
public Element AddElement(string text, GenericCallback pressed = null) {
|
||||
return this.AddElement(p => text, pressed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -89,8 +87,7 @@ namespace MLEM.Ui.Elements {
|
|||
/// </summary>
|
||||
/// <param name="text">The text to display</param>
|
||||
/// <param name="pressed">The resulting paragraph's <see cref="Element.OnPressed"/> event</param>
|
||||
/// <param name="index">The index to add the child at, or -1 to add it to the end of the <see cref="Element.Children"/> list</param>
|
||||
public Element AddElement(Paragraph.TextCallback text, GenericCallback pressed = null, int index = -1) {
|
||||
public Element AddElement(Paragraph.TextCallback text, GenericCallback pressed = null) {
|
||||
var paragraph = new Paragraph(Anchor.AutoLeft, 1, text) {
|
||||
CanBeMoused = true,
|
||||
CanBeSelected = true,
|
||||
|
@ -100,7 +97,7 @@ namespace MLEM.Ui.Elements {
|
|||
paragraph.OnPressed += pressed;
|
||||
paragraph.OnMouseEnter += e => paragraph.TextColor = Color.LightGray;
|
||||
paragraph.OnMouseExit += e => paragraph.TextColor = Color.White;
|
||||
this.AddElement(paragraph, index);
|
||||
this.AddElement(paragraph);
|
||||
return paragraph;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ using Microsoft.Xna.Framework;
|
|||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using MLEM.Extensions;
|
||||
using MLEM.Graphics;
|
||||
using MLEM.Input;
|
||||
using MLEM.Misc;
|
||||
using MLEM.Textures;
|
||||
|
@ -178,12 +177,12 @@ namespace MLEM.Ui.Elements {
|
|||
/// <summary>
|
||||
/// This element's transform matrix.
|
||||
/// Can easily be scaled using <see cref="ScaleTransform"/>.
|
||||
/// Note that, when this is non-null, a new <c>SpriteBatch.Begin</c> call is used for this element.
|
||||
/// Note that, when this is non-null, a new <see cref="SpriteBatch.Begin"/> call is used for this element.
|
||||
/// </summary>
|
||||
public Matrix Transform = Matrix.Identity;
|
||||
/// <summary>
|
||||
/// The call that this element should make to <see cref="SpriteBatch"/> to begin drawing.
|
||||
/// Note that, when this is non-null, a new <c>SpriteBatch.Begin</c> call is used for this element.
|
||||
/// Note that, when this is non-null, a new <see cref="SpriteBatch.Begin"/> call is used for this element.
|
||||
/// </summary>
|
||||
#pragma warning disable CS0618
|
||||
[Obsolete("BeginImpl is deprecated. You can create a custom element class and override Draw instead.")]
|
||||
|
@ -193,14 +192,7 @@ namespace MLEM.Ui.Elements {
|
|||
/// Set this field to false to disallow the element from being selected.
|
||||
/// An unselectable element is skipped by automatic navigation and its <see cref="OnSelected"/> callback will never be called.
|
||||
/// </summary>
|
||||
public virtual bool CanBeSelected {
|
||||
get => this.canBeSelected;
|
||||
set {
|
||||
this.canBeSelected = value;
|
||||
if (!this.canBeSelected && this.Root?.SelectedElement == this)
|
||||
this.Root.SelectElement(null);
|
||||
}
|
||||
}
|
||||
public virtual bool CanBeSelected { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Set this field to false to disallow the element from reacting to being moused over.
|
||||
/// </summary>
|
||||
|
@ -242,13 +234,13 @@ namespace MLEM.Ui.Elements {
|
|||
public virtual bool PreventParentSpill { get; set; }
|
||||
/// <summary>
|
||||
/// The transparency (alpha value) that this element is rendered with.
|
||||
/// Note that, when <see cref="Draw(Microsoft.Xna.Framework.GameTime,Microsoft.Xna.Framework.Graphics.SpriteBatch,float,MLEM.Graphics.SpriteBatchContext)"/> is called, this alpha value is multiplied with the <see cref="Parent"/>'s alpha value and passed down to this element's <see cref="Children"/>.
|
||||
/// Note that, when <see cref="Draw"/> is called, this alpha value is multiplied with the <see cref="Parent"/>'s alpha value and passed down to this element's <see cref="Children"/>.
|
||||
/// </summary>
|
||||
public virtual float DrawAlpha { get; set; } = 1;
|
||||
/// <summary>
|
||||
/// Stores whether this element is currently being moused over or touched.
|
||||
/// </summary>
|
||||
public bool IsMouseOver => this.Controls.MousedElement == this || this.Controls.TouchedElement == this;
|
||||
public bool IsMouseOver { get; protected set; }
|
||||
/// <summary>
|
||||
/// Returns whether this element is its <see cref="Root"/>'s <see cref="RootElement.SelectedElement"/>.
|
||||
/// </summary>
|
||||
|
@ -257,12 +249,6 @@ namespace MLEM.Ui.Elements {
|
|||
/// 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; }
|
||||
/// <summary>
|
||||
/// An optional string that represents a group of elements for automatic (keyboard and gamepad) navigation.
|
||||
/// All elements that share the same auto-nav group will be able to be navigated between, and all other elements will not be reachable from elements of other groups.
|
||||
/// Note that, if no element is previously selected and auto-navigation is invoked, this element cannot be chosen as the first element to navigate to if its auto-nav group is non-null.
|
||||
/// </summary>
|
||||
public virtual string AutoNavGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This Element's current <see cref="UiStyle"/>.
|
||||
|
@ -436,7 +422,6 @@ namespace MLEM.Ui.Elements {
|
|||
private int priority;
|
||||
private StyleProp<UiStyle> style;
|
||||
private StyleProp<Padding> childPadding;
|
||||
private bool canBeSelected = true;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new element with the given anchor and size and sets up some default event reactions.
|
||||
|
@ -448,6 +433,11 @@ namespace MLEM.Ui.Elements {
|
|||
this.size = size;
|
||||
|
||||
this.Children = new ReadOnlyCollection<Element>(this.children);
|
||||
|
||||
this.OnMouseEnter += element => this.IsMouseOver = true;
|
||||
this.OnMouseExit += element => this.IsMouseOver = false;
|
||||
this.OnTouchEnter += element => this.IsMouseOver = true;
|
||||
this.OnTouchExit += element => this.IsMouseOver = false;
|
||||
this.GetTabNextElement += (backward, next) => next;
|
||||
this.GetGamepadNextElement += (dir, next) => next;
|
||||
|
||||
|
@ -489,8 +479,6 @@ namespace MLEM.Ui.Elements {
|
|||
/// <param name="element">The child element to remove</param>
|
||||
public virtual void RemoveChild(Element element) {
|
||||
this.children.Remove(element);
|
||||
if (this.Root?.SelectedElement == element)
|
||||
this.Root.SelectElement(null);
|
||||
// set area dirty here so that a dirty call is made
|
||||
// upwards to us if the element is auto-positioned
|
||||
element.SetAreaDirty();
|
||||
|
@ -652,7 +640,7 @@ namespace MLEM.Ui.Elements {
|
|||
var newX = prevArea.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 (newX + newSize.X <= parentArea.Right + Epsilon) {
|
||||
pos.X = newX;
|
||||
pos.Y = prevArea.Y + this.ScaledOffset.Y;
|
||||
} else {
|
||||
|
@ -715,7 +703,7 @@ 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)) {
|
||||
if (!autoSize.Equals(this.UnscrolledArea.Size, 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?");
|
||||
|
@ -909,14 +897,13 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates this element and all of its <see cref="SortedChildren"/>
|
||||
/// Updates this element and all of its <see cref="GetRelevantChildren"/>
|
||||
/// </summary>
|
||||
/// <param name="time">The game's time</param>
|
||||
public virtual void Update(GameTime time) {
|
||||
this.System.InvokeOnElementUpdated(this, time);
|
||||
|
||||
// update all sorted children, not just relevant ones, because they might become relevant or irrelevant through updates
|
||||
foreach (var child in this.SortedChildren) {
|
||||
foreach (var child in this.GetRelevantChildren()) {
|
||||
if (child.System != null)
|
||||
child.Update(time);
|
||||
}
|
||||
|
@ -926,8 +913,8 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws this element by calling <see cref="Draw(Microsoft.Xna.Framework.GameTime,Microsoft.Xna.Framework.Graphics.SpriteBatch,float,MLEM.Graphics.SpriteBatchContext)"/> internally.
|
||||
/// If <see cref="Transform"/> or <see cref="BeginImpl"/> is set, a new <c>SpriteBatch.Begin</c> call is also started.
|
||||
/// Draws this element by calling <see cref="Draw"/> internally.
|
||||
/// If <see cref="Transform"/> or <see cref="BeginImpl"/> is set, a new <see cref="SpriteBatch.Begin"/> call is also started.
|
||||
/// </summary>
|
||||
/// <param name="time">The game's time</param>
|
||||
/// <param name="batch">The sprite batch to use for drawing</param>
|
||||
|
@ -937,37 +924,25 @@ namespace MLEM.Ui.Elements {
|
|||
/// <param name="effect">The effect that is used for drawing</param>
|
||||
/// <param name="depthStencilState">The depth stencil state that is used for drawing</param>
|
||||
/// <param name="matrix">The transformation matrix that is used for drawing</param>
|
||||
[Obsolete("Use DrawTransformed that takes a SpriteBatchContext instead")]
|
||||
public void DrawTransformed(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
this.DrawTransformed(time, batch, alpha, new SpriteBatchContext(SpriteSortMode.Deferred, blendState, samplerState, depthStencilState, null, effect, matrix));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws this element by calling <see cref="Draw(Microsoft.Xna.Framework.GameTime,Microsoft.Xna.Framework.Graphics.SpriteBatch,float,MLEM.Graphics.SpriteBatchContext)"/> internally.
|
||||
/// If <see cref="Transform"/> or <see cref="BeginImpl"/> is set, a new <c>SpriteBatch.Begin</c> call is also started.
|
||||
/// </summary>
|
||||
/// <param name="time">The game's time</param>
|
||||
/// <param name="batch">The sprite batch to use for drawing</param>
|
||||
/// <param name="alpha">The alpha to draw this element and its children with</param>
|
||||
/// <param name="context">The sprite batch context to use for drawing</param>
|
||||
public void DrawTransformed(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
#pragma warning disable CS0618
|
||||
var customDraw = this.BeginImpl != null || this.Transform != Matrix.Identity;
|
||||
var transformed = context;
|
||||
transformed.TransformMatrix = this.Transform * transformed.TransformMatrix;
|
||||
var mat = this.Transform * matrix;
|
||||
// TODO ending and beginning again when the matrix changes isn't ideal (https://github.com/MonoGame/MonoGame/issues/3156)
|
||||
if (customDraw) {
|
||||
// end the usual draw so that we can begin our own
|
||||
batch.End();
|
||||
// begin our own draw call
|
||||
batch.Begin(transformed);
|
||||
if (this.BeginImpl != null) {
|
||||
this.BeginImpl(this, time, batch, alpha, blendState, samplerState, depthStencilState, effect, mat);
|
||||
} else {
|
||||
batch.Begin(SpriteSortMode.Deferred, blendState, samplerState, depthStencilState, null, effect, mat);
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
||||
// draw content in custom begin call
|
||||
#pragma warning disable CS0618
|
||||
this.Draw(time, batch, alpha, transformed.BlendState, transformed.SamplerState, transformed.DepthStencilState, transformed.Effect, transformed.TransformMatrix);
|
||||
#pragma warning restore CS0618
|
||||
this.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, mat);
|
||||
if (this.System != null)
|
||||
this.System.Metrics.Draws++;
|
||||
|
||||
|
@ -975,13 +950,13 @@ namespace MLEM.Ui.Elements {
|
|||
// end our draw
|
||||
batch.End();
|
||||
// begin the usual draw again for other elements
|
||||
batch.Begin(context);
|
||||
batch.Begin(SpriteSortMode.Deferred, blendState, samplerState, depthStencilState, null, effect, matrix);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws this element and all of its children. Override this method to draw the content of custom elements.
|
||||
/// Note that, when this is called, <c>SpriteBatch.Begin</c> has already been called with custom <see cref="Transform"/> etc. applied.
|
||||
/// Note that, when this is called, <see cref="SpriteBatch.Begin"/> has already been called with custom <see cref="Transform"/> etc. applied.
|
||||
/// </summary>
|
||||
/// <param name="time">The game's time</param>
|
||||
/// <param name="batch">The sprite batch to use for drawing</param>
|
||||
|
@ -991,37 +966,21 @@ namespace MLEM.Ui.Elements {
|
|||
/// <param name="effect">The effect that is used for drawing</param>
|
||||
/// <param name="depthStencilState">The depth stencil state that is used for drawing</param>
|
||||
/// <param name="matrix">The transformation matrix that is used for drawing</param>
|
||||
[Obsolete("Use Draw that takes a SpriteBatchContext instead")]
|
||||
public virtual void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
this.Draw(time, batch, alpha, new SpriteBatchContext(SpriteSortMode.Deferred, blendState, samplerState, depthStencilState, null, effect, matrix));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws this element and all of its children. Override this method to draw the content of custom elements.
|
||||
/// Note that, when this is called, <c>SpriteBatch.Begin</c> has already been called with custom <see cref="Transform"/> etc. applied.
|
||||
/// </summary>
|
||||
/// <param name="time">The game's time</param>
|
||||
/// <param name="batch">The sprite batch to use for drawing</param>
|
||||
/// <param name="alpha">The alpha to draw this element and its children with</param>
|
||||
/// <param name="context">The sprite batch context to use for drawing</param>
|
||||
public virtual void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
this.System.InvokeOnElementDrawn(this, time, batch, alpha);
|
||||
if (this.IsSelected)
|
||||
this.System.InvokeOnSelectedElementDrawn(this, time, batch, alpha);
|
||||
|
||||
foreach (var child in this.GetRelevantChildren()) {
|
||||
if (!child.IsHidden) {
|
||||
#pragma warning disable CS0618
|
||||
child.DrawTransformed(time, batch, alpha * child.DrawAlpha, context.BlendState, context.SamplerState, context.DepthStencilState, context.Effect, context.TransformMatrix);
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
if (!child.IsHidden)
|
||||
child.DrawTransformed(time, batch, alpha * child.DrawAlpha, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws this element and all of its <see cref="GetRelevantChildren"/> early.
|
||||
/// Drawing early involves drawing onto <see cref="RenderTarget2D"/> instances rather than onto the screen.
|
||||
/// Note that, when this is called, <c>SpriteBatch.Begin</c> has not yet been called.
|
||||
/// Note that, when this is called, <see cref="SpriteBatch.Begin"/> has not yet been called.
|
||||
/// </summary>
|
||||
/// <param name="time">The game's time</param>
|
||||
/// <param name="batch">The sprite batch to use for drawing</param>
|
||||
|
@ -1169,7 +1128,7 @@ namespace MLEM.Ui.Elements {
|
|||
public delegate void OtherElementCallback(Element thisElement, Element otherElement);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate used inside of <see cref="Element.Draw(Microsoft.Xna.Framework.GameTime,Microsoft.Xna.Framework.Graphics.SpriteBatch,float,MLEM.Graphics.SpriteBatchContext)"/>
|
||||
/// A delegate used inside of <see cref="Element.Draw"/>
|
||||
/// </summary>
|
||||
/// <param name="element">The element that is being drawn</param>
|
||||
/// <param name="time">The game's time</param>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MLEM.Input;
|
||||
|
@ -116,9 +115,9 @@ namespace MLEM.Ui.Elements {
|
|||
return group;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="KeybindButton(MLEM.Ui.Anchor,Microsoft.Xna.Framework.Vector2,MLEM.Input.Keybind,MLEM.Input.InputHandler,string,MLEM.Input.Keybind,string,System.Func{MLEM.Input.GenericInput,string},int,System.Func{MLEM.Input.GenericInput,System.Collections.Generic.IEnumerable{MLEM.Input.GenericInput},bool})"/>
|
||||
public static Button KeybindButton(Anchor anchor, Vector2 size, Keybind keybind, InputHandler inputHandler, string activePlaceholder, GenericInput unbindKey = default, string unboundPlaceholder = "", Func<GenericInput, string> inputName = null, int index = 0, Func<GenericInput, IEnumerable<GenericInput>, bool> isKeybindAllowed = null) {
|
||||
return ElementHelper.KeybindButton(anchor, size, keybind, inputHandler, activePlaceholder, new Keybind(unbindKey), unboundPlaceholder, inputName, index, isKeybindAllowed);
|
||||
/// <inheritdoc cref="KeybindButton(MLEM.Ui.Anchor,Microsoft.Xna.Framework.Vector2,MLEM.Input.Keybind,MLEM.Input.InputHandler,string,MLEM.Input.Keybind,string,System.Func{MLEM.Input.GenericInput,string},int)"/>
|
||||
public static Button KeybindButton(Anchor anchor, Vector2 size, Keybind keybind, InputHandler inputHandler, string activePlaceholder, GenericInput unbindKey = default, string unboundPlaceholder = "", Func<GenericInput, string> inputName = null, int index = 0) {
|
||||
return KeybindButton(anchor, size, keybind, inputHandler, activePlaceholder, new Keybind(unbindKey), unboundPlaceholder, inputName, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -136,12 +135,9 @@ namespace MLEM.Ui.Elements {
|
|||
/// <param name="unboundPlaceholder">A placeholder text that is displayed if the keybind is unbound</param>
|
||||
/// <param name="inputName">An optional function to give each input a display name that is easier to read. If this is null, <see cref="GenericInput.ToString"/> is used.</param>
|
||||
/// <param name="index">The index in the <paramref name="keybind"/> that the button should display. Note that, if the index is greater than the amount of combinations, combinations entered using this button will be stored at an earlier index.</param>
|
||||
/// <param name="isKeybindAllowed">A function that can optionally determine whether a given input and modifier combination is allowed. If this is null, all combinations are allowed.</param>
|
||||
/// <returns>A keybind button with the given settings</returns>
|
||||
public static Button KeybindButton(Anchor anchor, Vector2 size, Keybind keybind, InputHandler inputHandler, string activePlaceholder, Keybind unbind = default, string unboundPlaceholder = "", Func<GenericInput, string> inputName = null, int index = 0, Func<GenericInput, IEnumerable<GenericInput>, bool> isKeybindAllowed = null) {
|
||||
string GetCurrentName() {
|
||||
return keybind.TryGetCombination(index, out var combination) ? combination.ToString(" + ", inputName) : unboundPlaceholder;
|
||||
}
|
||||
public static Button KeybindButton(Anchor anchor, Vector2 size, Keybind keybind, InputHandler inputHandler, string activePlaceholder, Keybind unbind = default, string unboundPlaceholder = "", Func<GenericInput, string> inputName = null, int index = 0) {
|
||||
string GetCurrentName() => keybind.TryGetCombination(index, out var combination) ? combination.ToString(" + ", inputName) : unboundPlaceholder;
|
||||
|
||||
var button = new Button(anchor, size, GetCurrentName());
|
||||
var activeNext = false;
|
||||
|
@ -154,21 +150,19 @@ namespace MLEM.Ui.Elements {
|
|||
button.SetData("Active", true);
|
||||
activeNext = false;
|
||||
} else if (button.GetData<bool>("Active")) {
|
||||
if (unbind != null && unbind.TryConsumePressed(inputHandler)) {
|
||||
if (unbind != null && unbind.IsPressed(inputHandler)) {
|
||||
keybind.Remove((c, i) => i == index);
|
||||
button.Text.Text = unboundPlaceholder;
|
||||
button.SetData("Active", false);
|
||||
} else if (inputHandler.InputsPressed.Length > 0) {
|
||||
var key = inputHandler.InputsPressed.FirstOrDefault(i => !i.IsModifier());
|
||||
if (key != default) {
|
||||
var mods = inputHandler.InputsDown.Where(i => i.IsModifier()).ToArray();
|
||||
if (isKeybindAllowed == null || isKeybindAllowed(key, mods)) {
|
||||
keybind.Remove((c, i) => i == index).Insert(index, key, mods);
|
||||
var mods = inputHandler.InputsDown.Where(i => i.IsModifier());
|
||||
keybind.Remove((c, i) => i == index).Insert(index, key, mods.ToArray());
|
||||
button.Text.Text = GetCurrentName();
|
||||
button.SetData("Active", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
button.Text.Text = GetCurrentName();
|
||||
}
|
||||
|
@ -190,7 +184,7 @@ namespace MLEM.Ui.Elements {
|
|||
/// <param name="textCallback">The text to display on the tooltip</param>
|
||||
/// <returns>The created tooltip instance</returns>
|
||||
public static Tooltip AddTooltip(this Element element, Paragraph.TextCallback textCallback) {
|
||||
return element.AddTooltip(new Tooltip(textCallback));
|
||||
return new Tooltip(textCallback, element);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -200,18 +194,7 @@ namespace MLEM.Ui.Elements {
|
|||
/// <param name="text">The text to display on the tooltip</param>
|
||||
/// <returns>The created tooltip instance</returns>
|
||||
public static Tooltip AddTooltip(this Element element, string text) {
|
||||
return element.AddTooltip(new Tooltip(text));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given <see cref="Tooltip"/> to the given element
|
||||
/// </summary>
|
||||
/// <param name="element">The element to add the tooltip to</param>
|
||||
/// <param name="tooltip">The tooltip to add</param>
|
||||
/// <returns>The passed tooltip, for chaining</returns>
|
||||
public static Tooltip AddTooltip(this Element element, Tooltip tooltip) {
|
||||
tooltip.AddToElement(element);
|
||||
return tooltip;
|
||||
return new Tooltip(text, element);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Graphics;
|
||||
|
||||
namespace MLEM.Ui.Elements {
|
||||
/// <summary>
|
||||
|
@ -21,10 +20,10 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
// since the group never accesses its own area when drawing, we have to update it manually
|
||||
this.UpdateAreaIfDirty();
|
||||
base.Draw(time, batch, alpha, context);
|
||||
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Extensions;
|
||||
using MLEM.Graphics;
|
||||
using MLEM.Misc;
|
||||
using MLEM.Textures;
|
||||
using MLEM.Ui.Style;
|
||||
|
@ -105,7 +103,7 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
if (this.Texture == null)
|
||||
return;
|
||||
var center = new Vector2(this.Texture.Width / 2F, this.Texture.Height / 2F);
|
||||
|
@ -118,7 +116,7 @@ namespace MLEM.Ui.Elements {
|
|||
var scale = new Vector2(1F / this.Texture.Width, 1F / this.Texture.Height) * this.DisplayArea.Size;
|
||||
batch.Draw(this.Texture, this.DisplayArea.Location + center * scale, color, this.ImageRotation, center, scale * this.ImageScale, this.ImageEffects, 0);
|
||||
}
|
||||
base.Draw(time, batch, alpha, context);
|
||||
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -4,7 +4,6 @@ using System.Linq;
|
|||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Extensions;
|
||||
using MLEM.Graphics;
|
||||
using MLEM.Misc;
|
||||
using MLEM.Textures;
|
||||
using MLEM.Ui.Style;
|
||||
|
@ -121,7 +120,7 @@ namespace MLEM.Ui.Elements {
|
|||
var offset = new Vector2(0, -this.ScrollBar.CurrentValue);
|
||||
// we ignore false grandchildren so that the children of the scroll bar stay in place
|
||||
foreach (var child in this.GetChildren(c => c != this.ScrollBar, true, true)) {
|
||||
if (!child.ScrollOffset.Equals(offset, Element.Epsilon)) {
|
||||
if (!child.ScrollOffset.Equals(offset, Epsilon)) {
|
||||
child.ScrollOffset = offset;
|
||||
this.relevantChildrenDirty = true;
|
||||
}
|
||||
|
@ -168,7 +167,7 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
// draw children onto the render target if we have one
|
||||
if (this.scrollOverflow && this.renderTarget != null) {
|
||||
this.UpdateAreaIfDirty();
|
||||
|
@ -180,22 +179,21 @@ namespace MLEM.Ui.Elements {
|
|||
batch.GraphicsDevice.Clear(Color.Transparent);
|
||||
// offset children by the render target's location
|
||||
var area = this.GetRenderTargetArea();
|
||||
var trans = Matrix.CreateTranslation(-area.X, -area.Y, 0);
|
||||
// do the usual draw, but within the render target
|
||||
var trans = context;
|
||||
trans.TransformMatrix = Matrix.CreateTranslation(-area.X, -area.Y, 0);
|
||||
batch.Begin(trans);
|
||||
base.Draw(time, batch, alpha, trans);
|
||||
batch.Begin(SpriteSortMode.Deferred, blendState, samplerState, depthStencilState, null, effect, trans);
|
||||
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, trans);
|
||||
batch.End();
|
||||
}
|
||||
batch.GraphicsDevice.PresentationParameters.RenderTargetUsage = lastUsage;
|
||||
batch.Begin(context);
|
||||
batch.Begin(SpriteSortMode.Deferred, blendState, samplerState, depthStencilState, null, effect, matrix);
|
||||
}
|
||||
|
||||
if (this.Texture.HasValue())
|
||||
batch.Draw(this.Texture, this.DisplayArea, this.DrawColor.OrDefault(Color.White) * alpha, this.Scale);
|
||||
// if we handle overflow, draw using the render target in DrawUnbound
|
||||
if (!this.scrollOverflow || this.renderTarget == null) {
|
||||
base.Draw(time, batch, alpha, context);
|
||||
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
} else {
|
||||
// draw the actual render target (don't apply the alpha here because it's already drawn onto with alpha)
|
||||
batch.Draw(this.renderTarget, this.GetRenderTargetArea(), Color.White);
|
||||
|
@ -246,13 +244,13 @@ namespace MLEM.Ui.Elements {
|
|||
|
||||
// the max value of the scrollbar is the amount of non-scaled pixels taken up by overflowing components
|
||||
var scrollBarMax = (childrenHeight - this.ChildPaddedArea.Height) / this.Scale;
|
||||
if (!this.ScrollBar.MaxValue.Equals(scrollBarMax, Element.Epsilon)) {
|
||||
if (!this.ScrollBar.MaxValue.Equals(scrollBarMax, Epsilon)) {
|
||||
this.ScrollBar.MaxValue = scrollBarMax;
|
||||
this.relevantChildrenDirty = true;
|
||||
|
||||
// update child padding based on whether the scroll bar is visible
|
||||
var childOffset = this.ScrollBar.IsHidden ? 0 : this.ScrollerSize.Value.X + this.ScrollBarOffset;
|
||||
if (!this.scrollBarChildOffset.Equals(childOffset, Element.Epsilon)) {
|
||||
if (!this.scrollBarChildOffset.Equals(childOffset, Epsilon)) {
|
||||
this.ChildPadding += new Padding(0, -this.scrollBarChildOffset + childOffset, 0, 0);
|
||||
this.scrollBarChildOffset = childOffset;
|
||||
this.SetAreaDirty();
|
||||
|
|
|
@ -6,7 +6,6 @@ using MLEM.Extensions;
|
|||
using MLEM.Font;
|
||||
using MLEM.Formatting;
|
||||
using MLEM.Formatting.Codes;
|
||||
using MLEM.Graphics;
|
||||
using MLEM.Misc;
|
||||
using MLEM.Ui.Style;
|
||||
|
||||
|
@ -59,13 +58,8 @@ namespace MLEM.Ui.Elements {
|
|||
set {
|
||||
if (this.text != value) {
|
||||
this.text = value;
|
||||
this.IsHidden = string.IsNullOrWhiteSpace(this.text);
|
||||
this.SetTextDirty();
|
||||
|
||||
var force = string.IsNullOrWhiteSpace(this.text);
|
||||
if (this.forceHide != force) {
|
||||
this.forceHide = force;
|
||||
this.SetAreaDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -104,13 +98,9 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsHidden => base.IsHidden || this.forceHide;
|
||||
|
||||
private string text;
|
||||
private StyleProp<TextAlignment> alignment;
|
||||
private StyleProp<GenericFont> regularFont;
|
||||
private bool forceHide;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new paragraph with the given settings.
|
||||
|
@ -119,13 +109,19 @@ namespace MLEM.Ui.Elements {
|
|||
/// <param name="width">The paragraph's width. Note that its height is automatically calculated.</param>
|
||||
/// <param name="textCallback">The paragraph's text</param>
|
||||
/// <param name="autoAdjustWidth">Whether the paragraph's width should automatically be calculated based on the text within it.</param>
|
||||
public Paragraph(Anchor anchor, float width, TextCallback textCallback, bool autoAdjustWidth = false) : this(anchor, width, string.Empty, autoAdjustWidth) {
|
||||
public Paragraph(Anchor anchor, float width, TextCallback textCallback, bool autoAdjustWidth = false)
|
||||
: this(anchor, width, "", autoAdjustWidth) {
|
||||
this.GetTextCallback = textCallback;
|
||||
this.Text = textCallback(this);
|
||||
if (this.Text == null)
|
||||
this.IsHidden = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Paragraph(Anchor,float,TextCallback,bool)"/>
|
||||
public Paragraph(Anchor anchor, float width, string text, bool autoAdjustWidth = false) : base(anchor, new Vector2(width, 0)) {
|
||||
this.Text = text;
|
||||
if (this.Text == null)
|
||||
this.IsHidden = true;
|
||||
this.AutoAdjustWidth = autoAdjustWidth;
|
||||
this.CanBeSelected = false;
|
||||
this.CanBeMoused = false;
|
||||
|
@ -135,8 +131,8 @@ namespace MLEM.Ui.Elements {
|
|||
protected override Vector2 CalcActualSize(RectangleF parentArea) {
|
||||
var size = base.CalcActualSize(parentArea);
|
||||
this.ParseText(size);
|
||||
var textSize = this.TokenizedText.Measure(this.RegularFont) * this.TextScale * this.TextScaleMultiplier * this.Scale;
|
||||
return new Vector2(this.AutoAdjustWidth ? textSize.X + this.ScaledPadding.Width : size.X, textSize.Y + this.ScaledPadding.Height);
|
||||
var (w, h) = this.TokenizedText.Measure(this.RegularFont) * this.TextScale * this.TextScaleMultiplier * this.Scale;
|
||||
return new Vector2(this.AutoAdjustWidth ? w + this.ScaledPadding.Width : size.X, h + this.ScaledPadding.Height);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -148,12 +144,12 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
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);
|
||||
base.Draw(time, batch, alpha, context);
|
||||
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -197,7 +193,7 @@ namespace MLEM.Ui.Elements {
|
|||
protected void SetTextDirty() {
|
||||
this.TokenizedText = null;
|
||||
// only set our area dirty if our size changed as a result of this action
|
||||
if (!this.AreaDirty && !this.CalcActualSize(this.ParentArea).Equals(this.DisplayArea.Size, Element.Epsilon))
|
||||
if (!this.AreaDirty && !this.CalcActualSize(this.ParentArea).Equals(this.DisplayArea.Size, Epsilon))
|
||||
this.SetAreaDirty();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Extensions;
|
||||
using MLEM.Graphics;
|
||||
using MLEM.Misc;
|
||||
using MLEM.Textures;
|
||||
using MLEM.Ui.Style;
|
||||
|
@ -74,7 +73,7 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
batch.Draw(this.Texture, this.DisplayArea, (Color) this.Color * alpha, this.Scale);
|
||||
|
||||
var percentage = this.CurrentValue / this.MaxValue;
|
||||
|
@ -107,7 +106,7 @@ namespace MLEM.Ui.Elements {
|
|||
} else {
|
||||
batch.Draw(batch.GetBlankTexture(), offsetArea, (Color) this.ProgressColor * alpha);
|
||||
}
|
||||
base.Draw(time, batch, alpha, context);
|
||||
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -4,7 +4,7 @@ using MLEM.Ui.Style;
|
|||
namespace MLEM.Ui.Elements {
|
||||
/// <summary>
|
||||
/// A radio button element to use inside of a <see cref="UiSystem"/>.
|
||||
/// A radio button is a variation of a <see cref="Checkbox"/> that causes all other radio buttons in the same <see cref="RootElement"/> to be deselected upon selection.
|
||||
/// A radio button is a variation of a <see cref="Checkbox"/> that causes all other radio buttons in the same <see cref="Group"/> to be deselected upon selection.
|
||||
/// </summary>
|
||||
public class RadioButton : Checkbox {
|
||||
|
||||
|
@ -26,13 +26,13 @@ namespace MLEM.Ui.Elements {
|
|||
base(anchor, size, label, defaultChecked) {
|
||||
this.Group = group;
|
||||
|
||||
// don't += because we want to override the checking/unchecking behavior of Checkbox
|
||||
// don't += because we want to override the checking + unchecking behavior of Checkbox
|
||||
this.OnPressed = element => {
|
||||
this.Checked = true;
|
||||
this.Root.Element.AndChildren(e => {
|
||||
if (e != this && e is RadioButton r && r.Group == this.Group)
|
||||
r.Checked = false;
|
||||
});
|
||||
foreach (var sib in this.GetSiblings()) {
|
||||
if (sib is RadioButton radio && radio.Group == this.Group)
|
||||
radio.Checked = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@ using System.Linq;
|
|||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input.Touch;
|
||||
using MLEM.Extensions;
|
||||
using MLEM.Graphics;
|
||||
using MLEM.Input;
|
||||
using MLEM.Misc;
|
||||
using MLEM.Textures;
|
||||
|
@ -46,7 +44,7 @@ namespace MLEM.Ui.Elements {
|
|||
// force current value to be clamped
|
||||
this.CurrentValue = this.CurrentValue;
|
||||
// auto-hide if necessary
|
||||
var shouldHide = this.maxValue <= Element.Epsilon;
|
||||
var shouldHide = this.maxValue <= Epsilon;
|
||||
if (this.AutoHideWhenEmpty && this.IsHidden != shouldHide) {
|
||||
this.IsHidden = shouldHide;
|
||||
this.OnAutoHide?.Invoke(this);
|
||||
|
@ -139,7 +137,7 @@ namespace MLEM.Ui.Elements {
|
|||
|
||||
// MOUSE INPUT
|
||||
var moused = this.Controls.MousedElement;
|
||||
if (moused == this && this.Input.WasMouseButtonUp(MouseButton.Left) && this.Input.IsMouseButtonDown(MouseButton.Left)) {
|
||||
if (moused == this && this.Input.IsMouseButtonPressed(MouseButton.Left)) {
|
||||
this.isMouseHeld = true;
|
||||
this.scrollStartOffset = this.TransformInverseAll(this.Input.ViewportMousePosition.ToVector2()) - this.ScrollerPosition;
|
||||
} else if (this.isMouseHeld && !this.Input.IsMouseButtonDown(MouseButton.Left)) {
|
||||
|
@ -189,31 +187,31 @@ namespace MLEM.Ui.Elements {
|
|||
|
||||
if (this.SmoothScrolling && this.scrollAdded != 0) {
|
||||
this.scrollAdded *= this.SmoothScrollFactor;
|
||||
if (Math.Abs(this.scrollAdded) <= Element.Epsilon)
|
||||
if (Math.Abs(this.scrollAdded) <= Epsilon)
|
||||
this.scrollAdded = 0;
|
||||
this.OnValueChanged?.Invoke(this, this.CurrentValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void ScrollToPos(Vector2 position) {
|
||||
var size = this.ScrollerSize * this.Scale;
|
||||
var (width, height) = this.ScrollerSize * this.Scale;
|
||||
if (this.Horizontal) {
|
||||
var offset = this.scrollStartOffset.X >= 0 && this.scrollStartOffset.X <= size.X ? this.scrollStartOffset.X : size.X / 2;
|
||||
this.CurrentValue = (position.X - this.Area.X - offset) / (this.Area.Width - size.X) * this.MaxValue;
|
||||
var offset = this.scrollStartOffset.X >= 0 && this.scrollStartOffset.X <= width ? this.scrollStartOffset.X : width / 2;
|
||||
this.CurrentValue = (position.X - this.Area.X - offset) / (this.Area.Width - width) * this.MaxValue;
|
||||
} else {
|
||||
var offset = this.scrollStartOffset.Y >= 0 && this.scrollStartOffset.Y <= size.Y ? this.scrollStartOffset.Y : size.Y / 2;
|
||||
this.CurrentValue = (position.Y - this.Area.Y - offset) / (this.Area.Height - size.Y) * this.MaxValue;
|
||||
var offset = this.scrollStartOffset.Y >= 0 && this.scrollStartOffset.Y <= height ? this.scrollStartOffset.Y : height / 2;
|
||||
this.CurrentValue = (position.Y - this.Area.Y - offset) / (this.Area.Height - height) * this.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
batch.Draw(this.Background, this.DisplayArea, Color.White * alpha, this.Scale);
|
||||
if (this.MaxValue > 0) {
|
||||
var scrollerRect = new RectangleF(this.ScrollerPosition, this.ScrollerSize * this.Scale);
|
||||
batch.Draw(this.ScrollerTexture, scrollerRect, Color.White * alpha, this.Scale);
|
||||
}
|
||||
base.Draw(time, batch, alpha, context);
|
||||
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -30,9 +30,9 @@ namespace MLEM.Ui.Elements {
|
|||
base.Update(time);
|
||||
|
||||
if (this.IsSelected) {
|
||||
if (this.CurrentValue > 0 && this.Controls.LeftButtons.TryConsumePressed(this.Input, this.Controls.GamepadIndex)) {
|
||||
if (this.Controls.LeftButtons.IsPressed(this.Input, this.Controls.GamepadIndex)) {
|
||||
this.CurrentValue -= this.StepPerScroll;
|
||||
} else if (this.CurrentValue < this.MaxValue && this.Controls.RightButtons.TryConsumePressed(this.Input, this.Controls.GamepadIndex)) {
|
||||
} else if (this.Controls.RightButtons.IsPressed(this.Input, this.Controls.GamepadIndex)) {
|
||||
this.CurrentValue += this.StepPerScroll;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace MLEM.Ui.Elements {
|
|||
// we squish children in order of priority, since auto-anchoring is based on addition order
|
||||
for (var i = 0; i < this.SortedChildren.Count; i++) {
|
||||
var child = this.SortedChildren[i];
|
||||
if (SquishingGroup.SquishChild(child, out var squished))
|
||||
if (SquishChild(child, out var squished))
|
||||
child.SetAreaAndUpdateChildren(squished);
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!pos.Equals(element.Area.Location, Element.Epsilon) || !size.Equals(element.Area.Size, Element.Epsilon)) {
|
||||
if (!pos.Equals(element.Area.Location, Epsilon) || !size.Equals(element.Area.Size, Epsilon)) {
|
||||
squishedArea = new RectangleF(pos, size);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using MLEM.Extensions;
|
||||
using MLEM.Font;
|
||||
using MLEM.Graphics;
|
||||
using MLEM.Input;
|
||||
using MLEM.Misc;
|
||||
using MLEM.Textures;
|
||||
|
@ -14,22 +18,57 @@ namespace MLEM.Ui.Elements {
|
|||
/// A text field element for use inside of a <see cref="UiSystem"/>.
|
||||
/// A text field is a selectable element that can be typed in, as well as copied and pasted from.
|
||||
/// If an on-screen keyboard is required, then this text field will automatically open an on-screen keyboard using <see cref="MlemPlatform.OpenOnScreenKeyboard"/>.
|
||||
/// This class interally uses MLEM's <see cref="TextInput"/>.
|
||||
/// </summary>
|
||||
public class TextField : Element {
|
||||
|
||||
/// <inheritdoc cref="TextInput.DefaultRule"/>
|
||||
public static readonly Rule DefaultRule = (field, add) => TextInput.DefaultRule(field.textInput, add);
|
||||
/// <inheritdoc cref="TextInput.OnlyLetters"/>
|
||||
public static readonly Rule OnlyLetters = (field, add) => TextInput.OnlyLetters(field.textInput, add);
|
||||
/// <inheritdoc cref="TextInput.OnlyNumbers"/>
|
||||
public static readonly Rule OnlyNumbers = (field, add) => TextInput.OnlyNumbers(field.textInput, add);
|
||||
/// <inheritdoc cref="TextInput.LettersNumbers"/>
|
||||
public static readonly Rule LettersNumbers = (field, add) => TextInput.LettersNumbers(field.textInput, add);
|
||||
/// <inheritdoc cref="TextInput.PathNames"/>
|
||||
public static readonly Rule PathNames = (field, add) => TextInput.PathNames(field.textInput, add);
|
||||
/// <inheritdoc cref="TextInput.FileNames"/>
|
||||
public static readonly Rule FileNames = (field, add) => TextInput.FileNames(field.textInput, add);
|
||||
/// <summary>
|
||||
/// A <see cref="Rule"/> that allows any visible character and spaces
|
||||
/// </summary>
|
||||
public static readonly Rule DefaultRule = (field, add) => {
|
||||
foreach (var c in add) {
|
||||
if (char.IsControl(c) && (!field.Multiline || c != '\n'))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
/// <summary>
|
||||
/// A <see cref="Rule"/> that only allows letters
|
||||
/// </summary>
|
||||
public static readonly Rule OnlyLetters = (field, add) => {
|
||||
foreach (var c in add) {
|
||||
if (!char.IsLetter(c))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
/// <summary>
|
||||
/// A <see cref="Rule"/> that only allows numerals
|
||||
/// </summary>
|
||||
public static readonly Rule OnlyNumbers = (field, add) => {
|
||||
foreach (var c in add) {
|
||||
if (!char.IsNumber(c))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
/// <summary>
|
||||
/// A <see cref="Rule"/> that only allows letters and numerals
|
||||
/// </summary>
|
||||
public static readonly Rule LettersNumbers = (field, add) => {
|
||||
foreach (var c in add) {
|
||||
if (!char.IsLetter(c) || !char.IsNumber(c))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
/// <summary>
|
||||
/// A <see cref="Rule"/> that only allows characters not contained in <see cref="Path.GetInvalidPathChars"/>
|
||||
/// </summary>
|
||||
public static readonly Rule PathNames = (field, add) => add.IndexOfAny(Path.GetInvalidPathChars()) < 0;
|
||||
/// <summary>
|
||||
/// A <see cref="Rule"/> that only allows characters not contained in <see cref="Path.GetInvalidFileNameChars"/>
|
||||
/// </summary>
|
||||
public static readonly Rule FileNames = (field, add) => add.IndexOfAny(Path.GetInvalidFileNameChars()) < 0;
|
||||
|
||||
/// <summary>
|
||||
/// The color that this text field's text should display with
|
||||
|
@ -54,23 +93,23 @@ namespace MLEM.Ui.Elements {
|
|||
/// <summary>
|
||||
/// The scale that this text field should render text with
|
||||
/// </summary>
|
||||
public StyleProp<float> TextScale {
|
||||
get => this.textScale;
|
||||
set {
|
||||
this.textScale = value;
|
||||
this.textInput.TextScale = value;
|
||||
}
|
||||
}
|
||||
public StyleProp<float> TextScale;
|
||||
/// <summary>
|
||||
/// The font that this text field should display text with
|
||||
/// </summary>
|
||||
public StyleProp<GenericFont> Font {
|
||||
get => this.font;
|
||||
set {
|
||||
this.font = value;
|
||||
this.textInput.Font = value;
|
||||
}
|
||||
}
|
||||
public StyleProp<GenericFont> Font;
|
||||
/// <summary>
|
||||
/// This text field's current text
|
||||
/// </summary>
|
||||
public string Text => this.text.ToString();
|
||||
/// <summary>
|
||||
/// The text that displays in this text field if <see cref="Text"/> is empty
|
||||
/// </summary>
|
||||
public string PlaceholderText;
|
||||
/// <summary>
|
||||
/// An event that gets called when <see cref="Text"/> changes, either through input, or through a manual change.
|
||||
/// </summary>
|
||||
public TextChanged OnTextChange;
|
||||
/// <summary>
|
||||
/// The x position that text should start rendering at, based on the x position of this text field.
|
||||
/// </summary>
|
||||
|
@ -79,48 +118,11 @@ namespace MLEM.Ui.Elements {
|
|||
/// The width that the caret should render with, in pixels
|
||||
/// </summary>
|
||||
public StyleProp<float> CaretWidth;
|
||||
|
||||
/// <inheritdoc cref="TextInput.Text"/>
|
||||
public string Text => this.textInput.Text;
|
||||
/// <inheritdoc cref="TextInput.OnTextChange"/>
|
||||
public TextChanged OnTextChange;
|
||||
/// <inheritdoc cref="TextInput.InputRule"/>
|
||||
public Rule InputRule;
|
||||
/// <inheritdoc cref="TextInput.CaretPos"/>
|
||||
public int CaretPos {
|
||||
get => this.textInput.CaretPos;
|
||||
set => this.textInput.CaretPos = value;
|
||||
}
|
||||
/// <inheritdoc cref="TextInput.CaretLine"/>
|
||||
public int CaretLine => this.textInput.CaretLine;
|
||||
/// <inheritdoc cref="TextInput.CaretPosInLine"/>
|
||||
public int CaretPosInLine => this.textInput.CaretPosInLine;
|
||||
/// <inheritdoc cref="TextInput.MaskingCharacter"/>
|
||||
public char? MaskingCharacter {
|
||||
get => this.textInput.MaskingCharacter;
|
||||
set => this.textInput.MaskingCharacter = value;
|
||||
}
|
||||
/// <inheritdoc cref="TextInput.MaximumCharacters"/>
|
||||
public int? MaximumCharacters {
|
||||
get => this.textInput.MaximumCharacters;
|
||||
set => this.textInput.MaximumCharacters = value;
|
||||
}
|
||||
/// <inheritdoc cref="TextInput.Multiline"/>
|
||||
public bool Multiline {
|
||||
get => this.textInput.Multiline;
|
||||
set => this.textInput.Multiline = value;
|
||||
}
|
||||
|
||||
#if FNA
|
||||
/// <inheritdoc />
|
||||
// we need to make sure that the enter press doesn't get consumed by our press function so that it still works in TextInput
|
||||
public override bool CanBePressed => base.CanBePressed && !this.IsSelected;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The text that displays in this text field if <see cref="Text"/> is empty
|
||||
/// The rule used for text input.
|
||||
/// Rules allow only certain characters to be allowed inside of a text field.
|
||||
/// </summary>
|
||||
public string PlaceholderText;
|
||||
public Rule InputRule;
|
||||
/// <summary>
|
||||
/// The title of the <c>KeyboardInput</c> field on mobile devices and consoles
|
||||
/// </summary>
|
||||
|
@ -129,10 +131,72 @@ namespace MLEM.Ui.Elements {
|
|||
/// The description of the <c>KeyboardInput</c> field on mobile devices and consoles
|
||||
/// </summary>
|
||||
public string MobileDescription;
|
||||
/// <summary>
|
||||
/// The position of the caret within the text.
|
||||
/// This is always between 0 and the <see cref="string.Length"/> of <see cref="Text"/>
|
||||
/// </summary>
|
||||
public int CaretPos {
|
||||
get => this.caretPos;
|
||||
set {
|
||||
var val = MathHelper.Clamp(value, 0, this.text.Length);
|
||||
if (this.caretPos != val) {
|
||||
this.caretPos = val;
|
||||
this.caretBlinkTimer = 0;
|
||||
this.HandleTextChange(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The line of text that the caret is currently on.
|
||||
/// This can only be only non-0 if <see cref="Multiline"/> is true.
|
||||
/// </summary>
|
||||
public int CaretLine { get; private set; }
|
||||
/// <summary>
|
||||
/// The position in the current <see cref="CaretLine"/> that the caret is currently on.
|
||||
/// If <see cref="Multiline"/> is false, this value is always equal to <see cref="CaretPos"/>.
|
||||
/// </summary>
|
||||
public int CaretPosInLine { get; private set; }
|
||||
/// <summary>
|
||||
/// A character that should be displayed instead of this text field's <see cref="Text"/> content.
|
||||
/// The amount of masking characters displayed will be equal to the <see cref="Text"/>'s length.
|
||||
/// This behavior is useful for password fields or similar.
|
||||
/// </summary>
|
||||
public char? MaskingCharacter {
|
||||
get => this.maskingCharacter;
|
||||
set {
|
||||
this.maskingCharacter = value;
|
||||
this.HandleTextChange(false);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The maximum amount of characters that can be input into this text field.
|
||||
/// If this is set, the length of <see cref="Text"/> will never exceed this value.
|
||||
/// </summary>
|
||||
public int? MaximumCharacters;
|
||||
/// <summary>
|
||||
/// Whether this text field should support multi-line editing.
|
||||
/// If this is true, pressing <see cref="Keys.Enter"/> will insert a new line into the <see cref="Text"/> if the <see cref="InputRule"/> allows it.
|
||||
/// Additionally, text will be rendered with horizontal soft wraps, and lines that are outside of the text field's bounds will be hidden.
|
||||
/// </summary>
|
||||
public bool Multiline {
|
||||
get => this.multiline;
|
||||
set {
|
||||
this.multiline = value;
|
||||
this.HandleTextChange(false);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly TextInput textInput;
|
||||
private StyleProp<GenericFont> font;
|
||||
private StyleProp<float> textScale;
|
||||
private readonly StringBuilder text = new StringBuilder();
|
||||
|
||||
private char? maskingCharacter;
|
||||
private double caretBlinkTimer;
|
||||
private string displayedText;
|
||||
private string[] splitText;
|
||||
private int textOffset;
|
||||
private int lineOffset;
|
||||
private int caretPos;
|
||||
private float caretDrawOffset;
|
||||
private bool multiline;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new text field with the given settings
|
||||
|
@ -144,12 +208,7 @@ namespace MLEM.Ui.Elements {
|
|||
/// <param name="text">The text that the text field should contain by default</param>
|
||||
/// <param name="multiline">Whether the text field should support multi-line editing</param>
|
||||
public TextField(Anchor anchor, Vector2 size, Rule rule = null, GenericFont font = null, string text = null, bool multiline = false) : base(anchor, size) {
|
||||
this.textInput = new TextInput(null, Vector2.Zero, 1, null, ClipboardService.SetText, ClipboardService.GetText) {
|
||||
OnTextChange = (i, s) => this.OnTextChange?.Invoke(this, s),
|
||||
InputRule = (i, s) => this.InputRule.Invoke(this, s)
|
||||
};
|
||||
|
||||
this.InputRule = rule ?? TextField.DefaultRule;
|
||||
this.InputRule = rule ?? DefaultRule;
|
||||
this.Multiline = multiline;
|
||||
if (font != null)
|
||||
this.Font = font;
|
||||
|
@ -158,36 +217,75 @@ namespace MLEM.Ui.Elements {
|
|||
|
||||
MlemPlatform.EnsureExists();
|
||||
|
||||
this.OnPressed += async e => {
|
||||
this.OnPressed += OnPressed;
|
||||
this.OnTextInput += (element, key, character) => {
|
||||
if (!this.IsSelected || this.IsHidden)
|
||||
return;
|
||||
if (key == Keys.Back) {
|
||||
if (this.CaretPos > 0) {
|
||||
this.CaretPos--;
|
||||
this.RemoveText(this.CaretPos, 1);
|
||||
}
|
||||
} else if (key == Keys.Delete) {
|
||||
this.RemoveText(this.CaretPos, 1);
|
||||
} else if (this.Multiline && key == Keys.Enter) {
|
||||
this.InsertText('\n');
|
||||
} else {
|
||||
this.InsertText(character);
|
||||
}
|
||||
};
|
||||
this.OnDeselected += e => this.CaretPos = 0;
|
||||
this.OnSelected += e => this.CaretPos = this.text.Length;
|
||||
|
||||
async void OnPressed(Element e) {
|
||||
var title = this.MobileTitle ?? this.PlaceholderText;
|
||||
var result = await MlemPlatform.Current.OpenOnScreenKeyboard(title, this.MobileDescription, this.Text, false);
|
||||
if (result != null)
|
||||
this.SetText(this.Multiline ? result : result.Replace('\n', ' '), true);
|
||||
};
|
||||
this.OnTextInput += (element, key, character) => {
|
||||
if (this.IsSelected && !this.IsHidden)
|
||||
this.textInput.OnTextInput(key, character);
|
||||
};
|
||||
this.OnDeselected += e => this.CaretPos = 0;
|
||||
this.OnSelected += e => this.CaretPos = this.textInput.Length;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetAreaAndUpdateChildren(RectangleF area) {
|
||||
base.SetAreaAndUpdateChildren(area);
|
||||
this.textInput.Size = this.DisplayArea.Size / this.Scale - new Vector2(2 * this.TextOffsetX);
|
||||
this.textInput.TextScale = this.TextScale;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(GameTime time) {
|
||||
base.Update(time);
|
||||
if (this.IsSelected && !this.IsHidden)
|
||||
this.textInput.Update(time, this.Input);
|
||||
|
||||
// handle first initialization if not done
|
||||
if (this.displayedText == null)
|
||||
this.HandleTextChange(false);
|
||||
|
||||
if (!this.IsSelected || this.IsHidden)
|
||||
return;
|
||||
|
||||
if (this.Input.IsKeyPressed(Keys.Left)) {
|
||||
this.CaretPos--;
|
||||
} else if (this.Input.IsKeyPressed(Keys.Right)) {
|
||||
this.CaretPos++;
|
||||
} else if (this.Multiline && this.Input.IsKeyPressed(Keys.Up)) {
|
||||
this.MoveCaretToLine(this.CaretLine - 1);
|
||||
} else if (this.Multiline && this.Input.IsKeyPressed(Keys.Down)) {
|
||||
this.MoveCaretToLine(this.CaretLine + 1);
|
||||
} else if (this.Input.IsKeyPressed(Keys.Home)) {
|
||||
this.CaretPos = 0;
|
||||
} else if (this.Input.IsKeyPressed(Keys.End)) {
|
||||
this.CaretPos = this.text.Length;
|
||||
} else if (this.Input.IsModifierKeyDown(ModifierKey.Control)) {
|
||||
if (this.Input.IsKeyPressed(Keys.V)) {
|
||||
var clip = ClipboardService.GetText();
|
||||
if (clip != null)
|
||||
this.InsertText(clip, true);
|
||||
} else if (this.Input.IsKeyPressed(Keys.C)) {
|
||||
// until there is text selection, just copy the whole content
|
||||
ClipboardService.SetText(this.Text);
|
||||
}
|
||||
}
|
||||
|
||||
this.caretBlinkTimer += time.ElapsedGameTime.TotalSeconds;
|
||||
if (this.caretBlinkTimer >= 1)
|
||||
this.caretBlinkTimer = 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
var tex = this.Texture;
|
||||
var color = Color.White * alpha;
|
||||
if (this.IsMouseOver) {
|
||||
|
@ -196,31 +294,101 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
batch.Draw(tex, this.DisplayArea, color, this.Scale);
|
||||
|
||||
if (this.displayedText != null) {
|
||||
var lineHeight = this.Font.Value.LineHeight * this.TextScale * this.Scale;
|
||||
var textPos = this.DisplayArea.Location + new Vector2(
|
||||
var offset = new Vector2(
|
||||
this.TextOffsetX * this.Scale,
|
||||
this.Multiline ? this.TextOffsetX * this.Scale : this.DisplayArea.Height / 2 - lineHeight / 2);
|
||||
if (this.textInput.Length > 0 || this.IsSelected) {
|
||||
this.textInput.Draw(batch, textPos, this.Scale, this.IsSelected ? this.CaretWidth : 0, this.TextColor.OrDefault(Color.White) * alpha);
|
||||
var textPos = this.DisplayArea.Location + offset;
|
||||
if (this.text.Length > 0 || this.IsSelected) {
|
||||
var textColor = this.TextColor.OrDefault(Color.White);
|
||||
this.Font.Value.DrawString(batch, this.displayedText, textPos, textColor * alpha, 0, Vector2.Zero, this.TextScale * this.Scale, SpriteEffects.None, 0);
|
||||
|
||||
if (this.IsSelected && this.caretBlinkTimer < 0.5F) {
|
||||
var caretDrawPos = textPos + new Vector2(this.caretDrawOffset * this.TextScale * this.Scale, 0);
|
||||
if (this.Multiline)
|
||||
caretDrawPos.Y += this.Font.Value.LineHeight * (this.CaretLine - this.lineOffset) * this.TextScale * this.Scale;
|
||||
batch.Draw(batch.GetBlankTexture(), new RectangleF(caretDrawPos, new Vector2(this.CaretWidth * this.Scale, lineHeight)), null, textColor * alpha);
|
||||
}
|
||||
} else if (this.PlaceholderText != null) {
|
||||
this.Font.Value.DrawString(batch, this.PlaceholderText, textPos, this.PlaceholderColor.OrDefault(Color.Gray) * alpha, 0, Vector2.Zero, this.TextScale * this.Scale, SpriteEffects.None, 0);
|
||||
}
|
||||
base.Draw(time, batch, alpha, context);
|
||||
}
|
||||
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TextInput.SetText"/>
|
||||
/// <summary>
|
||||
/// Replaces this text field's text with the given text.
|
||||
/// If the resulting <see cref="Text"/> exceeds <see cref="MaximumCharacters"/>, the end will be cropped to fit.
|
||||
/// </summary>
|
||||
/// <param name="text">The new text</param>
|
||||
/// <param name="removeMismatching">If any characters that don't match the <see cref="InputRule"/> should be left out</param>
|
||||
public void SetText(object text, bool removeMismatching = false) {
|
||||
this.textInput.SetText(text, removeMismatching);
|
||||
var strg = text?.ToString() ?? string.Empty;
|
||||
if (!this.FilterText(ref strg, removeMismatching))
|
||||
return;
|
||||
if (this.MaximumCharacters != null && strg.Length > this.MaximumCharacters)
|
||||
strg = strg.Substring(0, this.MaximumCharacters.Value);
|
||||
this.text.Clear();
|
||||
this.text.Append(strg);
|
||||
this.CaretPos = this.text.Length;
|
||||
this.HandleTextChange();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TextInput.InsertText"/>
|
||||
/// <summary>
|
||||
/// Inserts the given text at the <see cref="CaretPos"/>.
|
||||
/// If the resulting <see cref="Text"/> exceeds <see cref="MaximumCharacters"/>, the end will be cropped to fit.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to insert</param>
|
||||
/// <param name="removeMismatching">If any characters that don't match the <see cref="InputRule"/> should be left out</param>
|
||||
public void InsertText(object text, bool removeMismatching = false) {
|
||||
this.textInput.InsertText(text, removeMismatching);
|
||||
var strg = text?.ToString() ?? string.Empty;
|
||||
if (!this.FilterText(ref strg, removeMismatching))
|
||||
return;
|
||||
if (this.MaximumCharacters != null && this.text.Length + strg.Length > this.MaximumCharacters)
|
||||
strg = strg.Substring(0, this.MaximumCharacters.Value - this.text.Length);
|
||||
this.text.Insert(this.CaretPos, strg);
|
||||
this.CaretPos += strg.Length;
|
||||
this.HandleTextChange();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TextInput.RemoveText"/>
|
||||
/// <summary>
|
||||
/// Removes the given amount of text at the given index
|
||||
/// </summary>
|
||||
/// <param name="index">The index</param>
|
||||
/// <param name="length">The amount of text to remove</param>
|
||||
public void RemoveText(int index, int length) {
|
||||
this.textInput.RemoveText(index, length);
|
||||
if (index < 0 || index >= this.text.Length)
|
||||
return;
|
||||
this.text.Remove(index, length);
|
||||
// ensure that caret pos is still in bounds
|
||||
this.CaretPos = this.CaretPos;
|
||||
this.HandleTextChange();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the <see cref="CaretPos"/> to the given line, if it exists.
|
||||
/// Additionally maintains the <see cref="CaretPosInLine"/> roughly based on the visual distance that the caret has from the left border of the current <see cref="CaretLine"/>.
|
||||
/// </summary>
|
||||
/// <param name="line">The line to move the caret to</param>
|
||||
/// <returns>True if the caret was moved, false if it was not (which indicates that the line with the given <paramref name="line"/> index does not exist)</returns>
|
||||
public bool MoveCaretToLine(int line) {
|
||||
var (destStart, destEnd) = this.GetLineBounds(line);
|
||||
if (destEnd > 0) {
|
||||
// find the position whose distance from the start is closest to the current distance from the start
|
||||
var destAccum = "";
|
||||
while (destAccum.Length < destEnd - destStart) {
|
||||
if (this.Font.Value.MeasureString(destAccum).X >= this.caretDrawOffset) {
|
||||
this.CaretPos = destStart + destAccum.Length;
|
||||
return true;
|
||||
}
|
||||
destAccum += this.text[destStart + destAccum.Length];
|
||||
}
|
||||
// if we don't find a proper position, just move to the end of the destination line
|
||||
this.CaretPos = destEnd;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -235,6 +403,154 @@ namespace MLEM.Ui.Elements {
|
|||
this.CaretWidth = this.CaretWidth.OrStyle(style.TextFieldCaretWidth);
|
||||
}
|
||||
|
||||
private bool FilterText(ref string text, bool removeMismatching) {
|
||||
if (removeMismatching) {
|
||||
var result = new StringBuilder();
|
||||
foreach (var c in text) {
|
||||
if (this.InputRule(this, c.ToCachedString()))
|
||||
result.Append(c);
|
||||
}
|
||||
text = result.ToString();
|
||||
} else if (!this.InputRule(this, text))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void HandleTextChange(bool textChanged = true) {
|
||||
// not initialized yet
|
||||
if (!this.Font.HasValue())
|
||||
return;
|
||||
var maxWidth = this.DisplayArea.Width / this.Scale - this.TextOffsetX * 2;
|
||||
if (this.Multiline) {
|
||||
// soft wrap if we're multiline
|
||||
this.splitText = this.Font.Value.SplitStringSeparate(this.text, maxWidth, this.TextScale).ToArray();
|
||||
this.displayedText = string.Join("\n", this.splitText);
|
||||
this.UpdateCaretData();
|
||||
|
||||
var maxHeight = this.DisplayArea.Height / this.Scale - this.TextOffsetX * 2;
|
||||
if (this.Font.Value.MeasureString(this.displayedText).Y * this.TextScale > maxHeight) {
|
||||
var maxLines = (maxHeight / (this.Font.Value.LineHeight * this.TextScale)).Floor();
|
||||
if (this.lineOffset > this.CaretLine) {
|
||||
// if we're moving up
|
||||
this.lineOffset = this.CaretLine;
|
||||
} else if (this.CaretLine >= maxLines) {
|
||||
// if we're moving down
|
||||
var limit = this.CaretLine - (maxLines - 1);
|
||||
if (limit > this.lineOffset)
|
||||
this.lineOffset = limit;
|
||||
}
|
||||
// calculate resulting string
|
||||
var ret = new StringBuilder();
|
||||
var lines = 0;
|
||||
var originalIndex = 0;
|
||||
for (var i = 0; i < this.displayedText.Length; i++) {
|
||||
if (lines >= this.lineOffset) {
|
||||
if (ret.Length <= 0)
|
||||
this.textOffset = originalIndex;
|
||||
ret.Append(this.displayedText[i]);
|
||||
}
|
||||
if (this.displayedText[i] == '\n') {
|
||||
lines++;
|
||||
if (this.text[originalIndex] == '\n')
|
||||
originalIndex++;
|
||||
} else {
|
||||
originalIndex++;
|
||||
}
|
||||
if (lines - this.lineOffset >= maxLines)
|
||||
break;
|
||||
}
|
||||
this.displayedText = ret.ToString();
|
||||
} else {
|
||||
this.lineOffset = 0;
|
||||
this.textOffset = 0;
|
||||
}
|
||||
} else {
|
||||
// not multiline, so scroll horizontally based on caret position
|
||||
if (this.Font.Value.MeasureString(this.text).X * this.TextScale > maxWidth) {
|
||||
if (this.textOffset > this.CaretPos) {
|
||||
// if we're moving the caret to the left
|
||||
this.textOffset = this.CaretPos;
|
||||
} else {
|
||||
// if we're moving the caret to the right
|
||||
var importantArea = this.text.ToString(this.textOffset, Math.Min(this.CaretPos, this.text.Length) - this.textOffset);
|
||||
var bound = this.CaretPos - this.Font.Value.TruncateString(importantArea, maxWidth, this.TextScale, true).Length;
|
||||
if (this.textOffset < bound)
|
||||
this.textOffset = bound;
|
||||
}
|
||||
var visible = this.text.ToString(this.textOffset, this.text.Length - this.textOffset);
|
||||
this.displayedText = this.Font.Value.TruncateString(visible, maxWidth, this.TextScale);
|
||||
} else {
|
||||
this.displayedText = this.Text;
|
||||
this.textOffset = 0;
|
||||
}
|
||||
this.UpdateCaretData();
|
||||
}
|
||||
|
||||
if (this.MaskingCharacter != null)
|
||||
this.displayedText = new string(this.MaskingCharacter.Value, this.displayedText.Length);
|
||||
|
||||
if (textChanged)
|
||||
this.OnTextChange?.Invoke(this, this.Text);
|
||||
}
|
||||
|
||||
private void UpdateCaretData() {
|
||||
if (this.splitText != null) {
|
||||
var line = 0;
|
||||
var index = 0;
|
||||
for (var d = 0; d < this.splitText.Length; d++) {
|
||||
var startOfLine = 0;
|
||||
var split = this.splitText[d];
|
||||
for (var i = 0; i <= split.Length; i++) {
|
||||
if (index == this.CaretPos) {
|
||||
this.CaretLine = line;
|
||||
this.CaretPosInLine = i - startOfLine;
|
||||
this.caretDrawOffset = this.Font.Value.MeasureString(split.Substring(startOfLine, this.CaretPosInLine)).X;
|
||||
return;
|
||||
}
|
||||
if (i < split.Length) {
|
||||
// manual splits
|
||||
if (split[i] == '\n') {
|
||||
startOfLine = i + 1;
|
||||
line++;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
// max width splits
|
||||
line++;
|
||||
}
|
||||
} else if (this.displayedText != null) {
|
||||
this.CaretLine = 0;
|
||||
this.CaretPosInLine = this.CaretPos;
|
||||
this.caretDrawOffset = this.Font.Value.MeasureString(this.displayedText.Substring(0, this.CaretPos - this.textOffset)).X;
|
||||
}
|
||||
}
|
||||
|
||||
private (int, int) GetLineBounds(int boundLine) {
|
||||
if (this.splitText != null) {
|
||||
var line = 0;
|
||||
var index = 0;
|
||||
var startOfLineIndex = 0;
|
||||
for (var d = 0; d < this.splitText.Length; d++) {
|
||||
var split = this.splitText[d];
|
||||
for (var i = 0; i < split.Length; i++) {
|
||||
index++;
|
||||
if (split[i] == '\n') {
|
||||
if (boundLine == line)
|
||||
return (startOfLineIndex, index - 1);
|
||||
line++;
|
||||
startOfLineIndex = index;
|
||||
}
|
||||
}
|
||||
if (boundLine == line)
|
||||
return (startOfLineIndex, index - 1);
|
||||
line++;
|
||||
startOfLineIndex = index;
|
||||
}
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate method used for <see cref="TextField.OnTextChange"/>
|
||||
/// </summary>
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MLEM.Extensions;
|
||||
using MLEM.Input;
|
||||
using MLEM.Ui.Style;
|
||||
|
||||
|
@ -13,13 +11,6 @@ namespace MLEM.Ui.Elements {
|
|||
/// </summary>
|
||||
public class Tooltip : Panel {
|
||||
|
||||
/// <summary>
|
||||
/// A list of <see cref="Elements.Paragraph"/> objects that this tooltip automatically manages.
|
||||
/// A paragraph that is contained in this list will automatically have the <see cref="UiStyle.TooltipTextWidth"/> and <see cref="UiStyle.TooltipTextColor"/> applied.
|
||||
/// To add a paragraph to both this list and to <see cref="Element.Children"/>, use <see cref="AddParagraph(Elements.Paragraph,int)"/>.
|
||||
/// </summary>
|
||||
public readonly List<Paragraph> Paragraphs = new List<Paragraph>();
|
||||
|
||||
/// <summary>
|
||||
/// The offset that this tooltip's top left corner should have from the mouse position
|
||||
/// </summary>
|
||||
|
@ -32,41 +23,9 @@ namespace MLEM.Ui.Elements {
|
|||
/// The amount of time that the mouse has to be over an element before it appears
|
||||
/// </summary>
|
||||
public StyleProp<TimeSpan> Delay;
|
||||
/// <summary>
|
||||
/// The <see cref="Elements.Paragraph.TextColor"/> that this tooltip's <see cref="Paragraphs"/> should have
|
||||
/// </summary>
|
||||
public StyleProp<Color> ParagraphTextColor {
|
||||
get => this.paragraphTextColor;
|
||||
set {
|
||||
this.paragraphTextColor = value;
|
||||
this.UpdateParagraphsStyles();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The <see cref="Elements.Paragraph.TextScale"/> that this tooltip's <see cref="Paragraphs"/> should have
|
||||
/// </summary>
|
||||
public StyleProp<float> ParagraphTextScale {
|
||||
get => this.paragraphTextScale;
|
||||
set {
|
||||
this.paragraphTextScale = value;
|
||||
this.UpdateParagraphsStyles();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The width that this tooltip's <see cref="Paragraphs"/> should have
|
||||
/// </summary>
|
||||
public StyleProp<float> ParagraphWidth {
|
||||
get => this.paragraphWidth;
|
||||
set {
|
||||
this.paragraphWidth = value;
|
||||
this.UpdateParagraphsStyles();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The paragraph of text that this tooltip displays
|
||||
/// </summary>
|
||||
[Obsolete("Use Paragraphs instead, which allows for multiple paragraphs to be managed by one tooltip")]
|
||||
public Paragraph Paragraph;
|
||||
/// <summary>
|
||||
/// Determines whether this tooltip should display when <see cref="UiControls.IsAutoNavMode"/> is true, which is when the UI is being controlled using a keyboard or gamepad.
|
||||
|
@ -86,9 +45,6 @@ namespace MLEM.Ui.Elements {
|
|||
private TimeSpan delayCountdown;
|
||||
private bool autoHidden;
|
||||
private Element snapElement;
|
||||
private StyleProp<float> paragraphWidth;
|
||||
private StyleProp<float> paragraphTextScale;
|
||||
private StyleProp<Color> paragraphTextColor;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new tooltip with the given settings
|
||||
|
@ -97,11 +53,8 @@ namespace MLEM.Ui.Elements {
|
|||
/// <param name="elementToHover">The element that should automatically cause the tooltip to appear and disappear when hovered and not hovered, respectively</param>
|
||||
public Tooltip(string text = null, Element elementToHover = null) :
|
||||
base(Anchor.TopLeft, Vector2.One, Vector2.Zero) {
|
||||
if (text != null) {
|
||||
#pragma warning disable CS0618
|
||||
this.Paragraph = this.AddParagraph(text);
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
if (text != null)
|
||||
this.Paragraph = this.AddChild(new Paragraph(Anchor.TopLeft, 0, text));
|
||||
this.Init(elementToHover);
|
||||
}
|
||||
|
||||
|
@ -112,9 +65,7 @@ namespace MLEM.Ui.Elements {
|
|||
/// <param name="elementToHover">The element that should automatically cause the tooltip to appear and disappear when hovered and not hovered, respectively</param>
|
||||
public Tooltip(Paragraph.TextCallback textCallback, Element elementToHover = null) :
|
||||
base(Anchor.TopLeft, Vector2.One, Vector2.Zero) {
|
||||
#pragma warning disable CS0618
|
||||
this.Paragraph = this.AddParagraph(textCallback);
|
||||
#pragma warning restore CS0618
|
||||
this.Paragraph = this.AddChild(new Paragraph(Anchor.TopLeft, 0, textCallback));
|
||||
this.Init(elementToHover);
|
||||
}
|
||||
|
||||
|
@ -150,47 +101,11 @@ namespace MLEM.Ui.Elements {
|
|||
this.MouseOffset = this.MouseOffset.OrStyle(style.TooltipOffset);
|
||||
this.AutoNavOffset = this.AutoNavOffset.OrStyle(style.TooltipAutoNavOffset);
|
||||
this.Delay = this.Delay.OrStyle(style.TooltipDelay);
|
||||
this.ParagraphTextColor = this.ParagraphTextColor.OrStyle(style.TooltipTextColor);
|
||||
this.ParagraphTextScale = this.ParagraphTextScale.OrStyle(style.TextScale);
|
||||
this.ParagraphWidth = this.ParagraphWidth.OrStyle(style.TooltipTextWidth);
|
||||
this.ChildPadding = this.ChildPadding.OrStyle(style.TooltipChildPadding);
|
||||
this.UpdateParagraphsStyles();
|
||||
if (this.Paragraph != null) {
|
||||
this.Paragraph.TextColor = this.Paragraph.TextColor.OrStyle(style.TooltipTextColor, 1);
|
||||
this.Paragraph.Size = new Vector2(style.TooltipTextWidth, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given paragraph to this tooltip's managed <see cref="Paragraphs"/> list, as well as to its children using <see cref="Element.AddChild{T}"/>.
|
||||
/// A paragraph that is contained in the <see cref="Paragraphs"/> list will automatically have the <see cref="UiStyle.TooltipTextWidth"/> and <see cref="UiStyle.TooltipTextColor"/> applied.
|
||||
/// </summary>
|
||||
/// <param name="paragraph">The paragraph to add</param>
|
||||
/// <returns>The added paragraph, for chaining</returns>
|
||||
/// <param name="index">The index to add the child at, or -1 to add it to the end of the <see cref="Element.Children"/> list</param>
|
||||
public Paragraph AddParagraph(Paragraph paragraph, int index = -1) {
|
||||
this.Paragraphs.Add(paragraph);
|
||||
this.AddChild(paragraph, index);
|
||||
this.UpdateParagraphStyle(paragraph);
|
||||
return paragraph;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new paragraph with the given text callback to this tooltip's managed <see cref="Paragraphs"/> list, as well as to its children using <see cref="Element.AddChild{T}"/>.
|
||||
/// A paragraph that is contained in the <see cref="Paragraphs"/> list will automatically have the <see cref="UiStyle.TooltipTextWidth"/> and <see cref="UiStyle.TooltipTextColor"/> applied.
|
||||
/// </summary>
|
||||
/// <param name="text">The text that the paragraph should display</param>
|
||||
/// <returns>The created paragraph, for chaining</returns>
|
||||
/// <param name="index">The index to add the child at, or -1 to add it to the end of the <see cref="Element.Children"/> list</param>
|
||||
public Paragraph AddParagraph(Paragraph.TextCallback text, int index = -1) {
|
||||
return this.AddParagraph(new Paragraph(Anchor.AutoLeft, 0, text), index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new paragraph with the given text to this tooltip's managed <see cref="Paragraphs"/> list, as well as to its children using <see cref="Element.AddChild{T}"/>.
|
||||
/// A paragraph that is contained in the <see cref="Paragraphs"/> list will automatically have the <see cref="UiStyle.TooltipTextWidth"/> and <see cref="UiStyle.TooltipTextColor"/> applied.
|
||||
/// </summary>
|
||||
/// <param name="text">The text that the paragraph should display</param>
|
||||
/// <returns>The created paragraph, for chaining</returns>
|
||||
/// <param name="index">The index to add the child at, or -1 to add it to the end of the <see cref="Element.Children"/> list</param>
|
||||
public Paragraph AddParagraph(string text, int index = -1) {
|
||||
return this.AddParagraph(p => text, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -256,7 +171,7 @@ namespace MLEM.Ui.Elements {
|
|||
elementToHover.OnMouseEnter += e => this.Display(e.System, $"{e.GetType().Name}Tooltip");
|
||||
elementToHover.OnMouseExit += e => this.Remove();
|
||||
elementToHover.OnSelected += e => {
|
||||
if (this.DisplayInAutoNavMode && e.Controls.IsAutoNavMode) {
|
||||
if (this.DisplayInAutoNavMode) {
|
||||
this.snapElement = e;
|
||||
this.Display(e.System, $"{e.GetType().Name}Tooltip");
|
||||
}
|
||||
|
@ -270,6 +185,9 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
private void Init(Element elementToHover) {
|
||||
if (this.Paragraph != null)
|
||||
this.Paragraph.AutoAdjustWidth = true;
|
||||
|
||||
this.SetWidthBasedOnChildren = true;
|
||||
this.SetHeightBasedOnChildren = true;
|
||||
this.CanBeMoused = false;
|
||||
|
@ -292,23 +210,5 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
}
|
||||
|
||||
private void UpdateParagraphsStyles() {
|
||||
foreach (var paragraph in this.Paragraphs)
|
||||
this.UpdateParagraphStyle(paragraph);
|
||||
|
||||
#pragma warning disable CS0618
|
||||
// still set style here in case someone changed the paragraph field manually
|
||||
if (this.Paragraph != null)
|
||||
this.UpdateParagraphStyle(this.Paragraph);
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
private void UpdateParagraphStyle(Paragraph paragraph) {
|
||||
paragraph.TextColor = paragraph.TextColor.OrStyle(this.ParagraphTextColor, 1);
|
||||
paragraph.TextScale = paragraph.TextScale.OrStyle(this.ParagraphTextScale, 1);
|
||||
paragraph.Size = new Vector2(this.ParagraphWidth, 0);
|
||||
paragraph.AutoAdjustWidth = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
<RootNamespace>MLEM.Ui</RootNamespace>
|
||||
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Authors>Ellpeck</Authors>
|
||||
<Description>A mouse, keyboard, gamepad and touch ready Ui system for FNA that features automatic anchoring, sizing and several ready-to-use element types</Description>
|
||||
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes>
|
||||
<PackageTags>fna ellpeck mlem ui user interface graphical gui system mouse keyboard gamepad touch</PackageTags>
|
||||
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TextCopy" Version="6.1.0" />
|
||||
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
|
||||
|
||||
<ProjectReference Include="..\FNA\FNA.Core.csproj">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
|
||||
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<Authors>Ellpeck</Authors>
|
||||
<Description>A mouse, keyboard, gamepad and touch ready Ui system for MonoGame that features automatic anchoring, sizing and several ready-to-use element types</Description>
|
||||
<Description>A mouse, keyboard, gamepad and touch ready Ui system that features automatic anchoring, sizing and several ready-to-use element types</Description>
|
||||
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes>
|
||||
<PackageTags>monogame ellpeck mlem ui user interface graphical gui system mouse keyboard gamepad touch</PackageTags>
|
||||
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
|
||||
|
@ -18,7 +18,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TextCopy" Version="6.1.0" />
|
||||
<PackageReference Include="TextCopy" Version="4.3.1" />
|
||||
<ProjectReference Include="..\MLEM\MLEM.csproj" />
|
||||
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
|
||||
|
|
|
@ -1,298 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Formatting;
|
||||
using MLEM.Misc;
|
||||
using MLEM.Textures;
|
||||
using MLEM.Ui.Elements;
|
||||
using MLEM.Ui.Style;
|
||||
|
||||
namespace MLEM.Ui.Parsers {
|
||||
/// <summary>
|
||||
/// A class for parsing Markdown strings into a set of MLEM.Ui elements with styling for each individual <see cref="ElementType"/>.
|
||||
/// To parse, use <see cref="Parse"/> or <see cref="ParseInto"/>. To style the parsed output, use <see cref="Style{T}"/> before parsing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this parser is rather rudimentary and doesn't deal well with very complex Markdown documents. Missing features are as follows:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Lines that end without a double space are still converted to distinct lines rather than being merged with the next line</description></item>
|
||||
/// <item><description>Better list handling, including nested lists</description></item>
|
||||
/// <item><description>Horizontal rules</description></item>
|
||||
/// <item><description>Tables</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public class UiMarkdownParser {
|
||||
|
||||
private static readonly ElementType[] ElementTypes = EnumHelper.GetValues<ElementType>().ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// The base path for markdown images, which is prepended to the image link.
|
||||
/// </summary>
|
||||
public string ImageBasePath;
|
||||
/// <summary>
|
||||
/// An action that is invoked when an image fails to load while parsing.
|
||||
/// This action receives the expected location of the image, as well as the <see cref="Exception"/> that occured.
|
||||
/// </summary>
|
||||
public Action<string, Exception> ImageExceptionHandler;
|
||||
/// <summary>
|
||||
/// The graphics device that should be used when loading images and other graphics-dependent content.
|
||||
/// </summary>
|
||||
public GraphicsDevice GraphicsDevice;
|
||||
/// <summary>
|
||||
/// The name of the font used for inline code as well as code blocks.
|
||||
/// This only has an effect if a font with this name is added to the used <see cref="UiStyle"/>'s <see cref="UiStyle.AdditionalFonts"/>.
|
||||
/// This defaults to "Monospaced" if default styling is applied in <see cref="UiMarkdownParser(bool)"/>.
|
||||
/// </summary>
|
||||
public string CodeFont;
|
||||
|
||||
private readonly Dictionary<ElementType, Action<Element>> elementStyles = new Dictionary<ElementType, Action<Element>>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new UI markdown parser and optionally initializes some default style settings.
|
||||
/// </summary>
|
||||
/// <param name="applyDefaultStyling">Whether default style settings should be applied.</param>
|
||||
public UiMarkdownParser(bool applyDefaultStyling = true) {
|
||||
if (applyDefaultStyling) {
|
||||
this.CodeFont = "Monospaced";
|
||||
this.Style<VerticalSpace>(ElementType.VerticalSpace, v => v.Size = new Vector2(1, 5));
|
||||
for (var i = 0; i < 6; i++) {
|
||||
var level = i;
|
||||
this.Style<Paragraph>(UiMarkdownParser.ElementTypes[Array.IndexOf(UiMarkdownParser.ElementTypes, ElementType.Header1) + i], p => {
|
||||
p.Alignment = TextAlignment.Center;
|
||||
p.TextScaleMultiplier = 2 - level * 0.15F;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the given markdown string into a set of elements (using <see cref="Parse"/>) and adds them as children to the givem <paramref name="element"/>.
|
||||
/// During this process, the element stylings specified using <see cref="Style{T}"/> are also applied.
|
||||
/// </summary>
|
||||
/// <param name="markdown">The markdown to parse.</param>
|
||||
/// <param name="element">The element to add the parsed elements to.</param>
|
||||
/// <returns>The <paramref name="element"/>, for chaining.</returns>
|
||||
public Element ParseInto(string markdown, Element element) {
|
||||
foreach (var (_, e) in this.Parse(markdown))
|
||||
element.AddChild(e);
|
||||
return element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the given markdown string into a set of elements and returns them along with their <see cref="ElementType"/>.
|
||||
/// During this process, the element stylings specified using <see cref="Style{T}"/> are also applied.
|
||||
/// </summary>
|
||||
/// <param name="markdown">The markdown to parse.</param>
|
||||
/// <returns>The parsed elements.</returns>
|
||||
public IEnumerable<(ElementType, Element)> Parse(string markdown) {
|
||||
foreach (var (t, e) in this.ParseUnstyled(markdown)) {
|
||||
if (this.elementStyles.TryGetValue(t, out var style))
|
||||
style.Invoke(e);
|
||||
yield return (t, e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </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>
|
||||
/// <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 UiMarkdownParser Style<T>(ElementType types, Action<T> style, bool add = false) where T : Element {
|
||||
foreach (var type in UiMarkdownParser.ElementTypes) {
|
||||
if (types.HasFlag(type)) {
|
||||
if (add && this.elementStyles.ContainsKey(type)) {
|
||||
this.elementStyles[type] += Action;
|
||||
} else {
|
||||
this.elementStyles[type] = Action;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
|
||||
void Action(Element e) {
|
||||
style.Invoke(e as T ?? throw new ArgumentException($"Expected {typeof(T)} for style action but got {e.GetType()}"));
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<(ElementType, Element)> ParseUnstyled(string markdown) {
|
||||
var inCodeBlock = false;
|
||||
foreach (var line in markdown.Split('\n')) {
|
||||
// code blocks
|
||||
if (line.Trim().StartsWith("```")) {
|
||||
inCodeBlock = !inCodeBlock;
|
||||
continue;
|
||||
}
|
||||
// code block content
|
||||
if (inCodeBlock) {
|
||||
yield return (ElementType.CodeBlock, new Paragraph(Anchor.AutoLeft, 1, $"<f {this.CodeFont}>{line}</f>"));
|
||||
continue;
|
||||
}
|
||||
|
||||
// quotes
|
||||
if (line.StartsWith(">")) {
|
||||
yield return (ElementType.Blockquote, new Paragraph(Anchor.AutoLeft, 1, line.Substring(1).Trim()));
|
||||
continue;
|
||||
}
|
||||
|
||||
// vertical space (empty lines)
|
||||
if (line.Trim().Length <= 0) {
|
||||
yield return (ElementType.VerticalSpace, new VerticalSpace(0));
|
||||
continue;
|
||||
}
|
||||
|
||||
// images
|
||||
var imageMatch = Regex.Match(line, @"!\[\]\(([^)]+)\)");
|
||||
if (imageMatch.Success) {
|
||||
if (this.GraphicsDevice == null)
|
||||
throw new NullReferenceException("A markdown parser requires a GraphicsDevice for parsing images");
|
||||
|
||||
TextureRegion image = null;
|
||||
LoadImageAsync();
|
||||
yield return (ElementType.Image, new Image(Anchor.AutoLeft, new Vector2(1, -1), _ => image) {
|
||||
OnDisposed = e => image?.Texture.Dispose()
|
||||
});
|
||||
|
||||
async void LoadImageAsync() {
|
||||
var loc = imageMatch.Groups[1].Value;
|
||||
// only apply the base path for relative files
|
||||
if (this.ImageBasePath != null && !loc.StartsWith("http") && !Path.IsPathRooted(loc))
|
||||
loc = $"{this.ImageBasePath}/{loc}";
|
||||
try {
|
||||
Texture2D tex;
|
||||
if (loc.StartsWith("http")) {
|
||||
using (var client = new HttpClient()) {
|
||||
using (var src = await client.GetStreamAsync(loc)) {
|
||||
using (var memory = new MemoryStream()) {
|
||||
// download the full stream before passing it to texture
|
||||
await src.CopyToAsync(memory);
|
||||
tex = Texture2D.FromStream(this.GraphicsDevice, memory);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
using (var stream = Path.IsPathRooted(loc) ? File.OpenRead(loc) : TitleContainer.OpenStream(loc))
|
||||
tex = Texture2D.FromStream(this.GraphicsDevice, stream);
|
||||
}
|
||||
image = new TextureRegion(tex);
|
||||
} catch (Exception e) {
|
||||
if (this.ImageExceptionHandler != null) {
|
||||
this.ImageExceptionHandler.Invoke(loc, e);
|
||||
} else {
|
||||
throw new Exception($"Couldn't parse image {loc}, and no ImageExceptionHandler was set", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// headers
|
||||
var parsedHeader = false;
|
||||
for (var h = 6; h >= 1; h--) {
|
||||
if (line.StartsWith(new string('#', h))) {
|
||||
var type = UiMarkdownParser.ElementTypes[Array.IndexOf(UiMarkdownParser.ElementTypes, ElementType.Header1) + h - 1];
|
||||
yield return (type, new Paragraph(Anchor.AutoLeft, 1, line.Substring(h).Trim()));
|
||||
parsedHeader = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (parsedHeader)
|
||||
continue;
|
||||
|
||||
// parse everything else as a paragraph (with formatting)
|
||||
var par = line;
|
||||
// replace links
|
||||
par = Regex.Replace(par, @"<([^>]+)>", "<l $1>$1</l>");
|
||||
par = Regex.Replace(par, @"\[([^\]]+)\]\(([^)]+)\)", "<l $2>$1</l>");
|
||||
// replace formatting
|
||||
par = Regex.Replace(par, @"\*\*([^\*]+)\*\*", "<b>$1</b>");
|
||||
par = Regex.Replace(par, @"__([^_]+)__", "<b>$1</b>");
|
||||
par = Regex.Replace(par, @"\*([^\*]+)\*", "<i>$1</i>");
|
||||
par = Regex.Replace(par, @"_([^_]+)_", "<i>$1</i>");
|
||||
par = Regex.Replace(par, @"~~([^~]+)~~", "<st>$1</st>");
|
||||
// replace inline code with custom code font
|
||||
par = Regex.Replace(par, @"`([^`]+)`", $"<f {this.CodeFont}>$1</f>");
|
||||
yield return (ElementType.Paragraph, new Paragraph(Anchor.AutoLeft, 1, par));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A flags enumeration used by <see cref="UiMarkdownParser"/> that contains the types of elements that can be parsed and returned in <see cref="Parse"/> or <see cref="UiMarkdownParser.ParseInto"/>.
|
||||
/// This is a flags enumeration so that <see cref="UiMarkdownParser.Style{T}"/> can have multiple element types being styled at the same time.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ElementType {
|
||||
|
||||
/// <summary>
|
||||
/// A blockquote.
|
||||
/// This element type is a <see cref="Paragraph"/>.
|
||||
/// </summary>
|
||||
Blockquote = 1,
|
||||
/// <summary>
|
||||
/// A vertical space, which is a gap between multiple markdown paragraphs.
|
||||
/// This element type is a <see cref="VerticalSpace"/>.
|
||||
/// </summary>
|
||||
VerticalSpace = 2,
|
||||
/// <summary>
|
||||
/// An image.
|
||||
/// This element type is an <see cref="Image"/>.
|
||||
/// </summary>
|
||||
Image = 4,
|
||||
/// <summary>
|
||||
/// A header with header level 1.
|
||||
/// This element type is a <see cref="Paragraph"/>.
|
||||
/// </summary>
|
||||
Header1 = 8,
|
||||
/// <summary>
|
||||
/// A header with header level 2.
|
||||
/// This element type is a <see cref="Paragraph"/>.
|
||||
/// </summary>
|
||||
Header2 = 16,
|
||||
/// <summary>
|
||||
/// A header with header level 3.
|
||||
/// This element type is a <see cref="Paragraph"/>.
|
||||
/// </summary>
|
||||
Header3 = 32,
|
||||
/// <summary>
|
||||
/// A header with header level 4.
|
||||
/// This element type is a <see cref="Paragraph"/>.
|
||||
/// </summary>
|
||||
Header4 = 64,
|
||||
/// <summary>
|
||||
/// A header with header level 5.
|
||||
/// This element type is a <see cref="Paragraph"/>.
|
||||
/// </summary>
|
||||
Header5 = 128,
|
||||
/// <summary>
|
||||
/// A header with header level 6.
|
||||
/// This element type is a <see cref="Paragraph"/>.
|
||||
/// </summary>
|
||||
Header6 = 256,
|
||||
/// <summary>
|
||||
/// A combined flag that contains <see cref="Header1"/> through <see cref="Header6"/>.
|
||||
/// This element type is a <see cref="Paragraph"/>.
|
||||
/// </summary>
|
||||
Headers = ElementType.Header1 | ElementType.Header2 | ElementType.Header3 | ElementType.Header4 | ElementType.Header5 | ElementType.Header6,
|
||||
/// <summary>
|
||||
/// A paragraph, which is one line of markdown text.
|
||||
/// This element type is a <see cref="Paragraph"/>.
|
||||
/// </summary>
|
||||
Paragraph = 512,
|
||||
/// <summary>
|
||||
/// A single line of a code block.
|
||||
/// This element type is a <see cref="Paragraph"/>.
|
||||
/// </summary>
|
||||
CodeBlock = 1024
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -57,7 +57,7 @@ namespace MLEM.Ui {
|
|||
|
||||
/// <summary>
|
||||
/// The <see cref="RootElement"/> that is currently active.
|
||||
/// The active root element is the one with the highest <see cref="RootElement.Priority"/> that <see cref="RootElement.CanBeActive"/>.
|
||||
/// The active root element is the one with the highest <see cref="RootElement.Priority"/> that whose <see cref="RootElement.CanSelectContent"/> property is true.
|
||||
/// </summary>
|
||||
public RootElement ActiveRoot { get; protected set; }
|
||||
/// <summary>
|
||||
|
@ -155,33 +155,29 @@ namespace MLEM.Ui {
|
|||
public virtual void Update() {
|
||||
if (this.IsInputOurs)
|
||||
this.Input.Update();
|
||||
this.ActiveRoot = this.System.GetRootElements().FirstOrDefault(root => root.CanBeActive);
|
||||
this.ActiveRoot = this.System.GetRootElements().FirstOrDefault(root => !root.Element.IsHidden && root.CanSelectContent);
|
||||
|
||||
// MOUSE INPUT
|
||||
if (this.HandleMouse) {
|
||||
var mousedNow = this.GetElementUnderPos(new Vector2(this.Input.ViewportMousePosition.X, this.Input.ViewportMousePosition.Y));
|
||||
var mousedNow = this.GetElementUnderPos(this.Input.ViewportMousePosition.ToVector2());
|
||||
this.SetMousedElement(mousedNow);
|
||||
|
||||
if (this.Input.IsMouseButtonPressedAvailable(MouseButton.Left)) {
|
||||
if (this.Input.IsMouseButtonPressed(MouseButton.Left)) {
|
||||
this.IsAutoNavMode = false;
|
||||
var selectedNow = mousedNow != null && mousedNow.CanBeSelected ? mousedNow : null;
|
||||
this.SelectElement(this.ActiveRoot, selectedNow);
|
||||
if (mousedNow != null && mousedNow.CanBePressed) {
|
||||
if (mousedNow != null && mousedNow.CanBePressed)
|
||||
this.System.InvokeOnElementPressed(mousedNow);
|
||||
this.Input.TryConsumeMouseButtonPressed(MouseButton.Left);
|
||||
}
|
||||
} else if (this.Input.IsMouseButtonPressedAvailable(MouseButton.Right)) {
|
||||
} else if (this.Input.IsMouseButtonPressed(MouseButton.Right)) {
|
||||
this.IsAutoNavMode = false;
|
||||
if (mousedNow != null && mousedNow.CanBePressed) {
|
||||
if (mousedNow != null && mousedNow.CanBePressed)
|
||||
this.System.InvokeOnElementSecondaryPressed(mousedNow);
|
||||
this.Input.TryConsumeMouseButtonPressed(MouseButton.Right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// KEYBOARD INPUT
|
||||
if (this.HandleKeyboard) {
|
||||
if (this.KeyboardButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
|
||||
if (this.KeyboardButtons.IsPressed(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
|
||||
|
@ -190,19 +186,15 @@ namespace MLEM.Ui {
|
|||
// first action on element using space or enter
|
||||
this.System.InvokeOnElementPressed(this.SelectedElement);
|
||||
}
|
||||
this.KeyboardButtons.TryConsumePressed(this.Input, this.GamepadIndex);
|
||||
}
|
||||
} else if (this.Input.IsKeyPressedAvailable(Keys.Tab)) {
|
||||
} else if (this.Input.IsKeyPressed(Keys.Tab)) {
|
||||
this.IsAutoNavMode = true;
|
||||
// tab or shift-tab to next or previous element
|
||||
var backward = this.Input.IsModifierKeyDown(ModifierKey.Shift);
|
||||
var next = this.GetTabNextElement(backward);
|
||||
if (this.SelectedElement?.Root != null)
|
||||
next = this.SelectedElement.GetTabNextElement(backward, next);
|
||||
if (next != this.SelectedElement) {
|
||||
this.SelectElement(this.ActiveRoot, next);
|
||||
this.Input.TryConsumeKeyPressed(Keys.Tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,28 +230,20 @@ namespace MLEM.Ui {
|
|||
|
||||
// GAMEPAD INPUT
|
||||
if (this.HandleGamepad) {
|
||||
if (this.GamepadButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
|
||||
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) {
|
||||
if (this.GamepadButtons.IsPressed(this.Input, this.GamepadIndex)) {
|
||||
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed)
|
||||
this.System.InvokeOnElementPressed(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) {
|
||||
} else if (this.SecondaryGamepadButtons.IsPressed(this.Input, this.GamepadIndex)) {
|
||||
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed)
|
||||
this.System.InvokeOnElementSecondaryPressed(this.SelectedElement);
|
||||
this.SecondaryGamepadButtons.TryConsumePressed(this.Input, this.GamepadIndex);
|
||||
}
|
||||
} else if (this.DownButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
|
||||
if (this.HandleGamepadNextElement(Direction2.Down))
|
||||
this.DownButtons.TryConsumePressed(this.Input, this.GamepadIndex);
|
||||
} else if (this.LeftButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
|
||||
if (this.HandleGamepadNextElement(Direction2.Left))
|
||||
this.LeftButtons.TryConsumePressed(this.Input, this.GamepadIndex);
|
||||
} else if (this.RightButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
|
||||
if (this.HandleGamepadNextElement(Direction2.Right))
|
||||
this.RightButtons.TryConsumePressed(this.Input, this.GamepadIndex);
|
||||
} else if (this.UpButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
|
||||
if (this.HandleGamepadNextElement(Direction2.Up))
|
||||
this.UpButtons.TryConsumePressed(this.Input, this.GamepadIndex);
|
||||
} else if (this.DownButtons.IsPressed(this.Input, this.GamepadIndex)) {
|
||||
this.HandleGamepadNextElement(Direction2.Down);
|
||||
} else if (this.LeftButtons.IsPressed(this.Input, this.GamepadIndex)) {
|
||||
this.HandleGamepadNextElement(Direction2.Left);
|
||||
} else if (this.RightButtons.IsPressed(this.Input, this.GamepadIndex)) {
|
||||
this.HandleGamepadNextElement(Direction2.Right);
|
||||
} else if (this.UpButtons.IsPressed(this.Input, this.GamepadIndex)) {
|
||||
this.HandleGamepadNextElement(Direction2.Up);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -355,6 +339,8 @@ namespace MLEM.Ui {
|
|||
if (root == null)
|
||||
return null;
|
||||
this.selectedElements.TryGetValue(root.Name, out var element);
|
||||
if (element != null && !element.CanBeSelected)
|
||||
return null;
|
||||
return element;
|
||||
}
|
||||
|
||||
|
@ -367,17 +353,15 @@ namespace MLEM.Ui {
|
|||
protected virtual Element GetTabNextElement(bool backward) {
|
||||
if (this.ActiveRoot == null)
|
||||
return null;
|
||||
var children = this.ActiveRoot.Element.GetChildren(c => !c.IsHidden, true, true).Append(this.ActiveRoot.Element)
|
||||
// we can't add these checks to GetChildren because it ignores false grandchildren
|
||||
.Where(c => c.CanBeSelected && c.AutoNavGroup == this.SelectedElement?.AutoNavGroup);
|
||||
var children = this.ActiveRoot.Element.GetChildren(c => !c.IsHidden, true, true).Append(this.ActiveRoot.Element);
|
||||
if (this.SelectedElement?.Root != this.ActiveRoot) {
|
||||
// if we don't have an element selected in this root, navigate to the first one without a group
|
||||
var allowed = children.Where(c => c.AutoNavGroup == null);
|
||||
return backward ? allowed.LastOrDefault() : allowed.FirstOrDefault();
|
||||
return backward ? children.LastOrDefault(c => c.CanBeSelected) : children.FirstOrDefault(c => c.CanBeSelected);
|
||||
} else {
|
||||
var foundCurr = false;
|
||||
Element lastFound = null;
|
||||
foreach (var child in children) {
|
||||
if (!child.CanBeSelected)
|
||||
continue;
|
||||
if (child == this.SelectedElement) {
|
||||
// when going backwards, return the last element found before the current one
|
||||
if (backward)
|
||||
|
@ -402,20 +386,17 @@ namespace MLEM.Ui {
|
|||
protected virtual Element GetGamepadNextElement(Direction2 direction) {
|
||||
if (this.ActiveRoot == null)
|
||||
return null;
|
||||
var children = this.ActiveRoot.Element.GetChildren(c => !c.IsHidden, true, true).Append(this.ActiveRoot.Element)
|
||||
// we can't add these checks to GetChildren because it ignores false grandchildren
|
||||
.Where(c => c.CanBeSelected && c.AutoNavGroup == this.SelectedElement?.AutoNavGroup);
|
||||
var children = this.ActiveRoot.Element.GetChildren(c => !c.IsHidden, true, true).Append(this.ActiveRoot.Element);
|
||||
if (this.SelectedElement?.Root != this.ActiveRoot) {
|
||||
// if we don't have an element selected in this root, navigate to the first one without a group
|
||||
return children.FirstOrDefault(c => c.AutoNavGroup == null);
|
||||
return children.FirstOrDefault(c => c.CanBeSelected);
|
||||
} else {
|
||||
Element closest = null;
|
||||
float closestPriority = 0;
|
||||
foreach (var child in children) {
|
||||
if (child == this.SelectedElement)
|
||||
if (!child.CanBeSelected || child == this.SelectedElement)
|
||||
continue;
|
||||
var offset = child.Area.Center - this.SelectedElement.Area.Center;
|
||||
var angle = Math.Abs(MathHelper.WrapAngle(direction.Angle() - (float) Math.Atan2(offset.Y, offset.X)));
|
||||
var (xOffset, yOffset) = child.Area.Center - this.SelectedElement.Area.Center;
|
||||
var angle = Math.Abs(direction.Angle() - (float) Math.Atan2(yOffset, xOffset));
|
||||
if (angle >= MathHelper.PiOver2 - Element.Epsilon)
|
||||
continue;
|
||||
var distSq = child.Area.DistanceSquared(this.SelectedElement.Area);
|
||||
|
@ -430,16 +411,13 @@ namespace MLEM.Ui {
|
|||
}
|
||||
}
|
||||
|
||||
private bool HandleGamepadNextElement(Direction2 dir) {
|
||||
private void HandleGamepadNextElement(Direction2 dir) {
|
||||
this.IsAutoNavMode = true;
|
||||
var next = this.GetGamepadNextElement(dir);
|
||||
if (this.SelectedElement != null)
|
||||
next = this.SelectedElement.GetGamepadNextElement(dir, next);
|
||||
if (next != null) {
|
||||
if (next != null)
|
||||
this.SelectElement(this.ActiveRoot, next);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ using MLEM.Ui.Elements;
|
|||
namespace MLEM.Ui {
|
||||
/// <summary>
|
||||
/// A snapshot of update and rendering statistics from <see cref="UiSystem.Metrics"/> to be used for runtime debugging and profiling.
|
||||
/// This metrics struct works similarly to <see cref="GraphicsMetrics"/>.
|
||||
/// </summary>
|
||||
public struct UiMetrics {
|
||||
|
||||
|
@ -32,12 +33,12 @@ namespace MLEM.Ui {
|
|||
public uint Updates { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time that <see cref="Element.Draw(Microsoft.Xna.Framework.GameTime,Microsoft.Xna.Framework.Graphics.SpriteBatch,float,MLEM.Graphics.SpriteBatchContext)"/> took.
|
||||
/// The amount of time that <see cref="Element.Draw"/> took.
|
||||
/// Can be divided by <see cref="Draws"/> to get an average per draw.
|
||||
/// </summary>
|
||||
public TimeSpan DrawTime { get; internal set; }
|
||||
/// <summary>
|
||||
/// The amount of times that <see cref="Element.Draw(Microsoft.Xna.Framework.GameTime,Microsoft.Xna.Framework.Graphics.SpriteBatch,float,MLEM.Graphics.SpriteBatchContext)"/> was called.
|
||||
/// The amount of times that <see cref="Element.Draw"/> was called.
|
||||
/// </summary>
|
||||
public uint Draws { get; internal set; }
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ using Microsoft.Xna.Framework;
|
|||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Formatting;
|
||||
using MLEM.Formatting.Codes;
|
||||
using MLEM.Graphics;
|
||||
using MLEM.Input;
|
||||
using MLEM.Misc;
|
||||
using MLEM.Textures;
|
||||
|
@ -78,32 +77,23 @@ namespace MLEM.Ui {
|
|||
/// <summary>
|
||||
/// The blend state that this ui system and all of its elements draw with
|
||||
/// </summary>
|
||||
[Obsolete("Set this through SpriteBatchContext instead")]
|
||||
public BlendState BlendState;
|
||||
/// <summary>
|
||||
/// The sampler state that this ui system and all of its elements draw with.
|
||||
/// The default is <see cref="Microsoft.Xna.Framework.Graphics.SamplerState.PointClamp"/>, as that is the one that works best with pixel graphics.
|
||||
/// </summary>
|
||||
[Obsolete("Set this through SpriteBatchContext instead")]
|
||||
public SamplerState SamplerState;
|
||||
public SamplerState SamplerState = SamplerState.PointClamp;
|
||||
/// <summary>
|
||||
/// The depth stencil state that this ui system and all of its elements draw with.
|
||||
/// The default is <see cref="Microsoft.Xna.Framework.Graphics.DepthStencilState.None"/>, which is also the default for <c>SpriteBatch.Begin</c>.
|
||||
/// The default is <see cref="Microsoft.Xna.Framework.Graphics.DepthStencilState.None"/>, which is also the default for <see cref="SpriteBatch.Begin"/>.
|
||||
/// </summary>
|
||||
[Obsolete("Set this through SpriteBatchContext instead")]
|
||||
public DepthStencilState DepthStencilState;
|
||||
public DepthStencilState DepthStencilState = DepthStencilState.None;
|
||||
/// <summary>
|
||||
/// The effect that this ui system and all of its elements draw with.
|
||||
/// The default is null, which means that no custom effect will be used.
|
||||
/// </summary>
|
||||
[Obsolete("Set this through SpriteBatchContext instead")]
|
||||
public Effect Effect;
|
||||
/// <summary>
|
||||
/// The spriteb atch context that this ui system and all of its elements should draw with.
|
||||
/// The default <see cref="MLEM.Graphics.SpriteBatchContext.SamplerState"/> is <see cref="Microsoft.Xna.Framework.Graphics.SamplerState.PointClamp"/>, as that is the one that works best with pixel graphics.
|
||||
/// </summary>
|
||||
public SpriteBatchContext SpriteBatchContext = new SpriteBatchContext(samplerState: SamplerState.PointClamp);
|
||||
/// <summary>
|
||||
/// The <see cref="TextFormatter"/> that this ui system's <see cref="Paragraph"/> elements format their text with.
|
||||
/// To add new formatting codes to the ui system, add them to this formatter.
|
||||
/// </summary>
|
||||
|
@ -241,10 +231,10 @@ namespace MLEM.Ui {
|
|||
MlemPlatform.Current?.AddTextInputListener(game.Window, (sender, key, character) => this.ApplyToAll(e => e.OnTextInput?.Invoke(e, key, character)));
|
||||
|
||||
if (automaticViewport) {
|
||||
this.Viewport = new Rectangle(0, 0, game.Window.ClientBounds.Width, game.Window.ClientBounds.Height);
|
||||
this.AutoScaleReferenceSize = new Point(this.Viewport.Width, this.Viewport.Height);
|
||||
this.Viewport = new Rectangle(Point.Zero, game.Window.ClientBounds.Size);
|
||||
this.AutoScaleReferenceSize = this.Viewport.Size;
|
||||
game.Window.ClientSizeChanged += (sender, args) => {
|
||||
this.Viewport = new Rectangle(0, 0, game.Window.ClientBounds.Width, game.Window.ClientBounds.Height);
|
||||
this.Viewport = new Rectangle(Point.Zero, game.Window.ClientBounds.Size);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -306,24 +296,9 @@ namespace MLEM.Ui {
|
|||
foreach (var root in this.rootElements) {
|
||||
if (root.Element.IsHidden)
|
||||
continue;
|
||||
var context = this.SpriteBatchContext;
|
||||
context.TransformMatrix = root.Transform * context.TransformMatrix;
|
||||
|
||||
#pragma warning disable CS0618
|
||||
if (this.BlendState != null)
|
||||
context.BlendState = this.BlendState;
|
||||
if (this.SamplerState != null)
|
||||
context.SamplerState = this.SamplerState;
|
||||
if (this.DepthStencilState != null)
|
||||
context.DepthStencilState = this.DepthStencilState;
|
||||
if (this.Effect != null)
|
||||
context.Effect = this.Effect;
|
||||
#pragma warning restore CS0618
|
||||
|
||||
batch.Begin(context);
|
||||
#pragma warning disable CS0618
|
||||
root.Element.DrawTransformed(time, batch, this.DrawAlpha * root.Element.DrawAlpha, context.BlendState, context.SamplerState, context.DepthStencilState, context.Effect, context.TransformMatrix);
|
||||
#pragma warning restore CS0618
|
||||
batch.Begin(SpriteSortMode.Deferred, this.BlendState, this.SamplerState, this.DepthStencilState, null, this.Effect, root.Transform);
|
||||
var alpha = this.DrawAlpha * root.Element.DrawAlpha;
|
||||
root.Element.DrawTransformed(time, batch, alpha, this.BlendState, this.SamplerState, this.DepthStencilState, this.Effect, root.Transform);
|
||||
batch.End();
|
||||
}
|
||||
|
||||
|
@ -508,6 +483,7 @@ namespace MLEM.Ui {
|
|||
/// The <see cref="UiSystem"/> that this root element is a part of.
|
||||
/// </summary>
|
||||
public readonly UiSystem System;
|
||||
private float scale = 1;
|
||||
/// <summary>
|
||||
/// The scale of this root element.
|
||||
/// Note that, to change the scale of every root element, you can use <see cref="UiSystem.GlobalScale"/>
|
||||
|
@ -521,6 +497,7 @@ namespace MLEM.Ui {
|
|||
this.Element.ForceUpdateArea();
|
||||
}
|
||||
}
|
||||
private int priority;
|
||||
/// <summary>
|
||||
/// The priority of this root element.
|
||||
/// A higher priority means the element will be updated first, as well as rendered on top.
|
||||
|
@ -537,6 +514,7 @@ namespace MLEM.Ui {
|
|||
/// This is a combination of this root element's <see cref="Scale"/> as well as the ui system's <see cref="UiSystem.GlobalScale"/>.
|
||||
/// </summary>
|
||||
public float ActualScale => this.System.GlobalScale * this.Scale;
|
||||
|
||||
/// <summary>
|
||||
/// The transformation that this root element (and all of its children) has.
|
||||
/// This transform is applied both to input, as well as to rendering.
|
||||
|
@ -546,6 +524,7 @@ namespace MLEM.Ui {
|
|||
/// An inversion of <see cref="Transform"/>
|
||||
/// </summary>
|
||||
public Matrix InvTransform => Matrix.Invert(this.Transform);
|
||||
|
||||
/// <summary>
|
||||
/// The child element of this root element that is currently selected.
|
||||
/// If there is no selected element in this root, this value will be <c>null</c>.
|
||||
|
@ -553,17 +532,9 @@ namespace MLEM.Ui {
|
|||
public Element SelectedElement => this.System.Controls.GetSelectedElement(this);
|
||||
/// <summary>
|
||||
/// Determines whether this root element contains any children that <see cref="Elements.Element.CanBeSelected"/>.
|
||||
/// This value is automatically calculated, and used in <see cref="CanBeActive"/>.
|
||||
/// This value is automatically calculated.
|
||||
/// </summary>
|
||||
public bool CanSelectContent => this.Element.CanBeSelected || this.Element.GetChildren(c => c.CanBeSelected, true).Any();
|
||||
/// <summary>
|
||||
/// Determines whether this root element can become the <see cref="UiControls.ActiveRoot"/>.
|
||||
/// This property returns <see langword="true"/> if <see cref="CanSelectContent"/> is <see langword="true"/> and the <see cref="Element"/> is not hidden, or if it has been set to <see langword="true"/> manually.
|
||||
/// </summary>
|
||||
public bool CanBeActive {
|
||||
get => this.canBeActive || (!this.Element.IsHidden && this.CanSelectContent);
|
||||
set => this.canBeActive = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event that is invoked when a <see cref="Element"/> is added to this root element or any of its children.
|
||||
|
@ -582,10 +553,6 @@ namespace MLEM.Ui {
|
|||
/// </summary>
|
||||
public event Action<UiSystem> OnRemovedFromUi;
|
||||
|
||||
private float scale = 1;
|
||||
private bool canBeActive;
|
||||
private int priority;
|
||||
|
||||
internal RootElement(string name, Element element, UiSystem system) {
|
||||
this.Name = name;
|
||||
this.Element = element;
|
||||
|
|
|
@ -58,7 +58,7 @@ namespace MLEM.Cameras {
|
|||
}
|
||||
/// <summary>
|
||||
/// The matrix that this camera "sees", based on its position and scale.
|
||||
/// Use this in your <c>SpriteBatch.Begin</c> calls to render based on the camera's viewport.
|
||||
/// Use this in your <see cref="SpriteBatch.Begin"/> calls to render based on the camera's viewport.
|
||||
/// </summary>
|
||||
public Matrix ViewMatrix {
|
||||
get {
|
||||
|
@ -105,7 +105,7 @@ namespace MLEM.Cameras {
|
|||
/// <param name="roundPosition">Whether the camera's <see cref="Position"/> should be rounded to full integers when calculating the <see cref="ViewMatrix"/></param>
|
||||
public Camera(GraphicsDevice graphicsDevice, bool roundPosition = true) {
|
||||
this.graphicsDevice = graphicsDevice;
|
||||
this.AutoScaleReferenceSize = new Point(this.Viewport.Width, this.Viewport.Height);
|
||||
this.AutoScaleReferenceSize = this.Viewport.Size;
|
||||
this.RoundPosition = roundPosition;
|
||||
}
|
||||
|
||||
|
@ -164,7 +164,7 @@ namespace MLEM.Cameras {
|
|||
if (this.Max.Y > max.Y)
|
||||
this.Max = new Vector2(this.Max.X, max.Y);
|
||||
}
|
||||
return !this.Position.Equals(lastPos, Camera.Epsilon);
|
||||
return !this.Position.Equals(lastPos, Epsilon);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -173,7 +173,7 @@ namespace MLEM.Cameras {
|
|||
/// <param name="delta">The amount to zoom in or out by</param>
|
||||
/// <param name="zoomCenter">The position that should be regarded as the zoom's center, in screen space. The default value is the center.</param>
|
||||
public void Zoom(float delta, Vector2? zoomCenter = null) {
|
||||
var center = (zoomCenter ?? new Vector2(this.Viewport.Width, this.Viewport.Height) / 2) / this.ActualScale;
|
||||
var center = (zoomCenter ?? this.Viewport.Size.ToVector2() / 2) / this.ActualScale;
|
||||
var lastScale = this.Scale;
|
||||
this.Scale += delta;
|
||||
this.Position += center * ((this.Scale - lastScale) / this.Scale);
|
||||
|
|
|
@ -15,9 +15,9 @@ namespace MLEM.Extensions {
|
|||
/// <param name="c">The character to turn into a string</param>
|
||||
/// <returns>A string representing the character</returns>
|
||||
public static string ToCachedString(this char c) {
|
||||
if (!CharExtensions.Cache.TryGetValue(c, out var ret)) {
|
||||
if (!Cache.TryGetValue(c, out var ret)) {
|
||||
ret = c.ToString();
|
||||
CharExtensions.Cache.Add(c, ret);
|
||||
Cache.Add(c, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace MLEM.Extensions {
|
|||
/// <param name="value">The number to parse.</param>
|
||||
/// <returns>The resulting color.</returns>
|
||||
public static Color FromHexRgba(int value) {
|
||||
return new Color(value >> 16 & 0xFF, value >> 8 & 0xFF, value >> 0 & 0xFF, value >> 24 & 0xFF);
|
||||
return new Color((value >> 16) & 0xFF, (value >> 8) & 0xFF, (value >> 0) & 0xFF, (value >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -60,7 +60,7 @@ namespace MLEM.Extensions {
|
|||
/// <param name="value">The number to parse.</param>
|
||||
/// <returns>The resulting color.</returns>
|
||||
public static Color FromHexRgb(int value) {
|
||||
return new Color(value >> 16 & 0xFF, value >> 8 & 0xFF, value >> 0 & 0xFF);
|
||||
return new Color((value >> 16) & 0xFF, (value >> 8) & 0xFF, (value >> 0) & 0xFF);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -73,7 +73,7 @@ namespace MLEM.Extensions {
|
|||
if (value.StartsWith("#"))
|
||||
value = value.Substring(1);
|
||||
var val = int.Parse(value, NumberStyles.HexNumber);
|
||||
return value.Length > 6 ? ColorHelper.FromHexRgba(val) : ColorHelper.FromHexRgb(val);
|
||||
return value.Length > 6 ? FromHexRgba(val) : FromHexRgb(val);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,18 +21,18 @@ namespace MLEM.Extensions {
|
|||
manager.IsFullScreen = fullscreen;
|
||||
if (fullscreen) {
|
||||
var view = manager.GraphicsDevice.Viewport;
|
||||
GraphicsExtensions.lastWidth = view.Width;
|
||||
GraphicsExtensions.lastHeight = view.Height;
|
||||
lastWidth = view.Width;
|
||||
lastHeight = view.Height;
|
||||
|
||||
var curr = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode;
|
||||
manager.PreferredBackBufferWidth = curr.Width;
|
||||
manager.PreferredBackBufferHeight = curr.Height;
|
||||
} else {
|
||||
if (GraphicsExtensions.lastWidth <= 0 || GraphicsExtensions.lastHeight <= 0)
|
||||
if (lastWidth <= 0 || lastHeight <= 0)
|
||||
throw new InvalidOperationException("Can't call SetFullscreen to change out of fullscreen mode without going into fullscreen mode first");
|
||||
|
||||
manager.PreferredBackBufferWidth = GraphicsExtensions.lastWidth;
|
||||
manager.PreferredBackBufferHeight = GraphicsExtensions.lastHeight;
|
||||
manager.PreferredBackBufferWidth = lastWidth;
|
||||
manager.PreferredBackBufferHeight = lastHeight;
|
||||
}
|
||||
manager.ApplyChanges();
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ namespace MLEM.Extensions {
|
|||
/// <param name="manager">The graphics device manager</param>
|
||||
/// <param name="window">The window whose bounds to use</param>
|
||||
public static void ResetWidthAndHeight(this GraphicsDeviceManager manager, GameWindow window) {
|
||||
var (width, height) = (window.ClientBounds.Width, window.ClientBounds.Height);
|
||||
var (_, _, width, height) = window.ClientBounds;
|
||||
manager.PreferredBackBufferWidth = Math.Max(height, width);
|
||||
manager.PreferredBackBufferHeight = Math.Min(height, width);
|
||||
manager.ApplyChanges();
|
||||
|
@ -90,12 +90,7 @@ namespace MLEM.Extensions {
|
|||
/// <param name="target">The target to apply</param>
|
||||
public TargetContext(GraphicsDevice device, RenderTarget2D target) {
|
||||
this.device = device;
|
||||
#if FNA
|
||||
// RenderTargetCount doesn't exist in FNA but we still want the optimization in MG
|
||||
this.lastTargets = device.GetRenderTargets();
|
||||
#else
|
||||
this.lastTargets = device.RenderTargetCount <= 0 ? null : device.GetRenderTargets();
|
||||
#endif
|
||||
device.SetRenderTarget(target);
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue