1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-24 21:48:35 +01:00

Compare commits

...

7 commits

41 changed files with 361 additions and 113 deletions

View file

@ -13,12 +13,17 @@ Additions
- Added GenericFont SplitStringSeparate which differentiates between existing newline characters and splits due to maximum width
- Added StaticSpriteBatch class
- Added missing easing functions Quart and Quint to Easings
- Added RotationVector extension methods for Matrix and Quaternion
Improvements
- Cache TokenizedString inner offsets for non-Left text alignments to improve performance
- Exposed Camera's RoundPosition
- Exposed the epsilon value used by Camera
- Added Padding.Empty
- Throw an exception when text formatter macros resolve recursively too many times
Fixes
- Fixed some end-of-line inconsistencies when using the Right text alignment
### MLEM.Ui
Additions
@ -28,6 +33,7 @@ Additions
Improvements
- *Made Image ScaleToImage take ui scale into account*
- *Added style properties for a lot of hardcoded default element styles*
- *Allow setting a custom effect and depth stencil state for ui drawing*
- Exposed the epsilon value used by Element calculations
- Allow style properties to set style values with a higher priority, which allows elements to style their default children
- Allow changing the entire ui style for a single element
@ -39,6 +45,7 @@ Fixes
- Fixed VerticalSpace height parameter being an integer
- Fixed text not being pasted into a text field at all if it contains characters that don't match the input rule
- Fixed panels that don't auto-hide their scroll bars ignoring their width for child padding
- Fixed some inconsistencies with element transformations and mouse interaction
Removals
- *Removed ScrollBar ScrollerOffset*

View file

