Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ppy
GitHub Repository: ppy/osu
Path: blob/master/osu.Game/Screens/Play/HUD/ModDisplay.cs
4544 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 System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics.Containers;
using osu.Game.Localisation.SkinComponents;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using osuTK;

namespace osu.Game.Screens.Play.HUD
{
    /// <summary>
    /// Displays a single-line horizontal auto-sized flow of mods. For cases where wrapping is required, use <see cref="ModFlowDisplay"/> instead.
    /// </summary>
    public partial class ModDisplay : CompositeDrawable, IHasCurrentValue<IReadOnlyList<Mod>>
    {
        public const float MOD_ICON_SCALE = 0.6f;

        private ExpansionMode expansionMode = ExpansionMode.ExpandOnHover;

        public ExpansionMode ExpansionMode
        {
            get => expansionMode;
            set
            {
                if (expansionMode == value)
                    return;

                expansionMode = value;

                if (IsLoaded)
                    updateExpansionMode();
            }
        }

        private readonly BindableWithCurrent<IReadOnlyList<Mod>> current = new BindableWithCurrent<IReadOnlyList<Mod>>(Array.Empty<Mod>());

        public Bindable<IReadOnlyList<Mod>> Current
        {
            get => current.Current;
            set
            {
                ArgumentNullException.ThrowIfNull(value);

                current.Current = value;
            }
        }

        private bool showExtendedInformation;

        public bool ShowExtendedInformation
        {
            get => showExtendedInformation;
            set
            {
                showExtendedInformation = value;
                foreach (var icon in iconsContainer)
                    icon.ShowExtendedInformation = value;
            }
        }

        public FillDirection FillDirection
        {
            get => iconsContainer.Direction;
            set => iconsContainer.Direction = value;
        }

        private readonly FillFlowContainer<ModIcon> iconsContainer;

        public ModDisplay(bool showExtendedInformation = true)
        {
            this.showExtendedInformation = showExtendedInformation;

            AutoSizeAxes = Axes.Both;

            InternalChild = iconsContainer = new ReverseChildIDFillFlowContainer<ModIcon>
            {
                AutoSizeAxes = Axes.Both,
                Direction = FillDirection.Horizontal,
            };
        }

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

            Current.BindValueChanged(updateDisplay, true);
            updateExpansionMode(0);
        }

        private void updateDisplay(ValueChangedEvent<IReadOnlyList<Mod>> mods)
        {
            iconsContainer.Clear();

            foreach (Mod mod in mods.NewValue.AsOrdered())
                iconsContainer.Add(new ModIcon(mod, showExtendedInformation: showExtendedInformation) { Scale = new Vector2(MOD_ICON_SCALE) });
        }

        private void updateExpansionMode(double duration = 500)
        {
            switch (expansionMode)
            {
                case ExpansionMode.AlwaysExpanded:
                    expand(duration);
                    break;

                case ExpansionMode.AlwaysContracted:
                    contract(duration);
                    break;

                case ExpansionMode.ExpandOnHover:
                    if (IsHovered)
                        expand(duration);
                    else
                        contract(duration);
                    break;
            }
        }

        private void expand(double duration = 500)
        {
            if (ExpansionMode != ExpansionMode.AlwaysContracted)
                iconsContainer.TransformSpacingTo(new Vector2(5, -10), duration, Easing.OutQuint);
        }

        private void contract(double duration = 500)
        {
            if (ExpansionMode != ExpansionMode.AlwaysExpanded)
                iconsContainer.TransformSpacingTo(new Vector2(-25), duration, Easing.OutQuint);
        }

        protected override bool OnHover(HoverEvent e)
        {
            expand();
            return base.OnHover(e);
        }

        protected override void OnHoverLost(HoverLostEvent e)
        {
            contract();
            base.OnHoverLost(e);
        }
    }

    public enum ExpansionMode
    {
        /// <summary>
        /// The <see cref="ModDisplay"/> will expand only when hovered.
        /// </summary>
        [LocalisableDescription(typeof(SkinnableModDisplayStrings), nameof(SkinnableModDisplayStrings.ExpandOnHover))]
        ExpandOnHover,

        /// <summary>
        /// The <see cref="ModDisplay"/> will always be expanded.
        /// </summary>
        [LocalisableDescription(typeof(SkinnableModDisplayStrings), nameof(SkinnableModDisplayStrings.AlwaysExpanded))]
        AlwaysExpanded,

        /// <summary>
        /// The <see cref="ModDisplay"/> will always be contracted.
        /// </summary>
        [LocalisableDescription(typeof(SkinnableModDisplayStrings), nameof(SkinnableModDisplayStrings.AlwaysContracted))]
        AlwaysContracted,
    }
}