1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-06-02 05:13:38 +02:00

Compare commits

...

16 commits
6.3.1 ... main

Author SHA1 Message Date
Ell 8fdc3546c6 Added the ScissorGroup element 2024-05-30 13:24:35 +02:00
Ell d879894e30 Include the SpriteBatchContext in OnDrawn, OnElementDrawn and OnSelectedElementDrawn 2024-05-30 12:48:08 +02:00
Ell ee2b0266aa automatically set TreatSizeAsMaximum for height-based scrolling panels 2024-05-29 23:37:42 +02:00
Ell ab96e97f8e Fixed hidden scroll bars inhibiting scrolling on their parent panel 2024-05-29 23:34:23 +02:00
Ell f499ed94a7 Allow scrolling panels to set height based on children with TreatSizeAsMaximum 2024-05-29 23:30:12 +02:00
Ell 85d20b6433 Added a RectangleF.FromCorners overload that accepts points 2024-04-13 21:14:34 +02:00
Ell 7bf418f8b2 Allow NumberExtensions.GetPoints to include bottom and right coordinates 2024-04-13 21:12:49 +02:00
Ell dd9230abf0 defaults for string GetArea methods 2024-04-10 23:52:35 +02:00
Ell fc2d705526 allow GetTokenUnderPos to work with rotation, origin and effects 2024-04-10 23:42:16 +02:00
Ell fdb0571860 xml docs and cleanup 2024-04-10 20:45:16 +02:00
Ell 76eb5d0679 use faster draw methods for formatting codes 2024-04-10 20:33:53 +02:00
Ell 9a03a8c62d Added the ability for formatted (tokenized) strings to be drawn with custom rotation, origin and flipping
Closes #18
2024-04-10 20:27:00 +02:00
Ell e00769a4ed moved out string transforms into CalculateStringTransform in preparation for #18 2024-04-10 18:47:10 +02:00
Ell e2635bf9b6 various tooltip features and improvements 2024-04-10 17:58:01 +02:00
Ell 556be37523 updated changelog links 2024-04-05 15:20:46 +02:00
Ell 49bc5b7f25 bump upcoming version 2024-04-05 11:14:35 +02:00
42 changed files with 462 additions and 183 deletions

View file

@ -2,6 +2,7 @@
MLEM tries to adhere to [semantic versioning](https://semver.org/). Potentially breaking changes are written in **bold**. MLEM tries to adhere to [semantic versioning](https://semver.org/). Potentially breaking changes are written in **bold**.
Jump to version: Jump to version:
- [7.0.0](#700-in-development)
- [6.3.1](#631) - [6.3.1](#631)
- [6.3.0](#630) - [6.3.0](#630)
- [6.2.0](#620) - [6.2.0](#620)
@ -12,6 +13,29 @@ Jump to version:
- [5.1.0](#510) - [5.1.0](#510)
- [5.0.0](#500) - [5.0.0](#500)
## 7.0.0 (In Development)
### MLEM
Additions
- **Added the ability for formatted (tokenized) strings to be drawn with custom rotation, origin and flipping**
- Added a RectangleF.FromCorners overload that accepts points
Improvements
- Allow NumberExtensions.GetPoints to include bottom and right coordinates
### MLEM.Ui
Additions
- Added the ability to set the anchor that should be used when a tooltip attaches to an element or the mouse
- Added the ability to display tooltips using the auto-nav style even when using the mouse
- Added the ScissorGroup element, which applies a scissor rectangle when drawing its content
Improvements
- **Include the SpriteBatchContext in OnDrawn, OnElementDrawn and OnSelectedElementDrawn**
- Allow scrolling panels to set height based on children by setting TreatSizeAsMaximum
Fixes
- Fixed hidden scroll bars inhibiting scrolling on their parent panel
## 6.3.1 ## 6.3.1
No code changes No code changes

View file

@ -77,7 +77,8 @@ namespace Demos {
this.activeDemo = demo.Value.Item2.Invoke(this); this.activeDemo = demo.Value.Item2.Invoke(this);
this.activeDemo.LoadContent(); this.activeDemo.LoadContent();
}, },
PositionOffset = new Vector2(0, 1) PositionOffset = new Vector2(0, 1),
Tooltip = {DisplayInAutoNavMode = true}
}); });
} }

View file

@ -27,6 +27,7 @@ namespace Demos {
private TokenizedString tokenizedText; private TokenizedString tokenizedText;
private GenericFont font; private GenericFont font;
private bool drawBounds; private bool drawBounds;
private bool transform;
private float Scale { private float Scale {
get { get {
// calculate our scale based on how much larger the window is, so that the text scales with the window // calculate our scale based on how much larger the window is, so that the text scales with the window
@ -71,9 +72,13 @@ namespace Demos {
// we draw the tokenized text in the center of the screen // 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 // since the text is already center-aligned, we only need to align it on the y axis here
var size = this.tokenizedText.GetArea(Vector2.Zero, this.Scale).Size; var size = this.tokenizedText.GetArea(scale: this.Scale).Size;
var pos = new Vector2(this.GraphicsDevice.Viewport.Width / 2, (this.GraphicsDevice.Viewport.Height - size.Y) / 2); var pos = new Vector2(this.GraphicsDevice.Viewport.Width / 2, (this.GraphicsDevice.Viewport.Height - size.Y) / 2);
var rotation = this.transform ? 0.25F : 0;
var origin = this.transform ? new Vector2(size.X / this.Scale, 0) : Vector2.Zero;
var effects = this.transform ? SpriteEffects.FlipHorizontally : SpriteEffects.None;
// draw bounds, which can be toggled with B in this demo // draw bounds, which can be toggled with B in this demo
if (this.drawBounds) { if (this.drawBounds) {
var blank = this.SpriteBatch.GetBlankTexture(); var blank = this.SpriteBatch.GetBlankTexture();
@ -85,9 +90,14 @@ namespace Demos {
} }
// draw the text itself (start and end indices are optional) // draw the text itself (start and end indices are optional)
this.tokenizedText.Draw(time, this.SpriteBatch, pos, this.font, Color.White, this.Scale, 0, this.startIndex, this.endIndex); this.tokenizedText.Draw(time, this.SpriteBatch, pos, this.font, Color.White, this.Scale, 0, rotation, origin, effects, this.startIndex, this.endIndex);
this.SpriteBatch.End(); this.SpriteBatch.End();
// an example of how to interact with the text
var hovered = this.tokenizedText.GetTokenUnderPos(pos, this.InputHandler.ViewportMousePosition.ToVector2(), this.Scale, this.font, rotation, origin, effects);
if (hovered != null)
Console.WriteLine($"Hovering \"{hovered.Substring}\"");
} }
public override void Update(GameTime time) { public override void Update(GameTime time) {
@ -97,6 +107,8 @@ namespace Demos {
// change some demo showcase info based on keybinds // change some demo showcase info based on keybinds
if (this.InputHandler.IsPressed(Keys.B)) if (this.InputHandler.IsPressed(Keys.B))
this.drawBounds = !this.drawBounds; this.drawBounds = !this.drawBounds;
if (this.InputHandler.IsPressed(Keys.T))
this.transform = !this.transform;
if (this.startIndex > 0 && this.InputHandler.IsDown(Keys.Left)) if (this.startIndex > 0 && this.InputHandler.IsDown(Keys.Left))
this.startIndex--; this.startIndex--;
if (this.startIndex < this.tokenizedText.String.Length && this.InputHandler.IsDown(Keys.Right)) if (this.startIndex < this.tokenizedText.String.Length && this.InputHandler.IsDown(Keys.Right))

View file

@ -223,14 +223,15 @@ namespace Demos {
PositionOffset = new Vector2(0, 1) PositionOffset = new Vector2(0, 1)
}); });
var subPanel = this.root.AddChild(new Panel(Anchor.AutoLeft, new Vector2(1, 25), Vector2.Zero, false, true) { this.root.AddChild(new VerticalSpace(3));
PositionOffset = new Vector2(0, 1), var dynamicSubPanel = this.root.AddChild(new Panel(Anchor.AutoLeft, new Vector2(1, 50), Vector2.Zero, true, true) {
Texture = null, Texture = null,
ChildPadding = Padding.Empty ChildPadding = Padding.Empty
}); });
subPanel.AddChild(new Paragraph(Anchor.AutoLeft, 1, "This is a nested scrolling panel!")); dynamicSubPanel.AddChild(new Paragraph(Anchor.AutoLeft, 1, "This is a dynamic height nested panel with a maximum height, at which point it starts displaying a scroll bar instead!"));
for (var i = 1; i <= 5; i++) dynamicSubPanel.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 10), "Press to add more") {
subPanel.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 10), $"Button {i}") {PositionOffset = new Vector2(0, 1)}); OnPressed = _ => dynamicSubPanel.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 10), "I do nothing"))
});
const string alignText = "Paragraphs can have <l Left>left</l> aligned text, <l Right>right</l> aligned text and <l Center>center</l> aligned text."; const string alignText = "Paragraphs can have <l Left>left</l> aligned text, <l Right>right</l> aligned text and <l Center>center</l> aligned text.";
this.root.AddChild(new VerticalSpace(3)); this.root.AddChild(new VerticalSpace(3));

View file

@ -12,7 +12,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Ellpeck</Authors> <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> <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> <PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG.html</PackageReleaseNotes>
<PackageTags>fna ellpeck mlem utility extensions data serialize</PackageTags> <PackageTags>fna ellpeck mlem utility extensions data serialize</PackageTags>
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl> <PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl> <RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>

View file

@ -10,7 +10,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Ellpeck</Authors> <Authors>Ellpeck</Authors>
<Description>Simple loading and processing of textures and other data for MonoGame, including the ability to load non-XNB content files easily</Description> <Description>Simple loading and processing of textures and 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> <PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG.html</PackageReleaseNotes>
<PackageTags>monogame ellpeck mlem utility extensions data serialize</PackageTags> <PackageTags>monogame ellpeck mlem utility extensions data serialize</PackageTags>
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl> <PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl> <RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>

View file

