// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable enable using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Threading; using osu.Game.Graphics.Containers; using osu.Game.Overlays; using osuTK; namespace osu.Game.Beatmaps.Drawables.Cards { public class BeatmapCardContent : CompositeDrawable { public Drawable MainContent { set => bodyContent.Child = value; } public Drawable ExpandedContent { set => dropdownScroll.Child = value; } public IBindable Expanded => expanded; private readonly BindableBool expanded = new BindableBool(); private readonly Box background; private readonly Container content; private readonly Container bodyContent; private readonly Container dropdownContent; private readonly OsuScrollContainer dropdownScroll; private readonly Container borderContainer; public BeatmapCardContent(float height) { RelativeSizeAxes = Axes.X; Height = height; Anchor = Anchor.Centre; Origin = Anchor.Centre; InternalChild = content = new HoverHandlingContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, CornerRadius = BeatmapCard.CORNER_RADIUS, Masking = true, Unhovered = _ => updateFromHoverChange(), Children = new Drawable[] { background = new Box { RelativeSizeAxes = Axes.Both }, bodyContent = new Container { RelativeSizeAxes = Axes.X, Height = height, CornerRadius = BeatmapCard.CORNER_RADIUS, Masking = true, }, dropdownContent = new HoverHandlingContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Margin = new MarginPadding { Top = height }, Alpha = 0, Hovered = _ => { updateFromHoverChange(); return true; }, Unhovered = _ => updateFromHoverChange(), Child = dropdownScroll = new ExpandedContentScrollContainer { RelativeSizeAxes = Axes.X, ScrollbarVisible = false } }, borderContainer = new Container { RelativeSizeAxes = Axes.Both, CornerRadius = BeatmapCard.CORNER_RADIUS, Masking = true, BorderThickness = 3, Child = new Box { RelativeSizeAxes = Axes.Both, Alpha = 0, AlwaysPresent = true } } } }; } [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { background.Colour = colourProvider.Background2; borderContainer.BorderColour = colourProvider.Highlight1; } protected override void LoadComplete() { base.LoadComplete(); Expanded.BindValueChanged(_ => updateState(), true); FinishTransforms(true); } private ScheduledDelegate? scheduledExpandedChange; public void ExpandAfterDelay() => queueExpandedStateChange(true, 100); public void CancelExpand() => scheduledExpandedChange?.Cancel(); private void updateFromHoverChange() => queueExpandedStateChange(content.IsHovered || dropdownContent.IsHovered, 100); private void queueExpandedStateChange(bool newState, int delay = 0) { if (Expanded.Disabled) return; scheduledExpandedChange?.Cancel(); scheduledExpandedChange = Scheduler.AddDelayed(() => expanded.Value = newState, delay); } private void updateState() { // Scale value is intentionally chosen to fit in the spacing of listing displays, as to not overlap horizontally with adjacent cards. // This avoids depth issues where a hovered (scaled) card to the right of another card would be beneath the card to the left. this.ScaleTo(Expanded.Value ? 1.03f : 1, 500, Easing.OutQuint); background.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); dropdownContent.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); borderContainer.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); content.TweenEdgeEffectTo(new EdgeEffectParameters { Type = EdgeEffectType.Shadow, Offset = new Vector2(0, 2), Radius = 10, Colour = Colour4.Black.Opacity(Expanded.Value ? 0.3f : 0f), Hollow = true, }, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } } }