mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-25 22:18:34 +01:00
Compare commits
84 commits
a1c5b8e2d6
...
9b090c954f
Author | SHA1 | Date | |
---|---|---|---|
9b090c954f | |||
064dc5607e | |||
663d7148fe | |||
79f7206686 | |||
711f60a97e | |||
48dfa8f1ee | |||
e673ccea61 | |||
ba1058748e | |||
08e28cb95b | |||
288b8352af | |||
d32bc0cbfb | |||
d58c5d8b33 | |||
6f05263980 | |||
6e2c2b3730 | |||
92018eea1e | |||
04c6bb5ff8 | |||
2973bd98e2 | |||
87b575b5c3 | |||
61055148ef | |||
334918103f | |||
42a87471fc | |||
9c370f75e8 | |||
059e26781b | |||
5fcdda80dc | |||
5d7d238630 | |||
aff61508c4 | |||
282a398f3b | |||
6b08e892f7 | |||
aabb1ed5df | |||
1795acb30e | |||
59af00c89a | |||
d0ece92550 | |||
01fb5288ff | |||
7b1da2f1a7 | |||
f53305ce42 | |||
d03116a49a | |||
7d9633d989 | |||
42993f1a0b | |||
0a93fb7da7 | |||
144062fa64 | |||
16053d9d04 | |||
61770d59b1 | |||
93a82bcf36 | |||
fdf04a7e77 | |||
cb496f613f | |||
951f4babd5 | |||
b9f2de8290 | |||
f0f1d7f8ed | |||
fcca5300ae | |||
bd9d3f970b | |||
161d44dbe0 | |||
30bcdc1710 | |||
15b873a8ad | |||
6dc4011ef5 | |||
8968e6025d | |||
03accff6ae | |||
5ba550619d | |||
7ebbe49786 | |||
61439aa521 | |||
874be1fd6e | |||
acd15fea14 | |||
47b58b1942 | |||
16b9e26969 | |||
98118e540a | |||
58b716aabb | |||
63d2353694 | |||
15a57d8db9 | |||
5a1b31e8a3 | |||
435042e1f5 | |||
4c24284a3f | |||
bc0f9d5c0c | |||
610527374e | |||
4a88cca8bf | |||
8adee49e55 | |||
46c77d2444 | |||
6393d879d9 | |||
c78bafd000 | |||
783da33107 | |||
be26a2ebc2 | |||
45afd9ac79 | |||
ad29b46df3 | |||
f445f59078 | |||
902391d278 | |||
62d2b28ec0 |
225 changed files with 4910 additions and 2353 deletions
113
.editorconfig
Normal file
113
.editorconfig
Normal file
|
@ -0,0 +1,113 @@
|
|||
[*]
|
||||
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
Normal file
6
.gitmodules
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
[submodule "FNA"]
|
||||
path = FNA
|
||||
url = https://github.com/FNA-XNA/FNA
|
||||
[submodule "FontStashSharp"]
|
||||
path = FontStashSharp
|
||||
url = https://github.com/FontStashSharp/FontStashSharp
|
87
CHANGELOG.md
87
CHANGELOG.md
|
@ -2,11 +2,98 @@
|
|||
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
|
||||
|
|
36
Demos.Android/.config/dotnet-tools.json
Normal file
36
Demos.Android/.config/dotnet-tools.json
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"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,17 +1,14 @@
|
|||
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 static Android.Views.SystemUiFlags;
|
||||
using Uri = Android.Net.Uri;
|
||||
|
||||
namespace Demos.Android {
|
||||
[Activity(
|
||||
namespace Demos.Android;
|
||||
|
||||
[Activity(
|
||||
Label = "@string/app_name",
|
||||
MainLauncher = true,
|
||||
Icon = "@drawable/icon",
|
||||
|
@ -19,8 +16,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;
|
||||
|
@ -47,9 +44,12 @@ namespace Demos.Android {
|
|||
public override void OnWindowFocusChanged(bool hasFocus) {
|
||||
base.OnWindowFocusChanged(hasFocus);
|
||||
// hide the status bar
|
||||
if (hasFocus)
|
||||
this.Window.DecorView.SystemUiVisibility = (StatusBarVisibility) (ImmersiveSticky | LayoutStable | LayoutHideNavigation | LayoutFullscreen | HideNavigation | Fullscreen);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
6
Demos.Android/AndroidManifest.xml
Normal file
6
Demos.Android/AndroidManifest.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?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,104 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<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>
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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" />
|
||||
<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" />
|
||||
</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>
|
||||
|
|
|
@ -1,5 +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: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>
|
|
@ -1,15 +0,0 @@
|
|||
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
76
Demos.Android/Resources/Resource.Designer.cs
generated
|
@ -1,76 +0,0 @@
|
|||
#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
|
36
Demos.DesktopGL/.config/dotnet-tools.json
Normal file
36
Demos.DesktopGL/.config/dotnet-tools.json
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
37
Demos.DesktopGL/Demos.DesktopGL.FNA.csproj
Normal file
37
Demos.DesktopGL/Demos.DesktopGL.FNA.csproj
Normal file
|
@ -0,0 +1,37 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<ApplicationIcon>Icon.ico</ApplicationIcon>
|
||||
<AssemblyName>MLEM Desktop Demos</AssemblyName>
|
||||
<RootNamespace>Demos.DesktopGL</RootNamespace>
|
||||
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
||||
<!-- We still use the MG content builder for ease of compatibility between the MG and FNA demo projects -->
|
||||
<MonoGamePlatform>DesktopGL</MonoGamePlatform>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Demos\Demos.FNA.csproj" />
|
||||
<ProjectReference Include="..\MLEM.Startup\MLEM.Startup.FNA.csproj" />
|
||||
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.FNA.csproj" />
|
||||
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.0.1641" />
|
||||
<ProjectReference Include="..\FNA\FNA.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MonoGameContentReference Include="..\Demos\Content\Content.mgcb" />
|
||||
<Content Include="..\Demos\Content\*\**" />
|
||||
<EmbeddedResource Include="Icon.ico" />
|
||||
<EmbeddedResource Include="Icon.bmp" />
|
||||
<Content Include="FnaNative/**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Link>%(Filename)%(Extension)</Link>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -2,28 +2,32 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.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>
|
BIN
Demos.DesktopGL/FnaNative/FAudio.dll
Normal file
BIN
Demos.DesktopGL/FnaNative/FAudio.dll
Normal file
Binary file not shown.
BIN
Demos.DesktopGL/FnaNative/FNA3D.dll
Normal file
BIN
Demos.DesktopGL/FnaNative/FNA3D.dll
Normal file
Binary file not shown.
BIN
Demos.DesktopGL/FnaNative/SDL2.dll
Normal file
BIN
Demos.DesktopGL/FnaNative/SDL2.dll
Normal file
Binary file not shown.
BIN
Demos.DesktopGL/FnaNative/libFAudio.0.dylib
Normal file
BIN
Demos.DesktopGL/FnaNative/libFAudio.0.dylib
Normal file
Binary file not shown.
BIN
Demos.DesktopGL/FnaNative/libFAudio.so.0
Normal file
BIN
Demos.DesktopGL/FnaNative/libFAudio.so.0
Normal file
Binary file not shown.
BIN
Demos.DesktopGL/FnaNative/libFNA3D.0.dylib
Normal file
BIN
Demos.DesktopGL/FnaNative/libFNA3D.0.dylib
Normal file
Binary file not shown.
BIN
Demos.DesktopGL/FnaNative/libFNA3D.so.0
Normal file
BIN
Demos.DesktopGL/FnaNative/libFNA3D.so.0
Normal file
Binary file not shown.
BIN
Demos.DesktopGL/FnaNative/libMoltenVK.dylib
Normal file
BIN
Demos.DesktopGL/FnaNative/libMoltenVK.dylib
Normal file
Binary file not shown.
BIN
Demos.DesktopGL/FnaNative/libSDL2-2.0.0.dylib
Normal file
BIN
Demos.DesktopGL/FnaNative/libSDL2-2.0.0.dylib
Normal file
Binary file not shown.
BIN
Demos.DesktopGL/FnaNative/libSDL2-2.0.so.0
Normal file
BIN
Demos.DesktopGL/FnaNative/libSDL2-2.0.so.0
Normal file
Binary file not shown.
BIN
Demos.DesktopGL/FnaNative/libtheorafile.dll
Normal file
BIN
Demos.DesktopGL/FnaNative/libtheorafile.dll
Normal file
Binary file not shown.
BIN
Demos.DesktopGL/FnaNative/libtheorafile.dylib
Normal file
BIN
Demos.DesktopGL/FnaNative/libtheorafile.dylib
Normal file
Binary file not shown.
BIN
Demos.DesktopGL/FnaNative/libtheorafile.so
Normal file
BIN
Demos.DesktopGL/FnaNative/libtheorafile.so
Normal file
Binary file not shown.
BIN
Demos.DesktopGL/FnaNative/libvulkan.1.dylib
Normal file
BIN
Demos.DesktopGL/FnaNative/libvulkan.1.dylib
Normal file
Binary file not shown.
|
@ -1,11 +1,17 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using MLEM.Misc;
|
||||
|
||||
namespace Demos.DesktopGL {
|
||||
public static class Program {
|
||||
|
||||
public static void Main() {
|
||||
#if FNA
|
||||
MlemPlatform.Current = new MlemPlatform.DesktopFna(a => TextInputEXT.TextInput += a);
|
||||
#else
|
||||
MlemPlatform.Current = new MlemPlatform.DesktopGl<TextInputEventArgs>((w, c) => w.TextInput += c);
|
||||
#endif
|
||||
using var game = new GameImpl();
|
||||
game.Run();
|
||||
}
|
||||
|
|
|
@ -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(LoadContent<Texture2D>("Textures/Anim"), 4, 4);
|
||||
var atlas = new UniformTextureAtlas(Demo.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, transformMatrix: Matrix.CreateScale(10));
|
||||
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(10));
|
||||
// draw the group's current region
|
||||
// if not using a group, just draw the animation's CurrentRegion here
|
||||
this.SpriteBatch.Draw(this.group.CurrentRegion, new Vector2(10, 10), Color.White);
|
||||
|
|
|
@ -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 = LoadContent<Texture2D>("Textures/AutoTiling");
|
||||
this.texture = Demo.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, transformMatrix: Matrix.CreateScale(10));
|
||||
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(10));
|
||||
for (var x = 0; x < 6; x++) {
|
||||
for (var y = 0; y < 5; y++) {
|
||||
// don't draw non-grass tiles ( )
|
||||
|
@ -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) * TileSize, new TextureRegion(this.texture, 0, 0, TileSize, TileSize), ConnectsTo, Color.White);
|
||||
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);
|
||||
|
||||
// when drawing extended auto-tiles, the same rules apply, but the source texture layout is different
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
this.SpriteBatch.End();
|
||||
|
|
|
@ -13,6 +13,13 @@
|
|||
|
||||
#---------------------------------- 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
|
||||
|
@ -34,12 +41,8 @@
|
|||
/processorParam:TextureFormat=Compressed
|
||||
/build:Fonts/TestFontItalic.spritefont
|
||||
|
||||
#begin Fonts/MonospacedFont.spritefont
|
||||
/importer:FontDescriptionImporter
|
||||
/processor:FontDescriptionProcessor
|
||||
/processorParam:PremultiplyAlpha=True
|
||||
/processorParam:TextureFormat=Compressed
|
||||
/build:Fonts/MonospacedFont.spritefont
|
||||
#begin Markdown.md
|
||||
/copy:Markdown.md
|
||||
|
||||
#begin Textures/Anim.png
|
||||
/importer:TextureImporter
|
||||
|
|
23
Demos/Content/Markdown.md
Normal file
23
Demos/Content/Markdown.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
# 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() {
|
||||
|
||||
}
|
||||
```
|
21
Demos/Demos.FNA.csproj
Normal file
21
Demos/Demos.FNA.csproj
Normal file
|
@ -0,0 +1,21 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<RootNamespace>Demos</RootNamespace>
|
||||
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MLEM.Startup\MLEM.Startup.FNA.csproj" />
|
||||
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.FNA.csproj" />
|
||||
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FNA\FNA.Core.csproj">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -11,7 +11,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.263">
|
||||
<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 = EasingFields
|
||||
private static readonly Easings.Easing[] Easings = EasingsDemo.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) % Easings.Length;
|
||||
this.current = (this.current + 1) % EasingsDemo.Easings.Length;
|
||||
this.progress = 0;
|
||||
}
|
||||
});
|
||||
this.group.AddChild(new Paragraph(Anchor.AutoCenter, 1, p => EasingFields[this.current].Name, true));
|
||||
this.group.AddChild(new Paragraph(Anchor.AutoCenter, 1, p => EasingsDemo.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);
|
||||
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null);
|
||||
var view = this.GraphicsDevice.Viewport;
|
||||
|
||||
// graph the easing function
|
||||
var graphEase = Easings[this.current].ScaleInput(0, view.Width).ScaleOutput(-view.Height / 3, view.Height / 3);
|
||||
var graphEase = EasingsDemo.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 = Easings[this.current].AndReverse().ScaleOutput(0, view.Height / 4);
|
||||
var dotEase = EasingsDemo.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,7 +9,9 @@ using MLEM.Textures;
|
|||
using MLEM.Ui;
|
||||
using MLEM.Ui.Elements;
|
||||
using MLEM.Ui.Style;
|
||||
#if !FNA
|
||||
using MonoGame.Framework.Utilities;
|
||||
#endif
|
||||
|
||||
namespace Demos {
|
||||
public class GameImpl : MlemGame {
|
||||
|
@ -23,11 +25,12 @@ namespace Demos {
|
|||
private TimeSpan secondCounter;
|
||||
|
||||
static GameImpl() {
|
||||
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)));
|
||||
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)));
|
||||
}
|
||||
|
||||
public GameImpl() {
|
||||
|
@ -45,13 +48,6 @@ 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;
|
||||
|
@ -74,7 +70,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 Demos) {
|
||||
foreach (var demo in GameImpl.Demos) {
|
||||
selection.AddChild(new Button(Anchor.AutoCenter, new Vector2(1, 10), demo.Key, demo.Value.Item1) {
|
||||
OnPressed = e => {
|
||||
selection.IsHidden = true;
|
||||
|
@ -92,9 +88,9 @@ namespace Demos {
|
|||
}
|
||||
|
||||
protected override UiStyle InitializeDefaultUiStyle(SpriteBatch batch) {
|
||||
var tex = LoadContent<Texture2D>("Textures/Test");
|
||||
var tex = MlemGame.LoadContent<Texture2D>("Textures/Test");
|
||||
return new UntexturedStyle(this.SpriteBatch) {
|
||||
Font = new GenericSpriteFont(LoadContent<SpriteFont>("Fonts/TestFont")),
|
||||
Font = new GenericSpriteFont(MlemGame.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 AStar2.InfiniteCost;
|
||||
return this.world[nextPos.X, nextPos.Y] ? 1 : AStar2.InfiniteCost;
|
||||
return float.PositiveInfinity;
|
||||
return this.world[nextPos.X, nextPos.Y] ? 1 : float.PositiveInfinity;
|
||||
}
|
||||
|
||||
// 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, transformMatrix: Matrix.CreateScale(14));
|
||||
this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null, null, Matrix.CreateScale(14));
|
||||
var tex = this.SpriteBatch.GetBlankTexture();
|
||||
// draw the world with simple shapes
|
||||
for (var x = 0; x < 50; x++) {
|
||||
|
@ -87,9 +87,9 @@ namespace Demos {
|
|||
// in a real game, you'd obviously make your characters walk along the path instead of drawing it
|
||||
if (this.path != null) {
|
||||
for (var i = 1; i < this.path.Count; i++) {
|
||||
var (firstX, firstY) = this.path[i - 1];
|
||||
var (secondX, secondY) = this.path[i];
|
||||
this.SpriteBatch.Draw(tex, RectangleF.FromCorners(new Vector2(firstX + 0.25F, firstY + 0.25F), new Vector2(secondX + 0.75F, secondY + 0.75F)), Color.Blue);
|
||||
var first = this.path[i - 1];
|
||||
var second = this.path[i];
|
||||
this.SpriteBatch.Draw(tex, RectangleF.FromCorners(new Vector2(first.X + 0.25F, first.Y + 0.25F), new Vector2(second.X + 0.75F, second.Y + 0.75F)), Color.Blue);
|
||||
}
|
||||
}
|
||||
this.SpriteBatch.End();
|
||||
|
|
81
Demos/TextFormattingDemo.cs
Normal file
81
Demos/TextFormattingDemo.cs
Normal file
|
@ -0,0 +1,81 @@
|
|||
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,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Coroutine;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
@ -7,13 +8,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 {
|
||||
|
@ -26,7 +27,7 @@ namespace Demos {
|
|||
public UiDemo(MlemGame game) : base(game) {}
|
||||
|
||||
public override void LoadContent() {
|
||||
this.testTexture = LoadContent<Texture2D>("Textures/Test");
|
||||
this.testTexture = Demo.LoadContent<Texture2D>("Textures/Test");
|
||||
this.testPatch = new NinePatch(new TextureRegion(this.testTexture, 0, 8, 24, 24), 8);
|
||||
base.LoadContent();
|
||||
|
||||
|
@ -36,7 +37,10 @@ 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(LoadContent<SpriteFont>("Fonts/TestFont"), LoadContent<SpriteFont>("Fonts/TestFontBold"), LoadContent<SpriteFont>("Fonts/TestFontItalic")),
|
||||
Font = new GenericSpriteFont(
|
||||
Demo.LoadContent<SpriteFont>("Fonts/TestFont"),
|
||||
Demo.LoadContent<SpriteFont>("Fonts/TestFontBold"),
|
||||
Demo.LoadContent<SpriteFont>("Fonts/TestFontItalic")),
|
||||
TextScale = 0.1F,
|
||||
PanelTexture = this.testPatch,
|
||||
ButtonTexture = new NinePatch(new TextureRegion(this.testTexture, 24, 8, 16, 16), 4),
|
||||
|
@ -47,7 +51,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(LoadContent<SpriteFont>("Fonts/MonospacedFont"))}},
|
||||
AdditionalFonts = {{"Monospaced", new GenericSpriteFont(Demo.LoadContent<SpriteFont>("Fonts/MonospacedFont"))}},
|
||||
LinkColor = Color.CornflowerBlue
|
||||
};
|
||||
var untexturedStyle = new UntexturedStyle(this.SpriteBatch) {
|
||||
|
@ -69,7 +73,7 @@ namespace Demos {
|
|||
// add the root to the demos' ui
|
||||
this.UiRoot.AddChild(this.root);
|
||||
|
||||
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "This is a small demo for MLEM.Ui, a user interface library that is part of the MLEM Library for Extending MonoGame."));
|
||||
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "This is a small demo for MLEM.Ui, a user interface library that is part of the MLEM Library for Extending MonoGame and FNA."));
|
||||
var image = this.root.AddChild(new Image(Anchor.AutoCenter, new Vector2(50, 50), new TextureRegion(this.testTexture, 0, 0, 8, 8)) {IsHidden = true, Padding = new Padding(3)});
|
||||
// Setting the x or y coordinate of the size to 1 or a lower number causes the width or height to be a percentage of the parent's width or height
|
||||
// (for example, setting the size's x to 0.75 would make the element's width be 0.75*parentWidth)
|
||||
|
@ -88,12 +92,7 @@ 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>. 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 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 VerticalSpace(3));
|
||||
this.root.AddChild(new Paragraph(Anchor.AutoCenter, 1, "Multiline text input:", true));
|
||||
|
@ -156,7 +155,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(WobbleButton(element)),
|
||||
OnPressed = element => CoroutineHandler.Start(UiDemo.WobbleButton(element)),
|
||||
PositionOffset = new Vector2(0, 1)
|
||||
});
|
||||
// Another button that shows animations!
|
||||
|
@ -189,13 +188,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(WobbleProgressBar(bar1));
|
||||
CoroutineHandler.Start(UiDemo.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(WobbleProgressBar(bar2));
|
||||
CoroutineHandler.Start(UiDemo.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(WobbleProgressBar(bar3));
|
||||
CoroutineHandler.Start(UiDemo.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(WobbleProgressBar(bar4));
|
||||
CoroutineHandler.Start(UiDemo.WobbleProgressBar(bar4));
|
||||
|
||||
this.root.AddChild(new VerticalSpace(3));
|
||||
var dropdown = this.root.AddChild(new Dropdown(Anchor.AutoLeft, new Vector2(1, 10), "Dropdown Menu"));
|
||||
|
@ -223,6 +222,13 @@ 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 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 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.
|
||||
|
||||
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,15 +35,19 @@ 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:
|
||||
DesktopGL and WindowsDX using MonoGame:
|
||||
```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 just set the platform to a stub one like so:
|
||||
If you're not using text input, you can leave the platform uninitialized or just set it 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** is an addition to the game framework [MonoGame](https://www.monogame.net/) that provides extension methods, quality of life improvements and additional features like a ui system and easy input handling.
|
||||
**MLEM Library for Extending MonoGame and FNA** is 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.
|
||||
|
||||
# What next?
|
||||
- Get it on [NuGet](https://www.nuget.org/packages?q=mlem)
|
||||
- Get prerelease builds on [BaGet](https://nuget.ellpeck.de)
|
||||
- Get prerelease builds on [BaGet](https://nuget.ellpeck.de/?q=mlem)
|
||||
- See the source code on [GitHub](https://github.com/Ellpeck/MLEM)
|
||||
- See tutorials and API documentation on [the website](https://mlem.ellpeck.de/)
|
||||
- Check out [the demos](https://github.com/Ellpeck/MLEM/tree/release/Demos) on [Desktop](https://github.com/Ellpeck/MLEM/tree/release/Demos.DesktopGL) or [Android](https://github.com/Ellpeck/MLEM/tree/release/Demos.Android)
|
||||
- See [the changelog](https://mlem.ellpeck.de/CHANGELOG.html) for information on updates
|
||||
|
||||
# Packages
|
||||
- **MLEM** is the base package, which provides 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** is the base package, which provides various small addons and abstractions for MonoGame and FNA, including a text formatting system and simple input handling
|
||||
- **MLEM.Ui** provides a mouse, keyboard, gamepad and touch ready Ui system that features automatic anchoring, sizing and several ready-to-use element types
|
||||
- **MLEM.Extended** ties in with MonoGame.Extended and other MonoGame and FNA libraries
|
||||
- **MLEM.Data** provides simple loading and processing of textures and other data, including the ability to load non-XNB content files easily
|
||||
- **MLEM.Startup** combines MLEM with some other useful libraries into a quick Game startup class
|
||||
- **MLEM.Templates** contains cross-platform project templates
|
||||
|
||||
# Made with MLEM
|
||||
- [A Breath of Spring Air](https://ellpeck.itch.io/a-breath-of-spring-air), a short platformer ([Source](https://git.ellpeck.de/Ellpeck/GreatSpringGameJam))
|
||||
- [Don't Wake Up](https://ellpeck.itch.io/dont-wake-up), a short puzzle game ([Source](https://github.com/Ellpeck/DontLetGo))
|
||||
- [Pong clone](https://github.com/luanfagu/pong), a very simple pong clone ([Source](https://github.com/luanfagu/pong))
|
||||
- [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 and MLEM. Here are some of them:
|
||||
There are several other libraries and tools that work well in combination with MonoGame, FNA and MLEM. Here are some of them:
|
||||
- [Contentless](https://github.com/Ellpeck/Contentless), a tool that removes the need to add assets to the MonoGame Content Pipeline manually
|
||||
- [GameBundle](https://github.com/Ellpeck/GameBundle), a tool that packages MonoGame and other .NET Core applications into several distributable formats
|
||||
- [GameBundle](https://github.com/Ellpeck/GameBundle), a tool that packages MonoGame and other .NET applications into several distributable formats
|
||||
- [MonoGame.Extended](https://github.com/craftworkgames/MonoGame.Extended), a package that also provides several additional features for MonoGame
|
||||
- [Coroutine](https://github.com/Ellpeck/Coroutine), a package that implements Unity-style coroutines for any project
|
||||
- [Illumilib](https://github.com/Ellpeck/Illumilib), a simple keyboard and mouse lighting library with support for Razer, Logitech and Corsair devices
|
||||
|
|
1
FNA
Submodule
1
FNA
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 102990f514f1e5bfac07d33f7c33e2e712946da4
|
1
FontStashSharp
Submodule
1
FontStashSharp
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit f0774130cad6cec0b790a58bc7c811a186443fb3
|
5
Jenkinsfile
vendored
5
Jenkinsfile
vendored
|
@ -1,6 +1,11 @@
|
|||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
stage('Submodules') {
|
||||
steps {
|
||||
sh 'git submodule update --init --recursive --force'
|
||||
}
|
||||
}
|
||||
stage('Cake Build') {
|
||||
steps {
|
||||
sh 'dotnet tool restore'
|
||||
|
|
|
@ -13,13 +13,16 @@ 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>
|
||||
|
@ -50,15 +53,19 @@ namespace MLEM.Data.Content {
|
|||
/// <param name="originalAssetName">The original name of the asset.</param>
|
||||
/// <param name="currentAsset">The current asset instance.</param>
|
||||
/// <typeparam name="T">The asset's type.</typeparam>
|
||||
protected override void ReloadAsset<T>(string originalAssetName, T currentAsset) {
|
||||
protected
|
||||
#if !FNA
|
||||
override
|
||||
#endif
|
||||
void ReloadAsset<T>(string originalAssetName, T currentAsset) {
|
||||
this.Read(originalAssetName, currentAsset);
|
||||
}
|
||||
|
||||
private T Read<T>(string assetName, T existing) {
|
||||
var triedFiles = new List<string>();
|
||||
if (readers == null)
|
||||
readers = CollectContentReaders();
|
||||
foreach (var reader in readers) {
|
||||
if (RawContentManager.readers == null)
|
||||
RawContentManager.readers = RawContentManager.CollectContentReaders();
|
||||
foreach (var reader in RawContentManager.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[] {"ogg", "wav", "mp3"};
|
||||
return new[] {"wav"};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System.IO;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Extensions;
|
||||
|
||||
namespace MLEM.Data.Content {
|
||||
/// <inheritdoc />
|
||||
|
@ -7,11 +9,26 @@ 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 {
|
||||
return Texture2D.FromStream(manager.GraphicsDevice, stream);
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
Serializers[content] = serializer;
|
||||
ContentExtensions.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 (!Serializers.TryGetValue(content, out var serializer)) {
|
||||
if (!ContentExtensions.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 = GetJsonSerializer(content);
|
||||
var serializer = content.GetJsonSerializer();
|
||||
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 ?? JsonExtensions) {
|
||||
foreach (var extension in extensions ?? ContentExtensions.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 = DefaultFlags, Predicate<FieldInfo> fieldInclusion = null) {
|
||||
var copy = (T) Construct(typeof(T), flags);
|
||||
public static T Copy<T>(this T obj, BindingFlags flags = CopyExtensions.DefaultFlags, Predicate<FieldInfo> fieldInclusion = null) {
|
||||
var copy = (T) CopyExtensions.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 = DefaultFlags, Predicate<FieldInfo> fieldInclusion = null) {
|
||||
var copy = (T) Construct(typeof(T), flags);
|
||||
public static T DeepCopy<T>(this T obj, BindingFlags flags = CopyExtensions.DefaultFlags, Predicate<FieldInfo> fieldInclusion = null) {
|
||||
var copy = (T) CopyExtensions.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 = DefaultFlags, Predicate<FieldInfo> fieldInclusion = null) {
|
||||
public static void CopyInto<T>(this T obj, T otherObj, BindingFlags flags = CopyExtensions.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 = DefaultFlags, Predicate<FieldInfo> fieldInclusion = null) {
|
||||
public static void DeepCopyInto<T>(this T obj, T otherObj, BindingFlags flags = CopyExtensions.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 = Construct(field.FieldType, flags);
|
||||
otherVal = CopyExtensions.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 (!ConstructorCache.TryGetValue(t, out var constructor)) {
|
||||
if (!CopyExtensions.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");
|
||||
ConstructorCache.Add(t, constructor);
|
||||
CopyExtensions.ConstructorCache.Add(t, constructor);
|
||||
}
|
||||
return constructor.Invoke(new object[constructor.GetParameters().Length]);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
@ -5,6 +6,7 @@ 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 {
|
||||
|
@ -50,9 +52,13 @@ namespace MLEM.Data {
|
|||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns an enumerable of all of the <see cref="TextureRegion"/>s in this atlas.
|
||||
/// Returns an enumerable of all of the <see cref="TextureRegion"/> values 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>();
|
||||
|
||||
|
@ -79,9 +85,8 @@ namespace MLEM.Data {
|
|||
}
|
||||
var atlas = new DataTextureAtlas(texture);
|
||||
|
||||
// parse each texture region: "<name> loc <u> <v> <w> <h> [piv <px> <py>] [off <ox> <oy>]"
|
||||
// parse each texture region: "<names> 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),
|
||||
|
@ -91,18 +96,23 @@ namespace MLEM.Data {
|
|||
var loc = new Rectangle(
|
||||
int.Parse(match.Groups[2].Value), int.Parse(match.Groups[3].Value),
|
||||
int.Parse(match.Groups[4].Value), int.Parse(match.Groups[5].Value));
|
||||
loc.Offset(off);
|
||||
loc.Offset(off.ToPoint());
|
||||
|
||||
// pivot
|
||||
var piv = !match.Groups[6].Success ? Vector2.Zero : off + new Vector2(
|
||||
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 = name
|
||||
Name = trimmed
|
||||
};
|
||||
atlas.regions.Add(name, region);
|
||||
atlas.regions.Add(trimmed, 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 = (GetValue(this) & GetValue(flags)) == GetValue(flags);
|
||||
ret = (DynamicEnum.GetValue(this) & DynamicEnum.GetValue(flags)) == DynamicEnum.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 = (GetValue(this) & GetValue(flags)) != 0;
|
||||
ret = (DynamicEnum.GetValue(this) & DynamicEnum.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 (GetValue(this) != 0) {
|
||||
foreach (var v in GetValues(this.GetType())) {
|
||||
if (this.HasFlag(v) && GetValue(v) != 0)
|
||||
if (DynamicEnum.GetValue(this) != 0) {
|
||||
foreach (var v in DynamicEnum.GetValues(this.GetType())) {
|
||||
if (this.HasFlag(v) && DynamicEnum.GetValue(v) != 0)
|
||||
included.Add(v);
|
||||
}
|
||||
}
|
||||
this.name = included.Count > 0 ? string.Join(" | ", included) : GetValue(this).ToString();
|
||||
this.name = included.Count > 0 ? string.Join(" | ", included) : DynamicEnum.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 = GetStorage(typeof(T));
|
||||
var storage = DynamicEnum.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 = Construct(typeof(T), name, value);
|
||||
var ret = DynamicEnum.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 (GetStorage(typeof(T)).Values.ContainsKey(value))
|
||||
while (DynamicEnum.GetStorage(typeof(T)).Values.ContainsKey(value))
|
||||
value++;
|
||||
return Add<T>(name, value);
|
||||
return DynamicEnum.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 (GetStorage(typeof(T)).Values.ContainsKey(value))
|
||||
while (DynamicEnum.GetStorage(typeof(T)).Values.ContainsKey(value))
|
||||
value <<= 1;
|
||||
return Add<T>(name, value);
|
||||
return DynamicEnum.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 GetValues(typeof(T)).Cast<T>();
|
||||
return DynamicEnum.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 GetStorage(type).Values.Values;
|
||||
return DynamicEnum.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 = GetStorage(typeof(T)).OrCache;
|
||||
var cache = DynamicEnum.GetStorage(typeof(T)).OrCache;
|
||||
if (!cache.TryGetValue((left, right), out var ret)) {
|
||||
ret = GetEnumValue<T>(GetValue(left) | GetValue(right));
|
||||
ret = DynamicEnum.GetEnumValue<T>(DynamicEnum.GetValue(left) | DynamicEnum.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 = GetStorage(typeof(T)).AndCache;
|
||||
var cache = DynamicEnum.GetStorage(typeof(T)).AndCache;
|
||||
if (!cache.TryGetValue((left, right), out var ret)) {
|
||||
ret = GetEnumValue<T>(GetValue(left) & GetValue(right));
|
||||
ret = DynamicEnum.GetEnumValue<T>(DynamicEnum.GetValue(left) & DynamicEnum.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 = GetStorage(typeof(T)).XorCache;
|
||||
var cache = DynamicEnum.GetStorage(typeof(T)).XorCache;
|
||||
if (!cache.TryGetValue((left, right), out var ret)) {
|
||||
ret = GetEnumValue<T>(GetValue(left) ^ GetValue(right));
|
||||
ret = DynamicEnum.GetEnumValue<T>(DynamicEnum.GetValue(left) ^ DynamicEnum.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 = GetStorage(typeof(T)).NegCache;
|
||||
var cache = DynamicEnum.GetStorage(typeof(T)).NegCache;
|
||||
if (!cache.TryGetValue(value, out var ret)) {
|
||||
ret = GetEnumValue<T>(~GetValue(value));
|
||||
ret = DynamicEnum.GetEnumValue<T>(~DynamicEnum.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) GetEnumValue(typeof(T), value);
|
||||
return (T) DynamicEnum.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 = GetStorage(type);
|
||||
var storage = DynamicEnum.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 = Construct(type, null, value);
|
||||
combined = DynamicEnum.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) Parse(typeof(T), strg);
|
||||
return (T) DynamicEnum.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 = GetStorage(type).ParseCache;
|
||||
var cache = DynamicEnum.GetStorage(type).ParseCache;
|
||||
if (!cache.TryGetValue(strg, out var cached)) {
|
||||
BigInteger? accum = null;
|
||||
foreach (var val in strg.Split('|')) {
|
||||
foreach (var defined in GetValues(type)) {
|
||||
foreach (var defined in DynamicEnum.GetValues(type)) {
|
||||
if (defined.name == val.Trim()) {
|
||||
accum = (accum ?? 0) | GetValue(defined);
|
||||
accum = (accum ?? 0) | DynamicEnum.GetValue(defined);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (accum != null)
|
||||
cached = GetEnumValue(type, accum.Value);
|
||||
cached = DynamicEnum.GetEnumValue(type, accum.Value);
|
||||
cache.Add(strg, cached);
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
|
||||
private static Storage GetStorage(Type type) {
|
||||
if (!Storages.TryGetValue(type, out var storage)) {
|
||||
if (!DynamicEnum.Storages.TryGetValue(type, out var storage)) {
|
||||
storage = new Storage();
|
||||
Storages.Add(type, storage);
|
||||
DynamicEnum.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 Converters)
|
||||
foreach (var converter in JsonConverters.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(GetEntries(type, memberName)) {}
|
||||
public StaticJsonConverter(Type type, string memberName) : this(StaticJsonConverter<T>.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>
|
||||
|
|
45
MLEM.Data/MLEM.Data.FNA.csproj
Normal file
45
MLEM.Data/MLEM.Data.FNA.csproj
Normal file
|
@ -0,0 +1,45 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
<RootNamespace>MLEM.Data</RootNamespace>
|
||||
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Authors>Ellpeck</Authors>
|
||||
<Description>Simple loading and processing of textures and other data for FNA, including the ability to load non-XNB content files easily</Description>
|
||||
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes>
|
||||
<PackageTags>fna ellpeck mlem utility extensions data serialize</PackageTags>
|
||||
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
|
||||
|
||||
<!--TODO remove lidgren support eventually (methods marked as obsolete since 5.2.0)-->
|
||||
<PackageReference Include="Lidgren.Network" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json.Bson" Version="1.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<ProjectReference Include="..\FNA\FNA.Core.csproj">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
|
||||
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -3,11 +3,12 @@
|
|||
<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 data for MLEM Library for Extending MonoGame</Description>
|
||||
<Description>Simple loading and processing of textures and other data for MonoGame, including the ability to load non-XNB content files easily</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>
|
||||
|
@ -15,7 +16,6 @@
|
|||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -6,6 +6,7 @@ using Microsoft.Xna.Framework;
|
|||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Extensions;
|
||||
using MLEM.Textures;
|
||||
using static MLEM.Extensions.TextureExtensions;
|
||||
|
||||
namespace MLEM.Data {
|
||||
/// <summary>
|
||||
|
@ -33,7 +34,10 @@ namespace MLEM.Data {
|
|||
/// </summary>
|
||||
public TimeSpan LastTotalTime => this.LastCalculationTime + this.LastPackTime;
|
||||
|
||||
private readonly List<Request> textures = new List<Request>();
|
||||
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 bool autoIncreaseMaxWidth;
|
||||
private readonly bool forcePowerOfTwo;
|
||||
private readonly bool forceSquare;
|
||||
|
@ -42,13 +46,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;
|
||||
|
@ -58,37 +62,99 @@ namespace MLEM.Data {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new texture to this texture packer to be packed.
|
||||
/// 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.
|
||||
/// 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>
|
||||
public void Add(Texture2D texture, Action<TextureRegion> result) {
|
||||
this.Add(new TextureRegion(texture), result);
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <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 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) {
|
||||
/// <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) {
|
||||
if (this.PackedTexture != null)
|
||||
throw new InvalidOperationException("Cannot add texture to a texture packer that is already packed");
|
||||
if (texture.Width > this.maxWidth) {
|
||||
var paddedWidth = texture.Width + 2 * padding;
|
||||
if (paddedWidth > this.maxWidth) {
|
||||
if (this.autoIncreaseMaxWidth) {
|
||||
this.maxWidth = texture.Width;
|
||||
this.maxWidth = paddedWidth;
|
||||
} else {
|
||||
throw new InvalidOperationException($"Cannot add texture with width {texture.Width} to a texture packer with max width {this.maxWidth}");
|
||||
}
|
||||
}
|
||||
this.textures.Add(new Request(texture, result));
|
||||
this.texturesToPack.Add(new Request(texture, result, padding, padWithPixels));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// 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.
|
||||
/// The resulting texture will be stored in <see cref="PackedTexture"/>.
|
||||
/// All of the result callbacks that were added will also be invoked.
|
||||
/// </summary>
|
||||
|
@ -99,20 +165,23 @@ 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.textures.OrderByDescending(t => t.Texture.Width * t.Texture.Height)) {
|
||||
var area = this.FindFreeArea(new Point(request.Texture.Width, request.Texture.Height));
|
||||
request.PackedArea = area;
|
||||
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);
|
||||
}
|
||||
stopwatch.Stop();
|
||||
this.LastCalculationTime = stopwatch.Elapsed;
|
||||
|
||||
// figure out texture size and generate texture
|
||||
var width = this.textures.Max(t => t.PackedArea.Right);
|
||||
var height = this.textures.Max(t => t.PackedArea.Bottom);
|
||||
var width = this.alreadyPackedTextures.Max(t => t.PackedArea.Right);
|
||||
var height = this.alreadyPackedTextures.Max(t => t.PackedArea.Bottom);
|
||||
if (this.forcePowerOfTwo) {
|
||||
width = ToPowerOfTwo(width);
|
||||
height = ToPowerOfTwo(height);
|
||||
width = RuntimeTexturePacker.ToPowerOfTwo(width);
|
||||
height = RuntimeTexturePacker.ToPowerOfTwo(height);
|
||||
}
|
||||
if (this.forceSquare)
|
||||
width = height = Math.Max(width, height);
|
||||
|
@ -121,20 +190,21 @@ namespace MLEM.Data {
|
|||
// copy texture data onto the packed texture
|
||||
stopwatch.Restart();
|
||||
using (var data = this.PackedTexture.GetTextureData()) {
|
||||
foreach (var request in this.textures)
|
||||
CopyRegion(data, request);
|
||||
foreach (var request in this.alreadyPackedTextures)
|
||||
this.CopyRegion(data, request);
|
||||
}
|
||||
stopwatch.Stop();
|
||||
this.LastPackTime = stopwatch.Elapsed;
|
||||
|
||||
// invoke callbacks
|
||||
foreach (var request in this.textures) {
|
||||
request.Result.Invoke(new TextureRegion(this.PackedTexture, request.PackedArea));
|
||||
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));
|
||||
if (this.disposeTextures)
|
||||
request.Texture.Texture.Dispose();
|
||||
}
|
||||
|
||||
this.textures.Clear();
|
||||
this.ClearTempCollections();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -143,9 +213,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>
|
||||
|
@ -153,13 +223,17 @@ namespace MLEM.Data {
|
|||
this.Reset();
|
||||
}
|
||||
|
||||
private Rectangle FindFreeArea(Point size) {
|
||||
var pos = new Point(0, 0);
|
||||
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;
|
||||
var lowestY = int.MaxValue;
|
||||
while (true) {
|
||||
var intersected = false;
|
||||
var area = new Rectangle(pos, size);
|
||||
foreach (var tex in this.textures) {
|
||||
var area = new Rectangle(pos.X, pos.Y, size.X, size.Y);
|
||||
foreach (var tex in this.alreadyPackedTextures) {
|
||||
if (tex.PackedArea.Intersects(area)) {
|
||||
pos.X = tex.PackedArea.Right;
|
||||
// when we move down, we want to move down by the smallest intersecting texture's height
|
||||
|
@ -179,16 +253,51 @@ namespace MLEM.Data {
|
|||
}
|
||||
}
|
||||
|
||||
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 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 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) {
|
||||
|
@ -202,11 +311,15 @@ 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) {
|
||||
public Request(TextureRegion texture, Action<TextureRegion> result, int padding, bool padWithPixels) {
|
||||
this.Texture = texture;
|
||||
this.Result = result;
|
||||
this.Padding = padding;
|
||||
this.PadWithPixels = padWithPixels;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
41
MLEM.Extended/MLEM.Extended.FNA.csproj
Normal file
41
MLEM.Extended/MLEM.Extended.FNA.csproj
Normal file
|
@ -0,0 +1,41 @@
|
|||
<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.0.4">
|
||||
<PackageReference Include="FontStashSharp.MonoGame" Version="1.1.6">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
|
||||
|
|
|
@ -4,6 +4,7 @@ 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;
|
||||
|
@ -92,6 +93,20 @@ 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.
|
||||
|
@ -101,12 +116,8 @@ 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 ?? 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();
|
||||
var draw = drawFunction ?? IndividualTiledMapRenderer.DefaultDraw;
|
||||
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];
|
||||
|
@ -116,6 +127,26 @@ 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>
|
||||
|
@ -125,8 +156,17 @@ 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="SetMap"/> if no custom draw function is passed
|
||||
/// The default implementation of <see cref="DrawDelegate"/> that is used by <see cref="Draw"/> 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>
|
||||
|
@ -137,6 +177,18 @@ 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.
|
||||
|
@ -155,6 +207,13 @@ 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,7 +4,8 @@ using MonoGame.Extended.Tiled;
|
|||
|
||||
namespace MLEM.Extended.Tiled {
|
||||
/// <summary>
|
||||
/// A struct that represents a position on a <see cref="TiledMap"/> with multiple layers.
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public struct LayerPosition : IEquatable<LayerPosition> {
|
||||
|
@ -53,8 +54,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;
|
||||
}
|
||||
|
||||
|
@ -104,7 +105,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 Add(left, right);
|
||||
return LayerPosition.Add(left, right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -114,7 +115,16 @@ 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 Add(left, -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
132
MLEM.Extended/Tiled/LayerPositionF.cs
Normal file
132
MLEM.Extended/Tiled/LayerPositionF.cs
Normal file
|
@ -0,0 +1,132 @@
|
|||
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,7 +5,6 @@ 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 {
|
||||
|
@ -144,9 +143,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 (!StubTilesetTiles.TryGetValue(localId, out tilesetTile)) {
|
||||
if (!TiledExtensions.StubTilesetTiles.TryGetValue(localId, out tilesetTile)) {
|
||||
tilesetTile = new TiledMapTilesetTile(localId);
|
||||
StubTilesetTiles.Add(localId, tilesetTile);
|
||||
TiledExtensions.StubTilesetTiles.Add(localId, tilesetTile);
|
||||
}
|
||||
}
|
||||
return tilesetTile;
|
||||
|
@ -271,12 +270,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 = None) {
|
||||
public static RectangleF GetArea(this TiledMapObject obj, TiledMap map, Vector2? position = null, TiledMapTileFlipFlags flipFlags = TiledMapTileFlipFlags.None) {
|
||||
var tileSize = map.GetTileSize();
|
||||
var area = new RectangleF(obj.Position / tileSize, obj.Size / tileSize);
|
||||
if (flipFlags.HasFlag(FlipHorizontally))
|
||||
if (flipFlags.HasFlag(TiledMapTileFlipFlags.FlipHorizontally))
|
||||
area.X = 1 - area.X - area.Width;
|
||||
if (flipFlags.HasFlag(FlipVertically))
|
||||
if (flipFlags.HasFlag(TiledMapTileFlipFlags.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 ?? DefaultCollectCollisions;
|
||||
this.collisionFunction = collisionFunction ?? TiledMapCollisions.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
Normal file
70
MLEM.FNA.sln
Normal file
|
@ -0,0 +1,70 @@
|
|||
|
||||
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
|
37
MLEM.Startup/MLEM.Startup.FNA.csproj
Normal file
37
MLEM.Startup/MLEM.Startup.FNA.csproj
Normal file
|
@ -0,0 +1,37 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
<RootNamespace>MLEM.Startup</RootNamespace>
|
||||
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Authors>Ellpeck</Authors>
|
||||
<Description>MLEM Library for Extending FNA combined with some other useful libraries into a quick Game startup class</Description>
|
||||
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes>
|
||||
<PackageTags>fna ellpeck mlem utility extensions</PackageTags>
|
||||
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Coroutine" Version="2.1.3" />
|
||||
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.FNA.csproj" />
|
||||
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
|
||||
|
||||
<ProjectReference Include="..\FNA\FNA.Core.csproj">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
|
||||
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -18,7 +18,7 @@ namespace MLEM.Startup {
|
|||
/// <summary>
|
||||
/// The static game instance's input handler
|
||||
/// </summary>
|
||||
public static InputHandler Input => instance.InputHandler;
|
||||
public static InputHandler Input => MlemGame.instance.InputHandler;
|
||||
|
||||
/// <summary>
|
||||
/// This game's graphics device manager, initialized in the constructor
|
||||
|
@ -64,12 +64,14 @@ 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) {
|
||||
instance = this;
|
||||
MlemGame.instance = this;
|
||||
|
||||
this.GraphicsDeviceManager = new GraphicsDeviceManager(this) {
|
||||
PreferredBackBufferWidth = windowWidth,
|
||||
PreferredBackBufferHeight = windowHeight,
|
||||
#if !FNA
|
||||
HardwareModeSwitch = false
|
||||
#endif
|
||||
};
|
||||
this.Window.AllowUserResizing = true;
|
||||
this.Content.RootDirectory = "Content";
|
||||
|
@ -160,7 +162,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 instance.Content.Load<T>(name);
|
||||
return MlemGame.instance.Content.Load<T>(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"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,2 +0,0 @@
|
|||
{
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
using MLEM.Startup;
|
||||
|
||||
namespace TemplateNamespace {
|
||||
public class GameImpl : MlemGame {
|
||||
namespace TemplateNamespace;
|
||||
|
||||
public class GameImpl : MlemGame {
|
||||
|
||||
public static GameImpl Instance { get; private set; }
|
||||
|
||||
|
@ -9,5 +10,4 @@ namespace TemplateNamespace {
|
|||
Instance = this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
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);
|
||||
|
@ -10,5 +11,4 @@ namespace TemplateNamespace {
|
|||
game.Run();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<PublishReadyToRun>false</PublishReadyToRun>
|
||||
<TieredCompilation>false</TieredCompilation>
|
||||
<ApplicationIcon>Icon.ico</ApplicationIcon>
|
||||
|
@ -10,15 +10,18 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Contentless" Version="3.*" />
|
||||
<PackageReference Include="MLEM.Startup" Version="5.*" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.*" />
|
||||
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
|
||||
<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" />
|
||||
</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>
|
|
@ -1,2 +0,0 @@
|
|||
{
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
using MLEM.Startup;
|
||||
|
||||
namespace TemplateNamespace {
|
||||
public class GameImpl : MlemGame {
|
||||
namespace TemplateNamespace;
|
||||
|
||||
public class GameImpl : MlemGame {
|
||||
|
||||
public static GameImpl Instance { get; private set; }
|
||||
|
||||
|
@ -9,5 +10,4 @@ namespace TemplateNamespace {
|
|||
Instance = this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -5,8 +5,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MLEM.Startup" Version="5.*" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.*">
|
||||
<PackageReference Include="MLEM.Startup" Version="6.*" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.263">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Graphics;
|
||||
using MLEM.Misc;
|
||||
using MLEM.Textures;
|
||||
using MLEM.Ui.Style;
|
||||
|
@ -92,7 +93,7 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
var tex = this.Texture;
|
||||
var color = (Color) this.NormalColor * alpha;
|
||||
if (this.IsDisabled) {
|
||||
|
@ -103,7 +104,7 @@ namespace MLEM.Ui.Elements {
|
|||
color = (Color) this.HoveredColor * alpha;
|
||||
}
|
||||
batch.Draw(tex, this.DisplayArea, color, this.Scale);
|
||||
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
base.Draw(time, batch, alpha, context);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Graphics;
|
||||
using MLEM.Misc;
|
||||
using MLEM.Textures;
|
||||
using MLEM.Ui.Style;
|
||||
|
@ -108,7 +109,7 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
var tex = this.Texture;
|
||||
var color = Color.White * alpha;
|
||||
if (this.IsDisabled) {
|
||||
|
@ -123,7 +124,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, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
base.Draw(time, batch, alpha, context);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -55,8 +55,9 @@ namespace MLEM.Ui.Elements {
|
|||
/// Adds an element to this dropdown's <see cref="Panel"/>
|
||||
/// </summary>
|
||||
/// <param name="element">The element to add</param>
|
||||
public void AddElement(Element element) {
|
||||
this.Panel.AddChild(element);
|
||||
/// <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);
|
||||
// Since the dropdown causes elements to be over each other,
|
||||
// usual gamepad code doesn't apply
|
||||
element.GetGamepadNextElement = (dir, usualNext) => {
|
||||
|
@ -77,8 +78,9 @@ 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>
|
||||
public Element AddElement(string text, GenericCallback pressed = null) {
|
||||
return this.AddElement(p => text, pressed);
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -87,7 +89,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>
|
||||
public Element AddElement(Paragraph.TextCallback text, GenericCallback pressed = null) {
|
||||
/// <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) {
|
||||
var paragraph = new Paragraph(Anchor.AutoLeft, 1, text) {
|
||||
CanBeMoused = true,
|
||||
CanBeSelected = true,
|
||||
|
@ -97,7 +100,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);
|
||||
this.AddElement(paragraph, index);
|
||||
return paragraph;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ 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;
|
||||
|
@ -177,12 +178,12 @@ namespace MLEM.Ui.Elements {
|
|||
/// <summary>
|
||||
/// This element's transform matrix.
|
||||
/// Can easily be scaled using <see cref="ScaleTransform"/>.
|
||||
/// Note that, when this is non-null, a new <see cref="SpriteBatch.Begin"/> call is used for this element.
|
||||
/// Note that, when this is non-null, a new <c>SpriteBatch.Begin</c> call is used for this element.
|
||||
/// </summary>
|
||||
public Matrix Transform = Matrix.Identity;
|
||||
/// <summary>
|
||||
/// The call that this element should make to <see cref="SpriteBatch"/> to begin drawing.
|
||||
/// Note that, when this is non-null, a new <see cref="SpriteBatch.Begin"/> call is used for this element.
|
||||
/// Note that, when this is non-null, a new <c>SpriteBatch.Begin</c> call is used for this element.
|
||||
/// </summary>
|
||||
#pragma warning disable CS0618
|
||||
[Obsolete("BeginImpl is deprecated. You can create a custom element class and override Draw instead.")]
|
||||
|
@ -192,7 +193,14 @@ 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; set; } = true;
|
||||
public virtual bool CanBeSelected {
|
||||
get => this.canBeSelected;
|
||||
set {
|
||||
this.canBeSelected = value;
|
||||
if (!this.canBeSelected && this.Root?.SelectedElement == this)
|
||||
this.Root.SelectElement(null);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Set this field to false to disallow the element from reacting to being moused over.
|
||||
/// </summary>
|
||||
|
@ -234,13 +242,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"/> 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(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"/>.
|
||||
/// </summary>
|
||||
public virtual float DrawAlpha { get; set; } = 1;
|
||||
/// <summary>
|
||||
/// Stores whether this element is currently being moused over or touched.
|
||||
/// </summary>
|
||||
public bool IsMouseOver { get; protected set; }
|
||||
public bool IsMouseOver => this.Controls.MousedElement == this || this.Controls.TouchedElement == this;
|
||||
/// <summary>
|
||||
/// Returns whether this element is its <see cref="Root"/>'s <see cref="RootElement.SelectedElement"/>.
|
||||
/// </summary>
|
||||
|
@ -249,6 +257,12 @@ 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"/>.
|
||||
|
@ -422,6 +436,7 @@ 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.
|
||||
|
@ -433,11 +448,6 @@ 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;
|
||||
|
||||
|
@ -479,6 +489,8 @@ 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();
|
||||
|
@ -640,7 +652,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 + Epsilon) {
|
||||
if (newX + newSize.X <= parentArea.Right + Element.Epsilon) {
|
||||
pos.X = newX;
|
||||
pos.Y = prevArea.Y + this.ScaledOffset.Y;
|
||||
} else {
|
||||
|
@ -703,7 +715,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, Epsilon)) {
|
||||
if (!autoSize.Equals(this.UnscrolledArea.Size, Element.Epsilon)) {
|
||||
recursion++;
|
||||
if (recursion >= 16) {
|
||||
throw new ArithmeticException($"The area of {this} with root {this.Root.Name} has recursively updated too often. Does its child {foundChild} contain any conflicting auto-sizing settings?");
|
||||
|
@ -897,13 +909,14 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates this element and all of its <see cref="GetRelevantChildren"/>
|
||||
/// Updates this element and all of its <see cref="SortedChildren"/>
|
||||
/// </summary>
|
||||
/// <param name="time">The game's time</param>
|
||||
public virtual void Update(GameTime time) {
|
||||
this.System.InvokeOnElementUpdated(this, time);
|
||||
|
||||
foreach (var child in this.GetRelevantChildren()) {
|
||||
// update all sorted children, not just relevant ones, because they might become relevant or irrelevant through updates
|
||||
foreach (var child in this.SortedChildren) {
|
||||
if (child.System != null)
|
||||
child.Update(time);
|
||||
}
|
||||
|
@ -913,8 +926,8 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// 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>
|
||||
|
@ -924,25 +937,37 @@ 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 mat = this.Transform * matrix;
|
||||
var transformed = context;
|
||||
transformed.TransformMatrix = this.Transform * transformed.TransformMatrix;
|
||||
// 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
|
||||
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);
|
||||
}
|
||||
batch.Begin(transformed);
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
||||
// draw content in custom begin call
|
||||
this.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, mat);
|
||||
#pragma warning disable CS0618
|
||||
this.Draw(time, batch, alpha, transformed.BlendState, transformed.SamplerState, transformed.DepthStencilState, transformed.Effect, transformed.TransformMatrix);
|
||||
#pragma warning restore CS0618
|
||||
if (this.System != null)
|
||||
this.System.Metrics.Draws++;
|
||||
|
||||
|
@ -950,13 +975,13 @@ namespace MLEM.Ui.Elements {
|
|||
// end our draw
|
||||
batch.End();
|
||||
// begin the usual draw again for other elements
|
||||
batch.Begin(SpriteSortMode.Deferred, blendState, samplerState, depthStencilState, null, effect, matrix);
|
||||
batch.Begin(context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws this element and all of its children. Override this method to draw the content of custom elements.
|
||||
/// Note that, when this is called, <see cref="SpriteBatch.Begin"/> has already been called with custom <see cref="Transform"/> etc. applied.
|
||||
/// Note that, when this is called, <c>SpriteBatch.Begin</c> has already been called with custom <see cref="Transform"/> etc. applied.
|
||||
/// </summary>
|
||||
/// <param name="time">The game's time</param>
|
||||
/// <param name="batch">The sprite batch to use for drawing</param>
|
||||
|
@ -966,21 +991,37 @@ 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)
|
||||
child.DrawTransformed(time, batch, alpha * child.DrawAlpha, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws this element and all of its <see cref="GetRelevantChildren"/> early.
|
||||
/// Drawing early involves drawing onto <see cref="RenderTarget2D"/> instances rather than onto the screen.
|
||||
/// Note that, when this is called, <see cref="SpriteBatch.Begin"/> has not yet been called.
|
||||
/// Note that, when this is called, <c>SpriteBatch.Begin</c> has not yet been called.
|
||||
/// </summary>
|
||||
/// <param name="time">The game's time</param>
|
||||
/// <param name="batch">The sprite batch to use for drawing</param>
|
||||
|
@ -1128,7 +1169,7 @@ namespace MLEM.Ui.Elements {
|
|||
public delegate void OtherElementCallback(Element thisElement, Element otherElement);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate used inside of <see cref="Element.Draw"/>
|
||||
/// A delegate used inside of <see cref="Element.Draw(Microsoft.Xna.Framework.GameTime,Microsoft.Xna.Framework.Graphics.SpriteBatch,float,MLEM.Graphics.SpriteBatchContext)"/>
|
||||
/// </summary>
|
||||
/// <param name="element">The element that is being drawn</param>
|
||||
/// <param name="time">The game's time</param>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MLEM.Input;
|
||||
|
@ -115,9 +116,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)"/>
|
||||
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);
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -135,9 +136,12 @@ 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) {
|
||||
string GetCurrentName() => 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, Func<GenericInput, IEnumerable<GenericInput>, bool> isKeybindAllowed = null) {
|
||||
string GetCurrentName() {
|
||||
return keybind.TryGetCombination(index, out var combination) ? combination.ToString(" + ", inputName) : unboundPlaceholder;
|
||||
}
|
||||
|
||||
var button = new Button(anchor, size, GetCurrentName());
|
||||
var activeNext = false;
|
||||
|
@ -150,19 +154,21 @@ namespace MLEM.Ui.Elements {
|
|||
button.SetData("Active", true);
|
||||
activeNext = false;
|
||||
} else if (button.GetData<bool>("Active")) {
|
||||
if (unbind != null && unbind.IsPressed(inputHandler)) {
|
||||
if (unbind != null && unbind.TryConsumePressed(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());
|
||||
keybind.Remove((c, i) => i == index).Insert(index, key, mods.ToArray());
|
||||
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);
|
||||
button.Text.Text = GetCurrentName();
|
||||
button.SetData("Active", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
button.Text.Text = GetCurrentName();
|
||||
}
|
||||
|
@ -184,7 +190,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 new Tooltip(textCallback, element);
|
||||
return element.AddTooltip(new Tooltip(textCallback));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -194,7 +200,18 @@ 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 new Tooltip(text, element);
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Graphics;
|
||||
|
||||
namespace MLEM.Ui.Elements {
|
||||
/// <summary>
|
||||
|
@ -20,10 +21,10 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
// since the group never accesses its own area when drawing, we have to update it manually
|
||||
this.UpdateAreaIfDirty();
|
||||
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
base.Draw(time, batch, alpha, context);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
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;
|
||||
|
@ -103,7 +105,7 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
if (this.Texture == null)
|
||||
return;
|
||||
var center = new Vector2(this.Texture.Width / 2F, this.Texture.Height / 2F);
|
||||
|
@ -116,7 +118,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, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
base.Draw(time, batch, alpha, context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -4,6 +4,7 @@ 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;
|
||||
|
@ -120,7 +121,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, Epsilon)) {
|
||||
if (!child.ScrollOffset.Equals(offset, Element.Epsilon)) {
|
||||
child.ScrollOffset = offset;
|
||||
this.relevantChildrenDirty = true;
|
||||
}
|
||||
|
@ -167,7 +168,7 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
// draw children onto the render target if we have one
|
||||
if (this.scrollOverflow && this.renderTarget != null) {
|
||||
this.UpdateAreaIfDirty();
|
||||
|
@ -179,21 +180,22 @@ 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
|
||||
batch.Begin(SpriteSortMode.Deferred, blendState, samplerState, depthStencilState, null, effect, trans);
|
||||
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, trans);
|
||||
var trans = context;
|
||||
trans.TransformMatrix = Matrix.CreateTranslation(-area.X, -area.Y, 0);
|
||||
batch.Begin(trans);
|
||||
base.Draw(time, batch, alpha, trans);
|
||||
batch.End();
|
||||
}
|
||||
batch.GraphicsDevice.PresentationParameters.RenderTargetUsage = lastUsage;
|
||||
batch.Begin(SpriteSortMode.Deferred, blendState, samplerState, depthStencilState, null, effect, matrix);
|
||||
batch.Begin(context);
|
||||
}
|
||||
|
||||
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, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
base.Draw(time, batch, alpha, context);
|
||||
} 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);
|
||||
|
@ -244,13 +246,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, Epsilon)) {
|
||||
if (!this.ScrollBar.MaxValue.Equals(scrollBarMax, Element.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, Epsilon)) {
|
||||
if (!this.scrollBarChildOffset.Equals(childOffset, Element.Epsilon)) {
|
||||
this.ChildPadding += new Padding(0, -this.scrollBarChildOffset + childOffset, 0, 0);
|
||||
this.scrollBarChildOffset = childOffset;
|
||||
this.SetAreaDirty();
|
||||
|
|
|
@ -6,6 +6,7 @@ using MLEM.Extensions;
|
|||
using MLEM.Font;
|
||||
using MLEM.Formatting;
|
||||
using MLEM.Formatting.Codes;
|
||||
using MLEM.Graphics;
|
||||
using MLEM.Misc;
|
||||
using MLEM.Ui.Style;
|
||||
|
||||
|
@ -58,8 +59,13 @@ 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,9 +104,13 @@ 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.
|
||||
|
@ -109,19 +119,13 @@ 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, "", autoAdjustWidth) {
|
||||
public Paragraph(Anchor anchor, float width, TextCallback textCallback, bool autoAdjustWidth = false) : this(anchor, width, string.Empty, 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;
|
||||
|
@ -131,8 +135,8 @@ namespace MLEM.Ui.Elements {
|
|||
protected override Vector2 CalcActualSize(RectangleF parentArea) {
|
||||
var size = base.CalcActualSize(parentArea);
|
||||
this.ParseText(size);
|
||||
var (w, h) = this.TokenizedText.Measure(this.RegularFont) * this.TextScale * this.TextScaleMultiplier * this.Scale;
|
||||
return new Vector2(this.AutoAdjustWidth ? w + this.ScaledPadding.Width : size.X, h + this.ScaledPadding.Height);
|
||||
var textSize = this.TokenizedText.Measure(this.RegularFont) * this.TextScale * this.TextScaleMultiplier * this.Scale;
|
||||
return new Vector2(this.AutoAdjustWidth ? textSize.X + this.ScaledPadding.Width : size.X, textSize.Y + this.ScaledPadding.Height);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -144,12 +148,12 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
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, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
base.Draw(time, batch, alpha, context);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -193,7 +197,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, Epsilon))
|
||||
if (!this.AreaDirty && !this.CalcActualSize(this.ParentArea).Equals(this.DisplayArea.Size, Element.Epsilon))
|
||||
this.SetAreaDirty();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ 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;
|
||||
|
@ -73,7 +74,7 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
batch.Draw(this.Texture, this.DisplayArea, (Color) this.Color * alpha, this.Scale);
|
||||
|
||||
var percentage = this.CurrentValue / this.MaxValue;
|
||||
|
@ -106,7 +107,7 @@ namespace MLEM.Ui.Elements {
|
|||
} else {
|
||||
batch.Draw(batch.GetBlankTexture(), offsetArea, (Color) this.ProgressColor * alpha);
|
||||
}
|
||||
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
base.Draw(time, batch, alpha, context);
|
||||
}
|
||||
|
||||
/// <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="Group"/> 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="RootElement"/> 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;
|
||||
foreach (var sib in this.GetSiblings()) {
|
||||
if (sib is RadioButton radio && radio.Group == this.Group)
|
||||
radio.Checked = false;
|
||||
}
|
||||
this.Root.Element.AndChildren(e => {
|
||||
if (e != this && e is RadioButton r && r.Group == this.Group)
|
||||
r.Checked = false;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ 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;
|
||||
|
@ -44,7 +46,7 @@ namespace MLEM.Ui.Elements {
|
|||
// force current value to be clamped
|
||||
this.CurrentValue = this.CurrentValue;
|
||||
// auto-hide if necessary
|
||||
var shouldHide = this.maxValue <= Epsilon;
|
||||
var shouldHide = this.maxValue <= Element.Epsilon;
|
||||
if (this.AutoHideWhenEmpty && this.IsHidden != shouldHide) {
|
||||
this.IsHidden = shouldHide;
|
||||
this.OnAutoHide?.Invoke(this);
|
||||
|
@ -137,7 +139,7 @@ namespace MLEM.Ui.Elements {
|
|||
|
||||
// MOUSE INPUT
|
||||
var moused = this.Controls.MousedElement;
|
||||
if (moused == this && this.Input.IsMouseButtonPressed(MouseButton.Left)) {
|
||||
if (moused == this && this.Input.WasMouseButtonUp(MouseButton.Left) && this.Input.IsMouseButtonDown(MouseButton.Left)) {
|
||||
this.isMouseHeld = true;
|
||||
this.scrollStartOffset = this.TransformInverseAll(this.Input.ViewportMousePosition.ToVector2()) - this.ScrollerPosition;
|
||||
} else if (this.isMouseHeld && !this.Input.IsMouseButtonDown(MouseButton.Left)) {
|
||||
|
@ -187,31 +189,31 @@ namespace MLEM.Ui.Elements {
|
|||
|
||||
if (this.SmoothScrolling && this.scrollAdded != 0) {
|
||||
this.scrollAdded *= this.SmoothScrollFactor;
|
||||
if (Math.Abs(this.scrollAdded) <= Epsilon)
|
||||
if (Math.Abs(this.scrollAdded) <= Element.Epsilon)
|
||||
this.scrollAdded = 0;
|
||||
this.OnValueChanged?.Invoke(this, this.CurrentValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void ScrollToPos(Vector2 position) {
|
||||
var (width, height) = this.ScrollerSize * this.Scale;
|
||||
var size = this.ScrollerSize * this.Scale;
|
||||
if (this.Horizontal) {
|
||||
var offset = this.scrollStartOffset.X >= 0 && this.scrollStartOffset.X <= width ? this.scrollStartOffset.X : width / 2;
|
||||
this.CurrentValue = (position.X - this.Area.X - offset) / (this.Area.Width - width) * this.MaxValue;
|
||||
var offset = this.scrollStartOffset.X >= 0 && this.scrollStartOffset.X <= size.X ? this.scrollStartOffset.X : size.X / 2;
|
||||
this.CurrentValue = (position.X - this.Area.X - offset) / (this.Area.Width - size.X) * this.MaxValue;
|
||||
} else {
|
||||
var offset = this.scrollStartOffset.Y >= 0 && this.scrollStartOffset.Y <= height ? this.scrollStartOffset.Y : height / 2;
|
||||
this.CurrentValue = (position.Y - this.Area.Y - offset) / (this.Area.Height - height) * this.MaxValue;
|
||||
var offset = this.scrollStartOffset.Y >= 0 && this.scrollStartOffset.Y <= size.Y ? this.scrollStartOffset.Y : size.Y / 2;
|
||||
this.CurrentValue = (position.Y - this.Area.Y - offset) / (this.Area.Height - size.Y) * this.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
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, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
base.Draw(time, batch, alpha, context);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -30,9 +30,9 @@ namespace MLEM.Ui.Elements {
|
|||
base.Update(time);
|
||||
|
||||
if (this.IsSelected) {
|
||||
if (this.Controls.LeftButtons.IsPressed(this.Input, this.Controls.GamepadIndex)) {
|
||||
if (this.CurrentValue > 0 && this.Controls.LeftButtons.TryConsumePressed(this.Input, this.Controls.GamepadIndex)) {
|
||||
this.CurrentValue -= this.StepPerScroll;
|
||||
} else if (this.Controls.RightButtons.IsPressed(this.Input, this.Controls.GamepadIndex)) {
|
||||
} else if (this.CurrentValue < this.MaxValue && this.Controls.RightButtons.TryConsumePressed(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 (SquishChild(child, out var squished))
|
||||
if (SquishingGroup.SquishChild(child, out var squished))
|
||||
child.SetAreaAndUpdateChildren(squished);
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ namespace MLEM.Ui.Elements {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!pos.Equals(element.Area.Location, Epsilon) || !size.Equals(element.Area.Size, Epsilon)) {
|
||||
if (!pos.Equals(element.Area.Location, Element.Epsilon) || !size.Equals(element.Area.Size, Element.Epsilon)) {
|
||||
squishedArea = new RectangleF(pos, size);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
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;
|
||||
|
@ -18,57 +14,22 @@ 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 {
|
||||
|
||||
/// <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;
|
||||
/// <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>
|
||||
/// The color that this text field's text should display with
|
||||
|
@ -93,23 +54,23 @@ namespace MLEM.Ui.Elements {
|
|||
/// <summary>
|
||||
/// The scale that this text field should render text with
|
||||
/// </summary>
|
||||
public StyleProp<float> TextScale;
|
||||
public StyleProp<float> TextScale {
|
||||
get => this.textScale;
|
||||
set {
|
||||
this.textScale = value;
|
||||
this.textInput.TextScale = value;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The font that this text field should display text with
|
||||
/// </summary>
|
||||
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;
|
||||
public StyleProp<GenericFont> Font {
|
||||
get => this.font;
|
||||
set {
|
||||
this.font = value;
|
||||
this.textInput.Font = value;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The x position that text should start rendering at, based on the x position of this text field.
|
||||
/// </summary>
|
||||
|
@ -118,11 +79,48 @@ namespace MLEM.Ui.Elements {
|
|||
/// The width that the caret should render with, in pixels
|
||||
/// </summary>
|
||||
public StyleProp<float> CaretWidth;
|
||||
/// <summary>
|
||||
/// The rule used for text input.
|
||||
/// Rules allow only certain characters to be allowed inside of a text field.
|
||||
/// </summary>
|
||||
|
||||
/// <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
|
||||
/// </summary>
|
||||
public string PlaceholderText;
|
||||
/// <summary>
|
||||
/// The title of the <c>KeyboardInput</c> field on mobile devices and consoles
|
||||
/// </summary>
|
||||
|
@ -131,72 +129,10 @@ 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 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;
|
||||
private readonly TextInput textInput;
|
||||
private StyleProp<GenericFont> font;
|
||||
private StyleProp<float> textScale;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new text field with the given settings
|
||||
|
@ -208,7 +144,12 @@ 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.InputRule = rule ?? DefaultRule;
|
||||
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.Multiline = multiline;
|
||||
if (font != null)
|
||||
this.Font = font;
|
||||
|
@ -217,75 +158,36 @@ namespace MLEM.Ui.Elements {
|
|||
|
||||
MlemPlatform.EnsureExists();
|
||||
|
||||
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) {
|
||||
this.OnPressed += async e => {
|
||||
var title = this.MobileTitle ?? this.PlaceholderText;
|
||||
var result = await MlemPlatform.Current.OpenOnScreenKeyboard(title, this.MobileDescription, this.Text, false);
|
||||
if (result != null)
|
||||
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);
|
||||
|
||||
// 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;
|
||||
if (this.IsSelected && !this.IsHidden)
|
||||
this.textInput.Update(time, this.Input);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
|
||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
|
||||
var tex = this.Texture;
|
||||
var color = Color.White * alpha;
|
||||
if (this.IsMouseOver) {
|
||||
|
@ -294,101 +196,31 @@ 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 offset = new Vector2(
|
||||
var textPos = this.DisplayArea.Location + new Vector2(
|
||||
this.TextOffsetX * this.Scale,
|
||||
this.Multiline ? this.TextOffsetX * this.Scale : this.DisplayArea.Height / 2 - lineHeight / 2);
|
||||
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);
|
||||
}
|
||||
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);
|
||||
} 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, blendState, samplerState, depthStencilState, effect, matrix);
|
||||
base.Draw(time, batch, alpha, context);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <inheritdoc cref="TextInput.SetText"/>
|
||||
public void SetText(object text, bool removeMismatching = false) {
|
||||
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();
|
||||
this.textInput.SetText(text, removeMismatching);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <inheritdoc cref="TextInput.InsertText"/>
|
||||
public void InsertText(object text, bool removeMismatching = false) {
|
||||
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();
|
||||
this.textInput.InsertText(text, removeMismatching);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <inheritdoc cref="TextInput.RemoveText"/>
|
||||
public void RemoveText(int index, int 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;
|
||||
this.textInput.RemoveText(index, length);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -403,154 +235,6 @@ 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,5 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MLEM.Extensions;
|
||||
using MLEM.Input;
|
||||
using MLEM.Ui.Style;
|
||||
|
||||
|
@ -11,6 +13,13 @@ 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>
|
||||
|
@ -23,9 +32,41 @@ 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.
|
||||
|
@ -45,6 +86,9 @@ 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
|
||||
|
@ -53,8 +97,11 @@ 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)
|
||||
this.Paragraph = this.AddChild(new Paragraph(Anchor.TopLeft, 0, text));
|
||||
if (text != null) {
|
||||
#pragma warning disable CS0618
|
||||
this.Paragraph = this.AddParagraph(text);
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
this.Init(elementToHover);
|
||||
}
|
||||
|
||||
|
@ -65,7 +112,9 @@ 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) {
|
||||
this.Paragraph = this.AddChild(new Paragraph(Anchor.TopLeft, 0, textCallback));
|
||||
#pragma warning disable CS0618
|
||||
this.Paragraph = this.AddParagraph(textCallback);
|
||||
#pragma warning restore CS0618
|
||||
this.Init(elementToHover);
|
||||
}
|
||||
|
||||
|
@ -101,11 +150,47 @@ 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);
|
||||
if (this.Paragraph != null) {
|
||||
this.Paragraph.TextColor = this.Paragraph.TextColor.OrStyle(style.TooltipTextColor, 1);
|
||||
this.Paragraph.Size = new Vector2(style.TooltipTextWidth, 0);
|
||||
this.UpdateParagraphsStyles();
|
||||
}
|
||||
|
||||
/// <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>
|
||||
|
@ -171,7 +256,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) {
|
||||
if (this.DisplayInAutoNavMode && e.Controls.IsAutoNavMode) {
|
||||
this.snapElement = e;
|
||||
this.Display(e.System, $"{e.GetType().Name}Tooltip");
|
||||
}
|
||||
|
@ -185,9 +270,6 @@ 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;
|
||||
|
@ -210,5 +292,23 @@ 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
35
MLEM.Ui/MLEM.Ui.FNA.csproj
Normal file
35
MLEM.Ui/MLEM.Ui.FNA.csproj
Normal file
|
@ -0,0 +1,35 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
<RootNamespace>MLEM.Ui</RootNamespace>
|
||||
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Authors>Ellpeck</Authors>
|
||||
<Description>A mouse, keyboard, gamepad and touch ready Ui system for FNA that features automatic anchoring, sizing and several ready-to-use element types</Description>
|
||||
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes>
|
||||
<PackageTags>fna ellpeck mlem ui user interface graphical gui system mouse keyboard gamepad touch</PackageTags>
|
||||
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TextCopy" Version="6.1.0" />
|
||||
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
|
||||
|
||||
<ProjectReference Include="..\FNA\FNA.Core.csproj">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../Media/Logo.png" Pack="true" PackagePath="" />
|
||||
<None Include="../Docs/index.md" Pack="true" PackagePath="README.md" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<Authors>Ellpeck</Authors>
|
||||
<Description>A mouse, keyboard, gamepad and touch ready Ui system that features automatic anchoring, sizing and several ready-to-use element types</Description>
|
||||
<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>
|
||||
<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="4.3.1" />
|
||||
<PackageReference Include="TextCopy" Version="6.1.0" />
|
||||
<ProjectReference Include="..\MLEM\MLEM.csproj" />
|
||||
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
|
||||
|
|
298
MLEM.Ui/Parsers/UiMarkdownParser.cs
Normal file
298
MLEM.Ui/Parsers/UiMarkdownParser.cs
Normal file
|
@ -0,0 +1,298 @@
|
|||
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 whose <see cref="RootElement.CanSelectContent"/> property is true.
|
||||
/// The active root element is the one with the highest <see cref="RootElement.Priority"/> that <see cref="RootElement.CanBeActive"/>.
|
||||
/// </summary>
|
||||
public RootElement ActiveRoot { get; protected set; }
|
||||
/// <summary>
|
||||
|
@ -155,29 +155,33 @@ namespace MLEM.Ui {
|
|||
public virtual void Update() {
|
||||
if (this.IsInputOurs)
|
||||
this.Input.Update();
|
||||
this.ActiveRoot = this.System.GetRootElements().FirstOrDefault(root => !root.Element.IsHidden && root.CanSelectContent);
|
||||
this.ActiveRoot = this.System.GetRootElements().FirstOrDefault(root => root.CanBeActive);
|
||||
|
||||
// MOUSE INPUT
|
||||
if (this.HandleMouse) {
|
||||
var mousedNow = this.GetElementUnderPos(this.Input.ViewportMousePosition.ToVector2());
|
||||
var mousedNow = this.GetElementUnderPos(new Vector2(this.Input.ViewportMousePosition.X, this.Input.ViewportMousePosition.Y));
|
||||
this.SetMousedElement(mousedNow);
|
||||
|
||||
if (this.Input.IsMouseButtonPressed(MouseButton.Left)) {
|
||||
if (this.Input.IsMouseButtonPressedAvailable(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);
|
||||
} else if (this.Input.IsMouseButtonPressed(MouseButton.Right)) {
|
||||
this.Input.TryConsumeMouseButtonPressed(MouseButton.Left);
|
||||
}
|
||||
} else if (this.Input.IsMouseButtonPressedAvailable(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.IsPressed(this.Input, this.GamepadIndex)) {
|
||||
if (this.KeyboardButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
|
||||
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) {
|
||||
if (this.Input.IsModifierKeyDown(ModifierKey.Shift)) {
|
||||
// secondary action on element using space or enter
|
||||
|
@ -186,15 +190,19 @@ 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.IsKeyPressed(Keys.Tab)) {
|
||||
} else if (this.Input.IsKeyPressedAvailable(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,20 +238,28 @@ namespace MLEM.Ui {
|
|||
|
||||
// GAMEPAD INPUT
|
||||
if (this.HandleGamepad) {
|
||||
if (this.GamepadButtons.IsPressed(this.Input, this.GamepadIndex)) {
|
||||
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed)
|
||||
if (this.GamepadButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
|
||||
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) {
|
||||
this.System.InvokeOnElementPressed(this.SelectedElement);
|
||||
} else if (this.SecondaryGamepadButtons.IsPressed(this.Input, this.GamepadIndex)) {
|
||||
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed)
|
||||
this.GamepadButtons.TryConsumePressed(this.Input, this.GamepadIndex);
|
||||
}
|
||||
} else if (this.SecondaryGamepadButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
|
||||
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) {
|
||||
this.System.InvokeOnElementSecondaryPressed(this.SelectedElement);
|
||||
} 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -339,8 +355,6 @@ 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;
|
||||
}
|
||||
|
||||
|
@ -353,15 +367,17 @@ 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);
|
||||
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);
|
||||
if (this.SelectedElement?.Root != this.ActiveRoot) {
|
||||
return backward ? children.LastOrDefault(c => c.CanBeSelected) : children.FirstOrDefault(c => c.CanBeSelected);
|
||||
// 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();
|
||||
} 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)
|
||||
|
@ -386,17 +402,20 @@ 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);
|
||||
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);
|
||||
if (this.SelectedElement?.Root != this.ActiveRoot) {
|
||||
return children.FirstOrDefault(c => c.CanBeSelected);
|
||||
// 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);
|
||||
} else {
|
||||
Element closest = null;
|
||||
float closestPriority = 0;
|
||||
foreach (var child in children) {
|
||||
if (!child.CanBeSelected || child == this.SelectedElement)
|
||||
if (child == this.SelectedElement)
|
||||
continue;
|
||||
var (xOffset, yOffset) = child.Area.Center - this.SelectedElement.Area.Center;
|
||||
var angle = Math.Abs(direction.Angle() - (float) Math.Atan2(yOffset, xOffset));
|
||||
var offset = child.Area.Center - this.SelectedElement.Area.Center;
|
||||
var angle = Math.Abs(MathHelper.WrapAngle(direction.Angle() - (float) Math.Atan2(offset.Y, offset.X)));
|
||||
if (angle >= MathHelper.PiOver2 - Element.Epsilon)
|
||||
continue;
|
||||
var distSq = child.Area.DistanceSquared(this.SelectedElement.Area);
|
||||
|
@ -411,13 +430,16 @@ namespace MLEM.Ui {
|
|||
}
|
||||
}
|
||||
|
||||
private void HandleGamepadNextElement(Direction2 dir) {
|
||||
private bool 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,7 +5,6 @@ using MLEM.Ui.Elements;
|
|||
namespace MLEM.Ui {
|
||||
/// <summary>
|
||||
/// A snapshot of update and rendering statistics from <see cref="UiSystem.Metrics"/> to be used for runtime debugging and profiling.
|
||||
/// This metrics struct works similarly to <see cref="GraphicsMetrics"/>.
|
||||
/// </summary>
|
||||
public struct UiMetrics {
|
||||
|
||||
|
@ -33,12 +32,12 @@ namespace MLEM.Ui {
|
|||
public uint Updates { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time that <see cref="Element.Draw"/> took.
|
||||
/// The amount of time that <see cref="Element.Draw(Microsoft.Xna.Framework.GameTime,Microsoft.Xna.Framework.Graphics.SpriteBatch,float,MLEM.Graphics.SpriteBatchContext)"/> 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"/> was called.
|
||||
/// The amount of times that <see cref="Element.Draw(Microsoft.Xna.Framework.GameTime,Microsoft.Xna.Framework.Graphics.SpriteBatch,float,MLEM.Graphics.SpriteBatchContext)"/> was called.
|
||||
/// </summary>
|
||||
public uint Draws { get; internal set; }
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ 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;
|
||||
|
@ -77,23 +78,32 @@ 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>
|
||||
public SamplerState SamplerState = SamplerState.PointClamp;
|
||||
[Obsolete("Set this through SpriteBatchContext instead")]
|
||||
public SamplerState SamplerState;
|
||||
/// <summary>
|
||||
/// The depth stencil state that this ui system and all of its elements draw with.
|
||||
/// The default is <see cref="Microsoft.Xna.Framework.Graphics.DepthStencilState.None"/>, which is also the default for <see cref="SpriteBatch.Begin"/>.
|
||||
/// The default is <see cref="Microsoft.Xna.Framework.Graphics.DepthStencilState.None"/>, which is also the default for <c>SpriteBatch.Begin</c>.
|
||||
/// </summary>
|
||||
public DepthStencilState DepthStencilState = DepthStencilState.None;
|
||||
[Obsolete("Set this through SpriteBatchContext instead")]
|
||||
public DepthStencilState DepthStencilState;
|
||||
/// <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>
|
||||
|
@ -231,10 +241,10 @@ namespace MLEM.Ui {
|
|||
MlemPlatform.Current?.AddTextInputListener(game.Window, (sender, key, character) => this.ApplyToAll(e => e.OnTextInput?.Invoke(e, key, character)));
|
||||
|
||||
if (automaticViewport) {
|
||||
this.Viewport = new Rectangle(Point.Zero, game.Window.ClientBounds.Size);
|
||||
this.AutoScaleReferenceSize = this.Viewport.Size;
|
||||
this.Viewport = new Rectangle(0, 0, game.Window.ClientBounds.Width, game.Window.ClientBounds.Height);
|
||||
this.AutoScaleReferenceSize = new Point(this.Viewport.Width, this.Viewport.Height);
|
||||
game.Window.ClientSizeChanged += (sender, args) => {
|
||||
this.Viewport = new Rectangle(Point.Zero, game.Window.ClientBounds.Size);
|
||||
this.Viewport = new Rectangle(0, 0, game.Window.ClientBounds.Width, game.Window.ClientBounds.Height);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -296,9 +306,24 @@ namespace MLEM.Ui {
|
|||
foreach (var root in this.rootElements) {
|
||||
if (root.Element.IsHidden)
|
||||
continue;
|
||||
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);
|
||||
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.End();
|
||||
}
|
||||
|
||||
|
@ -483,7 +508,6 @@ 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"/>
|
||||
|
@ -497,7 +521,6 @@ 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.
|
||||
|
@ -514,7 +537,6 @@ 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.
|
||||
|
@ -524,7 +546,6 @@ 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>.
|
||||
|
@ -532,9 +553,17 @@ 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.
|
||||
/// This value is automatically calculated, and used in <see cref="CanBeActive"/>.
|
||||
/// </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.
|
||||
|
@ -553,6 +582,10 @@ 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 <see cref="SpriteBatch.Begin"/> calls to render based on the camera's viewport.
|
||||
/// Use this in your <c>SpriteBatch.Begin</c> calls to render based on the camera's viewport.
|
||||
/// </summary>
|
||||
public Matrix ViewMatrix {
|
||||
get {
|
||||
|
@ -105,7 +105,7 @@ namespace MLEM.Cameras {
|
|||
/// <param name="roundPosition">Whether the camera's <see cref="Position"/> should be rounded to full integers when calculating the <see cref="ViewMatrix"/></param>
|
||||
public Camera(GraphicsDevice graphicsDevice, bool roundPosition = true) {
|
||||
this.graphicsDevice = graphicsDevice;
|
||||
this.AutoScaleReferenceSize = this.Viewport.Size;
|
||||
this.AutoScaleReferenceSize = new Point(this.Viewport.Width, this.Viewport.Height);
|
||||
this.RoundPosition = roundPosition;
|
||||
}
|
||||
|
||||
|
@ -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, Epsilon);
|
||||
return !this.Position.Equals(lastPos, Camera.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 ?? this.Viewport.Size.ToVector2() / 2) / this.ActualScale;
|
||||
var center = (zoomCenter ?? new Vector2(this.Viewport.Width, this.Viewport.Height) / 2) / this.ActualScale;
|
||||
var lastScale = this.Scale;
|
||||
this.Scale += delta;
|
||||
this.Position += center * ((this.Scale - lastScale) / this.Scale);
|
||||
|
|
|
@ -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 (!Cache.TryGetValue(c, out var ret)) {
|
||||
if (!CharExtensions.Cache.TryGetValue(c, out var ret)) {
|
||||
ret = c.ToString();
|
||||
Cache.Add(c, ret);
|
||||
CharExtensions.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 ? FromHexRgba(val) : FromHexRgb(val);
|
||||
return value.Length > 6 ? ColorHelper.FromHexRgba(val) : ColorHelper.FromHexRgb(val);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,18 +21,18 @@ namespace MLEM.Extensions {
|
|||
manager.IsFullScreen = fullscreen;
|
||||
if (fullscreen) {
|
||||
var view = manager.GraphicsDevice.Viewport;
|
||||
lastWidth = view.Width;
|
||||
lastHeight = view.Height;
|
||||
GraphicsExtensions.lastWidth = view.Width;
|
||||
GraphicsExtensions.lastHeight = view.Height;
|
||||
|
||||
var curr = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode;
|
||||
manager.PreferredBackBufferWidth = curr.Width;
|
||||
manager.PreferredBackBufferHeight = curr.Height;
|
||||
} else {
|
||||
if (lastWidth <= 0 || lastHeight <= 0)
|
||||
if (GraphicsExtensions.lastWidth <= 0 || GraphicsExtensions.lastHeight <= 0)
|
||||
throw new InvalidOperationException("Can't call SetFullscreen to change out of fullscreen mode without going into fullscreen mode first");
|
||||
|
||||
manager.PreferredBackBufferWidth = lastWidth;
|
||||
manager.PreferredBackBufferHeight = lastHeight;
|
||||
manager.PreferredBackBufferWidth = GraphicsExtensions.lastWidth;
|
||||
manager.PreferredBackBufferHeight = GraphicsExtensions.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;
|
||||
var (width, height) = (window.ClientBounds.Width, window.ClientBounds.Height);
|
||||
manager.PreferredBackBufferWidth = Math.Max(height, width);
|
||||
manager.PreferredBackBufferHeight = Math.Min(height, width);
|
||||
manager.ApplyChanges();
|
||||
|
@ -90,7 +90,12 @@ namespace MLEM.Extensions {
|
|||
/// <param name="target">The target to apply</param>
|
||||
public TargetContext(GraphicsDevice device, RenderTarget2D target) {
|
||||
this.device = device;
|
||||
#if FNA
|
||||
// RenderTargetCount doesn't exist in FNA but we still want the optimization in MG
|
||||
this.lastTargets = device.GetRenderTargets();
|
||||
#else
|
||||
this.lastTargets = device.RenderTargetCount <= 0 ? null : device.GetRenderTargets();
|
||||
#endif
|
||||
device.SetRenderTarget(target);
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue