2019-08-12 19:44:16 +02:00
|
|
|
using System;
|
|
|
|
using System.Diagnostics;
|
2019-08-28 18:58:05 +02:00
|
|
|
using System.Linq;
|
2019-08-09 19:28:48 +02:00
|
|
|
using Microsoft.Xna.Framework;
|
|
|
|
using Microsoft.Xna.Framework.Graphics;
|
2019-08-12 19:44:16 +02:00
|
|
|
using MLEM.Extensions;
|
2019-08-09 19:28:48 +02:00
|
|
|
using MLEM.Textures;
|
2019-08-10 21:37:10 +02:00
|
|
|
using MLEM.Ui.Style;
|
2019-08-09 19:28:48 +02:00
|
|
|
|
|
|
|
namespace MLEM.Ui.Elements {
|
|
|
|
public class Panel : Element {
|
|
|
|
|
2019-08-10 21:37:10 +02:00
|
|
|
public NinePatch Texture;
|
2019-08-12 19:44:16 +02:00
|
|
|
public readonly ScrollBar ScrollBar;
|
|
|
|
private readonly bool scrollOverflow;
|
|
|
|
private RenderTarget2D renderTarget;
|
2019-08-09 19:28:48 +02:00
|
|
|
|
2019-08-13 23:54:29 +02:00
|
|
|
public Panel(Anchor anchor, Vector2 size, Vector2 positionOffset, bool setHeightBasedOnChildren = false, bool scrollOverflow = false, Point? scrollerSize = null) : base(anchor, size) {
|
2019-08-09 22:04:26 +02:00
|
|
|
this.PositionOffset = positionOffset;
|
2019-08-11 18:50:39 +02:00
|
|
|
this.SetHeightBasedOnChildren = setHeightBasedOnChildren;
|
2019-08-12 19:44:16 +02:00
|
|
|
this.scrollOverflow = scrollOverflow;
|
2019-08-09 19:28:48 +02:00
|
|
|
this.ChildPadding = new Point(5);
|
2019-08-28 18:27:17 +02:00
|
|
|
this.CanBeSelected = false;
|
2019-08-12 19:44:16 +02:00
|
|
|
|
|
|
|
if (scrollOverflow) {
|
|
|
|
var scrollSize = scrollerSize ?? Point.Zero;
|
|
|
|
this.ScrollBar = new ScrollBar(Anchor.TopRight, new Vector2(scrollSize.X, 1), scrollSize.Y, 0) {
|
|
|
|
StepPerScroll = 10,
|
2019-08-24 22:27:47 +02:00
|
|
|
OnValueChanged = (element, value) => this.ForceChildrenScroll(),
|
2019-09-01 19:53:52 +02:00
|
|
|
CanAutoAnchorsAttach = false,
|
|
|
|
AutoHideWhenEmpty = true
|
2019-08-12 19:44:16 +02:00
|
|
|
};
|
|
|
|
this.AddChild(this.ScrollBar);
|
|
|
|
|
|
|
|
// modify the padding so that the scroll bar isn't over top of something else
|
2019-08-13 23:54:29 +02:00
|
|
|
this.ScrollBar.PositionOffset -= new Vector2(scrollSize.X + 1, 0);
|
2019-08-12 19:44:16 +02:00
|
|
|
this.ChildPadding += new Point(scrollSize.X, 0);
|
2019-08-28 18:58:05 +02:00
|
|
|
|
|
|
|
// handle automatic element selection, the scroller needs to scroll to the right location
|
|
|
|
this.OnSelectedElementChanged += (element, otherElement) => {
|
|
|
|
if (this.Controls.SelectedLastElementWithMouse)
|
|
|
|
return;
|
|
|
|
if (otherElement == null || !otherElement.GetParentTree().Contains(this))
|
|
|
|
return;
|
|
|
|
this.ScrollBar.CurrentValue = (otherElement.Area.Bottom - this.Children[1].Area.Top - this.Area.Height / 2) / this.Scale;
|
|
|
|
};
|
2019-08-12 19:44:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void ForceUpdateArea() {
|
|
|
|
if (this.scrollOverflow) {
|
|
|
|
// sanity check
|
|
|
|
if (this.SetHeightBasedOnChildren)
|
|
|
|
throw new NotSupportedException("A panel can't both set height based on children and scroll overflow");
|
|
|
|
foreach (var child in this.Children) {
|
|
|
|
if (child != this.ScrollBar && child.Anchor < Anchor.AutoLeft)
|
|
|
|
throw new NotSupportedException($"A panel that handles overflow can't contain non-automatic anchors ({child})");
|
|
|
|
if (child is Panel panel && panel.scrollOverflow)
|
|
|
|
throw new NotSupportedException($"A panel that scrolls overflow cannot contain another panel that scrolls overflow ({child})");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
base.ForceUpdateArea();
|
2019-08-24 22:27:47 +02:00
|
|
|
this.ForceChildrenScroll();
|
2019-08-12 19:44:16 +02:00
|
|
|
|
|
|
|
if (this.scrollOverflow) {
|
2019-08-23 22:23:10 +02:00
|
|
|
// if there is only one child, then we have just the scroll bar
|
|
|
|
if (this.Children.Count == 1)
|
2019-08-12 19:44:16 +02:00
|
|
|
return;
|
2019-08-23 22:23:10 +02:00
|
|
|
// the "real" first child is the scroll bar, which we want to ignore
|
|
|
|
var firstChild = this.Children[1];
|
2019-08-24 15:12:11 +02:00
|
|
|
var lowestChild = this.GetLowestChild(false, true);
|
2019-08-12 19:44:16 +02:00
|
|
|
// the max value of the scrollbar is the amount of non-scaled pixels taken up by overflowing components
|
2019-08-24 20:33:55 +02:00
|
|
|
var childrenHeight = lowestChild.Area.Bottom - firstChild.Area.Top;
|
2019-08-24 19:21:06 +02:00
|
|
|
this.ScrollBar.MaxValue = ((childrenHeight - this.Area.Height) / this.Scale + this.ChildPadding.Y * 2).Floor();
|
2019-08-12 19:44:16 +02:00
|
|
|
|
|
|
|
// update the render target
|
|
|
|
var targetArea = this.GetRenderTargetArea();
|
|
|
|
if (this.renderTarget == null || targetArea.Width != this.renderTarget.Width || targetArea.Height != this.renderTarget.Height) {
|
|
|
|
if (this.renderTarget != null)
|
|
|
|
this.renderTarget.Dispose();
|
|
|
|
this.renderTarget = new RenderTarget2D(this.System.GraphicsDevice, targetArea.Width, targetArea.Height);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-24 22:27:47 +02:00
|
|
|
private void ForceChildrenScroll() {
|
|
|
|
if (!this.scrollOverflow)
|
|
|
|
return;
|
|
|
|
var offset = -this.ScrollBar.CurrentValue.Floor();
|
|
|
|
foreach (var child in this.GetChildren(c => c != this.ScrollBar, true))
|
|
|
|
child.ScrollOffset = new Point(0, offset);
|
|
|
|
}
|
|
|
|
|
2019-08-12 19:44:16 +02:00
|
|
|
public override void Draw(GameTime time, SpriteBatch batch, float alpha, Point offset) {
|
|
|
|
batch.Draw(this.Texture, this.DisplayArea.OffsetCopy(offset), Color.White * alpha, this.Scale);
|
|
|
|
// if we handle overflow, draw using the render target in DrawUnbound
|
|
|
|
if (!this.scrollOverflow) {
|
|
|
|
base.Draw(time, batch, alpha, offset);
|
2019-08-23 22:23:10 +02:00
|
|
|
} else if (this.renderTarget != null) {
|
2019-08-12 19:44:16 +02:00
|
|
|
// draw the actual render target (don't apply the alpha here because it's already drawn onto with alpha)
|
|
|
|
batch.Draw(this.renderTarget, this.GetRenderTargetArea().OffsetCopy(offset), Color.White);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-02 19:55:26 +02:00
|
|
|
public override void DrawEarly(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) {
|
2019-08-23 22:23:10 +02:00
|
|
|
if (this.scrollOverflow && this.renderTarget != null) {
|
2019-08-12 19:44:16 +02:00
|
|
|
// draw children onto the render target
|
|
|
|
batch.GraphicsDevice.SetRenderTarget(this.renderTarget);
|
|
|
|
batch.GraphicsDevice.Clear(Color.Transparent);
|
2019-09-02 19:55:26 +02:00
|
|
|
// we don't apply the matrix here because it's already applied when drawing the render target
|
2019-08-12 19:44:16 +02:00
|
|
|
batch.Begin(SpriteSortMode.Deferred, blendState, samplerState);
|
|
|
|
// offset children by the render target's location
|
|
|
|
var area = this.GetRenderTargetArea();
|
|
|
|
base.Draw(time, batch, alpha, new Point(-area.X, -area.Y));
|
|
|
|
batch.End();
|
|
|
|
batch.GraphicsDevice.SetRenderTarget(null);
|
|
|
|
}
|
2019-09-02 19:55:26 +02:00
|
|
|
base.DrawEarly(time, batch, alpha, blendState, samplerState, matrix);
|
2019-08-09 19:28:48 +02:00
|
|
|
}
|
|
|
|
|
2019-08-30 18:15:50 +02:00
|
|
|
public override Element GetElementUnderPos(Point position) {
|
2019-08-20 21:41:22 +02:00
|
|
|
// if overflow is handled, don't propagate mouse checks to hidden children
|
2019-08-30 18:15:50 +02:00
|
|
|
if (this.scrollOverflow && !this.GetRenderTargetArea().Contains(position))
|
2019-08-20 21:41:22 +02:00
|
|
|
return null;
|
2019-08-30 18:15:50 +02:00
|
|
|
return base.GetElementUnderPos(position);
|
2019-08-20 21:41:22 +02:00
|
|
|
}
|
|
|
|
|
2019-08-12 19:44:16 +02:00
|
|
|
private Rectangle GetRenderTargetArea() {
|
|
|
|
var area = this.ChildPaddedArea;
|
|
|
|
area.X = this.DisplayArea.X;
|
|
|
|
area.Width = this.DisplayArea.Width;
|
|
|
|
return area;
|
2019-08-09 19:28:48 +02:00
|
|
|
}
|
|
|
|
|
2019-08-10 21:37:10 +02:00
|
|
|
protected override void InitStyle(UiStyle style) {
|
|
|
|
base.InitStyle(style);
|
|
|
|
this.Texture = style.PanelTexture;
|
|
|
|
}
|
|
|
|
|
2019-08-09 19:28:48 +02:00
|
|
|
}
|
|
|
|
}
|