@ -44,7 +44,12 @@ namespace MLEM.Data.Content {
return this.Read<T>(assetName, default);
}
/// <inheritdoc/>
/// <summary>
/// Reloads the asset of the given type, with the given original name.
/// </summary>
/// <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) {
this.Read(originalAssetName, currentAsset);
}
@ -78,7 +83,9 @@ namespace MLEM.Data.Content {
throw new ContentLoadException($"Asset {assetName} not found. Tried files {string.Join(", ", triedFiles)}");
}
/// <inheritdoc/>
/// <summary>
/// Unloads this content manager, disposing all of the assets that it loaded.
/// </summary>
public override void Unload() {
foreach (var d in this.disposableAssets)
d.Dispose();
@ -86,7 +93,9 @@ namespace MLEM.Data.Content {
base.Unload();
}
/// <inheritdoc/>
/// <summary>
/// Initializes the component. Used to load non-graphical resources.
/// </summary>
public void Initialize() {
}

View file

@ -82,7 +82,8 @@ namespace MLEM.Data {
return ret;
}
/// <inheritdoc />
/// <summary>Returns a string that represents the current object.</summary>
/// <returns>A string that represents the current object.</returns>
public override string ToString() {
if (this.name == null) {
var included = new List<DynamicEnum>();

View file

@ -3,15 +3,26 @@ using MLEM.Misc;
using Newtonsoft.Json;
namespace MLEM.Data.Json {
/// <inheritdoc />
/// <summary>
/// Converts a <see cref="Direction2"/> to and from JSON
/// </summary>
public class Direction2Converter : JsonConverter<Direction2> {
/// <inheritdoc />
/// <summary>Writes the JSON representation of the object.</summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter" /> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, Direction2 value, JsonSerializer serializer) {
writer.WriteValue(value.ToString());
}
/// <inheritdoc />
/// <summary>Reads the JSON representation of the object.</summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader" /> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read. If there is no existing value then <c>null</c> will be used.</param>
/// <param name="hasExistingValue">The existing value has a value.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override Direction2 ReadJson(JsonReader reader, Type objectType, Direction2 existingValue, bool hasExistingValue, JsonSerializer serializer) {
Enum.TryParse<Direction2>(reader.Value.ToString(), out var dir);
return dir;

View file

@ -2,15 +2,26 @@ using System;
using Newtonsoft.Json;
namespace MLEM.Data.Json {
/// <inheritdoc />
/// <summary>
/// Converts a <see cref="DynamicEnum"/> to and from JSON
/// </summary>
public class DynamicEnumConverter : JsonConverter<DynamicEnum> {
/// <inheritdoc />
/// <summary>Writes the JSON representation of the object.</summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter" /> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, DynamicEnum value, JsonSerializer serializer) {
writer.WriteValue(value.ToString());
}
/// <inheritdoc />
/// <summary>Reads the JSON representation of the object.</summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader" /> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read. If there is no existing value then <c>null</c> will be used.</param>
/// <param name="hasExistingValue">The existing value has a value.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override DynamicEnum ReadJson(JsonReader reader, Type objectType, DynamicEnum existingValue, bool hasExistingValue, JsonSerializer serializer) {
return DynamicEnum.Parse(objectType, reader.Value.ToString());
}

View file

@ -4,15 +4,26 @@ using Microsoft.Xna.Framework;
using Newtonsoft.Json;
namespace MLEM.Data.Json {
/// <inheritdoc />
/// <summary>
/// Converts a <see cref="Point"/> to and from JSON
/// </summary>
public class PointConverter : JsonConverter<Point> {
/// <inheritdoc />
/// <summary>Writes the JSON representation of the object.</summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter" /> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, Point value, JsonSerializer serializer) {
writer.WriteValue(value.X.ToString(CultureInfo.InvariantCulture) + " " + value.Y.ToString(CultureInfo.InvariantCulture));
}
/// <inheritdoc />
/// <summary>Reads the JSON representation of the object.</summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader" /> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read. If there is no existing value then <c>null</c> will be used.</param>
/// <param name="hasExistingValue">The existing value has a value.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override Point ReadJson(JsonReader reader, Type objectType, Point existingValue, bool hasExistingValue, JsonSerializer serializer) {
var value = reader.Value.ToString().Split(' ');
return new Point(int.Parse(value[0], CultureInfo.InvariantCulture), int.Parse(value[1], CultureInfo.InvariantCulture));

View file

@ -4,17 +4,28 @@ using Microsoft.Xna.Framework;
using Newtonsoft.Json;
namespace MLEM.Data.Json {
/// <inheritdoc />
/// <summary>
/// Converts a <see cref="Rectangle"/> to and from JSON
/// </summary>
public class RectangleConverter : JsonConverter<Rectangle> {
/// <inheritdoc />
/// <summary>Writes the JSON representation of the object.</summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter" /> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, Rectangle value, JsonSerializer serializer) {
writer.WriteValue(
value.X.ToString(CultureInfo.InvariantCulture) + " " + value.Y.ToString(CultureInfo.InvariantCulture) + " " +
value.Width.ToString(CultureInfo.InvariantCulture) + " " + value.Height.ToString(CultureInfo.InvariantCulture));
}
/// <inheritdoc />
/// <summary>Reads the JSON representation of the object.</summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader" /> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read. If there is no existing value then <c>null</c> will be used.</param>
/// <param name="hasExistingValue">The existing value has a value.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override Rectangle ReadJson(JsonReader reader, Type objectType, Rectangle existingValue, bool hasExistingValue, JsonSerializer serializer) {
var value = reader.Value.ToString().Split(' ');
return new Rectangle(

View file

@ -4,17 +4,28 @@ using MLEM.Misc;
using Newtonsoft.Json;
namespace MLEM.Data.Json {
/// <inheritdoc />
/// <summary>
/// Converts a <see cref="RectangleF"/> to and from JSON
/// </summary>
public class RectangleFConverter : JsonConverter<RectangleF> {
/// <inheritdoc />
/// <summary>Writes the JSON representation of the object.</summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter" /> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, RectangleF value, JsonSerializer serializer) {
writer.WriteValue(
value.X.ToString(CultureInfo.InvariantCulture) + " " + value.Y.ToString(CultureInfo.InvariantCulture) + " " +
value.Width.ToString(CultureInfo.InvariantCulture) + " " + value.Height.ToString(CultureInfo.InvariantCulture));
}
/// <inheritdoc />
/// <summary>Reads the JSON representation of the object.</summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader" /> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read. If there is no existing value then <c>null</c> will be used.</param>
/// <param name="hasExistingValue">The existing value has a value.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override RectangleF ReadJson(JsonReader reader, Type objectType, RectangleF existingValue, bool hasExistingValue, JsonSerializer serializer) {
var value = reader.Value.ToString().Split(' ');
return new RectangleF(

View file

@ -33,14 +33,23 @@ namespace MLEM.Data.Json {
this(GetEntries(type, memberName)) {
}
/// <inheritdoc />
/// <summary>Writes the JSON representation of the object.</summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter" /> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer) {
if (!this.inverse.TryGetValue(value, out var key))
throw new InvalidOperationException($"Cannot write {value} that is not a registered entry");
writer.WriteValue(key);
}
/// <inheritdoc />
/// <summary>Reads the JSON representation of the object.</summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader" /> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read. If there is no existing value then <c>null</c> will be used.</param>
/// <param name="hasExistingValue">The existing value has a value.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer) {
var val = reader.Value?.ToString();
if (val == null)

View file

@ -4,15 +4,26 @@ using Microsoft.Xna.Framework;
using Newtonsoft.Json;
namespace MLEM.Data.Json {
/// <inheritdoc />
/// <summary>
/// Converts a <see cref="Vector2"/> to and from JSON
/// </summary>
public class Vector2Converter : JsonConverter<Vector2> {
/// <inheritdoc />
/// <summary>Writes the JSON representation of the object.</summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter" /> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, Vector2 value, JsonSerializer serializer) {
writer.WriteValue(value.X.ToString(CultureInfo.InvariantCulture) + " " + value.Y.ToString(CultureInfo.InvariantCulture));
}
/// <inheritdoc />
/// <summary>Reads the JSON representation of the object.</summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader" /> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read. If there is no existing value then <c>null</c> will be used.</param>
/// <param name="hasExistingValue">The existing value has a value.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override Vector2 ReadJson(JsonReader reader, Type objectType, Vector2 existingValue, bool hasExistingValue, JsonSerializer serializer) {
var value = reader.Value.ToString().Split(' ');
return new Vector2(float.Parse(value[0], CultureInfo.InvariantCulture), float.Parse(value[1], CultureInfo.InvariantCulture));

View file

@ -148,7 +148,7 @@ namespace MLEM.Data {
this.LastPackTime = TimeSpan.Zero;
}
/// <inheritdoc />
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose() {
this.Reset();
}

View file

@ -26,7 +26,16 @@ namespace MLEM.Extended.Extensions {
return new Misc.RectangleF(rect.X, rect.Y, rect.Width, rect.Height);
}
/// <inheritdoc cref="MLEM.Extensions.NumberExtensions.Penetrate"/>
/// <summary>
/// Calculates the amount that the rectangle <paramref name="rect"/> is penetrating the rectangle <paramref name="other"/> by.
/// If a penetration on both axes is occuring, the one with the lower value is returned.
/// This is useful for collision detection, as it can be used to push colliding objects out of each other.
/// </summary>
/// <param name="rect">The rectangle to do the penetration</param>
/// <param name="other">The rectangle that should be penetrated</param>
/// <param name="normal">The direction that the penetration occured in</param>
/// <param name="penetration">The amount that the penetration occured by, in the direction of <paramref name="normal"/></param>
/// <returns>Whether or not a penetration occured</returns>
public static bool Penetrate(this RectangleF rect, RectangleF other, out Vector2 normal, out float penetration) {
return rect.ToMlem().Penetrate(other.ToMlem(), out normal, out penetration);
}

View file

@ -9,17 +9,35 @@ namespace MLEM.Extended.Extensions {
/// </summary>
public static class SpriteBatchExtensions {
/// <inheritdoc cref="SpriteBatch.Draw(Texture2D,Rectangle,Rectangle?,Color,float,Vector2,SpriteEffects,float)"/>
/// <summary>Submit a sprite for drawing in the current batch.</summary>
/// <param name="batch">The sprite batch to draw with.</param>
/// <param name="texture">A texture.</param>
/// <param name="destinationRectangle">The drawing bounds on screen.</param>
/// <param name="sourceRectangle">An optional region on the texture which will be rendered. If null - draws full texture.</param>
/// <param name="color">A color mask.</param>
/// <param name="rotation">A rotation of this sprite.</param>
/// <param name="origin">Center of the rotation. 0,0 by default.</param>
/// <param name="effects">Modificators for drawing. Can be combined.</param>
/// <param name="layerDepth">A depth of the layer of this sprite.</param>
public static void Draw(this SpriteBatch batch, Texture2D texture, RectangleF destinationRectangle, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, SpriteEffects effects, float layerDepth) {
batch.Draw(texture, destinationRectangle.ToMlem(), sourceRectangle, color, rotation, origin, effects, layerDepth);
}
/// <inheritdoc cref="SpriteBatch.Draw(Texture2D,Rectangle,Rectangle?,Color)"/>
/// <summary>Submit a sprite for drawing in the current batch.</summary>
/// <param name="batch">The sprite batch to draw with.</param>
/// <param name="texture">A texture.</param>
/// <param name="destinationRectangle">The drawing bounds on screen.</param>
/// <param name="sourceRectangle">An optional region on the texture which will be rendered. If null - draws full texture.</param>
/// <param name="color">A color mask.</param>
public static void Draw(this SpriteBatch batch, Texture2D texture, RectangleF destinationRectangle, Rectangle? sourceRectangle, Color color) {
batch.Draw(texture, destinationRectangle, sourceRectangle, color, 0, Vector2.Zero, SpriteEffects.None, 0);
}
/// <inheritdoc cref="SpriteBatch.Draw(Texture2D,Rectangle,Color)"/>
/// <summary>Submit a sprite for drawing in the current batch.</summary>
/// <param name="batch">The sprite batch to draw with.</param>
/// <param name="texture">A texture.</param>
/// <param name="destinationRectangle">The drawing bounds on screen.</param>
/// <param name="color">A color mask.</param>
public static void Draw(this SpriteBatch batch, Texture2D texture, RectangleF destinationRectangle, Color color) {
batch.Draw(texture, destinationRectangle, null, color);
}
@ -38,6 +56,7 @@ namespace MLEM.Extended.Extensions {
for (var x = 0; x < tileCount.X; x++)
batch.DrawRectangle(start + new Vector2(x, y) * tileSize, tileSize, gridColor, gridThickness / 2);
}
var size = tileSize * tileCount.ToVector2() + new Vector2(gridThickness);
batch.DrawRectangle(start - new Vector2(gridThickness / 2), size, gridColor, gridThickness / 2);
}

View file

@ -51,12 +51,15 @@ namespace MLEM.Extended.Tiled {
return this.Layer == other.Layer && this.X == other.X && this.Y == other.Y;
}
/// <inheritdoc />
/// <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 LayerPosition other && this.Equals(other);
}
/// <inheritdoc />
/// <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;

View file

@ -91,7 +91,7 @@ namespace MLEM.Ui.Elements {
}
/// <inheritdoc />
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) {
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
var tex = this.Texture;
var color = (Color) this.NormalColor * alpha;
if (this.IsDisabled) {
@ -102,7 +102,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, matrix);
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
}
/// <inheritdoc />

View file

@ -84,7 +84,7 @@ namespace MLEM.Ui.Elements {
}
/// <inheritdoc />
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) {
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
var tex = this.Texture;
var color = Color.White * alpha;
if (this.IsMouseOver) {
@ -96,7 +96,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, matrix);
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
}
/// <inheritdoc />

View file

@ -885,8 +885,10 @@ namespace MLEM.Ui.Elements {
/// <param name="alpha">The alpha to draw this element and its children with</param>
/// <param name="blendState">The blend state that is used for drawing</param>
/// <param name="samplerState">The sampler state that is used for drawing</param>
/// <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>
public void DrawTransformed(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) {
public void DrawTransformed(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
var customDraw = this.BeginImpl != null || this.Transform != Matrix.Identity;
var mat = this.Transform * matrix;
if (customDraw) {
@ -894,18 +896,18 @@ namespace MLEM.Ui.Elements {
batch.End();
// begin our own draw call
if (this.BeginImpl != null) {
this.BeginImpl(this, time, batch, alpha, blendState, samplerState, mat);
this.BeginImpl(this, time, batch, alpha, blendState, samplerState, depthStencilState, effect, mat);
} else {
batch.Begin(SpriteSortMode.Deferred, blendState, samplerState, null, null, null, mat);
batch.Begin(SpriteSortMode.Deferred, blendState, samplerState, depthStencilState, null, effect, mat);
}
}
// draw content in custom begin call
this.Draw(time, batch, alpha, blendState, samplerState, mat);
this.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, mat);
if (customDraw) {
// end our draw
batch.End();
// begin the usual draw again for other elements
batch.Begin(SpriteSortMode.Deferred, blendState, samplerState, null, null, null, matrix);
batch.Begin(SpriteSortMode.Deferred, blendState, samplerState, depthStencilState, null, effect, matrix);
}
}
@ -918,15 +920,17 @@ namespace MLEM.Ui.Elements {
/// <param name="alpha">The alpha to draw this element and its children with</param>
/// <param name="blendState">The blend state that is used for drawing</param>
/// <param name="samplerState">The sampler state that is used for drawing</param>
/// <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>
public virtual void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) {
public virtual void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
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, matrix);
child.DrawTransformed(time, batch, alpha * child.DrawAlpha, blendState, samplerState, depthStencilState, effect, matrix);
}
}
@ -940,11 +944,13 @@ namespace MLEM.Ui.Elements {
/// <param name="alpha">The alpha to draw this element and its children with</param>
/// <param name="blendState">The blend state that is used for drawing</param>
/// <param name="samplerState">The sampler state that is used for drawing</param>
/// <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>
public virtual void DrawEarly(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) {
public virtual void DrawEarly(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
foreach (var child in this.GetRelevantChildren()) {
if (!child.IsHidden)
child.DrawEarly(time, batch, alpha * child.DrawAlpha, blendState, samplerState, matrix);
child.DrawEarly(time, batch, alpha * child.DrawAlpha, blendState, samplerState, depthStencilState, effect, matrix);
}
}
@ -956,8 +962,7 @@ namespace MLEM.Ui.Elements {
public virtual Element GetElementUnderPos(Vector2 position) {
if (this.IsHidden)
return null;
if (this.Transform != Matrix.Identity)
position = Vector2.Transform(position, Matrix.Invert(this.Transform));
position = this.TransformInverse(position);
var relevant = this.GetRelevantChildren();
for (var i = relevant.Count - 1; i >= 0; i--) {
var element = relevant[i].GetElementUnderPos(position);
@ -1019,6 +1024,28 @@ namespace MLEM.Ui.Elements {
this.SecondActionSound.SetFromStyle(style.ActionSound);
}
/// <summary>
/// Transforms the given <paramref name="position"/> by the inverse of this element's <see cref="Transform"/> matrix.
/// </summary>
/// <param name="position">The position to transform</param>
/// <returns>The transformed position</returns>
protected Vector2 TransformInverse(Vector2 position) {
return this.Transform != Matrix.Identity ? Vector2.Transform(position, Matrix.Invert(this.Transform)) : position;
}
/// <summary>
/// Transforms the given <paramref name="position"/> by this element's <see cref="Root"/>'s <see cref="RootElement.InvTransform"/>, the inverses of all of the <see cref="Transform"/> matrices of this element's parent tree (<see cref="GetParentTree"/>), and the inverse of this element's <see cref="Transform"/> matrix.
/// Note that, when using <see cref="UiControls.GetElementUnderPos"/>, this operation is done recursively, which is more efficient.
/// </summary>
/// <param name="position">The position to transform</param>
/// <returns>The transformed position</returns>
protected Vector2 TransformInverseAll(Vector2 position) {
position = Vector2.Transform(position, this.Root.InvTransform);
foreach (var parent in this.GetParentTree().Reverse())
position = parent.TransformInverse(position);
return this.TransformInverse(position);
}
/// <summary>
/// A delegate used for the <see cref="Element.OnTextInput"/> event.
/// </summary>
@ -1079,8 +1106,10 @@ namespace MLEM.Ui.Elements {
/// <param name="alpha">This element's draw alpha</param>
/// <param name="blendState">The blend state used for drawing</param>
/// <param name="samplerState">The sampler state used for drawing</param>
/// <param name="effect">The effect used for drawing</param>
/// <param name="depthStencilState">The depth stencil state used for drawing</param>
/// <param name="matrix">The transform matrix used for drawing</param>
public delegate void BeginDelegate(Element element, GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix);
public delegate void BeginDelegate(Element element, GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix);
}
}

View file

@ -20,10 +20,10 @@ namespace MLEM.Ui.Elements {
}
/// <inheritdoc />
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) {
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
// since the group never accesses its own area when drawing, we have to update it manually
this.UpdateAreaIfDirty();
base.Draw(time, batch, alpha, blendState, samplerState, matrix);
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
}
}

View file

@ -103,7 +103,7 @@ namespace MLEM.Ui.Elements {
}
/// <inheritdoc />
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) {
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
if (this.Texture == null)
return;
var center = new Vector2(this.Texture.Width / 2F, this.Texture.Height / 2F);
@ -116,7 +116,7 @@ namespace MLEM.Ui.Elements {
var scale = new Vector2(1F / this.Texture.Width, 1F / this.Texture.Height) * this.DisplayArea.Size;
batch.Draw(this.Texture, this.DisplayArea.Location + center * scale, color, this.ImageRotation, center, scale * this.ImageScale, this.ImageEffects, 0);
}
base.Draw(time, batch, alpha, blendState, samplerState, matrix);
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
}
/// <summary>

View file

@ -155,12 +155,12 @@ namespace MLEM.Ui.Elements {
}
/// <inheritdoc />
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) {
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
if (this.Texture.HasValue())
batch.Draw(this.Texture, this.DisplayArea, this.DrawColor.OrDefault(Color.White) * alpha, this.Scale);
// if we handle overflow, draw using the render target in DrawUnbound
if (!this.scrollOverflow || this.renderTarget == null) {
base.Draw(time, batch, alpha, blendState, samplerState, matrix);
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
} else {
// draw the actual render target (don't apply the alpha here because it's already drawn onto with alpha)
batch.Draw(this.renderTarget, this.GetRenderTargetArea(), Color.White);
@ -168,7 +168,7 @@ namespace MLEM.Ui.Elements {
}
/// <inheritdoc />
public override void DrawEarly(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) {
public override void DrawEarly(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
this.UpdateAreaIfDirty();
if (this.scrollOverflow && this.renderTarget != null) {
// draw children onto the render target
@ -178,19 +178,20 @@ namespace MLEM.Ui.Elements {
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, null, null, null, trans);
base.Draw(time, batch, alpha, blendState, samplerState, trans);
batch.Begin(SpriteSortMode.Deferred, blendState, samplerState, depthStencilState, null, effect, trans);
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, trans);
batch.End();
}
}
base.DrawEarly(time, batch, alpha, blendState, samplerState, matrix);
base.DrawEarly(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
}
/// <inheritdoc />
public override Element GetElementUnderPos(Vector2 position) {
// if overflow is handled, don't propagate mouse checks to hidden children
if (this.scrollOverflow && !this.GetRenderTargetArea().Contains(position))
return !this.IsHidden && this.CanBeMoused && this.DisplayArea.Contains(position) ? this : null;
var transformed = this.TransformInverse(position);
if (this.scrollOverflow && !this.GetRenderTargetArea().Contains(transformed))
return !this.IsHidden && this.CanBeMoused && this.DisplayArea.Contains(transformed) ? this : null;
return base.GetElementUnderPos(position);
}

View file

@ -139,12 +139,12 @@ namespace MLEM.Ui.Elements {
}
/// <inheritdoc />
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) {
var pos = this.DisplayArea.Location + new Vector2(GetAlignmentOffset(), 0);
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
var pos = this.DisplayArea.Location + new Vector2(this.GetAlignmentOffset(), 0);
var sc = this.TextScale * this.TextScaleMultiplier * this.Scale;
var color = this.TextColor.OrDefault(Color.White) * alpha;
this.TokenizedText.Draw(time, batch, pos, this.RegularFont, color, sc, 0, this.Alignment);
base.Draw(time, batch, alpha, blendState, samplerState, matrix);
this.TokenizedText.Draw(time, batch, pos, this.RegularFont, color, sc, 0);
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
}
/// <inheritdoc />
@ -251,7 +251,7 @@ namespace MLEM.Ui.Elements {
return ret;
// check if any of our token's parts are hovered
foreach (var rect in this.Token.GetArea(this.Parent.DisplayArea.Location, this.Scale * this.textScale)) {
if (rect.Contains(position))
if (rect.Contains(this.TransformInverse(position)))
return this;
}
return null;

View file

@ -73,7 +73,7 @@ namespace MLEM.Ui.Elements {
}
/// <inheritdoc />
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) {
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
batch.Draw(this.Texture, this.DisplayArea, (Color) this.Color * alpha, this.Scale);
var percentage = this.CurrentValue / this.MaxValue;
@ -106,7 +106,7 @@ namespace MLEM.Ui.Elements {
} else {
batch.Draw(batch.GetBlankTexture(), offsetArea, (Color) this.ProgressColor * alpha);
}
base.Draw(time, batch, alpha, blendState, samplerState, matrix);
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
}
/// <inheritdoc />

View file

@ -137,15 +137,14 @@ namespace MLEM.Ui.Elements {
// MOUSE INPUT
var moused = this.Controls.MousedElement;
var mousedPos = Vector2.Transform(this.Input.MousePosition.ToVector2(), this.Root.InvTransform);
if (moused == this && this.Controls.Input.IsMouseButtonPressed(MouseButton.Left)) {
this.isMouseHeld = true;
this.scrollStartOffset = mousedPos - this.ScrollerPosition;
this.scrollStartOffset = this.TransformInverseAll(this.Input.MousePosition.ToVector2()) - this.ScrollerPosition;
} else if (this.isMouseHeld && !this.Controls.Input.IsMouseButtonDown(MouseButton.Left)) {
this.isMouseHeld = false;
}
if (this.isMouseHeld)
this.ScrollToPos(mousedPos);
this.ScrollToPos(this.TransformInverseAll(this.Input.MousePosition.ToVector2()));
if (!this.Horizontal && moused != null && (moused == this.Parent || moused.GetParentTree().Contains(this.Parent))) {
var scroll = this.Input.LastScrollWheel - this.Input.ScrollWheel;
if (scroll != 0)
@ -157,7 +156,7 @@ namespace MLEM.Ui.Elements {
// are we dragging on top of the panel?
if (this.Input.GetGesture(GestureType.VerticalDrag, out var drag)) {
// if the element under the drag's start position is on top of the panel, start dragging
var touched = this.Parent.GetElementUnderPos(Vector2.Transform(drag.Position, this.Root.InvTransform));
var touched = this.Parent.GetElementUnderPos(this.TransformInverseAll(drag.Position));
if (touched != null && touched != this)
this.isDragging = true;
@ -173,7 +172,7 @@ namespace MLEM.Ui.Elements {
this.isTouchHeld = false;
} else {
foreach (var loc in this.Input.TouchState) {
var pos = Vector2.Transform(loc.Position, this.Root.InvTransform);
var pos = this.TransformInverseAll(loc.Position);
// if we just started touching and are on top of the scroller, then we should start scrolling
if (this.DisplayArea.Contains(pos) && !loc.TryGetPreviousLocation(out _)) {
this.isTouchHeld = true;
@ -206,13 +205,13 @@ namespace MLEM.Ui.Elements {
}
/// <inheritdoc />
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) {
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
batch.Draw(this.Background, this.DisplayArea, Color.White * alpha, this.Scale);
if (this.MaxValue > 0) {
var scrollerRect = new RectangleF(this.ScrollerPosition, this.ScrollerSize * this.Scale);
batch.Draw(this.ScrollerTexture, scrollerRect, Color.White * alpha, this.Scale);
}
base.Draw(time, batch, alpha, blendState, samplerState, matrix);
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
}
/// <inheritdoc />

View file

@ -285,7 +285,7 @@ namespace MLEM.Ui.Elements {
}
/// <inheritdoc />
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) {
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) {
var tex = this.Texture;
var color = Color.White * alpha;
if (this.IsMouseOver) {
@ -314,7 +314,7 @@ namespace MLEM.Ui.Elements {
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, matrix);
base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix);
}
/// <summary>

View file

@ -237,11 +237,10 @@ namespace MLEM.Ui {
/// Throughout the ui system, this is used for mouse input querying.
/// </summary>
/// <param name="position">The position to query</param>
/// <param name="transform">If this value is true, the <see cref="RootElement.Transform"/> will be applied.</param>
/// <returns>The element under the position, or null if there isn't one</returns>
public virtual Element GetElementUnderPos(Vector2 position, bool transform = true) {
public virtual Element GetElementUnderPos(Vector2 position) {
foreach (var root in this.System.GetRootElements()) {
var pos = transform ? Vector2.Transform(position, root.InvTransform) : position;
var pos = Vector2.Transform(position, root.InvTransform);
var moused = root.Element.GetElementUnderPos(pos);
if (moused != null)
return moused;

View file

@ -83,6 +83,16 @@ namespace MLEM.Ui {
/// </summary>
public SamplerState SamplerState = SamplerState.PointClamp;
/// <summary>
/// The depth stencil state that this ui system and all of its elements draw with.
/// The default is <see cref="Microsoft.Xna.Framework.Graphics.DepthStencilState.None"/>, which is also the default for <see cref="SpriteBatch.Begin"/>.
/// </summary>
public DepthStencilState DepthStencilState = DepthStencilState.None;
/// <summary>
/// The effect that this ui system and all of its elements draw with.
/// The default is null, which means that no custom effect will be used.
/// </summary>
public Effect Effect;
/// <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>
@ -236,7 +246,7 @@ namespace MLEM.Ui {
public void DrawEarly(GameTime time, SpriteBatch batch) {
foreach (var root in this.rootElements) {
if (!root.Element.IsHidden)
root.Element.DrawEarly(time, batch, this.DrawAlpha * root.Element.DrawAlpha, this.BlendState, this.SamplerState, root.Transform);
root.Element.DrawEarly(time, batch, this.DrawAlpha * root.Element.DrawAlpha, this.BlendState, this.SamplerState, this.DepthStencilState, this.Effect, root.Transform);
}
}
@ -250,9 +260,9 @@ namespace MLEM.Ui {
foreach (var root in this.rootElements) {
if (root.Element.IsHidden)
continue;
batch.Begin(SpriteSortMode.Deferred, this.BlendState, this.SamplerState, null, null, null, root.Transform);
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, root.Transform);
root.Element.DrawTransformed(time, batch, alpha, this.BlendState, this.SamplerState, this.DepthStencilState, this.Effect, root.Transform);
batch.End();
}
}

View file

@ -45,6 +45,11 @@ namespace MLEM.Extensions {
return Math.Abs(first.X - second.X) <= tolerance && Math.Abs(first.Y - second.Y) <= tolerance && Math.Abs(first.Z - second.Z) <= tolerance && Math.Abs(first.W - second.W) <= tolerance;
}
/// <inheritdoc cref="Equals(float,float,float)"/>
public static bool Equals(this Quaternion first, Quaternion second, float tolerance) {
return Math.Abs(first.X - second.X) <= tolerance && Math.Abs(first.Y - second.Y) <= tolerance && Math.Abs(first.Z - second.Z) <= tolerance && Math.Abs(first.W - second.W) <= tolerance;
}
/// <inheritdoc cref="Math.Floor(decimal)"/>
public static Vector2 FloorCopy(this Vector2 vec) {
return new Vector2(vec.X.Floor(), vec.Y.Floor());
@ -213,7 +218,7 @@ namespace MLEM.Extensions {
/// <summary>
/// Returns the rotation that the given matrix represents, as a <see cref="Quaternion"/>.
/// Returns <see cref="Quaternion.Identity"/> if the matrix does not contain valid rotation information.
/// Returns <see cref="Quaternion.Identity"/> if the matrix does not contain valid rotation information, or is not rotated.
/// </summary>
/// <param name="matrix">The matrix</param>
/// <returns>The rotation of the matrix</returns>
@ -228,6 +233,30 @@ namespace MLEM.Extensions {
0, 0, 0, 1));
}
/// <summary>
/// Returns the rotation that the given matrix represents, as a <see cref="Vector3"/> that contains the x, y and z rotations in radians.
/// Returns <see cref="Vector3.Zero"/> if the matrix does not contain valid rotation information, or is not rotated.
/// </summary>
/// <param name="matrix">The matrix</param>
/// <returns>The rotation of the matrix</returns>
public static Vector3 RotationVector(this Matrix matrix) {
return matrix.Rotation().RotationVector();
}
/// <summary>
/// Returns the rotation that the given quaternion represents, as a <see cref="Vector3"/> that contains the x, y and z rotations in radians.
/// Returns <see cref="Vector3.Zero"/> if the quaternion does not contain valid rotation information, or is not rotated.
/// </summary>
/// <param name="quaternion">The quaternion</param>
/// <returns>The rotation of the quaternion</returns>
public static Vector3 RotationVector(this Quaternion quaternion) {
var (x, y, z, w) = quaternion;
return new Vector3(
(float) Math.Atan2(2 * (w * x + y * z), 1 - 2 * (x * x + y * y)),
(float) Math.Asin(MathHelper.Clamp(2 * (w * y - z * x), -1, 1)),
(float) Math.Atan2(2 * (w * z + x * y), 1 - 2 * (y * y + z * z)));
}
/// <summary>
/// Calculates the amount that the rectangle <paramref name="rect"/> is penetrating the rectangle <paramref name="other"/> by.
/// If a penetration on both axes is occuring, the one with the lower value is returned.

View file

@ -110,19 +110,37 @@ namespace MLEM.Extensions {
return tex;
}
/// <inheritdoc cref="SpriteBatch.Draw(Texture2D,Rectangle,Rectangle?,Color,float,Vector2,SpriteEffects,float)"/>
/// <summary>Submit a sprite for drawing in the current batch.</summary>
/// <param name="batch">The sprite batch to draw with.</param>
/// <param name="texture">A texture.</param>
/// <param name="destinationRectangle">The drawing bounds on screen.</param>
/// <param name="sourceRectangle">An optional region on the texture which will be rendered. If null - draws full texture.</param>
/// <param name="color">A color mask.</param>
/// <param name="rotation">A rotation of this sprite.</param>
/// <param name="origin">Center of the rotation. 0,0 by default.</param>
/// <param name="effects">Modificators for drawing. Can be combined.</param>
/// <param name="layerDepth">A depth of the layer of this sprite.</param>
public static void Draw(this SpriteBatch batch, Texture2D texture, RectangleF destinationRectangle, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, SpriteEffects effects, float layerDepth) {
var source = sourceRectangle ?? new Rectangle(0, 0, texture.Width, texture.Height);
var scale = new Vector2(1F / source.Width, 1F / source.Height) * destinationRectangle.Size;
batch.Draw(texture, destinationRectangle.Location, sourceRectangle, color, rotation, origin, scale, effects, layerDepth);
}
/// <inheritdoc cref="SpriteBatch.Draw(Texture2D,Rectangle,Rectangle?,Color)"/>
/// <summary>Submit a sprite for drawing in the current batch.</summary>
/// <param name="batch">The sprite batch to draw with.</param>
/// <param name="texture">A texture.</param>
/// <param name="destinationRectangle">The drawing bounds on screen.</param>
/// <param name="sourceRectangle">An optional region on the texture which will be rendered. If null - draws full texture.</param>
/// <param name="color">A color mask.</param>
public static void Draw(this SpriteBatch batch, Texture2D texture, RectangleF destinationRectangle, Rectangle? sourceRectangle, Color color) {
batch.Draw(texture, destinationRectangle, sourceRectangle, color, 0, Vector2.Zero, SpriteEffects.None, 0);
}
/// <inheritdoc cref="SpriteBatch.Draw(Texture2D,Rectangle,Color)"/>
/// <summary>Submit a sprite for drawing in the current batch.</summary>
/// <param name="batch">The sprite batch to draw with.</param>
/// <param name="texture">A texture.</param>
/// <param name="destinationRectangle">The drawing bounds on screen.</param>
/// <param name="color">A color mask.</param>
public static void Draw(this SpriteBatch batch, Texture2D texture, RectangleF destinationRectangle, Color color) {
batch.Draw(texture, destinationRectangle, null, color);
}

View file

@ -104,7 +104,7 @@ namespace MLEM.Extensions {
return x >= 0 && y >= 0 && x < this.texture.Width && y < this.texture.Height;
}
/// <inheritdoc />
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose() {
this.Store();
}

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@ -114,6 +115,7 @@ namespace MLEM.Formatting {
/// <returns>The final, recursively resolved string</returns>
public string ResolveMacros(string s) {
// resolve macros that resolve into macros
var rec = 0;
bool matched;
do {
matched = false;
@ -124,6 +126,9 @@ namespace MLEM.Formatting {
return macro.Value(this, m, macro.Key);
});
}
rec++;
if (rec >= 16)
throw new ArithmeticException($"A string resolved macros recursively too many times. Does it contain any conflicting macros?\n{s}");
} while (matched);
return s;
}

View file

@ -111,7 +111,7 @@ namespace MLEM.Formatting {
}
/// <inheritdoc cref="GenericFont.DrawString(SpriteBatch,string,Vector2,Color,float,Vector2,float,SpriteEffects,float)"/>
public void Draw(GameTime time, SpriteBatch batch, Vector2 pos, GenericFont font, Color color, float scale, float depth, TextAlignment alignment = TextAlignment.Left) {
public void Draw(GameTime time, SpriteBatch batch, Vector2 pos, GenericFont font, Color color, float scale, float depth) {
var innerOffset = new Vector2(this.initialInnerOffset * scale, 0);
for (var t = 0; t < this.Tokens.Length; t++) {
var token = this.Tokens[t];
@ -178,7 +178,7 @@ namespace MLEM.Formatting {
foreach (var token in this.Tokens)
token.SplitDisplayString = token.DisplayString.Split('\n');
// token areas
// token areas and inner offsets
this.initialInnerOffset = this.GetInnerOffsetX(font, 0, 0, alignment);
var innerOffset = new Vector2(this.initialInnerOffset, 0);
for (var t = 0; t < this.Tokens.Length; t++) {
@ -205,18 +205,21 @@ namespace MLEM.Formatting {
private float GetInnerOffsetX(GenericFont font, int tokenIndex, int lineIndex, TextAlignment alignment) {
if (alignment > TextAlignment.Left) {
var token = this.Tokens[tokenIndex];
var restOfLine = font.MeasureString(token.SplitDisplayString[lineIndex], true).X;
if (lineIndex >= token.SplitDisplayString.Length - 1) {
// the line ends somewhere in or after the next token
// if we're the last line in our line array, then we don't contain a line split, so the line ends in a later token
var endsLater = lineIndex >= token.SplitDisplayString.Length - 1;
// if the line ends in our token, we should ignore trailing white space
var restOfLine = font.MeasureString(token.SplitDisplayString[lineIndex], !endsLater).X;
if (endsLater) {
for (var i = tokenIndex + 1; i < this.Tokens.Length; i++) {
var other = this.Tokens[i];
if (other.SplitDisplayString.Length > 1) {
// the line ends in this token
restOfLine += font.MeasureString(other.SplitDisplayString[0]).X;
// the line ends in this token (so we also ignore trailing whitespaces)
restOfLine += font.MeasureString(other.SplitDisplayString[0], true).X;
break;
} else {
// the line doesn't end in this token, so add it fully
restOfLine += font.MeasureString(other.DisplayString).X;
// the line doesn't end in this token (or it's the last token), so add it fully
var lastToken = i >= this.Tokens.Length - 1;
restOfLine += font.MeasureString(other.DisplayString, lastToken).X;
}
}
}

View file

@ -25,7 +25,8 @@ namespace MLEM.Input {
this.value = value;
}
/// <inheritdoc />
/// <summary>Returns this generic input, converted to a string.</summary>
/// <returns>This generic input, converted to a string.</returns>
public override string ToString() {
switch (this.Type) {
case InputType.Mouse:
@ -39,12 +40,15 @@ namespace MLEM.Input {
}
}
/// <inheritdoc />
/// <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><see langword="true" /> if <paramref name="obj" /> and this instance are the same type and represent the same value; otherwise, <see langword="false" />.</returns>
public override bool Equals(object obj) {
return obj is GenericInput o && this.Type == o.Type && this.value == o.value;
}
/// <inheritdoc />
/// <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() {
return ((int) this.Type * 397) ^ this.value;
}

View file

@ -147,7 +147,10 @@ namespace MLEM.Input {
return string.Join(joiner, this.combinations.Select(c => c.ToString(combinationJoiner, inputName)));
}
/// <inheritdoc />
/// <summary>
/// Converts this keybind into a string, separating every included <see cref="Combination"/> by a comma
/// </summary>
/// <returns>This keybind as a string</returns>
public override string ToString() {
return this.ToString(", ");
}

View file

@ -129,12 +129,15 @@ namespace MLEM.Misc {
return this.Left.Equals(other.Left) && this.Right.Equals(other.Right) && this.Top.Equals(other.Top) && this.Bottom.Equals(other.Bottom);
}
/// <inheritdoc />
/// <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><see langword="true" /> if <paramref name="obj" /> and this instance are the same type and represent the same value; otherwise, <see langword="false" />.</returns>
public override bool Equals(object obj) {
return obj is Padding other && this.Equals(other);
}
/// <inheritdoc />
/// <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.Left.GetHashCode();
hashCode = (hashCode * 397) ^ this.Right.GetHashCode();

View file

@ -163,17 +163,20 @@ namespace MLEM.Misc {
return this.X <= value.X && value.X + value.Width <= this.X + this.Width && this.Y <= value.Y && value.Y + value.Height <= this.Y + this.Height;
}
/// <inheritdoc />
/// <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><see langword="true" /> if <paramref name="obj" /> and this instance are the same type and represent the same value; otherwise, <see langword="false" />.</returns>
public override bool Equals(object obj) {
return obj is RectangleF f && this == f;
}
/// <inheritdoc />
/// <inheritdoc cref="Equals(object)"/>
public bool Equals(RectangleF other) {
return this == other;
}
/// <inheritdoc />
/// <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() {
return (((17 * 23 + this.X.GetHashCode()) * 23 + this.Y.GetHashCode()) * 23 + this.Width.GetHashCode()) * 23 + this.Height.GetHashCode();
}

View file

@ -2,11 +2,11 @@ using System;
using Microsoft.Xna.Framework;
namespace MLEM.Misc {
/// <inheritdoc />
/// <inheritdoc cref="Sound.SoundEffectInstanceHandler"/>
[Obsolete("This class has been moved to MLEM.Sound.SoundEffectInstanceHandler in 5.1.0")]
public class SoundEffectInstanceHandler : Sound.SoundEffectInstanceHandler {
/// <inheritdoc />
/// <inheritdoc cref="Sound.SoundEffectInstanceHandler(Game)"/>
public SoundEffectInstanceHandler(Game game) : base(game) {
}

View file

@ -356,7 +356,7 @@ namespace MLEM.Misc {
this.batchChanged = true;
}
/// <inheritdoc />
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose() {
this.spriteEffect.Dispose();
this.indices?.Dispose();

View file

@ -214,14 +214,17 @@ namespace MLEM.Pathfinding {
this.F = this.G + distance * defaultCost;
}
/// <inheritdoc />
/// <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><see langword="true" /> if <paramref name="obj" /> and this instance are the same type and represent the same value; otherwise, <see langword="false" />.</returns>
public override bool Equals(object obj) {
if (obj == this)
return true;
return obj is PathPoint<T> point && point.Pos.Equals(this.Pos);
}
/// <inheritdoc />
/// <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() {
return this.Pos.GetHashCode();
}

View file

@ -109,11 +109,14 @@ namespace MLEM.Sound {
return this.Add(effect.CreateInstance(), onStopped, emitter);
}
/// <inheritdoc />
/// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
public IEnumerator<Entry> GetEnumerator() {
return this.playingSounds.GetEnumerator();
}
/// <summary>Returns an enumerator that iterates through a collection.</summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}

View file

@ -1,3 +1,4 @@
using System;
using System.Text.RegularExpressions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
@ -107,6 +108,11 @@ namespace Tests {
const string strg = "This text uses a bunch of non-breaking~spaces to see if macros work. Additionally, it uses a macro that resolves into a bunch of other macros and then, at the end, into <testmacro> text</c>.";
const string goal = "This text uses a bunch of non-breaking\u00A0spaces to see if macros work. Additionally, it uses a macro that resolves into a bunch of other macros and then, at the end, into <c Blue>blue text</c>.";
Assert.AreEqual(this.formatter.ResolveMacros(strg), goal);
// test recursive macros
this.formatter.Macros.Add(new Regex("<rec1>"), (f, m, r) => "<rec2>");
this.formatter.Macros.Add(new Regex("<rec2>"), (f, m, r) => "<rec1>");
Assert.Throws<ArithmeticException>(() => this.formatter.ResolveMacros("Test <rec1> string"));
}
[Test]

View file

@ -43,10 +43,17 @@ namespace Tests {
}
[Test]
public void TestMatrixOps([Range(0.5F, 2, 0.5F)] float scale, [Range(-1, 1, 1F)] float rotationX) {
var matrix = Matrix.CreateRotationX(rotationX) * Matrix.CreateScale(scale, scale, scale);
public void TestMatrixOps([Range(0.5F, 2, 1)] float scale, [Range(-0.5F, 0.5F, 1)] float rotationX, [Range(-0.5F, 0.5F, 1)] float rotationY, [Range(-0.5F, 0.5F, 0.5F)] float rotationZ) {
var rotation = Matrix.CreateRotationX(rotationX) * Matrix.CreateRotationY(rotationY) * Matrix.CreateRotationZ(rotationZ);
var matrix = rotation * Matrix.CreateScale(scale, scale, scale);
Assert.IsTrue(matrix.Scale().Equals(new Vector3(scale), 0.001F), $"{matrix.Scale()} does not equal {new Vector2(scale)}");
Assert.AreEqual(matrix.Rotation(), Quaternion.CreateFromAxisAngle(Vector3.UnitX, rotationX));
Assert.IsTrue(matrix.Rotation().Equals(Quaternion.CreateFromRotationMatrix(rotation), 0.001F), $"{matrix.Rotation()} does not equal {Quaternion.CreateFromRotationMatrix(rotation)}");
Assert.IsTrue(matrix.RotationVector().Equals(new Vector3(rotationX, rotationY, rotationZ), 0.001F), $"{matrix.RotationVector()} does not equal {new Vector3(rotationX, rotationY, rotationZ)}");
// check against decomposed results
matrix.Decompose(out var sc, out var rot, out _);
Assert.AreEqual(matrix.Rotation(), rot);
Assert.AreEqual(matrix.Scale(), sc);
}
[Test]