1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-13 15:33:21 +08:00

Merge branch 'master' into refactor-osu-difficulty-hit-object

This commit is contained in:
Dan Balasescu 2021-12-08 15:53:44 +09:00
commit 1a09a3469a
22 changed files with 837 additions and 273 deletions

View File

@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2); speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2);
double travelDistance = osuPrevObj?.TravelDistance ?? 0; double travelDistance = osuPrevObj?.TravelDistance ?? 0;
double distance = Math.Min(single_spacing_threshold, travelDistance + osuCurrObj.LazyJumpDistance); double distance = Math.Min(single_spacing_threshold, travelDistance + osuCurrObj.MinimumJumpDistance);
return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime; return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime;
} }

View File

@ -6,12 +6,14 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Beatmaps.Drawables.Cards;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
@ -227,7 +229,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
new BasicScrollContainer new BasicScrollContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = new FillFlowContainer Child = new ReverseChildIDFillFlowContainer<Drawable>
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
@ -248,6 +250,17 @@ namespace osu.Game.Tests.Visual.Beatmaps
} }
[Test] [Test]
public void TestNormal() => createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo)); public void TestNormal()
{
createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo));
AddToggleStep("toggle expanded state", expanded =>
{
var card = this.ChildrenOfType<BeatmapCard>().Last();
if (!card.Expanded.Disabled)
card.Expanded.Value = expanded;
});
AddToggleStep("disable/enable expansion", disabled => this.ChildrenOfType<BeatmapCard>().ForEach(card => card.Expanded.Disabled = disabled));
}
} }
} }

View File

@ -10,6 +10,7 @@ using osu.Game.Beatmaps;
using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Dialog;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Beatmaps.IO;
@ -89,6 +90,7 @@ namespace osu.Game.Tests.Visual.Editing
confirmEditingBeatmap(() => targetDifficulty); confirmEditingBeatmap(() => targetDifficulty);
AddAssert("no objects selected", () => !EditorBeatmap.SelectedHitObjects.Any()); AddAssert("no objects selected", () => !EditorBeatmap.SelectedHitObjects.Any());
AddUntilStep("wait for drawable ruleset", () => Editor.ChildrenOfType<DrawableRuleset>().SingleOrDefault()?.IsLoaded == true);
AddStep("paste object", () => Editor.Paste()); AddStep("paste object", () => Editor.Paste());
if (sameRuleset) if (sameRuleset)

View File

@ -11,12 +11,14 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Online.Rooms.RoomStatuses;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
using osuTK; using osuTK;
@ -172,6 +174,39 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha)); AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
} }
[Test]
public void TestMultiplayerRooms()
{
AddStep("create rooms", () => Child = new FillFlowContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
Spacing = new Vector2(5),
Children = new[]
{
new DrawableMatchRoom(new Room
{
Name = { Value = "A host-only room" },
QueueMode = { Value = QueueMode.HostOnly },
Type = { Value = MatchType.HeadToHead }
}),
new DrawableMatchRoom(new Room
{
Name = { Value = "An all-players, team-versus room" },
QueueMode = { Value = QueueMode.AllPlayers },
Type = { Value = MatchType.TeamVersus }
}),
new DrawableMatchRoom(new Room
{
Name = { Value = "A round-robin room" },
QueueMode = { Value = QueueMode.AllPlayersRoundRobin },
Type = { Value = MatchType.HeadToHead }
}),
}
});
}
private DrawableRoom createLoungeRoom(Room room) private DrawableRoom createLoungeRoom(Room room)
{ {
room.Host.Value ??= new APIUser { Username = "peppy", Id = 2 }; room.Host.Value ??= new APIUser { Username = "peppy", Id = 2 };

View File

@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
}); });
AddAssert("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus); AddUntilStep("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus);
AddAssert("user state arrived", () => client.Room?.Users.FirstOrDefault()?.MatchState is TeamVersusUserState); AddAssert("user state arrived", () => client.Room?.Users.FirstOrDefault()?.MatchState is TeamVersusUserState);
} }
@ -162,13 +162,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
}); });
AddAssert("room type is head to head", () => client.Room?.Settings.MatchType == MatchType.HeadToHead); AddUntilStep("room type is head to head", () => client.Room?.Settings.MatchType == MatchType.HeadToHead);
AddUntilStep("team displays are not displaying teams", () => multiplayerScreenStack.ChildrenOfType<TeamDisplay>().All(d => d.DisplayedTeam == null)); AddUntilStep("team displays are not displaying teams", () => multiplayerScreenStack.ChildrenOfType<TeamDisplay>().All(d => d.DisplayedTeam == null));
AddStep("change to team vs", () => client.ChangeSettings(matchType: MatchType.TeamVersus)); AddStep("change to team vs", () => client.ChangeSettings(matchType: MatchType.TeamVersus));
AddAssert("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus); AddUntilStep("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus);
AddUntilStep("team displays are displaying teams", () => multiplayerScreenStack.ChildrenOfType<TeamDisplay>().All(d => d.DisplayedTeam != null)); AddUntilStep("team displays are displaying teams", () => multiplayerScreenStack.ChildrenOfType<TeamDisplay>().All(d => d.DisplayedTeam != null));
} }

