1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-11 06:37:19 +08:00

Abstractify beatmap panel piece and update all panel implementations

This commit is contained in:
Salman Alshamrani 2025-02-06 00:10:42 -05:00
parent ecc3aeadf2
commit 134e62c39a
6 changed files with 608 additions and 947 deletions

View File

@ -6,13 +6,9 @@ using System.Diagnostics;
using System.Threading;
using osu.Framework.Allocation;
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.Effects;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
@ -25,7 +21,6 @@ using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.SelectV2
{
@ -33,36 +28,23 @@ namespace osu.Game.Screens.SelectV2
{
public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT;
private const float colour_box_width = 30;
private const float corner_radius = 10;
// todo: this should be replaced with information from CarouselItem about how deep is BeatmapPanel in the carousel
// (i.e. whether it's under a beatmap set that's under a group, or just under a top-level beatmap set).
private const float difficulty_x_offset = 100f; // constant X offset for beatmap difficulty panels specifically.
private const float preselected_x_offset = 25f;
private const float selected_x_offset = 50f;
private const float duration = 500;
[Resolved]
private BeatmapCarousel? carousel { get; set; }
[Resolved]
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
[Resolved]
private IBindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
private Container panel = null!;
private CarouselPanelPiece panel = null!;
private StarCounter starCounter = null!;
private ConstrainedIconContainer iconContainer = null!;
private Box hoverLayer = null!;
private Box activationFlash = null!;
private Box backgroundBorder = null!;
private ConstrainedIconContainer difficultyIcon = null!;
private OsuSpriteText keyCountText = null!;
private StarRatingDisplay starRatingDisplay = null!;
private TopLocalRankV2 difficultyRank = null!;
private OsuSpriteText difficultyText = null!;
private OsuSpriteText authorText = null!;
private IBindable<StarDifficulty?>? starDifficultyBindable;
private CancellationTokenSource? starDifficultyCancellationSource;
[Resolved]
private OverlayColourProvider colourProvider { get; set; } = null!;
@ -73,16 +55,24 @@ namespace osu.Game.Screens.SelectV2
[Resolved]
private BeatmapDifficultyCache difficultyCache { get; set; } = null!;
private OsuSpriteText keyCountText = null!;
[Resolved]
private BeatmapCarousel? carousel { get; set; }
private IBindable<StarDifficulty?>? starDifficultyBindable;
private CancellationTokenSource? starDifficultyCancellationSource;
[Resolved]
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
private Container rightContainer = null!;
private Box starRatingGradient = null!;
private TopLocalRankV2 difficultyRank = null!;
private OsuSpriteText difficultyText = null!;
private OsuSpriteText authorText = null!;
[Resolved]
private IBindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos)
{
var inputRectangle = panel.TopLevelContent.DrawRectangle;
// Cover the gaps introduced by the spacing between BeatmapPanels.
inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f });
return inputRectangle.Contains(panel.TopLevelContent.ToLocalSpace(screenSpacePos));
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
@ -94,67 +84,21 @@ namespace osu.Game.Screens.SelectV2
Width = 1f;
Height = HEIGHT;
InternalChild = panel = new Container
InternalChild = panel = new CarouselPanelPiece(difficulty_x_offset)
{
Masking = true,
CornerRadius = corner_radius,
RelativeSizeAxes = Axes.Both,
X = corner_radius,
EdgeEffect = new EdgeEffectParameters
Icon = difficultyIcon = new ConstrainedIconContainer
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(1f),
Radius = 10,
Size = new Vector2(20),
Margin = new MarginPadding { Horizontal = 5f },
Colour = colourProvider.Background5,
},
Children = new Drawable[]
Children = new[]
{
new BufferedContainer
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
backgroundBorder = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.ForStarDifficulty(0),
EdgeSmoothness = new Vector2(2, 0),
},
rightContainer = new Container
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Masking = true,
CornerRadius = corner_radius,
RelativeSizeAxes = Axes.X,
Height = HEIGHT,
X = colour_box_width,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientHorizontal(colourProvider.Background3, colourProvider.Background4),
},
starRatingGradient = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
},
},
},
}
},
iconContainer = new ConstrainedIconContainer
{
X = colour_box_width / 2,
Origin = Anchor.Centre,
Anchor = Anchor.CentreLeft,
Size = new Vector2(20),
Colour = colourProvider.Background5,
},
new FillFlowContainer
{
Padding = new MarginPadding { Top = 8, Left = colour_box_width + corner_radius },
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Padding = new MarginPadding { Left = 10f },
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
@ -216,34 +160,10 @@ namespace osu.Game.Screens.SelectV2
}
}
},
hoverLayer = new Box
{
Colour = colours.Blue.Opacity(0.1f),
Alpha = 0,
Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
},
activationFlash = new Box
{
Blending = BlendingParameters.Additive,
Alpha = 0f,
RelativeSizeAxes = Axes.Both,
},
new HoverSounds(),
}
};
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
{
var inputRectangle = panel.DrawRectangle;
// Cover the gaps introduced by the spacing between BeatmapPanels.
inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f });
return inputRectangle.Contains(panel.ToLocalSpace(screenSpacePos));
}
protected override void LoadComplete()
{
base.LoadComplete();
@ -260,8 +180,8 @@ namespace osu.Game.Screens.SelectV2
updateKeyCount();
}, true);
Selected.BindValueChanged(_ => updateSelectionDisplay(), true);
KeyboardSelected.BindValueChanged(_ => updateKeyboardSelectedDisplay(), true);
Selected.BindValueChanged(s => panel.Active.Value = s.NewValue, true);
KeyboardSelected.BindValueChanged(k => panel.KeyboardActive.Value = k.NewValue, true);
}
protected override void PrepareForUse()
@ -271,63 +191,25 @@ namespace osu.Game.Screens.SelectV2
Debug.Assert(Item != null);
var beatmap = (BeatmapInfo)Item.Model;
iconContainer.Icon = beatmap.Ruleset.CreateInstance().CreateIcon();
difficultyIcon.Icon = beatmap.Ruleset.CreateInstance().CreateIcon();
difficultyRank.Beatmap = beatmap;
difficultyText.Text = beatmap.DifficultyName;
authorText.Text = BeatmapsetsStrings.ShowDetailsMappedBy(beatmap.Metadata.Author.Username);
starDifficultyBindable = null;
computeStarRating();
updateKeyCount();
updateSelectionDisplay();
FinishTransforms(true);
this.FadeInFromZero(duration, Easing.OutQuint);
// todo: only do this when visible.
// starCounter.ReplayAnimation();
}
private void updateSelectionDisplay()
protected override void FreeAfterUse()
{
bool selected = Selected.Value;
base.FreeAfterUse();
rightContainer.ResizeHeightTo(selected ? HEIGHT - 4 : HEIGHT, duration, Easing.OutQuint);
updatePanelPosition();
updateEdgeEffectColour();
}
private void updateKeyboardSelectedDisplay()
{
updatePanelPosition();
updateHover();
}
private void updatePanelPosition()
{
float x = difficulty_x_offset + selected_x_offset + preselected_x_offset;
if (Selected.Value)
x -= selected_x_offset;
if (KeyboardSelected.Value)
x -= preselected_x_offset;
this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint);
}
private void updateHover()
{
bool hovered = IsHovered || KeyboardSelected.Value;
if (hovered)
hoverLayer.FadeIn(100, Easing.OutQuint);
else
hoverLayer.FadeOut(1000, Easing.OutQuint);
difficultyRank.Beatmap = null;
starDifficultyBindable = null;
}
private void computeStarRating()
@ -341,34 +223,7 @@ namespace osu.Game.Screens.SelectV2
var beatmap = (BeatmapInfo)Item.Model;
starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token);
starDifficultyBindable.BindValueChanged(d =>
{
var value = d.NewValue ?? default;
starRatingDisplay.Current.Value = value;
starCounter.Current = (float)value.Stars;
iconContainer.FadeColour(value.Stars > 6.5f ? colours.Orange1 : colourProvider.Background5, duration, Easing.OutQuint);
var starRatingColour = colours.ForStarDifficulty(value.Stars);
backgroundBorder.FadeColour(starRatingColour, duration, Easing.OutQuint);
starCounter.FadeColour(starRatingColour, duration, Easing.OutQuint);
starRatingGradient.FadeColour(ColourInfo.GradientHorizontal(starRatingColour.Opacity(0.25f), starRatingColour.Opacity(0)), duration, Easing.OutQuint);
starRatingGradient.FadeIn(duration, Easing.OutQuint);
// todo: this doesn't work for dark star rating colours, still not sure how to fix.
activationFlash.FadeColour(starRatingColour, duration, Easing.OutQuint);
updateEdgeEffectColour();
}, true);
}
private void updateEdgeEffectColour()
{
panel.FadeEdgeEffectTo(Selected.Value
? colours.ForStarDifficulty(starDifficultyBindable?.Value?.Stars ?? 0f).Opacity(0.5f)
: Color4.Black.Opacity(0.4f), duration, Easing.OutQuint);
starDifficultyBindable.BindValueChanged(_ => updateDisplay(), true);
}
private void updateKeyCount()
@ -392,16 +247,18 @@ namespace osu.Game.Screens.SelectV2
keyCountText.Alpha = 0;
}
protected override bool OnHover(HoverEvent e)
private void updateDisplay()
{
updateHover();
return true;
}
var starDifficulty = starDifficultyBindable?.Value ?? default;
protected override void OnHoverLost(HoverLostEvent e)
{
updateHover();
base.OnHoverLost(e);
starRatingDisplay.Current.Value = starDifficulty;
starCounter.Current = (float)starDifficulty.Stars;
difficultyIcon.FadeColour(starDifficulty.Stars > 6.5f ? colours.Orange1 : colourProvider.Background5, duration, Easing.OutQuint);
var starRatingColour = colours.ForStarDifficulty(starDifficulty.Stars);
starCounter.FadeColour(starRatingColour, duration, Easing.OutQuint);
panel.AccentColour = starRatingColour;
}
protected override bool OnClick(ClickEvent e)
@ -430,7 +287,7 @@ namespace osu.Game.Screens.SelectV2
public void Activated()
{
activationFlash.FadeOutFromOne(500, Easing.OutQuint);
panel.Flash();
}
#endregion

