From b1d41d572cba080665bbb61d261d932a5d4cb2e0 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Fri, 9 Aug 2019 19:28:48 +0200 Subject: [PATCH] added panels and paragraphs --- MLEM.Extended/Font/GenericBitmapFont.cs | 10 ++++ MLEM.Ui/Elements/Element.cs | 70 ++++++++++++++++++------- MLEM.Ui/Elements/Panel.cs | 21 ++++++++ MLEM.Ui/Elements/Paragraph.cs | 68 ++++++++++++++++++++++++ MLEM.Ui/UiSystem.cs | 19 +++++-- MLEM/Font/GenericSpriteFont.cs | 10 ++++ MLEM/Font/IGenericFont.cs | 5 ++ MLEM/Textures/NinePatch.cs | 3 +- Tests/Content/Content.mgcb | 7 +++ Tests/Content/Fonts/TestFont.spritefont | 60 +++++++++++++++++++++ Tests/GameImpl.cs | 28 ++-------- 11 files changed, 252 insertions(+), 49 deletions(-) create mode 100644 MLEM.Ui/Elements/Panel.cs create mode 100644 MLEM.Ui/Elements/Paragraph.cs create mode 100644 Tests/Content/Fonts/TestFont.spritefont diff --git a/MLEM.Extended/Font/GenericBitmapFont.cs b/MLEM.Extended/Font/GenericBitmapFont.cs index 17cd34e..b7d778c 100644 --- a/MLEM.Extended/Font/GenericBitmapFont.cs +++ b/MLEM.Extended/Font/GenericBitmapFont.cs @@ -1,6 +1,8 @@ +using System.Collections.Generic; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using MLEM.Extended.Extensions; using MLEM.Font; using MonoGame.Extended.BitmapFonts; @@ -49,5 +51,13 @@ namespace MLEM.Extended.Font { batch.DrawString(this.Font, text, position, color, rotation, origin, scale, effects, layerDepth); } + public void DrawCenteredString(SpriteBatch batch, string text, Vector2 position, float scale, Color color, bool horizontal = true, bool vertical = false, float addedScale = 0) { + batch.DrawCenteredString(this.Font, text, position, scale, color, horizontal, vertical, addedScale); + } + + public IEnumerable SplitString(string text, float width, float scale) { + return this.Font.SplitString(text, width, scale); + } + } } \ No newline at end of file diff --git a/MLEM.Ui/Elements/Element.cs b/MLEM.Ui/Elements/Element.cs index 376ddaf..e74d17f 100644 --- a/MLEM.Ui/Elements/Element.cs +++ b/MLEM.Ui/Elements/Element.cs @@ -5,12 +5,13 @@ using Microsoft.Xna.Framework.Graphics; using MLEM.Extensions; namespace MLEM.Ui.Elements { - public class Element { + public abstract class Element { private Anchor anchor; - private Point size; + private Vector2 size; private Point offset; private Point padding; + private Point childPadding; public Anchor Anchor { get => this.anchor; set { @@ -18,7 +19,7 @@ namespace MLEM.Ui.Elements { this.SetDirty(); } } - public Point Size { + public Vector2 Size { get => this.size; set { this.size = value; @@ -39,8 +40,15 @@ namespace MLEM.Ui.Elements { this.SetDirty(); } } + public Point ChildPadding { + get => this.childPadding; + set { + this.childPadding = value; + this.SetDirty(); + } + } - public UiSystem System; + public UiSystem System { get; private set; } public Element Parent { get; private set; } private readonly List children = new List(); @@ -61,43 +69,54 @@ namespace MLEM.Ui.Elements { } } - protected bool AreaDirty; + private bool areaDirty; - public Element(Anchor anchor, Point size, Point positionOffset) { + public Element(Anchor anchor, Vector2 size, Point positionOffset) { this.anchor = anchor; this.size = size; this.offset = positionOffset; + this.SetDirty(); } public void AddChild(Element element) { this.children.Add(element); element.Parent = this; + element.System = this.System; this.SetDirty(); } public void RemoveChild(Element element) { this.children.Remove(element); element.Parent = null; + element.System = null; this.SetDirty(); } public void SetDirty() { - this.AreaDirty = true; + this.areaDirty = true; } public void UpdateAreaIfDirty() { - if (this.AreaDirty) + if (this.areaDirty) this.ForceUpdateArea(); } - public void ForceUpdateArea() { - this.AreaDirty = false; + public virtual void ForceUpdateArea() { + this.areaDirty = false; - var parentArea = this.Parent != null ? this.Parent.area : this.System.ScaledViewport; + Rectangle parentArea; + if (this.Parent != null) { + parentArea = this.Parent.area; + parentArea.Location += this.Parent.ChildPadding; + parentArea.Width -= this.Parent.ChildPadding.X * 2; + parentArea.Height -= this.Parent.ChildPadding.Y * 2; + } else { + parentArea = this.System.ScaledViewport; + } var parentCenterX = parentArea.X + parentArea.Width / 2; var parentCenterY = parentArea.Y + parentArea.Height / 2; - var actualSize = this.CalcActualSize(); + var actualSize = this.CalcActualSize(parentArea); var pos = new Point(); switch (this.anchor) { @@ -177,11 +196,13 @@ namespace MLEM.Ui.Elements { child.ForceUpdateArea(); } - private Point CalcActualSize() { - return this.size; + protected virtual Point CalcActualSize(Rectangle parentArea) { + return new Point( + (this.size.X > 1 ? this.size.X : parentArea.Width * this.size.X).Floor(), + (this.size.Y > 1 ? this.size.Y : parentArea.Height * this.size.Y).Floor()); } - public Element GetPreviousChild() { + protected Element GetPreviousChild() { if (this.Parent == null) return null; @@ -194,16 +215,25 @@ namespace MLEM.Ui.Elements { return lastChild; } - public void Update(GameTime time) { + public virtual void Update(GameTime time) { foreach (var child in this.children) child.Update(time); } - public void Draw(GameTime time, SpriteBatch batch) { - batch.Draw(batch.GetBlankTexture(), this.DisplayArea, this.Parent == null ? Color.Blue : Color.Red); - + public virtual void Draw(GameTime time, SpriteBatch batch, Color color) { foreach (var child in this.children) - child.Draw(time, batch); + child.Draw(time, batch, color); + } + + public virtual void DrawUnbound(GameTime time, SpriteBatch batch, Color color, BlendState blendState = null, SamplerState samplerState = null) { + foreach (var child in this.children) + child.DrawUnbound(time, batch, color, blendState, samplerState); + } + + public void SetUiSystem(UiSystem system) { + this.System = system; + foreach (var child in this.children) + child.System = system; } } diff --git a/MLEM.Ui/Elements/Panel.cs b/MLEM.Ui/Elements/Panel.cs new file mode 100644 index 0000000..2e6b84f --- /dev/null +++ b/MLEM.Ui/Elements/Panel.cs @@ -0,0 +1,21 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MLEM.Textures; + +namespace MLEM.Ui.Elements { + public class Panel : Element { + + private readonly NinePatch texture; + + public Panel(Anchor anchor, Vector2 size, Point positionOffset, NinePatch texture) : base(anchor, size, positionOffset) { + this.texture = texture; + this.ChildPadding = new Point(5); + } + + public override void Draw(GameTime time, SpriteBatch batch, Color color) { + batch.Draw(this.texture, this.DisplayArea, color); + base.Draw(time, batch, color); + } + + } +} \ No newline at end of file diff --git a/MLEM.Ui/Elements/Paragraph.cs b/MLEM.Ui/Elements/Paragraph.cs new file mode 100644 index 0000000..03a6014 --- /dev/null +++ b/MLEM.Ui/Elements/Paragraph.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MLEM.Extensions; +using MLEM.Font; + +namespace MLEM.Ui.Elements { + public class Paragraph : Element { + + private string text; + private float lineHeight; + private string[] splitText; + private readonly IGenericFont font; + private readonly bool centerText; + + public float TextScale; + public string Text { + get => this.text; + set { + this.text = value; + this.SetDirty(); + } + + } + public IGenericFont Font => this.font ?? this.System.DefaultFont; + + public Paragraph(Anchor anchor, float width, Point positionOffset, string text, float textScale = 1, bool centerText = false, IGenericFont font = null) : base(anchor, new Vector2(width, 0), positionOffset) { + this.text = text; + this.font = font; + this.TextScale = textScale; + this.centerText = centerText; + } + + protected override Point CalcActualSize(Rectangle parentArea) { + var size = base.CalcActualSize(parentArea); + this.splitText = this.Font.SplitString(this.text, size.X, this.TextScale).ToArray(); + + this.lineHeight = 0; + var height = 0F; + foreach (var strg in this.splitText) { + var strgHeight = this.Font.MeasureString(strg).Y * this.TextScale; + height += strgHeight + 1; + if (strgHeight > this.lineHeight) + this.lineHeight = strgHeight; + } + return new Point(size.X, height.Ceil()); + } + + public override void Draw(GameTime time, SpriteBatch batch, Color color) { + base.Draw(time, batch, color); + + var pos = this.DisplayArea.Location.ToVector2(); + var offset = new Vector2(); + foreach (var line in this.splitText) { + if (this.centerText) { + this.Font.DrawCenteredString(batch, line, pos + offset + new Vector2(this.DisplayArea.Width / 2, 0), this.TextScale, color); + } else { + this.Font.DrawString(batch, line, pos + offset, color, 0, Vector2.Zero, this.TextScale, SpriteEffects.None, 0); + } + offset.Y += this.lineHeight + 1; + } + } + + } + +} \ No newline at end of file diff --git a/MLEM.Ui/UiSystem.cs b/MLEM.Ui/UiSystem.cs index 17eefcc..1294a0b 100644 --- a/MLEM.Ui/UiSystem.cs +++ b/MLEM.Ui/UiSystem.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using MLEM.Extensions; +using MLEM.Font; using MLEM.Ui.Elements; namespace MLEM.Ui { @@ -11,7 +12,8 @@ namespace MLEM.Ui { private readonly GraphicsDevice graphicsDevice; private readonly List rootElements = new List(); - public float GlobalScale; + public readonly float GlobalScale; + public readonly IGenericFont DefaultFont; public Rectangle ScaledViewport { get { var bounds = this.graphicsDevice.Viewport.Bounds; @@ -19,9 +21,11 @@ namespace MLEM.Ui { } } - public UiSystem(GameWindow window, GraphicsDevice device, float scale) { + public UiSystem(GameWindow window, GraphicsDevice device, float scale, IGenericFont defaultFont) { this.graphicsDevice = device; this.GlobalScale = scale; + this.DefaultFont = defaultFont; + window.ClientSizeChanged += (sender, args) => { foreach (var root in this.rootElements) root.Element.ForceUpdateArea(); @@ -33,11 +37,16 @@ namespace MLEM.Ui { root.Element.Update(time); } - public void Draw(GameTime time, SpriteBatch batch, BlendState blendState = null, SamplerState samplerState = null) { + public void Draw(GameTime time, SpriteBatch batch, Color? color = null, BlendState blendState = null, SamplerState samplerState = null) { + var col = color ?? Color.White; + batch.Begin(SpriteSortMode.Deferred, blendState, samplerState, transformMatrix: Matrix.CreateScale(this.GlobalScale)); foreach (var root in this.rootElements) - root.Element.Draw(time, batch); + root.Element.Draw(time, batch, col); batch.End(); + + foreach (var root in this.rootElements) + root.Element.DrawUnbound(time, batch, col, blendState, samplerState); } public void Add(string name, Element root) { @@ -45,7 +54,7 @@ namespace MLEM.Ui { throw new ArgumentException($"There is already a root element with name {name}"); this.rootElements.Add(new RootElement(name, root)); - root.System = this; + root.SetUiSystem(this); } public void Remove(string name) { diff --git a/MLEM/Font/GenericSpriteFont.cs b/MLEM/Font/GenericSpriteFont.cs index 2eb4c00..e4640f0 100644 --- a/MLEM/Font/GenericSpriteFont.cs +++ b/MLEM/Font/GenericSpriteFont.cs @@ -1,6 +1,8 @@ +using System.Collections.Generic; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using MLEM.Extensions; namespace MLEM.Font { public class GenericSpriteFont : IGenericFont { @@ -47,5 +49,13 @@ namespace MLEM.Font { batch.DrawString(this.Font, text, position, color, rotation, origin, scale, effects, layerDepth); } + public void DrawCenteredString(SpriteBatch batch, string text, Vector2 position, float scale, Color color, bool horizontal = true, bool vertical = false, float addedScale = 0) { + batch.DrawCenteredString(this.Font, text, position, scale, color, horizontal, vertical, addedScale); + } + + public IEnumerable SplitString(string text, float width, float scale) { + return this.Font.SplitString(text, width, scale); + } + } } \ No newline at end of file diff --git a/MLEM/Font/IGenericFont.cs b/MLEM/Font/IGenericFont.cs index f4414e4..da78954 100644 --- a/MLEM/Font/IGenericFont.cs +++ b/MLEM/Font/IGenericFont.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -21,5 +22,9 @@ namespace MLEM.Font { void DrawString(SpriteBatch batch, StringBuilder text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth); + void DrawCenteredString(SpriteBatch batch, string text, Vector2 position, float scale, Color color, bool horizontal = true, bool vertical = false, float addedScale = 0); + + IEnumerable SplitString(string text, float width, float scale); + } } \ No newline at end of file diff --git a/MLEM/Textures/NinePatch.cs b/MLEM/Textures/NinePatch.cs index 9fa5afd..1342509 100644 --- a/MLEM/Textures/NinePatch.cs +++ b/MLEM/Textures/NinePatch.cs @@ -57,7 +57,8 @@ namespace MLEM.Textures { var dest = texture.CreateRectangles(destinationRectangle); var count = 0; foreach (var rect in dest) { - batch.Draw(texture.Region.Texture, rect, texture.SourceRectangles[count], color, rotation, origin, effects, layerDepth); + if (!rect.IsEmpty) + batch.Draw(texture.Region.Texture, rect, texture.SourceRectangles[count], color, rotation, origin, effects, layerDepth); count++; } } diff --git a/Tests/Content/Content.mgcb b/Tests/Content/Content.mgcb index d80adce..ff7a781 100644 --- a/Tests/Content/Content.mgcb +++ b/Tests/Content/Content.mgcb @@ -13,6 +13,13 @@ #---------------------------------- Content ---------------------------------# +#begin Fonts/TestFont.spritefont +/importer:FontDescriptionImporter +/processor:FontDescriptionProcessor +/processorParam:PremultiplyAlpha=True +/processorParam:TextureFormat=Compressed +/build:Fonts/TestFont.spritefont + #begin Textures/Test.png /importer:TextureImporter /processor:TextureProcessor diff --git a/Tests/Content/Fonts/TestFont.spritefont b/Tests/Content/Fonts/TestFont.spritefont new file mode 100644 index 0000000..b31c317 --- /dev/null +++ b/Tests/Content/Fonts/TestFont.spritefont @@ -0,0 +1,60 @@ + + + + + + + BitPotion + + + 32 + + + 0 + + + true + + + + + + + + + + + + ~ + + + + diff --git a/Tests/GameImpl.cs b/Tests/GameImpl.cs index 7bcc62b..7429029 100644 --- a/Tests/GameImpl.cs +++ b/Tests/GameImpl.cs @@ -3,6 +3,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using MLEM.Extended.Extensions; +using MLEM.Font; using MLEM.Input; using MLEM.Startup; using MLEM.Textures; @@ -14,28 +15,19 @@ namespace Tests { public class GameImpl : MlemGame { private Texture2D testTexture; - private TextureRegion testRegion; private NinePatch testPatch; - private NinePatchRegion2D extendedPatch; - private UiSystem uiSystem; - private Element testChild; protected override void LoadContent() { base.LoadContent(); this.testTexture = LoadContent("Textures/Test"); - this.testRegion = new TextureRegion(this.testTexture, 32, 0, 8, 8); this.testPatch = new NinePatch(new TextureRegion(this.testTexture, 0, 8, 24, 24), 8); - this.extendedPatch = this.testPatch.ToExtended(); // Ui system tests - this.uiSystem = new UiSystem(this.Window, this.GraphicsDevice, 5); + this.uiSystem = new UiSystem(this.Window, this.GraphicsDevice, 5, new GenericSpriteFont(LoadContent("Fonts/TestFont"))); - var root = new Element(Anchor.BottomLeft, new Point(100, 100), new Point(5, 5)); - for (var i = 0; i < 3; i++) - root.AddChild(new Element(Anchor.AutoInline, new Point(16, 16), Point.Zero) { - Padding = new Point(1, 1) - }); + var root = new Panel(Anchor.BottomLeft, new Vector2(100, 100), new Point(5, 5), this.testPatch); + root.AddChild(new Paragraph(Anchor.AutoLeft, 1, Point.Zero, "This is a test text that is hopefully long enough to cover at least a few lines, otherwise it would be very sad.", 0.2F)); this.uiSystem.Add("Test", root); } @@ -57,17 +49,7 @@ namespace Tests { base.Draw(gameTime); this.GraphicsDevice.Clear(Color.Black); - // Texture region tests - this.SpriteBatch.Begin(samplerState: SamplerState.PointClamp); - this.SpriteBatch.Draw(this.testRegion, new Vector2(10, 10), Color.White); - this.SpriteBatch.Draw(this.testRegion, new Vector2(30, 10), Color.White, 0, Vector2.Zero, 10, SpriteEffects.None, 0); - this.SpriteBatch.End(); - this.SpriteBatch.Begin(samplerState: SamplerState.PointClamp, transformMatrix: Matrix.CreateScale(10)); - this.SpriteBatch.Draw(this.testPatch, new Rectangle(20, 20, 40, 20), Color.White); - this.SpriteBatch.Draw(this.extendedPatch, new Rectangle(80, 20, 40, 20), Color.White); - this.SpriteBatch.End(); - - this.uiSystem.Draw(gameTime, this.SpriteBatch); + this.uiSystem.Draw(gameTime, this.SpriteBatch, samplerState: SamplerState.PointClamp); } }