View File

@ -31,10 +31,12 @@ namespace osu.Game.Beatmaps.Drawables.Cards
public class BeatmapCard : OsuClickableContainer public class BeatmapCard : OsuClickableContainer
{ {
public const float TRANSITION_DURATION = 400; public const float TRANSITION_DURATION = 400;
public const float CORNER_RADIUS = 10;
public Bindable<bool> Expanded { get; } = new BindableBool();
private const float width = 408; private const float width = 408;
private const float height = 100; private const float height = 100;
private const float corner_radius = 10;
private const float icon_area_width = 30; private const float icon_area_width = 30;
private readonly APIBeatmapSet beatmapSet; private readonly APIBeatmapSet beatmapSet;
@ -42,6 +44,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
private readonly BeatmapDownloadTracker downloadTracker; private readonly BeatmapDownloadTracker downloadTracker;
private BeatmapCardContent content = null!;
private BeatmapCardThumbnail thumbnail = null!; private BeatmapCardThumbnail thumbnail = null!;
private Container rightAreaBackground = null!; private Container rightAreaBackground = null!;
@ -73,242 +77,247 @@ namespace osu.Game.Beatmaps.Drawables.Cards
{ {
Width = width; Width = width;
Height = height; Height = height;
CornerRadius = corner_radius;
Masking = true;
FillFlowContainer leftIconArea; FillFlowContainer leftIconArea;
GridContainer titleContainer; GridContainer titleContainer;
GridContainer artistContainer; GridContainer artistContainer;
InternalChildren = new Drawable[] InternalChild = content = new BeatmapCardContent(height)
{ {
downloadTracker, MainContent = new Container
rightAreaBackground = new Container
{ {
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Both,
Width = icon_area_width + 2 * corner_radius,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
// workaround for masking artifacts at the top & bottom of card,
// which become especially visible on downloaded beatmaps (when the icon area has a lime background).
Padding = new MarginPadding { Vertical = 1 },
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Colour4.White
},
},
thumbnail = new BeatmapCardThumbnail(beatmapSet)
{
Name = @"Left (icon) area",
Size = new Vector2(height),
Padding = new MarginPadding { Right = corner_radius },
Child = leftIconArea = new FillFlowContainer
{
Margin = new MarginPadding(5),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(1)
}
},
new Container
{
Name = @"Right (button) area",
Width = 30,
RelativeSizeAxes = Axes.Y,
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
Padding = new MarginPadding { Vertical = 17.5f },
Child = rightAreaButtons = new Container<BeatmapCardIconButton>
{
RelativeSizeAxes = Axes.Both,
Children = new BeatmapCardIconButton[]
{
new FavouriteButton(beatmapSet)
{
Current = favouriteState,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
},
new DownloadButton(beatmapSet)
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
State = { BindTarget = downloadTracker.State }
},
new GoToBeatmapButton(beatmapSet)
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
State = { BindTarget = downloadTracker.State }
}
}
}
},
mainContent = new Container
{
Name = @"Main content",
X = height - corner_radius,
Height = height,
CornerRadius = corner_radius,
Masking = true,
Children = new Drawable[] Children = new Drawable[]
{ {
mainContentBackground = new BeatmapCardContentBackground(beatmapSet) downloadTracker,
rightAreaBackground = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Y,
}, Width = icon_area_width + 2 * CORNER_RADIUS,
new FillFlowContainer Anchor = Anchor.CentreRight,
{ Origin = Anchor.CentreRight,
RelativeSizeAxes = Axes.Both, // workaround for masking artifacts at the top & bottom of card,
Padding = new MarginPadding // which become especially visible on downloaded beatmaps (when the icon area has a lime background).
Padding = new MarginPadding { Vertical = 1 },
Child = new Box
{ {
Horizontal = 10, RelativeSizeAxes = Axes.Both,
Vertical = 4 Colour = Colour4.White
}, },
Direction = FillDirection.Vertical, },
Children = new Drawable[] thumbnail = new BeatmapCardThumbnail(beatmapSet)
{
Name = @"Left (icon) area",
Size = new Vector2(height),
Padding = new MarginPadding { Right = CORNER_RADIUS },
Child = leftIconArea = new FillFlowContainer
{ {
titleContainer = new GridContainer Margin = new MarginPadding(5),
{ AutoSizeAxes = Axes.Both,
RelativeSizeAxes = Axes.X, Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Y, Spacing = new Vector2(1)
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.AutoSize)
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize)
},
Content = new[]
{
new[]
{
new OsuSpriteText
{
Text = new RomanisableString(beatmapSet.TitleUnicode, beatmapSet.Title),
Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold),
RelativeSizeAxes = Axes.X,
Truncate = true
},
Empty()
}
}
},
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 OsuSpriteText
{
Text = createArtistText(),
Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold),
RelativeSizeAxes = Axes.X,
Truncate = true
},
Empty()
},
}
},
new LinkFlowContainer(s =>
{
s.Shadow = false;
s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold);
}).With(d =>
{
d.AutoSizeAxes = Axes.Both;
d.Margin = new MarginPadding { Top = 2 };
d.AddText("mapped by ", t => t.Colour = colourProvider.Content2);
d.AddUserLink(beatmapSet.Author);
}),
} }
}, },
new Container new Container
{ {
Name = @"Bottom content", Name = @"Right (button) area",
RelativeSizeAxes = Axes.X, Width = 30,
AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Anchor = Anchor.BottomLeft, Origin = Anchor.TopRight,
Origin = Anchor.BottomLeft, Anchor = Anchor.TopRight,
Padding = new MarginPadding Padding = new MarginPadding { Vertical = 17.5f },
Child = rightAreaButtons = new Container<BeatmapCardIconButton>
{ {
Horizontal = 10, RelativeSizeAxes = Axes.Both,
Vertical = 4 Children = new BeatmapCardIconButton[]
}, {
new FavouriteButton(beatmapSet)
{
Current = favouriteState,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
},
new DownloadButton(beatmapSet)
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
State = { BindTarget = downloadTracker.State }
},
new GoToBeatmapButton(beatmapSet)
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
State = { BindTarget = downloadTracker.State }
}
}
}
},
mainContent = new Container
{
Name = @"Main content",
X = height - CORNER_RADIUS,
Height = height,
CornerRadius = CORNER_RADIUS,
Masking = true,
Children = new Drawable[] Children = new Drawable[]
{ {
idleBottomContent = new FillFlowContainer mainContentBackground = new BeatmapCardContentBackground(beatmapSet)
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.Both,
AutoSizeAxes = Axes.Y, },
new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
{
Horizontal = 10,
Vertical = 4
},
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 3),
AlwaysPresent = true,
Children = new Drawable[] Children = new Drawable[]
{ {
statisticsContainer = new FillFlowContainer<BeatmapCardStatistic> titleContainer = new GridContainer
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal, ColumnDimensions = new[]
Spacing = new Vector2(10, 0),
Alpha = 0,
AlwaysPresent = true,
ChildrenEnumerable = createStatistics()
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(4, 0),
Children = new Drawable[]
{ {
new BeatmapSetOnlineStatusPill new Dimension(),
new Dimension(GridSizeMode.AutoSize)
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize)
},
Content = new[]
{
new[]
{ {
AutoSizeAxes = Axes.Both, new OsuSpriteText
Status = beatmapSet.Status, {
Anchor = Anchor.CentreLeft, Text = new RomanisableString(beatmapSet.TitleUnicode, beatmapSet.Title),
Origin = Anchor.CentreLeft Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold),
}, RelativeSizeAxes = Axes.X,
new DifficultySpectrumDisplay(beatmapSet) Truncate = true
{ },
Anchor = Anchor.CentreLeft, Empty()
Origin = Anchor.CentreLeft,
DotSize = new Vector2(6, 12)
} }
} }
} },
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 OsuSpriteText
{
Text = createArtistText(),
Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold),
RelativeSizeAxes = Axes.X,
Truncate = true
},
Empty()
},
}
},
new LinkFlowContainer(s =>
{
s.Shadow = false;
s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold);
}).With(d =>
{
d.AutoSizeAxes = Axes.Both;
d.Margin = new MarginPadding { Top = 2 };
d.AddText("mapped by ", t => t.Colour = colourProvider.Content2);
d.AddUserLink(beatmapSet.Author);
}),
} }
}, },
downloadProgressBar = new BeatmapCardDownloadProgressBar new Container
{ {
Name = @"Bottom content",
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = 6, AutoSizeAxes = Axes.Y,
Anchor = Anchor.Centre, Anchor = Anchor.BottomLeft,
Origin = Anchor.Centre, Origin = Anchor.BottomLeft,
State = { BindTarget = downloadTracker.State }, Padding = new MarginPadding
Progress = { BindTarget = downloadTracker.Progress } {
Horizontal = 10,
Vertical = 4
},
Children = new Drawable[]
{
idleBottomContent = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 3),
AlwaysPresent = true,
Children = new Drawable[]
{
statisticsContainer = new FillFlowContainer<BeatmapCardStatistic>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
Alpha = 0,
AlwaysPresent = true,
ChildrenEnumerable = createStatistics()
},
new BeatmapCardExtraInfoRow(beatmapSet)
{
Hovered = _ =>
{
content.ScheduleShow();
return false;
},
Unhovered = _ =>
{
// This hide should only trigger if the expanded content has not shown yet.
// ie. if the user has not shown intent to want to see it (quickly moved over the info row area).
if (!Expanded.Value)
content.ScheduleHide();
}
}
}
},
downloadProgressBar = new BeatmapCardDownloadProgressBar
{
RelativeSizeAxes = Axes.X,
Height = 6,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
State = { BindTarget = downloadTracker.State },
Progress = { BindTarget = downloadTracker.Progress }
}
}
} }
} }
} }
} }
} },
ExpandedContent = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = 10, Vertical = 13 },
Child = new BeatmapCardDifficultyList(beatmapSet)
},
Expanded = { BindTarget = Expanded }
}; };
if (beatmapSet.HasVideo) if (beatmapSet.HasVideo)
@ -344,7 +353,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
{ {
base.LoadComplete(); base.LoadComplete();
downloadTracker.State.BindValueChanged(_ => updateState(), true); downloadTracker.State.BindValueChanged(_ => updateState());
Expanded.BindValueChanged(_ => updateState(), true);
FinishTransforms(true); FinishTransforms(true);
} }
@ -356,6 +366,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
protected override void OnHoverLost(HoverLostEvent e) protected override void OnHoverLost(HoverLostEvent e)
{ {
content.ScheduleHide();
updateState(); updateState();
base.OnHoverLost(e); base.OnHoverLost(e);
} }
@ -386,19 +398,25 @@ namespace osu.Game.Beatmaps.Drawables.Cards
private void updateState() private void updateState()
{ {
float targetWidth = width - height; bool showDetails = IsHovered || Expanded.Value;
if (IsHovered)
targetWidth = targetWidth - icon_area_width + corner_radius;
thumbnail.Dimmed.Value = IsHovered; float targetWidth = width - height;
if (showDetails)
targetWidth = targetWidth - icon_area_width + CORNER_RADIUS;
thumbnail.Dimmed.Value = showDetails;
// Scale value is intentionally chosen to fit in the spacing of listing displays, as to not overlap horizontally with adjacent cards.
// This avoids depth issues where a hovered (scaled) card to the right of another card would be beneath the card to the left.
content.ScaleTo(Expanded.Value ? 1.03f : 1, 500, Easing.OutQuint);
mainContent.ResizeWidthTo(targetWidth, TRANSITION_DURATION, Easing.OutQuint); mainContent.ResizeWidthTo(targetWidth, TRANSITION_DURATION, Easing.OutQuint);
mainContentBackground.Dimmed.Value = IsHovered; mainContentBackground.Dimmed.Value = showDetails;
statisticsContainer.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); statisticsContainer.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint);
rightAreaBackground.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, TRANSITION_DURATION, Easing.OutQuint); rightAreaBackground.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, TRANSITION_DURATION, Easing.OutQuint);
rightAreaButtons.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); rightAreaButtons.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint);
foreach (var button in rightAreaButtons) foreach (var button in rightAreaButtons)
{ {

View File

@ -0,0 +1,234 @@
// 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.
#nullable enable
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Framework.Utils;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
using osuTK;
namespace osu.Game.Beatmaps.Drawables.Cards
{
public class BeatmapCardContent : CompositeDrawable
{
public Drawable MainContent
{
set => bodyContent.Child = value;
}
public Drawable ExpandedContent
{
set => dropdownScroll.Child = value;
}
public Bindable<bool> Expanded { get; } = new BindableBool();
private readonly Box background;
private readonly Container content;
private readonly Container bodyContent;
private readonly Container dropdownContent;
private readonly OsuScrollContainer dropdownScroll;
private readonly Container borderContainer;
public BeatmapCardContent(float height)
{
RelativeSizeAxes = Axes.X;
Height = height;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
InternalChild = content = new HoverHandlingContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
CornerRadius = BeatmapCard.CORNER_RADIUS,
Masking = true,
Unhovered = _ => checkForHide(),
Children = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both
},
bodyContent = new Container
{
RelativeSizeAxes = Axes.X,
Height = height,
CornerRadius = BeatmapCard.CORNER_RADIUS,
Masking = true,
},
dropdownContent = new HoverHandlingContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Top = height },
Alpha = 0,
Hovered = _ =>
{
keep();
return true;
},
Unhovered = _ => checkForHide(),
Child = dropdownScroll = new ExpandedContentScrollContainer
{
RelativeSizeAxes = Axes.X,
ScrollbarVisible = false
}
},
borderContainer = new Container
{
RelativeSizeAxes = Axes.Both,
CornerRadius = BeatmapCard.CORNER_RADIUS,
Masking = true,
BorderThickness = 3,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
}
};
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
background.Colour = colourProvider.Background2;
borderContainer.BorderColour = colourProvider.Highlight1;
}
protected override void LoadComplete()
{
base.LoadComplete();
Expanded.BindValueChanged(_ => updateState(), true);
FinishTransforms(true);
}
private ScheduledDelegate? scheduledExpandedChange;
public void ScheduleShow()
{
scheduledExpandedChange?.Cancel();
if (Expanded.Disabled || Expanded.Value)
return;
scheduledExpandedChange = Scheduler.AddDelayed(() =>
{
if (!Expanded.Disabled)
Expanded.Value = true;
}, 100);
}
public void ScheduleHide()
{
scheduledExpandedChange?.Cancel();
if (Expanded.Disabled || !Expanded.Value)
return;
scheduledExpandedChange = Scheduler.AddDelayed(() =>
{
if (!Expanded.Disabled)
Expanded.Value = false;
}, 500);
}
private void checkForHide()
{
if (Expanded.Disabled)
return;
if (content.IsHovered || dropdownContent.IsHovered)
return;
scheduledExpandedChange?.Cancel();
Expanded.Value = false;
}
private void keep()
{
if (Expanded.Disabled)
return;
scheduledExpandedChange?.Cancel();
Expanded.Value = true;
}
private void updateState()
{
background.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
dropdownContent.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
borderContainer.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
content.TweenEdgeEffectTo(new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0, 2),
Radius = 10,
Colour = Colour4.Black.Opacity(Expanded.Value ? 0.3f : 0f),
Hollow = true,
}, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
}
private class ExpandedContentScrollContainer : OsuScrollContainer
{
public ExpandedContentScrollContainer()
{
ScrollbarVisible = false;
}
protected override void Update()
{
base.Update();
Height = Math.Min(Content.DrawHeight, 400);
}
private bool allowScroll => !Precision.AlmostEquals(DrawSize, Content.DrawSize);
protected override bool OnDragStart(DragStartEvent e)
{
if (!allowScroll)
return false;
return base.OnDragStart(e);
}
protected override void OnDrag(DragEvent e)
{
if (!allowScroll)
return;
base.OnDrag(e);
}
protected override void OnDragEnd(DragEndEvent e)
{
if (!allowScroll)
return;
base.OnDragEnd(e);
}
protected override bool OnScroll(ScrollEvent e)
{
if (!allowScroll)
return false;
return base.OnScroll(e);
}
}
}
}

