mirror of
https://github.com/ppy/osu.git
synced 2025-03-11 05:17:25 +08:00
Implement beatmap set header design
This commit is contained in:
parent
a5fa04e4d6
commit
206b5c93c0
@ -0,0 +1,90 @@
|
||||
// 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 System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual.UserInterface;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneBeatmapCarouselSetPanel : ThemeComparisonTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
private BeatmapSetInfo beatmapSet = null!;
|
||||
|
||||
public TestSceneBeatmapCarouselSetPanel()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDisplay()
|
||||
{
|
||||
AddStep("set beatmap", () =>
|
||||
{
|
||||
beatmapSet = beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => b.OnlineID == 241526)
|
||||
?? beatmaps.GetAllUsableBeatmapSets().FirstOrDefault(b => !b.Protected)
|
||||
?? TestResources.CreateTestBeatmapSetInfo();
|
||||
CreateThemedContent(OverlayColourScheme.Aquamarine);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRandomBeatmap()
|
||||
{
|
||||
AddStep("random beatmap", () =>
|
||||
{
|
||||
beatmapSet = beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()).First();
|
||||
CreateThemedContent(OverlayColourScheme.Aquamarine);
|
||||
});
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent()
|
||||
{
|
||||
return new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BeatmapSetPanel
|
||||
{
|
||||
Item = new CarouselItem(beatmapSet)
|
||||
},
|
||||
new BeatmapSetPanel
|
||||
{
|
||||
Item = new CarouselItem(beatmapSet),
|
||||
KeyboardSelected = { Value = true }
|
||||
},
|
||||
new BeatmapSetPanel
|
||||
{
|
||||
Item = new CarouselItem(beatmapSet),
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
new BeatmapSetPanel
|
||||
{
|
||||
Item = new CarouselItem(beatmapSet),
|
||||
KeyboardSelected = { Value = true },
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
// 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 System;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneUpdateBeatmapSetButtonV2 : OsuTestScene
|
||||
{
|
||||
private UpdateBeatmapSetButtonV2 button = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
Child = button = new UpdateBeatmapSetButtonV2
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestNullBeatmap()
|
||||
{
|
||||
AddStep("null beatmap", () => button.BeatmapSet = null);
|
||||
AddAssert("button invisible", () => button.Alpha == 0f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUpdatedBeatmap()
|
||||
{
|
||||
AddStep("updated beatmap", () => button.BeatmapSet = new BeatmapSetInfo
|
||||
{
|
||||
Beatmaps = { new BeatmapInfo() }
|
||||
});
|
||||
AddAssert("button invisible", () => button.Alpha == 0f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNonUpdatedBeatmap()
|
||||
{
|
||||
AddStep("non-updated beatmap", () => button.BeatmapSet = new BeatmapSetInfo
|
||||
{
|
||||
Beatmaps =
|
||||
{
|
||||
new BeatmapInfo
|
||||
{
|
||||
MD5Hash = "test",
|
||||
OnlineMD5Hash = "online",
|
||||
LastOnlineUpdate = DateTimeOffset.Now,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddAssert("button visible", () => button.Alpha == 1f);
|
||||
}
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
dotSize = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateDotDimensions();
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,13 +42,27 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
dotSpacing = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateDotDimensions();
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private IBeatmapSetInfo? beatmapSet;
|
||||
|
||||
public IBeatmapSetInfo? BeatmapSet
|
||||
{
|
||||
get => beatmapSet;
|
||||
set
|
||||
{
|
||||
beatmapSet = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly FillFlowContainer<RulesetDifficultyGroup> flow;
|
||||
|
||||
public DifficultySpectrumDisplay(IBeatmapSetInfo beatmapSet)
|
||||
public DifficultySpectrumDisplay(IBeatmapSetInfo? beatmapSet = null)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
@ -59,25 +73,31 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Direction = FillDirection.Horizontal,
|
||||
};
|
||||
|
||||
// matching web: https://github.com/ppy/osu-web/blob/d06d8c5e735eb1f48799b1654b528e9a7afb0a35/resources/assets/lib/beatmapset-panel.tsx#L127
|
||||
bool collapsed = beatmapSet.Beatmaps.Count() > 12;
|
||||
|
||||
foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset).OrderBy(group => group.Key))
|
||||
flow.Add(new RulesetDifficultyGroup(rulesetGrouping.Key.OnlineID, rulesetGrouping, collapsed));
|
||||
BeatmapSet = beatmapSet;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
updateDotDimensions();
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
private void updateDotDimensions()
|
||||
private void updateDisplay()
|
||||
{
|
||||
foreach (var group in flow)
|
||||
flow.Clear();
|
||||
|
||||
if (beatmapSet == null)
|
||||
return;
|
||||
|
||||
// matching web: https://github.com/ppy/osu-web/blob/d06d8c5e735eb1f48799b1654b528e9a7afb0a35/resources/assets/lib/beatmapset-panel.tsx#L127
|
||||
bool collapsed = beatmapSet.Beatmaps.Count() > 12;
|
||||
|
||||
foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset).OrderBy(group => group.Key))
|
||||
{
|
||||
group.DotSize = DotSize;
|
||||
group.DotSpacing = DotSpacing;
|
||||
flow.Add(new RulesetDifficultyGroup(rulesetGrouping.Key.OnlineID, rulesetGrouping, collapsed, dotSize)
|
||||
{
|
||||
Spacing = new Vector2(DotSpacing, 0f),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,26 +106,14 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
private readonly int rulesetId;
|
||||
private readonly IEnumerable<IBeatmapInfo> beatmapInfos;
|
||||
private readonly bool collapsed;
|
||||
private readonly Vector2 dotSize;
|
||||
|
||||
public RulesetDifficultyGroup(int rulesetId, IEnumerable<IBeatmapInfo> beatmapInfos, bool collapsed)
|
||||
public RulesetDifficultyGroup(int rulesetId, IEnumerable<IBeatmapInfo> beatmapInfos, bool collapsed, Vector2 dotSize)
|
||||
{
|
||||
this.rulesetId = rulesetId;
|
||||
this.beatmapInfos = beatmapInfos;
|
||||
this.collapsed = collapsed;
|
||||
}
|
||||
|
||||
public Vector2 DotSize
|
||||
{
|
||||
set
|
||||
{
|
||||
foreach (var dot in Children.OfType<DifficultyDot>())
|
||||
dot.Size = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float DotSpacing
|
||||
{
|
||||
set => Spacing = new Vector2(value, 0);
|
||||
this.dotSize = dotSize;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -125,7 +133,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
if (!collapsed)
|
||||
{
|
||||
foreach (var beatmapInfo in beatmapInfos.OrderBy(bi => bi.StarRating))
|
||||
Add(new DifficultyDot(beatmapInfo.StarRating));
|
||||
Add(new DifficultyDot(beatmapInfo.StarRating, dotSize));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -145,9 +153,10 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
private readonly double starDifficulty;
|
||||
|
||||
public DifficultyDot(double starDifficulty)
|
||||
public DifficultyDot(double starDifficulty, Vector2 dotSize)
|
||||
{
|
||||
this.starDifficulty = starDifficulty;
|
||||
Size = dotSize;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -3,15 +3,24 @@
|
||||
|
||||
using System;
|
||||
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;
|
||||
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;
|
||||
|
||||
@ -19,63 +28,182 @@ namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class BeatmapSetPanel : PoolableDrawable, ICarouselPanel
|
||||
{
|
||||
public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 2;
|
||||
public const float HEIGHT = CarouselItem.DEFAULT_HEIGHT * 1.6f;
|
||||
|
||||
private const float arrow_container_width = 20;
|
||||
private const float corner_radius = 10;
|
||||
|
||||
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 set_x_offset = 20f; // constant X offset for beatmap set panels specifically.
|
||||
private const float preselected_x_offset = 25f;
|
||||
private const float expanded_x_offset = 50f;
|
||||
|
||||
private const float duration = 500;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapCarousel carousel { get; set; } = null!;
|
||||
private BeatmapCarousel? carousel { get; set; }
|
||||
|
||||
private OsuSpriteText text = null!;
|
||||
private Box box = null!;
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||
{
|
||||
var inputRectangle = DrawRectangle;
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
// Cover a gap introduced by the spacing between a BeatmapSetPanel and a BeatmapPanel either below/above it.
|
||||
inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING / 2f });
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
return inputRectangle.Contains(ToLocalSpace(screenSpacePos));
|
||||
}
|
||||
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!;
|
||||
|
||||
private OsuSpriteText titleText = null!;
|
||||
private OsuSpriteText artistText = null!;
|
||||
private UpdateBeatmapSetButtonV2 updateButton = null!;
|
||||
private BeatmapSetOnlineStatusPill statusPill = null!;
|
||||
private DifficultySpectrumDisplay difficultiesDisplay = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size = new Vector2(500, HEIGHT);
|
||||
Masking = true;
|
||||
Anchor = Anchor.TopRight;
|
||||
Origin = Anchor.TopRight;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = HEIGHT;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
InternalChild = panel = new Container
|
||||
{
|
||||
box = new Box
|
||||
{
|
||||
Colour = Color4.Yellow.Darken(5),
|
||||
Alpha = 0.8f,
|
||||
Masking = true,
|
||||
CornerRadius = corner_radius,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Padding = new MarginPadding(5),
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 10,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BufferedContainer
|
||||
{
|
||||
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,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Icon = FontAwesome.Solid.ChevronRight,
|
||||
Size = new Vector2(12),
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
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[]
|
||||
{
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
hoverLayer = new Box
|
||||
{
|
||||
Colour = colours.Blue.Opacity(0.1f),
|
||||
Alpha = 0,
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new HoverSounds(),
|
||||
}
|
||||
};
|
||||
|
||||
Expanded.BindValueChanged(value =>
|
||||
{
|
||||
box.FadeColour(value.NewValue ? Color4.Yellow.Darken(2) : Color4.Yellow.Darken(5), 500, Easing.OutQuint);
|
||||
});
|
||||
|
||||
KeyboardSelected.BindValueChanged(value =>
|
||||
{
|
||||
if (value.NewValue)
|
||||
{
|
||||
BorderThickness = 5;
|
||||
BorderColour = Color4.Pink;
|
||||
}
|
||||
else
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||
{
|
||||
BorderThickness = 0;
|
||||
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);
|
||||
}
|
||||
|
||||
protected override void PrepareForUse()
|
||||
@ -84,16 +212,101 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
Debug.Assert(Item != null);
|
||||
|
||||
var beatmapSetInfo = (BeatmapSetInfo)Item.Model;
|
||||
var beatmapSet = (BeatmapSetInfo)Item.Model;
|
||||
|
||||
text.Text = $"{beatmapSetInfo.Metadata}";
|
||||
// Choice of background image matches BSS implementation (always uses the lowest `beatmap_id` from the set).
|
||||
background.Beatmap = beatmaps.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID));
|
||||
|
||||
this.FadeInFromZero(500, Easing.OutQuint);
|
||||
titleText.Text = new RomanisableString(beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title);
|
||||
artistText.Text = new RomanisableString(beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist);
|
||||
updateButton.BeatmapSet = beatmapSet;
|
||||
statusPill.Status = beatmapSet.Status;
|
||||
difficultiesDisplay.BeatmapSet = beatmapSet;
|
||||
|
||||
updateExpandedDisplay();
|
||||
FinishTransforms(true);
|
||||
|
||||
this.FadeInFromZero(duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void FreeAfterUse()
|
||||
{
|
||||
base.FreeAfterUse();
|
||||
|
||||
background.Beatmap = null;
|
||||
updateButton.BeatmapSet = null;
|
||||
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 = glow_offset + 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)
|
||||
carousel.CurrentSelection = Item!.Model;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
108
osu.Game/Screens/SelectV2/BeatmapSetPanelBackground.cs
Normal file
108
osu.Game/Screens/SelectV2/BeatmapSetPanelBackground.cs
Normal file
@ -0,0 +1,108 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class BeatmapSetPanelBackground : ModelBackedDrawable<WorkingBeatmap>
|
||||
{
|
||||
protected override bool TransformImmediately => true;
|
||||
|
||||
public WorkingBeatmap? Beatmap
|
||||
{
|
||||
get => Model;
|
||||
set => Model = value;
|
||||
}
|
||||
|
||||
protected override Drawable CreateDrawable(WorkingBeatmap? model) => new BackgroundSprite(model);
|
||||
|
||||
private partial class BackgroundSprite : CompositeDrawable
|
||||
{
|
||||
private readonly WorkingBeatmap? working;
|
||||
|
||||
public BackgroundSprite(WorkingBeatmap? working)
|
||||
{
|
||||
this.working = working;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
var texture = working?.GetPanelBackground();
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Sprite
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
FillMode = FillMode.Fill,
|
||||
Texture = texture,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Depth = -1,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
// This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle
|
||||
Shear = new Vector2(0.8f, 0),
|
||||
Alpha = 0.5f,
|
||||
Children = new[]
|
||||
{
|
||||
// The left half with no gradient applied
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
Width = 0.4f,
|
||||
},
|
||||
// Piecewise-linear gradient with 3 segments to make it appear smoother
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)),
|
||||
Width = 0.05f,
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)),
|
||||
Width = 0.2f,
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)),
|
||||
Width = 0.05f,
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
InternalChild = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background6,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
108
osu.Game/Screens/SelectV2/TopLocalRankV2.cs
Normal file
108
osu.Game/Screens/SelectV2/TopLocalRankV2.cs
Normal file
@ -0,0 +1,108 @@
|
||||
// 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 System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
using osuTK;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class TopLocalRankV2 : CompositeDrawable
|
||||
{
|
||||
private BeatmapInfo? beatmap;
|
||||
|
||||
public BeatmapInfo? Beatmap
|
||||
{
|
||||
get => beatmap;
|
||||
set
|
||||
{
|
||||
beatmap = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateSubscription();
|
||||
}
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RealmAccess realm { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
private IDisposable? scoreSubscription;
|
||||
|
||||
private readonly UpdateableRank updateable;
|
||||
|
||||
public ScoreRank? DisplayedRank => updateable.Rank;
|
||||
|
||||
public TopLocalRankV2(BeatmapInfo? beatmap = null)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
InternalChild = updateable = new UpdateableRank
|
||||
{
|
||||
Size = new Vector2(40, 20),
|
||||
Alpha = 0,
|
||||
};
|
||||
|
||||
Beatmap = beatmap;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
ruleset.BindValueChanged(_ => updateSubscription(), true);
|
||||
}
|
||||
|
||||
private void updateSubscription()
|
||||
{
|
||||
scoreSubscription?.Dispose();
|
||||
|
||||
if (beatmap == null)
|
||||
return;
|
||||
|
||||
scoreSubscription = realm.RegisterForNotifications(r =>
|
||||
r.All<ScoreInfo>()
|
||||
.Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0"
|
||||
+ $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1"
|
||||
+ $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.Hash)} == {nameof(ScoreInfo.BeatmapHash)}"
|
||||
+ $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2"
|
||||
+ $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmap.ID, ruleset.Value.ShortName),
|
||||
localScoresChanged);
|
||||
}
|
||||
|
||||
private void localScoresChanged(IRealmCollection<ScoreInfo> sender, ChangeSet? changes)
|
||||
{
|
||||
// This subscription may fire from changes to linked beatmaps, which we don't care about.
|
||||
// It's currently not possible for a score to be modified after insertion, so we can safely ignore callbacks with only modifications.
|
||||
if (changes?.HasCollectionChanges() == false)
|
||||
return;
|
||||
|
||||
ScoreInfo? topScore = sender.MaxBy(info => (info.TotalScore, -info.Date.UtcDateTime.Ticks));
|
||||
updateable.Rank = topScore?.Rank;
|
||||
updateable.Alpha = topScore != null ? 1 : 0;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
scoreSubscription?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
198
osu.Game/Screens/SelectV2/UpdateBeatmapSetButtonV2.cs
Normal file
198
osu.Game/Screens/SelectV2/UpdateBeatmapSetButtonV2.cs
Normal file
@ -0,0 +1,198 @@
|
||||
// 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 System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Select.Carousel;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class UpdateBeatmapSetButtonV2 : OsuAnimatedButton
|
||||
{
|
||||
private BeatmapSetInfo? beatmapSet;
|
||||
|
||||
public BeatmapSetInfo? BeatmapSet
|
||||
{
|
||||
get => beatmapSet;
|
||||
set
|
||||
{
|
||||
beatmapSet = value;
|
||||
|
||||
if (IsLoaded)
|
||||
beatmapChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private SpriteIcon icon = null!;
|
||||
private Box progressFill = null!;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapModelDownloader beatmapDownloader { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private LoginOverlay? loginOverlay { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IDialogOverlay? dialogOverlay { get; set; }
|
||||
|
||||
public UpdateBeatmapSetButtonV2()
|
||||
{
|
||||
Size = new Vector2(75f, 22f);
|
||||
}
|
||||
|
||||
private Bindable<bool> preferNoVideo = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
const float icon_size = 14;
|
||||
|
||||
preferNoVideo = config.GetBindable<bool>(OsuSetting.PreferNoVideo);
|
||||
|
||||
Content.Anchor = Anchor.Centre;
|
||||
Content.Origin = Anchor.Centre;
|
||||
Content.Shear = new Vector2(OsuGame.SHEAR, 0);
|
||||
|
||||
Content.AddRange(new Drawable[]
|
||||
{
|
||||
progressFill = new Box
|
||||
{
|
||||
Colour = Color4.White,
|
||||
Alpha = 0.2f,
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 0,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Padding = new MarginPadding { Horizontal = 5, Vertical = 3 },
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(4),
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Size = new Vector2(icon_size),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
icon = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = FontAwesome.Solid.SyncAlt,
|
||||
Size = new Vector2(icon_size),
|
||||
},
|
||||
}
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Font = OsuFont.Default.With(weight: FontWeight.Bold),
|
||||
Text = "Update",
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Action = performUpdate;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
beatmapChanged();
|
||||
}
|
||||
|
||||
private void beatmapChanged()
|
||||
{
|
||||
Alpha = beatmapSet?.AllBeatmapsUpToDate == false ? 1 : 0;
|
||||
icon.Spin(4000, RotationDirection.Clockwise);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
icon.Spin(400, RotationDirection.Clockwise);
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
icon.Spin(4000, RotationDirection.Clockwise);
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
private bool updateConfirmed;
|
||||
|
||||
private void performUpdate()
|
||||
{
|
||||
Debug.Assert(beatmapSet != null);
|
||||
|
||||
if (!api.IsLoggedIn)
|
||||
{
|
||||
loginOverlay?.Show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (dialogOverlay != null && beatmapSet.Status == BeatmapOnlineStatus.LocallyModified && !updateConfirmed)
|
||||
{
|
||||
dialogOverlay.Push(new UpdateLocalConfirmationDialog(() =>
|
||||
{
|
||||
updateConfirmed = true;
|
||||
performUpdate();
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
updateConfirmed = false;
|
||||
|
||||
beatmapDownloader.DownloadAsUpdate(beatmapSet, preferNoVideo.Value);
|
||||
attachExistingDownload();
|
||||
}
|
||||
|
||||
private void attachExistingDownload()
|
||||
{
|
||||
Debug.Assert(beatmapSet != null);
|
||||
var download = beatmapDownloader.GetExistingDownload(beatmapSet);
|
||||
|
||||
if (download != null)
|
||||
{
|
||||
Enabled.Value = false;
|
||||
TooltipText = string.Empty;
|
||||
|
||||
download.DownloadProgressed += progress => progressFill.ResizeWidthTo(progress, 100, Easing.OutQuint);
|
||||
download.Failure += _ => attachExistingDownload();
|
||||
}
|
||||
else
|
||||
{
|
||||
Enabled.Value = true;
|
||||
TooltipText = "Update beatmap with online changes";
|
||||
|
||||
progressFill.ResizeWidthTo(0, 100, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user