From 3c4567e4a1fc7535837926e9f73f6dca1024edb7 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Sun, 30 Jan 2022 01:13:59 +0100 Subject: [PATCH] Use a scissor rectangle for panels in favor of a render target, and marked UiSystem.DrawEarly and Element.DrawEarly as obsolete --- CHANGELOG.md | 2 ++ MLEM.Startup/MlemGame.cs | 1 - MLEM.Ui/Elements/Element.cs | 2 ++ MLEM.Ui/Elements/Panel.cs | 72 ++++++++++++------------------------- MLEM.Ui/UiSystem.cs | 1 + 5 files changed, 28 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb6ffd1..390a94c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,12 +35,14 @@ Improvements - Avoid unnecessary panel updates by using an Epsilon comparison when scrolling children - Allow setting a default text alignment for paragraphs in UiStyle - Made custom values of Element.Style persist when a new ui style is set +- Use a scissor rectangle for panels in favor of a render target Fixes - Fixed paragraph links having incorrect hover locations when using special text alignments Removals - Marked StyleProp equality members as obsolete +- Marked UiSystem.DrawEarly and Element.DrawEarly as obsolete ### MLEM.Extended Improvements diff --git a/MLEM.Startup/MlemGame.cs b/MLEM.Startup/MlemGame.cs index ed83878..0989adb 100644 --- a/MLEM.Startup/MlemGame.cs +++ b/MLEM.Startup/MlemGame.cs @@ -114,7 +114,6 @@ namespace MLEM.Startup { this.PreDraw?.Invoke(this, gameTime); CoroutineHandler.RaiseEvent(CoroutineEvents.PreDraw); - this.UiSystem.DrawEarly(gameTime, this.SpriteBatch); this.DoDraw(gameTime); this.UiSystem.Draw(gameTime, this.SpriteBatch); diff --git a/MLEM.Ui/Elements/Element.cs b/MLEM.Ui/Elements/Element.cs index 221084c..0699456 100644 --- a/MLEM.Ui/Elements/Element.cs +++ b/MLEM.Ui/Elements/Element.cs @@ -924,6 +924,7 @@ namespace MLEM.Ui.Elements { public void DrawTransformed(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) { var customDraw = this.BeginImpl != null || this.Transform != Matrix.Identity; var mat = this.Transform * matrix; + // TODO ending and beginning again when the matrix changes isn't ideal (https://github.com/MonoGame/MonoGame/issues/3156) if (customDraw) { // end the usual draw so that we can begin our own batch.End(); @@ -984,6 +985,7 @@ namespace MLEM.Ui.Elements { /// The effect that is used for drawing /// The depth stencil state that is used for drawing /// The transformation matrix that is used for drawing + [Obsolete("DrawEarly has been deprecated. There is no replacement, and all drawing code should be placed in Draw.")] public virtual void DrawEarly(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) { foreach (var child in this.GetRelevantChildren()) { if (!child.IsHidden) diff --git a/MLEM.Ui/Elements/Panel.cs b/MLEM.Ui/Elements/Panel.cs index c8914b4..7ab165b 100644 --- a/MLEM.Ui/Elements/Panel.cs +++ b/MLEM.Ui/Elements/Panel.cs @@ -13,10 +13,15 @@ namespace MLEM.Ui.Elements { /// A panel element to be used inside of a . /// The panel is a complex element that displays a box as a background to all of its child elements. /// Additionally, a panel can be set to on construction, which causes all elements that don't fit into the panel to be hidden until scrolled to using a . - /// As this behavior is accomplished using a , scrolling panels need to have their methods called using . /// public class Panel : Element { + private static readonly RasterizerState ScissorRasterizer = new RasterizerState { + // use the default cull mode from SpriteBatch, but with scissor test enabled + CullMode = CullMode.CullCounterClockwiseFace, + ScissorTestEnable = true + }; + /// /// The scroll bar that this panel contains. /// This is only nonnull if is true. @@ -51,7 +56,6 @@ namespace MLEM.Ui.Elements { private readonly List relevantChildren = new List(); private readonly bool scrollOverflow; - private RenderTarget2D renderTarget; private bool relevantChildrenDirty; private float scrollBarChildOffset; @@ -170,44 +174,34 @@ namespace MLEM.Ui.Elements { public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) { if (this.Texture.HasValue()) batch.Draw(this.Texture, this.DisplayArea, this.DrawColor.OrDefault(Color.White) * alpha, this.Scale); - // if we handle overflow, draw using the render target in DrawUnbound - if (!this.scrollOverflow || this.renderTarget == null) { - base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix); - } else { - // 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(), Color.White); - } - } + // if we handle overflow, draw using a scissor rectangle + if (this.scrollOverflow) { + this.UpdateAreaIfDirty(); - /// - public override void DrawEarly(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, Effect effect, Matrix matrix) { - this.UpdateAreaIfDirty(); - if (this.scrollOverflow && this.renderTarget != null) { - // draw children onto the render target - using (batch.GraphicsDevice.WithRenderTarget(this.renderTarget)) { - batch.GraphicsDevice.Clear(Color.Transparent); - // offset children by the render target's location - var area = this.GetRenderTargetArea(); - var trans = Matrix.CreateTranslation(-area.X, -area.Y, 0); - // do the usual draw, but within the render target - batch.Begin(SpriteSortMode.Deferred, blendState, samplerState, depthStencilState, null, effect, trans); - base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, trans); - batch.End(); - } + batch.End(); + batch.GraphicsDevice.ScissorRectangle = (Rectangle) this.GetInnerArea(); + + // do the usual draw, but with the scissor rectangle applied + batch.Begin(SpriteSortMode.Deferred, blendState, samplerState, depthStencilState, ScissorRasterizer, effect, matrix); + base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix); + batch.End(); + + batch.Begin(SpriteSortMode.Deferred, blendState, samplerState, depthStencilState, null, effect, matrix); + } else { + base.Draw(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix); } - base.DrawEarly(time, batch, alpha, blendState, samplerState, depthStencilState, effect, matrix); } /// public override Element GetElementUnderPos(Vector2 position) { // if overflow is handled, don't propagate mouse checks to hidden children var transformed = this.TransformInverse(position); - if (this.scrollOverflow && !this.GetRenderTargetArea().Contains(transformed)) + if (this.scrollOverflow && !this.GetInnerArea().Contains(transformed)) return !this.IsHidden && this.CanBeMoused && this.DisplayArea.Contains(transformed) ? this : null; return base.GetElementUnderPos(position); } - private RectangleF GetRenderTargetArea() { + private RectangleF GetInnerArea() { var area = this.ChildPaddedArea; area.X = this.DisplayArea.X; area.Width = this.DisplayArea.Width; @@ -258,26 +252,6 @@ namespace MLEM.Ui.Elements { // the scroller height has the same relation to the scroll bar height as the visible area has to the total height of the panel's content var scrollerHeight = Math.Min(this.ChildPaddedArea.Height / childrenHeight / this.Scale, 1) * this.ScrollBar.Area.Height; this.ScrollBar.ScrollerSize = new Vector2(this.ScrollerSize.Value.X, Math.Max(this.ScrollerSize.Value.Y, scrollerHeight)); - - // update the render target - var targetArea = (Rectangle) this.GetRenderTargetArea(); - if (targetArea.Width <= 0 || targetArea.Height <= 0) - return; - if (this.renderTarget == null || targetArea.Width != this.renderTarget.Width || targetArea.Height != this.renderTarget.Height) { - if (this.renderTarget != null) - this.renderTarget.Dispose(); - this.renderTarget = targetArea.IsEmpty ? null : new RenderTarget2D(this.System.Game.GraphicsDevice, targetArea.Width, targetArea.Height); - this.relevantChildrenDirty = true; - } - } - - /// - public override void Dispose() { - if (this.renderTarget != null) { - this.renderTarget.Dispose(); - this.renderTarget = null; - } - base.Dispose(); } private void SetScrollBarStyle() { @@ -291,7 +265,7 @@ namespace MLEM.Ui.Elements { private void ForceUpdateRelevantChildren() { this.relevantChildrenDirty = false; this.relevantChildren.Clear(); - var visible = this.GetRenderTargetArea(); + var visible = this.GetInnerArea(); foreach (var child in this.SortedChildren) { if (child.Area.Intersects(visible)) { this.relevantChildren.Add(child); diff --git a/MLEM.Ui/UiSystem.cs b/MLEM.Ui/UiSystem.cs index 5e4f72f..77f9f46 100644 --- a/MLEM.Ui/UiSystem.cs +++ b/MLEM.Ui/UiSystem.cs @@ -264,6 +264,7 @@ namespace MLEM.Ui { /// /// The game's time /// The sprite batch to use for drawing + [Obsolete("DrawEarly has been deprecated. There is no replacement, so only Draw has to be called.")] public void DrawEarly(GameTime time, SpriteBatch batch) { this.Metrics.ResetDraws(); this.Stopwatch.Restart();