View File

@ -0,0 +1,43 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.API.Requests.Responses;
using osuTK;
namespace osu.Game.Beatmaps.Drawables.Cards
{
public class BeatmapCardExtraInfoRow : HoverHandlingContainer
{
public BeatmapCardExtraInfoRow(APIBeatmapSet beatmapSet)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(4, 0),
Children = new Drawable[]
{
new BeatmapSetOnlineStatusPill
{
AutoSizeAxes = Axes.Both,
Status = beatmapSet.Status,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
new DifficultySpectrumDisplay(beatmapSet)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
DotSize = new Vector2(6, 12)
}
}
};
}
}
}

View File

@ -0,0 +1,29 @@
// 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.
#nullable enable
using System;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
namespace osu.Game.Beatmaps.Drawables.Cards
{
public class HoverHandlingContainer : Container
{
public Func<HoverEvent, bool>? Hovered { get; set; }
public Action<HoverLostEvent>? Unhovered { get; set; }
protected override bool OnHover(HoverEvent e)
{
bool handledByBase = base.OnHover(e);
return Hovered?.Invoke(e) ?? handledByBase;
}
protected override void OnHoverLost(HoverLostEvent e)
{
base.OnHoverLost(e);
Unhovered?.Invoke(e);
}
}
}

View File

