1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-26 13:50:33 +08:00

Merge pull request #32757 from smoogipoo/playlist-completion-icon

Add display to show completed playlist items
This commit is contained in:
Dean Herbert
2025-04-16 19:41:12 +09:00
committed by GitHub
Unverified
6 changed files with 162 additions and 3 deletions
@@ -105,6 +105,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("no item selected", () => playlist.SelectedItem.Value == null);
}
[Test]
public void TestMarkCompleted()
{
createPlaylist();
AddStep("mark some items as complete", () =>
{
playlist.Items[0].MarkCompleted();
playlist.Items[2].MarkCompleted();
playlist.Items[3].MarkCompleted();
playlist.Items[5].MarkCompleted();
});
}
[Test]
public void TestSelectable()
{
@@ -0,0 +1,19 @@
// 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.Localisation;
namespace osu.Game.Localisation
{
public static class DrawableRoomPlaylistItemStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.DrawableRoomPlaylistItem";
/// <summary>
/// "You have completed this beatmap"
/// </summary>
public static LocalisableString CompletedTooltip => new TranslatableString(getKey(@"completed_tooltip"), @"You have completed this beatmap");
private static string getKey(string key) => $@"{prefix}:{key}";
}
}
@@ -10,10 +10,22 @@ namespace osu.Game.Online.Rooms
/// </summary>
public class ItemAttemptsCount
{
/// <summary>
/// The playlist item this object describes.
/// </summary>
[JsonProperty("id")]
public int PlaylistItemID { get; set; }
/// <summary>
/// The number of times the user attempted the playlist item.
/// </summary>
[JsonProperty("attempts")]
public int Attempts { get; set; }
/// <summary>
/// Whether the user has a passing score on the playlist item.
/// </summary>
[JsonProperty("passed")]
public bool Passed { get; set; }
}
}
+7
View File
@@ -85,6 +85,11 @@ namespace osu.Game.Online.Rooms
private readonly Bindable<bool> valid = new BindableBool(true);
[JsonIgnore]
public IBindable<bool> Completed => completed;
private readonly Bindable<bool> completed = new BindableBool(false);
[JsonConstructor]
private PlaylistItem()
: this(new APIBeatmap())
@@ -118,6 +123,8 @@ namespace osu.Game.Online.Rooms
public void MarkInvalid() => valid.Value = false;
public void MarkCompleted() => completed.Value = true;
#region Newtonsoft.Json implicit ShouldSerialize() methods
// The properties in this region are used implicitly by Newtonsoft.Json to not serialise certain fields in some cases.
@@ -31,13 +31,13 @@ using osu.Game.Online.Chat;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play.HUD;
using osu.Game.Users.Drawables;
using osuTK;
using osuTK.Graphics;
using osu.Game.Localisation;
namespace osu.Game.Screens.OnlinePlay
{
@@ -76,6 +76,7 @@ namespace osu.Game.Screens.OnlinePlay
private readonly DelayedLoadWrapper onScreenLoader;
private readonly IBindable<bool> valid = new Bindable<bool>();
private readonly IBindable<bool> completed = new Bindable<bool>();
private IBeatmapInfo? beatmap;
private IRulesetInfo? ruleset;
@@ -128,6 +129,7 @@ namespace osu.Game.Screens.OnlinePlay
Item = item;
valid.BindTo(item.Valid);
completed.BindTo(item.Completed);
}
[BackgroundDependencyLoader]
@@ -525,9 +527,27 @@ namespace osu.Game.Screens.OnlinePlay
private IEnumerable<Drawable> createButtons() => new[]
{
beatmap == null ? Empty() : new PlaylistDownloadButton(beatmap),
new CompletionIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Visible = { BindTarget = completed }
},
beatmap == null
? Empty().With(d =>
{
d.Anchor = Anchor.Centre;
d.Origin = Anchor.Centre;
})
: new PlaylistDownloadButton(beatmap)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
showResultsButton = new GrayButton(FontAwesome.Solid.ChartPie)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(30, 30),
Action = () => RequestResults?.Invoke(Item),
Alpha = AllowShowingResults ? 1 : 0,
@@ -535,13 +555,17 @@ namespace osu.Game.Screens.OnlinePlay
},
editButton = new PlaylistEditButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(30, 30),
Alpha = AllowEditing ? 1 : 0,
Action = () => RequestEdit?.Invoke(Item),
TooltipText = CommonStrings.ButtonsEdit
TooltipText = Resources.Localisation.Web.CommonStrings.ButtonsEdit
},
removeButton = new PlaylistRemoveButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(30, 30),
Alpha = AllowDeletion ? 1 : 0,
Action = () => RequestDeletion?.Invoke(Item),
@@ -768,5 +792,64 @@ namespace osu.Game.Screens.OnlinePlay
this.allowInteraction = allowInteraction;
}
}
private partial class CompletionIcon : CompositeDrawable, IHasTooltip
{
public readonly BindableBool Visible = new BindableBool();
[Resolved]
private OsuColour colours { get; set; } = null!;
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(16),
Masking = true,
Colour = colours.Lime0,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both
},
new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Scale = new Vector2(0.5f),
Colour = OsuColour.Gray(0.5f),
Icon = FontAwesome.Solid.Check
}
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
Visible.BindValueChanged(onVisibleChanged, true);
}
private void onVisibleChanged(ValueChangedEvent<bool> visible)
{
if (visible.NewValue)
{
Size = new Vector2(16);
Alpha = 1;
}
else
{
Size = Vector2.Zero;
Alpha = 0;
}
}
public LocalisableString TooltipText => DrawableRoomPlaylistItemStrings.CompletedTooltip;
}
}
}
@@ -465,6 +465,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
});
updateSetupState();
updateUserScore();
updateGameplayState();
}
@@ -480,6 +481,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
case nameof(Room.RoomID):
updateSetupState();
break;
case nameof(Room.UserScore):
updateUserScore();
break;
}
}
@@ -507,12 +512,32 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
progressSection.Alpha = room.MaxAttempts != null ? 1 : 0;
drawablePlaylist.Items.ReplaceRange(0, drawablePlaylist.Items.Count, room.Playlist);
updateUserScore();
// Select an initial item for the user to help them get into a playable state quicker.
SelectedItem.Value = room.Playlist.FirstOrDefault();
});
}
}
/// <summary>
/// Responds to changes in <see cref="Room.UserScore"/> to mark playlist items as completed.
/// </summary>
private void updateUserScore()
{
if (room.UserScore == null)
return;
if (drawablePlaylist.Items.Count == 0)
return;
foreach (var item in room.UserScore.PlaylistItemAttempts)
{
if (item.Passed)
drawablePlaylist.Items.Single(i => i.ID == item.PlaylistItemID).MarkCompleted();
}
}
/// <summary>
/// Adjusts the rate at which the <see cref="Room"/> is updated.
/// </summary>