View File

@ -6,12 +6,9 @@ using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
@ -19,10 +16,8 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.SelectV2
{
@ -30,18 +25,22 @@ namespace osu.Game.Screens.SelectV2
{
public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 1.6f;
private const float arrow_container_width = 20;
private const float corner_radius = 10;
// todo: this should be replaced with information from CarouselItem about how deep is BeatmapPanel in the carousel
// (i.e. whether it's under a beatmap set that's under a group, or just under a top-level beatmap set).
private const float set_x_offset = 20f; // constant X offset for beatmap set/standalone panels specifically.
private const float preselected_x_offset = 25f;
private const float expanded_x_offset = 50f;
private const float duration = 500;
private CarouselPanelPiece panel = null!;
private BeatmapSetPanelBackground background = null!;
private OsuSpriteText titleText = null!;
private OsuSpriteText artistText = null!;
private Drawable chevronIcon = null!;
private UpdateBeatmapSetButtonV2 updateButton = null!;
private BeatmapSetOnlineStatusPill statusPill = null!;
private DifficultySpectrumDisplay difficultiesDisplay = null!;
[Resolved]
private BeatmapCarousel? carousel { get; set; }
@ -51,22 +50,15 @@ namespace osu.Game.Screens.SelectV2
[Resolved]
private BeatmapManager beatmaps { get; set; } = null!;
[Resolved]
private OsuColour colours { get; set; } = null!;
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos)
{
var inputRectangle = panel.TopLevelContent.DrawRectangle;
private Container panel = null!;
private Box backgroundBorder = null!;
private BeatmapSetPanelBackground background = null!;
private Container backgroundContainer = null!;
private FillFlowContainer mainFlowContainer = null!;
private SpriteIcon chevronIcon = null!;
private Box hoverLayer = null!;
// Cover a gap introduced by the spacing between a BeatmapSetPanel and a BeatmapPanel either above it or below it.
inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f });
private OsuSpriteText titleText = null!;
private OsuSpriteText artistText = null!;
private UpdateBeatmapSetButtonV2 updateButton = null!;
private BeatmapSetOnlineStatusPill statusPill = null!;
private DifficultySpectrumDisplay difficultiesDisplay = null!;
return inputRectangle.Contains(panel.TopLevelContent.ToLocalSpace(screenSpacePos));
}
[BackgroundDependencyLoader]
private void load()
@ -76,137 +68,89 @@ namespace osu.Game.Screens.SelectV2
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
InternalChild = panel = new Container
InternalChild = panel = new CarouselPanelPiece(set_x_offset)
{
Masking = true,
CornerRadius = corner_radius,
RelativeSizeAxes = Axes.Both,
X = corner_radius,
EdgeEffect = new EdgeEffectParameters
Icon = chevronIcon = new Container
{
Type = EdgeEffectType.Shadow,
Radius = 10,
},
Children = new Drawable[]
{
new BufferedContainer
Size = new Vector2(22),
Child = new SpriteIcon
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
backgroundBorder = new Box
{
RelativeSizeAxes = Axes.Y,
Alpha = 0,
EdgeSmoothness = new Vector2(2, 0),
},
backgroundContainer = new Container
{
Masking = true,
CornerRadius = corner_radius,
RelativeSizeAxes = Axes.X,
MaskingSmoothness = 2,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Children = new Drawable[]
{
background = new BeatmapSetPanelBackground
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
},
},
},
}
},
chevronIcon = new SpriteIcon
{
X = arrow_container_width / 2,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Anchor = Anchor.CentreLeft,
Icon = FontAwesome.Solid.ChevronRight,
Size = new Vector2(12),
X = 1f,
Colour = colourProvider.Background5,
},
mainFlowContainer = new FillFlowContainer
},
Background = background = new BeatmapSetPanelBackground
{
RelativeSizeAxes = Axes.Both,
},
Child = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Padding = new MarginPadding { Top = 7.5f, Left = 15, Bottom = 5 },
Children = new Drawable[]
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Padding = new MarginPadding { Top = 7.5f, Left = 15, Bottom = 5 },
Children = new Drawable[]
titleText = new OsuSpriteText
{
titleText = new OsuSpriteText
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true),
},
artistText = new OsuSpriteText
{
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true),
},
new FillFlowContainer
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 5f },
Children = new Drawable[]
{
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true),
Shadow = true,
},
artistText = new OsuSpriteText
{
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true),
Shadow = true,
},
new FillFlowContainer
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 5f },
Children = new Drawable[]
updateButton = new UpdateBeatmapSetButtonV2
{
updateButton = new UpdateBeatmapSetButtonV2
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Right = 5f, Top = -2f },
},
statusPill = new BeatmapSetOnlineStatusPill
{
AutoSizeAxes = Axes.Both,
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
TextSize = 11,
TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 },
Margin = new MarginPadding { Right = 5f },
},
difficultiesDisplay = new DifficultySpectrumDisplay
{
DotSize = new Vector2(5, 10),
DotSpacing = 2,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Right = 5f, Top = -2f },
},
}
statusPill = new BeatmapSetOnlineStatusPill
{
AutoSizeAxes = Axes.Both,
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
TextSize = 11,
TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 },
Margin = new MarginPadding { Right = 5f },
},
difficultiesDisplay = new DifficultySpectrumDisplay
{
DotSize = new Vector2(5, 10),
DotSpacing = 2,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
},
}
},
hoverLayer = new Box
{
Colour = colours.Blue.Opacity(0.1f),
Alpha = 0,
Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
},
new HoverSounds(),
}
}
};
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
{
var inputRectangle = panel.DrawRectangle;
// Cover a gap introduced by the spacing between a BeatmapSetPanel and a BeatmapPanel either above it or below it.
inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f });
return inputRectangle.Contains(panel.ToLocalSpace(screenSpacePos));
}
protected override void LoadComplete()
{
base.LoadComplete();
Expanded.BindValueChanged(_ => updateExpandedDisplay(), true);
KeyboardSelected.BindValueChanged(_ => updateKeyboardSelectedDisplay(), true);
Expanded.BindValueChanged(_ => onExpanded(), true);
KeyboardSelected.BindValueChanged(k => panel.KeyboardActive.Value = k.NewValue, true);
}
private void onExpanded()
{
panel.Active.Value = Expanded.Value;
chevronIcon.ResizeWidthTo(Expanded.Value ? 22 : 0f, duration, Easing.OutQuint);
chevronIcon.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint);
}
protected override void PrepareForUse()
@ -226,9 +170,7 @@ namespace osu.Game.Screens.SelectV2
statusPill.Status = beatmapSet.Status;
difficultiesDisplay.BeatmapSet = beatmapSet;
updateExpandedDisplay();
FinishTransforms(true);
this.FadeInFromZero(duration, Easing.OutQuint);
}
@ -241,70 +183,6 @@ namespace osu.Game.Screens.SelectV2
difficultiesDisplay.BeatmapSet = null;
}
private void updateExpandedDisplay()
{
if (Item == null)
return;
updatePanelPosition();
backgroundBorder.RelativeSizeAxes = Expanded.Value ? Axes.Both : Axes.Y;
backgroundBorder.Width = Expanded.Value ? 1 : arrow_container_width + corner_radius;
backgroundBorder.FadeTo(Expanded.Value ? 1 : 0, duration, Easing.OutQuint);
chevronIcon.FadeTo(Expanded.Value ? 1 : 0, duration, Easing.OutQuint);
backgroundContainer.ResizeHeightTo(Expanded.Value ? HEIGHT - 4 : HEIGHT, duration, Easing.OutQuint);
backgroundContainer.MoveToX(Expanded.Value ? arrow_container_width : 0, duration, Easing.OutQuint);
mainFlowContainer.MoveToX(Expanded.Value ? arrow_container_width : 0, duration, Easing.OutQuint);
panel.EdgeEffect = panel.EdgeEffect with { Radius = Expanded.Value ? 15 : 10 };
panel.FadeEdgeEffectTo(Expanded.Value
? Color4Extensions.FromHex(@"4EBFFF").Opacity(0.5f)
: Color4.Black.Opacity(0.4f), duration, Easing.OutQuint);
}
private void updateKeyboardSelectedDisplay()
{
updatePanelPosition();
updateHover();
}
private void updatePanelPosition()
{
float x = set_x_offset + expanded_x_offset + preselected_x_offset;
if (Expanded.Value)
x -= expanded_x_offset;
if (KeyboardSelected.Value)
x -= preselected_x_offset;
this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint);
}
private void updateHover()
{
bool hovered = IsHovered || KeyboardSelected.Value;
if (hovered)
hoverLayer.FadeIn(100, Easing.OutQuint);
else
hoverLayer.FadeOut(1000, Easing.OutQuint);
}
protected override bool OnHover(HoverEvent e)
{
updateHover();
return true;
}
protected override void OnHoverLost(HoverLostEvent e)
{
updateHover();
base.OnHoverLost(e);
}
protected override bool OnClick(ClickEvent e)
{
if (carousel != null)

View File

@ -8,12 +8,9 @@ using System.Linq;
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
@ -21,13 +18,11 @@ using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.SelectV2
{
@ -35,15 +30,9 @@ namespace osu.Game.Screens.SelectV2
{
public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 1.6f;
private const float difficulty_icon_container_width = 30;
private const float corner_radius = 10;
// todo: this should be replaced with information from CarouselItem about how deep is BeatmapPanel in the carousel
// (i.e. whether it's under a beatmap set that's under a group, or just under a top-level beatmap set).
private const float set_x_offset = 20f; // constant X offset for beatmap set/standalone panels specifically.
private const float preselected_x_offset = 25f;
private const float selected_x_offset = 50f;
private const float standalone_x_offset = 20f; // constant X offset for beatmap set/standalone panels specifically.
private const float duration = 500;
@ -71,12 +60,8 @@ namespace osu.Game.Screens.SelectV2
private IBindable<StarDifficulty?>? starDifficultyBindable;
private CancellationTokenSource? starDifficultyCancellationSource;
private Container panel = null!;
private Box backgroundBorder = null!;
private CarouselPanelPiece panel = null!;
private BeatmapSetPanelBackground background = null!;
private Container backgroundContainer = null!;
private FillFlowContainer mainFlowContainer = null!;
private Box hoverLayer = null!;
private OsuSpriteText titleText = null!;
private OsuSpriteText artistText = null!;
@ -91,6 +76,16 @@ namespace osu.Game.Screens.SelectV2
private OsuSpriteText difficultyName = null!;
private OsuSpriteText difficultyAuthor = null!;
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos)
{
var inputRectangle = panel.TopLevelContent.DrawRectangle;
// Cover a gap introduced by the spacing between a BeatmapSetPanel and a BeatmapPanel either above it or below it.
inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f });
return inputRectangle.Contains(panel.TopLevelContent.ToLocalSpace(screenSpacePos));
}
[BackgroundDependencyLoader]
private void load()
{
@ -100,167 +95,109 @@ namespace osu.Game.Screens.SelectV2
Width = 1f;
Height = HEIGHT;
InternalChild = panel = new Container
InternalChild = panel = new CarouselPanelPiece(standalone_x_offset)
{
Masking = true,
CornerRadius = corner_radius,
RelativeSizeAxes = Axes.Both,
X = corner_radius,
EdgeEffect = new EdgeEffectParameters
Icon = difficultyIcon = new ConstrainedIconContainer
{
Type = EdgeEffectType.Shadow,
Radius = 10,
Size = new Vector2(20),
Margin = new MarginPadding { Horizontal = 5f },
Colour = colourProvider.Background5,
},
Children = new Drawable[]
Background = background = new BeatmapSetPanelBackground
{
new BufferedContainer
RelativeSizeAxes = Axes.Both,
},
Child = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Padding = new MarginPadding { Top = 7.5f, Left = 15, Bottom = 5 },
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
titleText = new OsuSpriteText
{
backgroundBorder = new Box
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true),
Shadow = true,
},
artistText = new OsuSpriteText
{
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true),
Shadow = true,
},
new FillFlowContainer
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 5f },
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Y,
Alpha = 0,
EdgeSmoothness = new Vector2(2, 0),
},
backgroundContainer = new Container
{
Masking = true,
CornerRadius = corner_radius,
RelativeSizeAxes = Axes.X,
MaskingSmoothness = 2,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Children = new Drawable[]
updateButton = new UpdateBeatmapSetButtonV2
{
background = new BeatmapSetPanelBackground
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
},
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Right = 5f, Top = -2f },
},
},
}
},
difficultyIcon = new ConstrainedIconContainer
{
X = difficulty_icon_container_width / 2,
Origin = Anchor.Centre,
Anchor = Anchor.CentreLeft,
Size = new Vector2(20),
},
mainFlowContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Padding = new MarginPadding { Top = 7.5f, Left = 15, Bottom = 5 },
Children = new Drawable[]
{
titleText = new OsuSpriteText
{
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true),
Shadow = true,
},
artistText = new OsuSpriteText
{
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true),
Shadow = true,
},
new FillFlowContainer
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 5f },
Children = new Drawable[]
statusPill = new BeatmapSetOnlineStatusPill
{
updateButton = new UpdateBeatmapSetButtonV2
AutoSizeAxes = Axes.Both,
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
TextSize = 11,
TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 },
Margin = new MarginPadding { Right = 5f },
},
difficultyLine = new FillFlowContainer
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Right = 5f, Top = -2f },
},
statusPill = new BeatmapSetOnlineStatusPill
{
AutoSizeAxes = Axes.Both,
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
TextSize = 11,
TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 },
Margin = new MarginPadding { Right = 5f },
},
difficultyLine = new FillFlowContainer
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
difficultyStarRating = new StarRatingDisplay(default, StarRatingDisplaySize.Small)
{
difficultyStarRating = new StarRatingDisplay(default, StarRatingDisplaySize.Small)
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
Scale = new Vector2(8f / 9f),
Margin = new MarginPadding { Right = 5f },
},
difficultyRank = new TopLocalRankV2
{
Scale = new Vector2(8f / 11),
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
Margin = new MarginPadding { Right = 5f },
},
difficultyKeyCountText = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold),
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Alpha = 0,
Margin = new MarginPadding { Bottom = 2f },
},
difficultyName = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold),
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Margin = new MarginPadding { Right = 5f, Bottom = 2f },
},
difficultyAuthor = new OsuSpriteText
{
Colour = colourProvider.Content2,
Font = OsuFont.GetFont(weight: FontWeight.SemiBold),
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Margin = new MarginPadding { Right = 5f, Bottom = 2f },
}
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
Scale = new Vector2(8f / 9f),
Margin = new MarginPadding { Right = 5f },
},
difficultyRank = new TopLocalRankV2
{
Scale = new Vector2(8f / 11),
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
Margin = new MarginPadding { Right = 5f },
},
difficultyKeyCountText = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold),
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Alpha = 0,
Margin = new MarginPadding { Bottom = 2f },
},
difficultyName = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold),
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Margin = new MarginPadding { Right = 5f, Bottom = 2f },
},
difficultyAuthor = new OsuSpriteText
{
Colour = colourProvider.Content2,
Font = OsuFont.GetFont(weight: FontWeight.SemiBold),
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Margin = new MarginPadding { Right = 5f, Bottom = 2f },
}
},
}
},
}
},
}
},
hoverLayer = new Box
{
Colour = colours.Blue.Opacity(0.1f),
Alpha = 0,
Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
},
new HoverSounds(),
}
}
},
};
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
{
var inputRectangle = panel.DrawRectangle;
// Cover a gap introduced by the spacing between a BeatmapSetPanel and a BeatmapPanel either above it or below it.
inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f });
return inputRectangle.Contains(panel.ToLocalSpace(screenSpacePos));
}
protected override void LoadComplete()
{
base.LoadComplete();
@ -277,8 +214,8 @@ namespace osu.Game.Screens.SelectV2
updateKeyCount();
}, true);
Selected.BindValueChanged(_ => updateSelectedDisplay(), true);
KeyboardSelected.BindValueChanged(_ => updateKeyboardSelectedDisplay(), true);
Selected.BindValueChanged(s => panel.Active.Value = s.NewValue, true);
KeyboardSelected.BindValueChanged(k => panel.KeyboardActive.Value = k.NewValue, true);
}
protected override void PrepareForUse()
@ -308,7 +245,6 @@ namespace osu.Game.Screens.SelectV2
computeStarRating();
updateSelectedDisplay();
FinishTransforms(true);
this.FadeInFromZero(duration, Easing.OutQuint);
@ -324,55 +260,6 @@ namespace osu.Game.Screens.SelectV2
starDifficultyBindable = null;
}
private void updateSelectedDisplay()
{
if (Item == null)
return;
updatePanelPosition();
backgroundBorder.RelativeSizeAxes = Selected.Value ? Axes.Both : Axes.Y;
backgroundBorder.Width = Selected.Value ? 1 : difficulty_icon_container_width + corner_radius;
backgroundBorder.FadeTo(Selected.Value ? 1 : 0, duration, Easing.OutQuint);
difficultyIcon.FadeTo(Selected.Value ? 1 : 0, duration, Easing.OutQuint);
backgroundContainer.ResizeHeightTo(Selected.Value ? HEIGHT - 4 : HEIGHT, duration, Easing.OutQuint);
backgroundContainer.MoveToX(Selected.Value ? difficulty_icon_container_width : 0, duration, Easing.OutQuint);
mainFlowContainer.MoveToX(Selected.Value ? difficulty_icon_container_width : 0, duration, Easing.OutQuint);
panel.EdgeEffect = panel.EdgeEffect with { Radius = Selected.Value ? 15 : 10 };
updateEdgeEffectColour();
}
private void updateKeyboardSelectedDisplay()
{
updatePanelPosition();
updateHover();
}
private void updatePanelPosition()
{
float x = set_x_offset + selected_x_offset + preselected_x_offset;
if (Selected.Value)
x -= selected_x_offset;
if (KeyboardSelected.Value)
x -= preselected_x_offset;
this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint);
}
private void updateHover()
{
bool hovered = IsHovered || KeyboardSelected.Value;
if (hovered)
hoverLayer.FadeIn(100, Easing.OutQuint);
else
hoverLayer.FadeOut(1000, Easing.OutQuint);
}
private void computeStarRating()
{
starDifficultyCancellationSource?.Cancel();
@ -384,23 +271,7 @@ namespace osu.Game.Screens.SelectV2
var beatmap = (BeatmapInfo)Item.Model;
starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token);
starDifficultyBindable.BindValueChanged(d =>
{
var value = d.NewValue ?? default;
backgroundBorder.FadeColour(colours.ForStarDifficulty(value.Stars), duration, Easing.OutQuint);
difficultyIcon.FadeColour(value.Stars > 6.5f ? colours.Orange1 : colourProvider.Background5, duration, Easing.OutQuint);
difficultyStarRating.Current.Value = value;
updateEdgeEffectColour();
}, true);
}
private void updateEdgeEffectColour()
{
panel.FadeEdgeEffectTo(Selected.Value
? colours.ForStarDifficulty(starDifficultyBindable?.Value?.Stars ?? 0f).Opacity(0.5f)
: Color4.Black.Opacity(0.4f), duration, Easing.OutQuint);
starDifficultyBindable.BindValueChanged(_ => updateDisplay(), true);
}
private void updateKeyCount()
@ -424,16 +295,13 @@ namespace osu.Game.Screens.SelectV2
difficultyKeyCountText.Alpha = 0;
}
protected override bool OnHover(HoverEvent e)
private void updateDisplay()
{
updateHover();
return true;
}
var starDifficulty = starDifficultyBindable?.Value ?? default;
protected override void OnHoverLost(HoverLostEvent e)
{
updateHover();
base.OnHoverLost(e);
panel.AccentColour = colours.ForStarDifficulty(starDifficulty.Stars);
difficultyIcon.FadeColour(starDifficulty.Stars > 6.5f ? colours.Orange1 : colourProvider.Background5, duration, Easing.OutQuint);
difficultyStarRating.Current.Value = starDifficulty;
}
protected override bool OnClick(ClickEvent e)