@ -163,7 +163,7 @@ namespace osu.Game.Online.Multiplayer
Debug.Assert(joinedRoom != null); Debug.Assert(joinedRoom != null);
// Populate playlist items. // Populate playlist items.
var playlistItems = await Task.WhenAll(joinedRoom.Playlist.Select(createPlaylistItem)).ConfigureAwait(false); var playlistItems = await Task.WhenAll(joinedRoom.Playlist.Select(item => createPlaylistItem(item, item.ID == joinedRoom.Settings.PlaylistItemId))).ConfigureAwait(false);
// Populate users. // Populate users.
Debug.Assert(joinedRoom.Users != null); Debug.Assert(joinedRoom.Users != null);
@ -470,7 +470,32 @@ namespace osu.Game.Online.Multiplayer
Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings) Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings)
{ {
Scheduler.Add(() => updateLocalRoomSettings(newSettings)); Debug.Assert(APIRoom != null);
Debug.Assert(Room != null);
Scheduler.Add(() =>
{
// ensure the new selected item is populated immediately.
var playlistItem = APIRoom.Playlist.Single(p => p.ID == newSettings.PlaylistItemId);
if (playlistItem != null)
{
GetAPIBeatmap(playlistItem.BeatmapID).ContinueWith(b =>
{
// Should be called outside of the `Scheduler` logic (and specifically accessing `Exception`) to suppress an exception from firing outwards.
bool success = b.Exception == null;
Scheduler.Add(() =>
{
if (success)
playlistItem.Beatmap.Value = b.Result;
updateLocalRoomSettings(newSettings);
});
});
}
});
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -629,7 +654,7 @@ namespace osu.Game.Online.Multiplayer
if (Room == null) if (Room == null)
return; return;
var playlistItem = await createPlaylistItem(item).ConfigureAwait(false); var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false);
Scheduler.Add(() => Scheduler.Add(() =>
{ {
@ -673,7 +698,7 @@ namespace osu.Game.Online.Multiplayer
if (Room == null) if (Room == null)
return; return;
var playlistItem = await createPlaylistItem(item).ConfigureAwait(false); var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false);
Scheduler.Add(() => Scheduler.Add(() =>
{ {
@ -728,10 +753,8 @@ namespace osu.Game.Online.Multiplayer
CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId); CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId);
} }
private async Task<PlaylistItem> createPlaylistItem(MultiplayerPlaylistItem item) private async Task<PlaylistItem> createPlaylistItem(MultiplayerPlaylistItem item, bool populateBeatmapImmediately)
{ {
var apiBeatmap = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false);
var ruleset = Rulesets.GetRuleset(item.RulesetID); var ruleset = Rulesets.GetRuleset(item.RulesetID);
Debug.Assert(ruleset != null); Debug.Assert(ruleset != null);
@ -741,8 +764,8 @@ namespace osu.Game.Online.Multiplayer
var playlistItem = new PlaylistItem var playlistItem = new PlaylistItem
{ {
ID = item.ID, ID = item.ID,
BeatmapID = item.BeatmapID,
OwnerID = item.OwnerID, OwnerID = item.OwnerID,
Beatmap = { Value = apiBeatmap },
Ruleset = { Value = ruleset }, Ruleset = { Value = ruleset },
Expired = item.Expired, Expired = item.Expired,
PlaylistOrder = item.PlaylistOrder, PlaylistOrder = item.PlaylistOrder,
@ -752,6 +775,9 @@ namespace osu.Game.Online.Multiplayer
playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))); playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance)));
playlistItem.AllowedMods.AddRange(item.AllowedMods.Select(m => m.ToMod(rulesetInstance))); playlistItem.AllowedMods.AddRange(item.AllowedMods.Select(m => m.ToMod(rulesetInstance)));
if (populateBeatmapImmediately)
playlistItem.Beatmap.Value = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false);
return playlistItem; return playlistItem;
} }

