From ef3726b077dd25e63581a87b6bc6b209d7cc80bc Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Fri, 9 Aug 2019 22:04:26 +0200 Subject: [PATCH] added auto-scaled text, buttons and vertical space --- MLEM.Ui/Elements/AutoScaledText.cs | 50 +++++++++++++++++++++++++++++ MLEM.Ui/Elements/Button.cs | 39 ++++++++++++++++++++++ MLEM.Ui/Elements/Element.cs | 36 ++++++++++++++++++--- MLEM.Ui/Elements/Panel.cs | 3 +- MLEM.Ui/Elements/Paragraph.cs | 2 +- MLEM.Ui/Elements/VerticalSpace.cs | 10 ++++++ MLEM.Ui/UiSystem.cs | 39 +++++++++++++++++++++- MLEM/Extensions/ColorExtensions.cs | 4 +++ MLEM/Input/InputHandler.cs | 5 +++ MLEM/Misc/EnumHelper.cs | 13 ++++++++ Tests/Content/Textures/Test.png | Bin 1003 -> 1046 bytes Tests/GameImpl.cs | 14 ++++++-- 12 files changed, 206 insertions(+), 9 deletions(-) create mode 100644 MLEM.Ui/Elements/AutoScaledText.cs create mode 100644 MLEM.Ui/Elements/Button.cs create mode 100644 MLEM.Ui/Elements/VerticalSpace.cs create mode 100644 MLEM/Misc/EnumHelper.cs diff --git a/MLEM.Ui/Elements/AutoScaledText.cs b/MLEM.Ui/Elements/AutoScaledText.cs new file mode 100644 index 0000000..c46c97c --- /dev/null +++ b/MLEM.Ui/Elements/AutoScaledText.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MLEM.Extensions; +using MLEM.Font; + +namespace MLEM.Ui.Elements { + public class AutoScaledText : Element { + + private readonly IGenericFont font; + private readonly bool centered; + private float scale; + private string text; + + public IGenericFont Font => this.font ?? this.System.DefaultFont; + public string Text { + get => this.text; + set { + this.text = value; + this.SetDirty(); + } + } + public Color Color; + + public AutoScaledText(Anchor anchor, Vector2 size, string text, bool centered = false, Color? color = null, IGenericFont font = null) : base(anchor, size) { + this.Text = text; + this.centered = centered; + this.font = font; + this.Color = color ?? Color.White; + } + + public override void ForceUpdateArea() { + base.ForceUpdateArea(); + + this.scale = 0; + Vector2 measure; + do { + this.scale += 0.1F; + measure = this.Font.MeasureString(this.Text) * this.scale; + } while (measure.X <= this.DisplayArea.Size.X && measure.Y <= this.DisplayArea.Size.Y); + } + + public override void Draw(GameTime time, SpriteBatch batch, Color color) { + var pos = this.DisplayArea.Location.ToVector2() + this.DisplayArea.Size.ToVector2() / 2; + this.Font.DrawCenteredString(batch, this.Text, pos, this.scale, this.Color.CopyAlpha(color), true, true); + base.Draw(time, batch, color); + } + + } +} \ No newline at end of file diff --git a/MLEM.Ui/Elements/Button.cs b/MLEM.Ui/Elements/Button.cs new file mode 100644 index 0000000..64ce765 --- /dev/null +++ b/MLEM.Ui/Elements/Button.cs @@ -0,0 +1,39 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MLEM.Extensions; +using MLEM.Textures; + +namespace MLEM.Ui.Elements { + public class Button : Element { + + public NinePatch Texture; + public NinePatch HoveredTexture; + public Color HoveredColor; + public AutoScaledText Text; + + public Button(Anchor anchor, Vector2 size, NinePatch texture, string text = null, NinePatch hoveredTexture = null, Color? hoveredColor = null) : base(anchor, size) { + this.Texture = texture; + this.HoveredTexture = hoveredTexture; + this.HoveredColor = hoveredColor ?? Color.White; + + if (text != null) { + this.Text = new AutoScaledText(Anchor.Center, Vector2.One, text, true) { + IgnoresMouse = true + }; + this.AddChild(this.Text); + } + } + + public override void Draw(GameTime time, SpriteBatch batch, Color color) { + var tex = this.Texture; + if (this.IsMouseOver) { + if (this.HoveredTexture != null) + tex = this.HoveredTexture; + color = this.HoveredColor.CopyAlpha(color); + } + batch.Draw(tex, this.DisplayArea, color); + base.Draw(time, batch, color); + } + + } +} \ No newline at end of file diff --git a/MLEM.Ui/Elements/Element.cs b/MLEM.Ui/Elements/Element.cs index eb89f39..426c0cf 100644 --- a/MLEM.Ui/Elements/Element.cs +++ b/MLEM.Ui/Elements/Element.cs @@ -3,10 +3,12 @@ using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using MLEM.Extensions; +using MLEM.Input; namespace MLEM.Ui.Elements { public abstract class Element { + private readonly List children = new List(); private Anchor anchor; private Vector2 size; private Point offset; @@ -48,10 +50,16 @@ namespace MLEM.Ui.Elements { } } + public MouseClickCallback OnClicked; + public MouseClickCallback OnMouseDown; + public MouseCallback OnMouseEnter; + public MouseCallback OnMouseExit; + public UiSystem System { get; private set; } public Element Parent { get; private set; } + public bool IsMouseOver { get; private set; } public bool IsHidden; - private readonly List children = new List(); + public bool IgnoresMouse; private Rectangle area; public Rectangle Area { @@ -72,10 +80,13 @@ namespace MLEM.Ui.Elements { } private bool areaDirty; - public Element(Anchor anchor, Vector2 size, Point positionOffset) { + public Element(Anchor anchor, Vector2 size) { this.anchor = anchor; this.size = size; - this.offset = positionOffset; + + this.OnMouseEnter += (element, mousePos) => this.IsMouseOver = true; + this.OnMouseExit += (element, mousePos) => this.IsMouseOver = false; + this.SetDirty(); } @@ -238,8 +249,25 @@ namespace MLEM.Ui.Elements { public void SetUiSystem(UiSystem system) { this.System = system; foreach (var child in this.children) - child.System = system; + child.SetUiSystem(system); } + public Element GetMousedElement(Vector2 mousePos) { + if (this.IsHidden || this.IgnoresMouse) + return null; + if (!this.Area.Contains(mousePos)) + return null; + foreach (var child in this.children) { + var element = child.GetMousedElement(mousePos); + if (element != null) + return element; + } + return this; + } + + public delegate void MouseClickCallback(Element element, Vector2 mousePos, MouseButton button); + + public delegate void MouseCallback(Element element, Vector2 mousePos); + } } \ No newline at end of file diff --git a/MLEM.Ui/Elements/Panel.cs b/MLEM.Ui/Elements/Panel.cs index 2e6b84f..47677dd 100644 --- a/MLEM.Ui/Elements/Panel.cs +++ b/MLEM.Ui/Elements/Panel.cs @@ -7,8 +7,9 @@ namespace MLEM.Ui.Elements { private readonly NinePatch texture; - public Panel(Anchor anchor, Vector2 size, Point positionOffset, NinePatch texture) : base(anchor, size, positionOffset) { + public Panel(Anchor anchor, Vector2 size, Point positionOffset, NinePatch texture) : base(anchor, size) { this.texture = texture; + this.PositionOffset = positionOffset; this.ChildPadding = new Point(5); } diff --git a/MLEM.Ui/Elements/Paragraph.cs b/MLEM.Ui/Elements/Paragraph.cs index 03a6014..b6af4fa 100644 --- a/MLEM.Ui/Elements/Paragraph.cs +++ b/MLEM.Ui/Elements/Paragraph.cs @@ -26,7 +26,7 @@ namespace MLEM.Ui.Elements { } 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) { + public Paragraph(Anchor anchor, float width, string text, float textScale = 1, bool centerText = false, IGenericFont font = null) : base(anchor, new Vector2(width, 0)) { this.text = text; this.font = font; this.TextScale = textScale; diff --git a/MLEM.Ui/Elements/VerticalSpace.cs b/MLEM.Ui/Elements/VerticalSpace.cs new file mode 100644 index 0000000..13cb800 --- /dev/null +++ b/MLEM.Ui/Elements/VerticalSpace.cs @@ -0,0 +1,10 @@ +using Microsoft.Xna.Framework; + +namespace MLEM.Ui.Elements { + public class VerticalSpace : Element { + + public VerticalSpace(int height) : base(Anchor.AutoCenter, new Vector2(1, height)) { + } + + } +} \ No newline at end of file diff --git a/MLEM.Ui/UiSystem.cs b/MLEM.Ui/UiSystem.cs index d2104fa..1b201b8 100644 --- a/MLEM.Ui/UiSystem.cs +++ b/MLEM.Ui/UiSystem.cs @@ -4,6 +4,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using MLEM.Extensions; using MLEM.Font; +using MLEM.Input; using MLEM.Ui.Elements; namespace MLEM.Ui { @@ -11,6 +12,8 @@ namespace MLEM.Ui { private readonly GraphicsDevice graphicsDevice; private readonly List rootElements = new List(); + private readonly InputHandler inputHandler; + private readonly bool isInputOurs; public readonly float GlobalScale; public readonly IGenericFont DefaultFont; @@ -20,11 +23,15 @@ namespace MLEM.Ui { return new Rectangle(bounds.X, bounds.Y, (bounds.Width / this.GlobalScale).Floor(), (bounds.Height / this.GlobalScale).Floor()); } } + public Vector2 MousePos => this.inputHandler.MousePosition.ToVector2() / this.GlobalScale; + public Element MousedElement { get; private set; } - public UiSystem(GameWindow window, GraphicsDevice device, float scale, IGenericFont defaultFont) { + public UiSystem(GameWindow window, GraphicsDevice device, float scale, IGenericFont defaultFont, InputHandler inputHandler = null) { this.graphicsDevice = device; this.GlobalScale = scale; this.DefaultFont = defaultFont; + this.inputHandler = inputHandler ?? new InputHandler(); + this.isInputOurs = inputHandler == null; window.ClientSizeChanged += (sender, args) => { foreach (var root in this.rootElements) @@ -33,6 +40,27 @@ namespace MLEM.Ui { } public void Update(GameTime time) { + if (this.isInputOurs) + this.inputHandler.Update(); + + var mousedNow = this.GetMousedElement(); + if (mousedNow != this.MousedElement) { + if (this.MousedElement != null) + this.MousedElement.OnMouseExit?.Invoke(this.MousedElement, this.MousePos); + if (mousedNow != null) + mousedNow.OnMouseEnter?.Invoke(mousedNow, this.MousePos); + this.MousedElement = mousedNow; + } + + if (mousedNow != null && (mousedNow.OnMouseDown != null || mousedNow.OnClicked != null)) { + foreach (var button in InputHandler.MouseButtons) { + if (mousedNow.OnMouseDown != null && this.inputHandler.IsMouseButtonDown(button)) + mousedNow.OnMouseDown(mousedNow, this.MousePos, button); + if (mousedNow.OnClicked != null && this.inputHandler.IsMouseButtonPressed(button)) + mousedNow.OnClicked(mousedNow, this.MousePos, button); + } + } + foreach (var root in this.rootElements) root.Element.Update(time); } @@ -77,6 +105,15 @@ namespace MLEM.Ui { return this.rootElements.FindIndex(element => element.Name == name); } + private Element GetMousedElement() { + foreach (var root in this.rootElements) { + var moused = root.Element.GetMousedElement(this.MousePos); + if (moused != null) + return moused; + } + return null; + } + } public struct RootElement { diff --git a/MLEM/Extensions/ColorExtensions.cs b/MLEM/Extensions/ColorExtensions.cs index 2f85987..a41553e 100644 --- a/MLEM/Extensions/ColorExtensions.cs +++ b/MLEM/Extensions/ColorExtensions.cs @@ -12,5 +12,9 @@ namespace MLEM.Extensions { return new Color((int) (value >> 16 & 0xFF), (int) (value >> 8 & 0xFF), (int) (value >> 0 & 0xFF), (int) (value >> 24 & 0xFF)); } + public static Color CopyAlpha(this Color color, Color other) { + return color * (other.A / 255F); + } + } } \ No newline at end of file diff --git a/MLEM/Input/InputHandler.cs b/MLEM/Input/InputHandler.cs index a996fae..359a95d 100644 --- a/MLEM/Input/InputHandler.cs +++ b/MLEM/Input/InputHandler.cs @@ -1,10 +1,15 @@ using System; +using System.Collections.Generic; +using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; +using MLEM.Misc; namespace MLEM.Input { public class InputHandler { + public static MouseButton[] MouseButtons = EnumHelper.GetValues().ToArray(); + public KeyboardState LastKeyboardState { get; private set; } public KeyboardState KeyboardState { get; private set; } private readonly bool handleKeyboard; diff --git a/MLEM/Misc/EnumHelper.cs b/MLEM/Misc/EnumHelper.cs new file mode 100644 index 0000000..6d7905b --- /dev/null +++ b/MLEM/Misc/EnumHelper.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MLEM.Misc { + public static class EnumHelper { + + public static IEnumerable GetValues() { + return Enum.GetValues(typeof(T)).Cast(); + } + + } +} \ No newline at end of file diff --git a/Tests/Content/Textures/Test.png b/Tests/Content/Textures/Test.png index 742c6dacd3aee6fcb85fc26dab913af7b21cd9ba..cec603e4ee1b8279aef29d47d4f150e804ece061 100644 GIT binary patch delta 949 zcmV;m14{hs2bKtsF@MlWL_t(|obB2@ZzDw<$MN5e7e9a;$%qt40a2xMiA3H&N;`4N4?l1qDSaIwRT?Cq*>J1*JF%gI6Tp_3jSE-i+7w+DXo{!baauWADuDc%}b5 zt|$8DhYPDMdU0R(-~S~^Iy;Sh`t6&1rQF$RtYN>aWS&Wq|9|evx%}&kLXtF|bT;xO z>F&$w^Zd&A{8ul&^S{&S4K|+fr>{Scwmm-{7dIsiM|~yJxzc>@gEQ=Reg4_0eA>&` z_sjcgWuD444oSLGDM`A%C^VjQb#^MRpzcuq_j|)gH>Sc+$~q`k*wiN)zkT(!}caO@FRrp80E+?-p^7<)2>}rHR#S zx(sHTSe;)Pef(ptPH(Wa*E1UIZ)1#g>j1~C?A^S-p7yeK?|9N#$$yHfaa48RT$UP+ z`bv}SJ-goPX#o4%XaFkkUsu9917NuBOvbjyH zBj{m8-+zAfg|&9MXtUNTiX#8HD@~HWUcbLAOQm_CUw{5>uLt~PHe;XPqS#uy5?HAU zstfq8G?y+(Wm)<%l4Oig6jl5BQTO@p2PA2lJ$)*{yYW-N2ZQ%imZfhD`J$9HRWRd@ zX-t!(MPAAnBWsNB`B4-pFIF4X@Esm*$IOC2Vt;FG6+mm%>z(Ms;rn}6`s(fvN!6%; z!)Dg!_8^s-YIF!$C)(lP17wS0S2_Gone|0o2yWd@N9^Y7(7l!3 ze;iDG>ld_Q)5_q2z*>ot>-~%Zhp)9MKw=a)27U?%T4{@o3Ss#!8gdQ5Ht=}(1`sk> zY<~lfhyODubg=3TYMN#Iv2&mrkA;We8WjKMAGX$(=?wqJq1A054qq$VKpc-&wt?&C z264FJ`Z>Q@@~8f}|6BEQg1feJ+YXDmX@xg|hcC|Z>gaXuUfwK;6w<00000NkvXXu0mjfTCwjo delta 905 zcmV;419tqD2@0R{0;z%4p?(^w#W10K6| z%a|qaAa5ht0_0U>=-@Y?t&4{M-qN`65FkoT)D|W3E(3ihN}{Y3A_1!Xeili0$CH5P zxqre%FW#OyZP1N-x_$FSl5~6&hw`gW*;2dXqgaF9fs*M=l7GT`-_F-xoaU0G(YUi# zFG<(G7OyAgCe(j;_9}c&r`uoqjlVztCEE1+v0YqMI2iVnOr}b+X$a1scM$52j}*$@ zf4p5hS8DUEd~!(AwX7uR;xyN2e4yha>8c{=s81{{0`6aD$)bS|jvu{}G#W3;ex5Fm z?sA)QqFkKjI)5DY@1MI1_2p-u4EstZ(+7WWp595So(=Ht#ga5jiX`xXPqRf3LU4Xs z1jmmr@rHNCTxKNfRPJdUQG9}Yl`0VTbCT?5($+=OQ zINeMZ!AujUlXIgV-eu}^`x{3+v%&7x=2*WE@cho+uluWI?@P~)#+{}5Z&4&i(f8$< z)nM3Dnrt4~~1XqmNtOHp2nAdZtdjbWOLn*!^5!A^Lqrp%a@87!fzI(ShHmP zm{>;8>wgu!{Ozf8Zn0@|&MAtb@cK}hr0{w5d26lGEZ6IIe{fE3G2@(5IRT_1sK%JB zWxm`ehyN@|#+bF_K>LIx;QRg~S!=@x&gNFm6akDorm{?u=9!f-M(#e~*4mVMi^OQ-is1v5bVg++Zf&mU@s|0uJ% z0jz_c`kUw*^;+N7*Pw}I14dR_NuW%Ta{Q$G6D+Hu8y256r~qU1+Evq1YbwggDb z0{b8=0kwA8U{ax8VTjhb2jCdEKSBc387z*0`=gN=Kw|Xoi^g_ojQ;&V0{qW4dJIJS zHDPiL#Qtbx6S#VB5bYJ0@AKUxpY_k}-}3haw;ks;T^9Ar3jYMQUp&i-tJj(T^Jbne z-R$+3JIleARGAp%JAqLYDH1b4+r`s)z7|+X;hvz&G8tn+GWa&-yM*QE%UA-F!2>;$ f&;tq>KpT7okDTzdvXrv800000NkvXXu0mjfxESDQ diff --git a/Tests/GameImpl.cs b/Tests/GameImpl.cs index 7429029..767463a 100644 --- a/Tests/GameImpl.cs +++ b/Tests/GameImpl.cs @@ -18,16 +18,26 @@ namespace Tests { private NinePatch testPatch; private UiSystem uiSystem; + public GameImpl() { + this.IsMouseVisible = true; + } + protected override void LoadContent() { base.LoadContent(); this.testTexture = LoadContent("Textures/Test"); this.testPatch = new NinePatch(new TextureRegion(this.testTexture, 0, 8, 24, 24), 8); // Ui system tests - this.uiSystem = new UiSystem(this.Window, this.GraphicsDevice, 5, new GenericSpriteFont(LoadContent("Fonts/TestFont"))); + this.uiSystem = new UiSystem(this.Window, this.GraphicsDevice, 5, new GenericSpriteFont(LoadContent("Fonts/TestFont")), this.InputHandler); 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)); + root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "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)); + root.AddChild(new Button(Anchor.AutoCenter, new Vector2(1, 15), new NinePatch(new TextureRegion(this.testTexture, 24, 8, 16, 16), 4), "Test Button") { + OnClicked = (element, pos, button) => { + if (button == MouseButton.Left) + Console.WriteLine("Clicked"); + } + }); this.uiSystem.Add("Test", root); }