View File

@ -0,0 +1,240 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
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.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.SelectV2
{
public partial class CarouselPanelPiece : Container
{
private const float corner_radius = 10;
private const float left_edge_x_offset = 20f;
private const float keyboard_active_x_offset = 25f;
private const float active_x_offset = 50f;
private const float duration = 500;
private readonly float panelXOffset;
private readonly Box backgroundBorder;
private readonly Box backgroundGradient;
private readonly Box backgroundAccentGradient;
private readonly Container backgroundLayer;
private readonly Container backgroundLayerHorizontalPadding;
private readonly Container backgroundContainer;
private readonly Container iconContainer;
private readonly Box activationFlash;
private readonly Box hoverLayer;
public Container TopLevelContent { get; }
protected override Container Content { get; }
public Drawable Background
{
set => backgroundContainer.Child = value;
}
public Drawable Icon
{
set => iconContainer.Child = value;
}
private Color4? accentColour;
public Color4? AccentColour
{
get => accentColour;
set
{
accentColour = value;
updateDisplay();
}
}
public readonly BindableBool Active = new BindableBool();
public readonly BindableBool KeyboardActive = new BindableBool();
public CarouselPanelPiece(float panelXOffset)
{
this.panelXOffset = panelXOffset;
RelativeSizeAxes = Axes.Both;
InternalChild = TopLevelContent = new Container
{
Masking = true,
CornerRadius = corner_radius,
RelativeSizeAxes = Axes.Both,
X = corner_radius,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(1f),
Radius = 10,
},
Children = new Drawable[]
{
new BufferedContainer
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
backgroundBorder = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
backgroundLayerHorizontalPadding = new Container
{
RelativeSizeAxes = Axes.Both,
Child = backgroundLayer = new Container
{
RelativeSizeAxes = Axes.Both,
Child = new Container
{
Masking = true,
CornerRadius = corner_radius,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
backgroundGradient = new Box
{
RelativeSizeAxes = Axes.Both,
},
backgroundAccentGradient = new Box
{
RelativeSizeAxes = Axes.Both,
},
backgroundContainer = new Container
{
RelativeSizeAxes = Axes.Both,
},
}
},
},
}
},
},
iconContainer = new Container
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
},
Content = new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = panelXOffset + corner_radius },
},
hoverLayer = new Box
{
Alpha = 0,
Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
},
activationFlash = new Box
{
Colour = Color4.White.Opacity(0.4f),
Blending = BlendingParameters.Additive,
Alpha = 0f,
RelativeSizeAxes = Axes.Both,
},
new HoverSounds(),
}
};
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider, OsuColour colours)
{
hoverLayer.Colour = colours.Blue.Opacity(0.1f);
backgroundGradient.Colour = ColourInfo.GradientHorizontal(colourProvider.Background3, colourProvider.Background4);
}
protected override void LoadComplete()
{
base.LoadComplete();
Active.BindValueChanged(_ => updateDisplay());
KeyboardActive.BindValueChanged(_ => updateDisplay(), true);
}
public void Flash()
{
activationFlash.FadeOutFromOne(500, Easing.OutQuint);
}
private void updateDisplay()
{
backgroundLayer.TransformTo(nameof(Padding), backgroundLayer.Padding with { Vertical = Active.Value ? 2f : 0f }, duration, Easing.OutQuint);
var backgroundColour = accentColour ?? Color4.White;
var edgeEffectColour = accentColour ?? Color4Extensions.FromHex(@"4EBFFF");
backgroundAccentGradient.FadeColour(ColourInfo.GradientHorizontal(backgroundColour.Opacity(0.25f), backgroundColour.Opacity(0f)), duration, Easing.OutQuint);
backgroundBorder.FadeColour(backgroundColour, duration, Easing.OutQuint);
TopLevelContent.FadeEdgeEffectTo(Active.Value ? edgeEffectColour.Opacity(0.5f) : Color4.Black.Opacity(0.4f), duration, Easing.OutQuint);
updateXOffset();
updateHover();
}
private void updateXOffset()
{
float x = panelXOffset + active_x_offset + keyboard_active_x_offset + left_edge_x_offset;
if (Active.Value)
x -= active_x_offset;
if (KeyboardActive.Value)
x -= keyboard_active_x_offset;
this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint);
}
private void updateHover()
{
bool hovered = IsHovered || KeyboardActive.Value;
if (hovered)
hoverLayer.FadeIn(100, Easing.OutQuint);
else
hoverLayer.FadeOut(1000, Easing.OutQuint);
}
protected override bool OnHover(HoverEvent e)
{
updateDisplay();
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
updateDisplay();
base.OnHoverLost(e);
}
protected override void Update()
{
base.Update();
Content.Padding = Content.Padding with { Left = iconContainer.DrawWidth };
backgroundLayerHorizontalPadding.Padding = new MarginPadding { Left = iconContainer.DrawWidth };
}
}
}