View File

@ -151,7 +151,8 @@ namespace osu.Game.Overlays
} }
// spawn new children with the contained so we only clear old content at the last moment. // spawn new children with the contained so we only clear old content at the last moment.
var content = new FillFlowContainer<BeatmapCard> // reverse ID flow is required for correct Z-ordering of the cards' expandable content (last card should be front-most).
var content = new ReverseChildIDFillFlowContainer<BeatmapCard>
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
@ -22,7 +23,7 @@ namespace osu.Game.Overlays.Profile
public abstract string Identifier { get; } public abstract string Identifier { get; }
private readonly FillFlowContainer content; private readonly FillFlowContainer<Drawable> content;
private readonly Box background; private readonly Box background;
private readonly Box underscore; private readonly Box underscore;
@ -79,7 +80,9 @@ namespace osu.Game.Overlays.Profile
} }
} }
}, },
content = new FillFlowContainer // reverse ID flow is required for correct Z-ordering of the content (last item should be front-most).
// particularly important in BeatmapsSection, as it uses beatmap cards, which have expandable overhanging content.
content = new ReverseChildIDFillFlowContainer<Drawable>
{ {
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,

View File

@ -13,6 +13,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osuTK; using osuTK;
@ -26,7 +27,7 @@ namespace osu.Game.Overlays.Profile.Sections
protected int VisiblePages; protected int VisiblePages;
protected int ItemsPerPage; protected int ItemsPerPage;
protected FillFlowContainer ItemsContainer { get; private set; } protected ReverseChildIDFillFlowContainer<Drawable> ItemsContainer { get; private set; }
private APIRequest<List<TModel>> retrievalRequest; private APIRequest<List<TModel>> retrievalRequest;
private CancellationTokenSource loadCancellation; private CancellationTokenSource loadCancellation;
@ -48,11 +49,15 @@ namespace osu.Game.Overlays.Profile.Sections
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Children = new Drawable[] Children = new Drawable[]
{ {
ItemsContainer = new FillFlowContainer // reverse ID flow is required for correct Z-ordering of the items (last item should be front-most).
// particularly important in PaginatedBeatmapContainer, as it uses beatmap cards, which have expandable overhanging content.
ItemsContainer = new ReverseChildIDFillFlowContainer<Drawable>
{ {
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Spacing = new Vector2(0, 2), Spacing = new Vector2(0, 2),
// ensure the container and its contents are in front of the "more" button.
Depth = float.MinValue
}, },
moreButton = new ShowMoreButton moreButton = new ShowMoreButton
{ {

View File

@ -135,7 +135,8 @@ namespace osu.Game.Overlays.Rankings
Children = new Drawable[] Children = new Drawable[]
{ {
new ScoresTable(1, response.Users), new ScoresTable(1, response.Users),
new FillFlowContainer // reverse ID flow is required for correct Z-ordering of the cards' expandable content (last card should be front-most).
new ReverseChildIDFillFlowContainer<BeatmapCard>
{ {
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,

View File

@ -83,7 +83,9 @@ namespace osu.Game.Screens.Edit.Compose
{ {
base.LoadComplete(); base.LoadComplete();
EditorBeatmap.SelectedHitObjects.BindCollectionChanged((_, __) => updateClipboardActionAvailability()); EditorBeatmap.SelectedHitObjects.BindCollectionChanged((_, __) => updateClipboardActionAvailability());
clipboard.BindValueChanged(_ => updateClipboardActionAvailability(), true); clipboard.BindValueChanged(_ => updateClipboardActionAvailability());
composer.OnLoadComplete += _ => updateClipboardActionAvailability();
updateClipboardActionAvailability();
} }
#region Clipboard operations #region Clipboard operations
@ -131,7 +133,7 @@ namespace osu.Game.Screens.Edit.Compose
private void updateClipboardActionAvailability() private void updateClipboardActionAvailability()
{ {
CanCut.Value = CanCopy.Value = EditorBeatmap.SelectedHitObjects.Any(); CanCut.Value = CanCopy.Value = EditorBeatmap.SelectedHitObjects.Any();
CanPaste.Value = !string.IsNullOrEmpty(clipboard.Value); CanPaste.Value = composer.IsLoaded && !string.IsNullOrEmpty(clipboard.Value);
} }
private string formatSelectionAsString() private string formatSelectionAsString()

View File

@ -80,7 +80,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
private void updateRange(object sender, NotifyCollectionChangedEventArgs e) private void updateRange(object sender, NotifyCollectionChangedEventArgs e)
{ {
var orderedDifficulties = Playlist.Select(p => p.Beatmap.Value).OrderBy(b => b.StarRating).ToArray(); var orderedDifficulties = Playlist.Where(p => p.Beatmap.Value != null).Select(p => p.Beatmap.Value).OrderBy(b => b.StarRating).ToArray();
StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0); StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0);
StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0); StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0);

View File

@ -16,6 +16,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps.Drawables;
using osu.Game.Database; using osu.Game.Database;
@ -50,6 +51,7 @@ namespace osu.Game.Screens.OnlinePlay
private LinkFlowContainer authorText; private LinkFlowContainer authorText;
private ExplicitContentBeatmapPill explicitContentPill; private ExplicitContentBeatmapPill explicitContentPill;
private ModDisplay modDisplay; private ModDisplay modDisplay;
private FillFlowContainer buttonsFlow;
private UpdateableAvatar ownerAvatar; private UpdateableAvatar ownerAvatar;
private readonly IBindable<bool> valid = new Bindable<bool>(); private readonly IBindable<bool> valid = new Bindable<bool>();
@ -66,10 +68,19 @@ namespace osu.Game.Screens.OnlinePlay
[Resolved] [Resolved]
private UserLookupCache userLookupCache { get; set; } private UserLookupCache userLookupCache { get; set; }
[Resolved]
private BeatmapLookupCache beatmapLookupCache { get; set; }
private PanelBackground panelBackground;
private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both };
private readonly bool allowEdit; private readonly bool allowEdit;
private readonly bool allowSelection; private readonly bool allowSelection;
private readonly bool showItemOwner; private readonly bool showItemOwner;
private FillFlowContainer mainFillFlow;
protected override bool ShouldBeConsideredForInput(Drawable child) => allowEdit || !allowSelection || SelectedItem.Value == Model; protected override bool ShouldBeConsideredForInput(Drawable child) => allowEdit || !allowSelection || SelectedItem.Value == Model;
public DrawableRoomPlaylistItem(PlaylistItem item, bool allowEdit, bool allowSelection, bool showItemOwner) public DrawableRoomPlaylistItem(PlaylistItem item, bool allowEdit, bool allowSelection, bool showItemOwner)
@ -130,11 +141,34 @@ namespace osu.Game.Screens.OnlinePlay
valid.BindValueChanged(_ => Scheduler.AddOnce(refresh)); valid.BindValueChanged(_ => Scheduler.AddOnce(refresh));
requiredMods.CollectionChanged += (_, __) => Scheduler.AddOnce(refresh); requiredMods.CollectionChanged += (_, __) => Scheduler.AddOnce(refresh);
onScreenLoader.DelayedLoadStarted += _ =>
{
Task.Run(async () =>
{
try
{
if (showItemOwner)
{
var foundUser = await userLookupCache.GetUserAsync(Item.OwnerID).ConfigureAwait(false);
Schedule(() => ownerAvatar.User = foundUser);
}
if (Item.Beatmap.Value == null)
{
var foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false);
Schedule(() => Item.Beatmap.Value = foundBeatmap);
}
}
catch (Exception e)
{
Logger.Log($"Error while populating playlist item {e}");
}
});
};
refresh(); refresh();
} }
private PanelBackground panelBackground;
private void refresh() private void refresh()
{ {
if (!valid.Value) if (!valid.Value)
@ -143,22 +177,22 @@ namespace osu.Game.Screens.OnlinePlay
maskingContainer.BorderColour = colours.Red; maskingContainer.BorderColour = colours.Red;
} }
if (showItemOwner) if (Item.Beatmap.Value != null)
{ difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(ICON_HEIGHT) };
ownerAvatar.Show(); else
userLookupCache.GetUserAsync(Item.OwnerID) difficultyIconContainer.Clear();
.ContinueWith(u => Schedule(() => ownerAvatar.User = u.Result), TaskContinuationOptions.OnlyOnRanToCompletion);
}
difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(ICON_HEIGHT) };
panelBackground.Beatmap.Value = Item.Beatmap.Value; panelBackground.Beatmap.Value = Item.Beatmap.Value;
beatmapText.Clear(); beatmapText.Clear();
beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineID.ToString(), null, text =>
if (Item.Beatmap.Value != null)
{ {
text.Truncate = true; beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineID.ToString(), null, text =>
}); {
text.Truncate = true;
});
}
authorText.Clear(); authorText.Clear();
@ -168,10 +202,16 @@ namespace osu.Game.Screens.OnlinePlay
authorText.AddUserLink(Item.Beatmap.Value.Metadata.Author); authorText.AddUserLink(Item.Beatmap.Value.Metadata.Author);
} }
bool hasExplicitContent = (Item.Beatmap.Value.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true; bool hasExplicitContent = (Item.Beatmap.Value?.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true;
explicitContentPill.Alpha = hasExplicitContent ? 1 : 0; explicitContentPill.Alpha = hasExplicitContent ? 1 : 0;
modDisplay.Current.Value = requiredMods.ToArray(); modDisplay.Current.Value = requiredMods.ToArray();
buttonsFlow.Clear();
buttonsFlow.ChildrenEnumerable = CreateButtons();
difficultyIconContainer.FadeInFromZero(500, Easing.OutQuint);
mainFillFlow.FadeInFromZero(500, Easing.OutQuint);
} }
protected override Drawable CreateContent() protected override Drawable CreateContent()
@ -192,6 +232,7 @@ namespace osu.Game.Screens.OnlinePlay
Alpha = 0, Alpha = 0,
AlwaysPresent = true AlwaysPresent = true
}, },
onScreenLoader,
panelBackground = new PanelBackground panelBackground = new PanelBackground
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -217,7 +258,7 @@ namespace osu.Game.Screens.OnlinePlay
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Left = 8, Right = 8 }, Margin = new MarginPadding { Left = 8, Right = 8 },
}, },
new FillFlowContainer mainFillFlow = new FillFlowContainer
{ {
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
@ -273,7 +314,7 @@ namespace osu.Game.Screens.OnlinePlay
} }
} }
}, },
new FillFlowContainer buttonsFlow = new FillFlowContainer
{ {
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
@ -305,9 +346,9 @@ namespace osu.Game.Screens.OnlinePlay
} }
protected virtual IEnumerable<Drawable> CreateButtons() => protected virtual IEnumerable<Drawable> CreateButtons() =>
new Drawable[] new[]
{ {
new PlaylistDownloadButton(Item), Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item),
new PlaylistRemoveButton new PlaylistRemoveButton
{ {
Size = new Vector2(30, 30), Size = new Vector2(30, 30),

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
@ -184,20 +185,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
Spacing = new Vector2(5), Spacing = new Vector2(5),
Children = new Drawable[] ChildrenEnumerable = CreateBottomDetails()
{
new PlaylistCountPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
new StarRatingRangeDisplay
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Scale = new Vector2(0.8f)
}
}
} }
} }
}, },
@ -287,6 +275,37 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
protected virtual Drawable CreateBackground() => new OnlinePlayBackgroundSprite(); protected virtual Drawable CreateBackground() => new OnlinePlayBackgroundSprite();
protected virtual IEnumerable<Drawable> CreateBottomDetails()
{
var pills = new List<Drawable>();
if (Room.Type.Value != MatchType.Playlists)
{
pills.AddRange(new OnlinePlayComposite[]
{
new MatchTypePill(),
new QueueModePill(),
});
}
pills.AddRange(new Drawable[]
{
new PlaylistCountPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
new StarRatingRangeDisplay
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Scale = new Vector2(0.8f)
}
});
return pills;
}
private class RoomNameText : OsuSpriteText private class RoomNameText : OsuSpriteText
{ {
[Resolved(typeof(Room), nameof(Online.Rooms.Room.Name))] [Resolved(typeof(Room), nameof(Online.Rooms.Room.Name))]

View File

@ -0,0 +1,50 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public class MatchTypePill : OnlinePlayComposite
{
private OsuTextFlowContainer textFlow;
public MatchTypePill()
{
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new PillContainer
{
Child = textFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12))
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
Type.BindValueChanged(onMatchTypeChanged, true);
}
private void onMatchTypeChanged(ValueChangedEvent<MatchType> type)
{
textFlow.Clear();
textFlow.AddText(type.NewValue.GetLocalisableDescription());
}
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.Multiplayer;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public class QueueModePill : OnlinePlayComposite
{
private OsuTextFlowContainer textFlow;
public QueueModePill()
{
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new PillContainer
{
Child = textFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12))
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
QueueMode.BindValueChanged(onQueueModeChanged, true);
}
private void onQueueModeChanged(ValueChangedEvent<QueueMode> mode)
{
textFlow.Clear();
textFlow.AddText(mode.NewValue.GetLocalisableDescription());
}
}
}

