// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Overlays.Mods { public abstract partial class ModSelectPanel : OsuClickableContainer, IHasAccentColour, IFilterable { public abstract BindableBool Active { get; } public Color4 AccentColour { get; set; } public LocalisableString Title { get => titleText.Text; set => titleText.Text = value; } public LocalisableString Description { get => descriptionText.Text; set => descriptionText.Text = value; } public const float CORNER_RADIUS = 7; public const float HEIGHT = 42; public const double SAMPLE_PLAYBACK_DELAY = 30; protected virtual float IdleSwitchWidth => 14; protected virtual float ExpandedSwitchWidth => 30; protected virtual Colour4 BackgroundColour => Active.Value ? AccentColour.Darken(0.3f) : ColourProvider.Background3; protected virtual Colour4 ForegroundColour => Active.Value ? AccentColour : ColourProvider.Background2; protected virtual Colour4 TextColour => Active.Value ? ColourProvider.Background6 : Colour4.White; protected const double TRANSITION_DURATION = 150; protected readonly Box Background; protected readonly Container SwitchContainer; protected readonly Container MainContentContainer; protected readonly Box TextBackground; protected readonly FillFlowContainer TextFlow; [Resolved] protected OverlayColourProvider ColourProvider { get; private set; } = null!; private readonly OsuSpriteText titleText; private readonly OsuSpriteText descriptionText; private readonly Bindable samplePlaybackDisabled = new BindableBool(); private Sample? sampleOff; private Sample? sampleOn; private Bindable lastPlaybackTime = null!; protected ModSelectPanel() { RelativeSizeAxes = Axes.X; Height = HEIGHT; // all below properties are applied to `Content` rather than the `ModPanel` in its entirety // to allow external components to set these properties on the panel without affecting // its "internal" appearance. Content.Masking = true; Content.CornerRadius = CORNER_RADIUS; Content.BorderThickness = 2; Shear = new Vector2(OsuGame.SHEAR, 0); Children = new Drawable[] { Background = new Box { RelativeSizeAxes = Axes.Both }, SwitchContainer = new Container { RelativeSizeAxes = Axes.Y, }, MainContentContainer = new Container { RelativeSizeAxes = Axes.Both, Child = new Container { RelativeSizeAxes = Axes.Both, Masking = true, CornerRadius = CORNER_RADIUS, Children = new Drawable[] { TextBackground = new Box { RelativeSizeAxes = Axes.Both }, TextFlow = new FillFlowContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Horizontal = 17.5f, Vertical = 4 }, Direction = FillDirection.Vertical, Children = new[] { titleText = new TruncatingSpriteText { Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, Shear = new Vector2(-OsuGame.SHEAR, 0), Margin = new MarginPadding { Left = -18 * OsuGame.SHEAR }, ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`. }, descriptionText = new TruncatingSpriteText { Font = OsuFont.Default.With(size: 12), RelativeSizeAxes = Axes.X, Shear = new Vector2(-OsuGame.SHEAR, 0), ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`. } } } } } } }; Action = () => { if (!Active.Value) Select(); else Deselect(); }; } /// /// Performs all actions necessary to select this . /// protected abstract void Select(); /// /// Performs all actions necessary to deselect this . /// protected abstract void Deselect(); [BackgroundDependencyLoader] private void load(AudioManager audio, SessionStatics statics, ISamplePlaybackDisabler? samplePlaybackDisabler) { sampleOn = audio.Samples.Get(@"UI/check-on"); sampleOff = audio.Samples.Get(@"UI/check-off"); if (samplePlaybackDisabler != null) ((IBindable)samplePlaybackDisabled).BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); lastPlaybackTime = statics.GetBindable(Static.LastHoverSoundPlaybackTime); } protected sealed override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); protected override void LoadComplete() { base.LoadComplete(); Active.BindValueChanged(_ => { playStateChangeSamples(); UpdateState(); }); UpdateState(); FinishTransforms(true); } private void playStateChangeSamples() { if (samplePlaybackDisabled.Value) return; if (!IsPresent) return; bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= SAMPLE_PLAYBACK_DELAY; if (enoughTimePassedSinceLastPlayback) { if (Active.Value) sampleOn?.Play(); else sampleOff?.Play(); lastPlaybackTime.Value = Time.Current; } } protected override bool OnHover(HoverEvent e) { UpdateState(); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { UpdateState(); base.OnHoverLost(e); } private bool mouseDown; protected override bool OnMouseDown(MouseDownEvent e) { if (e.Button == MouseButton.Left) mouseDown = true; UpdateState(); return false; } protected override void OnMouseUp(MouseUpEvent e) { mouseDown = false; UpdateState(); base.OnMouseUp(e); } protected virtual void UpdateState() { float targetWidth = Active.Value ? ExpandedSwitchWidth : IdleSwitchWidth; double transitionDuration = TRANSITION_DURATION; Colour4 backgroundColour = BackgroundColour; Colour4 foregroundColour = ForegroundColour; Colour4 textColour = TextColour; // Hover affects colour of button background if (IsHovered) { backgroundColour = backgroundColour.Lighten(0.1f); foregroundColour = foregroundColour.Lighten(0.1f); } // Mouse down adds a halfway tween of the movement if (mouseDown) { targetWidth = (float)Interpolation.Lerp(IdleSwitchWidth, ExpandedSwitchWidth, 0.5f); transitionDuration *= 4; } Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, foregroundColour), transitionDuration, Easing.OutQuint); Background.FadeColour(backgroundColour, transitionDuration, Easing.OutQuint); SwitchContainer.ResizeWidthTo(targetWidth, transitionDuration, Easing.OutQuint); MainContentContainer.TransformTo(nameof(Padding), new MarginPadding { Left = targetWidth, Right = CORNER_RADIUS }, transitionDuration, Easing.OutQuint); TextBackground.FadeColour(foregroundColour, transitionDuration, Easing.OutQuint); TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint); } #region IFilterable public abstract IEnumerable FilterTerms { get; } private bool matchingFilter = true; public virtual bool MatchingFilter { get => matchingFilter; set { if (matchingFilter == value) return; matchingFilter = value; this.FadeTo(value ? 1 : 0); } } public bool FilteringActive { set { } } #endregion } }