Ellpeck / MLEM

Libraries for MonoGame and FNA that provide abstractions, quality of life improvements and additional features like a ui system and easy input handling.
https://mlem.ellpeck.de/
MIT License
84 stars 6 forks source link

Stack overflow related to panel scrolling and scrollbar auto-hiding #27

Closed all-iver closed 1 month ago

all-iver commented 1 month ago

MLEM can crash with a stack overflow due to layout recursion when children are added to a scrollable panel, just enough to make it show the scrollbar. Adding a smaller or larger amount of children seems to be okay. Code that demonstrates the crash is attached.

using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using MLEM.Ui;
using MLEM.Ui.Elements;
using MLEM.Ui.Style;

namespace mlemcrash;

public class Game1 : Game
{
    private GraphicsDeviceManager _graphics;
    private SpriteBatch _spriteBatch;
    UiSystem _ui { get; set; }

    public Game1()
    {
        _graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        IsMouseVisible = true;
    }

    protected override void Initialize()
    {
        // TODO: Add your initialization logic here
        Window.AllowUserResizing = true;

        var displayMode = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode;
        _graphics.PreferredBackBufferWidth = MathHelper.Min(displayMode.Width, 1800);
        _graphics.PreferredBackBufferHeight = MathHelper.Min(displayMode.Height, 1000);
        _graphics.IsFullScreen = false;
        _graphics.SynchronizeWithVerticalRetrace = false;
        _graphics.ApplyChanges();

        base.Initialize();
    }

    protected override void LoadContent()
    {
        _spriteBatch = new SpriteBatch(GraphicsDevice);

        // TODO: use this.Content to load your game content here
        _ui = new UiSystem(
            this,
            new UntexturedStyle(_spriteBatch)
            {
                SelectionIndicator = null,
                PanelScrollerSize = new Vector2(40, 40),
                PanelStepPerScroll = 100,
                ScrollBarSmoothScrolling = true,
            }
        );
        // _ui.AutoScaleWithScreen = true;
        // _ui.AutoScaleReferenceSize = new Point(1920, 1080);

        MakeUI();
    }

    void MakeUI() {
        var group = new SquishingGroup(Anchor.TopLeft, Vector2.One);
        var root = _ui.Add("UI", group);

        var centerGroup = new ScissorGroup(Anchor.TopCenter, Vector2.One);
        var centerPanel = new Panel(Anchor.TopRight, Vector2.One);
        centerPanel.DrawColor = Color.Red;
        centerPanel.Padding = new MLEM.Maths.Padding(5);
        centerGroup.AddChild(centerPanel);
        group.AddChild(centerGroup);

        var leftColumn = new Panel(Anchor.TopLeft, new Vector2(500, 1), scrollOverflow: true);
        group.AddChild(leftColumn);
        var numChildren = 22; // this crashes
        // numChildren = 15; // this does not crash
        // numChildren = 50; // also does not crash
        for (int i = 0; i < numChildren; i++) {
            var c = new Panel(Anchor.AutoLeft, new Vector2(1, 30), false);
            c.DrawColor = Color.Green;
            c.Padding = new MLEM.Maths.Padding(5);
            leftColumn.AddChild(c);
        }

        var bottomPane = new Panel(Anchor.BottomCenter, new Vector2(1, 500));
        group.AddChild(bottomPane);
    }

    protected override void Update(GameTime gameTime)
    {
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
            Exit();

        // TODO: Add your update logic here
        _ui.Update(gameTime);

        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);

        // TODO: Add your drawing code here
        _ui.Draw(gameTime, _spriteBatch);

        base.Draw(gameTime);
    }
}