View File

@ -228,7 +228,10 @@ namespace osu.Game.Screens.Play
onlineBeatmapRequest.Success += beatmapSet => Schedule(() => onlineBeatmapRequest.Success += beatmapSet => Schedule(() =>
{ {
this.beatmapSet = beatmapSet; this.beatmapSet = beatmapSet;
beatmapPanelContainer.Child = new BeatmapCard(this.beatmapSet); beatmapPanelContainer.Child = new BeatmapCard(this.beatmapSet)
{
Expanded = { Disabled = true }
};
checkForAutomaticDownload(); checkForAutomaticDownload();
}); });

View File

@ -4,9 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -89,15 +87,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay
getRoomRequest.TriggerSuccess(createResponseRoom(ServerSideRooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId), true)); getRoomRequest.TriggerSuccess(createResponseRoom(ServerSideRooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId), true));
return true; return true;
case GetBeatmapSetRequest getBeatmapSetRequest:
var onlineReq = new GetBeatmapSetRequest(getBeatmapSetRequest.ID, getBeatmapSetRequest.Type);
onlineReq.Success += res => getBeatmapSetRequest.TriggerSuccess(res);
onlineReq.Failure += e => getBeatmapSetRequest.TriggerFailure(e);
// Get the online API from the game's dependencies.
game.Dependencies.Get<IAPIProvider>().Queue(onlineReq);
return true;
case CreateRoomScoreRequest createRoomScoreRequest: case CreateRoomScoreRequest createRoomScoreRequest:
createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 }); createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 });
return true; return true;