Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ppy
GitHub Repository: ppy/osu
Path: blob/master/osu.Game/Overlays/MarqueeContainer.cs
2264 views
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osuTK;

namespace osu.Game.Overlays
{
    public partial class MarqueeContainer : CompositeDrawable
    {
        /// <summary>
        /// Whether the marquee should be allowed to scroll the content if it overflows.
        /// Note that upon changing the value of this, any existing scrolls will be terminated instantly.
        /// </summary>
        public bool AllowScrolling
        {
            get => allowScrolling;
            set
            {
                allowScrolling = value;
                ScheduleAfterChildren(updateScrolling);
            }
        }

        private bool allowScrolling = true;

        /// <summary>
        /// Time in milliseconds before scrolling begins.
        /// </summary>
        public double InitialMoveDelay { get; set; } = 1000;

        /// <summary>
        /// The <see cref="Anchor"/> to anchor the content to if it does not overflow.
        /// </summary>
        public Anchor NonOverflowingContentAnchor { get; init; } = Anchor.TopLeft;

        public Func<Drawable>? CreateContent
        {
            set
            {
                createContent = value;
                if (IsLoaded)
                    updateContent();
            }
        }

        private Func<Drawable>? createContent;

        private const float pixels_per_second = 50;
        private const float padding = 15;

        private Drawable mainContent = null!;
        private Drawable fillerContent = null!;
        private FillFlowContainer flow = null!;

        public MarqueeContainer()
        {
            RelativeSizeAxes = Axes.X;
            AutoSizeAxes = Axes.Y;
        }

        [BackgroundDependencyLoader]
        private void load()
        {
            InternalChild = flow = new FillFlowContainer
            {
                AutoSizeAxes = Axes.Both,
                Direction = FillDirection.Horizontal,
                Anchor = NonOverflowingContentAnchor,
                Origin = NonOverflowingContentAnchor,
                Spacing = new Vector2(padding),
                Padding = new MarginPadding { Horizontal = padding },
            };
        }

        protected override void LoadComplete()
        {
            base.LoadComplete();

            updateContent();
        }

        private void updateContent()
        {
            flow.Clear();

            if (createContent == null)
                return;

            flow.Add(mainContent = createContent());
            flow.Add(fillerContent = createContent().With(d => d.Alpha = 0));
            ScheduleAfterChildren(updateScrolling);
        }

        private void updateScrolling()
        {
            float overflowWidth = mainContent.DrawWidth + padding - DrawWidth;

            if (overflowWidth > 0 && AllowScrolling)
            {
                fillerContent.Alpha = 1;
                flow.Anchor = Anchor.TopLeft;
                flow.Origin = Anchor.TopLeft;

                float targetX = mainContent.DrawWidth + padding;

                flow.MoveToX(0)
                    .Delay(InitialMoveDelay)
                    .MoveToX(-targetX, targetX * 1000 / pixels_per_second)
                    .Loop();
            }
            else
            {
                fillerContent.Alpha = 0;
                flow.ClearTransforms();
                flow.MoveToX(0, 300, Easing.OutQuint);
                flow.Anchor = NonOverflowingContentAnchor;
                flow.Origin = NonOverflowingContentAnchor;
            }
        }
    }
}