mirror of
https://github.com/ppy/osu.git
synced 2026-05-19 23:40:44 +08:00
Merge branch 'master' into qp-fix-nullref
This commit is contained in:
@@ -253,7 +253,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
var hitObjects = selectedMovableObjects;
|
||||
|
||||
Quad quad = GeometryUtils.GetSurroundingQuad(hitObjects);
|
||||
Quad quad = GeometryUtils.GetSurroundingQuad(hitObjects, true);
|
||||
|
||||
Vector2 delta = Vector2.Zero;
|
||||
|
||||
|
||||
@@ -81,12 +81,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
changeHandler?.BeginChange();
|
||||
|
||||
objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho));
|
||||
OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider
|
||||
? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position))
|
||||
: GeometryUtils.GetSurroundingQuad(objectsInScale.Keys);
|
||||
originalConvexHull = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider2
|
||||
? GeometryUtils.GetConvexHull(slider2.Path.ControlPoints.Select(p => slider2.Position + p.Position))
|
||||
: GeometryUtils.GetConvexHull(objectsInScale.Keys);
|
||||
OriginalSurroundingQuad = GeometryUtils.GetSurroundingQuad(objectsInScale.Keys);
|
||||
originalConvexHull = GeometryUtils.GetConvexHull(objectsInScale.Keys);
|
||||
defaultOrigin = GeometryUtils.MinimumEnclosingCircle(originalConvexHull).Item1;
|
||||
}
|
||||
|
||||
@@ -312,7 +308,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
private void moveSelectionInBounds()
|
||||
{
|
||||
Quad quad = GeometryUtils.GetSurroundingQuad(objectsInScale!.Keys);
|
||||
Quad quad = GeometryUtils.GetSurroundingQuad(objectsInScale!.Keys, true);
|
||||
|
||||
Vector2 delta = Vector2.Zero;
|
||||
|
||||
|
||||
+22
-5
@@ -10,6 +10,7 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect;
|
||||
using osu.Game.Tests.Visual.OnlinePlay;
|
||||
@@ -17,11 +18,11 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneSelectionGrid : OnlinePlayTestScene
|
||||
public partial class TestSceneBeatmapSelectGrid : OnlinePlayTestScene
|
||||
{
|
||||
private MultiplayerPlaylistItem[] items = null!;
|
||||
|
||||
private SelectionGrid grid = null!;
|
||||
private BeatmapSelectGrid grid = null!;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmapManager { get; set; } = null!;
|
||||
@@ -58,7 +59,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("add grid", () => Child = grid = new SelectionGrid
|
||||
AddStep("add grid", () => Child = grid = new BeatmapSelectGrid
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
@@ -82,6 +83,22 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
// test scene is weird.
|
||||
});
|
||||
|
||||
AddStep("add selection 1", () => grid.ChildrenOfType<BeatmapSelectPanel>().First().AddUser(new APIUser
|
||||
{
|
||||
Id = 6411631,
|
||||
Username = "Maarvin",
|
||||
}, isOwnUser: true));
|
||||
AddStep("add selection 2", () => grid.ChildrenOfType<BeatmapSelectPanel>().Skip(5).First().AddUser(new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "peppy",
|
||||
}));
|
||||
AddStep("add selection 3", () => grid.ChildrenOfType<BeatmapSelectPanel>().Skip(10).First().AddUser(new APIUser
|
||||
{
|
||||
Id = 1040328,
|
||||
Username = "smoogipoo",
|
||||
}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -154,7 +171,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
var (candidateItems, _) = pickRandomItems(count);
|
||||
|
||||
grid.TransferCandidatePanelsToRollContainer(candidateItems);
|
||||
grid.Delay(SelectionGrid.ARRANGE_DELAY)
|
||||
grid.Delay(BeatmapSelectGrid.ARRANGE_DELAY)
|
||||
.Schedule(() => grid.ArrangeItemsForRollAnimation());
|
||||
});
|
||||
|
||||
@@ -162,7 +179,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
|
||||
AddStep("display roll order", () =>
|
||||
{
|
||||
var panels = grid.ChildrenOfType<SelectionPanel>().ToArray();
|
||||
var panels = grid.ChildrenOfType<BeatmapSelectPanel>().ToArray();
|
||||
|
||||
for (int i = 0; i < panels.Length; i++)
|
||||
{
|
||||
+3
-3
@@ -12,7 +12,7 @@ using osu.Game.Tests.Visual.Multiplayer;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneSelectionPanel : MultiplayerTestScene
|
||||
public partial class TestSceneBeatmapSelectPanel : MultiplayerTestScene
|
||||
{
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
@@ -20,9 +20,9 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
[Test]
|
||||
public void TestBeatmapPanel()
|
||||
{
|
||||
SelectionPanel? panel = null;
|
||||
BeatmapSelectPanel? panel = null;
|
||||
|
||||
AddStep("add panel", () => Child = panel = new SelectionPanel(new MultiplayerPlaylistItem())
|
||||
AddStep("add panel", () => Child = panel = new BeatmapSelectPanel(new MultiplayerPlaylistItem())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@@ -1,113 +0,0 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneMatchmakingScreenStack : MultiplayerTestScene
|
||||
{
|
||||
private const int user_count = 8;
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("join room", () =>
|
||||
{
|
||||
var room = CreateDefaultRoom(MatchType.Matchmaking);
|
||||
room.Playlist = Enumerable.Range(1, 50).Select(i => new PlaylistItem(new MultiplayerPlaylistItem
|
||||
{
|
||||
ID = i,
|
||||
BeatmapID = i,
|
||||
StarRating = i / 10.0,
|
||||
})).ToArray();
|
||||
|
||||
JoinRoom(room);
|
||||
});
|
||||
|
||||
WaitForJoined();
|
||||
|
||||
AddStep("add carousel", () =>
|
||||
{
|
||||
Child = new ScreenMatchmaking.ScreenStack
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("join users", () =>
|
||||
{
|
||||
var users = Enumerable.Range(1, user_count).Select(i => new MultiplayerRoomUser(i)
|
||||
{
|
||||
User = new APIUser
|
||||
{
|
||||
Username = $"Player {i}"
|
||||
}
|
||||
}).ToArray();
|
||||
|
||||
foreach (var user in users)
|
||||
MultiplayerClient.AddUser(user);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChangeStage()
|
||||
{
|
||||
for (int round = 1; round <= 2; round++)
|
||||
{
|
||||
AddLabel($"Round {round}");
|
||||
|
||||
int r = round;
|
||||
changeStage(MatchmakingStage.RoundWarmupTime, state => state.CurrentRound = r);
|
||||
changeStage(MatchmakingStage.UserBeatmapSelect);
|
||||
changeStage(MatchmakingStage.ServerBeatmapFinalised, state =>
|
||||
{
|
||||
MultiplayerPlaylistItem[] beatmaps = Enumerable.Range(1, 8).Select(i => new MultiplayerPlaylistItem
|
||||
{
|
||||
ID = i,
|
||||
BeatmapID = i,
|
||||
StarRating = i / 10.0,
|
||||
}).ToArray();
|
||||
|
||||
state.CandidateItems = beatmaps.Select(b => b.ID).ToArray();
|
||||
state.CandidateItem = beatmaps[0].ID;
|
||||
}, waitTime: 35);
|
||||
|
||||
changeStage(MatchmakingStage.WaitingForClientsBeatmapDownload);
|
||||
changeStage(MatchmakingStage.GameplayWarmupTime);
|
||||
changeStage(MatchmakingStage.Gameplay);
|
||||
changeStage(MatchmakingStage.ResultsDisplaying);
|
||||
}
|
||||
|
||||
changeStage(MatchmakingStage.Ended, state =>
|
||||
{
|
||||
int localUserId = API.LocalUser.Value.OnlineID;
|
||||
|
||||
state.Users[localUserId].Placement = 1;
|
||||
state.Users[localUserId].Rounds[1].Placement = 1;
|
||||
state.Users[localUserId].Rounds[1].TotalScore = 1;
|
||||
state.Users[localUserId].Rounds[1].Statistics[HitResult.LargeBonus] = 1;
|
||||
});
|
||||
}
|
||||
|
||||
private void changeStage(MatchmakingStage stage, Action<MatchmakingRoomState>? prepare = null, int waitTime = 5)
|
||||
{
|
||||
AddStep($"stage: {stage}", () => MultiplayerClient.MatchmakingChangeStage(stage, prepare).WaitSafely());
|
||||
AddWaitStep("wait", waitTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestScenePlayerPanel : MultiplayerTestScene
|
||||
{
|
||||
private MatchmakingUserPanel panel = null!;
|
||||
private PlayerPanel panel = null!;
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
@@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
AddStep("join room", () => JoinRoom(CreateDefaultRoom(MatchType.Matchmaking)));
|
||||
WaitForJoined();
|
||||
|
||||
AddStep("add panel", () => Child = panel = new MatchmakingUserPanel(new MultiplayerRoomUser(1)
|
||||
AddStep("add panel", () => Child = panel = new PlayerPanel(new MultiplayerRoomUser(1)
|
||||
{
|
||||
User = new APIUser
|
||||
{
|
||||
|
||||
@@ -14,13 +14,14 @@ using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneUserPanelOverlay : MultiplayerTestScene
|
||||
{
|
||||
private UserPanelOverlay list = null!;
|
||||
private PlayerPanelOverlay list = null!;
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
@@ -35,7 +36,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(0.8f),
|
||||
Child = list = new UserPanelOverlay()
|
||||
Child = list = new PlayerPanelOverlay()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -117,10 +118,10 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("two panels displayed", () => this.ChildrenOfType<MatchmakingUserPanel>().Count(), () => Is.EqualTo(2));
|
||||
AddUntilStep("two panels displayed", () => this.ChildrenOfType<UserPanel>().Count(), () => Is.EqualTo(2));
|
||||
|
||||
AddStep("remove a user", () => MultiplayerClient.RemoveUser(new APIUser { Id = 1 }));
|
||||
AddUntilStep("one panel displayed", () => this.ChildrenOfType<MatchmakingUserPanel>().Count(), () => Is.EqualTo(1));
|
||||
AddUntilStep("one panel displayed", () => this.ChildrenOfType<UserPanel>().Count(), () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -9,6 +9,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Visual.UserInterface;
|
||||
using osuTK;
|
||||
@@ -86,6 +87,64 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRanks()
|
||||
{
|
||||
for (int i = -1; i <= 7; i++)
|
||||
{
|
||||
ScoreRank rank = (ScoreRank)i;
|
||||
|
||||
AddStep($"display rank {rank}", () =>
|
||||
{
|
||||
ContentContainer.Child = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new (Type, object)[]
|
||||
{
|
||||
(typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Aquamarine))
|
||||
},
|
||||
Child = new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = 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[]
|
||||
{
|
||||
new PanelGroupRankDisplay
|
||||
{
|
||||
Item = new CarouselItem(new RankDisplayGroupDefinition(rank))
|
||||
},
|
||||
new PanelGroupRankDisplay
|
||||
{
|
||||
Item = new CarouselItem(new RankDisplayGroupDefinition(rank)),
|
||||
KeyboardSelected = { Value = true },
|
||||
},
|
||||
new PanelGroupRankDisplay
|
||||
{
|
||||
Item = new CarouselItem(new RankDisplayGroupDefinition(rank)),
|
||||
Expanded = { Value = true },
|
||||
},
|
||||
new PanelGroupRankDisplay
|
||||
{
|
||||
Item = new CarouselItem(new RankDisplayGroupDefinition(rank)),
|
||||
Expanded = { Value = true },
|
||||
KeyboardSelected = { Value = true },
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent()
|
||||
{
|
||||
return new OsuContextMenuContainer
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
public const float TRANSITION_DURATION = 340;
|
||||
public const float CORNER_RADIUS = 8;
|
||||
|
||||
protected const float WIDTH = 345;
|
||||
public const float WIDTH = 345;
|
||||
|
||||
public IBindable<bool> Expanded { get; }
|
||||
|
||||
@@ -77,25 +77,27 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
|
||||
containingInputManager = GetContainingInputManager();
|
||||
|
||||
Action = () =>
|
||||
{
|
||||
if (containingInputManager?.CurrentState.Keyboard.ShiftPressed == true)
|
||||
{
|
||||
switch (DownloadTracker.State.Value)
|
||||
{
|
||||
case DownloadState.NotDownloaded:
|
||||
if (!BeatmapSet.Availability.DownloadDisabled)
|
||||
beatmaps?.Download(BeatmapSet, preferNoVideo.Value);
|
||||
break;
|
||||
if (Action == null)
|
||||
throw new InvalidOperationException($"An action should be assigned to this {nameof(BeatmapCard)}. To use the default, assign {nameof(DefaultAction)}.");
|
||||
}
|
||||
|
||||
case DownloadState.LocallyAvailable:
|
||||
game?.PresentBeatmap(BeatmapSet);
|
||||
break;
|
||||
}
|
||||
protected void DefaultAction()
|
||||
{
|
||||
if (containingInputManager?.CurrentState.Keyboard.ShiftPressed == true)
|
||||
{
|
||||
switch (DownloadTracker.State.Value)
|
||||
{
|
||||
case DownloadState.NotDownloaded:
|
||||
if (!BeatmapSet.Availability.DownloadDisabled) beatmaps?.Download(BeatmapSet, preferNoVideo.Value);
|
||||
break;
|
||||
|
||||
case DownloadState.LocallyAvailable:
|
||||
game?.PresentBeatmap(BeatmapSet);
|
||||
break;
|
||||
}
|
||||
else
|
||||
beatmapSetOverlay?.FetchAndShowBeatmapSet(BeatmapSet.OnlineID);
|
||||
};
|
||||
}
|
||||
else
|
||||
beatmapSetOverlay?.FetchAndShowBeatmapSet(BeatmapSet.OnlineID);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
public BeatmapCardContentBackground(IBeatmapSetOnlineInfo onlineInfo)
|
||||
public BeatmapCardContentBackground(IBeatmapSetOnlineInfo onlineInfo, bool keepLoaded = false)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
@@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
cover = new DelayedLoadUnloadWrapper(() => createCover(onlineInfo), 500, 500)
|
||||
cover = new DelayedLoadUnloadWrapper(() => createCover(onlineInfo), keepLoaded ? 0 : 500, keepLoaded ? double.MaxValue : 1000)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Colour4.Transparent
|
||||
|
||||
@@ -46,6 +46,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
: base(beatmapSet, allowExpansion)
|
||||
{
|
||||
content = new BeatmapCardContent(height);
|
||||
|
||||
Action = DefaultAction;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
|
||||
@@ -54,6 +54,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
: base(beatmapSet, false)
|
||||
{
|
||||
content = new BeatmapCardContent(height);
|
||||
|
||||
Action = DefaultAction;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
||||
@@ -47,6 +47,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
: base(beatmapSet, allowExpansion)
|
||||
{
|
||||
content = new BeatmapCardContent(HEIGHT);
|
||||
|
||||
Action = DefaultAction;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
||||
@@ -35,11 +35,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
public BeatmapCardThumbnail(IBeatmapSetInfo beatmapSetInfo, IBeatmapSetOnlineInfo onlineInfo)
|
||||
public BeatmapCardThumbnail(IBeatmapSetInfo beatmapSetInfo, IBeatmapSetOnlineInfo onlineInfo, bool keepLoaded = false)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List)
|
||||
new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List, keepLoaded ? 0 : 500, keepLoaded ? double.MaxValue : 1000)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
OnlineInfo = onlineInfo
|
||||
|
||||
@@ -16,10 +16,12 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
||||
private readonly Bindable<DownloadState> state = new Bindable<DownloadState>();
|
||||
|
||||
private readonly APIBeatmapSet beatmapSet;
|
||||
private readonly bool allowNavigationToBeatmap;
|
||||
|
||||
public GoToBeatmapButton(APIBeatmapSet beatmapSet)
|
||||
public GoToBeatmapButton(APIBeatmapSet beatmapSet, bool allowNavigationToBeatmap)
|
||||
{
|
||||
this.beatmapSet = beatmapSet;
|
||||
this.allowNavigationToBeatmap = allowNavigationToBeatmap;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
@@ -27,7 +29,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
||||
{
|
||||
Action = () => game?.PresentBeatmap(beatmapSet);
|
||||
Icon.Icon = FontAwesome.Solid.AngleDoubleRight;
|
||||
TooltipText = "Go to beatmap";
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -40,8 +41,31 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
Enabled.Value = state.Value == DownloadState.LocallyAvailable;
|
||||
this.FadeTo(Enabled.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||
bool available = state.Value == DownloadState.LocallyAvailable;
|
||||
Enabled.Value = allowNavigationToBeatmap && available;
|
||||
|
||||
float alpha;
|
||||
|
||||
if (available && allowNavigationToBeatmap)
|
||||
{
|
||||
TooltipText = "Go to beatmap";
|
||||
Enabled.Value = true;
|
||||
alpha = 1f;
|
||||
}
|
||||
else if (available)
|
||||
{
|
||||
TooltipText = string.Empty;
|
||||
Enabled.Value = false;
|
||||
alpha = 0.3f;
|
||||
}
|
||||
else
|
||||
{
|
||||
TooltipText = string.Empty;
|
||||
Enabled.Value = false;
|
||||
alpha = 0;
|
||||
}
|
||||
|
||||
this.FadeTo(alpha, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
set
|
||||
{
|
||||
buttonsExpandedWidth = value;
|
||||
buttonArea.Width = value;
|
||||
if (IsLoaded)
|
||||
updateState();
|
||||
}
|
||||
@@ -67,7 +66,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
public CollapsibleButtonContainer(APIBeatmapSet beatmapSet)
|
||||
public CollapsibleButtonContainer(APIBeatmapSet beatmapSet, bool allowNavigationToBeatmap = true, bool keepBackgroundLoaded = false)
|
||||
{
|
||||
downloadTracker = new BeatmapDownloadTracker(beatmapSet);
|
||||
|
||||
@@ -116,14 +115,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.5f,
|
||||
},
|
||||
new GoToBeatmapButton(beatmapSet)
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
State = { BindTarget = downloadTracker.State },
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.5f,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -135,7 +126,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BeatmapCardContentBackground(beatmapSet)
|
||||
new BeatmapCardContentBackground(beatmapSet, keepBackgroundLoaded)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Dimmed = { BindTarget = ShowDetails }
|
||||
@@ -152,6 +143,15 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
buttons.Add(new GoToBeatmapButton(beatmapSet, allowNavigationToBeatmap)
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
State = { BindTarget = downloadTracker.State },
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.5f,
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -165,6 +165,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
buttonArea.Width = buttonsExpandedWidth;
|
||||
|
||||
float buttonAreaWidth = ShowDetails.Value ? ButtonsExpandedWidth : ButtonsCollapsedWidth;
|
||||
float mainAreaWidth = Width - buttonAreaWidth;
|
||||
|
||||
|
||||
+354
@@ -0,0 +1,354 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Beatmaps.Drawables.Cards;
|
||||
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.BeatmapSet;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
{
|
||||
public partial class BeatmapCardMatchmaking : BeatmapCard
|
||||
{
|
||||
private readonly APIBeatmap beatmap;
|
||||
|
||||
protected override Drawable IdleContent => idleBottomContent;
|
||||
protected override Drawable DownloadInProgressContent => downloadProgressBar;
|
||||
|
||||
public const float HEIGHT = 80;
|
||||
|
||||
[Cached]
|
||||
private readonly BeatmapCardContent content;
|
||||
|
||||
private BeatmapCardThumbnail thumbnail = null!;
|
||||
private CollapsibleButtonContainer buttonContainer = null!;
|
||||
|
||||
private FillFlowContainer<BeatmapCardStatistic> statisticsContainer = null!;
|
||||
|
||||
private FillFlowContainer idleBottomContent = null!;
|
||||
private BeatmapCardDownloadProgressBar downloadProgressBar = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
public BeatmapCardMatchmaking(APIBeatmap beatmap)
|
||||
: base(beatmap.BeatmapSet!, false)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
content = new BeatmapCardContent(HEIGHT);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Width = WIDTH;
|
||||
Height = HEIGHT;
|
||||
|
||||
FillFlowContainer leftIconArea = null!;
|
||||
FillFlowContainer titleBadgeArea = null!;
|
||||
GridContainer artistContainer = null!;
|
||||
|
||||
Child = content.With(c =>
|
||||
{
|
||||
c.MainContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
thumbnail = new BeatmapCardThumbnail(BeatmapSet, BeatmapSet, keepLoaded: true)
|
||||
{
|
||||
Name = @"Left (icon) area",
|
||||
Size = new Vector2(HEIGHT),
|
||||
Padding = new MarginPadding { Right = CORNER_RADIUS },
|
||||
Child = leftIconArea = new FillFlowContainer
|
||||
{
|
||||
Margin = new MarginPadding(4),
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(1)
|
||||
}
|
||||
},
|
||||
buttonContainer = new CollapsibleButtonContainer(BeatmapSet, allowNavigationToBeatmap: false, keepBackgroundLoaded: true)
|
||||
{
|
||||
X = HEIGHT - CORNER_RADIUS,
|
||||
Width = WIDTH - HEIGHT + CORNER_RADIUS,
|
||||
FavouriteState = { BindTarget = FavouriteState },
|
||||
ButtonsCollapsedWidth = 0,
|
||||
ButtonsExpandedWidth = 24,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new TruncatingSpriteText
|
||||
{
|
||||
Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title),
|
||||
Font = OsuFont.Default.With(size: 18f, weight: FontWeight.SemiBold),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
titleBadgeArea = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
artistContainer = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new TruncatingSpriteText
|
||||
{
|
||||
Text = createArtistText(),
|
||||
Font = OsuFont.Default.With(size: 14f, weight: FontWeight.SemiBold),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
Empty()
|
||||
},
|
||||
}
|
||||
},
|
||||
new LinkFlowContainer(s =>
|
||||
{
|
||||
s.Shadow = false;
|
||||
s.Font = OsuFont.GetFont(size: 11f, weight: FontWeight.SemiBold);
|
||||
}).With(d =>
|
||||
{
|
||||
d.AutoSizeAxes = Axes.Both;
|
||||
d.Margin = new MarginPadding { Top = 1 };
|
||||
d.AddText("mapped by ", t => t.Colour = colourProvider.Content2);
|
||||
d.AddUserLink(BeatmapSet.Author);
|
||||
}),
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Name = @"Bottom content",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
idleBottomContent = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 2),
|
||||
AlwaysPresent = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
statisticsContainer = new FillFlowContainer<BeatmapCardStatistic>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(8, 0),
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
ChildrenEnumerable = createStatistics()
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Masking = true,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colours.ForStarDifficulty(beatmap.StarRating).Darken(0.8f),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Padding = new MarginPadding(2),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(4, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new StarRatingDisplay(new StarDifficulty(beatmap.StarRating, 0), StarRatingDisplaySize.Small, animated: true)
|
||||
{
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Scale = new Vector2(0.875f),
|
||||
},
|
||||
new TruncatingSpriteText
|
||||
{
|
||||
Text = beatmap.DifficultyName,
|
||||
Font = OsuFont.Style.Caption2.With(weight: FontWeight.Bold),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
downloadProgressBar = new BeatmapCardDownloadProgressBar
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 5,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
State = { BindTarget = DownloadTracker.State },
|
||||
Progress = { BindTarget = DownloadTracker.Progress }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
c.Expanded.BindTarget = Expanded;
|
||||
});
|
||||
|
||||
if (BeatmapSet.HasVideo)
|
||||
leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(16) });
|
||||
|
||||
if (BeatmapSet.HasStoryboard)
|
||||
leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(16) });
|
||||
|
||||
if (BeatmapSet.FeaturedInSpotlight)
|
||||
{
|
||||
titleBadgeArea.Add(new SpotlightBeatmapBadge
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Margin = new MarginPadding { Left = 4 }
|
||||
});
|
||||
}
|
||||
|
||||
if (BeatmapSet.HasExplicitContent)
|
||||
{
|
||||
titleBadgeArea.Add(new ExplicitContentBeatmapBadge
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Margin = new MarginPadding { Left = 4 }
|
||||
});
|
||||
}
|
||||
|
||||
if (BeatmapSet.TrackId != null)
|
||||
{
|
||||
artistContainer.Content[0][1] = new FeaturedArtistBeatmapBadge
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Margin = new MarginPadding { Left = 4 }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private LocalisableString createArtistText()
|
||||
{
|
||||
var romanisableArtist = new RomanisableString(BeatmapSet.ArtistUnicode, BeatmapSet.Artist);
|
||||
return BeatmapsetsStrings.ShowDetailsByArtist(romanisableArtist);
|
||||
}
|
||||
|
||||
private IEnumerable<BeatmapCardStatistic> createStatistics()
|
||||
{
|
||||
var hypesStatistic = HypesStatistic.CreateFor(BeatmapSet);
|
||||
if (hypesStatistic != null)
|
||||
yield return hypesStatistic;
|
||||
|
||||
var nominationsStatistic = NominationsStatistic.CreateFor(BeatmapSet);
|
||||
if (nominationsStatistic != null)
|
||||
yield return nominationsStatistic;
|
||||
|
||||
yield return new FavouritesStatistic(BeatmapSet) { Current = FavouriteState };
|
||||
yield return new PlayCountStatistic(BeatmapSet);
|
||||
|
||||
var dateStatistic = BeatmapCardDateStatistic.CreateFor(BeatmapSet);
|
||||
if (dateStatistic != null)
|
||||
yield return dateStatistic;
|
||||
}
|
||||
|
||||
protected override void UpdateState()
|
||||
{
|
||||
base.UpdateState();
|
||||
|
||||
bool showDetails = IsHovered;
|
||||
|
||||
buttonContainer.ShowDetails.Value = showDetails;
|
||||
thumbnail.Dimmed.Value = showDetails;
|
||||
|
||||
statisticsContainer.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint);
|
||||
}
|
||||
|
||||
public override MenuItem[] ContextMenuItems
|
||||
{
|
||||
get
|
||||
{
|
||||
var items = base.ContextMenuItems.ToList();
|
||||
|
||||
foreach (var button in buttonContainer.Buttons)
|
||||
{
|
||||
if (button.Enabled.Value)
|
||||
items.Add(new OsuMenuItem(button.TooltipText.ToSentence(), MenuItemType.Standard, () => button.TriggerClick()));
|
||||
}
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-13
@@ -22,7 +22,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
{
|
||||
public partial class SelectionGrid : CompositeDrawable
|
||||
public partial class BeatmapSelectGrid : CompositeDrawable
|
||||
{
|
||||
public const double ARRANGE_DELAY = 200;
|
||||
|
||||
@@ -30,17 +30,17 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
private const double arrange_duration = 1000;
|
||||
private const double roll_duration = 4000;
|
||||
private const double present_beatmap_delay = 1200;
|
||||
private const float panel_spacing = 20;
|
||||
private const float panel_spacing = 4;
|
||||
|
||||
public event Action<MultiplayerPlaylistItem>? ItemSelected;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
private readonly Dictionary<long, SelectionPanel> panelLookup = new Dictionary<long, SelectionPanel>();
|
||||
private readonly Dictionary<long, BeatmapSelectPanel> panelLookup = new Dictionary<long, BeatmapSelectPanel>();
|
||||
|
||||
private readonly PanelGridContainer panelGridContainer;
|
||||
private readonly Container<SelectionPanel> rollContainer;
|
||||
private readonly Container<BeatmapSelectPanel> rollContainer;
|
||||
private readonly OsuScrollContainer scroll;
|
||||
|
||||
private bool allowSelection = true;
|
||||
@@ -51,7 +51,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
private Sample? swooshSample;
|
||||
private double? lastSamplePlayback;
|
||||
|
||||
public SelectionGrid()
|
||||
public BeatmapSelectGrid()
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
@@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
Spacing = new Vector2(panel_spacing)
|
||||
},
|
||||
},
|
||||
rollContainer = new Container<SelectionPanel>
|
||||
rollContainer = new Container<BeatmapSelectPanel>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
@@ -108,9 +108,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
|
||||
public void AddItem(MultiplayerPlaylistItem item)
|
||||
{
|
||||
var panel = panelLookup[item.ID] = new SelectionPanel(item)
|
||||
var panel = panelLookup[item.ID] = new BeatmapSelectPanel(item)
|
||||
{
|
||||
Size = new Vector2(300, 70),
|
||||
AllowSelection = allowSelection,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
@@ -176,7 +175,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
|
||||
var rng = new Random();
|
||||
|
||||
var remainingPanels = new List<SelectionPanel>();
|
||||
var remainingPanels = new List<BeatmapSelectPanel>();
|
||||
|
||||
foreach (var panel in panelGridContainer.Children.ToArray())
|
||||
{
|
||||
@@ -216,7 +215,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
{
|
||||
var panel = rollContainer.Children[i];
|
||||
|
||||
var position = positions[i] * (SelectionPanel.SIZE + new Vector2(panel_spacing));
|
||||
var position = positions[i] * (BeatmapSelectPanel.SIZE + new Vector2(panel_spacing));
|
||||
|
||||
panel.MoveTo(position, duration + stagger * i, new SplitEasingFunction(Easing.InCubic, Easing.OutExpo, 0.3f));
|
||||
|
||||
@@ -285,7 +284,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
while ((numSteps - 1) % rollContainer.Children.Count != finalItemIndex)
|
||||
numSteps++;
|
||||
|
||||
SelectionPanel? lastPanel = null;
|
||||
BeatmapSelectPanel? lastPanel = null;
|
||||
|
||||
for (int i = 0; i < numSteps; i++)
|
||||
{
|
||||
@@ -330,7 +329,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
{
|
||||
rollContainer.ChangeChildDepth(panel, float.MinValue);
|
||||
|
||||
panel.ShowBorder();
|
||||
panel.ShowChosenBorder();
|
||||
panel.MoveTo(Vector2.Zero, 1000, Easing.OutExpo)
|
||||
.ScaleTo(1.5f, 1000, Easing.OutExpo);
|
||||
|
||||
@@ -346,7 +345,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
PresentRolledBeatmap(finalItem);
|
||||
}
|
||||
|
||||
private partial class PanelGridContainer : FillFlowContainer<SelectionPanel>
|
||||
private partial class PanelGridContainer : FillFlowContainer<BeatmapSelectPanel>
|
||||
{
|
||||
public bool LayoutDisabled;
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
// 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.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.Drawables.Cards;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
{
|
||||
public partial class BeatmapSelectPanel : Container
|
||||
{
|
||||
public static readonly Vector2 SIZE = new Vector2(BeatmapCard.WIDTH, BeatmapCardNormal.HEIGHT);
|
||||
|
||||
public bool AllowSelection { get; set; }
|
||||
|
||||
public readonly MultiplayerPlaylistItem Item;
|
||||
|
||||
public Action<MultiplayerPlaylistItem>? Action { private get; init; }
|
||||
|
||||
private const float border_width = 3;
|
||||
|
||||
private Container scaleContainer = null!;
|
||||
private AvatarOverlay selectionOverlay = null!;
|
||||
private Drawable lighting = null!;
|
||||
|
||||
private Container border = null!;
|
||||
private Container mainContent = null!;
|
||||
|
||||
public override bool PropagatePositionalInputSubTree => AllowSelection;
|
||||
|
||||
public BeatmapSelectPanel(MultiplayerPlaylistItem item)
|
||||
{
|
||||
Item = item;
|
||||
Size = SIZE;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(BeatmapLookupCache lookupCache, OverlayColourProvider colourProvider)
|
||||
{
|
||||
InternalChild = scaleContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new[]
|
||||
{
|
||||
mainContent = new Container
|
||||
{
|
||||
Masking = true,
|
||||
CornerRadius = BeatmapCard.CORNER_RADIUS,
|
||||
CornerExponent = 10,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
{
|
||||
lighting = new Box
|
||||
{
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
},
|
||||
selectionOverlay = new AvatarOverlay
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
}
|
||||
}
|
||||
},
|
||||
border = new Container
|
||||
{
|
||||
Alpha = 0,
|
||||
Masking = true,
|
||||
CornerRadius = BeatmapCard.CORNER_RADIUS,
|
||||
CornerExponent = 10,
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
BorderThickness = border_width,
|
||||
BorderColour = colourProvider.Light1,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Radius = 40,
|
||||
Roundness = 300,
|
||||
Colour = colourProvider.Light3.Opacity(0.1f),
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0,
|
||||
Colour = Color4.Black,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
lookupCache.GetBeatmapAsync(Item.BeatmapID).ContinueWith(b => Schedule(() =>
|
||||
{
|
||||
var beatmap = b.GetResultSafely()!;
|
||||
beatmap.StarRating = Item.StarRating;
|
||||
|
||||
mainContent.Add(new BeatmapCardMatchmaking(beatmap)
|
||||
{
|
||||
Depth = float.MaxValue,
|
||||
Action = () => Action?.Invoke(Item),
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
public bool AddUser(APIUser user, bool isOwnUser = false) => selectionOverlay.AddUser(user, isOwnUser);
|
||||
public bool RemoveUser(APIUser user) => selectionOverlay.RemoveUser(user.Id);
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
lighting.FadeTo(0.2f, 50)
|
||||
.Then()
|
||||
.FadeTo(0.1f, 300);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
base.OnHoverLost(e);
|
||||
|
||||
lighting.FadeOut(200);
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
if (e.Button == MouseButton.Left)
|
||||
{
|
||||
scaleContainer.ScaleTo(0.95f, 400, Easing.OutExpo);
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseDown(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
base.OnMouseUp(e);
|
||||
|
||||
if (e.Button == MouseButton.Left)
|
||||
{
|
||||
scaleContainer.ScaleTo(1f, 500, Easing.OutElasticHalf);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
lighting.FadeTo(0.5f, 50)
|
||||
.Then()
|
||||
.FadeTo(0.1f, 400);
|
||||
|
||||
// pass through to let the beatmap card handle actual click.
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ShowChosenBorder()
|
||||
{
|
||||
border.FadeTo(1, 1000, Easing.OutQuint);
|
||||
}
|
||||
|
||||
public void ShowBorder()
|
||||
{
|
||||
border.FadeTo(1, 80, Easing.OutQuint)
|
||||
.Then()
|
||||
.FadeTo(0.7f, 800, Easing.OutQuint);
|
||||
}
|
||||
|
||||
public void HideBorder()
|
||||
{
|
||||
border.FadeOut(500, Easing.OutQuint);
|
||||
}
|
||||
|
||||
public void FadeInAndEnterFromBelow(double duration = 500, double delay = 0, float distance = 200)
|
||||
{
|
||||
scaleContainer
|
||||
.FadeOut()
|
||||
.MoveToY(distance)
|
||||
.Delay(delay)
|
||||
.FadeIn(duration / 2)
|
||||
.MoveToY(0, duration, Easing.OutExpo);
|
||||
}
|
||||
|
||||
public void PopOutAndExpire(double duration = 400, double delay = 0, Easing easing = Easing.InCubic)
|
||||
{
|
||||
AllowSelection = false;
|
||||
|
||||
scaleContainer.Delay(delay)
|
||||
.ScaleTo(0, duration, easing)
|
||||
.FadeOut(duration);
|
||||
|
||||
this.Delay(delay + duration).FadeOut().Expire();
|
||||
}
|
||||
|
||||
private partial class AvatarOverlay : CompositeDrawable
|
||||
{
|
||||
private readonly Container<SelectionAvatar> avatars;
|
||||
|
||||
private Sample? userAddedSample;
|
||||
private double? lastSamplePlayback;
|
||||
|
||||
public AvatarOverlay()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
InternalChild = avatars = new Container<SelectionAvatar>
|
||||
{
|
||||
AutoSizeAxes = Axes.X,
|
||||
Height = SelectionAvatar.AVATAR_SIZE,
|
||||
};
|
||||
|
||||
Padding = new MarginPadding(5);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
userAddedSample = audio.Samples.Get(@"Multiplayer/player-ready");
|
||||
}
|
||||
|
||||
public bool AddUser(APIUser user, bool isOwnUser)
|
||||
{
|
||||
if (avatars.Any(a => a.User.Id == user.Id))
|
||||
return false;
|
||||
|
||||
var avatar = new SelectionAvatar(user, isOwnUser);
|
||||
|
||||
avatars.Add(avatar);
|
||||
|
||||
if (lastSamplePlayback == null || Time.Current - lastSamplePlayback > OsuGameBase.SAMPLE_DEBOUNCE_TIME)
|
||||
{
|
||||
userAddedSample?.Play();
|
||||
lastSamplePlayback = Time.Current;
|
||||
}
|
||||
|
||||
updateAvatarLayout();
|
||||
|
||||
avatar.FinishTransforms();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveUser(int id)
|
||||
{
|
||||
if (avatars.SingleOrDefault(a => a.User.Id == id) is not SelectionAvatar avatar)
|
||||
return false;
|
||||
|
||||
avatar.PopOutAndExpire();
|
||||
avatars.ChangeChildDepth(avatar, float.MaxValue);
|
||||
|
||||
updateAvatarLayout();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateAvatarLayout()
|
||||
{
|
||||
const double stagger = 30;
|
||||
const float spacing = 4;
|
||||
|
||||
double delay = 0;
|
||||
float x = 0;
|
||||
|
||||
for (int i = avatars.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var avatar = avatars[i];
|
||||
|
||||
if (avatar.Expired)
|
||||
continue;
|
||||
|
||||
avatar.Delay(delay).MoveToX(x, 500, Easing.OutElasticQuarter);
|
||||
|
||||
x -= avatar.LayoutSize.X + spacing;
|
||||
|
||||
delay += stagger;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class SelectionAvatar : CompositeDrawable
|
||||
{
|
||||
public const float AVATAR_SIZE = 30;
|
||||
|
||||
public APIUser User { get; }
|
||||
|
||||
public bool Expired { get; private set; }
|
||||
|
||||
private readonly MatchmakingAvatar avatar;
|
||||
|
||||
public SelectionAvatar(APIUser user, bool isOwnUser)
|
||||
{
|
||||
User = user;
|
||||
Size = new Vector2(AVATAR_SIZE);
|
||||
|
||||
InternalChild = avatar = new MatchmakingAvatar(user, isOwnUser)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
avatar.ScaleTo(0)
|
||||
.ScaleTo(1, 500, Easing.OutElasticHalf)
|
||||
.FadeIn(200);
|
||||
}
|
||||
|
||||
public void PopOutAndExpire()
|
||||
{
|
||||
avatar.ScaleTo(0, 400, Easing.OutExpo);
|
||||
|
||||
this.FadeOut(100).Expire();
|
||||
Expired = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,502 +0,0 @@
|
||||
// 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.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
{
|
||||
public partial class SelectionPanel : Container
|
||||
{
|
||||
public static readonly Vector2 SIZE = new Vector2(300, 70);
|
||||
|
||||
private const float corner_radius = 6;
|
||||
private const float border_width = 3;
|
||||
|
||||
public readonly MultiplayerPlaylistItem Item;
|
||||
|
||||
private readonly Container scaleContainer;
|
||||
private readonly BeatmapPanel beatmapPanel;
|
||||
private readonly AvatarOverlay selectionOverlay;
|
||||
private readonly Container border;
|
||||
private readonly Box flash;
|
||||
|
||||
public bool AllowSelection;
|
||||
|
||||
public Action<MultiplayerPlaylistItem>? Action;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapLookupCache beatmapLookupCache { get; set; } = null!;
|
||||
|
||||
public override bool PropagatePositionalInputSubTree => AllowSelection;
|
||||
|
||||
public SelectionPanel(MultiplayerPlaylistItem item)
|
||||
{
|
||||
Item = item;
|
||||
Size = SIZE;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
scaleContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(-border_width),
|
||||
Child = border = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = corner_radius + border_width,
|
||||
Alpha = 0,
|
||||
Child = new Box { RelativeSizeAxes = Axes.Both },
|
||||
}
|
||||
},
|
||||
beatmapPanel = new BeatmapPanel
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
OverlayLayer =
|
||||
{
|
||||
Children = new[]
|
||||
{
|
||||
flash = new Box
|
||||
{
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
selectionOverlay = new AvatarOverlay
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Horizontal = 10 },
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
}
|
||||
},
|
||||
new HoverClickSounds(),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ContinueWith(b => Schedule(() =>
|
||||
{
|
||||
var beatmap = b.GetResultSafely()!;
|
||||
|
||||
beatmap.StarRating = Item.StarRating;
|
||||
|
||||
beatmapPanel.Beatmap = beatmap;
|
||||
}));
|
||||
}
|
||||
|
||||
public bool AddUser(APIUser user, bool isOwnUser = false) => selectionOverlay.AddUser(user, isOwnUser);
|
||||
|
||||
public bool RemoveUser(int userId) => selectionOverlay.RemoveUser(userId);
|
||||
|
||||
public bool RemoveUser(APIUser user) => RemoveUser(user.Id);
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
flash.FadeTo(0.2f, 50)
|
||||
.Then()
|
||||
.FadeTo(0.1f, 300);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
base.OnHoverLost(e);
|
||||
|
||||
flash.FadeOut(200);
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
if (e.Button == MouseButton.Left)
|
||||
{
|
||||
scaleContainer.ScaleTo(0.95f, 400, Easing.OutExpo);
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseDown(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
base.OnMouseUp(e);
|
||||
|
||||
if (e.Button == MouseButton.Left)
|
||||
{
|
||||
scaleContainer.ScaleTo(1f, 500, Easing.OutElasticHalf);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
Action?.Invoke(Item);
|
||||
|
||||
flash.FadeTo(0.5f, 50)
|
||||
.Then()
|
||||
.FadeTo(0.1f, 400);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ShowBorder() => border.Show();
|
||||
|
||||
public void HideBorder() => border.Hide();
|
||||
|
||||
public void FadeInAndEnterFromBelow(double duration = 500, double delay = 0, float distance = 200)
|
||||
{
|
||||
scaleContainer
|
||||
.FadeOut()
|
||||
.MoveToY(distance)
|
||||
.Delay(delay)
|
||||
.FadeIn(duration / 2)
|
||||
.MoveToY(0, duration, Easing.OutExpo);
|
||||
}
|
||||
|
||||
public void PopOutAndExpire(double duration = 400, double delay = 0, Easing easing = Easing.InCubic)
|
||||
{
|
||||
AllowSelection = false;
|
||||
|
||||
scaleContainer.Delay(delay)
|
||||
.ScaleTo(0, duration, easing)
|
||||
.FadeOut(duration);
|
||||
|
||||
this.Delay(delay + duration).FadeOut().Expire();
|
||||
}
|
||||
|
||||
// TODO: combine following two classes with above implementation for simplicity?
|
||||
private partial class BeatmapPanel : CompositeDrawable
|
||||
{
|
||||
public readonly Container OverlayLayer = new Container { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
public APIBeatmap? Beatmap
|
||||
{
|
||||
get => beatmap;
|
||||
set
|
||||
{
|
||||
if (beatmap?.OnlineID == value?.OnlineID)
|
||||
return;
|
||||
|
||||
beatmap = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateContent();
|
||||
}
|
||||
}
|
||||
|
||||
private APIBeatmap? beatmap;
|
||||
|
||||
private Container content = null!;
|
||||
private UpdateableOnlineBeatmapSetCover cover = null!;
|
||||
|
||||
public BeatmapPanel(APIBeatmap? beatmap = null)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
Masking = true;
|
||||
CornerRadius = 6;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
cover = new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.Card, timeBeforeLoad: 0, timeBeforeUnload: 10000)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientHorizontal(
|
||||
colourProvider.Background4.Opacity(0.7f),
|
||||
colourProvider.Background4.Opacity(0.4f)
|
||||
)
|
||||
},
|
||||
content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
OverlayLayer,
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
updateContent();
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
private void updateContent()
|
||||
{
|
||||
foreach (var child in content.Children)
|
||||
child.FadeOut(300).Expire();
|
||||
|
||||
cover.OnlineInfo = beatmap?.BeatmapSet;
|
||||
|
||||
if (beatmap != null)
|
||||
{
|
||||
var panelContent = new BeatmapPanelContent(beatmap)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
|
||||
content.Add(panelContent);
|
||||
|
||||
panelContent.FadeInFromZero(300);
|
||||
}
|
||||
}
|
||||
|
||||
private partial class BeatmapPanelContent : CompositeDrawable
|
||||
{
|
||||
private readonly APIBeatmap beatmap;
|
||||
|
||||
public BeatmapPanelContent(APIBeatmap beatmap)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Padding = new MarginPadding { Horizontal = 12 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new TruncatingSpriteText
|
||||
{
|
||||
Text = new RomanisableString(beatmap.Metadata.TitleUnicode, beatmap.Metadata.TitleUnicode),
|
||||
Font = OsuFont.Default.With(size: 19, weight: FontWeight.SemiBold),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
new TextFlowContainer(s =>
|
||||
{
|
||||
s.Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold);
|
||||
}).With(d =>
|
||||
{
|
||||
d.RelativeSizeAxes = Axes.X;
|
||||
d.AutoSizeAxes = Axes.Y;
|
||||
d.AddText("by ");
|
||||
d.AddText(new RomanisableString(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist));
|
||||
}),
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Margin = new MarginPadding { Top = 6 },
|
||||
Spacing = new Vector2(4),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new StarRatingDisplay(new StarDifficulty(beatmap.StarRating, 0), StarRatingDisplaySize.Small)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
new TruncatingSpriteText
|
||||
{
|
||||
Text = beatmap.DifficultyName,
|
||||
Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private partial class AvatarOverlay : CompositeDrawable
|
||||
{
|
||||
private readonly Dictionary<int, SelectionAvatar> avatars = new Dictionary<int, SelectionAvatar>();
|
||||
|
||||
private readonly Container<SelectionAvatar> avatarContainer;
|
||||
|
||||
private Sample? userAddedSample;
|
||||
private double? lastSamplePlayback;
|
||||
|
||||
public new Axes AutoSizeAxes
|
||||
{
|
||||
get => base.AutoSizeAxes;
|
||||
set => base.AutoSizeAxes = value;
|
||||
}
|
||||
|
||||
public new MarginPadding Padding
|
||||
{
|
||||
get => base.Padding;
|
||||
set => base.Padding = value;
|
||||
}
|
||||
|
||||
public AvatarOverlay()
|
||||
{
|
||||
InternalChild = avatarContainer = new Container<SelectionAvatar>();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
avatarContainer.AutoSizeAxes = AutoSizeAxes;
|
||||
avatarContainer.RelativeSizeAxes = RelativeSizeAxes;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
userAddedSample = audio.Samples.Get(@"Multiplayer/player-ready");
|
||||
}
|
||||
|
||||
public bool AddUser(APIUser user, bool isOwnUser)
|
||||
{
|
||||
if (avatars.ContainsKey(user.Id))
|
||||
return false;
|
||||
|
||||
var avatar = new SelectionAvatar(user, isOwnUser)
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
};
|
||||
|
||||
avatarContainer.Add(avatars[user.Id] = avatar);
|
||||
|
||||
if (lastSamplePlayback == null || Time.Current - lastSamplePlayback > OsuGameBase.SAMPLE_DEBOUNCE_TIME)
|
||||
{
|
||||
userAddedSample?.Play();
|
||||
lastSamplePlayback = Time.Current;
|
||||
}
|
||||
|
||||
updateLayout();
|
||||
|
||||
avatar.FinishTransforms();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveUser(int id)
|
||||
{
|
||||
if (!avatars.Remove(id, out var avatar))
|
||||
return false;
|
||||
|
||||
avatar.PopOutAndExpire();
|
||||
avatarContainer.ChangeChildDepth(avatar, float.MaxValue);
|
||||
|
||||
updateLayout();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateLayout()
|
||||
{
|
||||
const double stagger = 30;
|
||||
const float spacing = 4;
|
||||
|
||||
double delay = 0;
|
||||
float x = 0;
|
||||
|
||||
for (int i = avatarContainer.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var avatar = avatarContainer[i];
|
||||
|
||||
if (avatar.Expired)
|
||||
continue;
|
||||
|
||||
avatar.Delay(delay).MoveToX(x, 500, Easing.OutElasticQuarter);
|
||||
|
||||
x -= avatar.LayoutSize.X + spacing;
|
||||
|
||||
delay += stagger;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class SelectionAvatar : CompositeDrawable
|
||||
{
|
||||
public bool Expired { get; private set; }
|
||||
|
||||
private readonly Container content;
|
||||
|
||||
public SelectionAvatar(APIUser user, bool isOwnUser)
|
||||
{
|
||||
Size = new Vector2(30);
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Child = new MatchmakingAvatar(user, isOwnUser)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
content.ScaleTo(0)
|
||||
.ScaleTo(1, 500, Easing.OutElasticHalf)
|
||||
.FadeIn(200);
|
||||
}
|
||||
|
||||
public void PopOutAndExpire()
|
||||
{
|
||||
content.ScaleTo(0, 400, Easing.OutExpo);
|
||||
|
||||
this.FadeOut(100).Expire();
|
||||
Expired = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-9
@@ -16,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
public override PanelDisplayStyle PlayersDisplayStyle => PanelDisplayStyle.Split;
|
||||
public override Drawable PlayersDisplayArea { get; }
|
||||
|
||||
private readonly SelectionGrid selectionGrid;
|
||||
private readonly BeatmapSelectGrid beatmapSelectGrid;
|
||||
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
@@ -29,7 +29,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Horizontal = 200 },
|
||||
Child = selectionGrid = new SelectionGrid
|
||||
Child = beatmapSelectGrid = new BeatmapSelectGrid
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
@@ -37,8 +37,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Horizontal = 5 },
|
||||
Child = PlayersDisplayArea = Empty().With(d =>
|
||||
Child = PlayersDisplayArea = new Container().With(d =>
|
||||
{
|
||||
d.RelativeSizeAxes = Axes.Both;
|
||||
})
|
||||
@@ -55,7 +54,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
foreach (var item in client.Room!.Playlist)
|
||||
onItemAdded(item);
|
||||
|
||||
selectionGrid.ItemSelected += item => client.MatchmakingToggleSelection(item.ID);
|
||||
beatmapSelectGrid.ItemSelected += item => client.MatchmakingToggleSelection(item.ID);
|
||||
|
||||
client.MatchmakingItemSelected += onItemSelected;
|
||||
client.MatchmakingItemDeselected += onItemDeselected;
|
||||
@@ -66,22 +65,22 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
if (item.Expired)
|
||||
return;
|
||||
|
||||
selectionGrid.AddItem(item);
|
||||
beatmapSelectGrid.AddItem(item);
|
||||
});
|
||||
|
||||
private void onItemSelected(int userId, long itemId)
|
||||
{
|
||||
var user = client.Room!.Users.First(it => it.UserID == userId).User!;
|
||||
selectionGrid.SetUserSelection(user, itemId, true);
|
||||
beatmapSelectGrid.SetUserSelection(user, itemId, true);
|
||||
}
|
||||
|
||||
private void onItemDeselected(int userId, long itemId)
|
||||
{
|
||||
var user = client.Room!.Users.First(it => it.UserID == userId).User!;
|
||||
selectionGrid.SetUserSelection(user, itemId, false);
|
||||
beatmapSelectGrid.SetUserSelection(user, itemId, false);
|
||||
}
|
||||
|
||||
public void RollFinalBeatmap(long[] candidateItems, long finalItem) => selectionGrid.RollAndDisplayFinalBeatmap(candidateItems, finalItem);
|
||||
public void RollFinalBeatmap(long[] candidateItems, long finalItem) => beatmapSelectGrid.RollAndDisplayFinalBeatmap(candidateItems, finalItem);
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
{
|
||||
/// <summary>
|
||||
/// A circular player avatar used in matchmaking displays.
|
||||
/// Is part of a <see cref="MatchmakingUserPanel"/> but can also be used in isolation for a more ambient/decorative user display.
|
||||
/// Is part of a <see cref="PlayerPanel"/> but can also be used in isolation for a more ambient/decorative user display.
|
||||
/// </summary>
|
||||
public partial class MatchmakingAvatar : CompositeDrawable
|
||||
{
|
||||
|
||||
+3
-2
@@ -20,7 +20,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
/// A panel used throughout matchmaking to represent a user, including local information like their
|
||||
/// rank and high level statistics in the matchmaking system.
|
||||
/// </summary>
|
||||
public partial class MatchmakingUserPanel : UserPanel
|
||||
public partial class PlayerPanel : UserPanel
|
||||
{
|
||||
public static readonly Vector2 SIZE_HORIZONTAL = new Vector2(250, 100);
|
||||
public static readonly Vector2 SIZE_VERTICAL = new Vector2(150, 200);
|
||||
@@ -55,7 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
|
||||
private bool horizontal;
|
||||
|
||||
public MatchmakingUserPanel(MultiplayerRoomUser user)
|
||||
public PlayerPanel(MultiplayerRoomUser user)
|
||||
: base(user.User!)
|
||||
{
|
||||
RoomUser = user;
|
||||
@@ -66,6 +66,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
{
|
||||
Masking = true;
|
||||
CornerRadius = 10;
|
||||
CornerExponent = 10;
|
||||
|
||||
Add(scaleContainer = new Container
|
||||
{
|
||||
+11
-11
@@ -18,12 +18,12 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
/// A component which maintains the layout of the players in a matchmaking room.
|
||||
/// Can be controlled to display the panels in a certain location and in multiple styles.
|
||||
/// </summary>
|
||||
public partial class UserPanelOverlay : CompositeDrawable
|
||||
public partial class PlayerPanelOverlay : CompositeDrawable
|
||||
{
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
private Container<MatchmakingUserPanel> panels = null!;
|
||||
private Container<PlayerPanel> panels = null!;
|
||||
private PlayerPanelCellContainer gridLayout = null!;
|
||||
private PlayerPanelCellContainer splitLayoutLeft = null!;
|
||||
private PlayerPanelCellContainer splitLayoutRight = null!;
|
||||
@@ -40,7 +40,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
gridLayout = new PlayerPanelCellContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(20, 5),
|
||||
Spacing = new Vector2(20),
|
||||
},
|
||||
splitLayoutLeft = new PlayerPanelCellContainer
|
||||
{
|
||||
@@ -49,7 +49,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(20, 5),
|
||||
Spacing = new Vector2(5),
|
||||
},
|
||||
splitLayoutRight = new PlayerPanelCellContainer
|
||||
{
|
||||
@@ -58,9 +58,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(20, 5),
|
||||
Spacing = new Vector2(5),
|
||||
},
|
||||
panels = new Container<MatchmakingUserPanel>
|
||||
panels = new Container<PlayerPanel>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
@@ -110,7 +110,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
|
||||
private void onUserJoined(MultiplayerRoomUser user) => Scheduler.Add(() =>
|
||||
{
|
||||
panels.Add(new MatchmakingUserPanel(user)
|
||||
panels.Add(new PlayerPanel(user)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@@ -215,7 +215,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
public void AcquirePanels(MatchmakingUserPanel[] panels)
|
||||
public void AcquirePanels(PlayerPanel[] panels)
|
||||
{
|
||||
while (Count < panels.Length)
|
||||
{
|
||||
@@ -259,10 +259,10 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
|
||||
private partial class PlayerPanelCell : Drawable
|
||||
{
|
||||
private MatchmakingUserPanel? panel;
|
||||
private PlayerPanel? panel;
|
||||
private bool isAnimating;
|
||||
|
||||
public void AcquirePanel(MatchmakingUserPanel panel)
|
||||
public void AcquirePanel(PlayerPanel panel)
|
||||
{
|
||||
this.panel = panel;
|
||||
isAnimating = true;
|
||||
@@ -280,7 +280,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
if (panel?.Parent == null)
|
||||
return;
|
||||
|
||||
Size = panel.Horizontal ? MatchmakingUserPanel.SIZE_HORIZONTAL : MatchmakingUserPanel.SIZE_VERTICAL;
|
||||
Size = panel.Horizontal ? PlayerPanel.SIZE_HORIZONTAL : PlayerPanel.SIZE_VERTICAL;
|
||||
Size *= panel.Scale;
|
||||
|
||||
var targetPos = getFinalPosition();
|
||||
@@ -24,7 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
private Framework.Screens.ScreenStack screenStack = null!;
|
||||
private UserPanelOverlay playersList = null!;
|
||||
private PlayerPanelOverlay playersList = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@@ -45,7 +45,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
screenStack = new Framework.Screens.ScreenStack(),
|
||||
}
|
||||
},
|
||||
playersList = new UserPanelOverlay
|
||||
playersList = new PlayerPanelOverlay
|
||||
{
|
||||
DisplayArea = this
|
||||
},
|
||||
|
||||
@@ -42,6 +42,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
private Sample? countdownTickSample;
|
||||
private double? lastSamplePlayback;
|
||||
|
||||
private Container mainContent = null!;
|
||||
|
||||
public bool Active { get; private set; }
|
||||
|
||||
public float Progress => progressBar.Width;
|
||||
@@ -49,10 +51,14 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
public StageSegment(int? round, MatchmakingStage stage, LocalisableString displayText)
|
||||
{
|
||||
Round = round;
|
||||
|
||||
this.stage = stage;
|
||||
this.displayText = displayText;
|
||||
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
Anchor = Anchor.CentreLeft;
|
||||
Origin = Anchor.CentreLeft;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@@ -74,7 +80,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
Icon = FontAwesome.Solid.ArrowRight,
|
||||
Margin = new MarginPadding { Horizontal = 10 }
|
||||
},
|
||||
new Container
|
||||
mainContent = new Container
|
||||
{
|
||||
Masking = true,
|
||||
CornerRadius = 5,
|
||||
@@ -178,6 +184,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
if (wasActive)
|
||||
progressBar.Width = 1;
|
||||
|
||||
mainContent.ScaleTo(Active ? 1.3f : 1, 500, Easing.OutQuint);
|
||||
|
||||
bool isPreparing =
|
||||
(stage == MatchmakingStage.RoundWarmupTime && roomState.Stage == MatchmakingStage.WaitingForClientsJoin) ||
|
||||
(stage == MatchmakingStage.GameplayWarmupTime && roomState.Stage == MatchmakingStage.WaitingForClientsBeatmapDownload) ||
|
||||
|
||||
@@ -57,17 +57,16 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
{
|
||||
scroll = new StageScrollContainer
|
||||
{
|
||||
ScrollbarOverlapsContent = false,
|
||||
ScrollbarVisible = false,
|
||||
ClampExtension = 0,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Height = 36,
|
||||
Height = HEIGHT,
|
||||
Child = flow = new FillFlowContainer
|
||||
{
|
||||
Padding = new MarginPadding { Horizontal = 2000 },
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Direction = FillDirection.Horizontal,
|
||||
},
|
||||
},
|
||||
@@ -226,8 +225,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
|
||||
round = value.Value;
|
||||
|
||||
this.ScaleTo(6, 500, Easing.OutQuart)
|
||||
.MoveToY(-300, 500, Easing.OutQuart)
|
||||
this.ScaleTo(6, 1000, Easing.OutPow10)
|
||||
.MoveToY(-300, 1000, Easing.OutPow10)
|
||||
.Then()
|
||||
.MoveToY(0, 500, Easing.InQuart)
|
||||
.ScaleTo(1, 500, Easing.InQuart);
|
||||
|
||||
@@ -13,6 +13,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
@@ -788,9 +789,11 @@ namespace osu.Game.Screens.SelectV2
|
||||
private readonly DrawablePool<PanelBeatmapSet> setPanelPool = new DrawablePool<PanelBeatmapSet>(100);
|
||||
private readonly DrawablePool<PanelGroup> groupPanelPool = new DrawablePool<PanelGroup>(100);
|
||||
private readonly DrawablePool<PanelGroupStarDifficulty> starsGroupPanelPool = new DrawablePool<PanelGroupStarDifficulty>(11);
|
||||
private readonly DrawablePool<PanelGroupRankDisplay> ranksGroupPanelPool = new DrawablePool<PanelGroupRankDisplay>(9);
|
||||
|
||||
private void setupPools()
|
||||
{
|
||||
AddInternal(ranksGroupPanelPool);
|
||||
AddInternal(starsGroupPanelPool);
|
||||
AddInternal(groupPanelPool);
|
||||
AddInternal(beatmapPanelPool);
|
||||
@@ -829,6 +832,9 @@ namespace osu.Game.Screens.SelectV2
|
||||
case StarDifficultyGroupDefinition:
|
||||
return starsGroupPanelPool.Get();
|
||||
|
||||
case RankDisplayGroupDefinition:
|
||||
return ranksGroupPanelPool.Get();
|
||||
|
||||
case GroupDefinition:
|
||||
return groupPanelPool.Get();
|
||||
|
||||
@@ -1085,6 +1091,11 @@ namespace osu.Game.Screens.SelectV2
|
||||
/// </summary>
|
||||
public record StarDifficultyGroupDefinition(int Order, string Title, StarDifficulty Difficulty) : GroupDefinition(Order, Title);
|
||||
|
||||
/// <summary>
|
||||
/// Defines a grouping header for a set of carousel items grouped by achieved rank.
|
||||
/// </summary>
|
||||
public record RankDisplayGroupDefinition(ScoreRank Rank) : GroupDefinition(-(int)Rank, Rank.GetDescription());
|
||||
|
||||
/// <summary>
|
||||
/// Used to represent a portion of a <see cref="BeatmapSetInfo"/> under a <see cref="GroupDefinition"/>.
|
||||
/// The purpose of this model is to support splitting beatmap sets apart when the active grouping mode demands it.
|
||||
|
||||
@@ -433,7 +433,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
private IEnumerable<GroupDefinition> defineGroupByRankAchieved(BeatmapInfo beatmap, IReadOnlyDictionary<Guid, ScoreRank> topRankMapping)
|
||||
{
|
||||
if (topRankMapping.TryGetValue(beatmap.ID, out var rank))
|
||||
return new GroupDefinition(-(int)rank, rank.GetDescription()).Yield();
|
||||
return new RankDisplayGroupDefinition(rank).Yield();
|
||||
|
||||
return new GroupDefinition(int.MaxValue, "Unplayed").Yield();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
// 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.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
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.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Scoring;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public partial class PanelGroupRankDisplay : Panel
|
||||
{
|
||||
public const float HEIGHT = PanelGroup.HEIGHT;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
private Drawable iconContainer = null!;
|
||||
private Box backgroundBorder = null!;
|
||||
private Box contentBackground = null!;
|
||||
private OsuSpriteText starRatingText = null!;
|
||||
private CircularContainer countPill = null!;
|
||||
private OsuSpriteText countText = null!;
|
||||
private TrianglesV2 triangles = null!;
|
||||
private Box glow = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Height = PanelGroup.HEIGHT;
|
||||
|
||||
Icon = iconContainer = new Container
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Alpha = 0f,
|
||||
Child = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = FontAwesome.Solid.ChevronDown,
|
||||
Size = new Vector2(12),
|
||||
},
|
||||
};
|
||||
|
||||
Background = backgroundBorder = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Highlight1,
|
||||
};
|
||||
|
||||
AccentColour = colourProvider.Highlight1;
|
||||
Content.Children = new Drawable[]
|
||||
{
|
||||
contentBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
triangles = new TrianglesV2
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Thickness = 0.02f,
|
||||
SpawnRatio = 0.6f,
|
||||
Colour = ColourInfo.GradientHorizontal(colourProvider.Background6, colourProvider.Background5)
|
||||
},
|
||||
glow = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 0.5f,
|
||||
Colour = ColourInfo.GradientHorizontal(colourProvider.Highlight1, colourProvider.Highlight1.Opacity(0f)),
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(10f, 0f),
|
||||
Margin = new MarginPadding { Left = 10f },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
starRatingText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
UseFullGlyphHeight = false,
|
||||
Font = OsuFont.Style.Heading2,
|
||||
}
|
||||
}
|
||||
},
|
||||
countPill = 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),
|
||||
},
|
||||
countText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.Style.Caption1.With(weight: FontWeight.Bold),
|
||||
UseFullGlyphHeight = false,
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Expanded.BindValueChanged(_ => onExpanded(), true);
|
||||
}
|
||||
|
||||
private Color4 rankColour;
|
||||
|
||||
protected override void PrepareForUse()
|
||||
{
|
||||
base.PrepareForUse();
|
||||
|
||||
Debug.Assert(Item != null);
|
||||
|
||||
var group = (RankDisplayGroupDefinition)Item.Model;
|
||||
ScoreRank rank = group.Rank;
|
||||
|
||||
rankColour = OsuColour.ForRank(rank);
|
||||
|
||||
AccentColour = rankColour;
|
||||
backgroundBorder.Colour = rankColour;
|
||||
contentBackground.Colour = rankColour.Darken(1f);
|
||||
glow.Colour = ColourInfo.GradientHorizontal(rankColour, rankColour.Opacity(0f));
|
||||
|
||||
switch (rank)
|
||||
{
|
||||
case ScoreRank.SH:
|
||||
case ScoreRank.XH:
|
||||
starRatingText.Colour = DrawableRank.GetRankNameColour(rank);
|
||||
iconContainer.Colour = colourProvider.Background5;
|
||||
break;
|
||||
|
||||
case ScoreRank.X:
|
||||
case ScoreRank.S:
|
||||
starRatingText.Colour = DrawableRank.GetRankNameColour(rank);
|
||||
iconContainer.Colour = colourProvider.Background5;
|
||||
break;
|
||||
|
||||
case ScoreRank.F:
|
||||
starRatingText.Colour = DrawableRank.GetRankNameColour(rank);
|
||||
iconContainer.Colour = colourProvider.Content1;
|
||||
break;
|
||||
|
||||
default:
|
||||
starRatingText.Colour = Color4.White;
|
||||
iconContainer.Colour = colourProvider.Background5;
|
||||
break;
|
||||
}
|
||||
|
||||
starRatingText.Text = group.Title;
|
||||
|
||||
ColourInfo colour = ColourInfo.GradientHorizontal(rankColour.Darken(0.6f), rankColour.Darken(0.8f));
|
||||
|
||||
triangles.Colour = colour;
|
||||
|
||||
countText.Text = Item.NestedItemCount.ToLocalisableString(@"N0");
|
||||
|
||||
onExpanded();
|
||||
}
|
||||
|
||||
private void onExpanded()
|
||||
{
|
||||
const float duration = 500;
|
||||
|
||||
iconContainer.ResizeWidthTo(Expanded.Value ? 20f : 5f, duration, Easing.OutQuint);
|
||||
iconContainer.FadeTo(Expanded.Value ? 1f : 0f, duration, Easing.OutQuint);
|
||||
|
||||
glow.FadeTo(Expanded.Value ? 0.4f : 0, duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// Move the count pill in the opposite direction to keep it pinned to the screen regardless of the X position of TopLevelContent.
|
||||
countPill.X = -TopLevelContent.X;
|
||||
}
|
||||
|
||||
public override MenuItem[] ContextMenuItems
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Item == null)
|
||||
return Array.Empty<MenuItem>();
|
||||
|
||||
return new MenuItem[]
|
||||
{
|
||||
new OsuMenuItem(Expanded.Value ? WebCommonStrings.ButtonsCollapse.ToSentence() : WebCommonStrings.ButtonsExpand.ToSentence(), MenuItemType.Highlighted, () => TriggerClick())
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,8 +144,9 @@ namespace osu.Game.Utils
|
||||
/// Returns a gamefield-space quad surrounding the provided hit objects.
|
||||
/// </summary>
|
||||
/// <param name="hitObjects">The hit objects to calculate a quad for.</param>
|
||||
public static Quad GetSurroundingQuad(IEnumerable<IHasPosition> hitObjects) =>
|
||||
GetSurroundingQuad(enumerateStartAndEndPositions(hitObjects));
|
||||
/// <param name="startAndEndOnly">Whether to only include the start and end positions of the slider, or include every control point in the slider.</param>
|
||||
public static Quad GetSurroundingQuad(IEnumerable<IHasPosition> hitObjects, bool startAndEndOnly = false) =>
|
||||
GetSurroundingQuad(startAndEndOnly ? enumerateStartAndEndPositions(hitObjects) : enumeratePositions(hitObjects));
|
||||
|
||||
/// <summary>
|
||||
/// Returns the points that make up the convex hull of the provided points.
|
||||
@@ -202,7 +203,7 @@ namespace osu.Game.Utils
|
||||
}
|
||||
|
||||
public static List<Vector2> GetConvexHull(IEnumerable<IHasPosition> hitObjects) =>
|
||||
GetConvexHull(enumerateStartAndEndPositions(hitObjects));
|
||||
GetConvexHull(enumeratePositions(hitObjects));
|
||||
|
||||
private static IEnumerable<Vector2> enumerateStartAndEndPositions(IEnumerable<IHasPosition> hitObjects) =>
|
||||
hitObjects.SelectMany(h =>
|
||||
@@ -220,6 +221,17 @@ namespace osu.Game.Utils
|
||||
return new[] { h.Position };
|
||||
});
|
||||
|
||||
private static IEnumerable<Vector2> enumeratePositions(IEnumerable<IHasPosition> hitObjects) =>
|
||||
hitObjects.SelectMany(h =>
|
||||
{
|
||||
if (h is IHasPath path)
|
||||
{
|
||||
return path.Path.ControlPoints.Select(p => h.Position + p.Position);
|
||||
}
|
||||
|
||||
return new[] { h.Position };
|
||||
});
|
||||
|
||||
#region Welzl helpers
|
||||
|
||||
// Function to check whether a point lies inside or on the boundaries of the circle
|
||||
|
||||
Reference in New Issue
Block a user