View File

@ -10,10 +10,10 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osuTK;
using osuTK.Graphics;
@ -24,137 +24,83 @@ namespace osu.Game.Screens.SelectV2
{
public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT;
private const float corner_radius = 10;
private const float glow_offset = 10f; // extra space for any edge effect to not be cutoff by the right edge of the carousel.
private const float preselected_x_offset = 25f;
private const float selected_x_offset = 50f;
private const float duration = 500;
[Resolved]
private BeatmapCarousel? carousel { get; set; }
private Container panel = null!;
private Box activationFlash = null!;
private CarouselPanelPiece panel = null!;
private Drawable chevronIcon = null!;
private OsuSpriteText titleText = null!;
private Box hoverLayer = null!;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos)
{
var inputRectangle = panel.DrawRectangle;
var inputRectangle = panel.TopLevelContent.DrawRectangle;
// Cover a gap introduced by the spacing between a GroupPanel and a BeatmapPanel either below/above it.
// Cover a gap introduced by the spacing between a GroupPanel and other panel types either below/above it.
inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f });
return inputRectangle.Contains(panel.ToLocalSpace(screenSpacePos));
return inputRectangle.Contains(panel.TopLevelContent.ToLocalSpace(screenSpacePos));
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider, OsuColour colours)
private void load(OverlayColourProvider colourProvider)
{
Anchor = Anchor.TopRight;
Origin = Anchor.TopRight;
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
InternalChild = panel = new Container
InternalChild = panel = new CarouselPanelPiece(0)
{
RelativeSizeAxes = Axes.Both,
CornerRadius = corner_radius,
Masking = true,
X = corner_radius,
Icon = chevronIcon = new SpriteIcon
{
AlwaysPresent = true,
Icon = FontAwesome.Solid.ChevronDown,
Size = new Vector2(12),
Margin = new MarginPadding { Horizontal = 5f },
X = 2f,
Colour = colourProvider.Background3,
},
Background = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Dark1,
},
AccentColour = colourProvider.Highlight1,
Children = new Drawable[]
{
new Container
titleText = new OsuSpriteText
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = 10f },
Child = new Container
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
X = 10f,
},
new CircularContainer
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Size = new Vector2(50f, 14f),
Margin = new MarginPadding { Right = 20f },
Masking = true,
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
CornerRadius = corner_radius,
Masking = true,
Children = new Drawable[]
new Box
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background6,
},
}
}
},
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background3,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = 10f },
Child = new Container
{
RelativeSizeAxes = Axes.Both,
CornerRadius = corner_radius,
Masking = true,
Children = new Drawable[]
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.7f),
},
new OsuSpriteText
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background5,
},
titleText = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
X = 10f,
},
new CircularContainer
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Size = new Vector2(50f, 14f),
Margin = new MarginPadding { Right = 30f },
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.7f),
},
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold),
// TODO: requires Carousel/CarouselItem-side implementation
Text = "43",
UseFullGlyphHeight = false,
}
},
},
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold),
// TODO: requires Carousel/CarouselItem-side implementation
Text = "43",
UseFullGlyphHeight = false,
}
}
},
activationFlash = new Box
{
Colour = Color4.White,
Blending = BlendingParameters.Additive,
Alpha = 0,
RelativeSizeAxes = Axes.Both,
},
hoverLayer = new Box
{
Colour = colours.Blue.Opacity(0.1f),
Alpha = 0,
Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
},
new HoverSounds(),
},
}
}
};
}
@ -163,17 +109,17 @@ namespace osu.Game.Screens.SelectV2
{
base.LoadComplete();
Expanded.BindValueChanged(_ => updateExpandedDisplay(), true);
KeyboardSelected.BindValueChanged(_ => updateKeyboardSelectedDisplay(), true);
Expanded.BindValueChanged(_ => onExpanded(), true);
KeyboardSelected.BindValueChanged(k => panel.KeyboardActive.Value = k.NewValue, true);
}
private void updateExpandedDisplay()
private void onExpanded()
{
updatePanelPosition();
panel.Active.Value = Expanded.Value;
panel.Flash();
// todo: figma shares no extra visual feedback on this.
activationFlash.FadeTo(0.2f).FadeTo(0f, 500, Easing.OutQuint);
chevronIcon.ResizeWidthTo(Expanded.Value ? 12f : 0f, duration, Easing.OutQuint);
chevronIcon.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint);
}
protected override void PrepareForUse()
@ -186,6 +132,7 @@ namespace osu.Game.Screens.SelectV2
titleText.Text = group.Title;
FinishTransforms(true);
this.FadeInFromZero(500, Easing.OutQuint);
}
@ -197,47 +144,6 @@ namespace osu.Game.Screens.SelectV2
return true;
}
private void updateKeyboardSelectedDisplay()
{
updatePanelPosition();
updateHover();
}
private void updatePanelPosition()
{
float x = glow_offset + selected_x_offset + preselected_x_offset;
if (Expanded.Value)
x -= selected_x_offset;
if (KeyboardSelected.Value)
x -= preselected_x_offset;
this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint);
}
private void updateHover()
{
bool hovered = IsHovered || KeyboardSelected.Value;
if (hovered)
hoverLayer.FadeIn(100, Easing.OutQuint);
else
hoverLayer.FadeOut(1000, Easing.OutQuint);
}
protected override bool OnHover(HoverEvent e)
{
updateHover();
return true;
}
protected override void OnHoverLost(HoverLostEvent e)
{
updateHover();
base.OnHoverLost(e);
}
#region ICarouselPanel
public CarouselItem? Item { get; set; }
@ -249,7 +155,7 @@ namespace osu.Game.Screens.SelectV2
public void Activated()
{
// sets should never be activated.
// groups should never be activated.
throw new InvalidOperationException();
}

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
@ -26,10 +27,6 @@ namespace osu.Game.Screens.SelectV2
{
public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT;
private const float glow_offset = 10f; // extra space for the edge effect to not be cutoff by the right edge of the carousel.
private const float preselected_x_offset = 25f;
private const float expanded_x_offset = 50f;
private const float duration = 500;
[Resolved]
@ -41,20 +38,20 @@ namespace osu.Game.Screens.SelectV2
[Resolved]
private OverlayColourProvider colourProvider { get; set; } = null!;
private Box activationFlash = null!;
private Box outerLayer = null!;
private CarouselPanelPiece panel = null!;
private Drawable chevronIcon = null!;
private Box contentBackground = null!;
private StarRatingDisplay starRatingDisplay = null!;
private StarCounter starCounter = null!;
private Box hoverLayer = null!;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos)
{
var inputRectangle = DrawRectangle;
var inputRectangle = panel.TopLevelContent.DrawRectangle;
// Cover a gap introduced by the spacing between a GroupPanel and a BeatmapPanel either below/above it.
// Cover a gap introduced by the spacing between a GroupPanel and other panel types either below/above it.
inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f });
return inputRectangle.Contains(ToLocalSpace(screenSpacePos));
return inputRectangle.Contains(panel.TopLevelContent.ToLocalSpace(screenSpacePos));
}
[BackgroundDependencyLoader]
@ -65,118 +62,71 @@ namespace osu.Game.Screens.SelectV2
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
InternalChild = new Container
InternalChild = panel = new CarouselPanelPiece(0)
{
RelativeSizeAxes = Axes.Both,
CornerRadius = 10f,
Masking = true,
Icon = chevronIcon = new SpriteIcon
{
AlwaysPresent = true,
Icon = FontAwesome.Solid.ChevronDown,
Size = new Vector2(12),
Margin = new MarginPadding { Horizontal = 5f },
X = 2f,
},
Background = contentBackground = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Dark1,
},
AccentColour = colourProvider.Highlight1,
Children = new Drawable[]
{
new Container
new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = 10f },
Child = new Container
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(10f, 0f),
Margin = new MarginPadding { Left = 10f },
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
CornerRadius = 10f,
Masking = true,
Children = new Drawable[]
starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small)
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background6,
},
}
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
starCounter = new StarCounter
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Scale = new Vector2(8f / 20f),
},
}
},
outerLayer = new Box
new CircularContainer
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background3,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = 10f },
Child = new Container
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Size = new Vector2(50f, 14f),
Margin = new MarginPadding { Right = 20f },
Masking = true,
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
CornerRadius = 10f,
Masking = true,
Children = new Drawable[]
new Box
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.2f),
},
new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(10f, 0f),
Margin = new MarginPadding { Left = 10f },
Children = new Drawable[]
{
starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
starCounter = new StarCounter
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Scale = new Vector2(8f / 20f),
},
}
},
new CircularContainer
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Size = new Vector2(50f, 14f),
Margin = new MarginPadding { Right = 30f },
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.7f),
},
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold),
// TODO: requires Carousel/CarouselItem-side implementation
Text = "43",
UseFullGlyphHeight = false,
}
},
},
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.7f),
},
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold),
// TODO: requires Carousel/CarouselItem-side implementation
Text = "43",
UseFullGlyphHeight = false,
}
}
},
activationFlash = new Box
{
Colour = Color4.White,
Blending = BlendingParameters.Additive,
Alpha = 0,
RelativeSizeAxes = Axes.Both,
},
hoverLayer = new Box
{
Colour = colours.Blue.Opacity(0.1f),
Alpha = 0,
Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
},
new HoverSounds(),
},
}
}
};
}
@ -185,17 +135,17 @@ namespace osu.Game.Screens.SelectV2
{
base.LoadComplete();
Expanded.BindValueChanged(_ => updateExpandedDisplay(), true);
KeyboardSelected.BindValueChanged(_ => updateKeyboardSelectedDisplay(), true);
Expanded.BindValueChanged(_ => onExpanded(), true);
KeyboardSelected.BindValueChanged(k => panel.KeyboardActive.Value = k.NewValue, true);
}
private void updateExpandedDisplay()
private void onExpanded()
{
updatePanelPosition();
panel.Active.Value = Expanded.Value;
panel.Flash();
// todo: figma shares no extra visual feedback on this.
activationFlash.FadeTo(0.2f).FadeTo(0f, 500, Easing.OutQuint);
chevronIcon.ResizeWidthTo(Expanded.Value ? 12f : 0f, duration, Easing.OutQuint);
chevronIcon.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint);
}
protected override void PrepareForUse()
@ -209,12 +159,15 @@ namespace osu.Game.Screens.SelectV2
Color4 colour = group.StarNumber >= 9 ? OsuColour.Gray(0.2f) : colours.ForStarDifficulty(group.StarNumber);
Color4 contentColour = group.StarNumber >= 7 ? colours.Orange1 : colourProvider.Background5;
outerLayer.Colour = colour;
starCounter.Colour = contentColour;
panel.AccentColour = colour;
contentBackground.Colour = colour.Darken(0.3f);
starRatingDisplay.Current.Value = new StarDifficulty(group.StarNumber, 0);
starCounter.Current = group.StarNumber;
chevronIcon.Colour = contentColour;
starCounter.Colour = contentColour;
this.FadeInFromZero(500, Easing.OutQuint);
}
@ -226,47 +179,6 @@ namespace osu.Game.Screens.SelectV2
return true;
}
private void updateKeyboardSelectedDisplay()
{
updatePanelPosition();
updateHover();
}
private void updatePanelPosition()
{
float x = glow_offset + expanded_x_offset + preselected_x_offset;
if (Expanded.Value)
x -= expanded_x_offset;
if (KeyboardSelected.Value)
x -= preselected_x_offset;
this.TransformTo(nameof(Padding), new MarginPadding { Left = x }, duration, Easing.OutQuint);
}
private void updateHover()
{
bool hovered = IsHovered || KeyboardSelected.Value;
if (hovered)
hoverLayer.FadeIn(100, Easing.OutQuint);
else
hoverLayer.FadeOut(1000, Easing.OutQuint);
}
protected override bool OnHover(HoverEvent e)
{
updateHover();
return true;
}
protected override void OnHoverLost(HoverLostEvent e)
{
updateHover();
base.OnHoverLost(e);
}
#region ICarouselPanel
public CarouselItem? Item { get; set; }