mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-12-24 01:09:23 +01:00
basic element system
This commit is contained in:
parent
6d54eb4da3
commit
a67abd4661
4 changed files with 317 additions and 7 deletions
210
MLEM.Ui/Elements/Element.cs
Normal file
210
MLEM.Ui/Elements/Element.cs
Normal file
|
@ -0,0 +1,210 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Extensions;
|
||||
|
||||
namespace MLEM.Ui.Elements {
|
||||
public class Element {
|
||||
|
||||
private Anchor anchor;
|
||||
private Point size;
|
||||
private Point offset;
|
||||
private Point padding;
|
||||
public Anchor Anchor {
|
||||
get => this.anchor;
|
||||
set {
|
||||
this.anchor = value;
|
||||
this.SetDirty();
|
||||
}
|
||||
}
|
||||
public Point Size {
|
||||
get => this.size;
|
||||
set {
|
||||
this.size = value;
|
||||
this.SetDirty();
|
||||
}
|
||||
}
|
||||
public Point PositionOffset {
|
||||
get => this.offset;
|
||||
set {
|
||||
this.offset = value;
|
||||
this.SetDirty();
|
||||
}
|
||||
}
|
||||
public Point Padding {
|
||||
get => this.padding;
|
||||
set {
|
||||
this.padding = value;
|
||||
this.SetDirty();
|
||||
}
|
||||
}
|
||||
|
||||
public UiSystem System;
|
||||
public Element Parent { get; private set; }
|
||||
private readonly List<Element> children = new List<Element>();
|
||||
|
||||
private Rectangle area;
|
||||
public Rectangle Area {
|
||||
get {
|
||||
this.UpdateAreaIfDirty();
|
||||
return this.area;
|
||||
}
|
||||
}
|
||||
public Rectangle DisplayArea {
|
||||
get {
|
||||
var padded = this.Area;
|
||||
padded.Location += this.Padding;
|
||||
padded.Width -= this.Padding.X * 2;
|
||||
padded.Height -= this.Padding.Y * 2;
|
||||
return padded;
|
||||
}
|
||||
|
||||
}
|
||||
protected bool AreaDirty;
|
||||
|
||||
public Element(Anchor anchor, Point size, Point positionOffset) {
|
||||
this.anchor = anchor;
|
||||
this.size = size;
|
||||
this.offset = positionOffset;
|
||||
}
|
||||
|
||||
public void AddChild(Element element) {
|
||||
this.children.Add(element);
|
||||
element.Parent = this;
|
||||
this.SetDirty();
|
||||
}
|
||||
|
||||
public void RemoveChild(Element element) {
|
||||
this.children.Remove(element);
|
||||
element.Parent = null;
|
||||
this.SetDirty();
|
||||
}
|
||||
|
||||
public void SetDirty() {
|
||||
this.AreaDirty = true;
|
||||
}
|
||||
|
||||
public void UpdateAreaIfDirty() {
|
||||
if (this.AreaDirty)
|
||||
this.ForceUpdateArea();
|
||||
}
|
||||
|
||||
public void ForceUpdateArea() {
|
||||
this.AreaDirty = false;
|
||||
|
||||
var parentArea = this.Parent != null ? this.Parent.area : this.System.ScaledViewport;
|
||||
var parentCenterX = parentArea.X + parentArea.Width / 2;
|
||||
var parentCenterY = parentArea.Y + parentArea.Height / 2;
|
||||
|
||||
var actualSize = this.CalcActualSize();
|
||||
var pos = new Point();
|
||||
|
||||
switch (this.anchor) {
|
||||
case Anchor.TopLeft:
|
||||
case Anchor.AutoLeft:
|
||||
case Anchor.AutoInline:
|
||||
case Anchor.AutoInlineIgnoreOverflow:
|
||||
pos.X = parentArea.X + this.offset.X;
|
||||
pos.Y = parentArea.Y + this.offset.Y;
|
||||
break;
|
||||
case Anchor.TopCenter:
|
||||
case Anchor.AutoCenter:
|
||||
pos.X = parentCenterX - actualSize.X / 2 + this.offset.X;
|
||||
pos.Y = parentArea.Y + this.offset.Y;
|
||||
break;
|
||||
case Anchor.TopRight:
|
||||
case Anchor.AutoRight:
|
||||
pos.X = parentArea.Right - actualSize.X - this.offset.X;
|
||||
pos.Y = parentArea.Y + this.offset.Y;
|
||||
break;
|
||||
case Anchor.CenterLeft:
|
||||
pos.X = parentArea.X + this.offset.X;
|
||||
pos.Y = parentCenterY - actualSize.Y / 2 + this.offset.Y;
|
||||
break;
|
||||
case Anchor.Center:
|
||||
pos.X = parentCenterX - actualSize.X / 2 + this.offset.X;
|
||||
pos.Y = parentCenterY - actualSize.Y / 2 + this.offset.Y;
|
||||
break;
|
||||
case Anchor.CenterRight:
|
||||
pos.X = parentArea.Right - actualSize.X - this.offset.X;
|
||||
pos.Y = parentCenterY - actualSize.Y / 2 + this.offset.Y;
|
||||
break;
|
||||
case Anchor.BottomLeft:
|
||||
pos.X = parentArea.X + this.offset.X;
|
||||
pos.Y = parentArea.Bottom - actualSize.Y - this.offset.Y;
|
||||
break;
|
||||
case Anchor.BottomCenter:
|
||||
pos.X = parentCenterX - actualSize.X / 2 + this.offset.X;
|
||||
pos.Y = parentArea.Bottom - actualSize.Y - this.offset.Y;
|
||||
break;
|
||||
case Anchor.BottomRight:
|
||||
pos.X = parentArea.Right - actualSize.X - this.offset.X;
|
||||
pos.Y = parentArea.Bottom - actualSize.Y - this.offset.Y;
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.Anchor >= Anchor.AutoLeft) {
|
||||
var previousChild = this.GetPreviousChild();
|
||||
if (previousChild != null) {
|
||||
var prevArea = previousChild.Area;
|
||||
switch (this.Anchor) {
|
||||
case Anchor.AutoLeft:
|
||||
case Anchor.AutoCenter:
|
||||
case Anchor.AutoRight:
|
||||
pos.Y = prevArea.Bottom + this.PositionOffset.Y;
|
||||
break;
|
||||
case Anchor.AutoInline:
|
||||
var newX = prevArea.Right + this.PositionOffset.X;
|
||||
if (newX + actualSize.X <= parentArea.Right) {
|
||||
pos.X = newX;
|
||||
pos.Y = prevArea.Y;
|
||||
} else {
|
||||
pos.Y = prevArea.Bottom + this.PositionOffset.Y;
|
||||
}
|
||||
break;
|
||||
case Anchor.AutoInlineIgnoreOverflow:
|
||||
pos.X = prevArea.Right + this.PositionOffset.X;
|
||||
pos.Y = prevArea.Y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.area = new Rectangle(pos, actualSize);
|
||||
|
||||
foreach (var child in this.children)
|
||||
child.ForceUpdateArea();
|
||||
}
|
||||
|
||||
private Point CalcActualSize() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
public Element GetPreviousChild() {
|
||||
if (this.Parent == null)
|
||||
return null;
|
||||
|
||||
Element lastChild = null;
|
||||
foreach (var child in this.Parent.children) {
|
||||
if (child == this)
|
||||
break;
|
||||
lastChild = child;
|
||||
}
|
||||
return lastChild;
|
||||
}
|
||||
|
||||
public 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);
|
||||
|
||||
foreach (var child in this.children)
|
||||
child.Draw(time, batch);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
80
MLEM.Ui/UiSystem.cs
Normal file
80
MLEM.Ui/UiSystem.cs
Normal file
|
@ -0,0 +1,80 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using MLEM.Extensions;
|
||||
using MLEM.Ui.Elements;
|
||||
|
||||
namespace MLEM.Ui {
|
||||
public class UiSystem {
|
||||
|
||||
private readonly GraphicsDevice graphicsDevice;
|
||||
private readonly List<RootElement> rootElements = new List<RootElement>();
|
||||
|
||||
public float GlobalScale;
|
||||
public Rectangle ScaledViewport {
|
||||
get {
|
||||
var bounds = this.graphicsDevice.Viewport.Bounds;
|
||||
return new Rectangle(bounds.X, bounds.Y, (bounds.Width / this.GlobalScale).Floor(), (bounds.Height / this.GlobalScale).Floor());
|
||||
}
|
||||
}
|
||||
|
||||
public UiSystem(GameWindow window, GraphicsDevice device, float scale) {
|
||||
this.graphicsDevice = device;
|
||||
this.GlobalScale = scale;
|
||||
window.ClientSizeChanged += (sender, args) => {
|
||||
foreach (var root in this.rootElements)
|
||||
root.Element.ForceUpdateArea();
|
||||
};
|
||||
}
|
||||
|
||||
public void Update(GameTime time) {
|
||||
foreach (var root in this.rootElements)
|
||||
root.Element.Update(time);
|
||||
}
|
||||
|
||||
public void Draw(GameTime time, SpriteBatch batch, BlendState blendState = null, SamplerState samplerState = null) {
|
||||
batch.Begin(SpriteSortMode.Deferred, blendState, samplerState, transformMatrix: Matrix.CreateScale(this.GlobalScale));
|
||||
foreach (var root in this.rootElements)
|
||||
root.Element.Draw(time, batch);
|
||||
batch.End();
|
||||
}
|
||||
|
||||
public void Add(string name, Element root) {
|
||||
if (this.IndexOf(name) >= 0)
|
||||
throw new ArgumentException($"There is already a root element with name {name}");
|
||||
|
||||
this.rootElements.Add(new RootElement(name, root));
|
||||
root.System = this;
|
||||
}
|
||||
|
||||
public void Remove(string name) {
|
||||
var index = this.IndexOf(name);
|
||||
if (index < 0)
|
||||
return;
|
||||
this.rootElements.RemoveAt(index);
|
||||
}
|
||||
|
||||
public Element Get(string name) {
|
||||
var index = this.IndexOf(name);
|
||||
return index < 0 ? null : this.rootElements[index].Element;
|
||||
}
|
||||
|
||||
private int IndexOf(string name) {
|
||||
return this.rootElements.FindIndex(element => element.Name == name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct RootElement {
|
||||
|
||||
public readonly string Name;
|
||||
public readonly Element Element;
|
||||
|
||||
public RootElement(string name, Element element) {
|
||||
this.Name = name;
|
||||
this.Element = element;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@ using MLEM.Extended.Extensions;
|
|||
using MLEM.Input;
|
||||
using MLEM.Startup;
|
||||
using MLEM.Textures;
|
||||
using MLEM.Ui;
|
||||
using MLEM.Ui.Elements;
|
||||
using MonoGame.Extended.TextureAtlases;
|
||||
|
||||
namespace Tests {
|
||||
|
@ -16,12 +18,25 @@ namespace Tests {
|
|||
private NinePatch testPatch;
|
||||
private NinePatchRegion2D extendedPatch;
|
||||
|
||||
private UiSystem uiSystem;
|
||||
private Element testChild;
|
||||
|
||||
protected override void LoadContent() {
|
||||
base.LoadContent();
|
||||
this.testTexture = LoadContent<Texture2D>("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);
|
||||
|
||||
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)
|
||||
});
|
||||
this.uiSystem.Add("Test", root);
|
||||
}
|
||||
|
||||
protected override void Update(GameTime gameTime) {
|
||||
|
@ -34,6 +49,8 @@ namespace Tests {
|
|||
Console.WriteLine("Left was pressed");
|
||||
if (Input.IsGamepadButtonPressed(0, Buttons.A))
|
||||
Console.WriteLine("Gamepad A was pressed");
|
||||
|
||||
this.uiSystem.Update(gameTime);
|
||||
}
|
||||
|
||||
protected override void Draw(GameTime gameTime) {
|
||||
|
@ -49,6 +66,8 @@ namespace Tests {
|
|||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,19 +6,20 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MLEM.Extended\MLEM.Extended.csproj"/>
|
||||
<ProjectReference Include="..\MLEM.Startup\MLEM.Startup.csproj"/>
|
||||
<ProjectReference Include="..\MLEM\MLEM.csproj"/>
|
||||
<ProjectReference Include="..\MLEM.Extended\MLEM.Extended.csproj" />
|
||||
<ProjectReference Include="..\MLEM.Startup\MLEM.Startup.csproj" />
|
||||
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.csproj" />
|
||||
<ProjectReference Include="..\MLEM\MLEM.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MonoGame.Content.Builder" Version="3.7.0.9"/>
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL.Core" Version="3.7.0.7"/>
|
||||
<PackageReference Include="MonoGame.Content.Builder" Version="3.7.0.9" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL.Core" Version="3.7.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MonoGameContentReference Include="Content\Content.mgcb"/>
|
||||
<Content Include="Content\*\**"/>
|
||||
<MonoGameContentReference Include="Content\Content.mgcb" />
|
||||
<Content Include="Content\*\**" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
Loading…
Reference in a new issue