@ -38,7 +38,7 @@ namespace MLEM.Extended.Font {
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void DrawCharacter(SpriteBatch batch, int codePoint, string character, Vector2 position, Color color, float rotation, Vector2 scale, SpriteEffects effects, float layerDepth) { public override void DrawCharacter(SpriteBatch batch, int codePoint, string character, Vector2 position, Color color, float rotation, Vector2 scale, SpriteEffects effects, float layerDepth) {
batch.DrawString(this.Font, character, position, color, rotation, Vector2.Zero, scale, effects, layerDepth); batch.DrawString(this.Font, character, position, color, rotation, Vector2.Zero, scale, effects, layerDepth);
} }

View file

@ -47,7 +47,7 @@ namespace MLEM.Extended.Font {
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void DrawCharacter(SpriteBatch batch, int codePoint, string character, Vector2 position, Color color, float rotation, Vector2 scale, SpriteEffects effects, float layerDepth) { public override void DrawCharacter(SpriteBatch batch, int codePoint, string character, Vector2 position, Color color, float rotation, Vector2 scale, SpriteEffects effects, float layerDepth) {
this.Font.DrawText(batch, character, position, color, rotation, Vector2.Zero, scale, layerDepth, this.CharacterSpacing, this.LineSpacing); this.Font.DrawText(batch, character, position, color, rotation, Vector2.Zero, scale, layerDepth, this.CharacterSpacing, this.LineSpacing);
} }

View file

@ -12,7 +12,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Ellpeck</Authors> <Authors>Ellpeck</Authors>
<Description>MLEM Library for Extending FNA extension that ties in with other FNA libraries</Description> <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> <PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG.html</PackageReleaseNotes>
<PackageTags>fna ellpeck mlem utility extensions extended</PackageTags> <PackageTags>fna ellpeck mlem utility extensions extended</PackageTags>
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl> <PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl> <RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>

View file

@ -9,7 +9,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Ellpeck</Authors> <Authors>Ellpeck</Authors>
<Description>MLEM Library for Extending MonoGame extension that ties in with MonoGame.Extended and other MonoGame libraries</Description> <Description>MLEM Library for Extending MonoGame extension that ties in with MonoGame.Extended and other MonoGame libraries</Description>
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes> <PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG.html</PackageReleaseNotes>
<PackageTags>monogame ellpeck mlem utility extensions monogame.extended extended</PackageTags> <PackageTags>monogame ellpeck mlem utility extensions monogame.extended extended</PackageTags>
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl> <PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl> <RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>

View file

@ -12,7 +12,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Ellpeck</Authors> <Authors>Ellpeck</Authors>
<Description>MLEM Library for Extending FNA combined with some other useful libraries into a quick Game startup class</Description> <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> <PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG.html</PackageReleaseNotes>
<PackageTags>fna ellpeck mlem utility extensions</PackageTags> <PackageTags>fna ellpeck mlem utility extensions</PackageTags>
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl> <PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl> <RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>

View file

@ -10,7 +10,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Ellpeck</Authors> <Authors>Ellpeck</Authors>
<Description>MLEM Library for Extending MonoGame combined with some other useful libraries into a quick Game startup class</Description> <Description>MLEM Library for Extending MonoGame 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> <PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG.html</PackageReleaseNotes>
<PackageTags>monogame ellpeck mlem utility extensions</PackageTags> <PackageTags>monogame ellpeck mlem utility extensions</PackageTags>
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl> <PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl> <RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>

View file

@ -14,7 +14,7 @@
<Title>MLEM Templates</Title> <Title>MLEM Templates</Title>
<Authors>Ellpeck</Authors> <Authors>Ellpeck</Authors>
<Description>MLEM Library for Extending MonoGame cross-platform project templates</Description> <Description>MLEM Library for Extending MonoGame cross-platform project templates</Description>
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes> <PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG.html</PackageReleaseNotes>
<PackageTags>dotnet-new templates monogame ellpeck mlem utility extensions</PackageTags> <PackageTags>dotnet-new templates monogame ellpeck mlem utility extensions</PackageTags>
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl> <PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl> <RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>

View file

@ -1148,9 +1148,9 @@ namespace MLEM.Ui.Elements {
/// <param name="alpha">The alpha to draw this element and its children with</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> /// <param name="context">The sprite batch context to use for drawing</param>
public virtual void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) { public virtual void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
this.System.InvokeOnElementDrawn(this, time, batch, alpha); this.System.InvokeOnElementDrawn(this, time, batch, alpha, context);
if (this.IsSelected) if (this.IsSelected)
this.System.InvokeOnSelectedElementDrawn(this, time, batch, alpha); this.System.InvokeOnSelectedElementDrawn(this, time, batch, alpha, context);
foreach (var child in this.GetRelevantChildren()) { foreach (var child in this.GetRelevantChildren()) {
if (!child.IsHidden) { if (!child.IsHidden) {
@ -1387,7 +1387,8 @@ namespace MLEM.Ui.Elements {
/// <param name="time">The game's time</param> /// <param name="time">The game's time</param>
/// <param name="batch">The sprite batch used for drawing</param> /// <param name="batch">The sprite batch used for drawing</param>
/// <param name="alpha">The alpha this element is drawn with</param> /// <param name="alpha">The alpha this element is drawn with</param>
public delegate void DrawCallback(Element element, GameTime time, SpriteBatch batch, float alpha); /// <param name="context">The sprite batch context to use for drawing</param>
public delegate void DrawCallback(Element element, GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context);
/// <summary> /// <summary>
/// A generic delegate used inside of <see cref="Element.Update"/> /// A generic delegate used inside of <see cref="Element.Update"/>

View file

@ -78,6 +78,7 @@ namespace MLEM.Ui.Elements {
public Panel(Anchor anchor, Vector2 size, Vector2 positionOffset, bool setHeightBasedOnChildren = false, bool scrollOverflow = false, bool autoHideScrollbar = true) : base(anchor, size) { public Panel(Anchor anchor, Vector2 size, Vector2 positionOffset, bool setHeightBasedOnChildren = false, bool scrollOverflow = false, bool autoHideScrollbar = true) : base(anchor, size) {
this.PositionOffset = positionOffset; this.PositionOffset = positionOffset;
this.SetHeightBasedOnChildren = setHeightBasedOnChildren; this.SetHeightBasedOnChildren = setHeightBasedOnChildren;
this.TreatSizeAsMaximum = setHeightBasedOnChildren && scrollOverflow;
this.scrollOverflow = scrollOverflow; this.scrollOverflow = scrollOverflow;
this.CanBeSelected = false; this.CanBeSelected = false;
@ -119,8 +120,8 @@ namespace MLEM.Ui.Elements {
public override void ForceUpdateArea() { public override void ForceUpdateArea() {
if (this.scrollOverflow) { if (this.scrollOverflow) {
// sanity check // sanity check
if (this.SetHeightBasedOnChildren) if (this.SetHeightBasedOnChildren && !this.TreatSizeAsMaximum)
throw new NotSupportedException("A panel can't both set height based on children and scroll overflow"); throw new NotSupportedException("A panel can't both scroll overflow and set height based on children without a maximum");
foreach (var child in this.Children) { foreach (var child in this.Children) {
if (child != this.ScrollBar && !child.Anchor.IsAuto()) if (child != this.ScrollBar && !child.Anchor.IsAuto())
throw new NotSupportedException($"A panel that handles overflow can't contain non-automatic anchors ({child})"); throw new NotSupportedException($"A panel that handles overflow can't contain non-automatic anchors ({child})");
@ -295,7 +296,7 @@ namespace MLEM.Ui.Elements {
if (this.Children.Count > 1) { if (this.Children.Count > 1) {
var highestValidChild = this.Children.FirstOrDefault(c => c != this.ScrollBar && !c.IsHidden); var highestValidChild = this.Children.FirstOrDefault(c => c != this.ScrollBar && !c.IsHidden);
var lowestChild = this.GetLowestChild(c => c != this.ScrollBar && !c.IsHidden, true); var lowestChild = this.GetLowestChild(c => c != this.ScrollBar && !c.IsHidden, true);
childrenHeight = lowestChild.GetTotalCoveredArea(false).Bottom - highestValidChild.Area.Top; childrenHeight = lowestChild.GetTotalCoveredArea(true).Bottom - highestValidChild.UnscrolledArea.Top;
} else { } else {
// if we only have one child (the scroll bar), then the children take up no visual height // if we only have one child (the scroll bar), then the children take up no visual height
childrenHeight = 0; childrenHeight = 0;

View file

@ -141,12 +141,12 @@ namespace MLEM.Ui.Elements {
} }
/// <summary> /// <summary>
/// The inclusive index in this paragraph's <see cref="Text"/> to start drawing at. /// The inclusive index in this paragraph's <see cref="Text"/> to start drawing at.
/// This value is passed to <see cref="TokenizedString.Draw"/>. /// This value is passed to <see cref="TokenizedString.Draw(Microsoft.Xna.Framework.GameTime,Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Font.GenericFont,Microsoft.Xna.Framework.Color,Vector2,float,float,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Graphics.SpriteEffects,System.Nullable{int},System.Nullable{int})"/>.
/// </summary> /// </summary>
public int? DrawStartIndex; public int? DrawStartIndex;
/// <summary> /// <summary>
/// The exclusive index in this paragraph's <see cref="Text"/> to stop drawing at. /// The exclusive index in this paragraph's <see cref="Text"/> to stop drawing at.
/// This value is passed to <see cref="TokenizedString.Draw"/>. /// This value is passed to <see cref="TokenizedString.Draw(Microsoft.Xna.Framework.GameTime,Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Font.GenericFont,Microsoft.Xna.Framework.Color,Vector2,float,float,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Graphics.SpriteEffects,System.Nullable{int},System.Nullable{int})"/>.
/// </summary> /// </summary>
public int? DrawEndIndex; public int? DrawEndIndex;
@ -228,7 +228,7 @@ namespace MLEM.Ui.Elements {
this.CheckTextChange(); this.CheckTextChange();
this.TokenizeIfNecessary(); this.TokenizeIfNecessary();
this.AlignAndSplitIfNecessary(size); this.AlignAndSplitIfNecessary(size);
var textSize = this.tokenizedText.GetArea(Vector2.Zero, this.TextScale * this.TextScaleMultiplier * this.Scale).Size; var textSize = this.tokenizedText.GetArea(scale: this.TextScale * this.TextScaleMultiplier * this.Scale).Size;
// if we auto-adjust our width, then we would also split the same way with our adjusted width, so cache that // if we auto-adjust our width, then we would also split the same way with our adjusted width, so cache that
if (this.AutoAdjustWidth) if (this.AutoAdjustWidth)
this.lastAlignSplitWidth = textSize.X; this.lastAlignSplitWidth = textSize.X;
@ -246,7 +246,7 @@ namespace MLEM.Ui.Elements {
var pos = this.DisplayArea.Location + new Vector2(this.GetAlignmentOffset(), 0); var pos = this.DisplayArea.Location + new Vector2(this.GetAlignmentOffset(), 0);
var sc = this.TextScale * this.TextScaleMultiplier * this.Scale; var sc = this.TextScale * this.TextScaleMultiplier * this.Scale;
var color = this.TextColor.OrDefault(Color.White) * alpha; var color = this.TextColor.OrDefault(Color.White) * alpha;
this.TokenizedText.Draw(time, batch, pos, this.RegularFont, color, sc, 0, this.DrawStartIndex, this.DrawEndIndex); this.TokenizedText.Draw(time, batch, pos, this.RegularFont, color, sc, 0, 0, Vector2.Zero, SpriteEffects.None, this.DrawStartIndex, this.DrawEndIndex);
base.Draw(time, batch, alpha, context); base.Draw(time, batch, alpha, context);
} }

View file

@ -0,0 +1,61 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MLEM.Extensions;
using MLEM.Graphics;
namespace MLEM.Ui.Elements {
/// <summary>
/// A scissor group is a <see cref="Group"/> that sets the <see cref="GraphicsDevice.ScissorRectangle"/> before drawing its content (and thus, its <see cref="Element.Children"/>), preventing them from being drawn outside of this group's <see cref="Element.DisplayArea"/>'s bounds.
/// </summary>
public class ScissorGroup : Group {
/// <summary>
/// The rasterizer state that this scissor group should use when drawing.
/// By default, <see cref="CullMode"/> is set to <see cref="CullMode.CullCounterClockwiseFace"/> in accordance with <see cref="SpriteBatch"/>'s default behavior, and <see cref="RasterizerState.ScissorTestEnable"/> is set to <see langword="true"/>.
/// </summary>
public readonly RasterizerState Rasterizer = new RasterizerState {
// use the default cull mode from SpriteBatch, but with scissor test enabled
CullMode = CullMode.CullCounterClockwiseFace,
ScissorTestEnable = true
};
/// <summary>
/// Creates a new scissor group with the given settings
/// </summary>
/// <param name="anchor">The group's anchor</param>
/// <param name="size">The group's size</param>
/// <param name="setHeightBasedOnChildren">Whether the group's height should be based on its children's height, see <see cref="Element.SetHeightBasedOnChildren"/>.</param>
public ScissorGroup(Anchor anchor, Vector2 size, bool setHeightBasedOnChildren = true) : base(anchor, size, setHeightBasedOnChildren) {}
/// <summary>
/// Creates a new scissor group with the given settings
/// </summary>
/// <param name="anchor">The group's anchor</param>
/// <param name="size">The group's size</param>
/// <param name="setWidthBasedOnChildren">Whether the group's width should be based on its children's width, see <see cref="Element.SetWidthBasedOnChildren"/>.</param>
/// <param name="setHeightBasedOnChildren">Whether the group's height should be based on its children's height, see <see cref="Element.SetHeightBasedOnChildren"/>.</param>
public ScissorGroup(Anchor anchor, Vector2 size, bool setWidthBasedOnChildren, bool setHeightBasedOnChildren) : base(anchor, size, setWidthBasedOnChildren, setHeightBasedOnChildren) {}
/// <inheritdoc />
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
batch.End();
// apply our scissor rectangle
var lastScissor = batch.GraphicsDevice.ScissorRectangle;
batch.GraphicsDevice.ScissorRectangle = (Rectangle) this.DisplayArea.OffsetCopy(context.TransformMatrix.Translation.ToVector2());
// enable scissor test
var localContext = context;
localContext.RasterizerState = this.Rasterizer;
batch.Begin(localContext);
base.Draw(time, batch, alpha, localContext);
// revert back to previous behavior
batch.End();
batch.GraphicsDevice.ScissorRectangle = lastScissor;
batch.Begin(context);
}
}
}

View file

@ -251,7 +251,7 @@ namespace MLEM.Ui.Elements {
var foundMe = false; var foundMe = false;
foreach (var child in this.Parent.GetChildren(regardGrandchildren: true)) { foreach (var child in this.Parent.GetChildren(regardGrandchildren: true)) {
if (foundMe) { if (foundMe) {
if (child is ScrollBar b && !b.Horizontal && b.IsMousedForScrolling(moused)) if (child is ScrollBar b && !b.IsHidden && !b.Horizontal && b.IsMousedForScrolling(moused))
return false; return false;
} else if (child == this) { } else if (child == this) {
// once we found ourselves, all subsequent children are deeper/older! // once we found ourselves, all subsequent children are deeper/older!

View file

@ -1,8 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using MLEM.Input; using MLEM.Input;
using MLEM.Ui.Style; using MLEM.Ui.Style;
using Color = Microsoft.Xna.Framework.Color;
using RectangleF = MLEM.Misc.RectangleF;
#if FNA #if FNA
using MLEM.Extensions; using MLEM.Extensions;
#endif #endif
@ -23,14 +26,26 @@ namespace MLEM.Ui.Elements {
public readonly List<Paragraph> Paragraphs = new List<Paragraph>(); public readonly List<Paragraph> Paragraphs = new List<Paragraph>();
/// <summary> /// <summary>
/// The offset that this tooltip's top left corner should have from the mouse position /// The offset that this tooltip should have from the mouse position
/// </summary> /// </summary>
public StyleProp<Vector2> MouseOffset; public StyleProp<Vector2> MouseOffset;
/// <summary> /// <summary>
/// The offset that this tooltip's top center coordinate should have from the bottom center of the element snapped to when <see cref="DisplayInAutoNavMode"/> is true. /// The offset that this tooltip should have from the element snapped to when <see cref="DisplayInAutoNavMode"/> is true.
/// </summary> /// </summary>
public StyleProp<Vector2> AutoNavOffset; public StyleProp<Vector2> AutoNavOffset;
/// <summary> /// <summary>
/// The anchor that should be used when this tooltip is displayed using the mouse. The <see cref="MouseOffset"/> will be applied.
/// </summary>
public StyleProp<Anchor> MouseAnchor;
/// <summary>
/// The anchor that should be used when this tooltip is displayed using auto-nav mode. The <see cref="AutoNavOffset"/> will be applied.
/// </summary>
public StyleProp<Anchor> AutoNavAnchor;
/// <summary>
/// If this is <see langword="true"/>, and the mouse is used, the tooltip will attach to the hovered element in a static position using the <see cref="AutoNavOffset"/> and <see cref="AutoNavAnchor"/> properties, rather than following the mouse cursor exactly.
/// </summary>
public StyleProp<bool> UseAutoNavBehaviorForMouse;
/// <summary>
/// The amount of time that the mouse has to be over an element before it appears /// The amount of time that the mouse has to be over an element before it appears
/// </summary> /// </summary>
public StyleProp<TimeSpan> Delay; public StyleProp<TimeSpan> Delay;
@ -79,6 +94,7 @@ namespace MLEM.Ui.Elements {
/// The position that this tooltip should be following (or snapped to) instead of the <see cref="InputHandler.ViewportMousePosition"/>. /// The position that this tooltip should be following (or snapped to) instead of the <see cref="InputHandler.ViewportMousePosition"/>.
/// If this value is unset, <see cref="InputHandler.ViewportMousePosition"/> will be used as the snap position. /// If this value is unset, <see cref="InputHandler.ViewportMousePosition"/> will be used as the snap position.
/// Note that <see cref="MouseOffset"/> is still applied with this value set. /// Note that <see cref="MouseOffset"/> is still applied with this value set.
/// Note that, if <see cref="UseAutoNavBehaviorForMouse"/> is <see langword="true"/>, this value is ignored.
/// </summary> /// </summary>
public virtual Vector2? SnapPosition { get; set; } public virtual Vector2? SnapPosition { get; set; }
@ -151,6 +167,9 @@ namespace MLEM.Ui.Elements {
this.Texture = this.Texture.OrStyle(style.TooltipBackground); this.Texture = this.Texture.OrStyle(style.TooltipBackground);
this.MouseOffset = this.MouseOffset.OrStyle(style.TooltipOffset); this.MouseOffset = this.MouseOffset.OrStyle(style.TooltipOffset);
this.AutoNavOffset = this.AutoNavOffset.OrStyle(style.TooltipAutoNavOffset); this.AutoNavOffset = this.AutoNavOffset.OrStyle(style.TooltipAutoNavOffset);
this.MouseAnchor = this.MouseAnchor.OrStyle(style.TooltipMouseAnchor);
this.AutoNavAnchor = this.AutoNavAnchor.OrStyle(style.TooltipAutoNavAnchor);
this.UseAutoNavBehaviorForMouse = this.UseAutoNavBehaviorForMouse.OrStyle(style.TooltipUseAutoNavBehaviorForMouse);
this.Delay = this.Delay.OrStyle(style.TooltipDelay); this.Delay = this.Delay.OrStyle(style.TooltipDelay);
this.ParagraphTextColor = this.ParagraphTextColor.OrStyle(style.TooltipTextColor); this.ParagraphTextColor = this.ParagraphTextColor.OrStyle(style.TooltipTextColor);
this.ParagraphTextScale = this.ParagraphTextScale.OrStyle(style.TextScale); this.ParagraphTextScale = this.ParagraphTextScale.OrStyle(style.TextScale);
@ -201,11 +220,10 @@ namespace MLEM.Ui.Elements {
public void SnapPositionToMouse() { public void SnapPositionToMouse() {
Vector2 snapPosition; Vector2 snapPosition;
if (this.snapElement != null) { if (this.snapElement != null) {
// center our snap position below the snap element snapPosition = this.GetSnapOffset(this.AutoNavAnchor, this.snapElement.DisplayArea, this.AutoNavOffset);
snapPosition = new Vector2(this.snapElement.DisplayArea.Center.X, this.snapElement.DisplayArea.Bottom) + this.AutoNavOffset;
snapPosition.X -= this.DisplayArea.Width / 2F;
} else { } else {
snapPosition = (this.SnapPosition ?? this.Input.ViewportMousePosition.ToVector2()) + this.MouseOffset.Value; var mouseBounds = new RectangleF(this.SnapPosition ?? this.Input.ViewportMousePosition.ToVector2(), Vector2.Zero);
snapPosition = this.GetSnapOffset(this.MouseAnchor, mouseBounds, this.MouseOffset);
} }
var viewport = this.System.Viewport; var viewport = this.System.Viewport;
@ -255,8 +273,19 @@ namespace MLEM.Ui.Elements {
/// </summary> /// </summary>
/// <param name="elementToHover">The element that should automatically cause the tooltip to appear and disappear when hovered and not hovered, respectively</param> /// <param name="elementToHover">The element that should automatically cause the tooltip to appear and disappear when hovered and not hovered, respectively</param>
public void AddToElement(Element elementToHover) { public void AddToElement(Element elementToHover) {
elementToHover.OnMouseEnter += e => this.Display(e.System, $"{e.GetType().Name}Tooltip"); // mouse controls
elementToHover.OnMouseExit += e => this.Remove(); elementToHover.OnMouseEnter += e => {
if (this.UseAutoNavBehaviorForMouse)
this.snapElement = e;
this.Display(e.System, $"{e.GetType().Name}Tooltip");
};
elementToHover.OnMouseExit += e => {
this.Remove();
if (this.UseAutoNavBehaviorForMouse)
this.snapElement = null;
};
// auto-nav controls
elementToHover.OnSelected += e => { elementToHover.OnSelected += e => {
if (this.DisplayInAutoNavMode && e.Controls.IsAutoNavMode) { if (this.DisplayInAutoNavMode && e.Controls.IsAutoNavMode) {
this.snapElement = e; this.snapElement = e;
@ -312,5 +341,30 @@ namespace MLEM.Ui.Elements {
paragraph.AutoAdjustWidth = true; paragraph.AutoAdjustWidth = true;
} }
private Vector2 GetSnapOffset(Anchor anchor, RectangleF snapBounds, Vector2 offset) {
switch (anchor) {
case Anchor.TopLeft:
return snapBounds.Location - this.DisplayArea.Size - offset;
case Anchor.TopCenter:
return new Vector2(snapBounds.Center.X - this.DisplayArea.Width / 2F, snapBounds.Top - this.DisplayArea.Height) - offset;
case Anchor.TopRight:
return new Vector2(snapBounds.Right + offset.X, snapBounds.Top - this.DisplayArea.Height - offset.Y);
case Anchor.CenterLeft:
return new Vector2(snapBounds.X - this.DisplayArea.Width - offset.X, snapBounds.Center.Y - this.DisplayArea.Height / 2 + offset.Y);
case Anchor.Center:
return snapBounds.Center - this.DisplayArea.Size / 2 + offset;
case Anchor.CenterRight:
return new Vector2(snapBounds.Right, snapBounds.Center.Y - this.DisplayArea.Height / 2) + offset;
case Anchor.BottomLeft:
return new Vector2(snapBounds.X - this.DisplayArea.Width - offset.X, snapBounds.Bottom + offset.Y);
case Anchor.BottomCenter:
return new Vector2(snapBounds.Center.X - this.DisplayArea.Width / 2F, snapBounds.Bottom) + offset;
case Anchor.BottomRight:
return snapBounds.Location + snapBounds.Size + offset;
default:
throw new NotSupportedException($"Tooltip anchors don't support the {anchor} value");
}
}
} }
} }

View file

@ -11,7 +11,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Ellpeck</Authors> <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> <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> <PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG.html</PackageReleaseNotes>
<PackageTags>fna ellpeck mlem ui user interface graphical gui system mouse keyboard gamepad touch</PackageTags> <PackageTags>fna ellpeck mlem ui user interface graphical gui system mouse keyboard gamepad touch</PackageTags>
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl> <PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl> <RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>

View file

@ -9,7 +9,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Ellpeck</Authors> <Authors>Ellpeck</Authors>
<Description>A mouse, keyboard, gamepad and touch ready Ui system for MonoGame that features automatic anchoring, sizing and several ready-to-use element types</Description> <Description>A mouse, keyboard, gamepad and touch ready Ui system 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> <PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG.html</PackageReleaseNotes>
<PackageTags>monogame ellpeck mlem ui user interface graphical gui system mouse keyboard gamepad touch</PackageTags> <PackageTags>monogame ellpeck mlem ui user interface graphical gui system mouse keyboard gamepad touch</PackageTags>
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl> <PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl> <RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>

View file

@ -167,6 +167,18 @@ namespace MLEM.Ui.Style {
/// </summary> /// </summary>
public Vector2 TooltipAutoNavOffset = new Vector2(0, 8); public Vector2 TooltipAutoNavOffset = new Vector2(0, 8);
/// <summary> /// <summary>
/// The auto-nav anchor that is used or tooltips by default.
/// </summary>
public Anchor TooltipAutoNavAnchor = Anchor.BottomCenter;
/// <summary>
/// The mouse anchor that is used for tooltips by default.
/// </summary>
public Anchor TooltipMouseAnchor = Anchor.BottomRight;
/// <summary>
/// Whether tooltips should use auto-nav rendering behavior for tooltips even when using a mouse by default.
/// </summary>
public bool TooltipUseAutoNavBehaviorForMouse;
/// <summary>
/// The color that the text of a <see cref="Tooltip"/> should have /// The color that the text of a <see cref="Tooltip"/> should have
/// </summary> /// </summary>
public Color TooltipTextColor = Color.White; public Color TooltipTextColor = Color.White;

View file

@ -215,7 +215,7 @@ namespace MLEM.Ui {
this.Controls = new UiControls(this, inputHandler); this.Controls = new UiControls(this, inputHandler);
this.style = style; this.style = style;
this.OnElementDrawn += (e, time, batch, alpha) => e.OnDrawn?.Invoke(e, time, batch, alpha); this.OnElementDrawn += (e, time, batch, alpha, context) => e.OnDrawn?.Invoke(e, time, batch, alpha, context);
this.OnElementUpdated += (e, time) => e.OnUpdated?.Invoke(e, time); this.OnElementUpdated += (e, time) => e.OnUpdated?.Invoke(e, time);
this.OnElementPressed += e => e.OnPressed?.Invoke(e); this.OnElementPressed += e => e.OnPressed?.Invoke(e);
this.OnElementSecondaryPressed += e => e.OnSecondaryPressed?.Invoke(e); this.OnElementSecondaryPressed += e => e.OnSecondaryPressed?.Invoke(e);
@ -230,7 +230,7 @@ namespace MLEM.Ui {
this.OnMousedElementChanged += e => this.ApplyToAll(t => t.OnMousedElementChanged?.Invoke(t, e)); this.OnMousedElementChanged += e => this.ApplyToAll(t => t.OnMousedElementChanged?.Invoke(t, e));
this.OnTouchedElementChanged += e => this.ApplyToAll(t => t.OnTouchedElementChanged?.Invoke(t, e)); this.OnTouchedElementChanged += e => this.ApplyToAll(t => t.OnTouchedElementChanged?.Invoke(t, e));
this.OnSelectedElementChanged += e => this.ApplyToAll(t => t.OnSelectedElementChanged?.Invoke(t, e)); this.OnSelectedElementChanged += e => this.ApplyToAll(t => t.OnSelectedElementChanged?.Invoke(t, e));
this.OnSelectedElementDrawn += (element, time, batch, alpha) => { this.OnSelectedElementDrawn += (element, time, batch, alpha, context) => {
if (this.Controls.IsAutoNavMode && element.SelectionIndicator.HasValue()) if (this.Controls.IsAutoNavMode && element.SelectionIndicator.HasValue())
batch.Draw(element.SelectionIndicator, element.DisplayArea, Color.White * alpha, element.Scale / 2); batch.Draw(element.SelectionIndicator, element.DisplayArea, Color.White * alpha, element.Scale / 2);
}; };
@ -427,12 +427,12 @@ namespace MLEM.Ui {
} }
} }
internal void InvokeOnElementDrawn(Element element, GameTime time, SpriteBatch batch, float alpha) { internal void InvokeOnElementDrawn(Element element, GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
this.OnElementDrawn?.Invoke(element, time, batch, alpha); this.OnElementDrawn?.Invoke(element, time, batch, alpha, context);
} }
internal void InvokeOnSelectedElementDrawn(Element element, GameTime time, SpriteBatch batch, float alpha) { internal void InvokeOnSelectedElementDrawn(Element element, GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
this.OnSelectedElementDrawn?.Invoke(element, time, batch, alpha); this.OnSelectedElementDrawn?.Invoke(element, time, batch, alpha, context);
} }
internal void InvokeOnElementUpdated(Element element, GameTime time) { internal void InvokeOnElementUpdated(Element element, GameTime time) {

View file

@ -166,28 +166,44 @@ namespace MLEM.Extensions {
/// <summary> /// <summary>
/// Returns a set of <see cref="Point"/> values that are contained in the given <see cref="Rectangle"/>. /// Returns a set of <see cref="Point"/> values that are contained in the given <see cref="Rectangle"/>.
/// Note that <see cref="Rectangle.Left"/> and <see cref="Rectangle.Top"/> are inclusive, but <see cref="Rectangle.Right"/> and <see cref="Rectangle.Bottom"/> are not. /// Note that, by default, <see cref="Rectangle.Left"/> and <see cref="Rectangle.Top"/> are inclusive, but <see cref="Rectangle.Right"/> and <see cref="Rectangle.Bottom"/> are not.
/// </summary> /// </summary>
/// <param name="area">The area whose points to get</param> /// <param name="area">The area whose points to get</param>
/// <param name="bottomRightInclusive">Whether <see cref="Rectangle.Right"/> and <see cref="Rectangle.Bottom"/> should be inclusive, rather than exclusive.</param>
/// <returns>The points contained in the area</returns> /// <returns>The points contained in the area</returns>
public static IEnumerable<Point> GetPoints(this Rectangle area) { public static IEnumerable<Point> GetPoints(this Rectangle area, bool bottomRightInclusive = false) {
for (var x = area.Left; x < area.Right; x++) { if (bottomRightInclusive) {
for (var y = area.Top; y < area.Bottom; y++) for (var x = area.Left; x <= area.Right; x++) {
yield return new Point(x, y); for (var y = area.Top; y <= area.Bottom; y++)
yield return new Point(x, y);
}
} else {
for (var x = area.Left; x < area.Right; x++) {
for (var y = area.Top; y < area.Bottom; y++)
yield return new Point(x, y);
}
} }
} }
/// <summary> /// <summary>
/// Returns a set of <see cref="Vector2"/> values that are contained in the given <see cref="RectangleF"/>. /// Returns a set of <see cref="Vector2"/> values that are contained in the given <see cref="RectangleF"/>.
/// Note that <see cref="RectangleF.Left"/> and <see cref="RectangleF.Top"/> are inclusive, but <see cref="RectangleF.Right"/> and <see cref="RectangleF.Bottom"/> are not. /// Note that, by default, <see cref="RectangleF.Left"/> and <see cref="RectangleF.Top"/> are inclusive, but <see cref="RectangleF.Right"/> and <see cref="RectangleF.Bottom"/> are not.
/// </summary> /// </summary>
/// <param name="area">The area whose points to get</param> /// <param name="area">The area whose points to get</param>
/// <param name="interval">The distance that should be traveled between each point that is to be returned</param> /// <param name="interval">The distance that should be traveled between each point that is to be returned</param>
/// <param name="bottomRightInclusive">Whether <see cref="RectangleF.Right"/> and <see cref="RectangleF.Bottom"/> should be inclusive, rather than exclusive.</param>
/// <returns>The points contained in the area</returns> /// <returns>The points contained in the area</returns>
public static IEnumerable<Vector2> GetPoints(this RectangleF area, float interval = 1) { public static IEnumerable<Vector2> GetPoints(this RectangleF area, float interval = 1, bool bottomRightInclusive = false) {
for (var x = area.Left; x < area.Right; x += interval) { if (bottomRightInclusive) {
for (var y = area.Top; y < area.Bottom; y += interval) for (var x = area.Left; x <= area.Right; x += interval) {
yield return new Vector2(x, y); for (var y = area.Top; y <= area.Bottom; y += interval)
yield return new Vector2(x, y);
}
} else {
for (var x = area.Left; x < area.Right; x += interval) {
for (var y = area.Top; y < area.Bottom; y += interval)
yield return new Vector2(x, y);
}
} }
} }

View file

@ -4,6 +4,8 @@ using System.Linq;
using System.Text; using System.Text;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using MLEM.Formatting;
using MLEM.Formatting.Codes;
using MLEM.Misc; using MLEM.Misc;
namespace MLEM.Font { namespace MLEM.Font {
@ -59,7 +61,7 @@ namespace MLEM.Font {
/// <summary> /// <summary>
/// Draws the given code point with the given data for use in <see cref="DrawString(Microsoft.Xna.Framework.Graphics.SpriteBatch,System.Text.StringBuilder,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Color,float,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Graphics.SpriteEffects,float)"/>. /// Draws the given code point with the given data for use in <see cref="DrawString(Microsoft.Xna.Framework.Graphics.SpriteBatch,System.Text.StringBuilder,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Color,float,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Graphics.SpriteEffects,float)"/>.
/// Note that this method is only called internally. /// Note that this method should only be called internally for rendering of more complex strings, like in <see cref="TextFormatter"/> <see cref="Code"/> implementations.
/// </summary> /// </summary>
/// <param name="batch">The sprite batch to draw with.</param> /// <param name="batch">The sprite batch to draw with.</param>
/// <param name="codePoint">The code point which will be drawn.</param> /// <param name="codePoint">The code point which will be drawn.</param>
@ -70,7 +72,7 @@ namespace MLEM.Font {
/// <param name="scale">A scaling of this character.</param> /// <param name="scale">A scaling of this character.</param>
/// <param name="effects">Modificators for drawing. Can be combined.</param> /// <param name="effects">Modificators for drawing. Can be combined.</param>
/// <param name="layerDepth">A depth of the layer of this character.</param> /// <param name="layerDepth">A depth of the layer of this character.</param>
protected abstract void DrawCharacter(SpriteBatch batch, int codePoint, string character, Vector2 position, Color color, float rotation, Vector2 scale, SpriteEffects effects, float layerDepth); public abstract void DrawCharacter(SpriteBatch batch, int codePoint, string character, Vector2 position, Color color, float rotation, Vector2 scale, SpriteEffects effects, float layerDepth);
///<inheritdoc cref="SpriteBatch.DrawString(SpriteFont,string,Vector2,Color,float,Vector2,float,SpriteEffects,float)"/> ///<inheritdoc cref="SpriteBatch.DrawString(SpriteFont,string,Vector2,Color,float,Vector2,float,SpriteEffects,float)"/>
public void DrawString(SpriteBatch batch, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { public void DrawString(SpriteBatch batch, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) {
@ -174,6 +176,82 @@ namespace MLEM.Font {
return GenericFont.SplitStringSeparate(Enumerable.Repeat(new DecoratedCodePointSource(new CodePointSource(text), this, 0), 1), width, scale).First(); return GenericFont.SplitStringSeparate(Enumerable.Repeat(new DecoratedCodePointSource(new CodePointSource(text), this, 0), 1), width, scale).First();
} }
/// <summary>
/// Calculates a transformation matrix for drawing a string with the given data.
/// </summary>
/// <param name="position">The position to draw at.</param>
/// <param name="rotation">The rotation to draw with.</param>
/// <param name="origin">The origin to subtract from the position.</param>
/// <param name="scale">The scale to draw with.</param>
/// <param name="effects">The flipping to draw with.</param>
/// <param name="flipSize">The size of the string, which is only used when <paramref name="effects"/> is not <see cref="SpriteEffects.None"/>.</param>
/// <returns>A transformation matrix.</returns>
public Matrix CalculateStringTransform(Vector2 position, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, Vector2 flipSize) {
var (flipX, flipY) = (0F, 0F);
var flippedV = (effects & SpriteEffects.FlipVertically) != 0;
var flippedH = (effects & SpriteEffects.FlipHorizontally) != 0;
if (flippedV || flippedH) {
if (flippedH) {
origin.X *= -1;
flipX = -flipSize.X;
}
if (flippedV) {
origin.Y *= -1;
flipY = this.LineHeight - flipSize.Y;
}
}
var trans = Matrix.Identity;
if (rotation == 0) {
trans.M11 = flippedH ? -scale.X : scale.X;
trans.M22 = flippedV ? -scale.Y : scale.Y;
trans.M41 = (flipX - origin.X) * trans.M11 + position.X;
trans.M42 = (flipY - origin.Y) * trans.M22 + position.Y;
} else {
var sin = (float) Math.Sin(rotation);
var cos = (float) Math.Cos(rotation);
trans.M11 = (flippedH ? -scale.X : scale.X) * cos;
trans.M12 = (flippedH ? -scale.X : scale.X) * sin;
trans.M21 = (flippedV ? -scale.Y : scale.Y) * -sin;
trans.M22 = (flippedV ? -scale.Y : scale.Y) * cos;
trans.M41 = (flipX - origin.X) * trans.M11 + (flipY - origin.Y) * trans.M21 + position.X;
trans.M42 = (flipX - origin.X) * trans.M12 + (flipY - origin.Y) * trans.M22 + position.Y;
}
return trans;
}
/// <summary>
/// Moves the passed <paramref name="charPos"/> based on the given flipping data.
/// </summary>
/// <param name="charPos">The position to move.</param>
/// <param name="effects">The flipping to move based on.</param>
/// <param name="charSize">The size of the object to move, which is only used when <paramref name="effects"/> is not <see cref="SpriteEffects.None"/>.</param>
/// <returns>The moved position.</returns>
public Vector2 MoveFlipped(Vector2 charPos, SpriteEffects effects, Vector2 charSize) {
if ((effects & SpriteEffects.FlipHorizontally) != 0)
charPos.X += charSize.X;
if ((effects & SpriteEffects.FlipVertically) != 0)
charPos.Y += charSize.Y - this.LineHeight;
return charPos;
}
/// <summary>
/// Transforms the position of a single character to draw.
/// In general, it is efficient to calculate the transformation matrix once at the start (using <see cref="CalculateStringTransform"/>) and to then apply flipping data for each character individually (using <see cref="MoveFlipped"/>).
/// </summary>
/// <param name="stringPos">The position that the string is drawn at.</param>
/// <param name="charPosOffset">The offset from the <paramref name="stringPos"/> that the current character is drawn at.</param>
/// <param name="rotation">The rotation to draw with.</param>
/// <param name="origin">The origin to subtract from the position.</param>
/// <param name="scale">The scale to draw with.</param>
/// <param name="effects">The flipping to draw with.</param>
/// <param name="stringSize">The size of the string, which is only used when <paramref name="effects"/> is not <see cref="SpriteEffects.None"/>.</param>
/// <param name="charSize">The size of the current character, which is only used when <paramref name="effects"/> is not <see cref="SpriteEffects.None"/>.</param>
/// <returns>The transformed final draw position.</returns>
public Vector2 TransformSingleCharacter(Vector2 stringPos, Vector2 charPosOffset, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, Vector2 stringSize, Vector2 charSize) {
return Vector2.Transform(this.MoveFlipped(charPosOffset, effects, charSize), this.CalculateStringTransform(stringPos, rotation, origin, scale, effects, stringSize));
}
private Vector2 MeasureString(CodePointSource text, bool ignoreTrailingSpaces) { private Vector2 MeasureString(CodePointSource text, bool ignoreTrailingSpaces) {
var size = Vector2.Zero; var size = Vector2.Zero;
if (text.Length <= 0) if (text.Length <= 0)
@ -219,37 +297,8 @@ namespace MLEM.Font {
} }
private void DrawString(SpriteBatch batch, CodePointSource text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { private void DrawString(SpriteBatch batch, CodePointSource text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) {
var (flipX, flipY) = (0F, 0F); var flipSize = effects != SpriteEffects.None ? this.MeasureString(text, false) : Vector2.Zero;
var flippedV = (effects & SpriteEffects.FlipVertically) != 0; var trans = this.CalculateStringTransform(position, rotation, origin, scale, effects, flipSize);
var flippedH = (effects & SpriteEffects.FlipHorizontally) != 0;
if (flippedV || flippedH) {
var size = this.MeasureString(text, false);
if (flippedH) {
origin.X *= -1;
flipX = -size.X;
}
if (flippedV) {
origin.Y *= -1;
flipY = this.LineHeight - size.Y;
}
}
var trans = Matrix.Identity;
if (rotation == 0) {
trans.M11 = flippedH ? -scale.X : scale.X;
trans.M22 = flippedV ? -scale.Y : scale.Y;
trans.M41 = (flipX - origin.X) * trans.M11 + position.X;
trans.M42 = (flipY - origin.Y) * trans.M22 + position.Y;
} else {
var sin = (float) Math.Sin(rotation);
var cos = (float) Math.Cos(rotation);
trans.M11 = (flippedH ? -scale.X : scale.X) * cos;
trans.M12 = (flippedH ? -scale.X : scale.X) * sin;
trans.M21 = (flippedV ? -scale.Y : scale.Y) * -sin;
trans.M22 = (flippedV ? -scale.Y : scale.Y) * cos;
trans.M41 = (flipX - origin.X) * trans.M11 + (flipY - origin.Y) * trans.M21 + position.X;
trans.M42 = (flipX - origin.X) * trans.M12 + (flipY - origin.Y) * trans.M22 + position.Y;
}
var offset = Vector2.Zero; var offset = Vector2.Zero;
var index = 0; var index = 0;
@ -261,14 +310,7 @@ namespace MLEM.Font {
} else { } else {
var character = CodePointSource.ToString(codePoint); var character = CodePointSource.ToString(codePoint);
var charSize = this.MeasureString(character); var charSize = this.MeasureString(character);
var charPos = Vector2.Transform(this.MoveFlipped(offset, effects, charSize), trans);
var charPos = offset;
if (flippedH)
charPos.X += charSize.X;
if (flippedV)
charPos.Y += charSize.Y - this.LineHeight;
Vector2.Transform(ref charPos, ref trans, out charPos);
this.DrawCharacter(batch, codePoint, character, charPos, color, rotation, scale, effects, layerDepth); this.DrawCharacter(batch, codePoint, character, charPos, color, rotation, scale, effects, layerDepth);
offset.X += charSize.X; offset.X += charSize.X;
} }

View file

@ -39,7 +39,7 @@ namespace MLEM.Font {
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void DrawCharacter(SpriteBatch batch, int codePoint, string character, Vector2 position, Color color, float rotation, Vector2 scale, SpriteEffects effects, float layerDepth) { public override void DrawCharacter(SpriteBatch batch, int codePoint, string character, Vector2 position, Color color, float rotation, Vector2 scale, SpriteEffects effects, float layerDepth) {
batch.DrawString(this.Font, character, position, color, rotation, Vector2.Zero, scale, effects, layerDepth); batch.DrawString(this.Font, character, position, color, rotation, Vector2.Zero, scale, effects, layerDepth);
} }

View file

@ -92,12 +92,12 @@ namespace MLEM.Formatting.Codes {
} }
/// <inheritdoc cref="Formatting.Token.DrawCharacter"/> /// <inheritdoc cref="Formatting.Token.DrawCharacter"/>
public virtual bool DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, Token token, int indexInToken, ref Vector2 pos, GenericFont font, ref Color color, ref float scale, float depth) { public virtual bool DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, Token token, int indexInToken, Vector2 stringPos, ref Vector2 charPosOffset, GenericFont font, ref Color color, ref Vector2 scale, ref float rotation, ref Vector2 origin, float depth, SpriteEffects effects, Vector2 stringSize, Vector2 charSize) {
return false; return false;
} }
/// <inheritdoc cref="Formatting.Token.DrawSelf"/> /// <inheritdoc cref="Formatting.Token.DrawSelf"/>
public virtual void DrawSelf(GameTime time, SpriteBatch batch, Token token, Vector2 pos, GenericFont font, Color color, float scale, float depth) {} public virtual void DrawSelf(GameTime time, SpriteBatch batch, Token token, Vector2 stringPos, Vector2 charPosOffset, GenericFont font, Color color, Vector2 scale, float rotation, Vector2 origin, float depth, SpriteEffects effects, Vector2 stringSize) {}
/// <summary> /// <summary>
/// Creates a new formatting code from the given regex and regex match. /// Creates a new formatting code from the given regex and regex match.

View file

@ -36,9 +36,11 @@ namespace MLEM.Formatting.Codes {
} }
/// <inheritdoc /> /// <inheritdoc />
public override void DrawSelf(GameTime time, SpriteBatch batch, Token token, Vector2 pos, GenericFont font, Color color, float scale, float depth) { public override void DrawSelf(GameTime time, SpriteBatch batch, Token token, Vector2 stringPos, Vector2 charPosOffset, GenericFont font, Color color, Vector2 scale, float rotation, Vector2 origin, float depth, SpriteEffects effects, Vector2 stringSize) {
var actualColor = this.copyTextColor ? color : Color.White.CopyAlpha(color); var actualColor = this.copyTextColor ? color : Color.White.CopyAlpha(color);
batch.Draw(this.image.CurrentRegion, new RectangleF(pos, new Vector2(font.LineHeight * scale)), actualColor); var size = new Vector2(font.LineHeight);
var finalPos = font.TransformSingleCharacter(stringPos, charPosOffset, rotation, origin, scale, effects, stringSize, size);
batch.Draw(this.image.CurrentRegion, new RectangleF(finalPos, size * scale), actualColor, rotation, Vector2.Zero, effects, 0);
} }
} }

View file

@ -40,9 +40,9 @@ namespace MLEM.Formatting.Codes {
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, Token token, int indexInToken, ref Vector2 pos, GenericFont font, ref Color color, ref float scale, float depth) { public override bool DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, Token token, int indexInToken, Vector2 stringPos, ref Vector2 charPosOffset, GenericFont font, ref Color color, ref Vector2 scale, ref float rotation, ref Vector2 origin, float depth, SpriteEffects effects, Vector2 stringSize, Vector2 charSize) {
// since we inherit from UnderlineCode, we can just call base if selected // since we inherit from UnderlineCode, we can just call base if selected
return this.IsSelected() && base.DrawCharacter(time, batch, codePoint, character, token, indexInToken, ref pos, font, ref color, ref scale, depth); return this.IsSelected() && base.DrawCharacter(time, batch, codePoint, character, token, indexInToken, stringPos, ref charPosOffset, font, ref color, ref scale, ref rotation, ref origin, depth, effects, stringPos, charSize);
} }
} }

View file

@ -22,10 +22,11 @@ namespace MLEM.Formatting.Codes {
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, Token token, int indexInToken, ref Vector2 pos, GenericFont font, ref Color color, ref float scale, float depth) { public override bool DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, Token token, int indexInToken, Vector2 stringPos, ref Vector2 charPosOffset, GenericFont font, ref Color color, ref Vector2 scale, ref float rotation, ref Vector2 origin, float depth, SpriteEffects effects, Vector2 stringSize, Vector2 charSize) {
foreach (var dir in this.diagonals ? Direction2Helper.AllExceptNone : Direction2Helper.Adjacent) { foreach (var dir in this.diagonals ? Direction2Helper.AllExceptNone : Direction2Helper.Adjacent) {
var offset = Vector2.Normalize(dir.Offset().ToVector2()) * (this.thickness * scale); var offset = Vector2.Normalize(dir.Offset().ToVector2()) * this.thickness;
font.DrawString(batch, character, pos + offset, this.color.CopyAlpha(color), 0, Vector2.Zero, scale, SpriteEffects.None, depth); var finalPos = font.TransformSingleCharacter(stringPos, charPosOffset + offset, rotation, origin, scale, effects, stringSize, charSize);
font.DrawCharacter(batch, codePoint, character, finalPos, this.color.CopyAlpha(color), rotation, scale, effects, depth);
} }
return false; return false;
} }

View file

@ -18,8 +18,9 @@ namespace MLEM.Formatting.Codes {
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, Token token, int indexInToken, ref Vector2 pos, GenericFont font, ref Color color, ref float scale, float depth) { public override bool DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, Token token, int indexInToken, Vector2 stringPos, ref Vector2 charPosOffset, GenericFont font, ref Color color, ref Vector2 scale, ref float rotation, ref Vector2 origin, float depth, SpriteEffects effects, Vector2 stringSize, Vector2 charSize) {
font.DrawString(batch, character, pos + this.offset * scale, this.color.CopyAlpha(color), 0, Vector2.Zero, scale, SpriteEffects.None, depth); var finalPos = font.TransformSingleCharacter(stringPos, charPosOffset + this.offset, rotation, origin, scale, effects, stringSize, charSize);
font.DrawCharacter(batch, codePoint, character, finalPos, this.color.CopyAlpha(color), rotation, scale, effects, depth);
// we return false since we still want regular drawing to occur // we return false since we still want regular drawing to occur
return false; return false;
} }

View file

@ -15,8 +15,8 @@ namespace MLEM.Formatting.Codes {
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, Token token, int indexInToken, ref Vector2 pos, GenericFont font, ref Color color, ref float scale, float depth) { public override bool DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, Token token, int indexInToken, Vector2 stringPos, ref Vector2 charPosOffset, GenericFont font, ref Color color, ref Vector2 scale, ref float rotation, ref Vector2 origin, float depth, SpriteEffects effects, Vector2 stringSize, Vector2 charSize) {
pos.Y += this.offset * font.LineHeight * scale; charPosOffset.Y += this.offset * font.LineHeight * scale.Y;
return false; return false;
} }

View file

@ -19,13 +19,12 @@ namespace MLEM.Formatting.Codes {
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, Token token, int indexInToken, ref Vector2 pos, GenericFont font, ref Color color, ref float scale, float depth) { public override bool DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, Token token, int indexInToken, Vector2 stringPos, ref Vector2 charPosOffset, GenericFont font, ref Color color, ref Vector2 scale, ref float rotation, ref Vector2 origin, float depth, SpriteEffects effects, Vector2 stringSize, Vector2 charSize) {
// don't underline spaces at the end of lines // don't underline spaces at the end of lines
if (codePoint == ' ' && token.DisplayString.Length > indexInToken + 1 && token.DisplayString[indexInToken + 1] == '\n') if (codePoint == ' ' && token.DisplayString.Length > indexInToken + 1 && token.DisplayString[indexInToken + 1] == '\n')
return false; return false;
var size = font.MeasureString(character) * scale; var finalPos = font.TransformSingleCharacter(stringPos, charPosOffset + new Vector2(0, (this.yOffset - this.thickness) * charSize.Y), rotation, origin, scale, effects, stringSize, charSize);
var t = size.Y * this.thickness; batch.Draw(batch.GetBlankTexture(), new RectangleF(finalPos.X, finalPos.Y, charSize.X * scale.X, this.thickness * charSize.Y * scale.Y), null, color, rotation, Vector2.Zero, SpriteEffects.None, depth);
batch.Draw(batch.GetBlankTexture(), new RectangleF(pos.X, pos.Y + this.yOffset * size.Y - t, size.X, t), color);
return false; return false;
} }

View file

@ -28,9 +28,8 @@ namespace MLEM.Formatting.Codes {
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, Token token, int indexInToken, ref Vector2 pos, GenericFont font, ref Color color, ref float scale, float depth) { public override bool DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, Token token, int indexInToken, Vector2 stringPos, ref Vector2 charPosOffset, GenericFont font, ref Color color, ref Vector2 scale, ref float rotation, ref Vector2 origin, float depth, SpriteEffects effects, Vector2 stringSize, Vector2 charSize) {
var offset = new Vector2(0, (float) Math.Sin(token.Index + indexInToken + this.TimeIntoAnimation.TotalSeconds * this.modifier) * font.LineHeight * this.heightModifier * scale); charPosOffset += new Vector2(0, (float) Math.Sin(token.Index + indexInToken + this.TimeIntoAnimation.TotalSeconds * this.modifier) * font.LineHeight * this.heightModifier * scale.Y);
pos += offset;
// we return false since we still want regular drawing to occur, we just changed the position // we return false since we still want regular drawing to occur, we just changed the position
return false; return false;
} }

View file

@ -104,14 +104,19 @@ namespace MLEM.Formatting {
/// </summary> /// </summary>
/// <param name="time">The time</param> /// <param name="time">The time</param>
/// <param name="batch">The sprite batch to use</param> /// <param name="batch">The sprite batch to use</param>
/// <param name="pos">The position to draw the token at</param> /// <param name="stringPos">The position the string is drawn at.</param>
/// <param name="charPosOffset">The offset from the <paramref name="stringPos"/> that the current character is drawn at.</param>
/// <param name="font">The font to use to draw</param> /// <param name="font">The font to use to draw</param>
/// <param name="color">The color to draw with</param> /// <param name="color">The color to draw with</param>
/// <param name="scale">The scale to draw at</param> /// <param name="scale">The scale to draw with.</param>
/// <param name="rotation">The rotation to draw with.</param>
/// <param name="origin">The origin to subtract from the position.</param>
/// <param name="depth">The depth to draw at</param> /// <param name="depth">The depth to draw at</param>
public void DrawSelf(GameTime time, SpriteBatch batch, Vector2 pos, GenericFont font, Color color, float scale, float depth) { /// <param name="effects">The flipping to draw with.</param>
/// <param name="stringSize">The size of the string.</param>
public void DrawSelf(GameTime time, SpriteBatch batch, Vector2 stringPos, Vector2 charPosOffset, GenericFont font, Color color, Vector2 scale, float rotation, Vector2 origin, float depth, SpriteEffects effects, Vector2 stringSize) {
foreach (var code in this.AppliedCodes) foreach (var code in this.AppliedCodes)
code.DrawSelf(time, batch, this, pos, font, color, scale, depth); code.DrawSelf(time, batch, this, stringPos, charPosOffset, font, color, scale, rotation, origin, depth, effects, stringSize);
} }
/// <summary> /// <summary>
@ -122,19 +127,31 @@ namespace MLEM.Formatting {
/// <param name="codePoint">The code point of the character to draw</param> /// <param name="codePoint">The code point of the character to draw</param>
/// <param name="character">The string representation of the character to draw</param> /// <param name="character">The string representation of the character to draw</param>
/// <param name="indexInToken">The index within this token that the character is at</param> /// <param name="indexInToken">The index within this token that the character is at</param>
/// <param name="pos">The position to draw the token at</param> /// <param name="stringPos">The position the string is drawn at.</param>
/// <param name="charPosOffset">The offset from the <paramref name="stringPos"/> that the current character is drawn at.</param>
/// <param name="font">The font to use to draw</param> /// <param name="font">The font to use to draw</param>
/// <param name="color">The color to draw with</param> /// <param name="color">The color to draw with</param>
/// <param name="scale">The scale to draw at</param> /// <param name="scale">The scale to draw with.</param>
/// <param name="rotation">The rotation to draw with.</param>
/// <param name="origin">The origin to subtract from the position.</param>
/// <param name="depth">The depth to draw at</param> /// <param name="depth">The depth to draw at</param>
public void DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, int indexInToken, Vector2 pos, GenericFont font, Color color, float scale, float depth) { /// <param name="effects">The flipping to draw with.</param>
/// <param name="stringSize">The size of the string.</param>
/// <param name="charSize">The size of the current character.</param>
public void DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, int indexInToken, Vector2 stringPos, Vector2 charPosOffset, GenericFont font, Color color, Vector2 scale, float rotation, Vector2 origin, float depth, SpriteEffects effects, Vector2 stringSize, Vector2 charSize) {
foreach (var code in this.AppliedCodes) { foreach (var code in this.AppliedCodes) {
if (code.DrawCharacter(time, batch, codePoint, character, this, indexInToken, ref pos, font, ref color, ref scale, depth)) if (code.DrawCharacter(time, batch, codePoint, character, this, indexInToken, stringPos, ref charPosOffset, font, ref color, ref scale, ref rotation, ref origin, depth, effects, stringSize, charSize))
return; return;
} }
// if no code drew, we have to do it ourselves // if no code drew, we have to do it ourselves
font.DrawString(batch, character, pos, color, 0, Vector2.Zero, scale, SpriteEffects.None, depth); var finalPos = font.TransformSingleCharacter(stringPos, charPosOffset, rotation, origin, scale, effects, stringSize, charSize);
font.DrawCharacter(batch, codePoint, character, finalPos, color, rotation, scale, effects, depth);
}
/// <inheritdoc cref="GetArea(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2)"/>
public IEnumerable<RectangleF> GetArea(Vector2 stringPos = default, float scale = 1) {
return this.GetArea(stringPos, new Vector2(scale));
} }
/// <summary> /// <summary>
@ -144,7 +161,7 @@ namespace MLEM.Formatting {
/// <param name="stringPos">The position that the string is drawn at</param> /// <param name="stringPos">The position that the string is drawn at</param>
/// <param name="scale">The scale that the string is drawn at</param> /// <param name="scale">The scale that the string is drawn at</param>
/// <returns>A set of rectangles that this token contains</returns> /// <returns>A set of rectangles that this token contains</returns>
public IEnumerable<RectangleF> GetArea(Vector2 stringPos, float scale) { public IEnumerable<RectangleF> GetArea(Vector2 stringPos, Vector2 scale) {
return this.Area.Select(a => new RectangleF(stringPos + a.Location * scale, a.Size * scale)); return this.Area.Select(a => new RectangleF(stringPos + a.Location * scale, a.Size * scale));
} }

View file

@ -140,7 +140,7 @@ namespace MLEM.Formatting {
/// <inheritdoc cref="GenericFont.MeasureString(string,bool)"/> /// <inheritdoc cref="GenericFont.MeasureString(string,bool)"/>
[Obsolete("Measure is deprecated. Use GetArea, which returns the string's total size measurement, instead.")] [Obsolete("Measure is deprecated. Use GetArea, which returns the string's total size measurement, instead.")]
public Vector2 Measure(GenericFont font) { public Vector2 Measure(GenericFont font) {
return this.GetArea(Vector2.Zero, 1).Size; return this.GetArea().Size;
} }
/// <summary> /// <summary>
@ -149,7 +149,7 @@ namespace MLEM.Formatting {
/// <param name="stringPos">The position that this string is being rendered at, which will offset the resulting <see cref="RectangleF"/>.</param> /// <param name="stringPos">The position that this string is being rendered at, which will offset the resulting <see cref="RectangleF"/>.</param>
/// <param name="scale">The scale that this string is being rendered with, which will scale the resulting <see cref="RectangleF"/>.</param> /// <param name="scale">The scale that this string is being rendered with, which will scale the resulting <see cref="RectangleF"/>.</param>
/// <returns>The area that this tokenized string takes up.</returns> /// <returns>The area that this tokenized string takes up.</returns>
public RectangleF GetArea(Vector2 stringPos, float scale) { public RectangleF GetArea(Vector2 stringPos = default, float scale = 1) {
return new RectangleF(stringPos + this.area.Location * scale, this.area.Size * scale); return new RectangleF(stringPos + this.area.Location * scale, this.area.Size * scale);
} }
@ -162,17 +162,30 @@ namespace MLEM.Formatting {
code.Update(time); code.Update(time);
} }
/// <inheritdoc cref="GetTokenUnderPos(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2,MLEM.Font.GenericFont,float,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Graphics.SpriteEffects)"/>
public Token GetTokenUnderPos(Vector2 stringPos, Vector2 target, float scale, GenericFont font = null, float rotation = 0, Vector2 origin = default, SpriteEffects effects = SpriteEffects.None) {
return this.GetTokenUnderPos(stringPos, target, new Vector2(scale), font, rotation, origin, effects);
}
/// <summary> /// <summary>
/// Returns the token under the given position. /// Returns the token under the given position.
/// This can be used for hovering effects when the mouse is over a token, etc. /// This can be used for hovering effects when the mouse is over a token, etc.
/// </summary> /// </summary>
/// <param name="stringPos">The position that the string is drawn at</param> /// <param name="stringPos">The position that the string is drawn at.</param>
/// <param name="target">The position to use for checking the token</param> /// <param name="target">The position to use for checking the token.</param>
/// <param name="scale">The scale that the string is drawn at</param> /// <param name="scale">The scale that the string is drawn at.</param>
/// <returns>The token under the target position</returns> /// <param name="font">The font that the string is being drawn with. If this is <see langword="null"/>, all following parameters are ignored.</param>
public Token GetTokenUnderPos(Vector2 stringPos, Vector2 target, float scale) { /// <param name="rotation">The rotation that the string is being drawn with. If <paramref name="font"/> is <see langword="null"/>, this this is ignored.</param>
/// <param name="origin">The origin that the string is being drawn with. If <paramref name="font"/> is <see langword="null"/>, this this is ignored.</param>
/// <param name="effects">The sprite effects that the string is being drawn with. If <paramref name="font"/> is <see langword="null"/>, this is ignored.</param>
/// <returns>The token under the target position</returns>^
public Token GetTokenUnderPos(Vector2 stringPos, Vector2 target, Vector2 scale, GenericFont font = null, float rotation = 0, Vector2 origin = default, SpriteEffects effects = SpriteEffects.None) {
if (font != null) {
var transform = font.CalculateStringTransform(stringPos, rotation, origin, scale, effects, this.area.Size);
target = Vector2.Transform(target, Matrix.Invert(transform));
}
foreach (var token in this.Tokens) { foreach (var token in this.Tokens) {
foreach (var rect in token.GetArea(stringPos, scale)) { foreach (var rect in font != null ? token.GetArea() : token.GetArea(stringPos, scale)) {
if (rect.Contains(target)) if (rect.Contains(target))
return token; return token;
} }
@ -181,8 +194,13 @@ namespace MLEM.Formatting {
} }
/// <inheritdoc cref="GenericFont.DrawString(SpriteBatch,string,Vector2,Color,float,Vector2,float,SpriteEffects,float)"/> /// <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, int? startIndex = null, int? endIndex = null) { public void Draw(GameTime time, SpriteBatch batch, Vector2 pos, GenericFont font, Color color, float scale, float depth, float rotation = 0, Vector2 origin = default, SpriteEffects effects = SpriteEffects.None, int? startIndex = null, int? endIndex = null) {
var innerOffset = new Vector2(this.initialInnerOffset * scale, 0); this.Draw(time, batch, pos, font, color, new Vector2(scale), depth, rotation, origin, effects, startIndex, endIndex);
}
/// <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, Vector2 scale, float depth, float rotation = 0, Vector2 origin = default, SpriteEffects effects = SpriteEffects.None, int? startIndex = null, int? endIndex = null) {
var innerOffset = new Vector2(this.initialInnerOffset, 0);
for (var t = 0; t < this.Tokens.Length; t++) { for (var t = 0; t < this.Tokens.Length; t++) {
var token = this.Tokens[t]; var token = this.Tokens[t];
if (endIndex != null && token.Index >= endIndex) if (endIndex != null && token.Index >= endIndex)
@ -192,8 +210,8 @@ namespace MLEM.Formatting {
var drawColor = token.GetColor(color); var drawColor = token.GetColor(color);
if (startIndex == null || token.Index >= startIndex) if (startIndex == null || token.Index >= startIndex)
token.DrawSelf(time, batch, pos + innerOffset, drawFont, drawColor, scale, depth); token.DrawSelf(time, batch, pos, innerOffset, drawFont, drawColor, scale, rotation, origin, depth, effects, this.area.Size);
innerOffset.X += token.GetSelfWidth(drawFont) * scale; innerOffset.X += token.GetSelfWidth(drawFont);
var indexInToken = 0; var indexInToken = 0;
for (var l = 0; l < token.SplitDisplayString.Length; l++) { for (var l = 0; l < token.SplitDisplayString.Length; l++) {
@ -205,19 +223,20 @@ namespace MLEM.Formatting {
var (codePoint, length) = line.GetCodePoint(cpsIndex); var (codePoint, length) = line.GetCodePoint(cpsIndex);
var character = CodePointSource.ToString(codePoint); var character = CodePointSource.ToString(codePoint);
var charSize = drawFont.MeasureString(character);
if (startIndex == null || token.Index + indexInToken >= startIndex) if (startIndex == null || token.Index + indexInToken >= startIndex)
token.DrawCharacter(time, batch, codePoint, character, indexInToken, pos + innerOffset, drawFont, drawColor, scale, depth); token.DrawCharacter(time, batch, codePoint, character, indexInToken, pos, innerOffset, drawFont, drawColor, scale, rotation, origin, depth, effects, this.area.Size, charSize);
innerOffset.X += drawFont.MeasureString(character).X * scale; innerOffset.X += charSize.X;
indexInToken += length; indexInToken += length;
cpsIndex += length; cpsIndex += length;
} }
// only split at a new line, not between tokens! // only split at a new line, not between tokens!
if (l < token.SplitDisplayString.Length - 1) { if (l < token.SplitDisplayString.Length - 1) {
innerOffset.X = token.InnerOffsets[l] * scale; innerOffset.X = token.InnerOffsets[l];
innerOffset.Y += drawFont.LineHeight * scale; innerOffset.Y += drawFont.LineHeight;
} }
} }
} }

View file

@ -11,7 +11,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Ellpeck</Authors> <Authors>Ellpeck</Authors>
<Description>The MLEM base package, which provides various small addons and abstractions for FNA, including a text formatting system and simple input handling</Description> <Description>The MLEM base package, which provides various small addons and abstractions for FNA, including a text formatting system and simple input handling</Description>
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes> <PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG.html</PackageReleaseNotes>
<PackageTags>fna ellpeck mlem utility extensions</PackageTags> <PackageTags>fna ellpeck mlem utility extensions</PackageTags>
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl> <PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl> <RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>

View file

@ -9,7 +9,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Ellpeck</Authors> <Authors>Ellpeck</Authors>
<Description>The MLEM base package, which provides various small addons and abstractions for MonoGame, including a text formatting system and simple input handling</Description> <Description>The MLEM base package, which provides various small addons and abstractions for MonoGame, including a text formatting system and simple input handling</Description>
<PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG</PackageReleaseNotes> <PackageReleaseNotes>See the full changelog at https://mlem.ellpeck.de/CHANGELOG.html</PackageReleaseNotes>
<PackageTags>monogame ellpeck mlem utility extensions</PackageTags> <PackageTags>monogame ellpeck mlem utility extensions</PackageTags>
<PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl> <PackageProjectUrl>https://mlem.ellpeck.de/</PackageProjectUrl>
<RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl> <RepositoryUrl>https://github.com/Ellpeck/MLEM</RepositoryUrl>

View file

@ -264,7 +264,7 @@ namespace MLEM.Misc {
/// </summary> /// </summary>
/// <param name="corner1">The first corner to use</param> /// <param name="corner1">The first corner to use</param>
/// <param name="corner2">The second corner to use</param> /// <param name="corner2">The second corner to use</param>
/// <returns></returns> /// <returns>The created rectangle.</returns>
public static RectangleF FromCorners(Vector2 corner1, Vector2 corner2) { public static RectangleF FromCorners(Vector2 corner1, Vector2 corner2) {
var minX = Math.Min(corner1.X, corner2.X); var minX = Math.Min(corner1.X, corner2.X);
var minY = Math.Min(corner1.Y, corner2.Y); var minY = Math.Min(corner1.Y, corner2.Y);
@ -273,6 +273,21 @@ namespace MLEM.Misc {
return new RectangleF(minX, minY, maxX - minX, maxY - minY); return new RectangleF(minX, minY, maxX - minX, maxY - minY);
} }
/// <summary>
/// Creates a new rectangle based on two corners that form a bounding box.
/// The resulting rectangle will encompass both corners as well as all of the space between them.
/// </summary>
/// <param name="corner1">The first corner to use</param>
/// <param name="corner2">The second corner to use</param>
/// <returns>The created rectangle.</returns>
public static RectangleF FromCorners(Point corner1, Point corner2) {
var minX = Math.Min(corner1.X, corner2.X);
var minY = Math.Min(corner1.Y, corner2.Y);
var maxX = Math.Max(corner1.X, corner2.X);
var maxY = Math.Max(corner1.Y, corner2.Y);
return new RectangleF(minX, minY, maxX - minX, maxY - minY);
}
/// <summary> /// <summary>
/// Converts an int-based rectangle to a float-based rectangle. /// Converts an int-based rectangle to a float-based rectangle.
/// </summary> /// </summary>

View file

@ -10,7 +10,7 @@ MLEM is platform-agnostic and multi-targets .NET Standard 2.0, .NET 6.0 and .NET
- See the source code on [GitHub](https://github.com/Ellpeck/MLEM) - See the source code on [GitHub](https://github.com/Ellpeck/MLEM)
- See tutorials and API documentation on [the website](https://mlem.ellpeck.de/) - See tutorials and API documentation on [the website](https://mlem.ellpeck.de/)
- Check out [the demos](https://github.com/Ellpeck/MLEM/tree/main/Demos) on [Desktop](https://github.com/Ellpeck/MLEM/tree/main/Demos.DesktopGL) or [Android](https://github.com/Ellpeck/MLEM/tree/main/Demos.Android) - Check out [the demos](https://github.com/Ellpeck/MLEM/tree/main/Demos) on [Desktop](https://github.com/Ellpeck/MLEM/tree/main/Demos.DesktopGL) or [Android](https://github.com/Ellpeck/MLEM/tree/main/Demos.Android)
- See [the changelog](https://github.com/Ellpeck/MLEM/blob/main/CHANGELOG.md) for information on updates - See [the changelog](https://mlem.ellpeck.de/CHANGELOG.html) for information on updates
- Join [the Discord server](https://link.ellpeck.de/discordweb) to ask questions - Join [the Discord server](https://link.ellpeck.de/discordweb) to ask questions
# Packages # Packages

View file

@ -11,6 +11,7 @@ using MLEM.Data;
using MLEM.Data.Content; using MLEM.Data.Content;
using MLEM.Extended.Font; using MLEM.Extended.Font;
using MLEM.Extensions; using MLEM.Extensions;
using MLEM.Font;
using MLEM.Formatting; using MLEM.Formatting;
using MLEM.Formatting.Codes; using MLEM.Formatting.Codes;
using MLEM.Graphics; using MLEM.Graphics;
@ -70,9 +71,7 @@ public class GameImpl : MlemGame {
//var font = new GenericSpriteFont(LoadContent<SpriteFont>("Fonts/TestFont")); //var font = new GenericSpriteFont(LoadContent<SpriteFont>("Fonts/TestFont"));
//var font = new GenericBitmapFont(LoadContent<BitmapFont>("Fonts/Regular")); //var font = new GenericBitmapFont(LoadContent<BitmapFont>("Fonts/Regular"));
var font = new GenericStashFont(system.GetFont(32)); var font = new GenericStashFont(system.GetFont(32));
/*
var spriteFont = new GenericSpriteFont(MlemGame.LoadContent<SpriteFont>("Fonts/TestFont")); var spriteFont = new GenericSpriteFont(MlemGame.LoadContent<SpriteFont>("Fonts/TestFont"));
*/
this.UiSystem.Style = new UntexturedStyle(this.SpriteBatch) { this.UiSystem.Style = new UntexturedStyle(this.SpriteBatch) {
Font = font, Font = font,
TextScale = 0.5F, TextScale = 0.5F,
@ -258,43 +257,45 @@ public class GameImpl : MlemGame {
}).SetData("Ref", "Main"); }).SetData("Ref", "Main");
this.UiSystem.Add("SpillTest", spillPanel); this.UiSystem.Add("SpillTest", spillPanel);
/* var regularFont = spriteFont.Font; var regularFont = spriteFont.Font;
var genericFont = spriteFont; var genericFont = spriteFont;
var index = 0; var index = 0;
var pos = new Vector2(100, 20); var pos = new Vector2(100, 20);
var scale = 1F; var scale = 1F;
var origin = Vector2.Zero; var origin = Vector2.Zero;
var rotation = 0F; var rotation = 0F;
var effects = SpriteEffects.None; var effects = SpriteEffects.None;
this.OnDraw += (_, _) => { const string testString = "This is a\ntest string\n\twith long lines.\nLet's write some more stuff. Let's\r\nsplit lines weirdly.";
const string testString = "This is a\ntest string\n\twith long lines.\nLet's write some more stuff. Let's\r\nsplit lines weirdly."; var formatted = formatter.Tokenize(genericFont, testString);
if (MlemGame.Input.IsKeyPressed(Keys.I)) {
index++;
if (index == 1) {
scale = 2;
} else if (index == 2) {
origin = new Vector2(15, 15);
} else if (index == 3) {
rotation = 0.25F;
} else if (index == 4) {
effects = SpriteEffects.FlipHorizontally;
} else if (index == 5) {
effects = SpriteEffects.FlipVertically;
} else if (index == 6) {
effects = SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically;
}
}
this.SpriteBatch.Begin(); this.OnDraw += (_, time) => {
if (MlemGame.Input.IsKeyDown(Keys.LeftShift)) { if (MlemGame.Input.IsPressed(Keys.I)) {
this.SpriteBatch.DrawString(regularFont, testString, pos, Color.Red, rotation, origin, scale, effects, 0); index++;
} else { if (index == 1) {
genericFont.DrawString(this.SpriteBatch, testString, pos, Color.Green, rotation, origin, scale, effects, 0); scale = 2;
} } else if (index == 2) {
this.SpriteBatch.End(); origin = new Vector2(15, 15);
};*/ } else if (index == 3) {
rotation = 0.25F;
} else if (index == 4) {
effects = SpriteEffects.FlipHorizontally;
} else if (index == 5) {
effects = SpriteEffects.FlipVertically;
} else if (index == 6) {
effects = SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically;
}
}
this.SpriteBatch.Begin();
if (MlemGame.Input.IsDown(Keys.LeftShift)) {
this.SpriteBatch.DrawString(regularFont, testString, pos, Color.Red, rotation, origin, scale, effects, 0);
} else {
formatted.Draw(time, this.SpriteBatch, pos, genericFont, Color.Green, scale, 0, rotation, origin, effects);
}
this.SpriteBatch.End();
};
/*var viewport = new BoxingViewportAdapter(this.Window, this.GraphicsDevice, 1280, 720); /*var viewport = new BoxingViewportAdapter(this.Window, this.GraphicsDevice, 1280, 720);
var newPanel = new Panel(Anchor.TopLeft, new Vector2(200, 100), new Vector2(10, 10)); var newPanel = new Panel(Anchor.TopLeft, new Vector2(200, 100), new Vector2(10, 10));

View file

@ -2,7 +2,7 @@
#tool dotnet:?package=docfx&version=2.75.3 #tool dotnet:?package=docfx&version=2.75.3
// this is the upcoming version, for prereleases // this is the upcoming version, for prereleases
var version = Argument("version", "6.3.1"); var version = Argument("version", "7.0.0");
var target = Argument("target", "Default"); var target = Argument("target", "Default");
var gitRef = Argument("ref", "refs/heads/main"); var gitRef = Argument("ref", "refs/heads/main");
var buildNum = Argument("buildNum", ""); var buildNum = Argument("buildNum", "");