From b4fa2d90491066ac834d18be5517de473da0ac05 Mon Sep 17 00:00:00 2001 From: jorolf Date: Sat, 22 Dec 2018 16:51:24 +0100 Subject: [PATCH 001/623] WIP --- osu.Game.Tests/Visual/TestCaseUserPanel.cs | 1 + osu.Game.Tests/Visual/TestCaseUserProfile.cs | 15 +- .../Graphics/Containers/OsuHoverContainer.cs | 3 +- osu.Game/Graphics/OsuColour.cs | 10 + .../Overlays/Profile/Header/SupporterIcon.cs | 82 +- osu.Game/Overlays/Profile/ProfileHeader.cs | 1089 +++++++++++++---- osu.Game/Overlays/UserProfileOverlay.cs | 6 +- osu.Game/Users/User.cs | 21 + osu.Game/Users/UserPanel.cs | 4 +- osu.Game/Users/UserStatistics.cs | 18 + osu.sln.DotSettings | 1 + 11 files changed, 981 insertions(+), 269 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseUserPanel.cs b/osu.Game.Tests/Visual/TestCaseUserPanel.cs index a53af247f3..81b7465d47 100644 --- a/osu.Game.Tests/Visual/TestCaseUserPanel.cs +++ b/osu.Game.Tests/Visual/TestCaseUserPanel.cs @@ -38,6 +38,7 @@ namespace osu.Game.Tests.Visual Country = new Country { FlagName = @"AU" }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", IsSupporter = true, + SupportLevel = 3, }) { Width = 300 }, }, }); diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs index cb281d045b..ce41bc22ff 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays; @@ -27,7 +28,9 @@ namespace osu.Game.Tests.Visual typeof(UserProfileOverlay), typeof(RankGraph), typeof(LineGraph), - typeof(BadgeContainer) + typeof(BadgeContainer), + typeof(SectionsContainer<>), + typeof(SupporterIcon) }; public TestCaseUserProfile() @@ -58,6 +61,11 @@ namespace osu.Game.Tests.Visual { Ranks = new UserStatistics.UserRanks { Global = 2148, Country = 1 }, PP = 4567.89m, + Level = new UserStatistics.LevelInfo + { + Current = 727, + Progress = 69, + } }, RankHistory = new User.RankHistoryData { @@ -72,7 +80,10 @@ namespace osu.Game.Tests.Visual Description = "Outstanding help by being a voluntary test subject.", ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.jpg" } - } + }, + Title = "osu!volunteer", + Colour = "ff0000", + Achievements = new User.UserAchievement[0], }, false)); checkSupporterTag(false); diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs index af804735a8..418ccac290 100644 --- a/osu.Game/Graphics/Containers/OsuHoverContainer.cs +++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs @@ -33,7 +33,8 @@ namespace osu.Game.Graphics.Containers [BackgroundDependencyLoader] private void load(OsuColour colours) { - HoverColour = colours.Yellow; + if(HoverColour == default) + HoverColour = colours.Yellow; } protected override void LoadComplete() diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index fc627fa501..b069f2bf0e 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -92,5 +92,15 @@ namespace osu.Game.Graphics public readonly Color4 ChatBlue = FromHex(@"17292e"); public readonly Color4 ContextMenuGray = FromHex(@"223034"); + + public readonly Color4 CommunityUserGreenLight = FromHex(@"deff87"); + public readonly Color4 CommunityUserGreen = FromHex(@"05ffa2"); + public readonly Color4 CommunityUserGreenDark = FromHex(@"a6cc00"); + public readonly Color4 CommunityUserGrayGreenLighter = FromHex(@"9ebab1"); + public readonly Color4 CommunityUserGrayGreenLight = FromHex(@"77998e"); + public readonly Color4 CommunityUserGrayGreen = FromHex(@"4e7466"); + public readonly Color4 CommunityUserGrayGreenDark = FromHex(@"33413c"); + public readonly Color4 CommunityUserGrayGreenDarker = FromHex(@"2c3532"); + public readonly Color4 CommunityUserGrayGreenDarkest = FromHex(@"1e2422"); } } diff --git a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs index 1325ea4e9a..8b33a60d37 100644 --- a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; using osuTK; namespace osu.Game.Overlays.Profile.Header @@ -15,50 +14,73 @@ namespace osu.Game.Overlays.Profile.Header public class SupporterIcon : CircularContainer, IHasTooltip { private readonly Box background; + private readonly FillFlowContainer iconContainer; public string TooltipText => "osu!supporter"; + public int SupporterLevel + { + set + { + if (value == 0) + { + Hide(); + } + else + { + Show(); + iconContainer.Clear(); + for (int i = 0; i < value; i++) + { + iconContainer.Add(new SpriteIcon + { + Width = 12, + RelativeSizeAxes = Axes.Y, + Icon = FontAwesome.fa_heart, + }); + } + } + } + } + public SupporterIcon() { Masking = true; + AutoSizeAxes = Axes.X; + Hide(); + Children = new Drawable[] { - new Box { RelativeSizeAxes = Axes.Both }, - new CircularContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.8f), - Masking = true, - Children = new Drawable[] - { - background = new Box { RelativeSizeAxes = Axes.Both }, - new Triangles - { - TriangleScale = 0.2f, - ColourLight = OsuColour.FromHex(@"ff7db7"), - ColourDark = OsuColour.FromHex(@"de5b95"), - RelativeSizeAxes = Axes.Both, - Velocity = 0.3f, - }, - } - }, - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_heart, - Scale = new Vector2(0.45f), - } + background = new Box { RelativeSizeAxes = Axes.Both }, + iconContainer = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Height = 0.6f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } }; } + public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) + { + bool invalid = base.Invalidate(invalidation, source, shallPropagate); + + if ((invalidation & Invalidation.DrawSize) != 0) + { + iconContainer.Padding = new MarginPadding { Horizontal = DrawHeight / 2 }; + } + + return invalid; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { background.Colour = colours.Pink; + iconContainer.Colour = colours.CommunityUserGrayGreenDark; } } } diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index a8075ec295..78f35b3da8 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -2,52 +2,91 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using osuTK; -using osuTK.Graphics; +using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Extensions; +using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Profile.Components; +using osu.Game.Online.Chat; using osu.Game.Overlays.Profile.Header; +using osu.Game.Scoring; using osu.Game.Users; +using osuTK; namespace osu.Game.Overlays.Profile { public class ProfileHeader : Container { - private readonly LinkFlowContainer infoTextLeft; - private readonly LinkFlowContainer infoTextRight; - private readonly FillFlowContainer scoreText, scoreNumberText; private readonly RankGraph rankGraph; public readonly SupporterIcon SupporterTag; private readonly Container coverContainer; - private readonly Sprite levelBadge; - private readonly SpriteText levelText; - private readonly GradeBadge gradeSSPlus, gradeSS, gradeSPlus, gradeS, gradeA; - private readonly Box colourBar; - private readonly DrawableFlag countryFlag; - private readonly BadgeContainer badgeContainer; + private readonly OsuSpriteText coverInfoText; + private readonly CoverInfoTabControl infoTabControl; - private const float cover_height = 350; - private const float info_height = 150; - private const float info_width = 220; + private readonly Box headerTopBox; + private readonly UpdateableAvatar avatar; + private readonly OsuSpriteText usernameText; + private readonly OsuSpriteText titleText; + private readonly DrawableFlag userFlag; + private readonly OsuSpriteText userCountryText; + private readonly Box userIconSeperatorBox; + private readonly FillFlowContainer userStats; + + private readonly Box headerCenterBox; + private readonly OsuSpriteText followerText; + private readonly ProfileHeaderButton messageButton; + private readonly ProfileHeaderButton expandButton; + private readonly Sprite levelBadgeSprite; + private readonly OsuSpriteText levelBadgeText; + + private readonly Bar levelProgressBar; + private readonly OsuSpriteText levelProgressText; + + private readonly OverlinedInfoContainer hiddenDetailGlobal, hiddenDetailCountry; + + public readonly BindableBool DetailsVisible = new BindableBool(); + + private readonly Box headerDetailBox; + private readonly HasTooltipContainer totalPlayTimeTooltip; + private readonly OverlinedInfoContainer totalPlayTimeInfo, medalInfo, ppInfo; + private readonly Dictionary scoreRankInfos = new Dictionary(); + private readonly OverlinedInfoContainer detailGlobalRank, detailCountryRank; + + private const float cover_height = 150; + private const float cover_info_height = 75; + private const float info_height = 500; private const float avatar_size = 110; - private const float level_position = 30; - private const float level_height = 60; - private const float stats_width = 280; - public ProfileHeader(User user) + [Resolved(CanBeNull = true)] + private ChannelManager channelManager { get; set; } + + [Resolved(CanBeNull = true)] + private UserProfileOverlay userOverlay { get; set; } + + [Resolved(CanBeNull = true)] + private ChatOverlay chatOverlay { get; set; } + + public ProfileHeader() { + Container headerDetailContainer, expandedDetailContainer; + FillFlowContainer hiddenDetailContainer; + SpriteIcon expandButtonIcon; + RelativeSizeAxes = Axes.X; Height = cover_height + info_height; @@ -65,262 +104,491 @@ namespace osu.Game.Overlays.Profile RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.1f), Color4.Black.Opacity(0.75f)) }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN, Bottom = 20, Right = stats_width + UserProfileOverlay.CONTENT_X_MARGIN }, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Children = new Drawable[] - { - new UpdateableAvatar - { - User = user, - Size = new Vector2(avatar_size), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Masking = true, - CornerRadius = 5, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Radius = 4, - }, - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - X = avatar_size + 10, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - SupporterTag = new SupporterIcon - { - Alpha = 0, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Y = -75, - Size = new Vector2(25, 25) - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Y = -48, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = user.Username, - Font = @"Exo2.0-RegularItalic", - TextSize = 30, - }, - new ExternalLinkButton($@"https://osu.ppy.sh/users/{user.Id}") - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Left = 3, Bottom = 3 }, //To better lineup with the font - }, - } - }, - countryFlag = new DrawableFlag(user.Country) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Width = 30, - Height = 20 - } - } - }, - badgeContainer = new BadgeContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Bottom = 5 }, - Alpha = 0, - }, - } - }, - colourBar = new Box - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - X = UserProfileOverlay.CONTENT_X_MARGIN, - Height = 5, - Width = info_width, - Alpha = 0 - } } }, - new Box // this is a temporary workaround for incorrect masking behaviour of FillMode.Fill used in UserCoverBackground (see https://github.com/ppy/osu-framework/issues/1675) - { - RelativeSizeAxes = Axes.X, - Height = 1, - Y = cover_height, - Colour = OsuColour.Gray(34), - }, - infoTextLeft = new LinkFlowContainer(t => t.TextSize = 14) - { - X = UserProfileOverlay.CONTENT_X_MARGIN, - Y = cover_height + 20, - Width = info_width, - AutoSizeAxes = Axes.Y, - ParagraphSpacing = 0.8f, - LineSpacing = 0.2f - }, - infoTextRight = new LinkFlowContainer(t => - { - t.TextSize = 14; - t.Font = @"Exo2.0-RegularItalic"; - }) - { - X = UserProfileOverlay.CONTENT_X_MARGIN + info_width + 20, - Y = cover_height + 20, - Width = info_width, - AutoSizeAxes = Axes.Y, - ParagraphSpacing = 0.8f, - LineSpacing = 0.2f - }, new Container { - X = -UserProfileOverlay.CONTENT_X_MARGIN, - RelativeSizeAxes = Axes.Y, - Width = stats_width, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, + Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, + Y = cover_height, + Height = cover_info_height, + RelativeSizeAxes = Axes.X, + Anchor = Anchor.TopLeft, + Origin = Anchor.BottomLeft, + Depth = -float.MaxValue, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new[] + { + new OsuSpriteText + { + Text = "Player ", + Font = "Exo2.0-Regular", + TextSize = 30 + }, + coverInfoText = new OsuSpriteText + { + Text = "Info", + Font = "Exo2.0-Regular", + TextSize = 30 + } + } + }, + infoTabControl = new CoverInfoTabControl + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = cover_info_height - 30, + Margin = new MarginPadding { Left = -UserProfileOverlay.CONTENT_X_MARGIN }, + Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN } + } + } + }, + new FillFlowContainer + { + Margin = new MarginPadding { Top = cover_height }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, Children = new Drawable[] { new Container { RelativeSizeAxes = Axes.X, - Y = level_position, - Height = level_height, + Height = 150, Children = new Drawable[] { - new Box + headerTopBox = new Box { - Colour = Color4.Black.Opacity(0.5f), - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, }, - levelBadge = new Sprite + new FillFlowContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Height = 50, - Width = 50, - Alpha = 0 + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, + Height = avatar_size, + AutoSizeAxes = Axes.X, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new[] + { + avatar = new UpdateableAvatar + { + Size = new Vector2(avatar_size), + Masking = true, + CornerRadius = avatar_size * 0.25f, + }, + new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Padding = new MarginPadding { Left = 10 }, + Children = new Drawable[] + { + usernameText = new OsuSpriteText + { + Font = "Exo2.0-Regular", + TextSize = 24 + }, + new FillFlowContainer + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + titleText = new OsuSpriteText + { + TextSize = 18, + Font = "Exo2.0-Regular" + }, + SupporterTag = new SupporterIcon + { + Height = 20, + Margin = new MarginPadding { Top = 5 } + }, + userIconSeperatorBox = new Box + { + RelativeSizeAxes = Axes.X, + Height = 1.5f, + Margin = new MarginPadding { Top = 10 } + }, + new Container + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5 }, + Children = new Drawable[] + { + userFlag = new DrawableFlag + { + Size = new Vector2(30, 20) + }, + userCountryText = new OsuSpriteText + { + Font = "Exo2.0-Regular", + TextSize = 17.5f, + Margin = new MarginPadding { Left = 40 }, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + } + } + }, + } + } + } + } + } }, - levelText = new OsuSpriteText + userStats = new FillFlowContainer { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Y = 11, - TextSize = 20 + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Y, + Width = 300, + Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN }, + Padding = new MarginPadding { Vertical = 15 }, + Spacing = new Vector2(0, 2) } } }, new Container { RelativeSizeAxes = Axes.X, - Y = cover_height, - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - Height = cover_height - level_height - level_position - 5, + Height = 60, Children = new Drawable[] { - new Box + headerCenterBox = new Box { - Colour = Color4.Black.Opacity(0.5f), - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, }, - scoreText = new FillFlowContainer + new FillFlowContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Horizontal = 20, Vertical = 18 }, - Spacing = new Vector2(0, 2) - }, - scoreNumberText = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Horizontal = 20, Vertical = 18 }, - Spacing = new Vector2(0, 2) - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, Direction = FillDirection.Horizontal, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Y = -64, - Spacing = new Vector2(20, 0), - Children = new[] + Padding = new MarginPadding { Vertical = 10 }, + Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, + Spacing = new Vector2(10, 0), + Children = new Drawable[] { - gradeSSPlus = new GradeBadge("SSPlus") { Alpha = 0 }, - gradeSS = new GradeBadge("SS") { Alpha = 0 }, + new ProfileHeaderButton + { + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Right = 10 }, + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.fa_user, + FillMode = FillMode.Fit, + Size = new Vector2(50, 14) + }, + followerText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + TextSize = 16, + Font = "Exo2.0-Bold" + } + } + } + } + }, + messageButton = new ProfileHeaderButton + { + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.fa_envelope, + FillMode = FillMode.Fit, + Size = new Vector2(50, 14) + }, + } + }, + } }, - new FillFlowContainer + new Container { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Y = -18, - Spacing = new Vector2(20, 0), - Children = new[] + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Y, + Padding = new MarginPadding { Vertical = 10 }, + Width = UserProfileOverlay.CONTENT_X_MARGIN, + Child = expandButton = new ProfileHeaderButton { - gradeSPlus = new GradeBadge("SPlus") { Alpha = 0 }, - gradeS = new GradeBadge("S") { Alpha = 0 }, - gradeA = new GradeBadge("A") { Alpha = 0 }, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + expandButtonIcon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(20), + Icon = FontAwesome.fa_chevron_up, + }, + } + }, + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN }, + Children = new Drawable[] + { + new HasTooltipContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(40), + TooltipText = "Level", + Children = new Drawable[] + { + levelBadgeSprite = new Sprite + { + RelativeSizeAxes = Axes.Both, + }, + levelBadgeText = new OsuSpriteText + { + TextSize = 20, + Font = "Exo2.0-Medium", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + } + }, + expandedDetailContainer = new HasTooltipContainer + { + TooltipText = "Progress to next level", + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 200, + Height = 6, + Margin = new MarginPadding { Right = 50 }, + Children = new Drawable[] + { + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = levelProgressBar = new Bar + { + RelativeSizeAxes = Axes.Both, + BackgroundColour = Color4.Black, + Direction = BarDirection.LeftToRight, + } + }, + levelProgressText = new OsuSpriteText + { + Anchor = Anchor.BottomRight, + Origin = Anchor.TopRight, + Font = "Exo2.0-Bold", + TextSize = 12, + } + } + }, + hiddenDetailContainer = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 200, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Spacing = new Vector2(10, 0), + Margin = new MarginPadding { Right = 50 }, + Children = new[] + { + hiddenDetailGlobal = new OverlinedInfoContainer + { + Title = "Global Ranking" + }, + hiddenDetailCountry = new OverlinedInfoContainer + { + Title = "Country Ranking" + }, + } + } } } } }, - new Container + headerDetailContainer = new Container { RelativeSizeAxes = Axes.X, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Height = info_height - 15, + AutoSizeAxes = Axes.Y, Children = new Drawable[] { - new Box + headerDetailBox = new Box { - Colour = Color4.Black.Opacity(0.25f), - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, }, - rankGraph = new RankGraph + new FillFlowContainer { - RelativeSizeAxes = Axes.Both - } + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + totalPlayTimeTooltip = new HasTooltipContainer + { + AutoSizeAxes = Axes.Both, + TooltipText = "0 hours", + Child = totalPlayTimeInfo = new OverlinedInfoContainer + { + Title = "Total Play Time", + }, + }, + medalInfo = new OverlinedInfoContainer + { + Title = "Medals" + }, + ppInfo = new OverlinedInfoContainer + { + Title = "pp" + }, + } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Direction = FillDirection.Horizontal, + Children = new[] + { + scoreRankInfos[ScoreRank.XH] = new ScoreRankInfo(ScoreRank.XH), + scoreRankInfos[ScoreRank.X] = new ScoreRankInfo(ScoreRank.X), + scoreRankInfos[ScoreRank.SH] = new ScoreRankInfo(ScoreRank.SH), + scoreRankInfos[ScoreRank.S] = new ScoreRankInfo(ScoreRank.S), + scoreRankInfos[ScoreRank.A] = new ScoreRankInfo(ScoreRank.A), + } + } + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 130 }, + Children = new Drawable[] + { + rankGraph = new RankGraph + { + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Width = 130, + Anchor = Anchor.TopRight, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Horizontal = 10 }, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + detailGlobalRank = new OverlinedInfoContainer(true, 110) + { + Title = "Global Ranking" + }, + detailCountryRank = new OverlinedInfoContainer(false, 110) + { + Title = "Country Ranking" + }, + } + } + } + } + } + }, } } } } }; + + infoTabControl.AddItem("Info"); + infoTabControl.AddItem("Modding"); + + DetailsVisible.ValueChanged += newValue => expandButtonIcon.Icon = newValue ? FontAwesome.fa_chevron_down : FontAwesome.fa_chevron_up; + DetailsVisible.ValueChanged += newValue => hiddenDetailContainer.Alpha = newValue ? 1 : 0; + DetailsVisible.ValueChanged += newValue => expandedDetailContainer.Alpha = newValue ? 0 : 1; + DetailsVisible.ValueChanged += newValue => headerDetailContainer.Alpha = newValue ? 0 : 1; } - [BackgroundDependencyLoader] - private void load(TextureStore textures) + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, TextureStore textures) { - levelBadge.Texture = textures.Get(@"Profile/levelbadge"); + coverInfoText.Colour = colours.CommunityUserGreen; + + infoTabControl.AccentColour = colours.CommunityUserGreen; + + headerTopBox.Colour = colours.CommunityUserGrayGreenDarker; + userCountryText.Colour = colours.CommunityUserGrayGreenLighter; + userIconSeperatorBox.Colour = colours.CommunityUserGrayGreenLighter; + + headerCenterBox.Colour = colours.CommunityUserGrayGreenDark; + levelBadgeSprite.Texture = textures.Get("Profile/levelbadge"); + levelBadgeSprite.Colour = colours.Yellow; + levelProgressBar.AccentColour = colours.Yellow; + + hiddenDetailGlobal.LineColour = colours.Yellow; + hiddenDetailCountry.LineColour = colours.Yellow; + + headerDetailBox.Colour = colours.CommunityUserGrayGreenDarkest; + totalPlayTimeInfo.LineColour = colours.Yellow; + medalInfo.LineColour = colours.GreenLight; + ppInfo.LineColour = colours.Red; + + detailGlobalRank.LineColour = colours.Yellow; + detailCountryRank.LineColour = colours.Yellow; } private User user; public User User { - get { return user; } + get => user; set { user = value; @@ -340,9 +608,79 @@ namespace osu.Game.Overlays.Profile Depth = float.MaxValue, }, coverContainer.Add); - if (user.IsSupporter) - SupporterTag.Show(); + avatar.User = User; + usernameText.Text = user.Username; + userFlag.Country = user.Country; + userCountryText.Text = user.Country?.FullName; + SupporterTag.SupporterLevel = user.SupportLevel; + if(user.Title != null) + titleText.Text = user.Title; + titleText.Colour = OsuColour.FromHex(user.Colour ?? "fff"); + userStats.Add(new UserStatsLine("Ranked Score", user.Statistics.RankedScore.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Hit Accuracy", Math.Round(user.Statistics.Accuracy, 2).ToString("#0.00'%'"))); + userStats.Add(new UserStatsLine("Play Count", user.Statistics.PlayCount.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Total Score", user.Statistics.TotalScore.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Total Hits", user.Statistics.TotalHits.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Maximum Combo", user.Statistics.MaxCombo.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Replays Watched by Others", user.Statistics.ReplaysWatched.ToString("#,##0"))); + + followerText.Text = user.FollowerCount?.Length > 0 ? user.FollowerCount[0].ToString("#,##0") : "0"; + + if (!user.PMFriendsOnly) + messageButton.Action = () => + { + channelManager?.OpenPrivateChannel(user); + userOverlay?.Hide(); + chatOverlay?.Show(); + }; + + expandButton.Action = DetailsVisible.Toggle; + + levelBadgeText.Text = user.Statistics.Level.Current.ToString(); + levelProgressBar.Length = user.Statistics.Level.Progress / 100f; + levelProgressText.Text = user.Statistics.Level.Progress.ToString("0'%'"); + + hiddenDetailGlobal.Content = user.Statistics.Ranks.Global?.ToString("#,##0") ?? "-"; + hiddenDetailCountry.Content = user.Statistics.Ranks.Country?.ToString("#,##0") ?? "-"; + + medalInfo.Content = user.Achievements.Length.ToString(); + ppInfo.Content = user.Statistics.PP?.ToString("#,##0") ?? "0"; + + string formatTime(int? secondsNull) + { + if (secondsNull == null) return "0h 0m"; + + int seconds = secondsNull.Value; + string time = ""; + + int days = seconds / 86400; + seconds -= days * 86400; + if (days > 0) + time += days + "d "; + + int hours = seconds / 3600; + seconds -= hours * 3600; + time += hours + "h "; + + int minutes = seconds / 60; + time += minutes + "m"; + + return time; + } + + totalPlayTimeInfo.Content = formatTime(user.Statistics.PlayTime); + totalPlayTimeTooltip.TooltipText = (user.Statistics.PlayTime ?? 0) / 3600 + " hours"; + + foreach (var scoreRankInfo in scoreRankInfos) + scoreRankInfo.Value.RankCount = user.Statistics.GradesCount.GetForScoreRank(scoreRankInfo.Key); + + detailGlobalRank.Content = user.Statistics.Ranks.Global?.ToString("#,##0") ?? "-"; + detailCountryRank.Content = user.Statistics.Ranks.Country?.ToString("#,##0") ?? "-"; + + rankGraph.User.Value = user; + + /* if (!string.IsNullOrEmpty(user.Colour)) { colourBar.Colour = OsuColour.FromHex(user.Colour); @@ -457,24 +795,315 @@ namespace osu.Game.Overlays.Profile rankGraph.User.Value = user; } - badgeContainer.ShowBadges(user.Badges); + badgeContainer.ShowBadges(user.Badges);*/ } - private void tryAddInfoRightLine(FontAwesome icon, string str, string url = null) + private class CoverInfoTabControl : TabControl { - if (string.IsNullOrEmpty(str)) return; + private readonly Box bar; - infoTextRight.AddIcon(icon); - if (url != null) + private Color4 accentColour; + public Color4 AccentColour { - infoTextRight.AddLink(" " + str, url); - } - else - { - infoTextRight.AddText(" " + str); + get => accentColour; + set + { + if (accentColour == value) return; + + accentColour = value; + + bar.Colour = value; + + foreach (TabItem tabItem in TabContainer) + { + ((CoverInfoTabItem)tabItem).AccentColour = value; + } + } } - infoTextRight.NewLine(); + public MarginPadding Padding + { + set => TabContainer.Padding = value; + get => TabContainer.Padding; + } + + public CoverInfoTabControl() + { + TabContainer.Masking = false; + TabContainer.Spacing = new Vector2(20, 0); + + AddInternal(bar = new Box + { + RelativeSizeAxes = Axes.X, + Height = 2, + Anchor = Anchor.BottomLeft, + Origin = Anchor.CentreLeft + }); + } + + protected override Dropdown CreateDropdown() => null; + + protected override TabItem CreateTabItem(string value) => new CoverInfoTabItem(value) + { + AccentColour = AccentColour + }; + + private class CoverInfoTabItem : TabItem + { + private readonly OsuSpriteText text; + private readonly Drawable bar; + + private Color4 accentColour; + public Color4 AccentColour + { + get => accentColour; + set + { + accentColour = value; + + bar.Colour = value; + if (!Active) text.Colour = value; + } + } + + public CoverInfoTabItem(string value) + : base(value) + { + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + + Children = new[] + { + text = new OsuSpriteText + { + Margin = new MarginPadding { Bottom = 15 }, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Text = value, + TextSize = 14, + Font = "Exo2.0-Bold", + }, + bar = new Circle + { + RelativeSizeAxes = Axes.X, + Height = 0, + Origin = Anchor.CentreLeft, + Anchor = Anchor.BottomLeft, + }, + new HoverClickSounds() + }; + } + + protected override bool OnHover(HoverEvent e) + { + if (!Active) + onActivated(true); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + + if (!Active) + OnDeactivated(); + } + + protected override void OnActivated() + { + onActivated(); + } + + protected override void OnDeactivated() + { + text.FadeColour(AccentColour, 120, Easing.InQuad); + bar.ResizeHeightTo(0, 120, Easing.InQuad); + text.Font = "Exo2.0-Medium"; + } + + private void onActivated(bool fake = false) + { + text.FadeColour(Color4.White, 120, Easing.InQuad); + bar.ResizeHeightTo(7.5f, 120, Easing.InQuad); + if (!fake) + text.Font = "Exo2.0-Bold"; + } + } + } + + private class UserStatsLine : Container + { + private readonly OsuSpriteText rightText; + + public UserStatsLine(string left, string right) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Children = new Drawable[] + { + new OsuSpriteText + { + TextSize = 15, + Text = left, + Font = "Exo2.0-Medium" + }, + rightText = new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + TextSize = 15, + Text = right, + Font = "Exo2.0-Medium" + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + rightText.Colour = colours.BlueLight; + } + } + + private class ProfileHeaderButton : OsuHoverContainer + { + private readonly Box background; + private readonly Container content; + + protected override Container Content => content; + + protected override IEnumerable EffectTargets => new[] { background }; + + public ProfileHeaderButton() + { + HoverColour = Color4.Black.Opacity(0.75f); + IdleColour = Color4.Black.Opacity(0.7f); + AutoSizeAxes = Axes.X; + + base.Content.Add(new CircularContainer + { + Masking = true, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + content = new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 10 }, + } + } + }); + } + } + + private class HasTooltipContainer : Container, IHasTooltip + { + public string TooltipText { get; set; } + } + + private class OverlinedInfoContainer : CompositeDrawable + { + private readonly Circle line; + private readonly OsuSpriteText title, content; + + public string Title + { + set => title.Text = value; + } + + public string Content + { + set => content.Text = value; + } + + public Color4 LineColour + { + set => line.Colour = value; + } + + public OverlinedInfoContainer(bool big = false, int minimumWidth = 60) + { + AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + line = new Circle + { + RelativeSizeAxes = Axes.X, + Height = 4, + }, + title = new OsuSpriteText + { + Font = "Exo2.0-Bold", + TextSize = big ? 14 : 12, + }, + content = new OsuSpriteText + { + Font = "Exo2.0-Light", + TextSize = big ? 40 : 18, + }, + new Container //Add a minimum size to the FillFlowContainer + { + Width = minimumWidth, + } + } + }; + } + } + + public class ScoreRankInfo : CompositeDrawable + { + private readonly ScoreRank rank; + private readonly Sprite rankSprite; + private readonly OsuSpriteText rankCount; + + public int RankCount + { + set => rankCount.Text = value.ToString("#,##0"); + } + + public ScoreRankInfo(ScoreRank rank) + { + this.rank = rank; + + AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Width = 56, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + rankSprite = new Sprite + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit + }, + rankCount = new OsuSpriteText + { + Font = "Exo2.0-Bold", + TextSize = 12, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + rankSprite.Texture = textures.Get($"Grades/{rank.GetDescription()}"); + } } } } diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index c15f464c7c..ca99f07d9b 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays private SectionsContainer sectionsContainer; private ProfileTabControl tabs; - public const float CONTENT_X_MARGIN = 50; + public const float CONTENT_X_MARGIN = 70; public UserProfileOverlay() { @@ -113,12 +113,10 @@ namespace osu.Game.Overlays Colour = OsuColour.Gray(0.2f) }); - Header = new ProfileHeader(user); - Add(sectionsContainer = new SectionsContainer { RelativeSizeAxes = Axes.Both, - ExpandableHeader = Header, + ExpandableHeader = Header = new ProfileHeader(), FixedHeader = tabs, HeaderBackground = new Box { diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index a5d8c03a67..485c953b75 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -59,6 +59,9 @@ namespace osu.Game.Users [JsonProperty(@"is_supporter")] public bool IsSupporter; + [JsonProperty(@"support_level")] + public int SupportLevel; + [JsonProperty(@"is_gmt")] public bool IsGMT; @@ -71,6 +74,9 @@ namespace osu.Game.Users [JsonProperty(@"is_active")] public bool Active; + [JsonProperty(@"pm_friends_only")] + public bool PMFriendsOnly; + [JsonProperty(@"interests")] public string Interests; @@ -104,6 +110,9 @@ namespace osu.Game.Users [JsonProperty(@"post_count")] public int PostCount; + [JsonProperty(@"follower_count")] + public int[] FollowerCount; + [JsonProperty(@"playstyle")] public string[] PlayStyle; @@ -143,6 +152,18 @@ namespace osu.Game.Users [JsonProperty("badges")] public Badge[] Badges; + [JsonProperty("user_achievements")] + public UserAchievement[] Achievements; + + public class UserAchievement + { + [JsonProperty("achieved_at")] + public DateTimeOffset AchievedAt; + + [JsonProperty("achievement_id")] + public int ID; + } + public override string ToString() => Username; /// diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index d86f608bd1..3d0127bba4 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -185,8 +185,8 @@ namespace osu.Game.Users { infoContainer.Add(new SupporterIcon { - RelativeSizeAxes = Axes.Y, - Width = 20f, + Height = 20f, + SupporterLevel = user.SupportLevel }); } diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index f04bfb62bb..c400a3f15b 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -1,7 +1,9 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using Newtonsoft.Json; +using osu.Game.Scoring; namespace osu.Game.Users { @@ -37,6 +39,9 @@ namespace osu.Game.Users [JsonProperty(@"play_count")] public int PlayCount; + [JsonProperty(@"play_time")] + public int? PlayTime; + [JsonProperty(@"total_score")] public long TotalScore; @@ -68,6 +73,19 @@ namespace osu.Game.Users [JsonProperty(@"a")] public int A; + + public int GetForScoreRank(ScoreRank rank) + { + switch (rank) + { + case ScoreRank.XH: return SSPlus; + case ScoreRank.X: return SS; + case ScoreRank.SH: return SPlus; + case ScoreRank.S: return S; + case ScoreRank.A: return A; + default: throw new ArgumentException($"API does not return {rank.ToString()}"); + } + } } public struct UserRanks diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index d6882282e6..170a7bd8c9 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -208,6 +208,7 @@ MD5 NS OS + PM RGB RNG SHA From 2fe80d556844d82c8029d8999a08c8b5166e5094 Mon Sep 17 00:00:00 2001 From: jorolf Date: Sat, 22 Dec 2018 21:50:25 +0100 Subject: [PATCH 002/623] Update ProfileHeader to the new design --- .../Visual/TestCaseBadgeContainer.cs | 62 --- osu.Game.Tests/Visual/TestCaseUserProfile.cs | 1 - .../Graphics/Containers/SectionsContainer.cs | 11 + osu.Game/Graphics/UserInterface/LineGraph.cs | 11 +- .../Profile/Components/DrawableJoinDate.cs | 20 - .../Overlays/Profile/Components/GradeBadge.cs | 50 --- .../Overlays/Profile/Header/BadgeContainer.cs | 198 --------- .../Profile/Header/ProfileHeaderTabControl.cs | 149 +++++++ osu.Game/Overlays/Profile/Header/RankGraph.cs | 194 +++++--- osu.Game/Overlays/Profile/ProfileHeader.cs | 419 ++++++++---------- 10 files changed, 480 insertions(+), 635 deletions(-) delete mode 100644 osu.Game.Tests/Visual/TestCaseBadgeContainer.cs delete mode 100644 osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs delete mode 100644 osu.Game/Overlays/Profile/Components/GradeBadge.cs delete mode 100644 osu.Game/Overlays/Profile/Header/BadgeContainer.cs create mode 100644 osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs diff --git a/osu.Game.Tests/Visual/TestCaseBadgeContainer.cs b/osu.Game.Tests/Visual/TestCaseBadgeContainer.cs deleted file mode 100644 index 8177e2e272..0000000000 --- a/osu.Game.Tests/Visual/TestCaseBadgeContainer.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Game.Overlays.Profile.Header; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseBadgeContainer : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] { typeof(BadgeContainer) }; - - public TestCaseBadgeContainer() - { - BadgeContainer badgeContainer; - - Child = badgeContainer = new BadgeContainer - { - RelativeSizeAxes = Axes.Both - }; - - AddStep("Show 1 badge", () => badgeContainer.ShowBadges(new[] - { - new Badge - { - AwardedAt = DateTimeOffset.Now, - Description = "Appreciates compasses", - ImageUrl = "https://assets.ppy.sh/profile-badges/mg2018-1star.png", - } - })); - - AddStep("Show 2 badges", () => badgeContainer.ShowBadges(new[] - { - new Badge - { - AwardedAt = DateTimeOffset.Now, - Description = "Contributed to osu!lazer testing", - ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.png", - }, - new Badge - { - AwardedAt = DateTimeOffset.Now, - Description = "Appreciates compasses", - ImageUrl = "https://assets.ppy.sh/profile-badges/mg2018-1star.png", - } - })); - - AddStep("Show many badges", () => badgeContainer.ShowBadges(Enumerable.Range(1, 20).Select(i => new Badge - { - AwardedAt = DateTimeOffset.Now, - Description = $"Contributed to osu!lazer testing {i} times", - ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.jpg", - }).ToArray())); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs index ce41bc22ff..cff55c2506 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs @@ -28,7 +28,6 @@ namespace osu.Game.Tests.Visual typeof(UserProfileOverlay), typeof(RankGraph), typeof(LineGraph), - typeof(BadgeContainer), typeof(SectionsContainer<>), typeof(SupporterIcon) }; diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index 36fdbe6e94..f16b5773ea 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -141,6 +141,17 @@ namespace osu.Game.Graphics.Containers public void ScrollToTop() => scrollContainer.ScrollTo(0); + public override void InvalidateFromChild(Invalidation invalidation, Drawable source = null) + { + base.InvalidateFromChild(invalidation, source); + + if ((invalidation & Invalidation.DrawSize) != 0) + { + if (source == ExpandableHeader) //We need to recalculate the positions if the ExpandableHeader changed its size + lastKnownScroll = -1; + } + } + private float lastKnownScroll; protected override void UpdateAfterChildren() { diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index c84c500201..c750f7a89d 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -9,6 +9,7 @@ using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { @@ -63,13 +64,19 @@ namespace osu.Game.Graphics.UserInterface } } + public Color4 LineColour + { + get => maskingContainer.Colour; + set => maskingContainer.Colour = value; + } + public LineGraph() { Add(maskingContainer = new Container { Masking = true, RelativeSizeAxes = Axes.Both, - Child = path = new SmoothPath { RelativeSizeAxes = Axes.Both, PathWidth = 1 } + Child = path = new SmoothPath { RelativeSizeAxes = Axes.Both, PathWidth = 1.5f } }); } @@ -103,7 +110,7 @@ namespace osu.Game.Graphics.UserInterface for (int i = 0; i < values.Length; i++) { float x = (i + count - values.Length) / (float)(count - 1) * DrawWidth - 1; - float y = GetYPosition(values[i]) * DrawHeight - 1; + float y = GetYPosition(values[i]) * DrawHeight - path.PathWidth; // the -1 is for inner offset in path (actually -PathWidth) path.AddVertex(new Vector2(x, y)); } diff --git a/osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs b/osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs deleted file mode 100644 index 11ee329f33..0000000000 --- a/osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Profile.Components -{ - public class DrawableJoinDate : DrawableDate - { - public DrawableJoinDate(DateTimeOffset date) - : base(date) - { - } - - protected override string Format() => Text = Date.ToUniversalTime().Year < 2008 ? "Here since the beginning" : $"{Date:MMMM yyyy}"; - - public override string TooltipText => $"{Date:MMMM d, yyyy}"; - } -} diff --git a/osu.Game/Overlays/Profile/Components/GradeBadge.cs b/osu.Game/Overlays/Profile/Components/GradeBadge.cs deleted file mode 100644 index 14a47e8d03..0000000000 --- a/osu.Game/Overlays/Profile/Components/GradeBadge.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Overlays.Profile.Components -{ - public class GradeBadge : Container - { - private const float width = 50; - private readonly string grade; - private readonly Sprite badge; - private readonly SpriteText numberText; - - public int DisplayCount - { - set => numberText.Text = value.ToString(@"#,0"); - } - - public GradeBadge(string grade) - { - this.grade = grade; - Width = width; - Height = 41; - Add(badge = new Sprite - { - Width = width, - Height = 26 - }); - Add(numberText = new OsuSpriteText - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - TextSize = 14, - Font = @"Exo2.0-Bold" - }); - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - badge.Texture = textures.Get($"Grades/{grade}"); - } - } -} diff --git a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs deleted file mode 100644 index 06fef22309..0000000000 --- a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Users; -using osuTK; - -namespace osu.Game.Overlays.Profile.Header -{ - public class BadgeContainer : Container - { - private static readonly Vector2 badge_size = new Vector2(86, 40); - private static readonly MarginPadding outer_padding = new MarginPadding(3); - - private OsuSpriteText badgeCountText; - private FillFlowContainer badgeFlowContainer; - private FillFlowContainer outerBadgeContainer; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Child = new Container - { - Masking = true, - CornerRadius = 4, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray3 - }, - outerBadgeContainer = new OuterBadgeContainer(onOuterHover, onOuterHoverLost) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Direction = FillDirection.Vertical, - Padding = outer_padding, - Width = DrawableBadge.DRAWABLE_BADGE_SIZE.X + outer_padding.TotalHorizontal, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - badgeCountText = new OsuSpriteText - { - Alpha = 0, - TextSize = 12, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Font = "Exo2.0-Regular" - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - AutoSizeAxes = Axes.Both, - Child = badgeFlowContainer = new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - } - } - } - }, - } - }; - - Scheduler.AddDelayed(rotateBadges, 3000, true); - } - - private void rotateBadges() - { - if (outerBadgeContainer.IsHovered) return; - - visibleBadge = (visibleBadge + 1) % badgeCount; - - badgeFlowContainer.MoveToX(-DrawableBadge.DRAWABLE_BADGE_SIZE.X * visibleBadge, 500, Easing.InOutQuad); - } - - private int visibleBadge; - private int badgeCount; - - public void ShowBadges(Badge[] badges) - { - if (badges == null || badges.Length == 0) - { - Hide(); - return; - } - - badgeCount = badges.Length; - - badgeCountText.FadeTo(badgeCount > 1 ? 1 : 0); - badgeCountText.Text = $"{badges.Length} badges"; - - Show(); - visibleBadge = 0; - - badgeFlowContainer.Clear(); - for (var index = 0; index < badges.Length; index++) - { - int displayIndex = index; - LoadComponentAsync(new DrawableBadge(badges[index]) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }, asyncBadge => - { - badgeFlowContainer.Add(asyncBadge); - - // load in stable order regardless of async load order. - badgeFlowContainer.SetLayoutPosition(asyncBadge, displayIndex); - }); - } - } - - private void onOuterHover() - { - badgeFlowContainer.ClearTransforms(); - badgeFlowContainer.X = 0; - badgeFlowContainer.Direction = FillDirection.Full; - outerBadgeContainer.AutoSizeAxes = Axes.Both; - - badgeFlowContainer.MaximumSize = new Vector2(ChildSize.X, float.MaxValue); - } - - private void onOuterHoverLost() - { - badgeFlowContainer.X = -DrawableBadge.DRAWABLE_BADGE_SIZE.X * visibleBadge; - badgeFlowContainer.Direction = FillDirection.Horizontal; - outerBadgeContainer.AutoSizeAxes = Axes.Y; - outerBadgeContainer.Width = DrawableBadge.DRAWABLE_BADGE_SIZE.X + outer_padding.TotalHorizontal; - } - - private class OuterBadgeContainer : FillFlowContainer - { - private readonly Action hoverAction; - private readonly Action hoverLostAction; - - public OuterBadgeContainer(Action hoverAction, Action hoverLostAction) - { - this.hoverAction = hoverAction; - this.hoverLostAction = hoverLostAction; - } - - protected override bool OnHover(HoverEvent e) - { - hoverAction(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) => hoverLostAction(); - } - - private class DrawableBadge : Container, IHasTooltip - { - public static readonly Vector2 DRAWABLE_BADGE_SIZE = badge_size + outer_padding.Total; - - private readonly Badge badge; - - public DrawableBadge(Badge badge) - { - this.badge = badge; - Padding = outer_padding; - Size = DRAWABLE_BADGE_SIZE; - } - - [BackgroundDependencyLoader] - private void load(LargeTextureStore textures) - { - Child = new Sprite - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Texture = textures.Get(badge.ImageUrl), - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - Child.FadeInFromZero(200); - } - - public string TooltipText => badge.Description; - } - } -} diff --git a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs new file mode 100644 index 0000000000..b067273b04 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs @@ -0,0 +1,149 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile +{ + public class ProfileHeaderTabControl : TabControl + { + private readonly Box bar; + + private Color4 accentColour; + + public Color4 AccentColour + { + get => accentColour; + set + { + if (accentColour == value) return; + + accentColour = value; + + bar.Colour = value; + + foreach (TabItem tabItem in TabContainer) + { + ((ProfileHeaderTabItem)tabItem).AccentColour = value; + } + } + } + + public MarginPadding Padding + { + set => TabContainer.Padding = value; + get => TabContainer.Padding; + } + + public ProfileHeaderTabControl() + { + TabContainer.Masking = false; + TabContainer.Spacing = new Vector2(20, 0); + + AddInternal(bar = new Box + { + RelativeSizeAxes = Axes.X, + Height = 2, + Anchor = Anchor.BottomLeft, + Origin = Anchor.CentreLeft + }); + } + + protected override Dropdown CreateDropdown() => null; + + protected override TabItem CreateTabItem(string value) => new ProfileHeaderTabItem(value) + { + AccentColour = AccentColour + }; + + private class ProfileHeaderTabItem : TabItem + { + private readonly OsuSpriteText text; + private readonly Drawable bar; + + private Color4 accentColour; + + public Color4 AccentColour + { + get => accentColour; + set + { + accentColour = value; + + bar.Colour = value; + if (!Active) text.Colour = value; + } + } + + public ProfileHeaderTabItem(string value) + : base(value) + { + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + + Children = new[] + { + text = new OsuSpriteText + { + Margin = new MarginPadding { Bottom = 15 }, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Text = value, + TextSize = 14, + Font = "Exo2.0-Bold", + }, + bar = new Circle + { + RelativeSizeAxes = Axes.X, + Height = 0, + Origin = Anchor.CentreLeft, + Anchor = Anchor.BottomLeft, + }, + new HoverClickSounds() + }; + } + + protected override bool OnHover(HoverEvent e) + { + if (!Active) + onActivated(true); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + + if (!Active) + OnDeactivated(); + } + + protected override void OnActivated() + { + onActivated(); + } + + protected override void OnDeactivated() + { + text.FadeColour(AccentColour, 120, Easing.InQuad); + bar.ResizeHeightTo(0, 120, Easing.InQuad); + text.Font = "Exo2.0-Medium"; + } + + private void onActivated(bool fake = false) + { + text.FadeColour(Color4.White, 120, Easing.InQuad); + bar.ResizeHeightTo(7.5f, 120, Easing.InQuad); + if (!fake) + text.Font = "Exo2.0-Bold"; + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/RankGraph.cs b/osu.Game/Overlays/Profile/Header/RankGraph.cs index f74c8b5069..f67a8e47d4 100644 --- a/osu.Game/Overlays/Profile/Header/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/RankGraph.cs @@ -8,8 +8,8 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -19,19 +19,18 @@ using osuTK; namespace osu.Game.Overlays.Profile.Header { - public class RankGraph : Container + public class RankGraph : Container, IHasCustomTooltip { - private const float primary_textsize = 25; private const float secondary_textsize = 13; private const float padding = 10; private const float fade_duration = 150; private const int ranked_days = 88; - private readonly SpriteText rankText, performanceText, relativeText; private readonly RankChartLineGraph graph; private readonly OsuSpriteText placeholder; private KeyValuePair[] ranks; + private int dayIndex; public Bindable User = new Bindable(); public RankGraph() @@ -44,43 +43,20 @@ namespace osu.Game.Overlays.Profile.Header Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "No recent plays", - TextSize = 14, - Font = @"Exo2.0-RegularItalic", - }, - rankText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = @"Exo2.0-RegularItalic", - TextSize = primary_textsize - }, - relativeText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = @"Exo2.0-RegularItalic", - Y = 25, - TextSize = secondary_textsize - }, - performanceText = new OsuSpriteText - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Font = @"Exo2.0-RegularItalic", - TextSize = secondary_textsize + TextSize = 12, + Font = @"Exo2.0-Regular", }, graph = new RankChartLineGraph { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.X, - Height = 60, + RelativeSizeAxes = Axes.Both, Y = -secondary_textsize, Alpha = 0, } }; - graph.OnBallMove += showHistoryRankTexts; + graph.OnBallMove += i => dayIndex = i; User.ValueChanged += userChanged; } @@ -88,7 +64,7 @@ namespace osu.Game.Overlays.Profile.Header [BackgroundDependencyLoader] private void load(OsuColour colours) { - graph.Colour = colours.Yellow; + graph.LineColour = colours.Yellow; } private void userChanged(User user) @@ -97,9 +73,6 @@ namespace osu.Game.Overlays.Profile.Header if (user?.Statistics?.Ranks.Global == null) { - rankText.Text = string.Empty; - performanceText.Text = string.Empty; - relativeText.Text = string.Empty; graph.FadeOut(fade_duration, Easing.Out); ranks = null; return; @@ -114,27 +87,9 @@ namespace osu.Game.Overlays.Profile.Header graph.DefaultValueCount = ranks.Length; graph.Values = ranks.Select(x => -(float)Math.Log(x.Value)); - graph.SetStaticBallPosition(); } graph.FadeTo(ranks.Length > 1 ? 1 : 0, fade_duration, Easing.Out); - - updateRankTexts(); - } - - private void updateRankTexts() - { - var user = User.Value; - - performanceText.Text = user.Statistics.PP != null ? $"{user.Statistics.PP:#,0}pp" : string.Empty; - rankText.Text = user.Statistics.Ranks.Global > 0 ? $"#{user.Statistics.Ranks.Global:#,0}" : "no rank"; - relativeText.Text = user.Country != null && user.Statistics.Ranks.Country > 0 ? $"{user.Country.FullName} #{user.Statistics.Ranks.Country:#,0}" : "no rank"; - } - - private void showHistoryRankTexts(int dayIndex) - { - rankText.Text = $"#{ranks[dayIndex].Value:#,0}"; - relativeText.Text = dayIndex + 1 == ranks.Length ? "Now" : $"{ranked_days - ranks[dayIndex].Key} days ago"; } protected override bool OnHover(HoverEvent e) @@ -160,7 +115,6 @@ namespace osu.Game.Overlays.Profile.Header if (ranks?.Length > 1) { graph.HideBall(); - updateRankTexts(); } base.OnHoverLost(e); @@ -168,44 +122,62 @@ namespace osu.Game.Overlays.Profile.Header private class RankChartLineGraph : LineGraph { - private readonly CircularContainer staticBall; private readonly CircularContainer movingBall; + private readonly Box ballBg; + private readonly Box movingBar; public Action OnBallMove; public RankChartLineGraph() { - Add(staticBall = new CircularContainer + Add(movingBar = new Box { - Origin = Anchor.Centre, - Size = new Vector2(8), - Masking = true, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = 1.5f, + Alpha = 0, RelativePositionAxes = Axes.Both, - Child = new Box { RelativeSizeAxes = Axes.Both } }); + Add(movingBall = new CircularContainer { Origin = Anchor.Centre, - Size = new Vector2(8), + Size = new Vector2(18), Alpha = 0, Masking = true, + BorderThickness = 4, RelativePositionAxes = Axes.Both, - Child = new Box { RelativeSizeAxes = Axes.Both } + Child = ballBg = new Box { RelativeSizeAxes = Axes.Both } }); } - public void SetStaticBallPosition() => staticBall.Position = new Vector2(1, GetYPosition(Values.Last())); + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + ballBg.Colour = colours.CommunityUserGrayGreenDarkest; + movingBall.BorderColour = colours.Yellow; + movingBar.Colour = colours.Yellow; + } public void UpdateBallPosition(float mouseXPosition) { int index = calculateIndex(mouseXPosition); movingBall.Position = calculateBallPosition(index); + movingBar.X = movingBall.X; OnBallMove.Invoke(index); } - public void ShowBall() => movingBall.FadeIn(fade_duration); + public void ShowBall() + { + movingBall.FadeIn(fade_duration); + movingBar.FadeIn(fade_duration); + } - public void HideBall() => movingBall.FadeOut(fade_duration); + public void HideBall() + { + movingBall.FadeOut(fade_duration); + movingBar.FadeOut(fade_duration); + } private int calculateIndex(float mouseXPosition) => (int)Math.Round(mouseXPosition / DrawWidth * (DefaultValueCount - 1)); @@ -215,5 +187,97 @@ namespace osu.Game.Overlays.Profile.Header return new Vector2(index / (float)(DefaultValueCount - 1), y); } } + + public string TooltipText => User.Value?.Statistics?.Ranks.Global == null ? "" : $"{ranks[dayIndex].Value:#,##0}|{ranked_days - ranks[dayIndex].Key + 1}"; + + public ITooltip GetCustomTooltip() => new RankGraphTooltip(this); + + public class RankGraphTooltip : VisibilityContainer, ITooltip + { + private readonly RankGraph graph; + private readonly OsuSpriteText globalRankingText, timeText; + private readonly Box background; + + public string TooltipText { get; set; } + + public RankGraphTooltip(RankGraph graph) + { + this.graph = graph; + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 10; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = "Exo2.0-Bold", + TextSize = 12, + Text = "Global Ranking " + }, + globalRankingText = new OsuSpriteText + { + Font = "Exo2.0-Regular", + TextSize = 12, + } + } + }, + timeText = new OsuSpriteText + { + TextSize = 12, + Font = "Exo2.0-Regular" + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.CommunityUserGrayGreenDarker; + } + + public void Refresh() + { + var info = TooltipText.Split('|'); + globalRankingText.Text = info[0]; + timeText.Text = info[1] == "0" ? "now" : $"{info[1]} days ago"; + } + + private bool instantMove = true; + + public void Move(Vector2 pos) + { + if (instantMove) + { + Position = pos; + instantMove = false; + } + else + this.MoveTo(pos, 200, Easing.OutQuint); + } + + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + } } } diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index b35ae50c5e..6ab178cfe1 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -15,8 +15,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -36,7 +34,7 @@ namespace osu.Game.Overlays.Profile public readonly SupporterIcon SupporterTag; private readonly Container coverContainer; private readonly OsuSpriteText coverInfoText; - private readonly CoverInfoTabControl infoTabControl; + private readonly ProfileHeaderTabControl infoTabControl; private readonly Box headerTopBox; private readonly UpdateableAvatar avatar; @@ -67,9 +65,16 @@ namespace osu.Game.Overlays.Profile private readonly Dictionary scoreRankInfos = new Dictionary(); private readonly OverlinedInfoContainer detailGlobalRank, detailCountryRank; + private readonly Box headerBadgeBox; + private readonly FillFlowContainer badgeFlowContainer; + private readonly Container badgeContainer; + + private readonly Box headerBottomBox; + private readonly LinkFlowContainer bottomTopLinkContainer; + private readonly LinkFlowContainer bottomLinkContainer; + private const float cover_height = 150; private const float cover_info_height = 75; - private const float info_height = 500; private const float avatar_size = 110; [Resolved(CanBeNull = true)] @@ -83,12 +88,12 @@ namespace osu.Game.Overlays.Profile public ProfileHeader() { - Container headerDetailContainer, expandedDetailContainer; - FillFlowContainer hiddenDetailContainer; + Container expandedDetailContainer; + FillFlowContainer hiddenDetailContainer, headerDetailContainer; SpriteIcon expandButtonIcon; RelativeSizeAxes = Axes.X; - Height = cover_height + info_height; + AutoSizeAxes = Axes.Y; Children = new Drawable[] { @@ -137,7 +142,7 @@ namespace osu.Game.Overlays.Profile } } }, - infoTabControl = new CoverInfoTabControl + infoTabControl = new ProfileHeaderTabControl { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, @@ -181,7 +186,7 @@ namespace osu.Game.Overlays.Profile Size = new Vector2(avatar_size), Masking = true, CornerRadius = avatar_size * 0.25f, - OpenOnClick = { Value = false }, + OpenOnClick = { Value = false }, }, new Container { @@ -437,7 +442,7 @@ namespace osu.Game.Overlays.Profile } } }, - headerDetailContainer = new Container + new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -447,7 +452,7 @@ namespace osu.Game.Overlays.Profile { RelativeSizeAxes = Axes.Both, }, - new FillFlowContainer + headerDetailContainer = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -539,11 +544,82 @@ namespace osu.Game.Overlays.Profile } } } - } + }, } }, } - } + }, + badgeContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Children = new Drawable[] + { + headerBadgeBox = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new Container //artificial shadow + { + RelativeSizeAxes = Axes.X, + Height = 3, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new ColourInfo + { + TopLeft = Color4.Black.Opacity(0.2f), + TopRight = Color4.Black.Opacity(0.2f), + BottomLeft = Color4.Black.Opacity(0), + BottomRight = Color4.Black.Opacity(0) + } + }, + }, + badgeFlowContainer = new FillFlowContainer + { + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 5 }, + Spacing = new Vector2(10, 10), + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + } + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + headerBottomBox = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + bottomTopLinkContainer = new LinkFlowContainer(text => text.TextSize = 12) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + bottomLinkContainer = new LinkFlowContainer(text => text.TextSize = 12) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + } + } + } + }, } } }; @@ -557,6 +633,8 @@ namespace osu.Game.Overlays.Profile DetailsVisible.ValueChanged += newValue => headerDetailContainer.Alpha = newValue ? 0 : 1; } + private Color4 communityUserGrayGreenLighter; + [BackgroundDependencyLoader(true)] private void load(OsuColour colours, TextureStore textures) { @@ -583,9 +661,12 @@ namespace osu.Game.Overlays.Profile detailGlobalRank.LineColour = colours.Yellow; detailCountryRank.LineColour = colours.Yellow; - } - private readonly OsuSpriteText usernameText; + headerBadgeBox.Colour = colours.CommunityUserGrayGreenDarkest; + headerBottomBox.Colour = colours.CommunityUserGrayGreenDarker; + + communityUserGrayGreenLighter = colours.CommunityUserGrayGreenLighter; + } private User user; @@ -683,67 +764,68 @@ namespace osu.Game.Overlays.Profile rankGraph.User.Value = user; - /* - if (!string.IsNullOrEmpty(user.Colour)) + var badges = User.Badges; + if (badges.Length > 0) { - colourBar.Colour = OsuColour.FromHex(user.Colour); - colourBar.Show(); + badgeContainer.Show(); + for (var index = 0; index < badges.Length; index++) + { + int displayIndex = index; + LoadComponentAsync(new DrawableBadge(badges[index]), asyncBadge => + { + badgeFlowContainer.Add(asyncBadge); + + // load in stable order regardless of async load order. + badgeFlowContainer.SetLayoutPosition(asyncBadge, displayIndex); + }); + } } - void boldItalic(SpriteText t) => t.Font = @"Exo2.0-BoldItalic"; - void lightText(SpriteText t) => t.Alpha = 0.8f; - - OsuSpriteText createScoreText(string text) => new OsuSpriteText - { - TextSize = 14, - Text = text - }; - - OsuSpriteText createScoreNumberText(string text) => new OsuSpriteText - { - TextSize = 14, - Font = @"Exo2.0-Bold", - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Text = text - }; - - if (user.Country != null) - { - infoTextLeft.AddText("From ", lightText); - infoTextLeft.AddText(user.Country.FullName, boldItalic); - countryFlag.Country = user.Country; - } - - infoTextLeft.NewParagraph(); + void bold(SpriteText t) => t.Font = @"Exo2.0-Bold"; + void addSpacer(OsuTextFlowContainer textFlow) => textFlow.AddArbitraryDrawable(new Container { Width = 15 }); if (user.JoinDate.ToUniversalTime().Year < 2008) { - infoTextLeft.AddText(new DrawableJoinDate(user.JoinDate), lightText); + bottomTopLinkContainer.AddText("Here since the beginning"); } else { - infoTextLeft.AddText("Joined ", lightText); - infoTextLeft.AddText(new DrawableJoinDate(user.JoinDate), boldItalic); + bottomTopLinkContainer.AddText("Joined "); + bottomTopLinkContainer.AddText(new DrawableDate(user.JoinDate), bold); } + addSpacer(bottomTopLinkContainer); + if (user.LastVisit.HasValue) { - infoTextLeft.NewLine(); - infoTextLeft.AddText("Last seen ", lightText); - infoTextLeft.AddText(new DrawableDate(user.LastVisit.Value), boldItalic); - infoTextLeft.NewParagraph(); + bottomTopLinkContainer.AddText("Last seen "); + bottomTopLinkContainer.AddText(new DrawableDate(user.LastVisit.Value), bold); } - if (user.PlayStyle?.Length > 0) + addSpacer(bottomTopLinkContainer); + + bottomTopLinkContainer.AddText("Contributed "); + bottomTopLinkContainer.AddLink($@"{user.PostCount} forum posts", $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: bold); + + void tryAddInfo(FontAwesome icon, string content, string link = null) { - infoTextLeft.AddText("Plays with ", lightText); - infoTextLeft.AddText(string.Join(", ", user.PlayStyle), boldItalic); - } + if (string.IsNullOrEmpty(content)) return; - infoTextLeft.NewLine(); - infoTextLeft.AddText("Contributed ", lightText); - infoTextLeft.AddLink($@"{user.PostCount} forum posts", url: $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: boldItalic); + bottomLinkContainer.AddIcon(icon, text => + { + text.TextSize = 10; + text.Colour = communityUserGrayGreenLighter; + }); + if (link != null) + { + bottomLinkContainer.AddLink(" " + content, link, creationParameters: bold); + } + else + { + bottomLinkContainer.AddText(" " + content, bold); + } + addSpacer(bottomLinkContainer); + } string websiteWithoutProtcol = user.Website; if (!string.IsNullOrEmpty(websiteWithoutProtcol)) @@ -753,185 +835,16 @@ namespace osu.Game.Overlays.Profile websiteWithoutProtcol = websiteWithoutProtcol.Substring(protocolIndex + 2); } - tryAddInfoRightLine(FontAwesome.fa_map_marker, user.Location); - tryAddInfoRightLine(FontAwesome.fa_heart_o, user.Interests); - tryAddInfoRightLine(FontAwesome.fa_suitcase, user.Occupation); - infoTextRight.NewParagraph(); + tryAddInfo(FontAwesome.fa_map_marker, user.Location); + tryAddInfo(FontAwesome.fa_heart_o, user.Interests); + tryAddInfo(FontAwesome.fa_suitcase, user.Occupation); + bottomLinkContainer.NewLine(); if (!string.IsNullOrEmpty(user.Twitter)) - tryAddInfoRightLine(FontAwesome.fa_twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); - tryAddInfoRightLine(FontAwesome.fa_gamepad, user.Discord); - tryAddInfoRightLine(FontAwesome.fa_skype, user.Skype, @"skype:" + user.Skype + @"?chat"); - tryAddInfoRightLine(FontAwesome.fa_lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); - tryAddInfoRightLine(FontAwesome.fa_globe, websiteWithoutProtcol, user.Website); - - if (user.Statistics != null) - { - levelBadge.Show(); - levelText.Text = user.Statistics.Level.Current.ToString(); - - scoreText.Add(createScoreText("Ranked Score")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.RankedScore.ToString(@"#,0"))); - scoreText.Add(createScoreText("Accuracy")); - scoreNumberText.Add(createScoreNumberText($"{user.Statistics.Accuracy:0.##}%")); - scoreText.Add(createScoreText("Play Count")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.PlayCount.ToString(@"#,0"))); - scoreText.Add(createScoreText("Total Score")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.TotalScore.ToString(@"#,0"))); - scoreText.Add(createScoreText("Total Hits")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.TotalHits.ToString(@"#,0"))); - scoreText.Add(createScoreText("Max Combo")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.MaxCombo.ToString(@"#,0"))); - scoreText.Add(createScoreText("Replays Watched by Others")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.ReplaysWatched.ToString(@"#,0"))); - - gradeSSPlus.DisplayCount = user.Statistics.GradesCount.SSPlus; - gradeSSPlus.Show(); - gradeSS.DisplayCount = user.Statistics.GradesCount.SS; - gradeSS.Show(); - gradeSPlus.DisplayCount = user.Statistics.GradesCount.SPlus; - gradeSPlus.Show(); - gradeS.DisplayCount = user.Statistics.GradesCount.S; - gradeS.Show(); - gradeA.DisplayCount = user.Statistics.GradesCount.A; - gradeA.Show(); - - rankGraph.User.Value = user; - } - - badgeContainer.ShowBadges(user.Badges);*/ - } - - private class CoverInfoTabControl : TabControl - { - private readonly Box bar; - - private Color4 accentColour; - public Color4 AccentColour - { - get => accentColour; - set - { - if (accentColour == value) return; - - accentColour = value; - - bar.Colour = value; - - foreach (TabItem tabItem in TabContainer) - { - ((CoverInfoTabItem)tabItem).AccentColour = value; - } - } - } - - public MarginPadding Padding - { - set => TabContainer.Padding = value; - get => TabContainer.Padding; - } - - public CoverInfoTabControl() - { - TabContainer.Masking = false; - TabContainer.Spacing = new Vector2(20, 0); - - AddInternal(bar = new Box - { - RelativeSizeAxes = Axes.X, - Height = 2, - Anchor = Anchor.BottomLeft, - Origin = Anchor.CentreLeft - }); - } - - protected override Dropdown CreateDropdown() => null; - - protected override TabItem CreateTabItem(string value) => new CoverInfoTabItem(value) - { - AccentColour = AccentColour - }; - - private class CoverInfoTabItem : TabItem - { - private readonly OsuSpriteText text; - private readonly Drawable bar; - - private Color4 accentColour; - public Color4 AccentColour - { - get => accentColour; - set - { - accentColour = value; - - bar.Colour = value; - if (!Active) text.Colour = value; - } - } - - public CoverInfoTabItem(string value) - : base(value) - { - AutoSizeAxes = Axes.X; - RelativeSizeAxes = Axes.Y; - - Children = new[] - { - text = new OsuSpriteText - { - Margin = new MarginPadding { Bottom = 15 }, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Text = value, - TextSize = 14, - Font = "Exo2.0-Bold", - }, - bar = new Circle - { - RelativeSizeAxes = Axes.X, - Height = 0, - Origin = Anchor.CentreLeft, - Anchor = Anchor.BottomLeft, - }, - new HoverClickSounds() - }; - } - - protected override bool OnHover(HoverEvent e) - { - if (!Active) - onActivated(true); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - - if (!Active) - OnDeactivated(); - } - - protected override void OnActivated() - { - onActivated(); - } - - protected override void OnDeactivated() - { - text.FadeColour(AccentColour, 120, Easing.InQuad); - bar.ResizeHeightTo(0, 120, Easing.InQuad); - text.Font = "Exo2.0-Medium"; - } - - private void onActivated(bool fake = false) - { - text.FadeColour(Color4.White, 120, Easing.InQuad); - bar.ResizeHeightTo(7.5f, 120, Easing.InQuad); - if (!fake) - text.Font = "Exo2.0-Bold"; - } - } + tryAddInfo(FontAwesome.fa_twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); + tryAddInfo(FontAwesome.fa_gamepad, user.Discord); //todo: update fontawesome to include discord logo + tryAddInfo(FontAwesome.fa_skype, user.Skype, @"skype:" + user.Skype + @"?chat"); + tryAddInfo(FontAwesome.fa_lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); + tryAddInfo(FontAwesome.fa_link, websiteWithoutProtcol, user.Website); } private class UserStatsLine : Container @@ -1108,5 +1021,37 @@ namespace osu.Game.Overlays.Profile rankSprite.Texture = textures.Get($"Grades/{rank.GetDescription()}"); } } + + private class DrawableBadge : CompositeDrawable, IHasTooltip + { + public static readonly Vector2 DRAWABLE_BADGE_SIZE = new Vector2(86, 40); + + private readonly Badge badge; + + public DrawableBadge(Badge badge) + { + this.badge = badge; + Size = DRAWABLE_BADGE_SIZE; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) + { + InternalChild = new Sprite + { + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Texture = textures.Get(badge.ImageUrl), + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + InternalChild.FadeInFromZero(200); + } + + public string TooltipText => badge.Description; + } } } From da99161736610b1dba44962f8e03bd87a7efd462 Mon Sep 17 00:00:00 2001 From: jorolf Date: Sat, 22 Dec 2018 22:31:11 +0100 Subject: [PATCH 003/623] add some missing stuff --- osu.Game/Graphics/UserInterface/LineGraph.cs | 1 - .../Overlays/Profile/Header/SupporterIcon.cs | 1 - osu.Game/Overlays/Profile/ProfileHeader.cs | 44 ++++++++++++++++--- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index c750f7a89d..4948c0d7f4 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -111,7 +111,6 @@ namespace osu.Game.Graphics.UserInterface { float x = (i + count - values.Length) / (float)(count - 1) * DrawWidth - 1; float y = GetYPosition(values[i]) * DrawHeight - path.PathWidth; - // the -1 is for inner offset in path (actually -PathWidth) path.AddVertex(new Vector2(x, y)); } } diff --git a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs index 8b33a60d37..03135824de 100644 --- a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osuTK; namespace osu.Game.Overlays.Profile.Header { diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 6ab178cfe1..1a51ab4cb6 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Extensions; @@ -39,6 +40,7 @@ namespace osu.Game.Overlays.Profile private readonly Box headerTopBox; private readonly UpdateableAvatar avatar; private readonly OsuSpriteText usernameText; + private readonly ExternalLinkButton openUserExternally; private readonly OsuSpriteText titleText; private readonly DrawableFlag userFlag; private readonly OsuSpriteText userCountryText; @@ -76,6 +78,13 @@ namespace osu.Game.Overlays.Profile private const float cover_height = 150; private const float cover_info_height = 75; private const float avatar_size = 110; + private static readonly Dictionary play_styles = new Dictionary + { + {"keyboard", "Keyboard"}, + {"mouse", "Mouse"}, + {"tablet", "Tablet"}, + {"touch", "Touch Screen"}, + }; [Resolved(CanBeNull = true)] private ChannelManager channelManager { get; set; } @@ -195,10 +204,24 @@ namespace osu.Game.Overlays.Profile Padding = new MarginPadding { Left = 10 }, Children = new Drawable[] { - usernameText = new OsuSpriteText + new FillFlowContainer { - Font = "Exo2.0-Regular", - TextSize = 24 + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + usernameText = new OsuSpriteText + { + Font = "Exo2.0-Regular", + TextSize = 24 + }, + openUserExternally = new ExternalLinkButton + { + Margin = new MarginPadding { Left = 5 }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + } }, new FillFlowContainer { @@ -694,6 +717,7 @@ namespace osu.Game.Overlays.Profile avatar.User = User; usernameText.Text = user.Username; + openUserExternally.Link = $@"https://osu.ppy.sh/users/{user.Id}"; userFlag.Country = user.Country; userCountryText.Text = user.Country?.FullName; SupporterTag.SupporterLevel = user.SupportLevel; @@ -796,16 +820,24 @@ namespace osu.Game.Overlays.Profile addSpacer(bottomTopLinkContainer); + if (user.PlayStyle?.Length > 0) + { + bottomTopLinkContainer.AddText("Plays with "); + bottomTopLinkContainer.AddText(string.Join(", ", user.PlayStyle.Select(style => play_styles[style])), bold); + + addSpacer(bottomTopLinkContainer); + } + if (user.LastVisit.HasValue) { bottomTopLinkContainer.AddText("Last seen "); bottomTopLinkContainer.AddText(new DrawableDate(user.LastVisit.Value), bold); + + addSpacer(bottomTopLinkContainer); } - addSpacer(bottomTopLinkContainer); - bottomTopLinkContainer.AddText("Contributed "); - bottomTopLinkContainer.AddLink($@"{user.PostCount} forum posts", $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: bold); + bottomTopLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: bold); void tryAddInfo(FontAwesome icon, string content, string link = null) { From 360c17e2c76b43525f67b159ff5c3652248543e6 Mon Sep 17 00:00:00 2001 From: jorolf Date: Sat, 22 Dec 2018 22:50:19 +0100 Subject: [PATCH 004/623] appease CodeFactor and AppVeyor --- osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs | 2 +- osu.Game/Overlays/Profile/Header/RankGraph.cs | 6 ++---- osu.Game/Overlays/Profile/ProfileHeader.cs | 1 - 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs index b067273b04..2576d627ea 100644 --- a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs +++ b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs @@ -10,7 +10,7 @@ using osu.Game.Graphics.UserInterface; using osuTK; using osuTK.Graphics; -namespace osu.Game.Overlays.Profile +namespace osu.Game.Overlays.Profile.Header { public class ProfileHeaderTabControl : TabControl { diff --git a/osu.Game/Overlays/Profile/Header/RankGraph.cs b/osu.Game/Overlays/Profile/Header/RankGraph.cs index f67a8e47d4..76a72fe420 100644 --- a/osu.Game/Overlays/Profile/Header/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/RankGraph.cs @@ -190,19 +190,17 @@ namespace osu.Game.Overlays.Profile.Header public string TooltipText => User.Value?.Statistics?.Ranks.Global == null ? "" : $"{ranks[dayIndex].Value:#,##0}|{ranked_days - ranks[dayIndex].Key + 1}"; - public ITooltip GetCustomTooltip() => new RankGraphTooltip(this); + public ITooltip GetCustomTooltip() => new RankGraphTooltip(); public class RankGraphTooltip : VisibilityContainer, ITooltip { - private readonly RankGraph graph; private readonly OsuSpriteText globalRankingText, timeText; private readonly Box background; public string TooltipText { get; set; } - public RankGraphTooltip(RankGraph graph) + public RankGraphTooltip() { - this.graph = graph; AutoSizeAxes = Axes.Both; Masking = true; CornerRadius = 10; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 1a51ab4cb6..77074abd09 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -353,7 +353,6 @@ namespace osu.Game.Overlays.Profile }, } }, - } }, new Container From e05fbd4136e338537e4ec6f9d64f958413b0f705 Mon Sep 17 00:00:00 2001 From: jorolf Date: Tue, 25 Dec 2018 01:09:49 +0100 Subject: [PATCH 005/623] address some comments and improve ui --- .../Overlays/Profile/Header/ProfileHeaderTabControl.cs | 2 +- osu.Game/Overlays/Profile/Header/RankGraph.cs | 2 ++ osu.Game/Overlays/Profile/ProfileHeader.cs | 10 +++++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs index 2576d627ea..db8a0b594c 100644 --- a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs +++ b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs @@ -38,8 +38,8 @@ namespace osu.Game.Overlays.Profile.Header public MarginPadding Padding { - set => TabContainer.Padding = value; get => TabContainer.Padding; + set => TabContainer.Padding = value; } public ProfileHeaderTabControl() diff --git a/osu.Game/Overlays/Profile/Header/RankGraph.cs b/osu.Game/Overlays/Profile/Header/RankGraph.cs index 76a72fe420..e681d2701a 100644 --- a/osu.Game/Overlays/Profile/Header/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/RankGraph.cs @@ -234,6 +234,8 @@ namespace osu.Game.Overlays.Profile.Header { Font = "Exo2.0-Regular", TextSize = 12, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, } } }, diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 77074abd09..7429a65210 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -20,6 +20,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Online.Chat; using osu.Game.Overlays.Profile.Header; using osu.Game.Scoring; @@ -95,6 +96,9 @@ namespace osu.Game.Overlays.Profile [Resolved(CanBeNull = true)] private ChatOverlay chatOverlay { get; set; } + [Resolved] + private APIAccess apiAccess { get; set; } + public ProfileHeader() { Container expandedDetailContainer; @@ -340,6 +344,7 @@ namespace osu.Game.Overlays.Profile }, messageButton = new ProfileHeaderButton { + Alpha = 0, RelativeSizeAxes = Axes.Y, Children = new Drawable[] { @@ -734,13 +739,16 @@ namespace osu.Game.Overlays.Profile followerText.Text = user.FollowerCount?.Length > 0 ? user.FollowerCount[0].ToString("#,##0") : "0"; - if (!user.PMFriendsOnly) + if (!user.PMFriendsOnly && apiAccess.LocalUser.Value.Id != user.Id) + { + messageButton.Show(); messageButton.Action = () => { channelManager?.OpenPrivateChannel(user); userOverlay?.Hide(); chatOverlay?.Show(); }; + } expandButton.Action = DetailsVisible.Toggle; From a33a1458a58bc7f65eb281d06c9691316b0e308f Mon Sep 17 00:00:00 2001 From: jorolf Date: Fri, 4 Jan 2019 22:52:00 +0100 Subject: [PATCH 006/623] update design and make play styles an enum --- osu.Game/Overlays/Profile/ProfileHeader.cs | 24 ++++++++++----------- osu.Game/Users/User.cs | 25 ++++++++++++++++++++-- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 7429a65210..e6d892f0b7 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -75,6 +75,7 @@ namespace osu.Game.Overlays.Profile private readonly Box headerBottomBox; private readonly LinkFlowContainer bottomTopLinkContainer; private readonly LinkFlowContainer bottomLinkContainer; + private Color4 linkBlue; private const float cover_height = 150; private const float cover_info_height = 75; @@ -693,6 +694,7 @@ namespace osu.Game.Overlays.Profile headerBottomBox.Colour = colours.CommunityUserGrayGreenDarker; communityUserGrayGreenLighter = colours.CommunityUserGrayGreenLighter; + linkBlue = colours.BlueLight; } private User user; @@ -827,10 +829,10 @@ namespace osu.Game.Overlays.Profile addSpacer(bottomTopLinkContainer); - if (user.PlayStyle?.Length > 0) + if (user.PlayStyles?.Length > 0) { bottomTopLinkContainer.AddText("Plays with "); - bottomTopLinkContainer.AddText(string.Join(", ", user.PlayStyle.Select(style => play_styles[style])), bold); + bottomTopLinkContainer.AddText(string.Join(", ", user.PlayStyles.Select(style => style.GetDescription())), bold); addSpacer(bottomTopLinkContainer); } @@ -857,7 +859,11 @@ namespace osu.Game.Overlays.Profile }); if (link != null) { - bottomLinkContainer.AddLink(" " + content, link, creationParameters: bold); + bottomLinkContainer.AddLink(" " + content, link, creationParameters: text => + { + bold(text); + text.Colour = linkBlue; + }); } else { @@ -888,8 +894,6 @@ namespace osu.Game.Overlays.Profile private class UserStatsLine : Container { - private readonly OsuSpriteText rightText; - public UserStatsLine(string left, string right) { RelativeSizeAxes = Axes.X; @@ -902,22 +906,16 @@ namespace osu.Game.Overlays.Profile Text = left, Font = "Exo2.0-Medium" }, - rightText = new OsuSpriteText + new OsuSpriteText { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, TextSize = 15, Text = right, - Font = "Exo2.0-Medium" + Font = "Exo2.0-Bold" }, }; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - rightText.Colour = colours.BlueLight; - } } private class ProfileHeaderButton : OsuHoverContainer diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 485c953b75..1005b094aa 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -2,7 +2,11 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.ComponentModel; +using System.Linq; +using System.Security.Cryptography; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using osu.Framework.Configuration; namespace osu.Game.Users @@ -113,8 +117,13 @@ namespace osu.Game.Users [JsonProperty(@"follower_count")] public int[] FollowerCount; - [JsonProperty(@"playstyle")] - public string[] PlayStyle; + [JsonProperty] + private string[] playstyle + { + set { PlayStyles = value?.Select(str => Enum.Parse(typeof(PlayStyle), str, true)).Cast().ToArray(); } + } + + public PlayStyle[] PlayStyles; [JsonProperty(@"playmode")] public string PlayMode; @@ -174,5 +183,17 @@ namespace osu.Game.Users Username = "system", Id = 0 }; + + public enum PlayStyle + { + [Description("Keyboard")] + Keyboard, + [Description("Mouse")] + Mouse, + [Description("Tablet")] + Tablet, + [Description("Touch Screen")] + Touch, + } } } From 8bab87c072e96ef0ca2784d5ad6716ef7709994e Mon Sep 17 00:00:00 2001 From: jorolf Date: Fri, 4 Jan 2019 23:09:21 +0100 Subject: [PATCH 007/623] update resources and remove unused usings --- osu-resources | 2 +- osu.Game/Users/User.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu-resources b/osu-resources index 9880089b4e..677897728f 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit 9880089b4e8fcd78d68f30c8a40d43bf8dccca86 +Subproject commit 677897728f4332fa1200e0280ca02c4b987c6c47 diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 1005b094aa..f90781df77 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -4,9 +4,7 @@ using System; using System.ComponentModel; using System.Linq; -using System.Security.Cryptography; using Newtonsoft.Json; -using Newtonsoft.Json.Converters; using osu.Framework.Configuration; namespace osu.Game.Users From f83163e78e626ab72ecf544eba5d8e5d8e0b3084 Mon Sep 17 00:00:00 2001 From: HoLLy Date: Tue, 8 Jan 2019 22:28:48 +0100 Subject: [PATCH 008/623] Attempt to implement Catch performance calculator --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 3 + .../Difficulty/CatchPerformanceCalculator.cs | 98 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 1f1d2475f6..2b0de183d3 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -16,6 +16,7 @@ using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Difficulty; using osu.Game.Rulesets.Difficulty; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Catch { @@ -112,6 +113,8 @@ namespace osu.Game.Rulesets.Catch public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new CatchPerformanceCalculator(this, beatmap, score); + public override int? LegacyID => 2; public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs new file mode 100644 index 0000000000..feb7db41aa --- /dev/null +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -0,0 +1,98 @@ +// Copyright (c) 2007-2019 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Difficulty +{ + public class CatchPerformanceCalculator : PerformanceCalculator + { + protected new CatchDifficultyAttributes Attributes => (CatchDifficultyAttributes)base.Attributes; + + private Mod[] mods; + private int countGreat; + private int countGood; + private int countMeh; + private int countMiss; + private int countKatu; + + public CatchPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) + : base(ruleset, beatmap, score) { } + + public override double Calculate(Dictionary categoryDifficulty = null) + { + mods = Score.Mods; + countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]); + countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]); + countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]); + countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]); + countKatu = Convert.ToInt32(Score.Statistics[HitResult.Ok]); // TODO: check + + // Don't count scores made with supposedly unranked mods + if (mods.Any(m => !m.Ranked)) + return 0; + + // We are heavily relying on aim in catch the beat + // TODO: replaced Aim with StarRating, not sure if this is correct! + double value = Math.Pow(5.0f * Math.Max(1.0f, Attributes.StarRating / 0.0049f) - 4.0f, 2.0f) / 100000.0f; + + // Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo + int numTotalHits = totalComboHits(); + + // Longer maps are worth more + float lengthBonus = + 0.95f + 0.4f * Math.Min(1.0f, numTotalHits / 3000.0f) + + (numTotalHits > 3000 ? (float)Math.Log10(numTotalHits / 3000.0f) * 0.5f : 0.0f); + + // Longer maps are worth more + value *= lengthBonus; + + // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available + value *= Math.Pow(0.97f, countMiss); + + // Combo scaling + float beatmapMaxCombo = Attributes.MaxCombo; + if (beatmapMaxCombo > 0) + value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); + + float approachRate = (float)Attributes.ApproachRate; + float approachRateFactor = 1.0f; + if (approachRate > 9.0f) + approachRateFactor += 0.1f * (approachRate - 9.0f); // 10% for each AR above 9 + else if (approachRate < 8.0f) + approachRateFactor += 0.025f * (8.0f - approachRate); // 2.5% for each AR below 8 + + value *= approachRateFactor; + + if (mods.Any(m => m is ModHidden)) + // Hiddens gives nothing on max approach rate, and more the lower it is + value *= 1.05f + 0.075f * (10.0f - Math.Min(10.0f, approachRate)); // 7.5% for each AR below 10 + + if (mods.Any(m => m is ModFlashlight)) + // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps. + value *= 1.35f * lengthBonus; + + // Scale the aim value with accuracy _slightly_ + value *= Math.Pow(accuracy(), 5.5f); + + // Custom multipliers for NoFail. SpunOut is not applicable. + if (mods.Any(m => m is ModNoFail)) + value *= 0.90f; + + return value; + } + + private float accuracy() => totalHits() == 0 ? 0 : MathHelper.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f); + private int totalHits() => countMeh + countGood + countGreat + countMiss + countKatu; + private int totalSuccessfulHits() => countMeh + countGood + countGreat; + private int totalComboHits() => countMeh + countGood + countGreat; + } +} From 41d0bff243fd4115b96e01188ae851da2775c9c5 Mon Sep 17 00:00:00 2001 From: HoLLy Date: Wed, 9 Jan 2019 00:20:15 +0100 Subject: [PATCH 009/623] Assume katu is part of HitResult.Miss --- .../Difficulty/CatchPerformanceCalculator.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index feb7db41aa..a64d23197b 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty private int countGood; private int countMeh; private int countMiss; - private int countKatu; public CatchPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) : base(ruleset, beatmap, score) { } @@ -34,7 +33,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]); countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]); countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]); - countKatu = Convert.ToInt32(Score.Statistics[HitResult.Ok]); // TODO: check // Don't count scores made with supposedly unranked mods if (mods.Any(m => !m.Ranked)) @@ -91,7 +89,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty } private float accuracy() => totalHits() == 0 ? 0 : MathHelper.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f); - private int totalHits() => countMeh + countGood + countGreat + countMiss + countKatu; + private int totalHits() => countMeh + countGood + countGreat + countMiss; // TODO: not counting katu private int totalSuccessfulHits() => countMeh + countGood + countGreat; private int totalComboHits() => countMeh + countGood + countGreat; } From 75a5691c5fe94523ff5530df53358ed5934a1703 Mon Sep 17 00:00:00 2001 From: HoLLy Date: Wed, 9 Jan 2019 00:51:49 +0100 Subject: [PATCH 010/623] Set license header year to 2018, remove old TODO comment --- .../Difficulty/CatchPerformanceCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index a64d23197b..5596080167 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2007-2019 ppy Pty Ltd . +// Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty } private float accuracy() => totalHits() == 0 ? 0 : MathHelper.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f); - private int totalHits() => countMeh + countGood + countGreat + countMiss; // TODO: not counting katu + private int totalHits() => countMeh + countGood + countGreat + countMiss; private int totalSuccessfulHits() => countMeh + countGood + countGreat; private int totalComboHits() => countMeh + countGood + countGreat; } From 09f12fcd425384d32ad3f87e02b427d80633f332 Mon Sep 17 00:00:00 2001 From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com> Date: Thu, 10 Jan 2019 22:47:28 +0000 Subject: [PATCH 011/623] Removed TODO comment regarding Aim being StarRating Co-Authored-By: HoLLy-HaCKeR --- osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 5596080167..b5262d02f5 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -39,7 +39,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty return 0; // We are heavily relying on aim in catch the beat - // TODO: replaced Aim with StarRating, not sure if this is correct! double value = Math.Pow(5.0f * Math.Max(1.0f, Attributes.StarRating / 0.0049f) - 4.0f, 2.0f) / 100000.0f; // Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo From 6ba7e2b670da39a224b1ee9428553683485651a3 Mon Sep 17 00:00:00 2001 From: jorolf Date: Fri, 11 Jan 2019 01:12:19 +0100 Subject: [PATCH 012/623] split the profile header into several components --- osu.Game.Tests/Visual/TestCaseUserProfile.cs | 76 +- .../Visual/TestCaseUserProfileHeader.cs | 76 ++ .../Profile/Header/BottomHeaderContainer.cs | 160 ++++ .../Profile/Header/CenterHeaderContainer.cs | 321 +++++++ .../Profile/Header/DetailHeaderContainer.cs | 238 +++++ .../Profile/Header/MedalHeaderContainer.cs | 132 +++ .../Profile/Header/TopHeaderContainer.cs | 210 +++++ osu.Game/Overlays/Profile/ProfileHeader.cs | 892 +----------------- 8 files changed, 1197 insertions(+), 908 deletions(-) create mode 100644 osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs create mode 100644 osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs create mode 100644 osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs create mode 100644 osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs create mode 100644 osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs create mode 100644 osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs index cff55c2506..3753328427 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs @@ -32,6 +32,44 @@ namespace osu.Game.Tests.Visual typeof(SupporterIcon) }; + public static readonly User TEST_USER = new User + { + Username = @"Somebody", + Id = 1, + Country = new Country { FullName = @"Alien" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", + JoinDate = DateTimeOffset.Now.AddDays(-1), + LastVisit = DateTimeOffset.Now, + ProfileOrder = new[] { "me" }, + Statistics = new UserStatistics + { + Ranks = new UserStatistics.UserRanks { Global = 2148, Country = 1 }, + PP = 4567.89m, + Level = new UserStatistics.LevelInfo + { + Current = 727, + Progress = 69, + } + }, + RankHistory = new User.RankHistoryData + { + Mode = @"osu", + Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() + }, + Badges = new[] + { + new Badge + { + AwardedAt = DateTimeOffset.FromUnixTimeSeconds(1505741569), + Description = "Outstanding help by being a voluntary test subject.", + ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.jpg" + } + }, + Title = "osu!volunteer", + Colour = "ff0000", + Achievements = new User.UserAchievement[0], + }; + public TestCaseUserProfile() { Add(profile = new TestUserProfileOverlay()); @@ -47,43 +85,7 @@ namespace osu.Game.Tests.Visual { base.LoadComplete(); - AddStep("Show offline dummy", () => profile.ShowUser(new User - { - Username = @"Somebody", - Id = 1, - Country = new Country { FullName = @"Alien" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", - JoinDate = DateTimeOffset.Now.AddDays(-1), - LastVisit = DateTimeOffset.Now, - ProfileOrder = new[] { "me" }, - Statistics = new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = 2148, Country = 1 }, - PP = 4567.89m, - Level = new UserStatistics.LevelInfo - { - Current = 727, - Progress = 69, - } - }, - RankHistory = new User.RankHistoryData - { - Mode = @"osu", - Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() - }, - Badges = new[] - { - new Badge - { - AwardedAt = DateTimeOffset.FromUnixTimeSeconds(1505741569), - Description = "Outstanding help by being a voluntary test subject.", - ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.jpg" - } - }, - Title = "osu!volunteer", - Colour = "ff0000", - Achievements = new User.UserAchievement[0], - }, false)); + AddStep("Show offline dummy", () => profile.ShowUser(TEST_USER, false)); checkSupporterTag(false); diff --git a/osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs b/osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs new file mode 100644 index 0000000000..c80227f149 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs @@ -0,0 +1,76 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Profile; +using osu.Game.Overlays.Profile.Header; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseUserProfileHeader : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ProfileHeader), + typeof(RankGraph), + typeof(LineGraph), + typeof(SupporterIcon) + }; + + [Resolved] + private APIAccess api { get; set; } + + private readonly ProfileHeader header; + + public TestCaseUserProfileHeader() + { + header = new ProfileHeader(); + Add(header); + + AddStep("Show offline dummy", () => header.User = TestCaseUserProfile.TEST_USER); + + AddStep("Show null dummy", () => header.User = new User + { + Username = "Null" + }); + + addOnlineStep("Show ppy", new User + { + Username = @"peppy", + Id = 2, + IsSupporter = true, + Country = new Country { FullName = @"Australia", FlagName = @"AU" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" + }); + + addOnlineStep("Show flyte", new User + { + Username = @"flyte", + Id = 3103765, + Country = new Country { FullName = @"Japan", FlagName = @"JP" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" + }); + } + + private void addOnlineStep(string name, User fallback) + { + AddStep(name, () => + { + if (api.IsLoggedIn) + { + var request = new GetUserRequest(fallback.Id); + request.Success += user => header.User = user; + api.Queue(request); + } + else + header.User = fallback; + }); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs new file mode 100644 index 0000000000..a3a6447a9f --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -0,0 +1,160 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header +{ + public class BottomHeaderContainer : Container + { + private LinkFlowContainer bottomTopLinkContainer; + private LinkFlowContainer bottomLinkContainer; + private Color4 linkBlue, communityUserGrayGreenLighter; + + private User user; + public User User + { + get => user; + set + { + if (user == value) return; + user = value; + updateDisplay(); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.CommunityUserGrayGreenDarker, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + bottomTopLinkContainer = new LinkFlowContainer(text => text.TextSize = 12) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + bottomLinkContainer = new LinkFlowContainer(text => text.TextSize = 12) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + } + } + }; + + linkBlue = colours.BlueLight; + communityUserGrayGreenLighter = colours.CommunityUserGrayGreenLighter; + } + + private void updateDisplay() + { + void bold(SpriteText t) => t.Font = @"Exo2.0-Bold"; + void addSpacer(OsuTextFlowContainer textFlow) => textFlow.AddArbitraryDrawable(new Container { Width = 15 }); + + bottomTopLinkContainer.Clear(); + bottomLinkContainer.Clear(); + + if (user == null) return; + + if (user.JoinDate.ToUniversalTime().Year < 2008) + { + bottomTopLinkContainer.AddText("Here since the beginning"); + } + else + { + bottomTopLinkContainer.AddText("Joined "); + bottomTopLinkContainer.AddText(new DrawableDate(user.JoinDate), bold); + } + + addSpacer(bottomTopLinkContainer); + + if (user.PlayStyles?.Length > 0) + { + bottomTopLinkContainer.AddText("Plays with "); + bottomTopLinkContainer.AddText(string.Join(", ", user.PlayStyles.Select(style => style.GetDescription())), bold); + + addSpacer(bottomTopLinkContainer); + } + + if (user.LastVisit.HasValue) + { + bottomTopLinkContainer.AddText("Last seen "); + bottomTopLinkContainer.AddText(new DrawableDate(user.LastVisit.Value), bold); + + addSpacer(bottomTopLinkContainer); + } + + bottomTopLinkContainer.AddText("Contributed "); + bottomTopLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: bold); + + void tryAddInfo(FontAwesome icon, string content, string link = null) + { + if (string.IsNullOrEmpty(content)) return; + + bottomLinkContainer.AddIcon(icon, text => + { + text.TextSize = 10; + text.Colour = communityUserGrayGreenLighter; + }); + if (link != null) + { + bottomLinkContainer.AddLink(" " + content, link, creationParameters: text => + { + bold(text); + text.Colour = linkBlue; + }); + } + else + { + bottomLinkContainer.AddText(" " + content, bold); + } + addSpacer(bottomLinkContainer); + } + + string websiteWithoutProtcol = user.Website; + if (!string.IsNullOrEmpty(websiteWithoutProtcol)) + { + int protocolIndex = websiteWithoutProtcol.IndexOf("//", StringComparison.Ordinal); + if (protocolIndex >= 0) + websiteWithoutProtcol = websiteWithoutProtcol.Substring(protocolIndex + 2); + } + + tryAddInfo(FontAwesome.fa_map_marker, user.Location); + tryAddInfo(FontAwesome.fa_heart_o, user.Interests); + tryAddInfo(FontAwesome.fa_suitcase, user.Occupation); + bottomLinkContainer.NewLine(); + if (!string.IsNullOrEmpty(user.Twitter)) + tryAddInfo(FontAwesome.fa_twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); + tryAddInfo(FontAwesome.fa_gamepad, user.Discord); //todo: update fontawesome to include discord logo + tryAddInfo(FontAwesome.fa_skype, user.Skype, @"skype:" + user.Skype + @"?chat"); + tryAddInfo(FontAwesome.fa_lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); + tryAddInfo(FontAwesome.fa_link, websiteWithoutProtcol, user.Website); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs new file mode 100644 index 0000000000..9005fd5ab2 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs @@ -0,0 +1,321 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.Chat; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header +{ + public class CenterHeaderContainer : Container + { + public readonly BindableBool DetailsVisible = new BindableBool(); + + private OsuSpriteText followerText; + private ProfileHeaderButton messageButton; + private OsuSpriteText levelBadgeText; + + private Bar levelProgressBar; + private OsuSpriteText levelProgressText; + + private ProfileHeader.OverlinedInfoContainer hiddenDetailGlobal, hiddenDetailCountry; + + [Resolved(CanBeNull = true)] + private ChannelManager channelManager { get; set; } + + [Resolved(CanBeNull = true)] + private UserProfileOverlay userOverlay { get; set; } + + [Resolved(CanBeNull = true)] + private ChatOverlay chatOverlay { get; set; } + + [Resolved] + private APIAccess apiAccess { get; set; } + + private User user; + public User User + { + get => user; + set + { + if (user == value) return; + user = value; + updateDisplay(); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, TextureStore textures) + { + Container hiddenDetailContainer, expandedDetailContainer; + SpriteIcon expandButtonIcon; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.CommunityUserGrayGreenDark + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Vertical = 10 }, + Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new ProfileHeaderButton + { + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Right = 10 }, + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.fa_user, + FillMode = FillMode.Fit, + Size = new Vector2(50, 14) + }, + followerText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + TextSize = 16, + Font = "Exo2.0-Bold" + } + } + } + } + }, + messageButton = new ProfileHeaderButton + { + Alpha = 0, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.fa_envelope, + FillMode = FillMode.Fit, + Size = new Vector2(50, 14) + }, + } + }, + } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Y, + Padding = new MarginPadding { Vertical = 10 }, + Width = UserProfileOverlay.CONTENT_X_MARGIN, + Child = new ProfileHeaderButton + { + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = DetailsVisible.Toggle, + Children = new Drawable[] + { + expandButtonIcon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(20), + Icon = FontAwesome.fa_chevron_up, + }, + } + }, + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN }, + Children = new Drawable[] + { + new ProfileHeader.HasTooltipContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(40), + TooltipText = "Level", + Children = new Drawable[] + { + new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = textures.Get("Profile/levelbadge"), + Colour = colours.Yellow, + }, + levelBadgeText = new OsuSpriteText + { + TextSize = 20, + Font = "Exo2.0-Medium", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + } + }, + expandedDetailContainer = new ProfileHeader.HasTooltipContainer + { + TooltipText = "Progress to next level", + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 200, + Height = 6, + Margin = new MarginPadding { Right = 50 }, + Children = new Drawable[] + { + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = levelProgressBar = new Bar + { + RelativeSizeAxes = Axes.Both, + BackgroundColour = Color4.Black, + Direction = BarDirection.LeftToRight, + AccentColour = colours.Yellow + } + }, + levelProgressText = new OsuSpriteText + { + Anchor = Anchor.BottomRight, + Origin = Anchor.TopRight, + Font = "Exo2.0-Bold", + TextSize = 12, + } + } + }, + hiddenDetailContainer = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 200, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Spacing = new Vector2(10, 0), + Margin = new MarginPadding { Right = 50 }, + Children = new[] + { + hiddenDetailGlobal = new ProfileHeader.OverlinedInfoContainer + { + Title = "Global Ranking", + LineColour = colours.Yellow + }, + hiddenDetailCountry = new ProfileHeader.OverlinedInfoContainer + { + Title = "Country Ranking", + LineColour = colours.Yellow + }, + } + } + } + } + }; + + DetailsVisible.ValueChanged += newValue => expandButtonIcon.Icon = newValue ? FontAwesome.fa_chevron_down : FontAwesome.fa_chevron_up; + DetailsVisible.ValueChanged += newValue => hiddenDetailContainer.Alpha = newValue ? 1 : 0; + DetailsVisible.ValueChanged += newValue => expandedDetailContainer.Alpha = newValue ? 0 : 1; + } + + private void updateDisplay() + { + followerText.Text = user.FollowerCount?.Length > 0 ? user.FollowerCount[0].ToString("#,##0") : "0"; + + if (!user.PMFriendsOnly && apiAccess.LocalUser.Value.Id != user.Id) + { + messageButton.Show(); + messageButton.Action = () => + { + channelManager?.OpenPrivateChannel(user); + userOverlay?.Hide(); + chatOverlay?.Show(); + }; + } + else + { + messageButton.Hide(); + } + + levelBadgeText.Text = user.Statistics?.Level.Current.ToString() ?? "0"; + levelProgressBar.Length = user.Statistics?.Level.Progress / 100f ?? 0; + levelProgressText.Text = user.Statistics?.Level.Progress.ToString("0'%'"); + + hiddenDetailGlobal.Content = user?.Statistics?.Ranks.Global?.ToString("#,##0") ?? "-"; + hiddenDetailCountry.Content = user?.Statistics?.Ranks.Country?.ToString("#,##0") ?? "-"; + + } + + private class ProfileHeaderButton : OsuHoverContainer + { + private readonly Box background; + private readonly Container content; + + protected override Container Content => content; + + protected override IEnumerable EffectTargets => new[] { background }; + + public ProfileHeaderButton() + { + HoverColour = Color4.Black.Opacity(0.75f); + IdleColour = Color4.Black.Opacity(0.7f); + AutoSizeAxes = Axes.X; + + base.Content.Add(new CircularContainer + { + Masking = true, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + content = new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 10 }, + } + } + }); + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs new file mode 100644 index 0000000000..678fc2ddb7 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -0,0 +1,238 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Scoring; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header +{ + public class DetailHeaderContainer : Container + { + private ProfileHeader.HasTooltipContainer totalPlayTimeTooltip; + private ProfileHeader.OverlinedInfoContainer totalPlayTimeInfo, medalInfo, ppInfo; + private readonly Dictionary scoreRankInfos = new Dictionary(); + private ProfileHeader.OverlinedInfoContainer detailGlobalRank, detailCountryRank; + private RankGraph rankGraph; + + private User user; + public User User + { + get => user; + set + { + if (user == value) return; + user = value; + updateDisplay(); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.CommunityUserGrayGreenDarkest, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + totalPlayTimeTooltip = new ProfileHeader.HasTooltipContainer + { + AutoSizeAxes = Axes.Both, + TooltipText = "0 hours", + Child = totalPlayTimeInfo = new ProfileHeader.OverlinedInfoContainer + { + Title = "Total Play Time", + LineColour = colours.Yellow, + }, + }, + medalInfo = new ProfileHeader.OverlinedInfoContainer + { + Title = "Medals", + LineColour = colours.GreenLight, + }, + ppInfo = new ProfileHeader.OverlinedInfoContainer + { + Title = "pp", + LineColour = colours.Red, + }, + } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Direction = FillDirection.Horizontal, + Children = new[] + { + scoreRankInfos[ScoreRank.XH] = new ScoreRankInfo(ScoreRank.XH), + scoreRankInfos[ScoreRank.X] = new ScoreRankInfo(ScoreRank.X), + scoreRankInfos[ScoreRank.SH] = new ScoreRankInfo(ScoreRank.SH), + scoreRankInfos[ScoreRank.S] = new ScoreRankInfo(ScoreRank.S), + scoreRankInfos[ScoreRank.A] = new ScoreRankInfo(ScoreRank.A), + } + } + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 130 }, + Children = new Drawable[] + { + rankGraph = new RankGraph + { + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Width = 130, + Anchor = Anchor.TopRight, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Horizontal = 10 }, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + detailGlobalRank = new ProfileHeader.OverlinedInfoContainer(true, 110) + { + Title = "Global Ranking", + LineColour = colours.Yellow, + }, + detailCountryRank = new ProfileHeader.OverlinedInfoContainer(false, 110) + { + Title = "Country Ranking", + LineColour = colours.Yellow, + }, + } + } + } + }, + } + }, + }; + } + + private void updateDisplay() + { + medalInfo.Content = user?.Achievements?.Length.ToString() ?? "0"; + ppInfo.Content = user?.Statistics?.PP?.ToString("#,##0") ?? "0"; + + string formatTime(int? secondsNull) + { + if (secondsNull == null) return "0h 0m"; + + int seconds = secondsNull.Value; + string time = ""; + + int days = seconds / 86400; + seconds -= days * 86400; + if (days > 0) + time += days + "d "; + + int hours = seconds / 3600; + seconds -= hours * 3600; + time += hours + "h "; + + int minutes = seconds / 60; + time += minutes + "m"; + + return time; + } + + totalPlayTimeInfo.Content = formatTime(user?.Statistics?.PlayTime); + totalPlayTimeTooltip.TooltipText = (user?.Statistics?.PlayTime ?? 0) / 3600 + " hours"; + + foreach (var scoreRankInfo in scoreRankInfos) + scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount.GetForScoreRank(scoreRankInfo.Key) ?? 0; + + detailGlobalRank.Content = user?.Statistics?.Ranks.Global?.ToString("#,##0") ?? "-"; + detailCountryRank.Content = user?.Statistics?.Ranks.Country?.ToString("#,##0") ?? "-"; + + rankGraph.User.Value = user; + } + + private class ScoreRankInfo : CompositeDrawable + { + private readonly ScoreRank rank; + private readonly Sprite rankSprite; + private readonly OsuSpriteText rankCount; + + public int RankCount + { + set => rankCount.Text = value.ToString("#,##0"); + } + + public ScoreRankInfo(ScoreRank rank) + { + this.rank = rank; + + AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Width = 56, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + rankSprite = new Sprite + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit + }, + rankCount = new OsuSpriteText + { + Font = "Exo2.0-Bold", + TextSize = 12, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + rankSprite.Texture = textures.Get($"Grades/{rank.GetDescription()}"); + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs new file mode 100644 index 0000000000..74f1e2f689 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -0,0 +1,132 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header +{ + public class MedalHeaderContainer : Container + { + private FillFlowContainer badgeFlowContainer; + + private User user; + public User User + { + get => user; + set + { + if (user == value) return; + user = value; + updateDisplay(); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Alpha = 0; + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.CommunityUserGrayGreenDarkest, + }, + new Container //artificial shadow + { + RelativeSizeAxes = Axes.X, + Height = 3, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new ColourInfo + { + TopLeft = Color4.Black.Opacity(0.2f), + TopRight = Color4.Black.Opacity(0.2f), + BottomLeft = Color4.Black.Opacity(0), + BottomRight = Color4.Black.Opacity(0) + } + }, + }, + badgeFlowContainer = new FillFlowContainer + { + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 5 }, + Spacing = new Vector2(10, 10), + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + } + }; + } + + private void updateDisplay() + { + var badges = User.Badges; + badgeFlowContainer.Clear(); + if (badges?.Length > 0) + { + Show(); + for (var index = 0; index < badges.Length; index++) + { + int displayIndex = index; + LoadComponentAsync(new DrawableBadge(badges[index]), asyncBadge => + { + badgeFlowContainer.Add(asyncBadge); + + // load in stable order regardless of async load order. + badgeFlowContainer.SetLayoutPosition(asyncBadge, displayIndex); + }); + } + } + else + { + Hide(); + } + } + + private class DrawableBadge : CompositeDrawable, IHasTooltip + { + public static readonly Vector2 DRAWABLE_BADGE_SIZE = new Vector2(86, 40); + + private readonly Badge badge; + + public DrawableBadge(Badge badge) + { + this.badge = badge; + Size = DRAWABLE_BADGE_SIZE; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) + { + InternalChild = new Sprite + { + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Texture = textures.Get(badge.ImageUrl), + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + InternalChild.FadeInFromZero(200); + } + + public string TooltipText => badge.Description; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs new file mode 100644 index 0000000000..b003f08b95 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -0,0 +1,210 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header +{ + public class TopHeaderContainer : Container + { + public SupporterIcon SupporterTag; + private UpdateableAvatar avatar; + private OsuSpriteText usernameText; + private ExternalLinkButton openUserExternally; + private OsuSpriteText titleText; + private DrawableFlag userFlag; + private OsuSpriteText userCountryText; + private FillFlowContainer userStats; + + private const float avatar_size = 110; + + private User user; + public User User + { + get => user; + set + { + if (user == value) return; + user = value; + updateDisplay(); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.CommunityUserGrayGreenDarker, + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, + Height = avatar_size, + AutoSizeAxes = Axes.X, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new[] + { + avatar = new UpdateableAvatar + { + Size = new Vector2(avatar_size), + Masking = true, + CornerRadius = avatar_size * 0.25f, + OpenOnClick = { Value = false }, + }, + new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Padding = new MarginPadding { Left = 10 }, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + usernameText = new OsuSpriteText + { + Font = "Exo2.0-Regular", + TextSize = 24 + }, + openUserExternally = new ExternalLinkButton + { + Margin = new MarginPadding { Left = 5 }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + } + }, + new FillFlowContainer + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + titleText = new OsuSpriteText + { + TextSize = 18, + Font = "Exo2.0-Regular" + }, + SupporterTag = new SupporterIcon + { + Height = 20, + Margin = new MarginPadding { Top = 5 } + }, + new Box + { + RelativeSizeAxes = Axes.X, + Height = 1.5f, + Margin = new MarginPadding { Top = 10 }, + Colour = colours.CommunityUserGrayGreenLighter, + }, + new Container + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5 }, + Children = new Drawable[] + { + userFlag = new DrawableFlag + { + Size = new Vector2(30, 20) + }, + userCountryText = new OsuSpriteText + { + Font = "Exo2.0-Regular", + TextSize = 17.5f, + Margin = new MarginPadding { Left = 40 }, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Colour = colours.CommunityUserGrayGreenLighter, + } + } + }, + } + } + } + } + } + }, + userStats = new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Y, + Width = 300, + Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN }, + Padding = new MarginPadding { Vertical = 15 }, + Spacing = new Vector2(0, 2) + } + }; + } + + private void updateDisplay() + { + avatar.User = User; + usernameText.Text = user.Username; + openUserExternally.Link = $@"https://osu.ppy.sh/users/{user.Id}"; + userFlag.Country = user.Country; + userCountryText.Text = user.Country?.FullName ?? "Alien"; + SupporterTag.SupporterLevel = user.SupportLevel; + titleText.Text = user.Title; + titleText.Colour = OsuColour.FromHex(user.Colour ?? "fff"); + + userStats.Clear(); + if (user.Statistics != null) + { + userStats.Add(new UserStatsLine("Ranked Score", user.Statistics.RankedScore.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Hit Accuracy", Math.Round(user.Statistics.Accuracy, 2).ToString("#0.00'%'"))); + userStats.Add(new UserStatsLine("Play Count", user.Statistics.PlayCount.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Total Score", user.Statistics.TotalScore.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Total Hits", user.Statistics.TotalHits.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Maximum Combo", user.Statistics.MaxCombo.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Replays Watched by Others", user.Statistics.ReplaysWatched.ToString("#,##0"))); + } + } + + private class UserStatsLine : Container + { + public UserStatsLine(string left, string right) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Children = new Drawable[] + { + new OsuSpriteText + { + TextSize = 15, + Text = left, + Font = "Exo2.0-Medium" + }, + new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + TextSize = 15, + Text = right, + Font = "Exo2.0-Bold" + }, + }; + } + } + } +} diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index e6d892f0b7..8709640b48 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -1,12 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; -using osu.Framework.Extensions; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -14,91 +10,32 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; -using osu.Game.Online.Chat; using osu.Game.Overlays.Profile.Header; -using osu.Game.Scoring; using osu.Game.Users; -using osuTK; namespace osu.Game.Overlays.Profile { public class ProfileHeader : Container { - private readonly RankGraph rankGraph; - - public readonly SupporterIcon SupporterTag; private readonly Container coverContainer; private readonly OsuSpriteText coverInfoText; private readonly ProfileHeaderTabControl infoTabControl; - private readonly Box headerTopBox; - private readonly UpdateableAvatar avatar; - private readonly OsuSpriteText usernameText; - private readonly ExternalLinkButton openUserExternally; - private readonly OsuSpriteText titleText; - private readonly DrawableFlag userFlag; - private readonly OsuSpriteText userCountryText; - private readonly Box userIconSeperatorBox; - private readonly FillFlowContainer userStats; - - private readonly Box headerCenterBox; - private readonly OsuSpriteText followerText; - private readonly ProfileHeaderButton messageButton; - private readonly ProfileHeaderButton expandButton; - private readonly Sprite levelBadgeSprite; - private readonly OsuSpriteText levelBadgeText; - - private readonly Bar levelProgressBar; - private readonly OsuSpriteText levelProgressText; - - private readonly OverlinedInfoContainer hiddenDetailGlobal, hiddenDetailCountry; + private readonly TopHeaderContainer topHeaderContainer; + public SupporterIcon SupporterTag => topHeaderContainer.SupporterTag; + private readonly CenterHeaderContainer centerHeaderContainer; public readonly BindableBool DetailsVisible = new BindableBool(); - private readonly Box headerDetailBox; - private readonly HasTooltipContainer totalPlayTimeTooltip; - private readonly OverlinedInfoContainer totalPlayTimeInfo, medalInfo, ppInfo; - private readonly Dictionary scoreRankInfos = new Dictionary(); - private readonly OverlinedInfoContainer detailGlobalRank, detailCountryRank; - - private readonly Box headerBadgeBox; - private readonly FillFlowContainer badgeFlowContainer; - private readonly Container badgeContainer; - - private readonly Box headerBottomBox; - private readonly LinkFlowContainer bottomTopLinkContainer; - private readonly LinkFlowContainer bottomLinkContainer; - private Color4 linkBlue; + private readonly DetailHeaderContainer detailHeaderContainer; + private readonly MedalHeaderContainer medalHeaderContainer; + private readonly BottomHeaderContainer bottomHeaderContainer; private const float cover_height = 150; private const float cover_info_height = 75; - private const float avatar_size = 110; - private static readonly Dictionary play_styles = new Dictionary - { - {"keyboard", "Keyboard"}, - {"mouse", "Mouse"}, - {"tablet", "Tablet"}, - {"touch", "Touch Screen"}, - }; - - [Resolved(CanBeNull = true)] - private ChannelManager channelManager { get; set; } - - [Resolved(CanBeNull = true)] - private UserProfileOverlay userOverlay { get; set; } - - [Resolved(CanBeNull = true)] - private ChatOverlay chatOverlay { get; set; } - - [Resolved] - private APIAccess apiAccess { get; set; } public ProfileHeader() { @@ -175,478 +112,30 @@ namespace osu.Game.Overlays.Profile Direction = FillDirection.Vertical, Children = new Drawable[] { - new Container + topHeaderContainer = new TopHeaderContainer { RelativeSizeAxes = Axes.X, Height = 150, - Children = new Drawable[] - { - headerTopBox = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, - Height = avatar_size, - AutoSizeAxes = Axes.X, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Children = new[] - { - avatar = new UpdateableAvatar - { - Size = new Vector2(avatar_size), - Masking = true, - CornerRadius = avatar_size * 0.25f, - OpenOnClick = { Value = false }, - }, - new Container - { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Padding = new MarginPadding { Left = 10 }, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - usernameText = new OsuSpriteText - { - Font = "Exo2.0-Regular", - TextSize = 24 - }, - openUserExternally = new ExternalLinkButton - { - Margin = new MarginPadding { Left = 5 }, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - } - }, - new FillFlowContainer - { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - titleText = new OsuSpriteText - { - TextSize = 18, - Font = "Exo2.0-Regular" - }, - SupporterTag = new SupporterIcon - { - Height = 20, - Margin = new MarginPadding { Top = 5 } - }, - userIconSeperatorBox = new Box - { - RelativeSizeAxes = Axes.X, - Height = 1.5f, - Margin = new MarginPadding { Top = 10 } - }, - new Container - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 5 }, - Children = new Drawable[] - { - userFlag = new DrawableFlag - { - Size = new Vector2(30, 20) - }, - userCountryText = new OsuSpriteText - { - Font = "Exo2.0-Regular", - TextSize = 17.5f, - Margin = new MarginPadding { Left = 40 }, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - } - } - }, - } - } - } - } - } - }, - userStats = new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Y, - Width = 300, - Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN }, - Padding = new MarginPadding { Vertical = 15 }, - Spacing = new Vector2(0, 2) - } - } }, - new Container + centerHeaderContainer = new CenterHeaderContainer { RelativeSizeAxes = Axes.X, Height = 60, - Children = new Drawable[] - { - headerCenterBox = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Padding = new MarginPadding { Vertical = 10 }, - Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, - Spacing = new Vector2(10, 0), - Children = new Drawable[] - { - new ProfileHeaderButton - { - RelativeSizeAxes = Axes.Y, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Direction = FillDirection.Horizontal, - Padding = new MarginPadding { Right = 10 }, - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.fa_user, - FillMode = FillMode.Fit, - Size = new Vector2(50, 14) - }, - followerText = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - TextSize = 16, - Font = "Exo2.0-Bold" - } - } - } - } - }, - messageButton = new ProfileHeaderButton - { - Alpha = 0, - RelativeSizeAxes = Axes.Y, - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.fa_envelope, - FillMode = FillMode.Fit, - Size = new Vector2(50, 14) - }, - } - }, - } - }, - new Container - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Y, - Padding = new MarginPadding { Vertical = 10 }, - Width = UserProfileOverlay.CONTENT_X_MARGIN, - Child = expandButton = new ProfileHeaderButton - { - RelativeSizeAxes = Axes.Y, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] - { - expandButtonIcon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(20), - Icon = FontAwesome.fa_chevron_up, - }, - } - }, - }, - new Container - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN }, - Children = new Drawable[] - { - new HasTooltipContainer - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Size = new Vector2(40), - TooltipText = "Level", - Children = new Drawable[] - { - levelBadgeSprite = new Sprite - { - RelativeSizeAxes = Axes.Both, - }, - levelBadgeText = new OsuSpriteText - { - TextSize = 20, - Font = "Exo2.0-Medium", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } - } - }, - expandedDetailContainer = new HasTooltipContainer - { - TooltipText = "Progress to next level", - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Width = 200, - Height = 6, - Margin = new MarginPadding { Right = 50 }, - Children = new Drawable[] - { - new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = levelProgressBar = new Bar - { - RelativeSizeAxes = Axes.Both, - BackgroundColour = Color4.Black, - Direction = BarDirection.LeftToRight, - } - }, - levelProgressText = new OsuSpriteText - { - Anchor = Anchor.BottomRight, - Origin = Anchor.TopRight, - Font = "Exo2.0-Bold", - TextSize = 12, - } - } - }, - hiddenDetailContainer = new FillFlowContainer - { - Direction = FillDirection.Horizontal, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Width = 200, - AutoSizeAxes = Axes.Y, - Alpha = 0, - Spacing = new Vector2(10, 0), - Margin = new MarginPadding { Right = 50 }, - Children = new[] - { - hiddenDetailGlobal = new OverlinedInfoContainer - { - Title = "Global Ranking" - }, - hiddenDetailCountry = new OverlinedInfoContainer - { - Title = "Country Ranking" - }, - } - } - } - } - } }, - new Container + detailHeaderContainer = new DetailHeaderContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - headerDetailBox = new Box - { - RelativeSizeAxes = Axes.Both, - }, - headerDetailContainer = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20), - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Children = new Drawable[] - { - totalPlayTimeTooltip = new HasTooltipContainer - { - AutoSizeAxes = Axes.Both, - TooltipText = "0 hours", - Child = totalPlayTimeInfo = new OverlinedInfoContainer - { - Title = "Total Play Time", - }, - }, - medalInfo = new OverlinedInfoContainer - { - Title = "Medals" - }, - ppInfo = new OverlinedInfoContainer - { - Title = "pp" - }, - } - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Direction = FillDirection.Horizontal, - Children = new[] - { - scoreRankInfos[ScoreRank.XH] = new ScoreRankInfo(ScoreRank.XH), - scoreRankInfos[ScoreRank.X] = new ScoreRankInfo(ScoreRank.X), - scoreRankInfos[ScoreRank.SH] = new ScoreRankInfo(ScoreRank.SH), - scoreRankInfos[ScoreRank.S] = new ScoreRankInfo(ScoreRank.S), - scoreRankInfos[ScoreRank.A] = new ScoreRankInfo(ScoreRank.A), - } - } - } - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Right = 130 }, - Children = new Drawable[] - { - rankGraph = new RankGraph - { - RelativeSizeAxes = Axes.Both, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - Width = 130, - Anchor = Anchor.TopRight, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Horizontal = 10 }, - Spacing = new Vector2(0, 20), - Children = new Drawable[] - { - detailGlobalRank = new OverlinedInfoContainer(true, 110) - { - Title = "Global Ranking" - }, - detailCountryRank = new OverlinedInfoContainer(false, 110) - { - Title = "Country Ranking" - }, - } - } - } - }, - } - }, - } }, - badgeContainer = new Container + medalHeaderContainer = new MedalHeaderContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Alpha = 0, - Children = new Drawable[] - { - headerBadgeBox = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new Container //artificial shadow - { - RelativeSizeAxes = Axes.X, - Height = 3, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = new ColourInfo - { - TopLeft = Color4.Black.Opacity(0.2f), - TopRight = Color4.Black.Opacity(0.2f), - BottomLeft = Color4.Black.Opacity(0), - BottomRight = Color4.Black.Opacity(0) - } - }, - }, - badgeFlowContainer = new FillFlowContainer - { - Direction = FillDirection.Full, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 5 }, - Spacing = new Vector2(10, 10), - Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, - } - } }, - new Container + bottomHeaderContainer = new BottomHeaderContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - headerBottomBox = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, - Spacing = new Vector2(0, 10), - Children = new Drawable[] - { - bottomTopLinkContainer = new LinkFlowContainer(text => text.TextSize = 12) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, - bottomLinkContainer = new LinkFlowContainer(text => text.TextSize = 12) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - } - } - } - } }, } } @@ -655,46 +144,16 @@ namespace osu.Game.Overlays.Profile infoTabControl.AddItem("Info"); infoTabControl.AddItem("Modding"); - DetailsVisible.ValueChanged += newValue => expandButtonIcon.Icon = newValue ? FontAwesome.fa_chevron_down : FontAwesome.fa_chevron_up; - DetailsVisible.ValueChanged += newValue => hiddenDetailContainer.Alpha = newValue ? 1 : 0; - DetailsVisible.ValueChanged += newValue => expandedDetailContainer.Alpha = newValue ? 0 : 1; - DetailsVisible.ValueChanged += newValue => headerDetailContainer.Alpha = newValue ? 0 : 1; + centerHeaderContainer.DetailsVisible.BindTo(DetailsVisible); + DetailsVisible.ValueChanged += newValue => detailHeaderContainer.Alpha = newValue ? 0 : 1; } - private Color4 communityUserGrayGreenLighter; - - [BackgroundDependencyLoader(true)] + [BackgroundDependencyLoader] private void load(OsuColour colours, TextureStore textures) { coverInfoText.Colour = colours.CommunityUserGreen; infoTabControl.AccentColour = colours.CommunityUserGreen; - - headerTopBox.Colour = colours.CommunityUserGrayGreenDarker; - userCountryText.Colour = colours.CommunityUserGrayGreenLighter; - userIconSeperatorBox.Colour = colours.CommunityUserGrayGreenLighter; - - headerCenterBox.Colour = colours.CommunityUserGrayGreenDark; - levelBadgeSprite.Texture = textures.Get("Profile/levelbadge"); - levelBadgeSprite.Colour = colours.Yellow; - levelProgressBar.AccentColour = colours.Yellow; - - hiddenDetailGlobal.LineColour = colours.Yellow; - hiddenDetailCountry.LineColour = colours.Yellow; - - headerDetailBox.Colour = colours.CommunityUserGrayGreenDarkest; - totalPlayTimeInfo.LineColour = colours.Yellow; - medalInfo.LineColour = colours.GreenLight; - ppInfo.LineColour = colours.Red; - - detailGlobalRank.LineColour = colours.Yellow; - detailCountryRank.LineColour = colours.Yellow; - - headerBadgeBox.Colour = colours.CommunityUserGrayGreenDarkest; - headerBottomBox.Colour = colours.CommunityUserGrayGreenDarker; - - communityUserGrayGreenLighter = colours.CommunityUserGrayGreenLighter; - linkBlue = colours.BlueLight; } private User user; @@ -704,13 +163,15 @@ namespace osu.Game.Overlays.Profile get => user; set { - user = value; - loadUser(); + medalHeaderContainer.User = detailHeaderContainer.User = bottomHeaderContainer.User = + centerHeaderContainer.User = topHeaderContainer.User = user = value; + updateDisplay(); } } - private void loadUser() + private void updateDisplay() { + coverContainer.RemoveAll(d => d is UserCoverBackground); LoadComponentAsync(new UserCoverBackground(user) { RelativeSizeAxes = Axes.Both, @@ -720,247 +181,14 @@ namespace osu.Game.Overlays.Profile OnLoadComplete = d => d.FadeInFromZero(200), Depth = float.MaxValue, }, coverContainer.Add); - - avatar.User = User; - usernameText.Text = user.Username; - openUserExternally.Link = $@"https://osu.ppy.sh/users/{user.Id}"; - userFlag.Country = user.Country; - userCountryText.Text = user.Country?.FullName; - SupporterTag.SupporterLevel = user.SupportLevel; - if(user.Title != null) - titleText.Text = user.Title; - titleText.Colour = OsuColour.FromHex(user.Colour ?? "fff"); - - userStats.Add(new UserStatsLine("Ranked Score", user.Statistics.RankedScore.ToString("#,##0"))); - userStats.Add(new UserStatsLine("Hit Accuracy", Math.Round(user.Statistics.Accuracy, 2).ToString("#0.00'%'"))); - userStats.Add(new UserStatsLine("Play Count", user.Statistics.PlayCount.ToString("#,##0"))); - userStats.Add(new UserStatsLine("Total Score", user.Statistics.TotalScore.ToString("#,##0"))); - userStats.Add(new UserStatsLine("Total Hits", user.Statistics.TotalHits.ToString("#,##0"))); - userStats.Add(new UserStatsLine("Maximum Combo", user.Statistics.MaxCombo.ToString("#,##0"))); - userStats.Add(new UserStatsLine("Replays Watched by Others", user.Statistics.ReplaysWatched.ToString("#,##0"))); - - followerText.Text = user.FollowerCount?.Length > 0 ? user.FollowerCount[0].ToString("#,##0") : "0"; - - if (!user.PMFriendsOnly && apiAccess.LocalUser.Value.Id != user.Id) - { - messageButton.Show(); - messageButton.Action = () => - { - channelManager?.OpenPrivateChannel(user); - userOverlay?.Hide(); - chatOverlay?.Show(); - }; - } - - expandButton.Action = DetailsVisible.Toggle; - - levelBadgeText.Text = user.Statistics.Level.Current.ToString(); - levelProgressBar.Length = user.Statistics.Level.Progress / 100f; - levelProgressText.Text = user.Statistics.Level.Progress.ToString("0'%'"); - - hiddenDetailGlobal.Content = user.Statistics.Ranks.Global?.ToString("#,##0") ?? "-"; - hiddenDetailCountry.Content = user.Statistics.Ranks.Country?.ToString("#,##0") ?? "-"; - - medalInfo.Content = user.Achievements.Length.ToString(); - ppInfo.Content = user.Statistics.PP?.ToString("#,##0") ?? "0"; - - string formatTime(int? secondsNull) - { - if (secondsNull == null) return "0h 0m"; - - int seconds = secondsNull.Value; - string time = ""; - - int days = seconds / 86400; - seconds -= days * 86400; - if (days > 0) - time += days + "d "; - - int hours = seconds / 3600; - seconds -= hours * 3600; - time += hours + "h "; - - int minutes = seconds / 60; - time += minutes + "m"; - - return time; - } - - totalPlayTimeInfo.Content = formatTime(user.Statistics.PlayTime); - totalPlayTimeTooltip.TooltipText = (user.Statistics.PlayTime ?? 0) / 3600 + " hours"; - - foreach (var scoreRankInfo in scoreRankInfos) - scoreRankInfo.Value.RankCount = user.Statistics.GradesCount.GetForScoreRank(scoreRankInfo.Key); - - detailGlobalRank.Content = user.Statistics.Ranks.Global?.ToString("#,##0") ?? "-"; - detailCountryRank.Content = user.Statistics.Ranks.Country?.ToString("#,##0") ?? "-"; - - rankGraph.User.Value = user; - - var badges = User.Badges; - if (badges.Length > 0) - { - badgeContainer.Show(); - for (var index = 0; index < badges.Length; index++) - { - int displayIndex = index; - LoadComponentAsync(new DrawableBadge(badges[index]), asyncBadge => - { - badgeFlowContainer.Add(asyncBadge); - - // load in stable order regardless of async load order. - badgeFlowContainer.SetLayoutPosition(asyncBadge, displayIndex); - }); - } - } - - void bold(SpriteText t) => t.Font = @"Exo2.0-Bold"; - void addSpacer(OsuTextFlowContainer textFlow) => textFlow.AddArbitraryDrawable(new Container { Width = 15 }); - - if (user.JoinDate.ToUniversalTime().Year < 2008) - { - bottomTopLinkContainer.AddText("Here since the beginning"); - } - else - { - bottomTopLinkContainer.AddText("Joined "); - bottomTopLinkContainer.AddText(new DrawableDate(user.JoinDate), bold); - } - - addSpacer(bottomTopLinkContainer); - - if (user.PlayStyles?.Length > 0) - { - bottomTopLinkContainer.AddText("Plays with "); - bottomTopLinkContainer.AddText(string.Join(", ", user.PlayStyles.Select(style => style.GetDescription())), bold); - - addSpacer(bottomTopLinkContainer); - } - - if (user.LastVisit.HasValue) - { - bottomTopLinkContainer.AddText("Last seen "); - bottomTopLinkContainer.AddText(new DrawableDate(user.LastVisit.Value), bold); - - addSpacer(bottomTopLinkContainer); - } - - bottomTopLinkContainer.AddText("Contributed "); - bottomTopLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: bold); - - void tryAddInfo(FontAwesome icon, string content, string link = null) - { - if (string.IsNullOrEmpty(content)) return; - - bottomLinkContainer.AddIcon(icon, text => - { - text.TextSize = 10; - text.Colour = communityUserGrayGreenLighter; - }); - if (link != null) - { - bottomLinkContainer.AddLink(" " + content, link, creationParameters: text => - { - bold(text); - text.Colour = linkBlue; - }); - } - else - { - bottomLinkContainer.AddText(" " + content, bold); - } - addSpacer(bottomLinkContainer); - } - - string websiteWithoutProtcol = user.Website; - if (!string.IsNullOrEmpty(websiteWithoutProtcol)) - { - int protocolIndex = websiteWithoutProtcol.IndexOf("//", StringComparison.Ordinal); - if (protocolIndex >= 0) - websiteWithoutProtcol = websiteWithoutProtcol.Substring(protocolIndex + 2); - } - - tryAddInfo(FontAwesome.fa_map_marker, user.Location); - tryAddInfo(FontAwesome.fa_heart_o, user.Interests); - tryAddInfo(FontAwesome.fa_suitcase, user.Occupation); - bottomLinkContainer.NewLine(); - if (!string.IsNullOrEmpty(user.Twitter)) - tryAddInfo(FontAwesome.fa_twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); - tryAddInfo(FontAwesome.fa_gamepad, user.Discord); //todo: update fontawesome to include discord logo - tryAddInfo(FontAwesome.fa_skype, user.Skype, @"skype:" + user.Skype + @"?chat"); - tryAddInfo(FontAwesome.fa_lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); - tryAddInfo(FontAwesome.fa_link, websiteWithoutProtcol, user.Website); } - private class UserStatsLine : Container - { - public UserStatsLine(string left, string right) - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Children = new Drawable[] - { - new OsuSpriteText - { - TextSize = 15, - Text = left, - Font = "Exo2.0-Medium" - }, - new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - TextSize = 15, - Text = right, - Font = "Exo2.0-Bold" - }, - }; - } - } - - private class ProfileHeaderButton : OsuHoverContainer - { - private readonly Box background; - private readonly Container content; - - protected override Container Content => content; - - protected override IEnumerable EffectTargets => new[] { background }; - - public ProfileHeaderButton() - { - HoverColour = Color4.Black.Opacity(0.75f); - IdleColour = Color4.Black.Opacity(0.7f); - AutoSizeAxes = Axes.X; - - base.Content.Add(new CircularContainer - { - Masking = true, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - }, - content = new Container - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 10 }, - } - } - }); - } - } - - private class HasTooltipContainer : Container, IHasTooltip + public class HasTooltipContainer : Container, IHasTooltip { public string TooltipText { get; set; } } - private class OverlinedInfoContainer : CompositeDrawable + public class OverlinedInfoContainer : CompositeDrawable { private readonly Circle line; private readonly OsuSpriteText title, content; @@ -1012,83 +240,5 @@ namespace osu.Game.Overlays.Profile }; } } - - public class ScoreRankInfo : CompositeDrawable - { - private readonly ScoreRank rank; - private readonly Sprite rankSprite; - private readonly OsuSpriteText rankCount; - - public int RankCount - { - set => rankCount.Text = value.ToString("#,##0"); - } - - public ScoreRankInfo(ScoreRank rank) - { - this.rank = rank; - - AutoSizeAxes = Axes.Both; - InternalChild = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - Width = 56, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - rankSprite = new Sprite - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit - }, - rankCount = new OsuSpriteText - { - Font = "Exo2.0-Bold", - TextSize = 12, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - } - } - }; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - rankSprite.Texture = textures.Get($"Grades/{rank.GetDescription()}"); - } - } - - private class DrawableBadge : CompositeDrawable, IHasTooltip - { - public static readonly Vector2 DRAWABLE_BADGE_SIZE = new Vector2(86, 40); - - private readonly Badge badge; - - public DrawableBadge(Badge badge) - { - this.badge = badge; - Size = DRAWABLE_BADGE_SIZE; - } - - [BackgroundDependencyLoader] - private void load(LargeTextureStore textures) - { - InternalChild = new Sprite - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Texture = textures.Get(badge.ImageUrl), - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - InternalChild.FadeInFromZero(200); - } - - public string TooltipText => badge.Description; - } } } From 14d2ed7085a82ab9ba5d6c91a2aefd6cec4b9471 Mon Sep 17 00:00:00 2001 From: jorolf Date: Tue, 22 Jan 2019 19:50:42 +0100 Subject: [PATCH 013/623] fix codefactor issue that I didn't notice and has been there for 12 days --- osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs index 9005fd5ab2..9be1eb4326 100644 --- a/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs @@ -278,7 +278,6 @@ namespace osu.Game.Overlays.Profile.Header hiddenDetailGlobal.Content = user?.Statistics?.Ranks.Global?.ToString("#,##0") ?? "-"; hiddenDetailCountry.Content = user?.Statistics?.Ranks.Country?.ToString("#,##0") ?? "-"; - } private class ProfileHeaderButton : OsuHoverContainer From 481f33d17be543c6cef45f5d9e4875cde2e94432 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 23 Jan 2019 14:27:34 +0100 Subject: [PATCH 014/623] add setting to toggle the gameplay cursor trail --- .../Configuration/OsuConfigManager.cs | 4 +++- osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs | 12 +++++++++++- osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs | 5 +++++ osu.Game/Configuration/GameConfigManager.cs | 5 ----- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuConfigManager.cs index 4fa49faf1d..492cc7cc7f 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuConfigManager.cs @@ -19,12 +19,14 @@ namespace osu.Game.Rulesets.Osu.Configuration Set(OsuSetting.SnakingInSliders, true); Set(OsuSetting.SnakingOutSliders, true); + Set(OsuSetting.ShowCursorTrail, true); } } public enum OsuSetting { SnakingInSliders, - SnakingOutSliders + SnakingOutSliders, + ShowCursorTrail } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs index 582b99af7c..cf6ae2ad3d 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -23,6 +24,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor protected override Container Content => fadeContainer; + private Bindable showTrail; + private readonly CursorTrail cursorTrail; private readonly Container fadeContainer; public GameplayCursor() @@ -32,11 +35,18 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new CursorTrail { Depth = 1 } + cursorTrail = new CursorTrail { Depth = 1 } } }; } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + showTrail = config.GetBindable(OsuSetting.ShowCursorTrail); + showTrail.ValueChanged += v => cursorTrail.Alpha = v ? 1 : 0; + } + private int downCount; private void updateExpandedState() diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs index 9aa0f4101d..12ef66fa25 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs @@ -34,6 +34,11 @@ namespace osu.Game.Rulesets.Osu.UI LabelText = "Snaking out sliders", Bindable = config.GetBindable(OsuSetting.SnakingOutSliders) }, + new SettingsCheckbox + { + LabelText = "Show cursor trail", + Bindable = config.GetBindable(OsuSetting.ShowCursorTrail) + }, }; } } diff --git a/osu.Game/Configuration/GameConfigManager.cs b/osu.Game/Configuration/GameConfigManager.cs index 736273dc35..b2f2a8e971 100644 --- a/osu.Game/Configuration/GameConfigManager.cs +++ b/osu.Game/Configuration/GameConfigManager.cs @@ -72,9 +72,6 @@ namespace osu.Game.Configuration Set(GameSetting.MenuParallax, true); - Set(GameSetting.SnakingInSliders, true); - Set(GameSetting.SnakingOutSliders, true); - // Gameplay Set(GameSetting.DimLevel, 0.3, 0, 1, 0.01); Set(GameSetting.BlurLevel, 0, 0, 1, 0.01); @@ -150,8 +147,6 @@ namespace osu.Game.Configuration DisplayStarsMinimum, DisplayStarsMaximum, RandomSelectAlgorithm, - SnakingInSliders, - SnakingOutSliders, ShowFpsDisplay, ChatDisplayHeight, Version, From 3e936d386d6975984942e0862b766b0d161b12ec Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 23 Jan 2019 14:54:03 +0100 Subject: [PATCH 015/623] add missing dependency --- osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs index 472da88e1f..b24abc3d0a 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Cursor; +using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Tests.Visual; @@ -20,6 +21,16 @@ namespace osu.Game.Rulesets.Osu.Tests public override IReadOnlyList RequiredTypes => new [] { typeof(CursorTrail) }; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + var configCache = dependencies.Get(); + dependencies.CacheAs((OsuConfigManager)configCache.GetConfigFor(new OsuRuleset())); + + return dependencies; + } + public CursorContainer Cursor => cursor; public bool ProvidingUserCursor => true; From 92edafc44a370ad4bdc4312c70190319a1aca3dc Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 23 Jan 2019 15:01:35 +0100 Subject: [PATCH 016/623] yeet whitespace --- osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs index b24abc3d0a..89b62116c5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Tests return dependencies; } - + public CursorContainer Cursor => cursor; public bool ProvidingUserCursor => true; From 3d5520d277bc3ccf9a750dc52b56783b3f1363d4 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 26 Jan 2019 07:08:55 +0100 Subject: [PATCH 017/623] correctly loop from preview point in SongSelect --- osu.Game/Screens/Select/SongSelect.cs | 5 +++-- osu.Game/osu.Game.csproj | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index c33e718540..10489c5193 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -584,8 +584,9 @@ namespace osu.Game.Screens.Select // Ensure the track is added to the TrackManager, since it is removed after the player finishes the map. // Using AddItemToList rather than AddItem so that it doesn't attempt to register adjustment dependencies more than once. Game.Audio.Track.AddItemToList(track); - if (preview) track.Seek(Beatmap.Value.Metadata.PreviewTime); - track.Start(); + if (preview) + track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; + track.Restart(); } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 669c7a6d8d..3ebe9c848a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,7 +18,7 @@ - + From 1fa87521f60a1eedec5eb4319a35ba2ff83739d9 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 26 Jan 2019 07:29:30 +0100 Subject: [PATCH 018/623] fix restarting with a different track in the same BeatmapSet --- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 10489c5193..41b4f29939 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -399,7 +399,6 @@ namespace osu.Game.Screens.Select { Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\""); - preview = beatmap?.BeatmapSetInfoID != Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID; Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value); if (beatmap != null) @@ -411,7 +410,8 @@ namespace osu.Game.Screens.Select } } - if (IsCurrentScreen) ensurePlayingSelected(preview); + if (IsCurrentScreen) + ensurePlayingSelected(true); UpdateBeatmap(Beatmap.Value); } From 07bb278ec0e85acfd534074241c0a0ede785296b Mon Sep 17 00:00:00 2001 From: Scotsoo Date: Sun, 27 Jan 2019 18:19:20 +0000 Subject: [PATCH 019/623] Considering GlobalActions in GameplatMenuOverlay --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 21 +++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 2c984e6135..f0eb3f49b5 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -39,6 +39,12 @@ namespace osu.Game.Screens.Play /// protected virtual Action BackAction => () => InternalButtons.Children.Last().Click(); + /// + /// Action that is invoked when is triggered. + /// + protected Action SelectAction => () => InternalButtons.Children.FirstOrDefault(f => f.Selected)?.Click(); + + public abstract string Header { get; } public abstract string Description { get; } @@ -235,6 +241,12 @@ namespace osu.Game.Screens.Play return true; } + if (action == GlobalAction.Select) + { + SelectAction.Invoke(); + return true; + } + return false; } @@ -289,15 +301,6 @@ namespace osu.Game.Screens.Play Selected.Value = true; return base.OnMouseMove(e); } - - protected override bool OnKeyDown(KeyDownEvent e) - { - if (e.Repeat || e.Key != Key.Enter || !Selected) - return false; - - Click(); - return true; - } } } } From 29c02fbaa7ad4e399d522072c1bd048d5b89891b Mon Sep 17 00:00:00 2001 From: Scotsoo Date: Sun, 27 Jan 2019 18:27:11 +0000 Subject: [PATCH 020/623] Remove redundant space --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index f0eb3f49b5..19afdb42e9 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -44,7 +44,6 @@ namespace osu.Game.Screens.Play /// protected Action SelectAction => () => InternalButtons.Children.FirstOrDefault(f => f.Selected)?.Click(); - public abstract string Header { get; } public abstract string Description { get; } From c00636d328d99452d5012ee61e221972f4b36da6 Mon Sep 17 00:00:00 2001 From: Scotsoo Date: Sun, 27 Jan 2019 19:09:30 +0000 Subject: [PATCH 021/623] Removing whitespace --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 19afdb42e9..e9c58ce3a2 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -43,7 +43,6 @@ namespace osu.Game.Screens.Play /// Action that is invoked when is triggered. /// protected Action SelectAction => () => InternalButtons.Children.FirstOrDefault(f => f.Selected)?.Click(); - public abstract string Header { get; } public abstract string Description { get; } From 648ed0e4677356a2b07721696ee3eece95fbe8af Mon Sep 17 00:00:00 2001 From: jorolf Date: Sun, 27 Jan 2019 23:45:00 +0100 Subject: [PATCH 022/623] update license headers --- osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs | 4 ++-- osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs | 4 ++-- osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs | 4 ++-- osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs | 4 ++-- osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs | 4 ++-- osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs | 4 ++-- osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs b/osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs index c80227f149..6f164890f9 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index a3a6447a9f..df0409272f 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; diff --git a/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs index 9be1eb4326..30671487d3 100644 --- a/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Framework.Allocation; diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index 678fc2ddb7..124299b0ba 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Framework.Allocation; diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs index 74f1e2f689..5a54270b80 100644 --- a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; diff --git a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs index db8a0b594c..fd7124f20f 100644 --- a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs +++ b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . 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.Shapes; diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index b003f08b95..4186d08729 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; From e2347ae7bf9e8511a2e60559693a6d6423201bfe Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 31 Jan 2019 10:52:52 +0100 Subject: [PATCH 023/623] Merge branch 'master' into correct-preview-loop --- osu-resources | 2 +- osu.Desktop/OsuGameDesktop.cs | 6 +- .../AppDelegate.cs | 15 + .../Application.cs | 15 + .../Entitlements.plist | 6 + osu.Game.Rulesets.Catch.Tests.iOS/Info.plist | 36 + .../osu.Game.Rulesets.Catch.Tests.iOS.csproj | 49 ++ .../Properties/AssemblyInfo.cs | 1 + .../AppDelegate.cs | 15 + .../Application.cs | 15 + .../Entitlements.plist | 6 + osu.Game.Rulesets.Mania.Tests.iOS/Info.plist | 36 + .../osu.Game.Rulesets.Mania.Tests.iOS.csproj | 49 ++ .../Properties/AssemblyInfo.cs | 1 + .../AppDelegate.cs | 15 + .../Application.cs | 15 + .../Entitlements.plist | 6 + osu.Game.Rulesets.Osu.Tests.iOS/Info.plist | 36 + .../osu.Game.Rulesets.Osu.Tests.iOS.csproj | 49 ++ .../Difficulty/OsuPerformanceCalculator.cs | 7 +- .../Preprocessing/OsuDifficultyBeatmap.cs | 8 +- .../Preprocessing/OsuDifficultyHitObject.cs | 46 +- .../Difficulty/Skills/Aim.cs | 31 +- .../Difficulty/Skills/Skill.cs | 3 + .../Difficulty/Skills/Speed.cs | 44 +- .../HitCircles/HitCirclePlacementBlueprint.cs | 5 +- .../Sliders/SliderPlacementBlueprint.cs | 8 + .../Mods/OsuModTouchDevice.cs | 16 + osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 + .../Properties/AssemblyInfo.cs | 1 + .../AppDelegate.cs | 15 + .../Application.cs | 15 + .../Entitlements.plist | 6 + osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist | 36 + .../osu.Game.Rulesets.Taiko.Tests.iOS.csproj | 49 ++ .../Properties/AssemblyInfo.cs | 1 + osu.Game.Tests.iOS/AppDelegate.cs | 14 + osu.Game.Tests.iOS/Application.cs | 15 + osu.Game.Tests.iOS/Entitlements.plist | 6 + osu.Game.Tests.iOS/Info.plist | 36 + osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj | 64 ++ .../Visual/TestCaseLoaderAnimation.cs | 7 +- osu.Game.Tests/Visual/TestCaseMultiHeader.cs | 9 +- osu.Game.Tests/Visual/TestCaseMultiScreen.cs | 1 + .../Visual/TestCasePlaySongSelect.cs | 5 +- osu.Game.Tests/Visual/TestCasePlayerLoader.cs | 5 +- .../Visual/TestCaseScreenBreadcrumbControl.cs | 35 +- osu.Game/Beatmaps/BeatmapManager.cs | 8 +- osu.Game/Database/ArchiveModelManager.cs | 46 +- .../Graphics/Cursor/MenuCursorContainer.cs | 1 + .../Graphics/UserInterface/FocusedTextBox.cs | 19 +- .../UserInterface/ScreenBreadcrumbControl.cs | 41 +- osu.Game/Online/API/APIDownloadRequest.cs | 9 +- osu.Game/OsuGame.cs | 135 +-- .../AccountCreation/AccountCreationScreen.cs | 12 +- .../Overlays/AccountCreation/ScreenEntry.cs | 4 +- .../Overlays/AccountCreation/ScreenWarning.cs | 10 +- .../Overlays/AccountCreation/ScreenWelcome.cs | 5 +- osu.Game/Overlays/AccountCreationOverlay.cs | 3 +- .../Chat/Selection/ChannelSelectionOverlay.cs | 2 +- osu.Game/Overlays/ChatOverlay.cs | 2 +- osu.Game/Overlays/Music/FilterControl.cs | 12 +- osu.Game/Overlays/Music/PlaylistOverlay.cs | 2 +- .../SearchableListFilterControl.cs | 13 +- .../SearchableList/SearchableListOverlay.cs | 2 +- osu.Game/Overlays/SettingsOverlay.cs | 2 +- osu.Game/Properties/AssemblyInfo.cs | 1 + osu.Game/Screens/BackgroundScreen.cs | 57 +- osu.Game/Screens/BackgroundScreenStack.cs | 32 + .../Backgrounds/BackgroundScreenBeatmap.cs | 2 +- .../Backgrounds/BackgroundScreenBlack.cs | 4 +- .../Backgrounds/BackgroundScreenCustom.cs | 2 +- .../Backgrounds/BackgroundScreenDefault.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 12 +- osu.Game/Screens/IOsuScreen.cs | 39 + osu.Game/Screens/Loader.cs | 10 +- osu.Game/Screens/Menu/Disclaimer.cs | 12 +- osu.Game/Screens/Menu/Intro.cs | 19 +- osu.Game/Screens/Menu/MainMenu.cs | 55 +- osu.Game/Screens/Multi/Header.cs | 8 +- .../Screens/Multi/IMultiplayerSubScreen.cs | 4 +- .../Screens/Multi/Lounge/LoungeSubScreen.cs | 37 +- .../Screens/Multi/Match/MatchSubScreen.cs | 40 +- osu.Game/Screens/Multi/Multiplayer.cs | 133 +-- .../Screens/Multi/MultiplayerSubScreen.cs | 90 +- .../Screens/Multi/Play/TimeshiftPlayer.cs | 22 +- osu.Game/Screens/OsuScreen.cs | 168 ++-- osu.Game/Screens/Play/Player.cs | 36 +- osu.Game/Screens/Play/PlayerLoader.cs | 75 +- .../Play/ScreenWithBeatmapBackground.cs | 8 +- osu.Game/Screens/Ranking/Results.cs | 10 +- osu.Game/Screens/ScreenWhiteBox.cs | 26 +- osu.Game/Screens/Select/EditSongSelect.cs | 4 +- osu.Game/Screens/Select/MatchSongSelect.cs | 11 +- osu.Game/Screens/Select/PlaySongSelect.cs | 4 +- osu.Game/Screens/Select/SongSelect.cs | 42 +- osu.Game/Screens/Tournament/Drawings.cs | 7 +- osu.Game/Tests/Visual/ScreenTestCase.cs | 41 +- osu.Game/Tests/Visual/TestCasePlayer.cs | 1 + osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 115 +++ osu.iOS.sln | 189 ++++ osu.iOS.sln.DotSettings | 815 ++++++++++++++++++ osu.iOS/AppDelegate.cs | 15 + osu.iOS/Application.cs | 16 + .../AppIcon.appiconset/Contents.json | 249 ++++++ .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 1237 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 3602 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 6457 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 2250 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 6069 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 10842 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 3602 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 9677 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 16681 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 16681 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 28381 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 8946 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 22553 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 25862 bytes .../AppIcon.appiconset/ItunesArtwork@2x.png | Bin 0 -> 251219 bytes osu.iOS/Assets.xcassets/Contents.json | 6 + osu.iOS/Entitlements.plist | 6 + osu.iOS/Info.plist | 42 + osu.iOS/LaunchScreen.storyboard | 27 + osu.iOS/Linker.xml | 27 + osu.iOS/iTunesArtwork | Bin 0 -> 99849 bytes osu.iOS/iTunesArtwork@2x | Bin 0 -> 251219 bytes osu.iOS/libbass.a | Bin 0 -> 1717480 bytes osu.iOS/libbass_fx.a | Bin 0 -> 621624 bytes osu.iOS/osu.iOS.csproj | 87 ++ osu.sln | 89 +- 132 files changed, 3159 insertions(+), 732 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs create mode 100644 osu.Game.Rulesets.Catch.Tests.iOS/Application.cs create mode 100644 osu.Game.Rulesets.Catch.Tests.iOS/Entitlements.plist create mode 100644 osu.Game.Rulesets.Catch.Tests.iOS/Info.plist create mode 100644 osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj create mode 100644 osu.Game.Rulesets.Mania.Tests.iOS/AppDelegate.cs create mode 100644 osu.Game.Rulesets.Mania.Tests.iOS/Application.cs create mode 100644 osu.Game.Rulesets.Mania.Tests.iOS/Entitlements.plist create mode 100644 osu.Game.Rulesets.Mania.Tests.iOS/Info.plist create mode 100644 osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj create mode 100644 osu.Game.Rulesets.Osu.Tests.iOS/AppDelegate.cs create mode 100644 osu.Game.Rulesets.Osu.Tests.iOS/Application.cs create mode 100644 osu.Game.Rulesets.Osu.Tests.iOS/Entitlements.plist create mode 100644 osu.Game.Rulesets.Osu.Tests.iOS/Info.plist create mode 100644 osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs create mode 100644 osu.Game.Rulesets.Taiko.Tests.iOS/AppDelegate.cs create mode 100644 osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs create mode 100644 osu.Game.Rulesets.Taiko.Tests.iOS/Entitlements.plist create mode 100644 osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist create mode 100644 osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj create mode 100644 osu.Game.Tests.iOS/AppDelegate.cs create mode 100644 osu.Game.Tests.iOS/Application.cs create mode 100644 osu.Game.Tests.iOS/Entitlements.plist create mode 100644 osu.Game.Tests.iOS/Info.plist create mode 100644 osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj create mode 100644 osu.Game/Screens/BackgroundScreenStack.cs create mode 100644 osu.Game/Screens/IOsuScreen.cs create mode 100644 osu.iOS.props create mode 100644 osu.iOS.sln create mode 100644 osu.iOS.sln.DotSettings create mode 100644 osu.iOS/AppDelegate.cs create mode 100644 osu.iOS/Application.cs create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 osu.iOS/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png create mode 100644 osu.iOS/Assets.xcassets/Contents.json create mode 100644 osu.iOS/Entitlements.plist create mode 100644 osu.iOS/Info.plist create mode 100644 osu.iOS/LaunchScreen.storyboard create mode 100644 osu.iOS/Linker.xml create mode 100644 osu.iOS/iTunesArtwork create mode 100644 osu.iOS/iTunesArtwork@2x create mode 100644 osu.iOS/libbass.a create mode 100644 osu.iOS/libbass_fx.a create mode 100644 osu.iOS/osu.iOS.csproj diff --git a/osu-resources b/osu-resources index 9880089b4e..677897728f 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit 9880089b4e8fcd78d68f30c8a40d43bf8dccca86 +Subproject commit 677897728f4332fa1200e0280ca02c4b987c6c47 diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index c4c0cc1fdd..ef8ec0c105 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -16,7 +16,6 @@ using osu.Desktop.Updater; using osu.Framework; using osu.Framework.Platform.Windows; using osu.Framework.Screens; -using osu.Game.Screens; using osu.Game.Screens.Menu; namespace osu.Desktop @@ -63,9 +62,10 @@ namespace osu.Desktop } } - protected override void ScreenChanged(OsuScreen current, Screen newScreen) + protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen) { - base.ScreenChanged(current, newScreen); + base.ScreenChanged(lastScreen, newScreen); + switch (newScreen) { case Intro _: diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs b/osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs new file mode 100644 index 0000000000..39fe3dac25 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Foundation; +using osu.Framework.iOS; +using osu.Game.Tests; + +namespace osu.Game.Rulesets.Catch.Tests.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameAppDelegate + { + protected override Framework.Game CreateGame() => new OsuTestBrowser(); + } +} diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs new file mode 100644 index 0000000000..44817c1304 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using UIKit; + +namespace osu.Game.Rulesets.Catch.Tests.iOS +{ + public class Application + { + public static void Main(string[] args) + { + UIApplication.Main(args, null, "AppDelegate"); + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Entitlements.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Entitlements.plist new file mode 100644 index 0000000000..9ae599370b --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist new file mode 100644 index 0000000000..5115746cbb --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleName + osu.Game.Rulesets.Catch.Tests.iOS + CFBundleIdentifier + ppy.osu-Game-Rulesets-Catch-Tests-iOS + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 10.0 + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + + diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj new file mode 100644 index 0000000000..da053f7598 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj @@ -0,0 +1,49 @@ + + + + + Debug + iPhoneSimulator + {4004C7B7-1A62-43F1-9DF2-52450FA67E70} + Exe + osu.Game.Rulesets.Catch.Tests + osu.Game.Rulesets.Catch.Tests.iOS + + + + + + + libbass.a + PreserveNewest + + + libbass_fx.a + PreserveNewest + + + Linker.xml + + + + + %(RecursiveDir)%(Filename)%(Extension) + + + + + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} + osu.Game + + + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3} + osu.Game.Rulesets.Catch + + + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58} + osu.Game.Resources + + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs index d4b6baf9b4..dba76eef49 100644 --- a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs @@ -9,3 +9,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests")] [assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.Dynamic")] +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.iOS")] diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/AppDelegate.cs b/osu.Game.Rulesets.Mania.Tests.iOS/AppDelegate.cs new file mode 100644 index 0000000000..9cd1e47023 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests.iOS/AppDelegate.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Foundation; +using osu.Framework.iOS; +using osu.Game.Tests; + +namespace osu.Game.Rulesets.Mania.Tests.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameAppDelegate + { + protected override Framework.Game CreateGame() => new OsuTestBrowser(); + } +} diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs new file mode 100644 index 0000000000..d47ac4643f --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using UIKit; + +namespace osu.Game.Rulesets.Mania.Tests.iOS +{ + public class Application + { + public static void Main(string[] args) + { + UIApplication.Main(args, null, "AppDelegate"); + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Entitlements.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Entitlements.plist new file mode 100644 index 0000000000..9ae599370b --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist new file mode 100644 index 0000000000..8780204d5b --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleName + osu.Game.Rulesets.Mania.Tests.iOS + CFBundleIdentifier + ppy.osu-Game-Rulesets-Mania-Tests-iOS + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 10.0 + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + + diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj new file mode 100644 index 0000000000..45ed2091a7 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj @@ -0,0 +1,49 @@ + + + + + Debug + iPhoneSimulator + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C} + Exe + osu.Game.Rulesets.Mania.Tests + osu.Game.Rulesets.Mania.Tests.iOS + + + + + + + libbass.a + PreserveNewest + + + libbass_fx.a + PreserveNewest + + + Linker.xml + + + + + %(RecursiveDir)%(Filename)%(Extension) + + + + + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} + osu.Game + + + {48F4582B-7687-4621-9CBE-5C24197CB536} + osu.Game.Rulesets.Mania + + + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58} + osu.Game.Resources + + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs index 21e6cf322b..f3ea6c7b71 100644 --- a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs @@ -9,3 +9,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests")] [assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests.Dynamic")] +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests.iOS")] diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/AppDelegate.cs b/osu.Game.Rulesets.Osu.Tests.iOS/AppDelegate.cs new file mode 100644 index 0000000000..01e635f09c --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests.iOS/AppDelegate.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Foundation; +using osu.Framework.iOS; +using osu.Game.Tests; + +namespace osu.Game.Rulesets.Osu.Tests.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameAppDelegate + { + protected override Framework.Game CreateGame() => new OsuTestBrowser(); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs new file mode 100644 index 0000000000..7a0797a909 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using UIKit; + +namespace osu.Game.Rulesets.Osu.Tests.iOS +{ + public class Application + { + public static void Main(string[] args) + { + UIApplication.Main(args, null, "AppDelegate"); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Entitlements.plist b/osu.Game.Rulesets.Osu.Tests.iOS/Entitlements.plist new file mode 100644 index 0000000000..9ae599370b --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist new file mode 100644 index 0000000000..f79215cf54 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleName + osu.Game.Rulesets.Osu.Tests.iOS + CFBundleIdentifier + ppy.osu-Game-Rulesets-Osu-Tests-iOS + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 10.0 + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + + diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj new file mode 100644 index 0000000000..349e46e02d --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj @@ -0,0 +1,49 @@ + + + + + Debug + iPhoneSimulator + {6653CA6F-DB06-4604-A3FD-762E25C2AF96} + Exe + osu.Game.Rulesets.Osu.Tests + osu.Game.Rulesets.Osu.Tests.iOS + + + + + + + libbass.a + PreserveNewest + + + libbass_fx.a + PreserveNewest + + + Linker.xml + + + + + %(RecursiveDir)%(Filename)%(Extension) + + + + + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} + osu.Game + + + {C92A607B-1FDD-4954-9F92-03FF547D9080} + osu.Game.Rulesets.Osu + + + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58} + osu.Game.Resources + + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 83deac78a1..13a21c5c55 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -88,7 +88,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double computeAimValue() { - double aimValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes.AimStrain / 0.0675f) - 4.0f, 3.0f) / 100000.0f; + double rawAim = Attributes.AimStrain; + + if (mods.Any(m => m is OsuModTouchDevice)) + rawAim = Math.Pow(rawAim, 0.8); + + double aimValue = Math.Pow(5.0f * Math.Max(1.0f, rawAim / 0.0675f) - 4.0f, 3.0f) / 100000.0f; // Longer maps are worth more double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) + diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs index 1736912e1f..068564d50c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs @@ -38,7 +38,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // The first jump is formed by the first two hitobjects of the map. // If the map has less than two OsuHitObjects, the enumerator will not return anything. for (int i = 1; i < objects.Count; i++) - yield return new OsuDifficultyHitObject(objects[i], objects[i - 1], timeRate); + { + var lastLast = i > 1 ? objects[i - 2] : null; + var last = objects[i - 1]; + var current = objects[i]; + + yield return new OsuDifficultyHitObject(lastLast, last, current, timeRate); + } } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 91d6c3d7a3..4e9ac26dc5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -40,14 +40,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing /// public double StrainTime { get; private set; } + /// + /// Angle the player has to take to hit this . + /// Calculated as the angle between the circles (current-2, current-1, current). + /// + public double? Angle { get; private set; } + + private readonly OsuHitObject lastLastObject; private readonly OsuHitObject lastObject; private readonly double timeRate; /// /// Initializes the object calculating extra data required for difficulty calculation. /// - public OsuDifficultyHitObject(OsuHitObject currentObject, OsuHitObject lastObject, double timeRate) + public OsuDifficultyHitObject(OsuHitObject lastLastObject, OsuHitObject lastObject, OsuHitObject currentObject, double timeRate) { + this.lastLastObject = lastLastObject; this.lastObject = lastObject; this.timeRate = timeRate; @@ -68,20 +76,30 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing scalingFactor *= 1 + smallCircleBonus; } - Vector2 lastCursorPosition = lastObject.StackedPosition; - - var lastSlider = lastObject as Slider; - if (lastSlider != null) + if (lastObject is Slider lastSlider) { computeSliderCursorPosition(lastSlider); - lastCursorPosition = lastSlider.LazyEndPosition ?? lastCursorPosition; - TravelDistance = lastSlider.LazyTravelDistance * scalingFactor; } + Vector2 lastCursorPosition = getEndCursorPosition(lastObject); + // Don't need to jump to reach spinners if (!(BaseObject is Spinner)) JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; + + if (lastLastObject != null) + { + Vector2 lastLastCursorPosition = getEndCursorPosition(lastLastObject); + + Vector2 v1 = lastLastCursorPosition - lastObject.StackedPosition; + Vector2 v2 = BaseObject.StackedPosition - lastCursorPosition; + + float dot = Vector2.Dot(v1, v2); + float det = v1.X * v2.Y - v1.Y * v2.X; + + Angle = Math.Abs(Math.Atan2(det, dot)); + } } private void setTimingValues() @@ -127,5 +145,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing computeVertex(time); computeVertex(slider.EndTime); } + + private Vector2 getEndCursorPosition(OsuHitObject hitObject) + { + Vector2 pos = hitObject.StackedPosition; + + var slider = hitObject as Slider; + if (slider != null) + { + computeSliderCursorPosition(slider); + pos = slider.LazyEndPosition ?? pos; + } + + return pos; + } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 9e5f13de62..b5e57985e9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -11,10 +11,39 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Aim : Skill { + private const double angle_bonus_begin = Math.PI / 3; + private const double timing_threshold = 107; + protected override double SkillMultiplier => 26.25; protected override double StrainDecayBase => 0.15; protected override double StrainValueOf(OsuDifficultyHitObject current) - => (Math.Pow(current.TravelDistance, 0.99) + Math.Pow(current.JumpDistance, 0.99)) / current.StrainTime; + { + double result = 0; + + const double scale = 90; + + double applyDiminishingExp(double val) => Math.Pow(val, 0.99); + + if (Previous.Count > 0) + { + if (current.Angle != null && current.Angle.Value > angle_bonus_begin) + { + var angleBonus = Math.Sqrt( + Math.Max(Previous[0].JumpDistance - scale, 0) + * Math.Pow(Math.Sin(current.Angle.Value - angle_bonus_begin), 2) + * Math.Max(current.JumpDistance - scale, 0)); + result = 1.5 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, Previous[0].StrainTime); + } + } + + double jumpDistanceExp = applyDiminishingExp(current.JumpDistance); + double travelDistanceExp = applyDiminishingExp(current.TravelDistance); + + return Math.Max( + result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(current.StrainTime, timing_threshold), + (Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / current.StrainTime + ); + } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs index 436d3d0853..2f23552eb9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs @@ -15,6 +15,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public abstract class Skill { + protected const double SINGLE_SPACING_THRESHOLD = 125; + protected const double STREAM_SPACING_THRESHOLD = 110; + /// /// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other. /// diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 809632806b..e78691ce53 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; namespace osu.Game.Rulesets.Osu.Difficulty.Skills @@ -10,30 +11,41 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Speed : Skill { + private const double angle_bonus_begin = 5 * Math.PI / 6; + private const double pi_over_4 = Math.PI / 4; + private const double pi_over_2 = Math.PI / 2; + protected override double SkillMultiplier => 1400; protected override double StrainDecayBase => 0.3; - private const double single_spacing_threshold = 125; - private const double stream_spacing_threshold = 110; - private const double almost_diameter = 90; + private const double min_speed_bonus = 75; // ~200BPM + private const double max_speed_bonus = 45; // ~330BPM + private const double speed_balancing_factor = 40; protected override double StrainValueOf(OsuDifficultyHitObject current) { - double distance = current.TravelDistance + current.JumpDistance; + double distance = Math.Min(SINGLE_SPACING_THRESHOLD, current.TravelDistance + current.JumpDistance); + double deltaTime = Math.Max(max_speed_bonus, current.DeltaTime); - double speedValue; - if (distance > single_spacing_threshold) - speedValue = 2.5; - else if (distance > stream_spacing_threshold) - speedValue = 1.6 + 0.9 * (distance - stream_spacing_threshold) / (single_spacing_threshold - stream_spacing_threshold); - else if (distance > almost_diameter) - speedValue = 1.2 + 0.4 * (distance - almost_diameter) / (stream_spacing_threshold - almost_diameter); - else if (distance > almost_diameter / 2) - speedValue = 0.95 + 0.25 * (distance - almost_diameter / 2) / (almost_diameter / 2); - else - speedValue = 0.95; + double speedBonus = 1.0; + if (deltaTime < min_speed_bonus) + speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2); - return speedValue / current.StrainTime; + double angleBonus = 1.0; + if (current.Angle != null && current.Angle.Value < angle_bonus_begin) + { + angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - current.Angle.Value)), 2) / 3.57; + if (current.Angle.Value < pi_over_2) + { + angleBonus = 1.28; + if (distance < 90 && current.Angle.Value < pi_over_4) + angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1); + else if (distance < 90) + angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1) * Math.Sin((pi_over_2 - current.Angle.Value) / pi_over_4); + } + } + + return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / SINGLE_SPACING_THRESHOLD, 3.5)) / current.StrainTime; } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs index 0dcf5ba551..a4050f0c31 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -5,6 +5,7 @@ using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; +using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles { @@ -22,8 +23,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles { base.LoadComplete(); - // Fixes a 1-frame position discrpancy due to the first mouse move event happening in the next frame - HitObject.Position = GetContainingInputManager().CurrentState.Mouse.Position; + // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame + HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero; } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index f563ad2264..989a53db1f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -47,6 +47,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders setState(PlacementState.Initial); } + protected override void LoadComplete() + { + base.LoadComplete(); + + // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame + HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero; + } + protected override bool OnMouseMove(MouseMoveEvent e) { switch (state) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs new file mode 100644 index 0000000000..571756d056 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModTouchDevice : Mod + { + public override string Name => "Touch Device"; + public override string Acronym => "TD"; + public override double ScoreMultiplier => 1; + + public override bool Ranked => true; + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 9415adc411..12d0a28a8f 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -82,6 +82,9 @@ namespace osu.Game.Rulesets.Osu if (mods.HasFlag(LegacyMods.Target)) yield return new OsuModTarget(); + + if (mods.HasFlag(LegacyMods.TouchDevice)) + yield return new OsuModTouchDevice(); } public override IEnumerable GetModsFor(ModType type) diff --git a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs index f37e8cb946..b9a7096330 100644 --- a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs @@ -9,3 +9,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests")] [assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests.Dynamic")] +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests.iOS")] diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/AppDelegate.cs b/osu.Game.Rulesets.Taiko.Tests.iOS/AppDelegate.cs new file mode 100644 index 0000000000..567220f316 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/AppDelegate.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Foundation; +using osu.Framework.iOS; +using osu.Game.Tests; + +namespace osu.Game.Rulesets.Taiko.Tests.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameAppDelegate + { + protected override Framework.Game CreateGame() => new OsuTestBrowser(); + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs new file mode 100644 index 0000000000..6613e9e2b4 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using UIKit; + +namespace osu.Game.Rulesets.Taiko.Tests.iOS +{ + public class Application + { + public static void Main(string[] args) + { + UIApplication.Main(args, null, "AppDelegate"); + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Entitlements.plist b/osu.Game.Rulesets.Taiko.Tests.iOS/Entitlements.plist new file mode 100644 index 0000000000..9ae599370b --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist new file mode 100644 index 0000000000..5fe822946a --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleName + osu.Game.Rulesets.Taiko.Tests.iOS + CFBundleIdentifier + ppy.osu-Game-Rulesets-Taiko-Tests-iOS + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 10.0 + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + + diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj new file mode 100644 index 0000000000..2ab0633786 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj @@ -0,0 +1,49 @@ + + + + + Debug + iPhoneSimulator + {7E408809-66AC-49D1-AF4D-98834F9B979A} + Exe + osu.Game.Rulesets.Taiko.Tests + osu.Game.Rulesets.Taiko.Tests.iOS + + + + + + + libbass.a + PreserveNewest + + + libbass_fx.a + PreserveNewest + + + Linker.xml + + + + + %(RecursiveDir)%(Filename)%(Extension) + + + + + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} + osu.Game + + + {F167E17A-7DE6-4AF5-B920-A5112296C695} + osu.Game.Rulesets.Taiko + + + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58} + osu.Game.Resources + + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs index ca6da65107..81f15fb293 100644 --- a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs @@ -9,3 +9,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests")] [assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests.Dynamic")] +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests.iOS")] diff --git a/osu.Game.Tests.iOS/AppDelegate.cs b/osu.Game.Tests.iOS/AppDelegate.cs new file mode 100644 index 0000000000..1e703e0c0a --- /dev/null +++ b/osu.Game.Tests.iOS/AppDelegate.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Foundation; +using osu.Framework.iOS; + +namespace osu.Game.Tests.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameAppDelegate + { + protected override Framework.Game CreateGame() => new OsuTestBrowser(); + } +} diff --git a/osu.Game.Tests.iOS/Application.cs b/osu.Game.Tests.iOS/Application.cs new file mode 100644 index 0000000000..a23fe4e129 --- /dev/null +++ b/osu.Game.Tests.iOS/Application.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using UIKit; + +namespace osu.Game.Tests.iOS +{ + public class Application + { + public static void Main(string[] args) + { + UIApplication.Main(args, null, "AppDelegate"); + } + } +} diff --git a/osu.Game.Tests.iOS/Entitlements.plist b/osu.Game.Tests.iOS/Entitlements.plist new file mode 100644 index 0000000000..9ae599370b --- /dev/null +++ b/osu.Game.Tests.iOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/osu.Game.Tests.iOS/Info.plist b/osu.Game.Tests.iOS/Info.plist new file mode 100644 index 0000000000..98a4223116 --- /dev/null +++ b/osu.Game.Tests.iOS/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleName + osu.Game.Tests.iOS + CFBundleIdentifier + ppy.osu-Game-Tests-iOS + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 10.0 + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + + diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj new file mode 100644 index 0000000000..636bedac13 --- /dev/null +++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj @@ -0,0 +1,64 @@ + + + + + Debug + iPhoneSimulator + Exe + {65FF8E19-6934-469B-B690-23C6D6E56A17} + osu.Game.Tests + osu.Game.Tests.iOS + + + + + + + libbass.a + PreserveNewest + + + libbass_fx.a + PreserveNewest + + + Linker.xml + + + + + %(RecursiveDir)%(Filename)%(Extension) + + + + + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} + osu.Game + + + {C92A607B-1FDD-4954-9F92-03FF547D9080} + osu.Game.Rulesets.Osu + + + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3} + osu.Game.Rulesets.Catch + + + {48F4582B-7687-4621-9CBE-5C24197CB536} + osu.Game.Rulesets.Mania + + + {F167E17A-7DE6-4AF5-B920-A5112296C695} + osu.Game.Rulesets.Taiko + + + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58} + osu.Game.Resources + + + + + + + + \ No newline at end of file diff --git a/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs b/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs index 70ed075101..2088f97580 100644 --- a/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs +++ b/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; using osu.Game.Screens; using osu.Game.Screens.Menu; using osuTK.Graphics; @@ -57,7 +58,7 @@ namespace osu.Game.Tests.Visual public OsuLogo Logo; private TestScreen screen; - public bool ScreenLoaded => screen.IsCurrentScreen; + public bool ScreenLoaded => screen.IsCurrentScreen(); public TestLoader(double delay) { @@ -96,7 +97,7 @@ namespace osu.Game.Tests.Visual { public TestScreen() { - Child = new Box + InternalChild = new Box { RelativeSizeAxes = Axes.Both, Colour = Color4.DarkSlateGray, @@ -107,7 +108,7 @@ namespace osu.Game.Tests.Visual protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); - Child.FadeInFromZero(200); + InternalChild.FadeInFromZero(200); } } } diff --git a/osu.Game.Tests/Visual/TestCaseMultiHeader.cs b/osu.Game.Tests/Visual/TestCaseMultiHeader.cs index 998cdcbad1..f7802e2d08 100644 --- a/osu.Game.Tests/Visual/TestCaseMultiHeader.cs +++ b/osu.Game.Tests/Visual/TestCaseMultiHeader.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Screens; using osu.Game.Screens; using osu.Game.Screens.Multi; @@ -15,15 +16,15 @@ namespace osu.Game.Tests.Visual { int index = 0; - OsuScreen currentScreen = new TestMultiplayerSubScreen(index); + ScreenStack screenStack = new ScreenStack(new TestMultiplayerSubScreen(index)) { RelativeSizeAxes = Axes.Both }; Children = new Drawable[] { - currentScreen, - new Header(currentScreen) + screenStack, + new Header(screenStack) }; - AddStep("push multi screen", () => currentScreen.Push(currentScreen = new TestMultiplayerSubScreen(++index))); + AddStep("push multi screen", () => screenStack.CurrentScreen.Push(new TestMultiplayerSubScreen(++index))); } private class TestMultiplayerSubScreen : OsuScreen, IMultiplayerSubScreen diff --git a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs index 13419167a7..d83b90d6e1 100644 --- a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs +++ b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using NUnit.Framework; +using osu.Framework.Screens; using osu.Game.Screens.Multi; using osu.Game.Screens.Multi.Lounge; using osu.Game.Screens.Multi.Lounge.Components; diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index 5de8a468e7..dedcc24e53 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Extensions; using osu.Framework.MathUtils; +using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets; @@ -81,7 +82,7 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load() + private void load(GameHost host) { factory = new DatabaseContextFactory(LocalStorage); factory.ResetDatabase(); @@ -95,7 +96,7 @@ namespace osu.Game.Tests.Visual usage.Migrate(); Dependencies.Cache(rulesets = new RulesetStore(factory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, null, null, defaultBeatmap = Beatmap.Default)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, null, host, defaultBeatmap = Beatmap.Default)); Beatmap.SetDefault(); } diff --git a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs index fd3d838149..82ce7e125a 100644 --- a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs @@ -3,6 +3,7 @@ using System.Threading; using osu.Framework.Allocation; +using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Screens.Play; @@ -26,7 +27,7 @@ namespace osu.Game.Tests.Visual AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); - AddUntilStep(() => !loader.IsCurrentScreen, "wait for no longer current"); + AddUntilStep(() => !loader.IsCurrentScreen(), "wait for no longer current"); AddStep("load slow dummy beatmap", () => { @@ -42,7 +43,7 @@ namespace osu.Game.Tests.Visual Scheduler.AddDelayed(() => slow.Ready = true, 5000); }); - AddUntilStep(() => !loader.IsCurrentScreen, "wait for no longer current"); + AddUntilStep(() => !loader.IsCurrentScreen(), "wait for no longer current"); } protected class SlowLoadPlayer : Player diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs index 0f34e4f10a..7202861833 100644 --- a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs +++ b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs @@ -19,16 +19,18 @@ namespace osu.Game.Tests.Visual public class TestCaseScreenBreadcrumbControl : OsuTestCase { private readonly ScreenBreadcrumbControl breadcrumbs; - private Screen currentScreen, changedScreen; + private readonly ScreenStack screenStack; public TestCaseScreenBreadcrumbControl() { - TestScreen startScreen; OsuSpriteText titleText; + IScreen startScreen = new TestScreenOne(); + screenStack = new ScreenStack(startScreen) { RelativeSizeAxes = Axes.Both }; + Children = new Drawable[] { - currentScreen = startScreen = new TestScreenOne(), + screenStack, new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -37,7 +39,7 @@ namespace osu.Game.Tests.Visual Spacing = new Vector2(10), Children = new Drawable[] { - breadcrumbs = new ScreenBreadcrumbControl(startScreen) + breadcrumbs = new ScreenBreadcrumbControl(screenStack) { RelativeSizeAxes = Axes.X, }, @@ -46,12 +48,7 @@ namespace osu.Game.Tests.Visual }, }; - breadcrumbs.Current.ValueChanged += s => - { - titleText.Text = $"Changed to {s.ToString()}"; - changedScreen = s; - }; - + breadcrumbs.Current.ValueChanged += s => titleText.Text = $"Changed to {s.ToString()}"; breadcrumbs.Current.TriggerChange(); waitForCurrent(); @@ -60,18 +57,14 @@ namespace osu.Game.Tests.Visual pushNext(); waitForCurrent(); - AddStep(@"make start current", () => - { - startScreen.MakeCurrent(); - currentScreen = startScreen; - }); + AddStep(@"make start current", () => startScreen.MakeCurrent()); waitForCurrent(); pushNext(); waitForCurrent(); AddAssert(@"only 2 items", () => breadcrumbs.Items.Count() == 2); - AddStep(@"exit current", () => changedScreen.Exit()); - AddAssert(@"current screen is first", () => startScreen == changedScreen); + AddStep(@"exit current", () => screenStack.CurrentScreen.Exit()); + AddAssert(@"current screen is first", () => startScreen == screenStack.CurrentScreen); } [BackgroundDependencyLoader] @@ -80,8 +73,8 @@ namespace osu.Game.Tests.Visual breadcrumbs.StripColour = colours.Blue; } - private void pushNext() => AddStep(@"push next screen", () => currentScreen = ((TestScreen)currentScreen).PushNext()); - private void waitForCurrent() => AddUntilStep(() => currentScreen.IsCurrentScreen, "current screen"); + private void pushNext() => AddStep(@"push next screen", () => ((TestScreen)screenStack.CurrentScreen).PushNext()); + private void waitForCurrent() => AddUntilStep(() => screenStack.CurrentScreen.IsCurrentScreen(), "current screen"); private abstract class TestScreen : OsuScreen { @@ -91,14 +84,14 @@ namespace osu.Game.Tests.Visual public TestScreen PushNext() { TestScreen screen = CreateNextScreen(); - Push(screen); + this.Push(screen); return screen; } protected TestScreen() { - Child = new FillFlowContainer + InternalChild = new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 42048692fc..21739f16c2 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -163,18 +163,14 @@ namespace osu.Game.Beatmaps downloadNotification.Progress = progress; }; - request.Success += data => + request.Success += filename => { downloadNotification.Text = $"Importing {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}"; Task.Factory.StartNew(() => { - BeatmapSetInfo importedBeatmap; - // This gets scheduled back to the update thread, but we want the import to run in the background. - using (var stream = new MemoryStream(data)) - using (var archive = new ZipArchiveReader(stream, beatmapSetInfo.ToString())) - importedBeatmap = Import(archive); + var importedBeatmap = Import(filename); downloadNotification.CompletionClickAction = () => { diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 4b6662178f..6bf9e2ff37 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -150,25 +150,9 @@ namespace osu.Game.Database { notification.Text = $"Importing ({++current} of {paths.Length})\n{Path.GetFileName(path)}"; - TModel import; - using (ArchiveReader reader = getReaderFrom(path)) - imported.Add(import = Import(reader)); + imported.Add(Import(path)); notification.Progress = (float)current / paths.Length; - - // We may or may not want to delete the file depending on where it is stored. - // e.g. reconstructing/repairing database with items from default storage. - // Also, not always a single file, i.e. for LegacyFilesystemReader - // TODO: Add a check to prevent files from storage to be deleted. - try - { - if (import != null && File.Exists(path)) - File.Delete(path); - } - catch (Exception e) - { - Logger.Error(e, $@"Could not delete original file after import ({Path.GetFileName(path)})"); - } } catch (Exception e) { @@ -195,6 +179,34 @@ namespace osu.Game.Database } } + /// + /// Import one from the filesystem and delete the file on success. + /// + /// The archive location on disk. + /// The imported model, if successful. + public TModel Import(string path) + { + TModel import; + using (ArchiveReader reader = getReaderFrom(path)) + import = Import(reader); + + // We may or may not want to delete the file depending on where it is stored. + // e.g. reconstructing/repairing database with items from default storage. + // Also, not always a single file, i.e. for LegacyFilesystemReader + // TODO: Add a check to prevent files from storage to be deleted. + try + { + if (import != null && File.Exists(path)) + File.Delete(path); + } + catch (Exception e) + { + Logger.Error(e, $@"Could not delete original file after import ({Path.GetFileName(path)})"); + } + + return import; + } + protected virtual void PresentCompletedImport(IEnumerable imported) { } diff --git a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs index a8870f3cf3..5f9b428dfc 100644 --- a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs +++ b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs @@ -50,6 +50,7 @@ namespace osu.Game.Graphics.Cursor if (!CanShowCursor) { currentTarget?.Cursor?.Hide(); + currentTarget = null; return; } diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 273ac12db4..73c9c0dd0e 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -3,7 +3,9 @@ using osuTK.Graphics; using System; +using osu.Framework.Allocation; using osu.Framework.Input.Events; +using osu.Framework.Platform; using osu.Game.Input.Bindings; using osuTK.Input; @@ -21,9 +23,16 @@ namespace osu.Game.Graphics.UserInterface private bool focus; + private bool allowImmediateFocus => host?.OnScreenKeyboardOverlapsGameWindow != true; + + public void TakeFocus() + { + if (allowImmediateFocus) GetContainingInputManager().ChangeFocus(this); + } + public bool HoldFocus { - get { return focus; } + get => allowImmediateFocus && focus; set { focus = value; @@ -32,6 +41,14 @@ namespace osu.Game.Graphics.UserInterface } } + private GameHost host; + + [BackgroundDependencyLoader] + private void load(GameHost host) + { + this.host = host; + } + // We may not be focused yet, but we need to handle keyboard input to be able to request focus public override bool HandleNonPositionalInput => HoldFocus || base.HandleNonPositionalInput; diff --git a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs index f7c907c5ed..6e01c69df5 100644 --- a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs @@ -10,45 +10,30 @@ namespace osu.Game.Graphics.UserInterface /// /// A which follows the active screen (and allows navigation) in a stack. /// - public class ScreenBreadcrumbControl : BreadcrumbControl + public class ScreenBreadcrumbControl : BreadcrumbControl { - private Screen last; - - public ScreenBreadcrumbControl(Screen initialScreen) + public ScreenBreadcrumbControl(ScreenStack stack) { - Current.ValueChanged += newScreen => - { - if (last != newScreen && !newScreen.IsCurrentScreen) - newScreen.MakeCurrent(); - }; + stack.ScreenPushed += onPushed; + stack.ScreenExited += onExited; - onPushed(initialScreen); + onPushed(null, stack.CurrentScreen); + + Current.ValueChanged += newScreen => newScreen.MakeCurrent(); } - private void screenChanged(Screen newScreen) + private void onPushed(IScreen lastScreen, IScreen newScreen) { - if (newScreen == null) return; - - if (last != null) - { - last.Exited -= screenChanged; - last.ModePushed -= onPushed; - } - - last = newScreen; - - newScreen.Exited += screenChanged; - newScreen.ModePushed += onPushed; - + AddItem(newScreen); Current.Value = newScreen; } - private void onPushed(Screen screen) + private void onExited(IScreen lastScreen, IScreen newScreen) { - Items.ToList().SkipWhile(i => i != Current.Value).Skip(1).ForEach(RemoveItem); - AddItem(screen); + if (newScreen != null) + Current.Value = newScreen; - screenChanged(screen); + Items.ToList().SkipWhile(s => s != Current.Value).Skip(1).ForEach(RemoveItem); } } } diff --git a/osu.Game/Online/API/APIDownloadRequest.cs b/osu.Game/Online/API/APIDownloadRequest.cs index b9449b57f2..97b869bccd 100644 --- a/osu.Game/Online/API/APIDownloadRequest.cs +++ b/osu.Game/Online/API/APIDownloadRequest.cs @@ -1,15 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.IO; using osu.Framework.IO.Network; namespace osu.Game.Online.API { public abstract class APIDownloadRequest : APIRequest { + private string filename; + protected override WebRequest CreateWebRequest() { - var request = new WebRequest(Uri); + var request = new FileWebRequest(filename = Path.GetTempFileName(), Uri); request.DownloadProgress += request_Progress; return request; } @@ -23,11 +26,11 @@ namespace osu.Game.Online.API private void onSuccess() { - Success?.Invoke(WebRequest.ResponseData); + Success?.Invoke(filename); } public event APIProgressHandler Progress; - public new event APISuccessHandler Success; + public new event APISuccessHandler Success; } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index d8499f4159..3cc040ecb1 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -79,27 +79,23 @@ namespace osu.Game public virtual Storage GetStorageForStableInstall() => null; - private Intro intro - { - get - { - Screen screen = screenStack; - while (screen != null && !(screen is Intro)) - screen = screen.ChildScreen; - return screen as Intro; - } - } - public float ToolbarOffset => Toolbar.Position.Y + Toolbar.DrawHeight; private IdleTracker idleTracker; public readonly Bindable OverlayActivationMode = new Bindable(); - private OsuScreen screenStack; + private BackgroundScreenStack backgroundStack; + private ParallaxContainer backgroundParallax; + + private ScreenStack screenStack; private VolumeOverlay volume; private OnScreenDisplay onscreenDisplay; + private OsuLogo osuLogo; + + private MainMenu menuScreen; + private Intro introScreen; private Bindable configRuleset; private readonly Bindable ruleset = new Bindable(); @@ -173,6 +169,8 @@ namespace osu.Game dependencies.CacheAs(ruleset); dependencies.CacheAs>(ruleset); + dependencies.Cache(osuLogo = new OsuLogo()); + // bind config int to database RulesetInfo configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); @@ -211,6 +209,12 @@ namespace osu.Game /// The beatmap to select. public void PresentBeatmap(BeatmapSetInfo beatmap) { + if (menuScreen == null) + { + Schedule(() => PresentBeatmap(beatmap)); + return; + } + CloseAllOverlays(false); void setBeatmap() @@ -233,16 +237,15 @@ namespace osu.Game } } - switch (currentScreen) + switch (screenStack.CurrentScreen) { case SongSelect _: break; default: // navigate to song select if we are not already there. - var menu = (MainMenu)intro.ChildScreen; - menu.MakeCurrent(); - menu.LoadToSolo(); + menuScreen.MakeCurrent(); + menuScreen.LoadToSolo(); break; } @@ -268,9 +271,7 @@ namespace osu.Game scoreLoad?.Cancel(); - var menu = intro.ChildScreen; - - if (menu == null) + if (menuScreen == null) { scoreLoad = Schedule(() => LoadScore(score, false)); return; @@ -291,7 +292,7 @@ namespace osu.Game return; } - if (!currentScreen.AllowExternalScreenChange) + if ((screenStack.CurrentScreen as IOsuScreen)?.AllowExternalScreenChange != true) { notifications.Post(new SimpleNotification { @@ -310,9 +311,9 @@ namespace osu.Game void loadScore() { - if (!menu.IsCurrentScreen) + if (!menuScreen.IsCurrentScreen()) { - menu.MakeCurrent(); + menuScreen.MakeCurrent(); this.Delay(500).Schedule(loadScore, out scoreLoad); return; } @@ -322,7 +323,7 @@ namespace osu.Game Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); Beatmap.Value.Mods.Value = databasedScoreInfo.Mods; - currentScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore))); + menuScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore))); } } @@ -336,11 +337,6 @@ namespace osu.Game { base.LoadComplete(); - // The next time this is updated is in UpdateAfterChildren, which occurs too late and results - // in the cursor being shown for a few frames during the intro. - // This prevents the cursor from showing until we have a screen with CursorVisible = true - MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; - // todo: all archive managers should be able to be looped here. SkinManager.PostNotification = n => notifications?.Post(n); SkinManager.GetStableStorage = GetStorageForStableInstall; @@ -350,6 +346,8 @@ namespace osu.Game BeatmapManager.PresentBeatmap = PresentBeatmap; + Container logoContainer; + AddRange(new Drawable[] { new VolumeControlReceptor @@ -361,6 +359,16 @@ namespace osu.Game screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays) { RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + backgroundParallax = new ParallaxContainer + { + RelativeSizeAxes = Axes.Both, + Child = backgroundStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }, + }, + screenStack = new ScreenStack { RelativeSizeAxes = Axes.Both }, + logoContainer = new Container { RelativeSizeAxes = Axes.Both }, + } }, overlayContent = new Container { @@ -370,12 +378,17 @@ namespace osu.Game idleTracker = new GameIdleTracker(6000) }); - loadComponentSingleFile(screenStack = new Loader(), d => + dependencies.Cache(backgroundStack); + + screenStack.ScreenPushed += screenPushed; + screenStack.ScreenExited += screenExited; + + loadComponentSingleFile(osuLogo, logoContainer.Add); + + loadComponentSingleFile(new Loader { - screenStack.ModePushed += screenAdded; - screenStack.Exited += screenRemoved; - screenContainer.Add(screenStack); - }); + RelativeSizeAxes = Axes.Both + }, screenStack.Push); loadComponentSingleFile(Toolbar = new Toolbar { @@ -383,7 +396,7 @@ namespace osu.Game OnHome = delegate { CloseAllOverlays(false); - intro?.ChildScreen?.MakeCurrent(); + menuScreen?.MakeCurrent(); }, }, floatingOverlayContent.Add); @@ -617,7 +630,7 @@ namespace osu.Game public bool OnPressed(GlobalAction action) { - if (intro == null) return false; + if (introScreen == null) return false; switch (action) { @@ -674,19 +687,20 @@ namespace osu.Game private Container floatingOverlayContent; - private OsuScreen currentScreen; private FrameworkConfigManager frameworkConfig; private ScalingContainer screenContainer; protected override bool OnExiting() { - if (screenStack.ChildScreen == null) return false; + if (screenStack.CurrentScreen is Loader) + return false; - if (intro == null) return true; + if (introScreen == null) + return true; - if (!intro.DidLoadMenu || intro.ChildScreen != null) + if (!introScreen.DidLoadMenu || !(screenStack.CurrentScreen is Intro)) { - Scheduler.Add(intro.MakeCurrent); + Scheduler.Add(introScreen.MakeCurrent); return true; } @@ -711,7 +725,7 @@ namespace osu.Game // we only want to apply these restrictions when we are inside a screen stack. // the use case for not applying is in visual/unit tests. - bool applyBeatmapRulesetRestrictions = !currentScreen?.AllowBeatmapRulesetChange ?? false; + bool applyBeatmapRulesetRestrictions = !(screenStack.CurrentScreen as IOsuScreen)?.AllowBeatmapRulesetChange ?? false; ruleset.Disabled = applyBeatmapRulesetRestrictions; Beatmap.Disabled = applyBeatmapRulesetRestrictions; @@ -719,7 +733,7 @@ namespace osu.Game screenContainer.Padding = new MarginPadding { Top = ToolbarOffset }; overlayContent.Padding = new MarginPadding { Top = ToolbarOffset }; - MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; + MenuCursorContainer.CanShowCursor = (screenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false; } /// @@ -748,24 +762,41 @@ namespace osu.Game this.ruleset.Disabled = rulesetDisabled; } - protected virtual void ScreenChanged(OsuScreen current, Screen newScreen) + protected virtual void ScreenChanged(IScreen lastScreen, IScreen newScreen) { - currentScreen = (OsuScreen)newScreen; + switch (newScreen) + { + case Intro intro: + introScreen = intro; + break; + case MainMenu menu: + menuScreen = menu; + break; + } + + if (newScreen is IOsuScreen newOsuScreen) + { + backgroundParallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * newOsuScreen.BackgroundParallaxAmount; + + OverlayActivationMode.Value = newOsuScreen.InitialOverlayActivationMode; + + if (newOsuScreen.HideOverlaysOnEnter) + CloseAllOverlays(); + else + Toolbar.State = Visibility.Visible; + } } - private void screenAdded(Screen newScreen) + private void screenPushed(IScreen lastScreen, IScreen newScreen) { - ScreenChanged(currentScreen, newScreen); + ScreenChanged(lastScreen, newScreen); Logger.Log($"Screen changed → {newScreen}"); - - newScreen.ModePushed += screenAdded; - newScreen.Exited += screenRemoved; } - private void screenRemoved(Screen newScreen) + private void screenExited(IScreen lastScreen, IScreen newScreen) { - ScreenChanged(currentScreen, newScreen); - Logger.Log($"Screen changed ← {currentScreen}"); + ScreenChanged(lastScreen, newScreen); + Logger.Log($"Screen changed ← {newScreen}"); if (newScreen == null) Exit(); diff --git a/osu.Game/Overlays/AccountCreation/AccountCreationScreen.cs b/osu.Game/Overlays/AccountCreation/AccountCreationScreen.cs index 071e1d0ffa..7e2ae405cb 100644 --- a/osu.Game/Overlays/AccountCreation/AccountCreationScreen.cs +++ b/osu.Game/Overlays/AccountCreation/AccountCreationScreen.cs @@ -8,22 +8,22 @@ namespace osu.Game.Overlays.AccountCreation { public abstract class AccountCreationScreen : Screen { - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); - Content.FadeOut().Delay(200).FadeIn(200); + this.FadeOut().Delay(200).FadeIn(200); } - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { base.OnResuming(last); - Content.FadeIn(200); + this.FadeIn(200); } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { base.OnSuspending(next); - Content.FadeOut(200); + this.FadeOut(200); } } } diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index b0d08d3c6c..02925a08e7 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.AccountCreation { this.api = api; - Children = new Drawable[] + InternalChildren = new Drawable[] { new FillFlowContainer { @@ -143,7 +143,7 @@ namespace osu.Game.Overlays.AccountCreation focusNextTextbox(); } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); processingOverlay.Hide(); diff --git a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs index cec0f1a767..3cc84e3562 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs @@ -26,12 +26,12 @@ namespace osu.Game.Overlays.AccountCreation private const string help_centre_url = "/help/wiki/Help_Centre#login"; - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { if (string.IsNullOrEmpty(api.ProvidedUsername)) { - Content.FadeOut(); - Push(new ScreenEntry()); + this.FadeOut(); + this.Push(new ScreenEntry()); return; } @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.AccountCreation if (string.IsNullOrEmpty(api.ProvidedUsername)) return; - Children = new Drawable[] + InternalChildren = new Drawable[] { new Sprite { @@ -104,7 +104,7 @@ namespace osu.Game.Overlays.AccountCreation new DangerousSettingsButton { Text = "I understand. This account isn't for me.", - Action = () => Push(new ScreenEntry()) + Action = () => this.Push(new ScreenEntry()) }, furtherAssistance = new LinkFlowContainer(cp => { cp.TextSize = 12; }) { diff --git a/osu.Game/Overlays/AccountCreation/ScreenWelcome.cs b/osu.Game/Overlays/AccountCreation/ScreenWelcome.cs index da59b294e5..d4b8323394 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenWelcome.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenWelcome.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Settings; using osu.Game.Screens.Menu; @@ -16,7 +17,7 @@ namespace osu.Game.Overlays.AccountCreation [BackgroundDependencyLoader] private void load() { - Child = new FillFlowContainer + InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.Both, Direction = FillDirection.Vertical, @@ -56,7 +57,7 @@ namespace osu.Game.Overlays.AccountCreation { Text = "Let's create an account!", Margin = new MarginPadding { Vertical = 120 }, - Action = () => Push(new ScreenWarning()) + Action = () => this.Push(new ScreenWarning()) } } }; diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index 718a56a441..dab960db25 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -6,6 +6,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API; @@ -82,7 +83,7 @@ namespace osu.Game.Overlays base.PopIn(); this.FadeIn(transition_time, Easing.OutQuint); - if (welcomeScreen.ChildScreen != null) + if (welcomeScreen.GetChildScreen() != null) welcomeScreen.MakeCurrent(); } diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs index 5969d7aded..00de5fd5fd 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs @@ -164,7 +164,7 @@ namespace osu.Game.Overlays.Chat.Selection protected override void OnFocus(FocusEvent e) { - GetContainingInputManager().ChangeFocus(search); + search.TakeFocus(); base.OnFocus(e); } diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 74edf48433..821c942a57 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -302,7 +302,7 @@ namespace osu.Game.Overlays protected override void OnFocus(FocusEvent e) { //this is necessary as textbox is masked away and therefore can't get focus :( - GetContainingInputManager().ChangeFocus(textbox); + textbox.TakeFocus(); base.OnFocus(e); } diff --git a/osu.Game/Overlays/Music/FilterControl.cs b/osu.Game/Overlays/Music/FilterControl.cs index ea18c9bb2c..c2c10d999f 100644 --- a/osu.Game/Overlays/Music/FilterControl.cs +++ b/osu.Game/Overlays/Music/FilterControl.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -53,10 +52,9 @@ namespace osu.Game.Overlays.Music public class FilterTextBox : SearchTextBox { - private Color4 backgroundColour; + protected override Color4 BackgroundUnfocused => OsuColour.Gray(0.06f); + protected override Color4 BackgroundFocused => OsuColour.Gray(0.12f); - protected override Color4 BackgroundUnfocused => backgroundColour; - protected override Color4 BackgroundFocused => backgroundColour; protected override bool AllowCommit => true; public FilterTextBox() @@ -64,12 +62,6 @@ namespace osu.Game.Overlays.Music Masking = true; CornerRadius = 5; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - backgroundColour = colours.Gray2; - } } } } diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index f69ab3ec38..7b5a59836f 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Music protected override void PopIn() { filter.Search.HoldFocus = true; - Schedule(() => GetContainingInputManager().ChangeFocus(filter.Search)); + Schedule(() => filter.Search.TakeFocus()); this.ResizeTo(new Vector2(1, playlist_height), transition_duration, Easing.OutQuint); this.FadeIn(transition_duration, Easing.OutQuint); diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs index f679e0186a..478e3d4c95 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs @@ -127,17 +127,10 @@ namespace osu.Game.Overlays.SearchableList private class FilterSearchTextBox : SearchTextBox { - protected override Color4 BackgroundUnfocused => backgroundColour; - protected override Color4 BackgroundFocused => backgroundColour; + protected override Color4 BackgroundUnfocused => OsuColour.Gray(0.06f); + protected override Color4 BackgroundFocused => OsuColour.Gray(0.12f); + protected override bool AllowCommit => true; - - private Color4 backgroundColour; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - backgroundColour = colours.Gray2.Opacity(0.9f); - } } } } diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index ecb610c1f4..87c369e246 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -103,7 +103,7 @@ namespace osu.Game.Overlays.SearchableList protected override void OnFocus(FocusEvent e) { - GetContainingInputManager().ChangeFocus(Filter.Search); + Filter.Search.TakeFocus(); } protected override void PopIn() diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index e5eee22164..802e97d92a 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -179,7 +179,7 @@ namespace osu.Game.Overlays protected override void OnFocus(FocusEvent e) { - GetContainingInputManager().ChangeFocus(searchTextBox); + searchTextBox.TakeFocus(); base.OnFocus(e); } diff --git a/osu.Game/Properties/AssemblyInfo.cs b/osu.Game/Properties/AssemblyInfo.cs index 48d642565e..cddea7a35f 100644 --- a/osu.Game/Properties/AssemblyInfo.cs +++ b/osu.Game/Properties/AssemblyInfo.cs @@ -9,3 +9,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("osu.Game.Tests")] [assembly: InternalsVisibleTo("osu.Game.Tests.Dynamic")] +[assembly: InternalsVisibleTo("osu.Game.Tests.iOS")] \ No newline at end of file diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index d2308ac1c3..bbe162cf7c 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Threading; using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Input.Events; @@ -12,6 +11,12 @@ namespace osu.Game.Screens { public abstract class BackgroundScreen : Screen, IEquatable { + protected BackgroundScreen() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + public virtual bool Equals(BackgroundScreen other) { return other?.GetType() == GetType(); @@ -26,64 +31,40 @@ namespace osu.Game.Screens return false; } - public override void Push(Screen screen) - { - // When trying to push a non-loaded screen, load it asynchronously and re-invoke Push - // once it's done. - if (screen.LoadState == LoadState.NotLoaded) - { - LoadComponentAsync(screen, d => Push((BackgroundScreen)d)); - return; - } - - // Make sure the in-progress loading is complete before pushing the screen. - while (screen.LoadState < LoadState.Ready) - Thread.Sleep(1); - - try - { - base.Push(screen); - } - catch (ScreenAlreadyExitedException) - { - // screen may have exited before the push was successful. - } - } - protected override void Update() { base.Update(); - Content.Scale = new Vector2(1 + x_movement_amount / DrawSize.X * 2); + Scale = new Vector2(1 + x_movement_amount / DrawSize.X * 2); } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { - Content.FadeOut(); - Content.MoveToX(x_movement_amount); + this.FadeOut(); + this.MoveToX(x_movement_amount); - Content.FadeIn(transition_length, Easing.InOutQuart); - Content.MoveToX(0, transition_length, Easing.InOutQuart); + this.FadeIn(transition_length, Easing.InOutQuart); + this.MoveToX(0, transition_length, Easing.InOutQuart); base.OnEntering(last); } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { - Content.MoveToX(-x_movement_amount, transition_length, Easing.InOutQuart); + this.MoveToX(-x_movement_amount, transition_length, Easing.InOutQuart); base.OnSuspending(next); } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { - Content.FadeOut(transition_length, Easing.OutExpo); - Content.MoveToX(x_movement_amount, transition_length, Easing.OutExpo); + this.FadeOut(transition_length, Easing.OutExpo); + this.MoveToX(x_movement_amount, transition_length, Easing.OutExpo); return base.OnExiting(next); } - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { - Content.MoveToX(0, transition_length, Easing.OutExpo); + this.MoveToX(0, transition_length, Easing.OutExpo); base.OnResuming(last); } } diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs new file mode 100644 index 0000000000..7bf167295c --- /dev/null +++ b/osu.Game/Screens/BackgroundScreenStack.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osuTK; + +namespace osu.Game.Screens +{ + public class BackgroundScreenStack : ScreenStack + { + public BackgroundScreenStack() + { + Scale = new Vector2(1.06f); + RelativeSizeAxes = Axes.Both; + } + + //public float ParallaxAmount { set => parallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * value; } + + public new void Push(BackgroundScreen screen) + { + if (screen == null) + return; + + if (EqualityComparer.Default.Equals((BackgroundScreen)CurrentScreen, screen)) + return; + + base.Push(screen); + } + } +} diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 3313db15e2..8706cc6668 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Backgrounds } b.Depth = newDepth; - Add(Background = b); + AddInternal(Background = b); Background.BlurSigma = BlurTarget; })); }); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBlack.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBlack.cs index 2584739787..9e2559cc56 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBlack.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBlack.cs @@ -12,14 +12,14 @@ namespace osu.Game.Screens.Backgrounds { public BackgroundScreenBlack() { - Child = new Box + InternalChild = new Box { Colour = Color4.Black, RelativeSizeAxes = Axes.Both, }; } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { Show(); } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs index 8792721600..0cb41bc562 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Backgrounds public BackgroundScreenCustom(string textureName) { this.textureName = textureName; - Add(new Background(textureName)); + AddInternal(new Background(textureName)); } public override bool Equals(BackgroundScreen other) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 9789248660..072da7c66e 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Backgrounds Background?.FadeOut(800, Easing.InOutSine); Background?.Expire(); - Add(Background = newBackground); + AddInternal(Background = newBackground); currentDisplay++; } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index e4ef023a7a..ac5ff504a2 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Edit { protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4"); - protected override bool HideOverlaysOnEnter => true; + public override bool HideOverlaysOnEnter => true; public override bool AllowBeatmapRulesetChange => false; private Box bottomBackground; @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Edit SummaryTimeline timeline; PlaybackControl playback; - Children = new[] + InternalChildren = new[] { new Container { @@ -96,7 +96,7 @@ namespace osu.Game.Screens.Edit { new EditorMenuItem("Export", MenuItemType.Standard, exportBeatmap), new EditorMenuItemSpacer(), - new EditorMenuItem("Exit", MenuItemType.Standard, Exit) + new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit) } } } @@ -194,20 +194,20 @@ namespace osu.Game.Screens.Edit return true; } - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { Beatmap.Value.Track?.Stop(); base.OnResuming(last); } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); Background.FadeColour(Color4.DarkGray, 500); Beatmap.Value.Track?.Stop(); } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { Background.FadeColour(Color4.White, 500); if (Beatmap.Value.Track != null) diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs new file mode 100644 index 0000000000..532d4963a0 --- /dev/null +++ b/osu.Game/Screens/IOsuScreen.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Screens; +using osu.Game.Overlays; + +namespace osu.Game.Screens +{ + public interface IOsuScreen : IScreen + { + /// + /// Whether the beatmap or ruleset should be allowed to be changed by the user or game. + /// Used to mark exclusive areas where this is strongly prohibited, like gameplay. + /// + bool AllowBeatmapRulesetChange { get; } + + bool AllowExternalScreenChange { get; } + + /// + /// Whether this allows the cursor to be displayed. + /// + bool CursorVisible { get; } + + /// + /// Whether all overlays should be hidden when this screen is entered or resumed. + /// + bool HideOverlaysOnEnter { get; } + + /// + /// Whether overlays should be able to be opened once this screen is entered or resumed. + /// + OverlayActivation InitialOverlayActivationMode { get; } + + /// + /// The amount of parallax to be applied while this screen is displayed. + /// + float BackgroundParallaxAmount { get; } + } +} diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 78e80766b2..9703d79442 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -17,9 +17,9 @@ namespace osu.Game.Screens { private bool showDisclaimer; - protected override bool HideOverlaysOnEnter => true; + public override bool HideOverlaysOnEnter => true; - protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; + public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; protected override bool AllowBackButton => false; @@ -55,11 +55,11 @@ namespace osu.Game.Screens protected virtual ShaderPrecompiler CreateShaderPrecompiler() => new ShaderPrecompiler(); - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); - LoadComponentAsync(precompiler = CreateShaderPrecompiler(), Add); + LoadComponentAsync(precompiler = CreateShaderPrecompiler(), AddInternal); LoadComponentAsync(loadableScreen = CreateLoadableScreen()); checkIfLoaded(); @@ -73,7 +73,7 @@ namespace osu.Game.Screens return; } - Push(loadableScreen); + this.Push(loadableScreen); } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index ed0ae3309c..c0ff37cc0b 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -23,8 +23,8 @@ namespace osu.Game.Screens.Menu private Color4 iconColour; private LinkFlowContainer textFlow; - protected override bool HideOverlaysOnEnter => true; - protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; + public override bool HideOverlaysOnEnter => true; + public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; public override bool CursorVisible => false; @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(OsuColour colours) { - Children = new Drawable[] + InternalChildren = new Drawable[] { icon = new SpriteIcon { @@ -116,7 +116,7 @@ namespace osu.Game.Screens.Menu LoadComponentAsync(intro = new Intro()); } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -130,12 +130,12 @@ namespace osu.Game.Screens.Menu supporterDrawables.ForEach(d => d.FadeOut().Delay(2000).FadeIn(500)); - Content + this .FadeInFromZero(500) .Then(5500) .FadeOut(250) .ScaleTo(0.9f, 250, Easing.InQuint) - .Finally(d => Push(intro)); + .Finally(d => this.Push(intro)); heart.FlashColour(Color4.White, 750, Easing.OutQuint).Loop(); } diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index 93a84ec14d..3a347342d7 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -34,8 +34,8 @@ namespace osu.Game.Screens.Menu private SampleChannel welcome; private SampleChannel seeya; - protected override bool HideOverlaysOnEnter => true; - protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; + public override bool HideOverlaysOnEnter => true; + public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; public override bool CursorVisible => false; @@ -111,7 +111,7 @@ namespace osu.Game.Screens.Menu Scheduler.AddDelayed(delegate { DidLoadMenu = true; - Push(mainMenu); + this.Push(mainMenu); }, delay_step_one); }, delay_step_two); } @@ -145,22 +145,21 @@ namespace osu.Game.Screens.Menu } } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { - Content.FadeOut(300); + this.FadeOut(300); base.OnSuspending(next); } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { //cancel exiting if we haven't loaded the menu yet. return !DidLoadMenu; } - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { - if (!(last is MainMenu)) - Content.FadeIn(300); + this.FadeIn(300); double fadeOutTime = EXIT_DELAY; //we also handle the exit transition. @@ -169,7 +168,7 @@ namespace osu.Game.Screens.Menu else fadeOutTime = 500; - Scheduler.AddDelayed(Exit, fadeOutTime); + Scheduler.AddDelayed(this.Exit, fadeOutTime); //don't want to fade out completely else we will stop running updates and shit will hit the fan. Game.FadeTo(0.01f, fadeOutTime); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 800bf8990e..04bc80ac87 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -25,28 +25,25 @@ namespace osu.Game.Screens.Menu { private readonly ButtonSystem buttons; - protected override bool HideOverlaysOnEnter => buttons.State == ButtonSystemState.Initial; + public override bool HideOverlaysOnEnter => buttons.State == ButtonSystemState.Initial; protected override bool AllowBackButton => buttons.State != ButtonSystemState.Initial; public override bool AllowExternalScreenChange => true; - private readonly BackgroundScreenDefault background; private Screen songSelect; private readonly MenuSideFlashes sideFlashes; - protected override BackgroundScreen CreateBackground() => background; + protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(); public MainMenu() { - background = new BackgroundScreenDefault(); - - Children = new Drawable[] + InternalChildren = new Drawable[] { new ExitConfirmOverlay { - Action = Exit, + Action = this.Exit, }, new ParallaxContainer { @@ -55,12 +52,12 @@ namespace osu.Game.Screens.Menu { buttons = new ButtonSystem { - OnChart = delegate { Push(new ChartListing()); }, - OnDirect = delegate { Push(new OnlineListing()); }, - OnEdit = delegate { Push(new Editor()); }, + OnChart = delegate { this.Push(new ChartListing()); }, + OnDirect = delegate {this.Push(new OnlineListing()); }, + OnEdit = delegate {this.Push(new Editor()); }, OnSolo = onSolo, - OnMulti = delegate { Push(new Multiplayer()); }, - OnExit = Exit, + OnMulti = delegate {this.Push(new Multiplayer()); }, + OnExit = this.Exit, } } }, @@ -73,10 +70,10 @@ namespace osu.Game.Screens.Menu { case ButtonSystemState.Initial: case ButtonSystemState.Exit: - background.FadeColour(Color4.White, 500, Easing.OutSine); + Background.FadeColour(Color4.White, 500, Easing.OutSine); break; default: - background.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine); + Background.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine); break; } }; @@ -85,8 +82,6 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader(true)] private void load(OsuGame game = null) { - LoadComponentAsync(background); - if (game != null) { buttons.OnSettings = game.ToggleSettings; @@ -104,7 +99,7 @@ namespace osu.Game.Screens.Menu public void LoadToSolo() => Schedule(onSolo); - private void onSolo() => Push(consumeSongSelect()); + private void onSolo() =>this.Push(consumeSongSelect()); private Screen consumeSongSelect() { @@ -113,7 +108,7 @@ namespace osu.Game.Screens.Menu return s; } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); buttons.FadeInFromZero(500); @@ -148,8 +143,8 @@ namespace osu.Game.Screens.Menu const float length = 300; - Content.FadeIn(length, Easing.OutQuint); - Content.MoveTo(new Vector2(0, 0), length, Easing.OutQuint); + this.FadeIn(length, Easing.OutQuint); + this.MoveTo(new Vector2(0, 0), length, Easing.OutQuint); sideFlashes.Delay(length).FadeIn(64, Easing.InQuint); } @@ -164,13 +159,13 @@ namespace osu.Game.Screens.Menu private void beatmap_ValueChanged(WorkingBeatmap newValue) { - if (!IsCurrentScreen) + if (!this.IsCurrentScreen()) return; - background.Next(); + ((BackgroundScreenDefault)Background).Next(); } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { base.OnSuspending(next); @@ -178,26 +173,26 @@ namespace osu.Game.Screens.Menu buttons.State = ButtonSystemState.EnteringMode; - Content.FadeOut(length, Easing.InSine); - Content.MoveTo(new Vector2(-800, 0), length, Easing.InSine); + this.FadeOut(length, Easing.InSine); + this.MoveTo(new Vector2(-800, 0), length, Easing.InSine); sideFlashes.FadeOut(64, Easing.OutQuint); } - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { base.OnResuming(last); - background.Next(); + ((BackgroundScreenDefault)Background).Next(); //we may have consumed our preloaded instance, so let's make another. preloadSongSelect(); } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { buttons.State = ButtonSystemState.Exit; - Content.FadeOut(3000); + this.FadeOut(3000); return base.OnExiting(next); } @@ -205,7 +200,7 @@ namespace osu.Game.Screens.Menu { if (!e.Repeat && e.ControlPressed && e.ShiftPressed && e.Key == Key.D) { - Push(new Drawings()); + this.Push(new Drawings()); return true; } diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 3448a23ac8..687a28b7a6 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Multi private readonly OsuSpriteText screenType; private readonly HeaderBreadcrumbControl breadcrumbs; - public Header(Screen initialScreen) + public Header(ScreenStack stack) { RelativeSizeAxes = Axes.X; Height = HEIGHT; @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Multi }, }, }, - breadcrumbs = new HeaderBreadcrumbControl(initialScreen) + breadcrumbs = new HeaderBreadcrumbControl(stack) { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, @@ -103,8 +103,8 @@ namespace osu.Game.Screens.Multi private class HeaderBreadcrumbControl : ScreenBreadcrumbControl { - public HeaderBreadcrumbControl(Screen initialScreen) - : base(initialScreen) + public HeaderBreadcrumbControl(ScreenStack stack) + : base(stack) { } diff --git a/osu.Game/Screens/Multi/IMultiplayerSubScreen.cs b/osu.Game/Screens/Multi/IMultiplayerSubScreen.cs index 542224262e..31ee123f83 100644 --- a/osu.Game/Screens/Multi/IMultiplayerSubScreen.cs +++ b/osu.Game/Screens/Multi/IMultiplayerSubScreen.cs @@ -3,8 +3,10 @@ namespace osu.Game.Screens.Multi { - public interface IMultiplayerSubScreen + public interface IMultiplayerSubScreen : IOsuScreen { + string Title { get; } + string ShortTitle { get; } } } diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index db0f105e0e..a56d2892a4 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -17,6 +16,8 @@ namespace osu.Game.Screens.Multi.Lounge { public class LoungeSubScreen : MultiplayerSubScreen { + public override string Title => "Lounge"; + protected readonly FilterControl Filter; private readonly Container content; @@ -24,20 +25,13 @@ namespace osu.Game.Screens.Multi.Lounge private readonly Action pushGameplayScreen; private readonly ProcessingOverlay processingOverlay; - [Resolved(CanBeNull = true)] - private IRoomManager roomManager { get; set; } - - public override string Title => "Lounge"; - - protected override Drawable TransitionContent => content; - public LoungeSubScreen(Action pushGameplayScreen) { this.pushGameplayScreen = pushGameplayScreen; RoomInspector inspector; - Children = new Drawable[] + InternalChildren = new Drawable[] { Filter = new FilterControl { Depth = -1 }, content = new Container @@ -81,7 +75,7 @@ namespace osu.Game.Screens.Multi.Lounge Filter.Search.Current.ValueChanged += s => filterRooms(); Filter.Tabs.Current.ValueChanged += t => filterRooms(); - Filter.Search.Exit += Exit; + Filter.Search.Exit += this.Exit; } protected override void UpdateAfterChildren() @@ -91,30 +85,29 @@ namespace osu.Game.Screens.Multi.Lounge content.Padding = new MarginPadding { Top = Filter.DrawHeight, - Left = SearchableListOverlay.WIDTH_PADDING - DrawableRoom.SELECTION_BORDER_WIDTH + HORIZONTAL_OVERFLOW_PADDING, - Right = SearchableListOverlay.WIDTH_PADDING + HORIZONTAL_OVERFLOW_PADDING, + Left = SearchableListOverlay.WIDTH_PADDING - DrawableRoom.SELECTION_BORDER_WIDTH + OsuScreen.HORIZONTAL_OVERFLOW_PADDING, + Right = SearchableListOverlay.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING, }; } protected override void OnFocus(FocusEvent e) { - GetContainingInputManager().ChangeFocus(Filter.Search); + Filter.Search.TakeFocus(); } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); Filter.Search.HoldFocus = true; } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { Filter.Search.HoldFocus = false; - // no base call; don't animate - return false; + return base.OnExiting(next); } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { base.OnSuspending(next); Filter.Search.HoldFocus = false; @@ -123,13 +116,13 @@ namespace osu.Game.Screens.Multi.Lounge private void filterRooms() { rooms.Filter(Filter.CreateCriteria()); - roomManager?.Filter(Filter.CreateCriteria()); + Manager?.Filter(Filter.CreateCriteria()); } private void joinRequested(Room room) { processingOverlay.Show(); - roomManager?.JoinRoom(room, r => + Manager?.JoinRoom(room, r => { Push(room); processingOverlay.Hide(); @@ -142,10 +135,10 @@ namespace osu.Game.Screens.Multi.Lounge public void Push(Room room) { // Handles the case where a room is clicked 3 times in quick succession - if (!IsCurrentScreen) + if (!this.IsCurrentScreen()) return; - Push(new MatchSubScreen(room, s => pushGameplayScreen?.Invoke(s))); + this.Push(new MatchSubScreen(room, s => pushGameplayScreen?.Invoke(s))); } } } diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 1b0efbdf09..980f321c92 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -21,6 +21,7 @@ namespace osu.Game.Screens.Multi.Match public class MatchSubScreen : MultiplayerSubScreen { public override bool AllowBeatmapRulesetChange => false; + public override string Title => room.RoomID.Value == null ? "New room" : room.Name.Value; public override string ShortTitle => "room"; @@ -36,12 +37,6 @@ namespace osu.Game.Screens.Multi.Match [Resolved] private BeatmapManager beatmapManager { get; set; } - [Resolved(CanBeNull = true)] - private OsuGame game { get; set; } - - [Resolved(CanBeNull = true)] - private IRoomManager manager { get; set; } - public MatchSubScreen(Room room, Action pushGameplayScreen) { this.room = room; @@ -55,7 +50,7 @@ namespace osu.Game.Screens.Multi.Match GridContainer bottomRow; MatchSettingsOverlay settings; - Children = new Drawable[] + InternalChildren = new Drawable[] { new GridContainer { @@ -77,7 +72,7 @@ namespace osu.Game.Screens.Multi.Match { Padding = new MarginPadding { - Left = 10 + HORIZONTAL_OVERFLOW_PADDING, + Left = 10 + OsuScreen.HORIZONTAL_OVERFLOW_PADDING, Right = 10, Vertical = 10, }, @@ -89,7 +84,7 @@ namespace osu.Game.Screens.Multi.Match Padding = new MarginPadding { Left = 10, - Right = 10 + HORIZONTAL_OVERFLOW_PADDING, + Right = 10 + OsuScreen.HORIZONTAL_OVERFLOW_PADDING, Vertical = 10, }, RelativeSizeAxes = Axes.Both, @@ -118,10 +113,9 @@ namespace osu.Game.Screens.Multi.Match }, }; - header.OnRequestSelectBeatmap = () => Push(new MatchSongSelect + header.OnRequestSelectBeatmap = () => this.Push(new MatchSongSelect { Selected = addPlaylistItem, - Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING } }); header.Tabs.Current.ValueChanged += t => @@ -141,7 +135,11 @@ namespace osu.Game.Screens.Multi.Match } }; - chat.Exit += Exit; + chat.Exit += () => + { + if (this.IsCurrentScreen()) + this.Exit(); + }; } [BackgroundDependencyLoader] @@ -150,9 +148,9 @@ namespace osu.Game.Screens.Multi.Match beatmapManager.ItemAdded += beatmapAdded; } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { - manager?.PartRoom(); + Manager?.PartRoom(); return base.OnExiting(next); } @@ -169,7 +167,7 @@ namespace osu.Game.Screens.Multi.Match // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineBeatmapID); - game?.ForcefullySetBeatmap(beatmapManager.GetWorkingBeatmap(localBeatmap)); + Game?.ForcefullySetBeatmap(beatmapManager.GetWorkingBeatmap(localBeatmap)); } private void setRuleset(RulesetInfo ruleset) @@ -177,7 +175,7 @@ namespace osu.Game.Screens.Multi.Match if (ruleset == null) return; - game?.ForcefullySetRuleset(ruleset); + Game?.ForcefullySetRuleset(ruleset); } private void beatmapAdded(BeatmapSetInfo model, bool existing, bool silent) => Schedule(() => @@ -192,7 +190,7 @@ namespace osu.Game.Screens.Multi.Match var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == bindings.CurrentBeatmap.Value.OnlineBeatmapID); if (localBeatmap != null) - game?.ForcefullySetBeatmap(beatmapManager.GetWorkingBeatmap(localBeatmap)); + Game?.ForcefullySetBeatmap(beatmapManager.GetWorkingBeatmap(localBeatmap)); }); private void addPlaylistItem(PlaylistItem item) @@ -209,11 +207,9 @@ namespace osu.Game.Screens.Multi.Match { default: case GameTypeTimeshift _: - pushGameplayScreen?.Invoke(new PlayerLoader(() => { - var player = new TimeshiftPlayer(room, room.Playlist.First().ID); - player.Exited += _ => leaderboard.RefreshScores(); - - return player; + pushGameplayScreen?.Invoke(new PlayerLoader(() => new TimeshiftPlayer(room, room.Playlist.First().ID) + { + Exited = () => leaderboard.RefreshScores() })); break; } diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index da15574029..1741ac0b7b 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Logging; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; @@ -15,6 +16,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; +using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet.Buttons; using osu.Game.Screens.Menu; using osu.Game.Screens.Multi.Lounge; @@ -24,31 +26,56 @@ using osuTK; namespace osu.Game.Screens.Multi { [Cached] - public class Multiplayer : OsuScreen, IOnlineComponent + public class Multiplayer : CompositeDrawable, IOsuScreen, IOnlineComponent { - private readonly MultiplayerWaveContainer waves; + public bool AllowBeatmapRulesetChange => (screenStack.CurrentScreen as IMultiplayerSubScreen)?.AllowBeatmapRulesetChange ?? true; + public bool AllowExternalScreenChange => (screenStack.CurrentScreen as IMultiplayerSubScreen)?.AllowExternalScreenChange ?? true; + public bool CursorVisible => (screenStack.CurrentScreen as IMultiplayerSubScreen)?.AllowExternalScreenChange ?? true; - public override bool AllowBeatmapRulesetChange => currentSubScreen?.AllowBeatmapRulesetChange ?? base.AllowBeatmapRulesetChange; + public bool HideOverlaysOnEnter => false; + public OverlayActivation InitialOverlayActivationMode => OverlayActivation.All; + + public float BackgroundParallaxAmount => 1; + + public bool ValidForResume { get; set; } = true; + public bool ValidForPush { get; set; } = true; + + public override bool RemoveWhenNotAlive => false; + + private readonly MultiplayerWaveContainer waves; private readonly OsuButton createButton; private readonly LoungeSubScreen loungeSubScreen; - - private OsuScreen currentSubScreen; + private readonly ScreenStack screenStack; [Cached(Type = typeof(IRoomManager))] private RoomManager roomManager; + [Resolved] + private IBindableBeatmap beatmap { get; set; } + + [Resolved] + private OsuGameBase game { get; set; } + [Resolved] private APIAccess api { get; set; } + [Resolved(CanBeNull = true)] + private OsuLogo logo { get; set; } + public Multiplayer() { - Child = waves = new MultiplayerWaveContainer + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + RelativeSizeAxes = Axes.Both; + + InternalChild = waves = new MultiplayerWaveContainer { RelativeSizeAxes = Axes.Both, }; - Padding = new MarginPadding { Horizontal = -HORIZONTAL_OVERFLOW_PADDING }; + screenStack = new ScreenStack(loungeSubScreen = new LoungeSubScreen(this.Push)) { RelativeSizeAxes = Axes.Both }; + Padding = new MarginPadding { Horizontal = -OsuScreen.HORIZONTAL_OVERFLOW_PADDING }; waves.AddRange(new Drawable[] { @@ -76,9 +103,9 @@ namespace osu.Game.Screens.Multi { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = Header.HEIGHT }, - Child = loungeSubScreen = new LoungeSubScreen(Push), + Child = screenStack }, - new Header(loungeSubScreen), + new Header(screenStack), createButton = new HeaderButton { Anchor = Anchor.TopRight, @@ -88,7 +115,7 @@ namespace osu.Game.Screens.Multi Margin = new MarginPadding { Top = 10, - Right = 10 + HORIZONTAL_OVERFLOW_PADDING, + Right = 10 + OsuScreen.HORIZONTAL_OVERFLOW_PADDING, }, Text = "Create room", Action = () => loungeSubScreen.Push(new Room @@ -99,8 +126,8 @@ namespace osu.Game.Screens.Multi roomManager = new RoomManager() }); - screenAdded(loungeSubScreen); - loungeSubScreen.Exited += _ => Exit(); + screenStack.ScreenPushed += screenPushed; + screenStack.ScreenExited += screenExited; } private readonly IBindable isIdle = new BindableBool(); @@ -122,7 +149,7 @@ namespace osu.Game.Screens.Multi private void updatePollingRate(bool idle) { - roomManager.TimeBetweenPolls = !IsCurrentScreen || !(currentSubScreen is LoungeSubScreen) ? 0 : (idle ? 120000 : 15000); + roomManager.TimeBetweenPolls = !this.IsCurrentScreen() || !(screenStack.CurrentScreen is LoungeSubScreen) ? 0 : (idle ? 120000 : 15000); Logger.Log($"Polling adjusted to {roomManager.TimeBetweenPolls}"); } @@ -135,114 +162,106 @@ namespace osu.Game.Screens.Multi private void forcefullyExit() { // This is temporary since we don't currently have a way to force screens to be exited - if (IsCurrentScreen) - Exit(); + if (this.IsCurrentScreen()) + this.Exit(); else { - MakeCurrent(); + this.MakeCurrent(); Schedule(forcefullyExit); } } - protected override void OnEntering(Screen last) + public void OnEntering(IScreen last) { - Content.FadeIn(); + this.FadeIn(); - base.OnEntering(last); waves.Show(); } - protected override bool OnExiting(Screen next) + public bool OnExiting(IScreen next) { waves.Hide(); - Content.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut(); + this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut(); cancelLooping(); - loungeSubScreen.MakeCurrent(); + + if (screenStack.CurrentScreen != null) + loungeSubScreen.MakeCurrent(); + updatePollingRate(isIdle.Value); - return base.OnExiting(next); + // the wave overlay transition takes longer than expected to run. + logo?.AppendAnimatingAction(() => logo.Delay(WaveContainer.DISAPPEAR_DURATION / 2).FadeOut(), false); + + return false; } - protected override void OnResuming(Screen last) + public void OnResuming(IScreen last) { - base.OnResuming(last); + this.FadeIn(250); + this.ScaleTo(1, 250, Easing.OutSine); - Content.FadeIn(250); - Content.ScaleTo(1, 250, Easing.OutSine); + logo?.AppendAnimatingAction(() => OsuScreen.ApplyLogoArrivingDefaults(logo), true); updatePollingRate(isIdle.Value); } - protected override void OnSuspending(Screen next) + public void OnSuspending(IScreen next) { - Content.ScaleTo(1.1f, 250, Easing.InSine); - Content.FadeOut(250); + this.ScaleTo(1.1f, 250, Easing.InSine); + this.FadeOut(250); cancelLooping(); roomManager.TimeBetweenPolls = 0; - - base.OnSuspending(next); } private void cancelLooping() { - var track = Beatmap.Value.Track; + var track = beatmap.Value.Track; if (track != null) track.Looping = false; } - protected override void LogoExiting(OsuLogo logo) - { - // the wave overlay transition takes longer than expected to run. - logo.Delay(WaveContainer.DISAPPEAR_DURATION / 2).FadeOut(); - base.LogoExiting(logo); - } - protected override void Update() { base.Update(); - if (!IsCurrentScreen) return; + if (!this.IsCurrentScreen()) return; - if (currentSubScreen is MatchSubScreen) + if (screenStack.CurrentScreen is MatchSubScreen) { - var track = Beatmap.Value.Track; + var track = beatmap.Value.Track; if (track != null) { track.Looping = true; if (!track.IsRunning) { - Game.Audio.AddItemToList(track); - track.Seek(Beatmap.Value.Metadata.PreviewTime); + game.Audio.AddItemToList(track); + track.Seek(beatmap.Value.Metadata.PreviewTime); track.Start(); } } createButton.Hide(); } - else if (currentSubScreen is LoungeSubScreen) + else if (screenStack.CurrentScreen is LoungeSubScreen) createButton.Show(); } - private void screenAdded(Screen newScreen) - { - currentSubScreen = (OsuScreen)newScreen; - updatePollingRate(isIdle.Value); + private void screenPushed(IScreen lastScreen, IScreen newScreen) + => updatePollingRate(isIdle.Value); - newScreen.ModePushed += screenAdded; - newScreen.Exited += screenRemoved; - } - - private void screenRemoved(Screen newScreen) + private void screenExited(IScreen lastScreen, IScreen newScreen) { - if (currentSubScreen is MatchSubScreen) + if (lastScreen is MatchSubScreen) cancelLooping(); - currentSubScreen = (OsuScreen)newScreen; updatePollingRate(isIdle.Value); + + if (screenStack.CurrentScreen == null) + this.Exit(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs index 3e33de34fd..ddea4d5dad 100644 --- a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs +++ b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs @@ -1,49 +1,95 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; +using osu.Game.Input.Bindings; +using osu.Game.Overlays; namespace osu.Game.Screens.Multi { - public abstract class MultiplayerSubScreen : OsuScreen, IMultiplayerSubScreen + public abstract class MultiplayerSubScreen : CompositeDrawable, IMultiplayerSubScreen, IKeyBindingHandler { - protected virtual Drawable TransitionContent => Content; + public virtual bool AllowBeatmapRulesetChange => true; + public bool AllowExternalScreenChange => true; + public bool CursorVisible => true; + public bool HideOverlaysOnEnter => false; + public OverlayActivation InitialOverlayActivationMode => OverlayActivation.All; + + public float BackgroundParallaxAmount => 1; + + public bool ValidForResume { get; set; } = true; + public bool ValidForPush { get; set; } = true; + + public override bool RemoveWhenNotAlive => false; + + public abstract string Title { get; } public virtual string ShortTitle => Title; - protected override void OnEntering(Screen last) - { - base.OnEntering(last); + [Resolved] + protected IBindableBeatmap Beatmap { get; private set; } - Content.FadeInFromZero(WaveContainer.APPEAR_DURATION, Easing.OutQuint); - TransitionContent.FadeInFromZero(WaveContainer.APPEAR_DURATION, Easing.OutQuint); - TransitionContent.MoveToX(200).MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); + [Resolved(CanBeNull = true)] + protected OsuGame Game { get; private set; } + + [Resolved(CanBeNull = true)] + protected IRoomManager Manager { get; private set; } + + protected MultiplayerSubScreen() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + RelativeSizeAxes = Axes.Both; } - protected override bool OnExiting(Screen next) + public virtual void OnEntering(IScreen last) { - Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); - TransitionContent.MoveToX(200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); - - return base.OnExiting(next); + this.FadeInFromZero(WaveContainer.APPEAR_DURATION, Easing.OutQuint); + this.FadeInFromZero(WaveContainer.APPEAR_DURATION, Easing.OutQuint); + this.MoveToX(200).MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); } - protected override void OnResuming(Screen last) + public virtual bool OnExiting(IScreen next) { - base.OnResuming(last); + this.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); + this.MoveToX(200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); - Content.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); - TransitionContent.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); + return false; } - protected override void OnSuspending(Screen next) + public virtual void OnResuming(IScreen last) { - base.OnSuspending(next); - - Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); - TransitionContent.MoveToX(-200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); + this.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); + this.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); } + + public virtual void OnSuspending(IScreen next) + { + this.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); + this.MoveToX(-200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); + } + + public virtual bool OnPressed(GlobalAction action) + { + if (!this.IsCurrentScreen()) return false; + + if (action == GlobalAction.Back) + { + this.Exit(); + return true; + } + + return false; + } + + public bool OnReleased(GlobalAction action) => action == GlobalAction.Back; + + public override string ToString() => Title; } } diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index 50a4dedf3c..36cf0f0282 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Logging; +using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; @@ -18,6 +19,8 @@ namespace osu.Game.Screens.Multi.Play { public class TimeshiftPlayer : Player { + public Action Exited; + private readonly Room room; private readonly int playlistItemId; @@ -50,7 +53,7 @@ namespace osu.Game.Screens.Multi.Play Schedule(() => { ValidForResume = false; - Exit(); + this.Exit(); }); }; @@ -60,6 +63,16 @@ namespace osu.Game.Screens.Multi.Play Thread.Sleep(1000); } + public override bool OnExiting(IScreen next) + { + if (base.OnExiting(next)) + return true; + + Exited?.Invoke(); + + return false; + } + protected override ScoreInfo CreateScore() { submitScore(); @@ -79,6 +92,13 @@ namespace osu.Game.Screens.Multi.Play api.Queue(request); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + Exited = null; + } + protected override Results CreateResults(ScoreInfo score) => new MatchResults(score, room); } } diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 45789d7892..ba5c7b2f0a 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using Microsoft.EntityFrameworkCore.Internal; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -11,17 +10,14 @@ using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Framework.Screens; using osu.Game.Beatmaps; -using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Rulesets; using osu.Game.Screens.Menu; -using osuTK; using osu.Game.Overlays; -using osu.Framework.Graphics.Containers; namespace osu.Game.Screens { - public abstract class OsuScreen : Screen, IKeyBindingHandler, IHasDescription + public abstract class OsuScreen : Screen, IOsuScreen, IKeyBindingHandler, IHasDescription { /// /// The amount of negative padding that should be applied to game background content which touches both the left and right sides of the screen. @@ -29,8 +25,6 @@ namespace osu.Game.Screens /// public const float HORIZONTAL_OVERFLOW_PADDING = 50; - public BackgroundScreen Background { get; private set; } - /// /// A user-facing title for this screen. /// @@ -42,80 +36,62 @@ namespace osu.Game.Screens public virtual bool AllowExternalScreenChange => false; - /// - /// Override to create a BackgroundMode for the current screen. - /// Note that the instance created may not be the used instance if it matches the BackgroundMode equality clause. - /// - protected virtual BackgroundScreen CreateBackground() => null; - - private Action updateOverlayStates; - /// /// Whether all overlays should be hidden when this screen is entered or resumed. /// - protected virtual bool HideOverlaysOnEnter => false; - - protected readonly Bindable OverlayActivationMode = new Bindable(); + public virtual bool HideOverlaysOnEnter => false; /// /// Whether overlays should be able to be opened once this screen is entered or resumed. /// - protected virtual OverlayActivation InitialOverlayActivationMode => OverlayActivation.All; + public virtual OverlayActivation InitialOverlayActivationMode => OverlayActivation.All; - /// - /// Whether this allows the cursor to be displayed. - /// public virtual bool CursorVisible => true; protected new OsuGameBase Game => base.Game as OsuGameBase; - private OsuLogo logo; - - /// - /// Whether the beatmap or ruleset should be allowed to be changed by the user or game. - /// Used to mark exclusive areas where this is strongly prohibited, like gameplay. - /// public virtual bool AllowBeatmapRulesetChange => true; protected readonly Bindable Beatmap = new Bindable(); - protected virtual float BackgroundParallaxAmount => 1; - - private ParallaxContainer backgroundParallaxContainer; + public virtual float BackgroundParallaxAmount => 1; protected readonly Bindable Ruleset = new Bindable(); private SampleChannel sampleExit; + protected BackgroundScreen Background => backgroundStack?.CurrentScreen as BackgroundScreen; + + private BackgroundScreen localBackground; + + [Resolved(canBeNull: true)] + private BackgroundScreenStack backgroundStack { get; set; } + + [Resolved(canBeNull: true)] + private OsuLogo logo { get; set; } + + protected OsuScreen() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + [BackgroundDependencyLoader(true)] private void load(BindableBeatmap beatmap, OsuGame osu, AudioManager audio, Bindable ruleset) { Beatmap.BindTo(beatmap); Ruleset.BindTo(ruleset); - if (osu != null) - { - OverlayActivationMode.BindTo(osu.OverlayActivationMode); - - updateOverlayStates = () => - { - if (HideOverlaysOnEnter) - osu.CloseAllOverlays(); - else - osu.Toolbar.State = Visibility.Visible; - }; - } - sampleExit = audio.Sample.Get(@"UI/screen-back"); } public virtual bool OnPressed(GlobalAction action) { - if (!IsCurrentScreen) return false; + if (!this.IsCurrentScreen()) return false; if (action == GlobalAction.Back && AllowBackButton) { - Exit(); + this.Exit(); return true; } @@ -124,7 +100,7 @@ namespace osu.Game.Screens public bool OnReleased(GlobalAction action) => action == GlobalAction.Back && AllowBackButton; - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { sampleExit?.Play(); applyArrivingDefaults(true); @@ -132,71 +108,32 @@ namespace osu.Game.Screens base.OnResuming(last); } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { base.OnSuspending(next); onSuspendingLogo(); } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { - OsuScreen lastOsu = last as OsuScreen; - - BackgroundScreen bg = CreateBackground(); - - if (lastOsu?.Background != null) - { - backgroundParallaxContainer = lastOsu.backgroundParallaxContainer; - - if (bg == null || lastOsu.Background.Equals(bg)) - //we can keep the previous mode's background. - Background = lastOsu.Background; - else - { - lastOsu.Background.Push(Background = bg); - } - } - else if (bg != null) - { - // this makes up for the fact our padding changes when the global toolbar is visible. - bg.Scale = new Vector2(1.06f); - - AddInternal(backgroundParallaxContainer = new ParallaxContainer - { - Depth = float.MaxValue, - Children = new[] - { - Background = bg - } - }); - } - - if ((logo = lastOsu?.logo) == null) - LoadComponentAsync(logo = new OsuLogo { Alpha = 0 }, AddInternal); - applyArrivingDefaults(false); + backgroundStack?.Push(localBackground = CreateBackground()); + base.OnEntering(last); } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { if (ValidForResume && logo != null) onExitingLogo(); - OsuScreen nextOsu = next as OsuScreen; - - if (Background != null && !Background.Equals(nextOsu?.Background)) - { - Background.Exit(); - - //We need to use MakeCurrent in case we are jumping up multiple game screens. - nextOsu?.Background?.MakeCurrent(); - } - if (base.OnExiting(next)) return true; + if (localBackground != null && backgroundStack?.CurrentScreen == localBackground) + backgroundStack?.Exit(); + Beatmap.UnbindAll(); return false; } @@ -205,6 +142,24 @@ namespace osu.Game.Screens /// Fired when this screen was entered or resumed and the logo state is required to be adjusted. /// protected virtual void LogoArriving(OsuLogo logo, bool resuming) + { + ApplyLogoArrivingDefaults(logo); + } + + private void applyArrivingDefaults(bool isResuming) + { + logo?.AppendAnimatingAction(() => + { + if (this.IsCurrentScreen()) LogoArriving(logo, isResuming); + }, true); + } + + /// + /// Applies default animations to an arriving logo. + /// Todo: This should not exist. + /// + /// The logo to apply animations to. + public static void ApplyLogoArrivingDefaults(OsuLogo logo) { logo.Action = null; logo.FadeOut(300, Easing.OutQuint); @@ -216,24 +171,9 @@ namespace osu.Game.Screens logo.Ripple = true; } - private void applyArrivingDefaults(bool isResuming) - { - logo.AppendAnimatingAction(() => - { - if (IsCurrentScreen) LogoArriving(logo, isResuming); - }, true); - - if (backgroundParallaxContainer != null) - backgroundParallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * BackgroundParallaxAmount; - - OverlayActivationMode.Value = InitialOverlayActivationMode; - - updateOverlayStates?.Invoke(); - } - private void onExitingLogo() { - logo.AppendAnimatingAction(() => { LogoExiting(logo); }, false); + logo?.AppendAnimatingAction(() => LogoExiting(logo), false); } /// @@ -245,7 +185,7 @@ namespace osu.Game.Screens private void onSuspendingLogo() { - logo.AppendAnimatingAction(() => { LogoSuspending(logo); }, false); + logo?.AppendAnimatingAction(() => LogoSuspending(logo), false); } /// @@ -254,5 +194,11 @@ namespace osu.Game.Screens protected virtual void LogoSuspending(OsuLogo logo) { } + + /// + /// Override to create a BackgroundMode for the current screen. + /// Note that the instance created may not be the used instance if it matches the BackgroundMode equality clause. + /// + protected virtual BackgroundScreen CreateBackground() => null; } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 54644b8d9c..71b7b77e5d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -40,11 +40,11 @@ namespace osu.Game.Screens.Play { protected override bool AllowBackButton => false; // handled by HoldForMenuButton - protected override float BackgroundParallaxAmount => 0.1f; + public override float BackgroundParallaxAmount => 0.1f; - protected override bool HideOverlaysOnEnter => true; + public override bool HideOverlaysOnEnter => true; - protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; + public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; public Action RestartRequested; @@ -166,7 +166,7 @@ namespace osu.Game.Screens.Play if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - Children = new Drawable[] + InternalChildren = new Drawable[] { pauseContainer = new PauseContainer(offsetClock, adjustableClock) { @@ -225,7 +225,7 @@ namespace osu.Game.Screens.Play { Action = () => { - if (!IsCurrentScreen) return; + if (!this.IsCurrentScreen()) return; fadeOut(true); Restart(); @@ -260,18 +260,18 @@ namespace osu.Game.Screens.Play private void performUserRequestedExit() { - if (!IsCurrentScreen) return; - Exit(); + if (!this.IsCurrentScreen()) return; + this.Exit(); } public void Restart() { - if (!IsCurrentScreen) return; + if (!this.IsCurrentScreen()) return; sampleRestart?.Play(); ValidForResume = false; RestartRequested?.Invoke(); - Exit(); + this.Exit(); } private ScheduledDelegate onCompletionEvent; @@ -290,13 +290,13 @@ namespace osu.Game.Screens.Play { onCompletionEvent = Schedule(delegate { - if (!IsCurrentScreen) return; + if (!this.IsCurrentScreen()) return; var score = CreateScore(); if (RulesetContainer.ReplayScore == null) scoreManager.Import(score, true); - Push(CreateResults(score)); + this.Push(CreateResults(score)); onCompletionEvent = null; }); @@ -331,15 +331,15 @@ namespace osu.Game.Screens.Play return true; } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); if (!LoadedBeatmapSuccessfully) return; - Content.Alpha = 0; - Content + Alpha = 0; + this .ScaleTo(0.7f) .ScaleTo(1, 750, Easing.OutQuint) .Delay(250) @@ -368,13 +368,13 @@ namespace osu.Game.Screens.Play pauseContainer.FadeIn(750, Easing.OutQuint); } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { fadeOut(); base.OnSuspending(next); } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { if (onCompletionEvent != null) { @@ -401,7 +401,7 @@ namespace osu.Game.Screens.Play private void fadeOut(bool instant = false) { float fadeOutDuration = instant ? 0 : 250; - Content.FadeOut(fadeOutDuration); + this.FadeOut(fadeOutDuration); Background?.FadeColour(Color4.White, fadeOutDuration, Easing.OutQuint); } @@ -425,7 +425,7 @@ namespace osu.Game.Screens.Play protected override void UpdateBackgroundElements() { - if (!IsCurrentScreen) return; + if (!this.IsCurrentScreen()) return; base.UpdateBackgroundElements(); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index be3fdc8d14..58e59604dd 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -30,10 +30,12 @@ namespace osu.Game.Screens.Play private Player player; + private Container content; + private BeatmapMetadataDisplay info; private bool hideOverlays; - protected override bool HideOverlaysOnEnter => hideOverlays; + public override bool HideOverlaysOnEnter => hideOverlays; private Task loadTask; @@ -51,34 +53,42 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { - Add(info = new BeatmapMetadataDisplay(Beatmap.Value) + InternalChild = content = new Container { - Alpha = 0, Anchor = Anchor.Centre, Origin = Anchor.Centre, - }); - - Add(new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20), - Margin = new MarginPadding(25), - Children = new PlayerSettingsGroup[] + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - visualSettings = new VisualSettings(), - new InputSettings() + info = new BeatmapMetadataDisplay(Beatmap.Value) + { + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Margin = new MarginPadding(25), + Children = new PlayerSettingsGroup[] + { + visualSettings = new VisualSettings(), + new InputSettings() + } + } } - }); + }; loadNewPlayer(); } private void playerLoaded(Player player) => info.Loading = false; - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { base.OnResuming(last); @@ -105,21 +115,21 @@ namespace osu.Game.Screens.Play private void contentIn() { - Content.ScaleTo(1, 650, Easing.OutQuint); - Content.FadeInFromZero(400); + content.ScaleTo(1, 650, Easing.OutQuint); + content.FadeInFromZero(400); } private void contentOut() { - Content.ScaleTo(0.7f, 300, Easing.InQuint); - Content.FadeOut(250); + content.ScaleTo(0.7f, 300, Easing.InQuint); + content.FadeOut(250); } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); - Content.ScaleTo(0.7f); + content.ScaleTo(0.7f); contentIn(); @@ -154,11 +164,12 @@ namespace osu.Game.Screens.Play protected override void OnHoverLost(HoverLostEvent e) { - if (GetContainingInputManager().HoveredDrawables.Contains(visualSettings)) + if (GetContainingInputManager()?.HoveredDrawables.Contains(visualSettings) == true) { // show user setting preview UpdateBackgroundElements(); } + base.OnHoverLost(e); } @@ -170,7 +181,7 @@ namespace osu.Game.Screens.Play private void pushWhenLoaded() { - if (!IsCurrentScreen) return; + if (!this.IsCurrentScreen()) return; try { @@ -191,7 +202,7 @@ namespace osu.Game.Screens.Play this.Delay(250).Schedule(() => { - if (!IsCurrentScreen) return; + if (!this.IsCurrentScreen()) return; loadTask = null; @@ -200,9 +211,9 @@ namespace osu.Game.Screens.Play ValidForResume = false; if (player.LoadedBeatmapSuccessfully) - Push(player); + this.Push(player); else - Exit(); + this.Exit(); }); }, 500); } @@ -218,15 +229,15 @@ namespace osu.Game.Screens.Play pushDebounce = null; } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { base.OnSuspending(next); cancelLoad(); } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { - Content.ScaleTo(0.7f, 150, Easing.InQuint); + content.ScaleTo(0.7f, 150, Easing.InQuint); this.FadeOut(150); cancelLoad(); diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs index 1c127dbdef..93ec7347c8 100644 --- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs +++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Play { protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); - protected new BackgroundScreenBeatmap Background => (BackgroundScreenBeatmap)base.Background; + protected new BackgroundScreenBeatmap Background => base.Background as BackgroundScreenBeatmap; public override bool AllowBeatmapRulesetChange => false; @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Play ShowStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); DimLevel.ValueChanged += _ => UpdateBackgroundElements(); @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Play InitializeBackgroundElements(); } - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { base.OnResuming(last); InitializeBackgroundElements(); @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Play /// protected virtual void UpdateBackgroundElements() { - if (!IsCurrentScreen) return; + if (!this.IsCurrentScreen()) return; Background?.FadeColour(OsuColour.Gray(BackgroundOpacity), BACKGROUND_FADE_DURATION, Easing.OutQuint); Background?.BlurTo(new Vector2((float)BlurLevel.Value * 25), BACKGROUND_FADE_DURATION, Easing.OutQuint); diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index d17a19a7ce..31863cea9b 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Ranking private IEnumerable allCircles => new Drawable[] { circleOuterBackground, circleInner, circleOuter }; - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); (Background as BackgroundScreenBeatmap)?.BlurTo(background_blur, 2500, Easing.OutQuint); @@ -98,7 +98,7 @@ namespace osu.Game.Screens.Ranking } } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { allCircles.ForEach(c => { @@ -107,7 +107,7 @@ namespace osu.Game.Screens.Ranking Background.ScaleTo(1f, transition_time / 4, Easing.OutQuint); - Content.FadeOut(transition_time / 4); + this.FadeOut(transition_time / 4); return base.OnExiting(next); } @@ -115,7 +115,7 @@ namespace osu.Game.Screens.Ranking [BackgroundDependencyLoader] private void load(OsuColour colours) { - Children = new Drawable[] + InternalChildren = new Drawable[] { new AspectContainer { @@ -260,7 +260,7 @@ namespace osu.Game.Screens.Ranking { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Action = Exit + Action = this.Exit }, }; diff --git a/osu.Game/Screens/ScreenWhiteBox.cs b/osu.Game/Screens/ScreenWhiteBox.cs index a513015d93..d250416b29 100644 --- a/osu.Game/Screens/ScreenWhiteBox.cs +++ b/osu.Game/Screens/ScreenWhiteBox.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg2"); - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -38,51 +38,51 @@ namespace osu.Game.Screens if (last != null) popButton.Alpha = 1; - Content.Alpha = 0; + Alpha = 0; textContainer.Position = new Vector2(DrawSize.X / 16, 0); boxContainer.ScaleTo(0.2f); boxContainer.RotateTo(-20); - using (Content.BeginDelayedSequence(300, true)) + using (BeginDelayedSequence(300, true)) { boxContainer.ScaleTo(1, transition_time, Easing.OutElastic); boxContainer.RotateTo(0, transition_time / 2, Easing.OutQuint); textContainer.MoveTo(Vector2.Zero, transition_time, Easing.OutExpo); - Content.FadeIn(transition_time, Easing.OutExpo); + this.FadeIn(transition_time, Easing.OutExpo); } } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { textContainer.MoveTo(new Vector2(DrawSize.X / 16, 0), transition_time, Easing.OutExpo); - Content.FadeOut(transition_time, Easing.OutExpo); + this.FadeOut(transition_time, Easing.OutExpo); return base.OnExiting(next); } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { base.OnSuspending(next); textContainer.MoveTo(new Vector2(-(DrawSize.X / 16), 0), transition_time, Easing.OutExpo); - Content.FadeOut(transition_time, Easing.OutExpo); + this.FadeOut(transition_time, Easing.OutExpo); } - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { base.OnResuming(last); textContainer.MoveTo(Vector2.Zero, transition_time, Easing.OutExpo); - Content.FadeIn(transition_time, Easing.OutExpo); + this.FadeIn(transition_time, Easing.OutExpo); } public ScreenWhiteBox() { FillFlowContainer childModeButtons; - Children = new Drawable[] + InternalChildren = new Drawable[] { boxContainer = new Container { @@ -148,7 +148,7 @@ namespace osu.Game.Screens Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Alpha = 0, - Action = Exit + Action = this.Exit }, childModeButtons = new FillFlowContainer { @@ -171,7 +171,7 @@ namespace osu.Game.Screens HoverColour = getColourFor(t).Lighten(0.2f), Action = delegate { - Push(Activator.CreateInstance(t) as Screen); + this.Push(Activator.CreateInstance(t) as Screen); } }); } diff --git a/osu.Game/Screens/Select/EditSongSelect.cs b/osu.Game/Screens/Select/EditSongSelect.cs index 30b5115770..bdf5f905fe 100644 --- a/osu.Game/Screens/Select/EditSongSelect.cs +++ b/osu.Game/Screens/Select/EditSongSelect.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Screens; + namespace osu.Game.Screens.Select { public class EditSongSelect : SongSelect @@ -9,7 +11,7 @@ namespace osu.Game.Screens.Select protected override bool OnStart() { - Exit(); + this.Exit(); return true; } } diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 5435b2291e..298e936c1c 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -3,6 +3,8 @@ using System; using Humanizer; +using osu.Framework.Graphics; +using osu.Framework.Screens; using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi; @@ -15,6 +17,11 @@ namespace osu.Game.Screens.Select public string ShortTitle => "song selection"; public override string Title => ShortTitle.Humanize(); + public MatchSongSelect() + { + Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; + } + protected override bool OnStart() { var item = new PlaylistItem @@ -28,8 +35,8 @@ namespace osu.Game.Screens.Select Selected?.Invoke(item); - if (IsCurrentScreen) - Exit(); + if (this.IsCurrentScreen()) + this.Exit(); return true; } diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 14f3ce6633..982a44a8d3 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Select }, Key.Number3); } - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { player = null; @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Select LoadComponentAsync(player = new PlayerLoader(() => new Player()), l => { - if (IsCurrentScreen) Push(player); + if (this.IsCurrentScreen())this.Push(player); }); return true; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 41b4f29939..3d232d9d73 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -88,7 +88,7 @@ namespace osu.Game.Screens.Select const float carousel_width = 640; const float filter_height = 100; - AddRange(new Drawable[] + AddRangeInternal(new Drawable[] { new ParallaxContainer { @@ -156,8 +156,8 @@ namespace osu.Game.Screens.Select Background = { Width = 2 }, Exit = () => { - if (IsCurrentScreen) - Exit(); + if (this.IsCurrentScreen()) + this.Exit(); }, }, } @@ -182,7 +182,7 @@ namespace osu.Game.Screens.Select if (ShowFooter) { - Add(FooterPanels = new Container + AddInternal(FooterPanels = new Container { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, @@ -193,7 +193,7 @@ namespace osu.Game.Screens.Select Bottom = Footer.HEIGHT, }, }); - Add(Footer = new Footer + AddInternal(Footer = new Footer { OnBack = ExitFromBack, }); @@ -210,7 +210,7 @@ namespace osu.Game.Screens.Select }); } - BeatmapDetails.Leaderboard.ScoreSelected += s => Push(new SoloResults(s)); + BeatmapDetails.Leaderboard.ScoreSelected += s =>this.Push(new SoloResults(s)); } [BackgroundDependencyLoader(true)] @@ -281,13 +281,13 @@ namespace osu.Game.Screens.Select return; } - Exit(); + this.Exit(); } public void Edit(BeatmapInfo beatmap = null) { Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap ?? beatmapNoDebounce); - Push(new Editor()); + this.Push(new Editor()); } /// @@ -331,7 +331,7 @@ namespace osu.Game.Screens.Select { if (beatmap is DummyWorkingBeatmap) return; - if (IsCurrentScreen && !Carousel.SelectBeatmap(beatmap?.BeatmapInfo, false)) + if (this.IsCurrentScreen() && !Carousel.SelectBeatmap(beatmap?.BeatmapInfo, false)) // If selecting new beatmap without bypassing filters failed, there's possibly a ruleset mismatch if (beatmap?.BeatmapInfo?.Ruleset != null && beatmap.BeatmapInfo.Ruleset != Ruleset.Value) { @@ -410,7 +410,7 @@ namespace osu.Game.Screens.Select } } - if (IsCurrentScreen) + if (this.IsCurrentScreen()) ensurePlayingSelected(true); UpdateBeatmap(Beatmap.Value); } @@ -431,11 +431,11 @@ namespace osu.Game.Screens.Select Carousel.SelectNextRandom(); } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); - Content.FadeInFromZero(250); + this.FadeInFromZero(250); FilterControl.Activate(); } @@ -476,7 +476,7 @@ namespace osu.Game.Screens.Select logo.FadeOut(logo_transition / 2, Easing.Out); } - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { BeatmapDetails.Leaderboard.RefreshScores(); @@ -490,26 +490,26 @@ namespace osu.Game.Screens.Select base.OnResuming(last); - Content.FadeIn(250); + this.FadeIn(250); - Content.ScaleTo(1, 250, Easing.OutSine); + this.ScaleTo(1, 250, Easing.OutSine); FilterControl.Activate(); } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { ModSelect.Hide(); - Content.ScaleTo(1.1f, 250, Easing.InSine); + this.ScaleTo(1.1f, 250, Easing.InSine); - Content.FadeOut(250); + this.FadeOut(250); FilterControl.Deactivate(); base.OnSuspending(next); } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { if (ModSelect.State == Visibility.Visible) { @@ -521,7 +521,7 @@ namespace osu.Game.Screens.Select beatmapInfoWedge.State = Visibility.Hidden; - Content.FadeOut(100); + this.FadeOut(100); FilterControl.Deactivate(); @@ -628,7 +628,7 @@ namespace osu.Game.Screens.Select public override bool OnPressed(GlobalAction action) { - if (!IsCurrentScreen) return false; + if (!this.IsCurrentScreen()) return false; switch (action) { diff --git a/osu.Game/Screens/Tournament/Drawings.cs b/osu.Game/Screens/Tournament/Drawings.cs index dae53be8fe..43b194d8d0 100644 --- a/osu.Game/Screens/Tournament/Drawings.cs +++ b/osu.Game/Screens/Tournament/Drawings.cs @@ -22,6 +22,7 @@ using osuTK; using osuTK.Graphics; using osu.Framework.IO.Stores; using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; namespace osu.Game.Screens.Tournament { @@ -29,7 +30,7 @@ namespace osu.Game.Screens.Tournament { private const string results_filename = "drawings_results.txt"; - protected override bool HideOverlaysOnEnter => true; + public override bool HideOverlaysOnEnter => true; protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(); @@ -70,13 +71,13 @@ namespace osu.Game.Screens.Tournament if (!TeamList.Teams.Any()) { - Exit(); + this.Exit(); return; } drawingsConfig = new DrawingsConfigManager(storage); - Children = new Drawable[] + InternalChildren = new Drawable[] { new Box { diff --git a/osu.Game/Tests/Visual/ScreenTestCase.cs b/osu.Game/Tests/Visual/ScreenTestCase.cs index df4af0e470..79c57ad9f4 100644 --- a/osu.Game/Tests/Visual/ScreenTestCase.cs +++ b/osu.Game/Tests/Visual/ScreenTestCase.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Screens; @@ -11,38 +13,25 @@ namespace osu.Game.Tests.Visual /// public abstract class ScreenTestCase : OsuTestCase { - private readonly TestOsuScreen baseScreen; + private readonly ScreenStack stack; + + [Cached] + private BackgroundScreenStack backgroundStack; protected ScreenTestCase() { - Add(baseScreen = new TestOsuScreen()); + Children = new Drawable[] + { + backgroundStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }, + stack = new ScreenStack { RelativeSizeAxes = Axes.Both } + }; } - protected void LoadScreen(OsuScreen screen) => baseScreen.LoadScreen(screen); - - public class TestOsuScreen : OsuScreen + protected void LoadScreen(OsuScreen screen) { - private OsuScreen nextScreen; - - public void LoadScreen(OsuScreen screen) => Schedule(() => - { - nextScreen = screen; - - if (IsCurrentScreen) - { - Push(screen); - nextScreen = null; - } - else - MakeCurrent(); - }); - - protected override void OnResuming(Screen last) - { - base.OnResuming(last); - if (nextScreen != null) - LoadScreen(nextScreen); - } + if (stack.CurrentScreen != null) + stack.Exit(); + stack.Push(screen); } } } diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index 008573cb8a..a926a06295 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; using osu.Framework.Lists; +using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3ebe9c848a..cb060a5eca 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,7 +18,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props new file mode 100644 index 0000000000..7f19c8687b --- /dev/null +++ b/osu.iOS.props @@ -0,0 +1,115 @@ + + + {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Resources + PackageReference + --nolinkaway -gcc_flags "-lstdc++ -framework AudioToolbox -framework SystemConfiguration -framework CFNetwork -framework Accelerate + + + true + full + false + bin\iPhoneSimulator\Debug + DEBUG;ENABLE_TEST_CLOUD; + prompt + 4 + iPhone Developer + true + true + true + 25823 + None + x86_64 + NSUrlSessionHandler + false + + Default + $(DefaultMtouchExtraArgs) -force_load $(OutputPath)\libbass.a -force_load $(OutputPath)\libbass_fx.a" + false + cjk,mideast,other,rare,west + + + pdbonly + true + bin\iPhone\Release + + prompt + 4 + iPhone Developer + true + true + Entitlements.plist + SdkOnly + ARM64 + NSUrlSessionHandler + + Default + $(DefaultMtouchExtraArgs) -force_load $(OutputPath)\libbass.a -force_load $(OutputPath)\libbass_fx.a" + false + cjk,mideast,other,rare,west + + + pdbonly + true + bin\iPhoneSimulator\Release + + prompt + 4 + iPhone Developer + true + None + x86_64 + NSUrlSessionHandler + + Default + $(DefaultMtouchExtraArgs) -force_load $(OutputPath)\libbass.a -force_load $(OutputPath)\libbass_fx.a" + false + cjk,mideast,other,rare,west + + + true + full + false + bin\iPhone\Debug + DEBUG;ENABLE_TEST_CLOUD; + prompt + 4 + iPhone Developer + true + true + true + true + Entitlements.plist + 28126 + SdkOnly + ARM64 + NSUrlSessionHandler + + Default + $(DefaultMtouchExtraArgs) -force_load $(OutputPath)\libbass.a -force_load $(OutputPath)\libbass_fx.a" + false + cjk,mideast,other,rare,west + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/osu.iOS.sln b/osu.iOS.sln new file mode 100644 index 0000000000..fe9741d767 --- /dev/null +++ b/osu.iOS.sln @@ -0,0 +1,189 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27004.2006 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game", "osu.Game\osu.Game.csproj", "{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Resources", "osu-resources\osu.Game.Resources\osu.Game.Resources.csproj", "{D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Osu", "osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj", "{C92A607B-1FDD-4954-9F92-03FF547D9080}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Catch", "osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj", "{58F6C80C-1253-4A0E-A465-B8C85EBEADF3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Taiko", "osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj", "{F167E17A-7DE6-4AF5-B920-A5112296C695}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Mania", "osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj", "{48F4582B-7687-4621-9CBE-5C24197CB536}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.iOS", "osu.iOS\osu.iOS.csproj", "{3F082D0B-A964-43D7-BDF7-C256D76A50D0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Tests.iOS", "osu.Game.Tests.iOS\osu.Game.Tests.iOS.csproj", "{65FF8E19-6934-469B-B690-23C6D6E56A17}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Taiko.Tests.iOS", "osu.Game.Rulesets.Taiko.Tests.iOS\osu.Game.Rulesets.Taiko.Tests.iOS.csproj", "{7E408809-66AC-49D1-AF4D-98834F9B979A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Osu.Tests.iOS", "osu.Game.Rulesets.Osu.Tests.iOS\osu.Game.Rulesets.Osu.Tests.iOS.csproj", "{6653CA6F-DB06-4604-A3FD-762E25C2AF96}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Mania.Tests.iOS", "osu.Game.Rulesets.Mania.Tests.iOS\osu.Game.Rulesets.Mania.Tests.iOS.csproj", "{39FD990E-B6CE-4B2A-999F-BC008CF2C64C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Catch.Tests.iOS", "osu.Game.Rulesets.Catch.Tests.iOS\osu.Game.Rulesets.Catch.Tests.iOS.csproj", "{4004C7B7-1A62-43F1-9DF2-52450FA67E70}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + Debug|iPhoneSimulator = Debug|iPhoneSimulator + Release|iPhone = Release|iPhone + Release|iPhoneSimulator = Release|iPhoneSimulator + Debug|iPhone = Debug|iPhone + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.Build.0 = Release|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhone.ActiveCfg = Release|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhone.Build.0 = Release|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhone.Build.0 = Debug|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|Any CPU.Build.0 = Release|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|iPhone.ActiveCfg = Release|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|iPhone.Build.0 = Release|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|iPhone.Build.0 = Debug|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.Build.0 = Release|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhone.ActiveCfg = Release|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhone.Build.0 = Release|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhone.Build.0 = Debug|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.Build.0 = Release|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhone.ActiveCfg = Release|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhone.Build.0 = Release|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhone.Build.0 = Debug|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.Build.0 = Release|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhone.ActiveCfg = Release|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhone.Build.0 = Release|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhone.Build.0 = Debug|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.Build.0 = Release|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhone.ActiveCfg = Release|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhone.Build.0 = Release|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhone.Build.0 = Debug|Any CPU + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|Any CPU.ActiveCfg = Release|iPhone + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhone.ActiveCfg = Release|iPhone + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhone.Build.0 = Release|iPhone + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhone.ActiveCfg = Debug|iPhone + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhone.Build.0 = Debug|iPhone + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|Any CPU.ActiveCfg = Release|iPhone + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhone.ActiveCfg = Release|iPhone + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhone.Build.0 = Release|iPhone + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhone.ActiveCfg = Debug|iPhone + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhone.Build.0 = Debug|iPhone + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|Any CPU.ActiveCfg = Release|iPhone + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhone.ActiveCfg = Release|iPhone + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhone.Build.0 = Release|iPhone + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhone.ActiveCfg = Debug|iPhone + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhone.Build.0 = Debug|iPhone + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|Any CPU.ActiveCfg = Release|iPhone + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhone.ActiveCfg = Release|iPhone + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhone.Build.0 = Release|iPhone + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhone.ActiveCfg = Debug|iPhone + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhone.Build.0 = Debug|iPhone + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|Any CPU.ActiveCfg = Release|iPhone + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhone.ActiveCfg = Release|iPhone + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhone.Build.0 = Release|iPhone + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhone.ActiveCfg = Debug|iPhone + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhone.Build.0 = Debug|iPhone + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|Any CPU.ActiveCfg = Release|iPhone + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhone.ActiveCfg = Release|iPhone + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhone.Build.0 = Release|iPhone + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhone.ActiveCfg = Debug|iPhone + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhone.Build.0 = Debug|iPhone + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {671B0BEC-2403-45B0-9357-2C97CC517668} + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.TextStylePolicy = $1 + $1.EolMarker = Windows + $1.scope = text/x-csharp + $1.FileWidth = 80 + $1.TabsToSpaces = True + $0.CSharpFormattingPolicy = $2 + $2.scope = text/x-csharp + EndGlobalSection +EndGlobal diff --git a/osu.iOS.sln.DotSettings b/osu.iOS.sln.DotSettings new file mode 100644 index 0000000000..3f5bd9d34d --- /dev/null +++ b/osu.iOS.sln.DotSettings @@ -0,0 +1,815 @@ + + True + True + True + True + ExplicitlyExcluded + ExplicitlyExcluded + SOLUTION + HINT + WARNING + + True + WARNING + WARNING + HINT + HINT + HINT + HINT + WARNING + WARNING + WARNING + HINT + WARNING + HINT + SUGGESTION + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + WARNING + HINT + WARNING + WARNING + DO_NOT_SHOW + HINT + WARNING + DO_NOT_SHOW + WARNING + HINT + HINT + HINT + ERROR + HINT + HINT + HINT + WARNING + WARNING + HINT + DO_NOT_SHOW + HINT + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + HINT + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + WARNING + + WARNING + WARNING + WARNING + ERROR + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + WARNING + WARNING + HINT + WARNING + HINT + HINT + HINT + HINT + HINT + HINT + HINT + + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + WARNING + HINT + HINT + WARNING + <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> + Code Cleanup (peppy) + True + True + True + True + True + True + True + True + NEXT_LINE + NEXT_LINE + True + NEVER + NEVER + False + NEVER + False + True + False + False + True + True + False + CHOP_IF_LONG + True + 200 + CHOP_IF_LONG + False + False + AABB + API + BPM + GC + GL + GLSL + HID + HUD + ID + IP + IPC + LTRB + MD5 + NS + OS + RGB + RNG + SHA + SRGB + TK + SS + PP + GMT + QAT + BNG + UI + HINT + <?xml version="1.0" encoding="utf-16"?> +<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> + <TypePattern DisplayName="COM interfaces or structs"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry Priority="100" DisplayName="Test Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern"> + <Group DisplayName="Fields/Properties"> + <Group DisplayName="Public Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Public Properties"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Internal Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Internal Properties"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Protected Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Protected Properties"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Private Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Private Properties"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Constructor/Destructor"> + <Entry DisplayName="Ctor"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + </Entry> + <Region Name="Disposal"> + <Entry DisplayName="Dtor"> + <Entry.Match> + <Kind Is="Destructor" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose()"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose(true)"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Virtual /> + <Override /> + </Or> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + </Region> + </Group> + <Group DisplayName="Methods"> + <Group DisplayName="Public"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Internal"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Protected"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Private"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + </Group> + </TypePattern> +</Patterns> + 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. + + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True + True + True + True + True + True + True + True + True + o!f – Object Initializer: Anchor&Origin + True + constant("Centre") + 0 + True + True + 2.0 + InCSharpFile + ofao + True + Anchor = Anchor.$anchor$, +Origin = Anchor.$anchor$, + True + True + o!f – InternalChildren = [] + True + True + 2.0 + InCSharpFile + ofic + True + InternalChildren = new Drawable[] +{ + $END$ +}; + True + True + o!f – new GridContainer { .. } + True + True + 2.0 + InCSharpFile + ofgc + True + new GridContainer +{ + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { $END$ }, + new Drawable[] { } + } +}; + True + True + o!f – new FillFlowContainer { .. } + True + True + 2.0 + InCSharpFile + offf + True + new FillFlowContainer +{ + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + $END$ + } +}, + True + True + o!f – new Container { .. } + True + True + 2.0 + InCSharpFile + ofcont + True + new Container +{ + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + $END$ + } +}, + True + True + o!f – BackgroundDependencyLoader load() + True + True + 2.0 + InCSharpFile + ofbdl + True + [BackgroundDependencyLoader] +private void load() +{ + $END$ +} + True + True + o!f – new Box { .. } + True + True + 2.0 + InCSharpFile + ofbox + True + new Box +{ + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, +}, + True + True + o!f – Children = [] + True + True + 2.0 + InCSharpFile + ofc + True + Children = new Drawable[] +{ + $END$ +}; + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True diff --git a/osu.iOS/AppDelegate.cs b/osu.iOS/AppDelegate.cs new file mode 100644 index 0000000000..97621e1e52 --- /dev/null +++ b/osu.iOS/AppDelegate.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Foundation; +using osu.Framework.iOS; +using osu.Game; + +namespace osu.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameAppDelegate + { + protected override Framework.Game CreateGame() => new OsuGame(); + } +} diff --git a/osu.iOS/Application.cs b/osu.iOS/Application.cs new file mode 100644 index 0000000000..8a5cfcdbe8 --- /dev/null +++ b/osu.iOS/Application.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using UIKit; + +namespace osu.iOS +{ + public class Application + { + public static void Main(string[] args) + { + UIApplication.Main(args, null, "AppDelegate"); + } + } +} + diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..bbd5f4f3ba --- /dev/null +++ b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,249 @@ +{ + "images": [ + { + "filename": "Icon-App-20x20@2x.png", + "size": "20x20", + "scale": "2x", + "idiom": "iphone" + }, + { + "filename": "Icon-App-20x20@3x.png", + "size": "20x20", + "scale": "3x", + "idiom": "iphone" + }, + { + "filename": "Icon-App-29x29@2x.png", + "size": "29x29", + "scale": "2x", + "idiom": "iphone" + }, + { + "filename": "Icon-App-29x29@3x.png", + "size": "29x29", + "scale": "3x", + "idiom": "iphone" + }, + { + "filename": "Icon-App-40x40@2x.png", + "size": "40x40", + "scale": "2x", + "idiom": "iphone" + }, + { + "filename": "Icon-App-40x40@3x.png", + "size": "40x40", + "scale": "3x", + "idiom": "iphone" + }, + { + "filename": "Icon-App-60x60@2x.png", + "size": "60x60", + "scale": "2x", + "idiom": "iphone" + }, + { + "filename": "Icon-App-60x60@3x.png", + "size": "60x60", + "scale": "3x", + "idiom": "iphone" + }, + { + "filename": "Icon-App-20x20@1x.png", + "size": "20x20", + "scale": "1x", + "idiom": "ipad" + }, + { + "filename": "Icon-App-20x20@2x.png", + "size": "20x20", + "scale": "2x", + "idiom": "ipad" + }, + { + "filename": "Icon-App-29x29@1x.png", + "size": "29x29", + "scale": "1x", + "idiom": "ipad" + }, + { + "filename": "Icon-App-29x29@2x.png", + "size": "29x29", + "scale": "2x", + "idiom": "ipad" + }, + { + "filename": "Icon-App-40x40@1x.png", + "size": "40x40", + "scale": "1x", + "idiom": "ipad" + }, + { + "filename": "Icon-App-40x40@2x.png", + "size": "40x40", + "scale": "2x", + "idiom": "ipad" + }, + { + "filename": "Icon-App-83.5x83.5@2x.png", + "size": "83.5x83.5", + "scale": "2x", + "idiom": "ipad" + }, + { + "filename": "Icon-App-76x76@1x.png", + "size": "76x76", + "scale": "1x", + "idiom": "ipad" + }, + { + "filename": "Icon-App-76x76@2x.png", + "size": "76x76", + "scale": "2x", + "idiom": "ipad" + }, + { + "filename": "ItunesArtwork@2x.png", + "size": "1024x1024", + "scale": "1x", + "idiom": "ios-marketing" + }, + { + "size": "60x60", + "scale": "2x", + "idiom": "car" + }, + { + "size": "60x60", + "scale": "3x", + "idiom": "car" + }, + { + "role": "notificationCenter", + "size": "24x24", + "subtype": "38mm", + "scale": "2x", + "idiom": "watch" + }, + { + "role": "notificationCenter", + "size": "27.5x27.5", + "subtype": "42mm", + "scale": "2x", + "idiom": "watch" + }, + { + "role": "companionSettings", + "size": "29x29", + "scale": "2x", + "idiom": "watch" + }, + { + "role": "companionSettings", + "size": "29x29", + "scale": "3x", + "idiom": "watch" + }, + { + "role": "appLauncher", + "size": "40x40", + "subtype": "38mm", + "scale": "2x", + "idiom": "watch" + }, + { + "role": "appLauncher", + "size": "44x44", + "subtype": "40mm", + "scale": "2x", + "idiom": "watch" + }, + { + "role": "appLauncher", + "size": "50x50", + "subtype": "44mm", + "scale": "2x", + "idiom": "watch" + }, + { + "role": "quickLook", + "size": "86x86", + "subtype": "38mm", + "scale": "2x", + "idiom": "watch" + }, + { + "role": "quickLook", + "size": "98x98", + "subtype": "42mm", + "scale": "2x", + "idiom": "watch" + }, + { + "role": "quickLook", + "size": "108x108", + "subtype": "44mm", + "scale": "2x", + "idiom": "watch" + }, + { + "size": "1024x1024", + "scale": "1x", + "idiom": "watch-marketing" + }, + { + "size": "16x16", + "scale": "1x", + "idiom": "mac" + }, + { + "size": "16x16", + "scale": "2x", + "idiom": "mac" + }, + { + "size": "32x32", + "scale": "1x", + "idiom": "mac" + }, + { + "size": "32x32", + "scale": "2x", + "idiom": "mac" + }, + { + "size": "128x128", + "scale": "1x", + "idiom": "mac" + }, + { + "size": "128x128", + "scale": "2x", + "idiom": "mac" + }, + { + "size": "256x256", + "scale": "1x", + "idiom": "mac" + }, + { + "size": "256x256", + "scale": "2x", + "idiom": "mac" + }, + { + "size": "512x512", + "scale": "1x", + "idiom": "mac" + }, + { + "size": "512x512", + "scale": "2x", + "idiom": "mac" + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} \ No newline at end of file diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dfcce1297b8ad9b84ca073334fb12d949c51d250 GIT binary patch literal 1237 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 Y2YK{MGIEg$@&Et;07*qoM6N<$g67-*fB*mh literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..8a21d9f81d2a2c0f6b8658761476e32ad36565f2 GIT binary patch literal 6457 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 T58IaJ00000NkvXXu0mjfwzQG) literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4477ece73a546a35bbb5abc8fc20c1645683e607 GIT binary patch literal 2250 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 literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..0f8c36a03870c9e7c0561fe0ca1e80d9fb7446a8 GIT binary patch literal 10842 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 literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..d563e8cf5e44bfb5c1dd733564da90f9379defdb GIT binary patch literal 3602 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 Y2YK{MGIEg$@&Et;07*qoM6N<$g67-*fB*mh literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0208f80e3bc60c86225146f78a3497db63738b23 GIT binary patch literal 9677 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 P00000NkvXXu0mjfGd9th literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f7ad8bb6bbbf3adf4610e6d613db4057bc46eb45 GIT binary patch literal 16681 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 BAs_$% literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f7ad8bb6bbbf3adf4610e6d613db4057bc46eb45 GIT binary patch literal 16681 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 BAs_$% literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..3d85a133aa71f1e744c51c96acfda1d19f9edfb6 GIT binary patch literal 28381 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 literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..edd53bc9542a7dfcb242f917db19752046a120df GIT binary patch literal 8946 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 q1s6C9c^_cU&A_fDRS@U>S36fzJQ* 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 literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..edff3788f08b1e096269fd187f08fdf65594dab1 GIT binary patch literal 25862 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 BVm<%> literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c04f6f3ec4965f7b06e29a1bda5b8d5fe27ba72c GIT binary patch literal 251219 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 ihBCBv)8__?8cC&XLrk0D^;$L%D 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�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 literal 0 HcmV?d00001 diff --git a/osu.iOS/Assets.xcassets/Contents.json b/osu.iOS/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..4caf392f92 --- /dev/null +++ b/osu.iOS/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/osu.iOS/Entitlements.plist b/osu.iOS/Entitlements.plist new file mode 100644 index 0000000000..9ae599370b --- /dev/null +++ b/osu.iOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist new file mode 100644 index 0000000000..fe711fc03c --- /dev/null +++ b/osu.iOS/Info.plist @@ -0,0 +1,42 @@ + + + + + LSRequiresIPhoneOS + + MinimumOSVersion + 10.0 + UIDeviceFamily + + 1 + 2 + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + UIStatusBarHidden + + UIRequiresFullScreen + + CFBundleIdentifier + sh.ppy.osulazer + CFBundleName + osu! + CFBundleShortVersionString + 0.1 + CFBundleVersion + 0.1.1 + + diff --git a/osu.iOS/LaunchScreen.storyboard b/osu.iOS/LaunchScreen.storyboard new file mode 100644 index 0000000000..3655e9f2f0 --- /dev/null +++ b/osu.iOS/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/osu.iOS/Linker.xml b/osu.iOS/Linker.xml new file mode 100644 index 0000000000..04591c55d0 --- /dev/null +++ b/osu.iOS/Linker.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/osu.iOS/iTunesArtwork b/osu.iOS/iTunesArtwork new file mode 100644 index 0000000000000000000000000000000000000000..ef7441433ad414ffc56fd513e5956d279aef8551 GIT binary patch literal 99849 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�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 ihBCBv)8__?8cC&XLrk0D^;$L%D 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�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 literal 0 HcmV?d00001 diff --git a/osu.iOS/libbass.a b/osu.iOS/libbass.a new file mode 100644 index 0000000000000000000000000000000000000000..c34e6a0a0cfb30a240d70443fa0276f6e2774f90 GIT binary patch literal 1717480 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

n1vJMuGM~kIoXjExl$^E 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 PzG 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

h`#jTBZQRdTM3sKKP!c16W2fH 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 ydCELbndd3bprQ_nijA<#w z2|vn@8c1|qlxAABy6AS}R#(^7HYy5Q?b6D&)@q$eItGI?rCq!0wq5x=U-w5c1kiRr z`*}RRe|*E^a_^jbe!kE9_j#XlUwr?`?;B$(xT?H+##k#dJO{kALB{%PW2|{|%hCp$ zz{_(<+v}xOau1r&SvF;-E6p;-O{<6UJY1!Nub*zT|w{zA!$X7w+rv@%Tj(CXjZ?MPBAqbpHHv=Y006sh3}2 z&-shl^_Ndo6C3p(e|Is8k9zmX-u*AV`{%v;7VrL$cYofyzv#3>bv#TRuT$ucUL6mP zwagjh8(db&{Q1{^;l`W$2Q#pajVD>g22ihly(A{`tIn zulC3~%EvQ*{^!52aQ+QIWB%eV-grG@nR;pcrG5*>yiOgOwsEJpbz~Wf=5J7c7RubX zeE!W}`uufYSU!Kn%A1a=BS#(8^sde{WIW}^ZLQ;ELg6Tfpys6Ws39_Opf3frw)zn z$hj3vp@+{O8>SiR5KQ-Zb&RDB!8FR{|IMF&-OV@qKe!LBQQdGE{TQ}y_N8q4QD z52x||2G;Q)b;P}Q$E!o>HM(H_bGXNrT4q()9dFVrt2T8F<2sS+Qm*6u{c(w9DE?>v zwIt4YJG5eJSJl3&y8dwZ@IjNv<^zMW-S6?-5^paUJDU%h+k5IJX7iR=(Pd2bg0&~i z>9$N}_)5Ju$!@&|=b}qv?S-YLywkG3`Akzkx6jyr+W~rm?daOz$g(;@g<34D+;&E5n>@ zGWxK=a-N5JZQpMe4O0kBD(iQ3VNKl`O z(aCo*gs%PK0|0I0BJ(awUw({D5nUF*d9rJ+*%@4b)TZS0^jTHZ1-;{V{;Efrz1^sS5d!t7czGgdU;O9!~<^6O*YL1;r5k`H$mMA z(p7f_X{NKaFhT8+FBpec#J3hgb~YaYuA;lDL-!P7jMrFC9XKS~L-ucBTuZr6VDL$v z`^Lt&Y7#r4`K<+wsV?4Du&Rc4FJVlB80&KyBYf(5;R7 z&T`rAp`;J(q;DynTeA7jFy}{NyzL~@@RL*5p5GMfNlzGc zU;5Tjiy9A&s%&UI)rzg{YH6$-I;L^Mr~rA#G+sW+O22aI1L=!K8SbX>*;50Jubf(u z{>xFpbnB@b(iiidG_El3D;uvF6(aqC^ev-;FH#mfZF6W|crHE@Oz(gBf%NVlfs-#IS6)2Pd#`uUS3?zTB;(D6Vd>_g#vGu% z-ouBVR9jZ=Yh9tvEnUXmSqPZV<^SRE;YneW%tw$B!Wqray~e=zz^VAog63p2FxLF- zIDELbih0+Z3%9R!{(d*bpKF`Bwo>jKI0oTP`ALoL|dOyWAjY~9& zW!o#<)_8{4+5Chd=Ah~-Pa8GV98{a-X%`GK2d^8D_N9n9_*VncuCFi$F`SPmI~Y1b zS2!Z=>?(6mcv{}ZzXr|0D+i?Qsx$|u4oEvYYz}H%W#RsAHPV|6;c-1|HKs+0?;@RQi zO$*^oYX2Yq+m+eh8`95gzV(uW(2_ z5nZ=8!~O5bt9)_tEAKaL>av>);+gd0h%)NWQu-qr63r~6FB7>sJUwD8^wOzzY3F{*ujq zst&rO@5>oOkogQS{%{=m!q}^LABx@cs}Q_P{g@eJEcRtxmVG<hFxG9aYqW9lwjf+|E$8xe#{XA(>A1mVZFB-Z<_J5o zHz|D=-82}#EoZ@xBe5oQ)+YFN3wU)~u8QXnm#yDsUjDc6=?~LkJEQ%$emtGx?LYSG z6b}RY|opg$Sw${x}HRqUuP!J&E4l zL7#+Q-{#%|Zus&^I@Ymt5qyY_F7i$COBb2QZ{fSIi)|k?vuo z)_2XB?CKwba+%l(=oF^$>!&8sL8?y74hzQD#>S){{hs)E^*?+ORy;yne8KslZ z$tN=}7P`<#U@l$B(aSeKUfPe{P_YcV09{^syxvu=USHbt5ITRfwsbD#(3OgQA6uf4 z5;jv*`Swuuzz*7{{EJSxGfIQ5-B3CS8^NZW+K|m48^cP!TnwQvokU+lW%WIPZuvlZ zFFNAOr{V{weUx#8d4GWZPir`YU9qPWX_#KxlfJkVNv|p~=T0B@tbT-kp5fGOs9ST@ zaXviI%$7bDRo@T)0o;vNmLlfr?l5}O2HFP3y|UM(?|+O-I?+X3(i3KIE#zu#e6wgZ z?ksL^c(b^j{`Bm+rF6!QTT0Jwx~Q~a(~{EtJI*SJ|7_viMr>HC@r~k!9qq*>yKXJ5 zCv81>zq+HcwAjmk<*tqrwAO3w*jZdd-G4)#D|bCYonLb5dx3na?-tV4?#<-cuw!iL zi~R0uch}g`7x=CASL}MTSWo_Cw7pb)P46tO-?gN)n0C(CacgN0^)24fQEJ?DsH8Ee zf1ji5eDW{eadAm`H_*o`H@SIkpd7|luhsBIv7Y*_Cg17lu};1-$iHXTMWv1DbxwVL z{n#kIR|22u4IL#jV`nkaII*-hePL-)Ggg*%H+-=)rU4jCI#gPho=}SH7*k5n zhw04Ym`T$}yRbBR*W%Lj#*R{C*O<~o-aXJ*!F*3It>3Y-^sO|o+;pJ>AL+N}s;wD^ zN~U3F(QV_x(sK64*0{sH7J@_UnoZ)7fW z;rm9w-5Dd1Wj?LWW(*Dw@O&lMEl*CyA0eGecwHGs*28C@xgGK!I6TGUHRO#}dwE3% znj85^eBTHDqx(sBd?S7uzNP!IGj`>WGin3>Wv|Nkya9a+|3&|Awef_PSNN?yoIyK& zeY~IH`*j%GYx2jr^&;cT{8Qp}@=qCjCI+9$KwnA~=~bl1xW~9x%eNBS#vMOPEK|4# zUOkbzRgdZ~uN&RQt=pR)$;DpchWKQI_^5^9&#^Lys{tTbh z{4yVvd{*sULnC?bc>LWDbPer^0`N=}a*;siGwR>lw(nP{WUlSEQdP zY3@P|oyBm&w&I4yG4O~*rEo*0cw%G5p@VAf!_%GM*rL)&+%@0Fo@>#d5#2$9&}HVk zm&?EOg%g`5eb2(z&sD|c&-ec;y|y0jx344E^0y}FFLuo21UMeUe}!&76;qMOG;lo7yCYB?kX_G zZiCG@g7qZEnn_F(vuSOy^c~ z#Q!d}Od3%#=K9<}(5G;GM-RTZq8ZKxvRm$)6`(Ab|h69IyT-yhHp- z>owKj_3`AC{YN+!z7KaDNH${z`#|dnA1vPv{Gq?xCCsPze2eD~3W1l`Ak(zYBAoYS zvW;Bw;cat##cEj0KS6fT4ImCZrjr(67*0}!-d=sT5jfWiDLA-Yi_Qeb2F+A(tCfV?`r**SAv^CiF z*WDg3Rl|4Ycw=(=6OE9|$zA8S}fUnh@MSn`sS?hDv zoo$7Y2Hq=PM%|CWuOjeS@i+O3B&#(Kw*q7FH?3DhQvK@{-ynY+T-N$rIe%k{P3p35 z4iOIb+dXxOKHdZ`@nIwT;}d0k0G-iU)Aip|N#B}oR6L-pEMw_=hx|Q4?-g&5PI)}u zARZ@MKz6|KY=`o-;b6M+AAB1^yrTsjR66NmbWc6Yh7ey~?5uZ=DM{zm+LC1D&=1r% zGgA)z2eEfteej{)KXncNPmv3eBkGp#D1&Z+Oo0B?=c#t4FqtdBoDbwxg46!r_0wxP z@0ni4dmj4FKaclJr|qv(rkwZe;CDIix#@p__pGAtul^_ehx=6U@;;t@IDeV;G(Mjf z`C}XD^PyxvA6n+|p}*ER;X_OK9R^-LA9@P8+1SutY;1V5Xj-jTb@X?{M40=(&O^kye8ia4iei0wh`la}Wbg1Xy6XG3;eLz>cd}^xsYIK&% zrpB8uS2{AdAqL-&PGs3lU6PR#d7f+|s~n!Q`(^Qik747%e~O7lH|Aj!ZQeVjDK`~f zn#eR4o+XdU!H=Gz-KX^K**HGg^pfw>x2L3EeJj1^`(x6#@_YAF*qgnM-q_eyRQbuN z=E6Gg{##rd@XxJ>7cTPn?4tCc&uFcG3A|O~9>X|I?X!v44!``5!v{wKccn?!mfiMq(mqIElfQjpS-sN96if3% z%1xw>7r4vD*u%ZtH!u4_Ft^C>^VZSu&+?d+kBavW@GHsxAe<8pp*J}E*Vh4um+@cp zIY$R9*ZBtXewy^P{&~p{q3;%<`H-0R?j>F68|6&i{ zz=7HT@iCGaetZn_CaSm_+0y6@tf90yF*m~D7(PbPn${f@1M@t*-1VW!CqC<)n}F$U z=9+HTcRkjPb4fo}l)Nn+K3qFlJQbdJEd5+MpX>mCy_I|ku1>ho4j!XfV%ix&l=1O81YUoY-@;*o4{5#XBaZ>EE!u(KKo55m zV(86*@oRgeH-|L0$UOOIM8A_`t{x}-*Izd+kJI`f{GPxVJKu4AMH|6!AD$8Me(Xu! zI6OW@zdj6J^{U5{9pdpSJKGr>^v}JVxBLDh#j!?`e~WHcSqNlV=g04-Sg;u$4P7R9 zKtosXJIeU#V$-^+@Ed+jI!#J+dq?5QP1sGqEQk+7evl#iuFnn0WbkfA}x0m#>GYyOh;=^pc6YRj$LMk z=Ox#XNvARoC(7;`Lp<0Z+J6^s#>@jTTjk%VyDn>bRRmA7Lm40v3nDhqhoTj4s zp%|-f@~RIZ+Lp}{p)VTCSbIbvXy2Ao-|Ki*-{s4Bnf%c*J10sR!RiyhL3C0_zG%wT z8wATIpp#?yap2j<#OXM==HPTPn=iZzToTzl#wDc-$FsjqV4RGJ*qN=!-)86#f2G~r zMLa{-lk{aQWflLRu~wVrWAY$BHf9C=n!x4r7iT>k+fF!bSmRPZvGwk^%+2>!(XSvn z!WGg>`u#{VoVaLwL0adQPU6aaSMSldq?f2K(swW6z5HRm-h$utSz@f8V=dCrkIK`D z!G4Z8eenseyx?)4UC*R~7S8TP`2uB@0&+(O) z|Lz-83-($L+l_imXSVx9r(XE>iNwbuvn4ZZXqGsUT%@xJ8sq}!_0CVPA}z$F7<>_I$H`BZC61*b+PPsOlQkuU$?9d=IUN}We2f% zkD1Qf9y4~!WAvk=%HGa%(_^&%81_;JaVwAEuXfW+%VYTY9|MOvB0L+@#B=sL<@T=R zhRJ}BjMeIZk7wbXhAWlLpU=33M|0w?-)t;!uSjlYUjz9Y?q}$m%_9Jk|MuUG# zSE&^Ef%M{1fZwO5|FTqpZX-R4xZh%+ zVM|eE2X$`ARgz{J-QTKb39$g7+&zV=+!odZ$fLPHACmqqe)9%#X{K>qQFP$Y4K^Wg z3tE|tT_JR&sF|C85gpxM+wpmY)^5Zn!U^FUV>X$bjjR=3g8$Jk{=2)DQ^z`51Y9%b zPTIR#el^%svilJDznnbW54_D>`gr8~G-GECDn)m^Q5?K!r{h0Q?0BryQd^17C|t63 zbUN!3@IC0Yxe(ZtDbfcg&PQ=w!db<3DXwcI*C*+pVrvp!T#jP$%j0oo(2v9p*REHb z!!Y{42Rr6%@?Gea@ndl!%QiSR%_k`H68xatrtxEDguCcWwXZQQ#n&j_M(^t2o#G#N z#o7xW!zVr_eM6t0@=WS8#GI%;#o1_&z?dC-N*jpr7@Pi5>A_8x5HGTa`QBX|Mc(1m z)e@~J2`7}U{Rl76mYyqke%pJlIc#QHUp2Yu>wK~A{h;(aku{zdPXt~hNfg4Ug1rK)(t+_zH z13ujU9G}Xcvma>!)a|!nGly{-I56JUK!Cj$ErkvA`@?kXLmFY3j;(TEqFe~r-O9a% zarp50|Mw*Qe|Jw(6?1hza!Nesf3hd(kH0y3Pmk z8OwXn&;B=DU(WY<_IcCs;(!-m@@d0j=OhB`tS$sPdbVHNn_H_{Yd+h(0h;ApH$D<0DF@z zuL6DpedXUq4-J?3%Ikn<2AKEh&hR~7cVwuWS)2zoSFu|Axc<}eqiIj8 z^bqZH9SN;C{;D#2S~5v(H0^!i^YQt+r$oxy_%PY9|FwR6HeojoD#V@-^yR#j9NuMR zq1Tjr4ko&rwz0kX>?v&DUikptI@C|gALOT%4lUZ14Es4{M9UTYcIEL@Fhgs~_#5Q!*|iGzAI}dW7|KUs?Cdb?&Eb)B=MdSr zvPD#fKYnLlLwwswb@AIy3ORmD*_QL1m_FgY=-_zxzw}jOB3ymCPN#g4^VH_>P_E^q zQZ-^gs*RJJ59qQ4b zzXzDP?Pb4jE|SL1PyeK7jbE43*yMv9lO9t--W9dS6&i#OKLf8$cB z)(CFuO98p>%Nn)s)6<*Bpr^5pPLDq%{wZva+4<4X(Kynoa-5g2tfjd9lcRIN)vd)n z%N}*sCc)t+8%G9NygQBkF4)7ptwXDF<6ClkQZKwt}k4N8Q z_xIQJVYKnbSB^~^<#A*ag(u#=_ zu`)^3>FrTljNVX%tPk1QynGoatY)PMnH6`&d%LqY{&>D@7mA0bJj|FLW|HS}-w2(Q z(>^?km@fET5?aMa9cE2WcD3dyvHCFwcPu*_l&|D~(`I{*l}CrFKp*I*Ih-vxbvLtu8XZJOt9A#Ch}V`KF7kDzHc# zx!(P8nS20dL+nGX(1lyC6%Pj34?Nt8j}9<@ z;ytvzvk>d1F3Jy$&B_`38GMrLd8IDll>BKYK zkLoN0^jX%hIOBl#-g-&xkY#Pf6IQnsRc9r9W(c(KBy|s_UezZbl41>99(zr`2OcZG zrsDOa>uDWJ@YxJZeLfq_^z+&K;j`s&dJ~!RclY(vi{|yE^WixObW8C5qsd^GbeETA za~4UKIC|RciqW}9M@!CheuFhd;tA=?O!n6%?ItI$%4uEp zrP;*pd*xK79(g&Jd@r5fR@fJ4E3}d}k2K#;`|!Ua_rwp``-Fd)@#!oZ?SWkj&bw#X z$e(Zh)^hS$kzw7|#HQia#Akzwb8~pA8HSC}G&~f4HaOOi2l%Zia|AvUA0z*re12Lt z5ge>6IzDr$SZ~ogYe&6Sd=CEqrpwqnr86CB?*iAP9}J{3rI}bx_};6%jji?>xtWRe zU2)TPU@-pUcMc!k1j~XJ>ht)GH_g*}4(pwhkaMaVJvr*kK|I*ymLChOHX;jK4(ux) zKKK#r0MU`)jGWDdV^8N^B)#5V-&JL{AE@EIpFhC+An%{yz3hjXUOj%fip^t7-}T;A ztln6fcK0Qvj}Y(j&!j2leWdNuj)ND;Y`%kZlc>*G$>(yC?P2EoM8+xo3663UEY$y) zxy|Xjcrbas1O9wRW$I(=yDH4K10m=&Y(JBW#m(&1TKeuQj%*_er3zPycLPdVoRz>ko}ku{7*a(N?su*36*N+%2_ zvKDdd(S=_Ao5(j$W3(BgY4535A7kx3&|h!h(rrCj-wl&r@=oUppuV5Br{qP7u&mZ?-&_@&4yownc3r&o@puG^Qjg&N2lYs{$w-o{@@!PBMF|nV7 z_h*;2hpyJEvkuOnPVxLl$>W@PaK&)vteqTtanC5rFMgo2rUZk(S6S}TB~EtA&yo%G zqG&~R(cY%}D1Ulc`7f?x0X1`P9cLaHHkh^e(7bhj(n8cL z+i74wM$!jsVnx@1)7r7mS^LRVJnhWyVg^R#2CbYdDDRtA^{{7meA?wVdl7 zQ#a-M!@uZ^^SnkpiB3*`0Us*YK6uq$t`@G1Tr0SC??t!e>Z40CER;;9(8R=FbFX+V>*bo0@7X zoX&6Q3f16|=tq0@Y$uJqe&AQsG#{wSHWy+YtWU`g~YFUcB-uSh3>CE&nUU(a`nwWe0f+D8Jfah=+x=z%(Bzf|Ae=P+hlX! zv9j5FgY5aB{o3!*rpA0aKt&r%M;5E!pB62emhFx=p(&V3nnR39YrL z{W}Yxa(W?VKxYcM^irP(KF~`u^s*j$Ar=OD`75Un>s@-u+*$Z6zkPZMJp#^J8+&BC z&LREq)Yl{nYsb=#&JU7Iw5(c(_lcGwvH|HwB(bjWDD&;aDrDMw#Me#dU96GrQ7ZpAPXF&L z3{GTv!pzl~{2tC6YTvFuk0H`4=$G(b^Xt=||K4CD_t7zP@b0tlKn~{G-Ri=LD}iAK&p zIg&D_0~|2G30x4}S9NSG{73KMgZs|Es}N4OcAc;D4o0729(IM}ZTT})ueqxr826Ij zryrZX1Om0)iZ^$4^t%dOypLtKcHP&3oX^f6E+v&iSLhu~EMkS79Y5I4o~F8F8~;po zq3=MuPgamme2kcr+6we;lYFjA<$T@SX+NILzvz`Q3B(&JKeXo9g|Wm&*8ev;wBE#C z;p5GN=1F>2`CP=t5cBo)k-B0B?MPqt^=a|ShrG1D`ATN_wjC@=H6!R`MsBC z;lNnN5Tw0OqV-pb?HUa%J5(mg{F2Wk*B0)etaO-G?k?TV$;(#*ZM*Vm0e$vpE=+nT zCOwip!Cw9f=)9R{=?2r`4~pH<8CIhEFO!}i&5Ag*L|j>0;Y*|m4!3f5c(KP1IOpd{ zUteG0y;}ubs`ubq=SuMWVCRweIu%?Ef;+#uu)PqExjI`7yyI1V2WUfdE_xF`*{ie; zSC7*^QA{);uJ?KCU=Ke!HRS+Vj1P@k(~*xPef{9ZR!jDD+(Xljz|bmFnxd6=8e15>}xqpTzQj54{& zALj_(jb&`oS2?=cnJ2Y9lzCu{%+V*uWu(ud|0b7V3up5$uqQiaH#_|anzpyC%q; zKQo2C**76)ySpYcKMCe%0`or+II@=3J2lr?(Vm zGwunRH+wd7@hmYh?mPcCbvk(ior~cu%aV2L2%BZE_@Hb+H3+2$jVLL+&AQ3V(zuZC)l(Dn~}iU zl5c=D1bACidV=0-jZkX^tBoU)OovcYFr z^FtOmJi@7ix&tA9T{od3a7dDq0t`xsjMZ z=8yUM6zfGAqu-|xeToBL^(z?P&e;W=MIqmw*C+I9?ATcj{iv==zb@q8vroIS%C9Tp zjZg8Dao}EMS`TD~eKn{4RhhL1)Ia$Q&`TZLI0?Kp4(T8A{b~=H)+?>pb)0J_yAM7B zK3+jSXTM15nyz4SU5+|x1;fD|OLLLj*K+Y3_~ncl|8b0T=I;{WHgabQa>q&QNI}PI z9a~O3Y`pjWeBqnNQ#7|ePZ0~~l#^c(W} z9H!4hz41CUh&+D%S)qG#PN5HMhg(S;p~KwibrN2hj=p;(nE9TEI9mwNHL} zcAjih(}AADoCXr$YzkjwcDtv;!Gr0K;2+|>XfgsHcKBOTI#)J-F7e)Bn{@EqWJSB; zeRmhmp*^Q8`NS{%^0K>C&Y2g|Rp(gJgj40??34Y-xT;R(vXi!xV|JB5U$-D&*ozdZd~oOWluQqDAVrd?6l2imlB)3bspxw=Alo&qj}Vx z{V!9u{GpM}>q}LfMK^-9FOhcD>Wht`nb4bJ*$<+9UDQ@!=dPp$6w_GPO( zzF)DLGt``TmS>BSHmfxDQ8T?KH z`=RB&QRYEdsK3AdMD%C(u6Se0^nAMd+tc!W=a9K?_a)qW?g!o-7I0`vRV-8307geJJ90FG2 z3qE{9Nf$k_niJY&a>LJV<#o_JRHS2K6{G#PCG$EV|R{X9~PgU!`-a9>L?g$G{oMwI9-Vr+)e^+zHW^ zv6^zi9eAI|V{T9_=_q5kqZ z*slTpy70N$gOAR7)gG{zV>|inx%85v&eXpcQ@;OT>W9=jWk9{6VZpt>AI*h}$Rn6W zs6Rygu20UjSq6?b#`zKcn4Por82d;r(bR$O7{eZ=-oCj(wrOo4#6Ip%!^=aR?61js z=d^b&XWf=IQp{gRHoqmqe8s`*M7FyUKFz##UkGnK>C>0Lb23*n*OW6Ytb3DIO+AC2 z-jZ|n!?Ne3r}lATCY8=W)Pd9JO2F2)uT(H)fTag7qd6P zOIsG8od0atyW*A!`)OX8@U<6Hub1!I0B!ouhO?$Vo41LZ;d>sFBCF3R1v@wA0@##- zpUd}ODn?eH?#KWu%`p95X!MGjP5a%IyZ?piO>nYVL92R1e=yi9W!h+m&mzwQVI9eR*XA-_Q7r`MMl zn-dFt8|S6TUl}w_2g2YD>mIe?@#uOsbDbeSa13U7rX2cuuVBl*J7=v{cK4td?{wx` z!z)E}Wv9-yPgCdkujS0Ldx-sd#reL$1Du)Y(#S>VKU+AjW0vfZ+9)=Jg=|otgV-qW zpGm4yzF65KYZ+rOp2=BHuXpqy^w@6MNFic)rAH}`A=XMdQXf8aN@jWKH5%6}Cr(gj z!9`Q+bK%U!T$Ozr{8neN&CS0+n~sj(f&Bumm+PoL-gJ06OQp`IPrz4_=q8+9u2`gR zA`2q$uG_f3h29wAS^OK_0{N8f*4az%7F<2ik(2Oc#oTKD67@%mymNd6jI~$K$UNym zk}=x1S|E+K`p#3EcnrQQJ;=w6pOatyF8}wxIrV4ruW%Rs-S=&@Y!6Qze$06Kc{b;p zmeuuqSzVNkJmb|dmpZUtdQ&l%Kc^@ox{}<{JtP^!`k0>UA2CMj$>nWG#!0s6JZkZL z<*TMn=_L{BlutvtOvtO>@2}QOMM=tzOz+p6>276)cp#)k`1Zoq4bHKX#b@wf+HpUrT=Ji*x^hk2el@r*TXsUA9>t zjf=O+=PWynJ_U?R*H+fm@oLa@0%ihi9?ItaX& zy{w$4r!)S+cfmxu8-8+lm26D-h>d<5cguy){ecyJ4)XC{=^^s^`A*2m_S3f+`k(%`SFdLQCw+?fp~u$Nso=XV&RkM~Regnnea ze$4-XepGq=hjv#&vp4Se7RlJ@+{6Dl~$6E_69v#W%*Sx`}t$N zWSE`nrXAvOG6ltr$gWa*`}k&(U)Gm<;;}w|vdIHIb(;|Wo^E6_JXU@;(UoL|8zF)yKPeA$(dMPc1*c%7?2j4|O(f1vC<7uA)0yih~+kijy|$?2tVr;dE5{;CQ#8 zaS3OM-BKFFxnnxhPw?2g+Kp8M*Y0nrJ^9=8;ePu34PbeHl=JysApgL;jO9tu1^G*# z#Lv1B+6~6rdPJ{Ulh+(bw)yl5O};3b%AqCn=o1+CL}=zTzSkokCW;qp$k4A~z<74(8V)AcLP_0adrM6VSdy$a98PeiZuxpz1hF$%qkNvh)#ou0|v>38hvE-RKD z{%LrmXi{l2d6ta+SI#X}8E5=nz5sNH9?+|JQl-bxd&0K(6|qGF(yOq?lpc<|bnWMN z(vv#31|AFTfP3%~_^8(gXF2o;4_6`&E&A&3-51Z(HwSKn&O^*oOKk;qGvBsIS8(n@ zrnr=J_Z;BN6#11F9wT3fe4i`JhhMVLz;7QXBh(wKMHdC$W5BzX=*gwsU7npupTtW) zL;jkZ#!m@)d7XUVlx%{BcJ2Cv+b^Bv`~=^=pbp;gEmP-RN12W%{m!O+@k{Amf{)<- z3vg5N(Z_{-;OWqjR=@rc&lEZ(cT(&ht76TMzJ@rzAehSZfaB4?;92r1qT8C@rNFO^ z{`)$!c)8MUCr$c^prMQ3X0d=2><<=+fB z7kq>H8fVil^3NJV>`uJ7NBdV4zc>N9FgA7b4xQ=1A;uT~CNX@>=?boA%jO{I$<%(G z!G%$|&JjZAaPU%oK2Hx(+Ohm_^^8mUPU0K1cYnX!oc@2&jw3UCeTng@{#Uc$C-rNt zeYv4{fJkm-fgk<4*i@Mv5d_- zyNTbQ>uxsGVZL^f>@Oc* zv^T4&rP(KicBb>5Y7chqGgz19uCJx9|)vPQOqbnM1DPBf$^+q{iy< z^30SK;IHhy72vRQrya*0 zVQ-!L-S*;n53x3McYAS2iuYN3A4z;0tS4!`N;;GL<0`AOHpDNiVHyfvXRpCi66!9jd}95Kk3 zA@@GTn5Q!4%Net1TRdKHS;N>XGSF$q?YYa$*0-g5Nau>cUvEzS(_8AF`t~tmsstDD zX2HfegM@y&uxZLE_Fw%LeE-+r!=9QNaP6+^M(3KCi|@u;^O}GBwY7>>!N(+@=_`@! zR*qsj{F?eAJ;Q}xs{_A@9{i5>J;f#-500^Wj1j*1bg)r)2dvaK`e{^p!v)LOW8d(2 z>4MdKy9j@(?*9IxD^_=WeLU}<_w)#UfB!sw`}*QXIiq}w=Dn}J{lzoA`p)#~)4jaD zF$3y5rK~=i`qoh2D$`c@{YQzRXWix8yc=Jj--4ZdfOYUv`0QiKmm$CM)sfF8y^roX zuS*E4jJdMYxh3W&oV&TwyXU-bpH5C7kNB(Ybl+d%y+1eM!%Z-g415^gtvHCfnBqmQ zMiF{^7jNfv-t-ao-avHsmiU*iC3BQFSU2Dk5}=Xx|00<;!Wa57VniW7~hi9JBz)h z;$0E;)hds8mnFVMo+kL#fj++F*!F#Kk-Bt=dn&cc{onsV3zGmD$ z$~SAJXBV|rqBzJPXKkr{ljfW*eD&J5toE(+l|`MGB!7JsXDn(>MR9ZLqtiF^Lba>4 z(a`wpBSCvzN3dmmM4{keiO$7VDC8rw*|rRF|6w#l^@vR3iAd<@vf7_Vf+Q2e){7~jZ>(YH>;h6-++ zpG+RVf7+wx^bZ^gp}(@;kk>fi6HgBEXiImUoeX_`+xaftvUSe6TYa7SZN-EE#f!X?%^Mw`c94c>H(!meuNrkG1l394{2C2*uYqsOZn-;>1idc zYu(RtrJk`hDwomEWnbldwPEaOo?WykYXPe<um4|}N1vcD@<5wrO^W3BYYs{4^+ZR#8A@y00sVENhDS}&FlI{^Go&dJuy zcCYkk(ua%iPB?y`pa$IP1gG-SE!`NSFPq`n2w%b#AS3%TF)9 zF5URmGCfXhtfDV1mdT07{q{}#Fw)mz*>7;R)^{pnPowX+<&YPBI=ISJVe3mjS`}-4 zPI_On(S3IJ8k@VFeb(~!G-3O9Z?v1qJEuE?50vrtzoV~P=&RaTi4Q{eX87Ar;dOeB z@vJ_7_x=91W#@c9vQXY&}B`?dV zjDCl+8_`cTcSF{eFOVd@PI%j8t&cX{uR=}ewA@2acS3gm*+wT=9u zGvyDHe-dLGg1oH4Z=*4)tZ0REdHL?Zf0sCW*Rch49>Nf0>1gT%k9+Hx>xmuie2*l} zjgLy^ALl#;e?O1G=jYR}=1Dl&N?yTCIIP&Bf$vW#d;i-q99MbyF@~4D*Lrb>XPb%! z?iyelDSsd)`^~lYc2c$;yi%IJI|0tKrUe~v2RFMd(kJly{i~Ip?OqXUF06!36)#-F zHIi#1XVx{cLv{gYI&~dYb%g8lweWNRO5;Fa=&hL*3O`*7}mC z=kB2obK&2?l=cMP>FC??&u#3~-tV>^#Sk23Z#4Q^PwR})tP8XEyyY8RgKXdo%vZ3d ze5LG8>Qwo8)K!&f>rs2EM>@RdQuAV^e0#|4$6Yf%mIEX?le{`-QqOmZHspU;Wo|31 zk)7-9N1CwBwf*ZE&$0HWPbQsjWjc1HVSoAr#uPNe^2$HfZa;}Dj1D?15N9U}FrGkq zsBEwL1kx~D&<76#k+e6=?a zJMpNv8R$-HADRQEw)^NkNIB$suKTER(1-jx)wdh-`q$uM@ECnxZQvgAgKxO&$}`*g z*Mp9=hn$0I|D-+SbHPu45BWUrnK_L*{toN`^xz}*kpGZ+h07iEcRBrx(T@;Uby=T< zFX$-;rH@o12Uh@#Fg!=F7@>3DfuZQbKp)blKY~m>5!pIZcuO9g{c{R-={R&E<<+^Z zhVo<3GxZ&$4VyMNzI~OCMQcjpcUqV9*A}GR%D=g9D|=eGe188=>Jxj*=u>o}Ge2&h z%KIbw6|d4-qTlE8_Zqw2Pp00HT;+74J?#6~hi$T=58@9}j4=tGuqL23pWxlu9vsT+ z)Y$F?4v~(nUBc6wfU#irb;|hr+3|(duA%%HW#vauM!2nSr#x?alNKNs*6bP%;Gf4t6`3H9wmAAZC>bjbkX3!FCQz0bZb zaLAD@zyle8d~oqzu@AF9(cHyZ*@b=b=d%axcdxVGpZt6I{Xf}vUZ8x7-Ck&+j$`dR zUqkvD)vdnJUYLF7uTn;9_QF3W-Rqy|O8qYPGrh9~8OybiYav%NysD17_Lyt0`GL*I zD0o-ZVD|BWXy4;XwGc(lWpmYV>Eos|NuR*w(@o#{A3C#1!G9C3KVbv7|0{4&w$}`v z!`kN)!AH!qdsf%1{Ay_8G~wh#)?wM(Qb8TJkneM(X`izAim#_5XQN-oH#5!qW)EGB z-pz0S41IXsjJwKyqu0N-RP6&Voj*1EDr6Nf*$m9IFG)J<1K^LI7qZV52d2|zZCRW2 zCAzk(&71h0DQiP*>O1UyAK($MiJttqsAgV#`qdoWP}Ywd%KGswuOCb4$EoVark@l$ zH@W9?Urn9jrAxhW&!P`H_shra2R3ai{S~~|Khr{eYAE~dz`^U&&(W`ZI9y5lf`Qgk zZ>9ZHHa%Yyd_W5a4ii;|xpRDA4bGZ!Z6tnq*DB_yGWlF9_3o(g>eZUY?5*p8?_Grp zyn5wJ5`PG3Y^0w<`p2mI0Qf5SXia^$o@pb-n2v=X_@HXF##fFXRp5ux6bl1JvYw;+ zI%4`v?{c2m4%{2YKJ4E&&Ucr1x6*4@G(4QSxQl!Xk3&1+pT}O;-TRC&-+#vY_9o9O zE_1(IIQ_DKvCPP?vbS?1vXpBWzrP0&Y~+e@y|=z!-X}XO5V5j{9MnZ$qotod(yF8v!bFh%hq9B`MShb zN2d#BXb)TdjsJ$fY98&D9_eYKx$VJ^q0gen63Z4MzO)5jrf{x@b(T+qTfTf>VcfHN zQudwLe$zOceZBLI!^PNt?s~ra{oi1sB`3c9?EG)!Wamg%`+NEy?f^fs+5D_etnZqD z-stOWrmE?Hf9?c)H<+~V41*t0tD)KP49Lh_fb=(9%ryom=ev_?iPN zcklen)SZP*{B~>z8=js3PdRI1os;&#XH7Ud2QtK=@7U32P0Xde!SS;FPYG9-lrAIP zaBp)z-;_V1{G5QK?c#(5Jb-<-a8`3&Q9f4IAOh?GcgK279)80)0qeVj2O4KRV@Og) zIyh(hGJfn(aBOC#t#DP@m@6KkjU=`TX9i3o{qTeryXB{Wu4*SxzWmGp>~_v6%3<5C zE8GXYNQVw)+Iw_HfX)igIRTP2;&VDHpqV^66F_=_=u$R^aJT%7fV0#W_K1Xd53W0B z1Sn4(=^D#m)_0}v*!&i)mY)$Io+a3bcFNNSo(-_Ki8avwKHO&qIkAALe);6!KRB&^ z%g&p}-1$69eiuu9RBRr)qA%+-XQB(gtS{^QI{NS!Jwm#O>a0-AwN0Oao9aFI8uUi` zs@SjJI>0md*BweSA6eIS(fEX$lmEy!gM|Y&{W{(`1+#AJ`5AS{Kc9py{BsKI+{||Y3(06zz9{rr}_Dq4~5yO-bE7aK^y6BSpW zxdM(8;Ooj$O>J3RIr#wcd#Bn$w%v^mF(C%Ou~Wozu`e!}9bb{mo@T{{b!CXv zk#AD8Gm8F(Q`h9s`D)u~FHRZl2^P;A!8?r;I5|EVwOw_HxJ_qV{eB~j|6dLtj*f(H z3BLld#kuS0zwpE1eVME^6?}*>7V$IDpvF^?acHwo_ZP3Ipe@JW;O)O3$9%+@zdGh; zEc0_7eVmy){{H)jIdcDf9c}vi?b$!<_(%NT)am>Vblw0zJ$nDWVg=ga@qRs^>BZlj!hc zp&0KX=dq(){tI?Oa zd^sW+EnjaS<;vKOvi_t#pg;0KO$0ZFqMI7@l*v51`oa2wzK<|ga6$%2CpGE4NBFPQ z7hldBlb$-0{#2LsNiyGw;h|rvtoFjHZ)l9|J^%IU;e#vCn^%ygc$MWm8^d{V(t9#| zi^6O@a6bJCfjbe_6m>>>kTqg-;hyF6FUou}r^ARHIdLrWs&lA~y%}D(6ubfktheY} zhi}r3^1?UK!=QE3T6m2#(WllPB**5)I9r1EqVZRFr~8k+`-)gw;YU0RSAWkrg+JuE zJ|*~E)BSsJdnG=W=SkDN?&hwx!0~7nm`N9LbY13O@c_oY8@zY+Qa#!1;n(Ao5$*~< zMNccdbkPpjAN@z(Ywk23eqG|x!X?38w%k_ay-7f;yHTy(z2#r`nx^Nkq(DZYpNvc9+dKzi(th7m)fxsI{#>Eu@|ko@m^Ha}iESGL`zrKQ1qOY4F3-crww$4e(1queCQ1sI3UJP;fzI1@qp@}{Rh zPRzt^#;}BSz{g7u5>vO?iKBU(_m2>Rc7~Hr-=H{?I+7oN7doR=v1ZO%nD`CfKlZ=R z!SfV-ht~ThNT>g85YjtytKhx2_VX0cui~$zgFTEt$Yfod4k4!<-!5{*_wTmw{&@af z{7=qVrGNODi#PH=DgI2lLnU~xb3tWm4a9ND=Vr=E_g;ff(R8?e-f`qVo}ZT?^k%6m zr?&8SLyxtF{8kx1zK%SSe_1=*{ZsPEznOy<~k z+l#?VGtRo3c(Ljko7~#<57Z%Cd5AlDE%G*-e^60T z2u1_pCRhr`k2s&$!E?B#sY~d8oeUELmk;2Ge3)wu*G#TDu2+#!`{8vjeNp-BOW}x@ z!7nd;2|@6^m3Z>FmkVDw2NF0%JMVVSTnXd1?DWoGLDtQ9VY9pM0r~J+73GgbTT7|ui;5}o@=jYl z@kapT#hXaeo;Ts%3F=n@Jrnt@-yb18N?H*4tlxi&|I6TuAQmNBj}7gQ^&ar9oIW|j zg*^k3$*xWEUq7W>HrZu^6D!GESw@>D z)+OwJ;8}i`=5t#Md&NU2i~Sr``Vy!Aj1PTGaIcTA2Y<8OBQ~!u&gFRx^YFUeUYMR` zPenYKOK_fGl5}vncdj>H^i7A})yAXbeTM#7W`%PmmE^PNLh>Ed+VmUg(~kaIukV%a z*jUEH>!JUFdY;Nlj)OnFbE#W>3nbcd!Q}lpt@qb?G|;z?G{LzZ`?hvDYuQ3=Q=M>b z{CD7Hd}v%PTuH9iamaUYt>m)!XMYAN`0FEizrM$(*yEWAboLH#-PT!~=mFSkR#y7; z*1~$myHNTRHqH)U!QS`owcZ@v$#2oG_M|MtR(Kh?C7MzGwq0))Ri^wK11-DSi!D3e zEVl5y%a$EHvtFwG_>;k5V~c-sj-O->Y0H6$mHzEJ^4G~WR{RV)*(B|&Pz(*V&LV{p3p<>%91`sIf7l3%V*U-8Sq>D_P0 z4jZgG;S11ht@a=7_0}gWV%Flsu4%rN{(_gzygT1PP+655!+0hn^?Y@A9d!t&L@$yD zCIt@yH$_LXx3z93`DQwG?&x(n#e)y$TSBxYUs*oreZ#>iA9rp3@^3dNeF*b8y;JSY zbH3dG?L|lM4Fx~085lmPd?&*n;Lfq^rUY}~ z+f8SIGsm-;w$sKyn`tX)4vnLG$YyGe_1jGAc_)3OnR{DII5NwTcS`#TY0RZ#JFVe) zO_}W!;{9*f1E~8KdFR_s_rVj(ZKo4O=j;*i?I&Wi`s}AAl=~q2X+H1&x9q1j=12BZ zf_YoR^GxO}3lnPQN`PBQ?r|>PezFqoTJ8ebMxMNqEqyH8s2aOZc;U;(=e@mMyO#-@ zGe79lL(s<+Us5L>Zy0XWt;-BJUqtbRzAQ%Y45*2M@@Ncy|91@~U4p{9b1hlqmD71?`1jf%DS2O_o@s|HIzfhgVgd`QQ5_ClErwqee|> z(1S*Z8ry?L8!;^h4H_ZZD6x~lP!1Y2K!g-xO9TB)l%Od_8#OvqV>y=8W|XIsnW58+ zr8QR6j7{5A>6Fs6J%mIvNYZqEOv_A}^80-EUOQ*;>hyW$x}N8c=UiF&?t8uM^>VL! z-RpJp?r3M@(4Ggk4ZWv0L(dGQH~9KEcdj$HnUFnmUm3hZ{`;ZDYvqRL_Cjx^{RBvt zudDr3@b%WH7k_xDSMkyRYOiJ~GEMu8QzG#IvM-~Qa{FB5*n5ulg0VkB zw902j9y~zbG(8gEj{85>L>j+Q8*cpe%aKiA&qh{4f4dgDZxeSjdNSVYdaLdO(KUn(0FW%s2Di)FAKCUespNBl)kqX^(R`{xn``roFd-Wzgs@d}9XPKZHO0 z4#HpY5O-09z5TP&HLS#Qr?DjPAs(fM?AGE6wUR)5z1OvDaRx)8$F=*OIOf z#%`k;JGkF%qD^k?Y#dPeq{iTdIQxHz^E%3`1zDL9)}H0t_Xir7yKnI3ZMC20qnxK~ zA+0H8)d}_SEoF^Em#eK14~ym0hvY10m^cT{_;2W=@Q3EbHbt0UWbQ)xkAFTni}tH< z%5xg;TTfiy;(V_B`+og+{4ij^NrU4&e_mav^}OnE>v>ln`}o;D z-=)X5Bqqd^{?8m>kAtfR{_$P*$kLDdYiP%fvCLe9=A6pV@48se@nxIFThfiQp5y!O zW<1q~`xO?iNp5JKYJhjB&1|PkG}hI6tmI5B{_SzpSMl$UpX@Oce)pB&*B&!HZg_lD z;|2Kc0^;6SraJJx&60zETlPJqxXpO1l(JM?7Y}@ww53r;0qQ}zm+b!0r1u8WxEC2J zKAviYN2eYS|1y4Q_s4~Q(S&aye47ccveK9;Mm!pe+A{7g>{L$Su;lCJfoBL`b2r~U zWpCW^@^$l2h0c~hvIPpxQ z@x~a82^XtvD9sU9FH4*x2Mfvf$H;djai?%kmfdc(CrRhX{JltOw+?lrcc=00^~m;a z&he|=`hN9(wSPa1-)?<(-(KxDv>3mX$yQ6AZIrBsSNwWGp1S??*{a(z>LSWs6Y?oO z{~T?275$_3oSroI5ud!C!(vWcV=%c?u&yfbNO++n1<4j&v)el#Z2+@8)u>KvlOc}RU@ROb$7G1ttu@<#IU zlgc^0RL%+OzQ9TFUPjox+jIP`K!1-z9_2HQJ$(5s+!ecDdr7@}X~akRZk*;vb^cT9 za9*0cd8zyAJ0G7!zo#@!@cojG)X3urohOvvx5m?>`f$I&`-=8gd}B}bq878}=HH#t zGA)fZiaz$cue2;7KGN>qHKVamG3Hi`&X}oRK)z>m&^~Q>U5@*7#^nBYT`1FxV#?t& z%!N(mdx84?w$AiSTs3oaIeV~0OFl`b8lufkUDe3^fbJsG8o?u@3s@KMzo9*yIRVX6 zov5Eo-_klFJz$>u4(`h$ecBVKcc?#>f60U7y@1UBv(6B9)7m$lR^(2G&g%JX{+so0 zjK0tM!PE^c?mCIi8@p*C>@Kw*;yvqxI4<6@Z3^KooKe4R`XgIrT__nLe`&5i?=COR zRmo0sBj4CEi+i-Zv4k%>RFBp8vHMNmZ3=Qva^vJXv}o(xTde<%b6fOm_xFUMdu}lI zbi7&=<8=H|$BAP~Fkwt4X7IB~y-Vw#?wFbOGsQukyLSPxXRm?tSh3cj+Zn6KUhC(1 z=vLzMdC0cul>ZcDozD9gA`go~jYDtUZ!Dm>z?Ln+ z(LwGFXjus&2h{c_nQ~8qAGE$BKB#k$-O_)|-l)e$z8(L=o1M&0=yzLEhLML{&t$qr*cSdPNeZ^Ts_~Y}&$W^}&l1{Gswn~vH(g8F_n-YufzZjm^m^4GY0iRq5t$7-y zxs*GfKO>zXO8TkC37}*LV}qeT^_hER(0TsIv;1g{NON|Y$CV$&hyRQc*46$EKbi+| z!ipole;Z{pfqoPD;;uCx#9i{egf%nfeGkx=PUE}s2mSkh#`9tB)=Jq&ew2?GhWJv5 zJC(Ti5>Kh(Ab&~|aZaHGVQAU~7iuh}ITG@{Pw&{FxJ~$vk`xk{Q=d*1(Fr$qTl5e1bg8crZbmcF#j_*KcA{_@RVfUhqpvP~}ZN;ni1yk*}fS zdEZ~rxk2&M`HQ!Wq)_Jb7mtl9o|enewYi5u>DBzkBa0uv@Lr?e{+{#?p?|60_3vwe z_ZREF1hs)nr5E9@?@##aW5@Tu$LkWwbDxO=Zau+kP6PYlX}|tFt#m*q=BtHgu9Z<$U$kSI>R+s~`E4 z&zI7pLd-F{d8Yg|?h?NTLg?^i@FaPXekr}j;cSh{biBN21IUZFxrbx)=2e}e?mg66 z+eO#cSxU(vrL~=coF@1YE{fCcmA9^8u zICmG$xnMN)!Nx?(O8R!>pT=90UuHiJ`xhp2uZHXAa{Ry#7rAZ1dsm8EUf!II|Ncho zGB;e#y&3ZkNjLt9x!;F*mjypI&y4flp3InV{N1Wg=^=|XzS`^NdkSH1HoD?X&vkxF z-ww}UzUyc7N&2>P8S*xClDw6EF*dGqbX~algWJ1C=eF1C!y#kTg?_)leVcoxIWRex z;vx=K zVQB4hluoa;CFG5J-b&{Ub%(pw?6U};^%p2Xi`WmL@aDeiJ~ncRr__%8_@!4koWqvv z41&5J!~2dnV?$_t>p|zVv*eGkS?;rWKl-Bd8h>A#yI|L=M1EO z7CM}%`LTWG{`mP{`g&K6)LQWnha2t}gj3)1b5OFzFW);?qK~fpse8x8n=22GN>(yo zu%L{-|1Cq)x4*4BYTh(!v+R9X;LA0}jc=#aKQS_&_UzA@qMt3e!J=t5Z>JL8m(R$y zXG837RK1`(xnnu$A&j9j_MB!srSem{ot3+}n}%=M6F!KpuJ6g|eg(xB0<%EvGu8X< zGwagKE3uE;`<9i~tP9Z#_5GKYITJ_kV{9wG8soiRf0TPDE*RDRI<2QPQ&v&J`TD=c zN{?Wt{RY;p!D5rbcQ9+r6KRz8(Qmli1r^Pt|)d&ukLM9FhLFO5pAkV%Gb@$ErYXHQP5|8iK7(lw_ z=Zzj+sC=PMt%cUSr}P@#lhuemqkhKe?{&cI#!eG3Vn$~}MD*N{5aw*dCG|B1M{{?Ua3_|f<{-N<|Pr_6!(PZ_`Bafo02gCD=b22EJ@ z$;>GtO|sMX+QfI_F|DP+Zbo>Lf-f7Va1*Y~mm z;drVOPMqsdcG90TClz4dhW2!%N)M!8p2-hS@7Yh+L7d9 z{MO_;Sdo#7>X!>G*(lvT{v-NW#%1bn3%HA0GV?j?vWZ)=?^N-35`UEVeR(K(skj~^ zob>-kL52M?s5aec_U5zy7QOQcS8n=p4_UTgKKG^h`(e;4UAgJYJ?iFJ$t!(V5k7wD z5a~MA+>y#e>C(3lv=3Z&%S*SHPV)ZBo7sQU4PPeZ+eI!fsLx^kk-LE@n*)+>g=G!Q zad6L+k#h=T%ek|RoV$a(6~RA}dD?%keBUZN_)G0e65DF}2M_MP9p~lxPSc_s)y3_UJ7B_4L%)@v~Dxn?jsRIS>Arj_e3I zD_f`2zNfQKYr3=ewdtD6M{ZrXyng0|$UW{JZ9Px(-zl>U1Lu7~e?-6(WlY8yYY5WWs0m??oo0F@8yh_rwRvXDWH{<$?0%%Yz=|f!&Xs zNgnw6lXyuw)S2W$(=AC|Pi_2k{3jVwY59GII*^^;mesCYJwM7GW;2Flj%&dk(t(W5 zBRxm$$9G3>es-bdPTNNhO1jhbpDniBY5Q+yT5Ug_dG9l|eSIs&Zu@q7XWW~ib}s!= zd3Nu&<=tJOZA0^!ll9g?W{zIUcw++J4m^*wyT2sQ(*3m-G6k8hJ~gHO>2V!Naq638 zDYz+4$&6~^yN~$f*N^iYE6!po&Z&eeIzt^88Gw#oU+kTSmuB>pZvG3SJ4*DB8{K>J z$?b`Dv0wAMzHFGOHe~dpX5M)^{Rq9))sKq8oKq)^FB|wC;J|s%<>XI$#5LzD9Y}Ki zi~j>MqKZ6JARj`YWJH)}pVuVovMl+Kt@drnhu{A2Wci?P)n1Qo^NGdVMn18aekZ~F zF>P*Yw>w99Fq1eIF}EnaAe(yB+(Rb1i^guVRw3Q;A3Z-bnhp)mgcTJ4Q)2x{SuT=vJxj9iPY>sgd&SkKwNln+ z(hbz#=UVYeH&EDz2`64@1m(w<|E?UtPoOL*M<~Yy8mlav>aL-@(iYY?y4~-*?`ws3 zPbUM$h}Y3Q|D<2xHO3E7x9TG@nO~HhzE`PvKlfYB^xaC^Q-MI@v!p94B)ydRM)aX4 z11SmZFH@fX0ym9=PbVw&-Uw#|I*Qrbhw<~~_iaIauP8=b=+}OGq)wmhAWp^<<6|N8 zn}ZSP>v^BwR<)jXy0+?%Wg_ADfsZMTp!yZH)ytvFLdmhr*J-20#8r%)J(ej{{Qu3d zO#h9J(}n4yjn;sLw9_`8{rsLZo*~bETb*P2@}Tm@c;;GYji)r8QClsO?$Q9S#<|0? zGrs>)$|9@9`&LPMg72eI4}l2l#C$6}{dUfz4qTF;&mKt&Z*kIeXKnqHQv!<@jXXH} zamSq_t$%mQ&5Jvy2I@CWd3fdg(OHWpOr2jpHr1*B!4$Pa_nQjpw~+x)TtZx7zRRRL z>Q?r;-;~w2!_<#wn(>4irn50EwV?^W+LuwWpSwTjOfdbqctv?V%WAL6pWmPB&cOe! ze((3^{(Ok|&YKfCy}sYcps&TOcl-Ljt?O&Az&GCh@U~lR#iO>XaDPoWwcV#dwO#)` zpGI%@+wXfu)=i`BF0lG=bOKig@ZHe~7Wn!B{iiz*GS`~-(0HK8qBS0{=Rq_c(7c5| z9_WF8PbUwQR(bDh(lwARK_DWo3u05&G5f>o65Hxly`5=!#@j{ zobT|*0cDhl^graeyWT0Ce<607?~wlY=gxO*jk4dH9^Y?Mz6_E4h{6ZH{J2W*_%r4^ zqR56z3FFI%EM&wCXj|9U-1Ps``Hoi7z8l$40;(Tx;dwkS^yAr9Kdy1R`f>cZdj5a? zvnLxOmY$zO+1?`ErG@jp-JDTNU?>aL72P=t-BIo9`BOva`O@iE{&ac)`Z_w;bm{B& zuIxl#f6CSA-Fv84PQ37@`Z4TYM~B}tJ%H~2&H8&UoKSx=cCSrWJ3gaMA8WkSlL3Fp z6@th2*~-&Y(u;g|$M|VZQU>UIWBwY{sru>@=R?)*KTMzR`ed@*7lyt0&w>-i_}`>l zKdjHLwc_!|^Is*L+WlXHcDpY=MZ0HiWx-sl-A_58-7^m|-tN_=ZM|P@+Sc>CKBP_i zdVb0YZCZ1xc3al>Af)f7(3Ul(KV4g%YR2?S$S3?W-j+44ufR@ixq!BOrt!MU2X^nq zM0XdlXALvX_ttpb*2(87?mwdqOD8WNjNgXU&qtu`HvIJeKpXD6-a8%H2&xTh6=FOu zC$wR|e^;B;SUppDqJPh~+OYcf3ibOZv|;)RR}Vk24XaH$E1#II@i}|Z&f^=7)0u<2 zP;FV`a^?ha-!xrqnR!9C&8mM^-Dw<7+dpm`uDUC*+AM1pC+XQUNZ+a2toHh!)MlCc z?_T-u?z}%b+X8!xn+kn_)edmqr!&~6>!UQLc9@&;`=vbmriaj7nQu-H(RWd%_TI|? zw89*^z3Y>QnIP%GoJp1_{D;jI))7aG(H#oWwcRmtlrbK1rvkGh;N) zp>6cD+9&YzPz`n!(9&tu7b~qTw22b@m0)k{z8ZV~cXi)AQg)q>->Cc*A}ed5E3r9B zJNL`Z*OwNO2EF$dP-Wtmh4f>cmDIk6;xg{6(0muVZWeP0>IavZHgrAV)P|NoPbMDj zBO1{6(jQTuACK=}_Uvy)mc7Ip6lIdqz#0JO<#fjKCdw+ExE2+9nb7PsbK`+1>(F7XML#&8dA08lUSrN&%E*-k@bal-K_~I-BAz4&hPFq% zb0oSa!=KNWyelW&XOefhl4~LEJq$lNw2=Lml6hw;CvSgen2|}t+#q?~L#O1oDO*3* z*g4SP&4sTd-@bg&oWbej3w!Gi`Z9%Yj}253E*N^6JyG!kPq1KEp?HiOd6=}?eeK`; z5A?NlE?MnZ|ZAf;#qHAJd-nLk`q}*PRw@GR(60nj%jJswZ_t!+-3JA zSFg~SA#ZPkbk#igDUJPcjIZ4NahfNPz2dwZ|KojMG^ww0_Wb0&ubVzz?`a`?g1$xa z89j^rHi^_3>|a-#W1b_HcxZ?_6sM`bu7jtw&ab{1zR#N!;Y=rS=V7P40J`_mi9F=) zWAO7XSxGza>NCckINp~=+Unxmam_n(3A5-rPtMUtWN2SOFx-^*>YPiM3wUQFme@2< z%UW#}=?RC>8Th7-=H#4}zedLJJ@v)!a&EhAq~(?KIZtsDYucU6+4eJMo5tF9|44f0 zra^s!hVR(#XKXMUdWCOCcRoJ&FyCW&1o^~wX{Irkz`1ks&KN{z0nl$!bswI-|1yEI z{Mz5)?}O4DrtZo)ch&PF85^GG{J>*F6YiQhdNq4n&f(lr=Jn5ylpu4;o#%)2owA89 zUpktOzlk56IU4-#<)eWOvqw|dzcTrXPmG3SKlxJjZgbA*F81a>8Xldv5PusAMpN;V zzUmX~FMo*r#g~p=bQk>u-|b1=!1rXX53(l)Imi1ZZE)N@=-T%hs6TKh9e3?XKOZ;G zuABYf1>F0Q!F#>E$y&R;0Qa;F0e3HrZ=Zs_?vXkEPJvJqcIFrEb^+GzG_p>6rp{;o z)dbR?{^;`~TJzEVnpEmHU32g3iK&0nn|s%O>ok4a=dRX~bo@+QHEVPN-|cbOOYf|D z+TFYEY}h=awG!=F^yfsq`)VddZGI}E4r;lhLHFrbN6j6r@Rs_}Lgz8v<;Qv*cUHu? z|F840p?7HCh15qS{qI!giDBDK--O6?o*9mHKR+ZtnS4vFlKPwAJT;t2JR$nys*ra# zUjcryptGs-OybES{(R!sJ%r+e^@Pg>H-ft3F35Ml)#i2g<^=BcTf@8Txg$GI;;B0F zrTYwOB99H^BU|&r4MXp--%58T6%tPs_BXL_s(|N8p7oB2FLRG9d*#ZZ9okercJDp= zozYV0Jf5Y?6!WY#>Y~oa29!Qj9mcfGaeenO_tk$42tP%*{DZ4u@mh_4wRlBWaI5Hd2hgk_cr>Ip&2uxtYte>ZSlX!gEgNV?$c6 z&^OORtP#FT8$CQh0Q>MLrlw$~;h5L9N%1mP@rA79X5%(f&f)6~JGJ=mb z4IK_`8#;`h?%QX_H<7QTzcBuC6;AOIr|&NhfBBZb2y}r(7ZR>W;mrG@(8bWrysMda z`t~J+E49Lv;jbKjtP_mCs{%ij#!qT&bK>DQj(t!E5AY3&A@w_oOZzKoxob_jF8xo2 zbVIMKEB5~*WgVa_jxXo2x#tCL`|ZG&hUczo`LDU5qVIoiye-6dH|0HnGEOt)t2JPi zqxasYhL)2CFaFQ$XCEk2(yc==?8eZKkmm<|9m(FKCcWgt)=vr;?6#Qk(}%5}6!X3h zn~NE|A{n3Zq&13b2^XXt6@b#`#`CRpSp59Iy?!#4be(SfWFl@Kwtf<#Q?Dl9W0za{ zj~P4ux$7q%>`v-_O=r;k?Ddmh6DCYskzS;Em3`3v)9WW~nkc{rNX`TY5nPZ*tVr}9R7x!ThCG{53V7o-gY8yW^C#Jx36cg-TinpQXR%dDSF zc!IT)AD|<#uZevTi9r3ijMaWU@&tC?-liWzzwWM8YQ1VwsL5fiG>z|Cr7_n(Wz+13 zSdMO;!W_&L=3ZD&d2I@M!4$2ho^s73&YDT=VY6oPnNzND{(!!o_D|o!_b%XZf6e4= z+N6E<#BZMg?IEJBy3M^kT2r{k8lSMw;BDI+8IhilwCk+n?HlaaE3eGByI4(4*^ zmYnYRfwRAc9-cs#qulhZq7>d4{9&ctfAIJrE8~$TpgV=JDcch*>lVO zH}x<2w&{K7v#Hd14!SbmOLTR1H%{t5g?)6=+uJ6kMy7I>r7W`Ps?R)refs=~8boo?hjRUkcrm=+f3cF(ohkk=+$(+-Ty_KF@o~$B$;b_B5s|{-0 z5vMO{V9#Esdv4;O=3YXK!!<{cLOXi*b)7A8?;;~rAozbM`Mo21Df3%A4DryC0|RU4!a|Juj-4jW-43ndeo2OoF_x`w%+ zOx*`V8`Zp-+*+`&;Ler&ZuCg@l?N%ev-tLu)5$(@r+K*cZsy=(jN5+DI8=KTa*A@y zC!WdNEk5}L<_}Vx#9tAIbcZT*gN;1LKzDtfvZC!it8hm`x=Zk>_qC`00_KaJVr>37 z6Rs}s@X%_WYr$0KF75GsyUx5z^Gg|#r{33lmK*;Cfu^Cmd5(bgdlaVS6z};96RtR= zX{em%D5(9O>AWk4`pradVSRU+<{i~Xt8Csy?<ROrrrs8hUJ898 zZ@TUM&*V+c03Fnx*0YJv=R-R!x(lv=J}ezwT=ScS^bMM~*kbyyRE47tdl=fE-_+dw zr_mE=d;6OfZyRY^fSjWpGnN{lU)vvqr*+&$sR8Awey$jJG~V7IMxHzqP8_-|qDKEZdgLfqTM{vVy; za`!bO+f&%v?0pA!IWqMV%w@9AnLWu>D?3MDMBZym?5uonB%82V#G(B?Q`z6c_=WTR zo4%3Geg(-@&SsLnh7rAMIMg{$cey#<9_V!Xew|GT zgi;2$?|vY_odBBSNI@5vz@7@n{gym)sN5gKe8IV??D=MGbabK{&ff#wtj zE4*~4M)9LGmw9PM1n@m;(ky=HSv}D5_QcWmuP2=c!G$`H@s4}uNO{t}!_-x?sLyvs zf_$soJBwuONq=xd8gn{cK2qxEjihYw?hFp_{NKq_vz4bb@{|tG^DR4*ug2r@H7*x8 z6Mc=T6U`f>#^A?>(t$McnHJ~%TE9*b*i#qEUq*L)zw(AYcW_dOyC}|nh`GI=j%0$G zS5&?<=kPxJEoJZSx7?7+JQj0!CVzqY+eXq`T86auZxVWp)(o}h8~w$-w?p$1nTbuO zu0PdVv@!A3%uKibRF}G&gFD>$-rvLUH+5vbKk3(_&5JE&OgMu%KJlW{A|CYiFa$!S z1MH1C=&XbXSGF)G`Oe6s*BTS=ktfLm);mYvg!h!En^qOLdGgMv_o7?Me;+!K^)1-9 z&mVPp4PNy0Irh7ov+T?%Fju>2NaIG6&hhWR`t8I^Tj_xIS7^;Gb!DF`_cV`m^TMlL z{*sJPnfp8zyo)pTq+9mN$9pEdI#;i>inrcy=NIRlHq9(e2V$knL(x|>c>66uVP=h9?9!Z3=A267O6e5uz>k;07w?R;ljjuT5r0f{?I%zM z$u`$cdCkO3pd14A0m|Ygx80piMx3s_vCrYe_D23sE)&1~X|7^EW9s>o$9&3TK4meV zGMZ1hxMgD6WYBMuarm_%e&8x2TkfWO)HWsKU!;6qq z+|SLdxaG>$q8 zIOn$jJ#xWg;Ey;n!P?ASkByw?#&gYhJn6)<_u_3sy0cF4>;V;*A5RMLq!LdG@uZq~ zCY>Ul-@kh7gEr#P-F14$R-U)=?8g%%o;2bK5>FcOqSFul@l(w z@>h&wo_FiU&u7~8vyiRNGvD-`(Kpb0-=trkx@s2deXK2_GwL4fiFZBk>Qrf~(0dm= zKQe_obuzG5*=6#qvkrQm%(MUPi&UNi_0NyIMSQz)o3!xxk>Cb+!^O1Gv<1H!8N_WW zI@+Ygdq#$Mp2G8F^PI`^6!Scp=d;XnD$f(mbJ~WpMkknO=^1IO&f=YKjJ&z(59lFp zjHIK}rLzAdQ2#HUj7S9aW&_Pu>`AHBYRQ~dQo?8aV=eDAr9Y5#O^?(eD(G=2NX z;HQSO&N+YKDU#IP=4X1bB zIFKG=+@$uq@9B|R!<#>P_B$@G-?HJF(M;2RFQ@*?;mvaKrrTyWyMFw3oJL)1yeu9p zF!87^Cw_M(JbsPqS8?fC@*wkN+7oeR5~scupg5;tpT5D1SMli%bT^JiXO3pzcNpFq z7SG~01Ak}XXZ&2$qZ9aE#EpmEH0^Wp%Y~ztgzmeBlX|JMP zz9UUa&%b^D*av>T#J37F@s|@uH=4HL%Ph6WTUb9zvp}NmmAUdMQ1@ad-4O-1o4Cwg&`$>xovFDem1D@ILbfspEU77#mo8pDMm*9^T{oH%2CN zZux%kd~6GI!SVfP;(4E^lf0bdeet}{|H*q&lDuyBcR%4?|1yO_vVf9LDR+GC{o zV0Ui^sJ#~dcg{w%qA>XBsG)4L6>rE}FVy}^k#~nT$%CFlD_#CmpsTYp zX3VT*&WnEC-z%uI3i7Y>`Rd1+-^&PuZ)DHOIjjwG)<47juBdxfG8I3xp?wemXq|&W<0@oJTW#<;1&Y(ry+Vx`4F#=ODFLnzbPMAJ*`h zi`BeOpfi4M5WQk@Bz`vi?6jGRPv_y`*>fk~v7he+zg>@Rvf;UHBO4#(zMksFfokSW zrK4oyH}q1Pdskx0mmeN@gSnOr_*D{r}h1xW3N1?)c)3NLo#%($dLeG{*4$(87V<>( zdt&_uu**~(z+*ZqqkZ`P{`w%a_9Z)@_$g}6`F@GH$F;Q6Q|*Vm)BSyWZt{Ck>Ms+o z>OV*y*f6)2O@`G@suPT|k9G% zRuU!*zofw@I_q*TbTwghJ~uVi_`dcEFT<`9TJ!P#Uf~NVD|_BvYaJTToNC@)^O*j; zy?s`@hP-u9sLG?-eiMfyilMjCI7)f;^Q$(l{W~G}Y2hos8Cm$swvmOjlQ*AZK8w5u zV)6YC#@)XEfYTX2&=_S;4*OAemzn#!)kZdKaqs)oy_r{%pC0(Kh5LSWf7NBss~Bem zS@TWX-;~fih}yB{-1R-Nh0I~Ub=P-BFQMEt56=3Y&ZSabk_YcFug&~n=)uG+#@a8| zpEo*l<-Ls2G_LaRUHt(%*1PDCU!d>*!HDkelG`KD>Wk#|qU-kh$RuQF2J_;>w7)ds z+svA13cQ@od|Wznc^$-`$GGff(t982N<+4C9`+U8>Bn65zQR-PyU}=^y^~iKm;Z6b z@q4|dQm*-EAX(ppLR}&qZGgWAZU+*KHn{IEg(@AK5%m5#Bsd z$-GPz`HQ*z#bZN!tMFf?-<1d0Z^|5boUxVe+juG^oXAH`<`7>N@6-2SQ$h`iM%+`h z-W8_(u|}Y?iMof-LDuBpFO@m_l4IMVXIJ3q{~%G1e*_BrPVoLAiY z3$Y9H9__*Q*8-dB~8l*~Dwz2BA2CevS(@Be^$Rs6^) z`jNx|bng$+JIOn7Pi|nG`qF~~X)$;)%J;jRv{rtt(%J=ue%UZKhxoRz0->sQT`sJ=WE^W8#T>hl|B%d?V zMyIl`Ub0VP8GWzt{d(`)@2cn28JqhiUiZ*D?00>~ssH7#sFPn!xa^^KV&RA0nN|OV zUz3Jk1+H884*7T|NZ(95{T1`J*#F>HUfRm`b4TVr@m7$!n7W0&Mro%GG^Z7)ziWi> z?9aM!pk+bO{cfS!`dRG39He|zUg{H7-ZwE;(_YfEam!e!aY2P!H=37s?_Id=(SbLZ zv!Af45r1BJsXhbsCGNOKb-D>&+7yrPKk{AObGKzca!dC9cM!!>srVVgU3H#jzKRa8)!%xUp5V@IB=<#f7cyj_Vd{BLn_r1eCroWoh?SKE!@4tNAt=dU(f}wMU zlt1zGgR?g$D&T3CuS2|#It}nX^ubre*MTtd`cIsf3B%*~Kj+{>uB?-cmAsvbzLghY zZ(_sKL*=ojhi+s4$@ywWi{Ek2JfzVkpWzNz@!ufzt-T!Itbf2=3vqb2-~Lnw@+Y0` zEmtRDoD6SIWDl@(sfnu|=Dq^PM(!PT;>#b5+Sf_DNp;sekv)>V zxnb@1)OVrr@8+v)|BHV*_DUB1_1!1wLDH8~9B&;kg1!2IZ_qbV#@s)SKf+!Df4DNA zxEhpJ`kSdI?DM?yyT=X|97>K)+UR+{F2giL(j+r;GCz6KCaz z#5vc*`8^Y7^BLkSFmc8{B+koCoE;|4sx!oSk%_YqKN~p`bi}zfP#5_7z*yj8DK#lC zr3|OcOs%4OOr%~ItPDOI+#5VUtvv0?v_2Y8+p%vS`|+{&kA1|s%30`CNgX@m@6fT| z9sB*UKOReSGMuv8G`Py|W)~pWaUG=eo@QQWewaY*Ez>2lYtJt#Rw4W1jc5!R; zj+}tgAwxX&ukUWoG-0+b1(&feGzRkAiwW|ag~=7-$N$yd!wOXue(z;ZP&M~)V21DW zT~EgBmE0l5=9wHY29{y`U(agdx}Wf*v#y5w2f*}m15P%k1^c{JyocxYxcR^AFA&G) zy)Z`!Z->dlZ^IfdOeg67syB1qtmwk)xs*6Y=tQ6W>X)OW z?grTWSRUiy4kiSu=(Kt;1t{mGG?OBfz&M8A_&!P`58=b}aEU5xCWqN_FzcsLE*S4u z`x>u^dj3jol)oc0D6@-5Q<$`4F50|GaTam0Y>^kIGgtn0IyV@RQ)OsE-D{8|C#tPAxycj;v??&C*`*FN2D1O z#?)ZE?^CLN|I@9vzMqmlOfjYb!*6Z{_1*k4@`4%LgB$3-n>&KPuXii%#$S*=%-CMi z2P*7Kzq^We-|M>X`ZezTxc!Q_G1?!y=A6{)6*jt``o$bcP)itmkROkiLLb1>n8t(D zB8H!vrh@-Xd5;q2xA-1&?fIv0F3*GRv!nQjCm?So2AoVVhVk7TbQ1ntKY;t|LpJTe z{C9sP$dedGvQ57e<&^9sb2|QvI2Gp4`K7>Gl>~rCgB4&t>S4K|VL{iv)F5o?(#4?s zMGZ~EbnOcb;^w;N8O${pHke}&@!|T*G8i(L2`W7*UtvD~D4}4U!CZr3gYqPIzT4&E z5rc;f4jUXam@wFHu-9OZ!QBSC4R#vbVX(tsyTKNN%?6tcHX5ukSZ%P%V9a2-!7_uT z21^VU7>pRqH<)KI+hCT#kikrYV-$|c=a9i+gM$Y580<6HV{o^@ZiBlFb{X7hu+!iU zgB=Fj4Qg(YzhJAuW`j)z8x1xX+-PvU!8(Jr25Ssf8;lvOG+1G<++dl(QiCN1iwzbU zEHD@`m~SxCV7kGeL1l`+;8FNgc*NjggAy&WKV)#&;Gn^TK}53auiM}*gIxyO3~n{p zYOuv%oxxgzH3q8YL7y-M`6$);L?Lw%06MR-{4-5 zrWxF0u+P}{8tgH++hCW$od!D%?f?}}hrxDZA2--$aI3*)gG~k-4K{#^NBkvRZ|v&~ z)*7ra7&CY(zEb!y+=ZpaUx~qDgHeO0V6DL#gVkU@{Zf^|n88YeWuV?uYOus$vB9XpB7+fw#Oa31 zGni{IY%s@Qw!tieA%mF)(+vU+qB*!X#o6AeN_LzVYSGL4Jb8WRPRqU+`yJR{<~U1C ze{(1O%JxPRE(iCGCLI0AxcwMRQ(?lHe&tU3kL~#;To3N1|G1NWV%)wB`=AMTl%(-@ zXMe!iUSY!3;l9V%cUkr&*sHxNogKy^q5{A6UKcXcpdS_K&7J$RC;s3dEk*r$#|RD7Vyqm3{OQI?V(ii}dS4KGpMMXfQ63oC zr-Lg0egu>?#500bO<^R+UKW!=v>jpe=l?n^m@Z@yMM=8BXpCY zJE60z@U_r=gi*RWK=E4}cnw%<=qf`;4P9X9bVDB{4*Bl_#iwzDEuhOM;5_ULf0s;m z4t70bp8F1i8^@CF71$mAZIa%_m~70VTSu8g8u8K{HJAsgT#p=bws)EG=srX|&__X) z-!54&9ZcG{W7mLDI@W_O9~n&it5=VEhduWigE{Ye zbmw~>wi*n9w-f)y1D<`%V5z|ZQ01C_z}enr%4yF4`GU^GP377L?LhnG+6{elz}ddT z`0s>10`0q3LGQ#U{p|*~f+{~&@H}}3UICqNg$rVrXVH816E8;bRDkLa$_!n!Kl#2K z>_W!=a3X2npMd`{0m3&Kbik{Lr}H-+)*8$O>E%*;ip#Xwf0)0*fB(ho3NdU*eY@_JGP~+b^B%F_X?}?6yM3LFKCoY=!poRSw+( zU2FUoLpNLQ>Chz@y{8aVe)2%2_wYV1KYja>@$JB_*P^qaw_@bK5mftWV{SF|*i~ZWztFM|W7oUaOGl2uAgKKA{kgNf$fUph=gdn&_k+rB9PESk^VKr6&Ta{xU89iGN}K$fBdry%_oL0+ru(=F61dHiJz+OQxq5 zyDE(QM=kq2?0QhVln%$>@Vj1qyWVxS=lbc#ZYT6EQ2E^mc0&94t%2SFU10oILU&m1 zVd!>?KKhR1#IVzQ%RuEn3Mze>pz`1IPBQ)u{O`8t2y`1p{u@B$fB2_f{u2g!f0_)x z6T1$K{BN}EtFX()C>`Z}9u|SB?@*t!J!taN{}ToZ&{?=C|9hd+q5b^#KnLY+{O^Qz zEcZI-T^PNm9aMf>L8T`KD!)ZPNyg`3S76ckKSthTq=&T{jQ_~+_m7g{Hey$8(Phv_ zdcF4+7|a2cze7KCwhyzGqx^LJkocj;K+!wFL1;gJ?a&G6BgTI#bid_Z0lgKY{565f z-+EB_D+ZOnu;u^g+vp<}9f2Oh$bY}V-Jt5V)8L+e_VU|naJRwEe@>=zD|XEo#k1aE zndM%H-EfbW&h>A3>5PC%cQ&YU8GaMJ%H(tBo0J3eAyDPg2_~TZa*0FtL-!f~Ezo-{ z_j2e~jN)$uRW5a)(ia6)E;*L}u{YpHi!O%l$H;#VsPbsrZR*`%!|r5ysn^*`CyU{{N| ziT70*_ez812Fnb#{=f_0U@*(4U-RtC3`PtNcYF4I25Y|W(J^oV?<)sy21~$Fun1fT z=7OZ|&MbrJ;1$>pf6uc|fJ*OfFb`}p_Azho{QyF5Mi5O^N;TfymI87O%h z1@*oNcnx$WsCst5dEoGOJ@-EFV(3Og=Nfbj4*!$quOC$X^?;XyJ3*y04qgd18!R;# z1@->6E)Sawt~XfOmE@1Z-{IRN7TpV7ZqZ%PWft86U24&V&?Od~4qa@~ZU4v`%Fg8b zw*CX-6pKFk_l!#{ItU$x?lOGW^LNMv=s|y60-X&VH~h8(I?D>*1Rb*ITIfuRjzFhd z^r24Vv_?>~|R~0B^zl2x+(s z90TP(49b2lsP^0qmVj|kd=mpN0!u;hS=i8-hVCT|il-JlANNd9@wNV~XJ2Npz+ebe z{Da?0=Bwvh$T5qqhTeryJnf+PuL)GRa!~IpvHTzYzwl$xz0gtUVf^xYp&h!&a&O;( zo(b*Ob2W4Uv|rC<&=F|Ao=c(gt?+r!c@{nPGJIswz0hHcZh+2#_Uo+*dYCjRAKeCb zfESX#RznwoYM;5F%Dsnosyw!Wmw_#y!Z#WF_28}8$3T@+6jc3Xf%2CLUIE?DyG6%A z)mPEW$$H4buE4lE1`oZIbnkeHbX#;2^dPifj;bY@o1WKOo{-)!c17_l;{2l&=#}^4u;dgu^89o=gxJ6gBqnBEA9`sg=KDr%! zz@nR=TP(T+y4j*b&`r=jA0PQTdJ=J}A8G@YzGj1EpyWoD!G6NZy$=*W?=qMRiVt^w zJ(-VU;_bBP9Oxa;o#apLx&H<7Ww~cUZ-w4z`!jwCH^328$ls1|LHE<9Gs)z14s)qfl;2G@i0z!ES({F$K2wR2mtTw|njheaQGp8gg) z#Tx?WfP24&tOPs2PlDN?-ZO|_#k&VoygQyx##@bDheek_w?q5&5rK|F`{kbv-Ddee z+D83Z{_~+*E&qAYEzo{_hM}7*_YiauajKkhK)0N*6P*C1U-g2L3*DgP!cOo~a0e*4 z&;lylM({>33skdW_1Y(z1_WS7FgdpG9tB(kn~BE5R&-JzKr<-uu^{zcx_*8bI-9wz1DN*vC5*Z#O9Uk^fAx zTte98S#<1ajjIQ1#sliZ6B=>;QA1t3mnC z2UT7n@B;8?tK)nG90q5Bds~y`*o9piM)B2wDxV548@d#{2t53hhh0z6pR_poy-ddk2s*#D$ge!IbW&>f)Kf60?pc*=v%`rDh)x1oJG(*m6i?ek4DbkL&fpdDzR zk7}WhHYfcTKp%nj-6PP4q5b&7&|}bkJvz{bpd;`ezZY^IrQJhsH2qQ#deHJewuSa@ z(L160EV>T5*P_dydn`Hxy&Kx^hYo)kIZ7H-u2rDwKNnOv9p>FCw|0YD4Q>Q)z`X`M z7mO39{I!5rK*tOn0ndlt`{iVQck#YG7TpHjgi-j7mVG{U>n%DR`f!uyZ?Gv@o;}zl zp#A!4g6@a*%fA77ujRiMx)*Z`>8b-uz;Y{m6uVMP9`+IJ{dzk32z+YMyP-QVSK+S> z{215_M!@w3D-9Nbv$2mpoQ$u1GxJ;+<-2Nga^1KLyBKu;1-QTuVdxU$FKVz5RQfWl zaEBY=TPs{Qw1dCP@Hfn}-jgu67gYEihVC%fX0R1hJvJM9V`DNsgPW)qV_$8s(qOv5 zqxjMLdJT3P++i?ou-RaP!E%F9gCS6ScIY9KeuLcx+YPoDYy?wzZ>^!L43--#F<1cJ zK)l(8b_@CR6Om5ZZp_ou+g~J8oJ70slgnBM;pBO#tilt>@v8+V5z|fcqQq| zGW6JAdGjZGLDg3rECzcwc=1+)@*g$0v7Yr%_DAr0q4taPDPQpXWql#ZUk-L5=nlhQ zJ)fsv`Fzs74Z0KBcaK4LV8lnIAhF*WwcKkTAUhBK+~8J&8$s1mouP}Zc=EA}TJiMVPrq#ZhmHSigCT>5@uT<-fl61G z!RGs&?Ik8XHP}@d`xy4?O}|x&T_v=i?y(x?<&3|m!2(eHk!$E|gP8_*6GrdVIS65~ z!HB`WRi1r^!8U^}21^V^4Hg<~T{wKAEXAa+MrB)Edd^Pmery+6-j7*xKp4PA4O=U#nJGJS>E z&b+y50C+Z~WI;?j_jO z8vA^M95nL!i)CK?T?XR@8$rFl4!i-(2URXdW1jzh@J8tMpxk#a^~NL325Z0o>8>(Z zY%pRl&tTtYz4vq*%mU{Sp99_owlDGC+i0*HRJ=zkJsdRHY;dE&SY=XPw10;7WzntB z6&BqHU2f5}(CNf;5$_+q%Ns`(7|a6C!#=Ut%Xb%e4Rpz0dU}6Zg_o{EQ2w$&#nb;6 zo_#lX0d(1Cl6qMI?<=+FwmZ>dpqq_c+X!6@-D2oE=&0qt3c8RuZXwU^Z9=Dt}?{ zTJZ29@4fLw$#~Xd*9N_V3g-7h7W7ujeee$I!-OjamEL?X4DP^9{dy}n4{QW)1vUAg z{2jjC^S|@<$K>xGTH-n%6Ap0eB>ES2bGVb_)&RulqKV7z^>h*i=pEdee~0g z6T?pYnF}hOJ)cg7Z^wN-M(M8tmHu*2>F=W~6t3g8r2l;E+M)gNP&RZN+UJWb=r)T! zN<>?seLgt?-D$4HC#W!PZMU@542DF&y51)%CH*VtWExMG53b9kW4TB#CGr zpgS<)gBlQ4ShD9v<`WD*$LY}epnd*tfbNw$@8mvXjMCqmbTwkv3Eczod!Yf`0qv)=7P`Ztv!M$y zyk{}($pV#G4Cw` z3BDxqNoRYy$N352N>Yf{27A z?V$4C3aVaWpvobqDEXcsc4OBkX?;Jq*`jNpgBZoza-E0kLFKFTI>z}Q!G-i^V^;!Q z28zxCi=qAe1fiqQhm8M2h0Oa|?j6uaFnZ4*i1#IXL8ZS3RQg*%rJwJ&j>j9quEwHk zKY@I;=%X~`bo{`+OL}k<=3uA#$TCP{ACGVN8s>vAdLQ5B9Jg64&}Sakcx;4h1=h0e3+2=rl$-q&KV_sV2^?N`!XExH(Fm97$SJ+xnb zh0t}-?Z$r?y4G^K%fnAG5w_OT6W~29;0;O_sPvVB^T0gIe-OLja5DbB zFmlnN%b=SqdiWyR7bc+kywGd+Q7}rpyQdp_kmN5}KOMPm(pijM9rQ+!-wRQ&7TV8O z1iA*g+^=WoYRi2vhkgP(y*CR~zJj3g)rXt%wIe4PUnO=O7Tx<%_#L}X;xB42&tMjA zs)zKCCc}?Sqo0|Uq`RO?FnWLSM;r$cyQC-EqdP%2zo6o&HCSP=*kB}^e$M1i1BQI) z22kZOjDnd5?U%D&maoN7RoQ=xIsx*++U zqvzAkF>>E+uzx+e3LUZN9O#m)WcW}Paa(jJ3VGC`8=;FVx&pe; zqQlSy7Jc{}`f1`+{*IpI^*`%D^*d4U(_lKN{-|QA=dWpshq}j6^%MnFPlX0^r_kO^ zIUmlXoT2@C=!edMF2S$rX)kn^kuEV>T56{GT~HppF5 z)#T@gn0;(*HEp>&fUgS!oO8f-JzWU$U)%wVa(LW8*mLk5pd^x_{jxW{0(!5s#- z8f-LJYp~K_iNW0yymDC&D*Z=;9u9)yyRIO5q&Yv?h}}-;U7+fx0qlhK`K}gv2lOH1 zzXH0$a?gUU!stC^p!lyC6#o@~#VSWo<;{IjffsytfU!8Eghil}>OW7y}uqERl%41f=OXgj;f7n%loSH_Ts_7Q~KY zp-$pi*N^+WbDP_rxzAl+cbvP7pTo~N&gaZ?jOP~f-1)rY{H=K|{gyuL=*3z5vW`f3 z&pDmkkYb+m{{~%>cj~=kFY#XUT=+Wuih0f=1Gkyyb~0IQp8LP-INSy5h8=D~S2fQK z*z4Raf9~&x*7L8e=Ofnh6zbadf3fv^o%MW&^?aZ8{3YwT)q2($Wk1fhtmj`^&xft& zN${BOKgW7DcYU~dnQPgXS{G)SV|49WDJPDa7E$7>tNfxfPB+yoyYB=m;-6Pnw|2$d4~(bOc*do8 z87o~LBP1eoaj_{QZ!G9HEAshk*REMhaoh+Gn@C-1 z^}Q?ZJCQD{Tbr~xo-uBHVBLy)ES_3a2Y=juk_A!Uw4!EtGGR*>S1sdqzNKJ|hpU&} zFLL!41WWH(ziesk18bKr{rrlxbq_47kzLh_&#zl?wVv)>vA*iwsukw(fz|8AshTy* z>Z(>$-RnAucwn_p-oK`*$_vhuiD+G2)$+A#YvucaWb_Xtz3S2R3)if^%2|330%Hvo zbk7=ij(@yp`Fg_hkH>p!AE?9rUbp$zthsM?#94a33331O`|n}@$x=|&?>?-P@Tgt$ z1^(4lQ*c#F?}ugAFRfa>u5RrbJRfhpV)Y97Kh7(uS#_e_1FKJAyNnibnuuMeQ$+6C zlg7F)){MXQk#O=iyI}TpSASymb%oc3!`DP+pu`#=AQEAL;nj&A*9YZSHn`|*E{{7GXDhO@4& zgL>kOp(8qBkFM79DSnZC%nhL{?fHe{Ftl`y;?ZyEQlvwj_w3Q-^D8u1vVJ{%UU)b9 zUo-Yp0)nnSnx(51)zYQM$D#1O@blq4?tUyMG>dx=74o<^YF3cjWi_*} zHE|vvNBkW(o9iwsVBge#1V&YhD;>hqL z08cK{}{S!qb)zZ3JKI|^E1t{V7MadckJoxJ0|y@#bs z?_RggvO6&j@pjm_lbrFN%EbREFZ7hFll1Xww6d;tyPlE9uhD!B#`}mzB9WyplzS`y{`AaH`le< z*_nIpxewp_d;EOw!H+wNK+7OP6V4UmLw|4Kq4XJnW3rnabn;*PIdDI%((Rwp&SEMWD#bf2$zrt_mcI$=MK(3Br72j<^j!{S zu{th2Yt(mTGws_F`8KQH%p&4y_I*N^nYk}LYu9&8W=nAfWpO-SE#sELa&vG&_j`9V zbu%G!@8hm)5#>1+ny4)3UX`6A;yjxxKI+1z)Y2a$NB0yf&Tz%zV=lI7Prk{!pu0Gm z%4j7xiL+GaFARmVxdyxoHYRkm_7Jy!il4`uP56EIobrUt*RA*C9Y5mhfn&z5fVsD+ z`*DvaXudA`n_YO=qm|<9IA^CaWo?lX>aTl0{k2*PKXCo7wqI*CsMl}*biH3^KNM(E zf6YHv8BC_y)n9pM*ZYNmYc~(O_I+H78Mu~xZo9v@@b$n6^}furRcLjA}I^+&?H#^6oh`*3Grb$~N|=14D4EmYxdhabFvnp=Cy%c zrz}A;7nYj>x%j=HyWIRn;ABy>y>A3QFuf7*W>xt|7FPL3;A7rf09G0VYxa0z+vc^hi%(p zaENR@+@qN+wBPfwlG22kP5M}|cv<>q{<}>JLtGrbvsi{Z*pM67tM4A)!6)6_ve`c( zwPP6!!dTqfPyytTb>fMNU0B`gQcZoqEa_xzG=Azsh ze+=#w3aPG*e!V?CSUF{lKi0m+ufJ=vpUv5f_l@R;N3C#%lNSiV-4o=%vBFBUfkvB~ z^+?`~)=&V5sEnn7okd3jUuKOAnC+ZBvkkq%y`$`#{mH3Zc+^d=2aeAnS~Hy6j25K@ zM^5tugw!W+HQu`!vjS>WufNlX>+1RRVDdC>4t7e(cmj2)&ztd-Gknp#-zYlrbS=xu zazz%>U*a0>QG|7&qh)2KB4!|TN?}`W_!=`8`P1H!_027{hXj$^?_Cy_U-MhUqvvZK zVfp2wtkH9bmsW~Kw^J%~S4uVRccu9~IjcZ9{3*AtbhXP=GPks-WTfONtu8SS(23`$ z=MY!6QfyiVecBq9tNinOSb3D*2IQQQgeJc|gboT!z|_8TMPMf{EEI$mU1rWVd_ zZ?%Yqkn6HVr17NjF@*kcn9fyQq5l)WS37^eCgS;Vmt22SnCEYbU|!f!&Ckt?Kf6c~ zOu`&)dc9L_-P6UfKPo_RTo;y=PO7aI;~X7_1koc-l}2I(jc93LVfpJ$L7d-nyqLz$ z3Vxj<}TTaGdxt7TqP%d9dy>ax47BCnQOi&72wTAPVyQ(cd!<+%MC zXKzuK*6KnWYwF^47V#v?YIDEuZLLk!4T)l@m@LL%{C0Hi{>C$nS;l0t?*H+8cAg8x zk4(8X-JU(`MrXmTciiQ^fBC9Me_2&i%gRKXh&F`E`zyWLu`yJA>G6T%cdk4VIp#x8 zh06zyA6LpE$EMKn-lYTOZw>!fN&|M-7Q8N{E~z$BlLVSdtl#D;|BFU)M}EHsLqq0X|I~KkU4AS+=_3cH;h2lXm>PTGtza z=53H8>ri$dTG>n-Y(k41_y7{=_gT2wOf^3ku$YFM*FL8VL_Hq6JSOvHN{JsKpo_6K{#c~`(blZ1+F-PmIcmW#gu}e zA;B4beF_WiOn?-$qZb%6uW3o3KKdME1D<1ST^RahG3i++l^dA>x)y0zVXt8_>uv`{ z3!#|QY0f*#ezEB&dp~Ht{yoU3wBVCBHHFP9b312@OAGFrpbKX&hzrk35Q00WfD14B zz>AU#spPz1`V^FWHn(%uxO7MZKHPwkvlH~eT~iK+cYdErAM&Ea^|&J`w=;Phw4dy_ za880HST#WiT2QL$$Gg1m;c2LEA=Rao%!!noonQ>ly2l7l@Pi>$#BxuxFRPV+b_=?NI@1!40P zDoJ-)f|V$_^B&x_fJYnYuG_pc1Gsv90cM~em^Q_M*`@Zn!dqT-hPSL(65Q)|1$Uob z8r+Q;+WYE*!TJ@q;<=b9e|-4O1beW4f)T$H!>`|DK%c%xqj){lirP@4g+_8E)f%bs zY1DGs72M-r8r<`AL3q{zJYfZDeU)mpp*91)h49-q>4Uoy4B= z9-7~C+6g(pW#9K0B;PNKL-)Sh^TBO#(9OEtIbwr*u9#g?UP4-WLx};>a5_h&A9J06 z%(_Ekm`sq88HWSUPER zBv~;6X>q;$h4CG_q!}$)E1>b%bv}O7W`9!IX1^I47rz=>o6#A*CgC|hv*(4)(6G2v zXLt-Wy-{~T>zMu-W)HwW6A5n5d8T4-aS zOpePDW^prCX9l$)@Se-VwV|-=y3Dq0|60?1EGxotc6(I*WgRBnr&VFK zE5*ZIhNS#YehvP!-p|1<(CnL=+iLx>skH9l30^rk#DcE0*W&DIYdVD?@B< zs?*kLEKPMzBD=x9-ESj(&Q9EN{4o1w^rw@&MWwjIIm(&Ty4`=R9kvL{9xtS_BhSKG z82FWVc7FG3sw9tEXqt`TIm*(EC7hR~HM^V8dJkG$BUQSarQ>d6DPK}p>MVJ(w4x*| zzjb+j_wr#iXga>!Sku7O-fiL6Cqvg*c7Ba`q>CjzpspfQ*L;;zn<5U7Brd!WyC{ z(ON3PH`&AUyZP04n!2t`u#tORC}B6jLN>w@rqYX{56@FRQUnk5H0V*&&g(_&b(?fb zS?r!9LAMl6DURa!7S7v-Gd59xCVSm^QAuaq3|HR8GcM7ho*R9Be)ss?yGVECPAM+- zm@7`A^>=gxo#D4V=6X-!>P{=HzCm4iJ=sG?A!=Fc$g)ldsXDhOVok3cu%>T~TFQ`X z=HC2IwGxAt^v%)hDqVg5TbA_HVU+egvZMfFONSN-=k9vlFBuM&N;K?IkeX+GfAH z6#Ie%yR-o|3cugIyk9O0)UU9Xfs;%s#t8Sz(B)QeGp%J$JA2Ob%RdPar*3fc%RhSO zyLiX)w%Sjw<}+ax@DIXzc9n;zh1TFRbqUH}i9 z&4#XCjfXX;b}V?9>erjE`t|zdxB%$^H%Ih<8&wYVue^FuEkNU3k50lfV%(}n&&GbC zaxQ&W3X6$wu556wbfu_gPx)|9-U)D>{VLZ{J$ai+(iiotEg#zAD8Gq%@>pZ-p;w{x zaQg@BkV-+^?*9>181Wa)+xCMta0`{U%=i@kvtg1c3-2OcQ1*J@J(ZiJtNbI%H74L% zK2eZ*Z49olfjX0LE!P-*g5gt5!tywyNm%pQq8>=GAznz0tEk#q<&D?9B$5}6+5Z3; z9QELVzu?W)lWX9+dJ7z4E9|MQ1*PtiO@6%*HqP$qfYHdn3CKbqi-c&lU!LJNhQ}qq zTNU-j-9WYyStxj0x-mr@A@<8@C^Pn7l))RX)iP#3-B;9OR$EQ?lXQkp%{b`u*#0#T z(Z`K{od}mUd}xm6!Dq^yI6g7RXR-(QjK30gVRKc(Ull`rvZ&j*Rk05E*ZSp8I*Hr7 z;n8iYGyF!>)pDD_Z*XRt=XU{N-&rNud{!9`FdR3@*yZB+4W6JGHslYtVx$|l`|mgl zf9s!3zAA$)n1WXz%oSsL`JQP`V+%C1)Wc!!n&?ct*$5kfbgSu=&dxKAuo*lj$-dQ} znCb{VnXm-fdTux;Vtr}jTH{YL_RH0sG#*<)M_a*h-GLetUx!f{R}$pGnX0dI2e|~y zzsg-sHX0$_!P77=sU~pj1P+!=4K(zvejfKT<2rcwoWb}6M|gb#EI)Aa7u0#Yy)!#Y zo5y1HdDLg>J4>S(K%Xv*bza+8hd#k>o=}Z%F2~R%80el#XW)!(tKixM-`VQ@+?I8D zLd#jjlD!x+{y zOx(A)%y9XCHJ9-mqUgX%F>2jso}<+srLQ#b1#ZAYKW><=l8kHoz_o;7*Yw0|2CkXF zFSZWR6QX{1&r#BPhiW3Eg+yDjr;)S?(v54uBS@z&>M2O;3wttXE>OP_`n`U&YXr22 zoGH$5L&A};CDsGJU{Gg=sEB;yo~R5+sEn*u?yA+w`DktK^Kjd?LO0+xKyJn%0YCJd z2h`dGdp8M`>um~}E!+KzjF{Jnr$n}ivu<&W@8FVdhmOGJpw+4;cz(-PXi?0HwN<8r z)^j+l`#x4|KDe-Y{q4rHip$JRYsP=9ojI=u-c>p94QIE)_KouD6OA=~b74ckRd!an z(IG%DXjX1SYi2v@^X8nxv&u;B&ukHVAN_2(`GD`fBoprE3kCSB*97!)bU}Vf4cb9t zfIA({ilg>gFX~dCd?UC|v<=sIabIdKd?!M9#)OK1F4Y<27O-IYb+|vZ*`Jg}8Vis0 z2}@4%pHCNNkv8#DvgIaj%%k7B_SYc%k9lwH|Yq=aUyf^Qn@p^J{K;eqtXsDTK0qa4i2gnm z#z5C;8^uMP*WKX6-6c~bMJ2R-=D*i8HDFO&+%G?@wyG&mZJHdg zoWh&IqdnM1vLF=cQ7FK&(93N(fj_P5m+y-7WUQ$R=WXZ>o%aN4eEstLb^(!9^vQ(z zZ^yNDT5kl%mK3xqR`n282A~i1{#&+v?2kFS*{?r4IS_ZM zU(RTk1Ae=)i!;KnXFBKCuUmrN3tQd`RA;RW#G(JEjgJPNTKAbh>y$h2jOhh`jB#n; zl_F>udmjz#UbfwDZ7ak0xC2j{9trF!`pkdbvWGE-O9HzwPMW6`G;b7Uk#_-{2{M&a zc}!oJWsn5`?^{9zMskfS#^;IA)zNBOlNat``}sP4QN-~IBIA3giZ12IR>-jaQE+v72ZJ8pT}f2dx5ui@rMT(9YT^2jkorob(Oy2#aZ zTH6paK&PKTBQeYtR4Og0h*7DOgZ97O|9j|mhl@TDiBd81H5yf(1eHF$4-}iVDsVE3 z=9VZlE%3CjJaE$Vqt0uKaQz7Uq+QVe&*y?lE#fp#h`}_w1W7Z!7 ziJ;OMpsweaEdhm|2&@AI?k1`=Qe+O)2VWD9xjsMg^Jbi4td_h(2;C-+ONo;p$x4{eIdp}rR3v*r>NEFaU6PUFlGi4Pd)9|m!0+-ve4VTwm`x< z%s>Y$n{8L*M)eslK+@4lIEOM#fpw-7{}KV8y4st+k&4y-F?DbJ6rDb+??cyVMop+Bz3J+Ko0eh?O&73Wr6kpqFKh9+2} z79eywU^`M&3v#Js!Ch^EhI`5TbVdG4ps4#b7o^TrIFF+48uM`LkZ>Lk4eD(w&ZDSX zGHVixqKZTPz#7gYbSPfb?canI71ftqZ50~s!btRf)29{TCx-zuL-KB3V4fw>&e43yo65)d4XrErS-$ST&8nH-FqYQR%^XxABK*S*Yk7f znA=*)m6Vj`!unIaC3uEbkD+xF_(H9pFN|8I6I)0>WL=J!9TLl^aEqPU%jmAYutm?p zwFCCz75Ul#?u9JKF>$j%*~ZGL(J0ePot*KD)}rn)!`h^I2*|G&<*BNE*pMu88@COy z=CJ<#q#xEoKU~=RD*a3-SZEmh88 zjFv;!BR`$y$M-I~BG32Ni@jd8#x~5FYKA>9=dY0d9Fp2m*!c+#Kef{s-1R4*7^0~iwC1Co7Q#q1kF-Qrd4fU zD%GEhTd}a3hj}?O_ zUzrjAs;}DkF(D^1#TE~|3e3uU6{dV;@_52L6pMG_j_k=-4|AVzsC~hDqGS zyTz$uD(rEru2gs#6S!I2OaWVQ<>g&CGxY$iJJ?raJis-97I|)3ZXWqBRVq32W7N4= zTm=62lg^{kkMT6LZ!)#%$7oZoYrXRb%FG^L?Hu3eldi~X!vpa=U^d9R-XZq4ble8&~=@KJ+mA`$2J?r1y9$NZ1ws8t}c4_P@2h(&`dko_93D7)`MiP7xuiRde}ij+@r1% zRU(+V9Fa+L#azwi=a_^ecxc1ZzHvX~c648_(#Q$W`w91BBqN4I0I>$t`l9ip@1_yL zbtMDWCH;~pLqPNo^52KIcEE&4e)Q-)U zn60O^u;;%lL+6#V7`%l&g~QHi@`Z1}pp?2{+hvt@NSP9ziRbe6r$MJrEz1B6JqBHxZ<^4R|A$Qyw_0+r%5p*?%)`Ds9; zG^55T(1x38s75!?C}eBWlds6L)H|g?M2d1PfM{ozEDSQ{+;DZ~PwZhGjCn5R`-$Mv74Bh^nl11X|@oj99C8Od_ z>j49;!FG$ZQ{rZ(&)V#~*oI9f8n4Jrh;N6EwkU?$!(Cj`n~ml9&Qglg!n+W^Y#?ab;v zU~%u1N=kX(ah~}q5l?OHua`J0mt7Bff)_;9e<~XU5oc}V(blOV*0Rwe5%wtePMhnv zgt46itqfi`j#d*soXYvb^1J=%Gr3vy(pu-2Z9I4j(RO76deVfp$32y2Jx4J_XXmJ_ zxt$|!V5qZ7r1p&pn74u=?IVD^goI+CP^?jAPsWp|_IV<$*08*>pHK@0BH!Sl@rl%$ za*nCJJg2ZGx*OU?EqSO(A{~>)x^txu6lV5Fgy*CCsjdQ%d_7V5%ItTywBRl_u?6}Y z$|PCr#Khnz zZmpl2vlgSV7G4Hw>!~)*i|=XhiEHSI>E!3QSM|7U$lKeh$;>EjE$Vw-9Fb335HDv_ z3nIQ}$AIto-{IDCmEwpGnsgw-u4h|Gx{(evh+e;f_R(i-@6@OE#JhFc#2E;|yRQFdV03#Bb1eNN6d47;xLaicc+ zO+YqrWx%Z`L~-k6y&>9lPhLk@4l!{v?YIB|BVU0fy^%{V8&qmvf&}0bjZnCfO z8-d6gz`G*~k_?M)=b*1XXH{NWD`EA4Xjd&epwe+Xh#eqqqX)Wuv>g~cJCN=6I6}7D zSy~iJz@IH$k$E3WBRlX8c>C|%f7<|({ffK;NOo=QYDo4g^8J^h4!z*9*HJ`Cs4yqS$rLm&+M}U%xD;2MW6#EJ|;K^fVCj0SzUKm7?Xb zYLWEhxR%0hHF7YZ=j$ylZ#JkZ3_FlbovHFnvIYtHr)u}zOJ}0~NHoqe5c4;~z#==* z(S5=3ZqLl}Y&%7?c7+uV@G3U)c- zbD#N>vdF7JdJ-h6abAy0Mf)oON7icBNU15}vGzbp&x41>Jg~M@)XV@*a?qa9)|O^J z5PI;k{0sOw77arWI)EOGZxSYatR#C`2Cb$+^x$Z$C6<~DS$!$Y#gZ>Zz%e9ZD*0!k z=O~MZXp9FXCW8_=BTIv{1PwCaU`9YAvw&(xBxpVg3%)hYf}drW@r?6i1Shz!G#8rF zG9`)=Bn!rYGugCw&RU20!!1XJLGK8lNb(fX0usPXc8dTuWM_f#~c>Oh`y*0g! zBfL4dE1hQ(Nr#JSaMw1@my+F7f}1Yn+?&^Zh66Uu0LmCStVT`xkPh6SVoLe_RQFia zXwf+&mWDdxE1lPU7~jiZ6sB`I$sKV?J(#^_RSHjl)yJe`?bxhIGGHEF&mn4KWvg8U z!#oTPN9Xly{SLX^faemOJ^pWe4}@QyZy6)TW7J;t@gh*SDv#p3ZbVL^nVYC`AwGh; zk9^t(USTzHG5roBxW0%$cpp7d^ic8if$2TZ7LmX{%#R zYDpL47mJUSjYV9(A)gmbK>REgQ!!qnePnz7!JlBVEkl;UEr~7dWz6Sbgwt#Bv#g7O zFA$?N_rBdo<6^tlCdP%9^|381?aqUQaA)b5f76ddBhYA>Wu{(sfy*Yu3ata;cSQT8 zDnhZ^nb`UU7F_-22ZxN01s{fL_F{KihITqdt^uzQ`01jyMV?-;1Z-Q9} zIXL$0hv*^S6|cL9=u4^?3M^5}gs`d-%|rQxwyMbg*l?62tGDb)aI*i?!EDwR?~;$( zxhD<20pqI(ao`Q&tECiiOBnzADIyJhRJavWtRhw)`9yO{l3UYtwy}H zw_nq$uiGiMDXcgi-c=^Gw8uVaL-rQ@u^VEyj!Y5i96nBGMHbc_VpBfV%*|Ts{IpGb zIxJ@X>K2^C^O(&MN<@oWc+TeOj;q+F*ucF7L?8CxEHc2M32kpbfgbR6lf-(TQCN&D zFBW>~Qk!C6AH!!gBK2OMchwW;^?1R<+l^=$GVAEh%kr9b`bIg+GO0~TF(1VEeA<{L zrMfQ5KWjgw#4|!VJY2zzB~(wbV1! zlhM?@*TX|k?^s2i3>v$)d^+6{Up$|Zy2QsBaMmbbUT6&M#lJWxNi;uMtsl4RCJJ?) zlmeelDDDvSBQdVIioHn>uO2<6rxaR$Q$xNDG?~^;b<-1*ys_I##3f>#v$~P9zV7?S zukf8_XvF%p8ya=M1d|orocCUv8<7x!-KKI?9yt?D-8vdcv*6n6W5$?G2GrYgp4o0w zXP?^5*=*|k>$X0b8oM4(Ag-)=Mp-nNX=?GS?cj<$2sa-a|Qzv9<}He0CoG z>FD`K|9*3leRdt&PnMQ*mv%gv^HxmioD#r#?@>)Fh<_3oT<7J9@J`Z~6b zc+>wmAM=9f6x3R&be?OM&%lTth)SFy=c2J+dOl_gH|wqz-`DgM-uA<`*lqkIl(6Ws zbiOm?X|A1JJde@RE$nc!lhAP-F*yofYE?$Lx#?@1O4|>-bf;jO-4?qQ_qkiH;eEB` zEX`x4XjQyp;&pgt_Jzf{N?JCTQ|U}UdqI7c!FC;_40M}YkVVC5p#r? z!w})nQN39Z=XHOe%JOPteSV~TFj6lSjAx-rz=QgbczIH3i=#zgDR5@VA zAP0m+V;kR+*O~GYU@GA2Z>k|VGvVyK?&e{#KiTsB*Ymo6KkQtF1%k z+5b{0K#SK7yOwBq>Fb3TXO;KKKL}FHeb%Z$rtIhe!RnC;Zdrr03J#?MYuX zulwyg?T&>#_x|jI?zWhpyJ9*FF_08-qvu|7kw3vsG2YsS{ATbPdP;>ys3VRW+hPd` zOsJ(16Vm#C|0m6%SrLG+YvK7l(D#kzY{8lWJ%x0CBlI#*zn*k|V?;M=R&2#*k*gv! z8bTa*tc&Lg<}zzDu(32hM*_|50`_~L?Cd}Eqy50@;q4Qp)%o+ff23jrnw5|E$6U~o zJr0!rO<-R4n1|7J;c-_QsA{IT*(sa_Lf-t~c$O)*?sXbk1uF-t>yw`jxZPHXn`U)8 zlu7k;cU6G6spf^D=X~{5G0r}QIy=~Knf=$V&SJ#-mK7O<2ZF+wD|Z8Rf2zr}kX%H)(F_QusdT z2OlXdX}`lg6VI`2Zi#wkZ(&xfe?R1O4ELI|+x&B8`NaXdcu$_K5x-WsrD>S86nHZB zG)_q+ZNet{KYa`JNnOk^!t3cUu~LU&F56;ibz|&x{z{;IV!Vq)C+@q9*!ozNAD&Rm z^(T~MPgv%!6!d&rJhbOTeWbxBpuhTvd;lMqgU{@4=(a=o^{*RRln*wruK7ZZ7_(7d zI}g#13fk&6LjY#*4#m>&u_AcRn8Is`IUg<;D)-^I4@d%EoiANN@Q}%qK1J8T8y((Wm3}FFxQEHq6T#kJ=2y zjB;59{9x24eO(K9k17KuF`FQ!G;-;r2TvB+qC@|~S*y|9I=rRSIU4B$K({-3L3TL_H8Qu~*Q3H`b%A(v8|gFJJ!4<$5W+FV!$Ha9=j+~P*8TXoy|*He z4K46B)PP=RVny@t^{9HpojQGRDi#v=gwq@2~x|gh)J~EO*86c*!PxTmieyYTO|hsIM@Z5%#{-2Po%Lj2o^c|OqA6i!Yeobda> zlxnd;aS*s!HPWxJUE`6aBKMYetposVs6lL6EYAI;)6RDu%!wsj=*D2`hJq&$whG$LS!#U{tjtTRFRTvjmNd29Q-_G!y zo8p7JR^$bLgMLp#9{rfp`tYa~8~l1;{3hPS_lfXNmD@S++W|LZ-3<}6uksEHfDL4H3>D=&}_vD6ukKHpSPH}~2-y{SlEg(v9 zglA8L&m&BNX#{~h`6 zSg7VQvQVrR5nAST%=&JR_n};%mj3Bxp>c{uY=HiE49BD21mZ-^W1&5uERq>WkT_TZ zl)Iu6=i=VGP>QEg13Jg>{1uOAumA&e{1YX<_@zGba6zV9=|AZX(Era@ih||4uKXY5 z`D4zf+z;!|{1f1QfU3{+{Ya#ElLu1!6F-*)eEqRE0_+G7>H8!@L!fAzqtlE$7l-NQ zfV-QTLv})7zv#Qbe*l)pN+w$htXT@^Ld>aZ-%tGk<0l z;m1pX|G5#lCtr2)rVq}r^luJ)=AUHBsJ~OZO{CpxK7^N3CmM3N8MrG8o=_F-zo*eK z{T1gKKPy@l*o7GM7p5J+0G~fIS;T%eEKN+D$Vs=B-ibY!$eQ8KFNeqY+nE}qJM_xk z7~MhK59~qp@1dKFfBjAQxmyZ8U!D9~{^z%7XImWFx3@9!eG8LV=%+^p=cl4!wCDkr zE?z`Tt)Ze6`cA$czbjxfRp%p8sp9rNR5-Pzw~5n9PBgI4qCP4$O?2Xp+I;vK#L6Mi9J^|8K9(4iVr{+G@msnJUt3@m+u~Nt~tB8cH%O zK43vFEa(L%T1xLNT|BpvImBN1wqU(#k@d<8g3MB1asvA9OK#1x5#6;bH7FC!X(umrDrB!EcpGoW2wY~u%*=IvY>X>m@BO* zbzqEWRLM@|#FNsz(kC&lZ@t4xH9d;vBL>>44M@+ar5c5BqEx!uIlFO&=#Gr<&VWs_ zs&qkPN;GRmeY|Kct#-Z$U+)N5jgC}vNo}dYRa-(j8dpjlAOoyCJlV>XRG@@bzN&GA zTA~TGQ$K>}23iBGcMd)^PRv!zS^NZwDG>&(Jinm(@&8yx%fTBur-koCd0IjD&8k)s zol`gFC8e=f{y~6dh>m6eH$byT>!$*9fxX+Be&fSlxDyZh9MG~uN4Fpr4zFS&#k1H% zijY(Mig1ccg1^h9_#nPZ{=4OGO0t8_&@VR0Fk_5JQ5<0=&|3EU@-*C*?yh8LE{JHa z#(sf&<-ohCa^MfhPI&Oau889V*y{Dc-ta(Xbs?QRzW+B>*45njypM#Ws zBS6_q$L1jKq7Wzo;W@K-j!3FL6}iCrRj32Mo2j1V0ne#N z16AvO8Q8n;(Lm+8)ySyPvT61%+jEBUqJRJt3^{IO-n=#(>GmJ=#l_#%LdXHqWN5So$0}1DD~f#H*fO`2Mv0|Q60aRA z%v^wx89=oG;ifb4K0%KbmubD@R^$2{j1Wfac=H>9ckN|p=aRtWeHhofko|K|}6#)eim zd4vY~qwPsU_Qe|P)e5b=f%3``f#1k&Se!TdLwM3~CIx;wqM%WJgJs{hBww!_>n9H) zBHfI3vte7d`h}VuK0Kt)6yj^i-b!H$J5KS;+ADW;QaMC_G8X}}dXh#k9-lZGMWVLZ z2e~aQ9l648&i5oo^^I%ML+`u?u}J8AM$}~?orAjx(yB<;CFmHm5sae!VaTlGYMjo% z`&&8RI*yxBbQp5V7^P7E4HEpH;3dKNirG}s^b_UAjc@e-DTkXtp?vZ!{1!| zmM8PxIK#FWQn72zfajNTg<`~!q1Ytde)8m?-KGx>e_;>0_qtP&zn5+DxqIbbsunk4 zBc1@2NRbuz)6NGa!Ls{1M)3a5-ysSlmGsIbouQ)+sXU)3ZA)Gi_A4pR2L=ccARfqc z*u1sacd@o0pRldb-vWMXR}44;y+Y3+?5Ge*ylk=b!v4?i8NVpXfq3kHYnbL#%;UHL z{-fpiM)cHpLCo(-RJ}ApmkW_>_{EWF2CO#Yt%!{63(F5*7P@Ts6_!_CUM$l0vdf&s za=#f*W1SAG9vpgdUw%(VIax}(-S8DcyGl^p_mebY@u8#nJuMGu{LtVgo?Uw=zvu08 zZog((F^RVsS~y!mE7kZ{pMd&qyp-srUoH4`Sw3_r7Q3q24%3?)g0Bo!AIoYOVX4BHq?Bfm!#D$pZh`53dui=egoV4fZEHjd@sJ^@%z8AGbYI! z?tZlR!SZ|$=1G%F?g#n(oYV}TTW$WYk@HV_UKGtQoLh@Cw3DCC3*9~`ik_*L$PZ8Z zyM*O;-l8^!Amf-_huneCQ9Ln*+E#M;qxO`E%?GGfAnFEk1X9nY07Yxl+o9vM!2gi~ z|3^K}xR5PDaw{w^KSCv3oIP?~^WM-s^%C8M{YCrS4`Q!PX)E5I9pd%lSTsYGD^5v0Q^n*r_+aH#H78%2b z)G;JY1hj&j6Dq^)cLQ1VM~pW-WT?BAdd;*lw@3EoBy14zr;3IRqFyAgOtn~1S`5vI z)jF`+=nUo3K~4aDGp!W#L87G>zgleey)R4tYC-$`8w5 zhE;ieRbD;xn4=Y9ic}A@IabM*rfn&=cc?uy_YL-CLzaQDB z#8a09>Q!DzUIOGjb1iLu#M8+7hmLe)P(FrkCjRkV?OSkF`!a^M&pyyT<9E03tGr!4 zy@1xCH+&iV1BJZr54@~u2ge~3PwXW*h)i?jI`3JxB(OJ&GSjK$l+S#BpuR}!0p-qq zkwv|zz*rEcKXP?XRJo}219XQyE}jy{s@@#P!ylX!x%FJ;ZEaQRnHEHiPhzaIV3AiP za+9xbYsS8@zY)2~pTHjEkh5(b$1+BXC9v|EvI;xR$OvyLit#1tfYvi|vxJ$DV)orm zv>kH%I7VD+Kgn^lc^R}46<0?i{*ShJ-@16Nl>8!#T&J$#e51ta$p&9WfzYS>hURdG zPjBS5z;9Jwok#Qe`!-gGoOusn{k4cC>3wSVSm@^MKySf!JdU`LGh92x8LW@&_km0W zSnOeW_dASv@`uvv6aD$!Tg!XeU)9Cgj+qTIJYHde^snRDT}u zzkGLl3f1-q)Ba713Tf4V!V1wUP@d+AL+e0MyV1f?H)*YWw}xhs^vhK^Pr4RSTEuH) zALcdsm#vO^jgAhqdeF|rn!0IR)T?OVZs4-!x2awu%pZ+oqzCYtK4T)UP@$S zZw<{6{59X%_Ij59e@&fAbwoSw3{W0;rkah|B<6R2nN2!ORJKD`X^IPRv>$Vv8R)W4 zm8DHZqFbdmlBfCIOH}QHO?=eF?*|9xbGsjRK=YV5v6#E)b~k*(R>rhSmP^?GKL*-I zf){6grJXs3Gl(a9>FN<2bI?uJ_q_z|i8!}zaBdy-Xyc@(s3Sk6A|i;unW^+4yw0qf1-*jQ7A{REKfGY5N} zPoTY@s{C=tR?;}UzEHCTI@hcp$&AM z>V+4gu9tdmS?oAWIsl$HpaUeG`>36pxYe25qJy*@A;yLZm5&ffx?@^wC{L-EV!b7j z7ddxA%k1pf(2a^0ZvcQM8`+zmH6A(o*S~%Cc-u`^>r9vAYX^J<`Q3lXrg@9nmG!FU zY@Sq|?;SXz)!(4%#gmut`}g3z44?L$haFA&01LhKHdhjs?*aeWj%+aEJMTyM4tCqc zs*UoT`RZOaxhR|eFU!V6_|q)4EbZ_)4P|Q&ZO1+hz$cC)onmMv`z86X`ZfTINV+&O z(u|sRF{UwP)wEmaj_r7UH20GxNq;?o9H}Ur`ojhAE@U^udZm|fMyL(KVE|W zIZYD7E=S=U7WyCH_X#0Rx{Wqx-2QLRTHHCWq?6jDx_p`|YL7njU}OE@xrwyTns$Ck z)E5~ud|yWBS5`}zhpX>HoNbsN@{;Tb!1EQfTH1T9m)nXn8`4C6o3O>?<+kD1#)%^KL)#*tEWeG8u-`!F zP59xpdTH(^7nAnQ_j8sQ;ByIMNx68u7;P8lVirSYuG+4cxTH~IE%vE50(2XeyD!c1 z(s$@mzx?TyY;OzJa9ZzV`nGa=@qP%a$;+(xwVvBYakdyQ_cHy`Vs0$7;SyIj+DmOP zFCKxrXfA3&-L_!hQa+!iPY;edjXu$k0eUDXRMoG`^NW=`u6wf7 zplYLoe)I~&ar3(;lYfc{U9*vS%}+99gWqxnaMynM7gvO?J7Deg%a2~+Ebz5Iz|z1` zCzd==QnI7ub9f=E5o3G`=$u~`m)u*}DuRsS^8wA-h=P zt5~s_dm48<@FsNgJ&h-nD#qFQwl5w4^42@@FlV8g?@~E zZL7?5^o3#tbyI&eorU;-wSFqt6g~dfmf#qiW}0l7H1n@n@p z6Iq$5#~x@=ocOaj6(GBX^ke7k>W^iB0;V)Lps=er{&HBtPQ2?|}x$NV1Eu zlJ@t@?U%1Evs5iZ>zJs9Lz%zA-#|1SmcMA^9)Lx;624_gx6*WA!!mRJZTM*89E~rM zos5tAt|ENrib!nyA6;0ZxZ`fQxF_8|&~?;HAf#N2X$I|C&d z^`Y;fw08Y-MB1>QF`hnjRruOZC=b_Ddr&_#Ax$^XhrW*J24sT^qNeo<16r>hZ>b@A z(7asx8r>^#x3TVUF`i&h+xJ*ov3Or;Km2+e`u{~~F`_#*Xe9^RIg9%NDtEE>bBxP< zZPWw${dbh4z8Cj@Uh08As9%1gH%6o@t-aKzOn8puksoFUm&+nIC@BN~5hvalEnrlZ!TLnk8uEw3zvfb)69ufAUxrK8X zb|bKf8`vdzNu-6FAeT1z6RzGR5BcmnVOJ!mT5!L75ViHo&!~Obc2-*>8vnRMT zpum8uPP!x~v=c`I?Wr+!Zoe+{iBb!ji^HyjVfmvzl6dcyF6@{aN1p1ro!K|gP7LI! zn_G$*O)^g)PMbObeD-=_8k6Ec4%%XM(GpVJw=TRGWb#keJ zw)b4rF4|Fo+V(--M{U}Q(fBo<7WbKdh6z5j+IQjSM{8=EN?BjrB^oOc)_a51kYm7G zh(`GNyM*kKe!j3EZvme81(eqcU&S2cs$n-*;46^L zX2jkIu7j+FB#`eQ%BtFfxcs?rFTnk zNu%3#01pBb0lft+5uOJl#im}txj*Fr&7xW!@B^8;&e2oV(YQ>$&1ukqvQ5AwV9sfl zFcxzXyM#oupD+6=4`Xo`=w-F2qJ3yAMlS$`kl)5gW8n&qoI)N9Mp?)}2;~AdBVw-z zUhRLAo%HFC+jQQ~{MV3YDiQ6=qSlUR!|a37h1sah2!1?!UbK~}N9qENP1cZ6$s)O? zjtY*xz=WE68*DWNs43TWB zk>1P)i}B7ZMP3w)eV!MmgP&Sqfn`2u*vj>p)3`q7GHi(5bWRcK4bLMYJ(-0p*sId8 zsop0T^j@-M&MG$I&7tv^fRIqJ63n)$_56xE;cs@NDRMgY0vOrCY@^R*E}JSn$rO29 z@PfpHv)_p)4c_z5mfOAOS158k%1>|ItjqvPQ%IWHiV|gnyI{8Wk-GyfA@Pu2wfX#3 z%G*G`2FEgPmp%l|v$S<8YPy0jx;Rd0%n?@|E?xFAtW3l6C%N6u>c$N5*zaQNazsV` zB(Q57^G17h$V1Qm6W+P!2)*WsKAnYrgY%jNrO1uA`m2kN@2^9@smH&;4r-QqM2l8X zPBvv%?OCB^mVB%f*U~kbb61<;MbLmDW?T0)7gBfTrlkH(6l$0FIHm(uaemX?ip~AV z!E!aL%HG;733by;bz3|sxjtQDaYrI&Mz7~9HU~xGrr`;HgNDgAorLsO;fc9}ex_kP=EWhO?ILne)G|Hq2kleui+MUP`L z%|CWhB)qgD&ksy2<;CCmo=dVp|9+0w=7}=zZ@Jj_TIRC5#MtMhWn7>B0=*B1tm7N{ zsV>7-L*JNsL!Z72-)Z=E)#TyHpq^8$fB!0`j*W95SBLjnMC^K*ngAL_=8Njdi_)zi z){uum^(U+m#cqRYIc7Xa83FYxG9%eI@TnE(`%F9UK<+X({qMi?q_`A=dtt{~{f~Or zToAg-^`bIS&EwHS{uQ`0C2wR_VpOcE=E1N&i2E%CbD_<0dPq@QYFQW4Rl85QhqtMw z!6W8H-U5(_TplJdsr*1*&x2|NGMeo$kjbO=hkx0DaopqQ)GVG016e!?Ee=r4mr5P( zpE~URM9bg5&g)4Xb}qy6(bu`%RSreQt`isC_fmXZ&7d&ph0$+`%#&(144PLGy5I^TM7}(~(Cbq&XZ|JXQ3yr%BMlwkx!$@H~RTK{1z>XhNWv6G?UfcHI2x3rYxFb zv00hnZdUI0G%NFwbyMuiyzhD*cwhoV8SJ$j8b&NzNPK|$@v00HE zLL(o^xMAr9YR-+D6-V|N@kw=zEw3i8NAoOexho3x{}a3I)7Zbrt2wxW@@g*N|IoY| zMSiyP>b#n~9-)}#`RcqH?~0*$HS`XOl%emSfX`S!G<|=V*{*HL>v<=a#;MkC+WMHQ z)<1S{t$$n%v&12W7zcmrcI@~99VMwSY=$TPNj*hv4B>3-GtP`AZ(dK8gIW-!bjs%O zK(o=jZ{)*+7i6FIJ_*`0Ts?Fsx~ zj!zLc?e!r!K8nnBM!h^37q zk$fL*&q@IvM~hS^juth4ly}86M1bc;-g-$-dhtk9chq>@1hFo@YqHrmI;n z7DJ;&Jb!p^7pG%cZ{iIUVck}-DDNMwc|FAu8qih_S%TUdka08(c-sNg%kxrmUYo?V z>RxbqtH&V=hGUzyVGSC6$O7gO+o>my!I|G}W4HHKUW@@==XQzPxDmN52a)@^#Ao6Z z`C83r5&L{u_Dv;iL$(cJMN*CnfA7$(9ewAM8T%5#}?g#sB^OeC{wq+3WlLQ`a-+p8L7a z59gkH?>TB!UBCFbc>SB3qylG3%g<1uw5%e5q11-{uY1v|a?? ztoWh-@78Zt*yqO7)wI7^@vDwH&U%Wwzd`r?1NwWYn5hju=e@gTK(tl0J)fA=yuHR{3H; z4i%02*}Gc{thd3w?6At7$-+Ah_9r2?T~RhB^e;;P6FeF3h90SiRd&2H>4aHpYDCrF z!1+IuT+sY>LI37X;aESWfFlr?|F7_VZwl6`>KnSxKd-!5(=$0$$neE-LK6qc8v!AW55yd&ix&PK@P^93vH$^bv6X<5Np z2Jna4@fpwxpWTj4y@(u7E`7ug{r>|oYyF^ZxSs5G%}ti^#<#~y&5`%De>Wc3=yvQ^ zi1XL&n6*95EE(q>#92C*sia*Q%9x7+?}Ab;ZN!Qlu=CxwawBw}3zA(K-q;Rw&R7bqe`}|U`i*3i{5LM- zNv(~?$r{Uk{kxS|(_D{mhmQHbwik^hj><-JcTPEYv^Uq9jSapbl8oTE#shpJ)ZO6%P4dH@W|7&rg$cv`itza6P~i* zMq3izPQW@SEE#g$l)Wr9Ar5ce7f-1B>Z$Q*>>+;j)!KgX*q7Nk-($Xsd@VZz+#4i; zf-jMNQH8yPd}(y$Cohc#&Z)&qBft8G+FHCc^5gygl@2eBIPwE;jgGtc(x@wX-x1rR z+I&VTXEX!$5a)7|cxSzUT4Hp5muq*RUMS=J`_+JsQtBtAPSTZ{Hrr@eEeOB52<(Jk z{Z;MD{yEypO5Id`^_R8e8?P0FmHgFNU{*cEP11N1a1;g4I3PLs?^R84+qZk>?ITb3yk==Bw+{OnGc_Pb;@*HrDu*NluaTks zxDeW%<#?mc<9PH<%^Fe{XuZNs6zu+a`hBqRl;*41rHH5Iv`F*)H3u6%(R@2WuUaSC zHiA|wlQv?}%hCEuDl8k|_0R^iy}_K`)(yHM!&7Z|!6Pkb!rrW*)AOFN6x_sw3x~F> z$oRnBJodxR7hwq?WnPm3Qj+dnX#WOU-;heAmdZPrhm!L~y>qbfU)pH@^(DoUJgXD( zby~v>7jP$T){q)^p6gw{hrrott`)mp{I-O>@^J27@m>b653d`%x^^9_le%?ErEVQ7 zZa=8%<4v#4n^*pC&+UV_LI%!yjMcm!SU=ASDRXk3d)A%>VVeuFS8Hn-_qr%qk&$q8 z3ch`?NhwiP=*^?mIjLo}-MXu`%gJ33wikB7RonFy&d%S?ojdpbxpQ~_M25b$Ol(-; z%mbG&nvSc&3}P9Z;7V=%aq|=u*lDy65j8l{$ng|YHn3k$o!KRS6W>3gTC?X%!HF@ zDSo}LvGW1K-FPW)NL1CGjT|#W;qxkdRCPl1o&pGb3bUW@nm~`A*+Jhx4_4B;* z8Z1>O&wLf~CR!sq)M#5nt-F&O;>TzfyD*oi>hBv*HJX5rfQ(IOry`tV3L1B=QPmvn ze!>LbooUeaI0$>bC!y1>B*pmzY^v(_ha=3-dpf0j=+7*zoy}U#ZcKih`-LfJ(=+}I z?^+RBxB5Ih@zOF3G6fay z|Aj?g9|HWsPv@}J(2||wEOGV(2chEqzrI7KZX8PKUxy|GF?gh=OT&0|QFvy<9cz-E z&W2ujs``Nk_TIXF;dv|)5Vp7U*Rk}&@5ASzkv+PIt2kx=*v*SBqEFYu^!Z2Q>3y_e zqTdwhdKj8E%`vb5njPO6H_W@#IDAR@p*5}ROZYur#deE_^}Be?irZhsob+y6;oC^> zmy!4?5_21MBl8Z>3s`R`DaE}YZDvu`JP&WcLlTTTW2ZH(&FniE^&yt|%$n6bRsD_U z3(sJ!@8seY7sKI>*_scKj;X1$W+xrCp#M%w;{U*HNL9U8v(lIB3|JwDj1?=M8SflB z^R_(ZuA0b!(DsbqDLk7)sR!Zq7xalzgR`MXRTDf8pVbvwV!dx^_ATX16N9rly)7x{ z4CwolcBCr;_M}7Yi}Fm$5vl{ASXIAm$nfqNjvbWtvv72%=FDQlNa9T!v)tElG^9ao_MatA(7}?ehLRH^yn1&frU~W9r zpjgW0Z1PXiSHMd4!2{5Lud4eShCV^~Ukzb36Izz$WBfPdLq_PYioh(Os;^+5uc{@8 z>AlJ>HH>Qme#cg2cug`V!fh&7q5t3Ei^%?IO_|YW8f~HedY=ibEJg&P{S;Rficof!=A#IDojFyJ}YniF4YwK^TH-|f~iU@LqpI14c{ZE&WAYP4w zW4`qtr0vnn7ANxT9q!H1`ecJcbn&I#rNTj?6Unqrn!=N$nSYC>& z7_$uKSj;KBrwJ`Eh@Bh^Iq&B89lpX2Dd>HT`#0L)kGag0x!CMb=eBkjX?{WfWEsy z_FWfR^P8B^(vyoIFFCZP7OzFq&=bC{!<_l&%r{^4o3;IOoYrsS#sqq5M0Hj}d>-~O zS}(p6y>(-U81>#-(7}(rm9fV02HTJN>lEj`InZzSozXeFyygj!JQKBI@9=fB<%@3v zqoyl(vG`tGeb?9NS`p)sZ}!9&pxaM0e_e3gvJce4I;>31Z{6VeOI3jzFZ&$62G+AM z;Y3ciXu-D@5jcf^Sn%Z^<*-&r7;I&#I;+m%+cihyWx^f4-R?hNV^$L2il@Xr28 znjDwjZyet7`Jw$%v3w1`)DC`~EY;tzem6qifV~k;wpDfA6^A#rLny`a@;3o~Cfw=p z>NmrdICQ>g@fC;fqs#!`)OJM-o?Lm(U!2X-qeV@BvnLkou59YDkaDQH|!QMU{z8_?1tmy(Y2Ds%oZSM*jK-tnA*C9ca;(f7TqwUg%NU9%x?oofz`lO0m(5 z86=l8ig%j~4Q;frC*V-R-4P+PNwV{VHk*vDb@(QyXtN1sp()ANXtWKZ$LzBbXVht^?VtGWHLr!20nbZ)6xzwlDH@)ib5_1Br*$ro%-5{Ir zsbAt$*<2Y`Vn{+Ci80jKgu$Ab&M-<1dyTTiu=#|4b@mCryMR5&+-wkKk2B>W+!_8X zZHkk7s9|{H$Q_hVeW&S!eJ891RDTTH4^522zuMB z^T@wCGE%F?^)Z0Y4TC+Aeeq%7Vo&2VaD(uvk2X0p9@eK$Zi1#)+>PDjpWDvG`qVK^ z7Sv*9(>edfm|vIkx4lW{WPR$rP0d)<{8dBBSvR}=Y?a}N?+$3;6fu*WqDC23WIu@4 z8b0+G$ak9+dCXsFzYToM5SdT(d8`9I^>(!80840V>a_;`)*IcyzaxLk@kYvc;~+S^RdcYrb=U3oEXf z+CoC(NyZAi()G>bbLS42?Nhg7-)utrH6?J$Xxu%@3cqR$=Y1l+R>S=w z*w5XiX$$dGVVBygGtTa5SAA**!lo!q2JNO6Vp7glW9HZ25Qf~7QBrtU40n^)RC+{S zrN;mr1if(ECSlAMp5FVv@bozi`?B@C^TeCel+ew^%XjR1%@*z5LLuys?iy6ycx+Ho zWAY%#=_9!#fv&E`d67ygt!xfxJb+rMXn$dMzN!K#RUjpKkI8l6ho<{A?tE$zp=<8k zZ**K|*=@szf7&iX!#$q))I}2QEWk?D{}NY(!OpkvN(ow8Tf}S|R)=BEz8DAoA6DR% zsRipa{ied_g|_p!K{FMva8Ki9MacuA)fY)c%ixXddH>*;3@5Z> zU((tNK38%b@x0jqxp*7>vS632_yOG5m><|-24y+ib$^>A74Z1OiNyJxexFEv1gfe z7F45U&re>$WBzZTZ9S1_!8opNaOEk`V)pL$fLPs`8o2`#&h(b&R{94QKbX<$f+24^ zZbX7R4l_I7)MUa7hQIp3j6HmZn}^$%0{6J$m7x!2fY!Sdp?tU>__wp}hbXt; zJQX;zDlD51nG&F5jcZKJ1C5#G=2Yg7MU+fLkW`k&myJlLhU!TuPK)gJdmYyGQJ zbt`%lds@QL8oUHBRtXEokL&TSHGatU{Eer^9LKBFFFayh(3`ty)e7t#Lp zG4C*))#buMSsZ70*W+DU#lr|?hUB^^)TT3c@pre8BlTd9e9|76L#e-Ctbf@*P84ev zNN91vr=G`mzqTJsPet@e9-^L+iZOKp7etg zD=T{@w9b|3y|`{3^~U?u*J#PF-9ygp*bw(u8iM_Qmn#xmFd23I;@k?mT$jJ_kE)IK_ag8s-0PcS28Sa!Sg)j#q=N9|6}^;HR(R9%w6xIz&?!>yb3Ts2nmE}D|yk{o|8K( zFyNG*t z{SHg;{)#>YkGFNx{?^~mGcop)3d%Hadz5o{=x@BcfnAB6S8)<^)r{8{d+eL>{vg@; zSNox(JDkfi;x0m`Z}?Wc{pkyhqAL`h_>OVn{3^xJBq&2p;iyh*>ZY7L8tYK_+7m&cB&C~ zVV{04a;Tk=MN~&1j%>#W=jn_~7ouv)8q>g4Mnn#C(&x+t;*g)!yy^U(jN=z$v|IGx zX0*2ge7}IaR!}ozXXrPbQ1?PK@@v4Jb?B^&Zq*J-{fGV)(tYhFc73!m#=)8~Yq})s zFZ{K>v!YwT!&^DD!xp;*oWgAv`5W;FVtj#x@}%6rT)~q;Ll>iUiydcoT@=_9Y`TQ| zw2|x~Es#aC#tj#~0LK$nBYTb}J(ugV)&+h%FD@@b9*1whZmj?uRoh7D48h*fh81#> zQ^A`AC9+K&-e#CLRvc! z#7s3c1#^eHEkcVk#rcq?PYilN+H7?Zc!$}!IuYx9nA9T1m8wmB=I9q5qjncKxF+lU zSBT-mhu^woPM=7rRqhmKg8JZl(&qOM^?AO2;-SX-cjZ%&Tr=vFf1llN^C{S$87=HH zd=GOScQ3wI3vXh#GXlGvMOY>vv#_7sTT7y4!hgr4nxfE*A}2xF46Rp{McT* zL$i74Q}2Y_;hQ&wg}W!~>U@`$y^`B4d&P-xNG-VI z;x4qWKizx5I!r4^y1b)%9NK#C?Z!IPEE~RD3M)+M-b1o2^c68^4M)EZSN2u`&i1rE zkGFOKDb-T}yXo2*H}9ezhL&=5o9wd!bZriurrBg)?+My$f2e~^_OTb&6B^S zXe*7d!OqjWZKb^1gj2&9b7|fSoEw!^b#_zfEYUx49`5obwyKXceLXAPTh`u^!Lx+2 z_tU*49sG5xGt{%E%i{|n6Qt$!P5wkpT0mQWt?CF!Rz&lAu)JPk+UZnqH(eyrXHS9o zs?jL=f685`=@8D0W>-Pum^r{LSSp)yhUPr@E+l}-*_G$6ME5mdjTA|bQm4dl<4}g> zyr~R>Mw9qLJ7-PI=bBbomhA2`_1}7P%$Rk{@~-7=zEAvN3`>E1L0!ae*77_tuTYpEJ88xFf4X;p2+TXd9#jC? zitH=l%c1wprj7enwJ+WleEud!{r_pqdlzQ}$9zKjn2(ay{5awV`0c*hTUhW*)J8lt zN#VAGwBTp=9m@~R>yJl0=VNf#a@;HJ>>`0vuLtK3Iik^*@2V6Qo{*Q$s&OY_WGg>Avb&ls?IZj5 z9?PsR^IX7N)h9hJ{I+Ov>sB?_Lw|k5&-U1T6|T@4AP=lq+TUVA9}zY5ab&;ZzfG3| zY9sprXy~6>sk}lP_R4yU>VcV%7mERSy$d;$0((9&r3SqG9c2~$s{NyjqpllU`_zSZ z?TfQkYeBVtMD{D_^``n2rtta|;jiF+6!ib%UX?I&EQ8-O!*;GY9o7XoCem<2DygZo zH*QYGLlZdOB*rMpaAm}p48v8UA>yhMfsxnuVT=gTk5&i8RRN{ui!1aZ_6ZU=L%yX(v|KW0~=Qb z?$sS^T=5MgG>^_wUJusvTMfaQe!ao&o1A^Cnyzs2ztFKUT8@pEv8z5O$HrjIS{&9t z16Q`dy2#T!TDqJG2Wyup>E35!kp}J}F%<6#Aq2Qf>nx6p{umjb+``K5pS6*}wN)P( zv`T3A?XiXC_QxY5AitnKWVTvh<{r>tWZa2+Zo!cehnp=~SqB@BYpY>5&mnjK5rav97QhML5V`WSF0YYp`u za~F6LyIWXGA2bo~#d&M0RsFo4oFx5q2}u{kY(UIG8~2Hb*`WD;;py2S=0L2tYo*Cp zI>ubC#jM8~ZMOTyci=Si5%)6o{`~Le-m7QE&CG_}Z>b}2AC>o_mP=c@L3^LAxc@Jb zAL(H4GgGtoiCaN;Y8F5HZTAdBFHky1Kr+$!y75{#Ez8i>gSQob zi&mLR4mb*T9=qg*we_5jIVhJt$B#bmiOL<&)N^PcjQPULp6INwwpi=}KS*VmLkt58 z*jSNaCr=r+NE?Yo_TwS;KU>v2*vr%99=iR{R@M50*u3-Gmyezs-m3O_mt$>kQedpn zrX0uG3O8-aRk(j_Q_k))hVY=FD@|}L(Y~k4z1kU;-1$?x?_5l1kB+e1S7QZ_@>Lz* zul@2TmSgR{tT_n(vzB86Yt6ElpK_OfSMa6M(ON9Ve~0vcJfbKQq@GXi(GBhPG{dW1 z?$XvxGs;`PoDlh$-PgTC904wjH_CIMbqQ}NEwwI}+wIE9nd#o+o|vx9|8;vbT-OZI zs^-+$eWz}*MpFhD;OAD99RfdRc**uG-vsgwZ>u^Er}&Rl(jMGPkrwPjO=Tj9nob>+ z@3_NzD;q_>@i|p^<$TJ$8`6g=zp)rE@1i4a7*4+Hz73odeqA*T0S}d8u3sHSUW$4h zIKPv3>sm#w2YnKI;2z-QaFZ9aKT4LuZ#m;@Z>@)gdld5s$I}kVKSUD!r)7Nf={s&^ z>nYb%CujV3pj|Q%v-|Mxp+!}@k9x>$*jt#x8DG753Hja`;Dg*~|10{7^;Y%LJJh*j z_wHL5=!2Z^ZSL+rvNJ3B9+w^ZvTfQ+f1USv|BBtaFh8`9>0|rnc%Q#r3C{0UxzG9e z{f^ytA;gk#zjody_DB02ySL8u(;hkWNBbSS_hg54|Hr-Y*882If&Gle_oVz}zmpiC zYcqWOPxd?Qe3M2e-xS>MgkitKTTS4byn{RBEzrv?GdSO8LZjGfcd6^gyPrPV?#Gq0 zBPa7`c0OIr$>ozgX-GAEBKAfW&1T*GtU#r@wl+NrwpQ1eH1c?g0~EseeZO)-Mh_>Ix%n-Uh3d(;56o9kz=~W zK4AH*w7iPEz&_wtwRZ2^4lDPq$3lSD;r{=~|1Vz0?lXk&I##F7>1h1FxAsRn{tvO^ z?{+IL$9v2g+U7qV1)P&_weN55=h`@k|H(M$)?pm5p9eXeuuCTc`vY%4{-etr0{vX) zYuL~6T?Yz^+4aI#Y&!}w5px1i_NdM_iamF zi|Up8+!ocN?Q=2nbq~z%{~M3N{cYMTC9gpC>lXENqusl%!y0_+emzR#;ak)FHU*JtP)ye`oa7V_`bS>(Q5+TEH2LA&(`gL^hn+NvNd$GyL_HqUgqThuj3 zKY2FV(3n4KnZpVRtH#%ReWK%>&mR95jSHcE;}E{yTPZ?&z>j-;z+QcTpYKu~y0_(v zt?unydgLX$x3EKOdJismA7fvW9~7XCmpaC{(=ltj;9lNg$0Uw6OgwaNfZtf9y&wvI z$^FNGb?FxMHoT#LF2{m!&aP49IASYA5^VzTt;SwAchL`c*N+TaKQQM*-}cGdJ(JC- z-#-|j*Dj2bB{O)EV&i(n61G`fH#aNxiQs)B<3~9EeI-iS@5D{AUm*OmH`)vXx;Mt8 z%xFrwi}EB_)6Q`Q$D|uZUn}&Dom-4Bb0Ti^8EC!{9`_ievakOErz;VYZy3EM$dIdW zUVY}tB&Twt&jJ(l$M@7E`VPDfGS#o6Bqs&60G?(8UI~d52Q-1WnhM9I$+XhiMvSv# zcWcBbSb=a{ooMCOP*RvNuqruEv1UVS!YS%Gds4a4OC-<2nh=jt@J@qvI)hh3+G-k6 z%@PMB2a%VOnv}cf^G@6H+n>ib49D1^gK)SqbOzq^EPVad+HPVdc&4z*JdI=8;*XFV zQ*KVUwnP3M^jO4Jq<9Mb&kT&IpWh>sZ$jZH*+YrA z8q3j=;&NxEx2S4^6*p8xbDQ7pjT>R5{1>}oj+O!bG~dbWpN^IfZZTTYd^!CljMJ;Kgb@)11G`9fz9ZsIamMa>+EC#~{&C%lb zAyI4|ByGI3sP^}`Z%OP8ZI`d(th{*>a+Ygjd*e;fh%t417_?%twr|lK5f^>)TQJ7g_StfZ&SZ)D`?6tu;K399`~qzd$jYrfBKsL zU?^5b8cLM@tL)il@YY9!74z<*=x_7h)=ktK&7b(jKbei&T%#VV@Egna;jBV@_va$| zSTrC0itGJ=@2iRb^wq*L6!DnH(tR;$r+r5z@*Db?WLPu41^ssc|F}CJ^yVr5^nEYC zwEWJPNE%yjZT%_#D8bxV9AR4va1(5AmGQM?*goobn<2Q53*0dQJYCYRtIp`$IrGbz zrMPQ1x6^6r#n$2Pm2DL>&-U|kieTaKt5^4 zo3qoMpTNy4lcB~s(&CP}+v+gYhbyuBy6?~SL_~h<=^2@gGsurUv494^?va%^CI8qH z18Bfoq9Xi`#&2V!dC$k5sK}46bW)=Byyyu(-E;4Yp0G8B;^;j}am*fFqo>6mbJwY= zMM;-D=17a1IiWNq^6H{U&Xt{?0KaVB;V#P9{po*Qwd%V3P6uwb=sUo`ToR~{7t8+Q z3E!XK{EMg4nx4)VJz{?s{A*Yvnz#6Q&y;sg={rgDO-G+y=Rb!M{2Mx$vhiB#2rMZK z>va}B7d&DND1**>MBe+k&*nzG{G!Lart_wcJ)QDC_Joi5*b_GBV~-LE9YoDr{axB` z-00^@D{>=U6}d6IRoK4w7AX^9|J78CR*0+m_5}pu!Ave(8s^UyP8qyuus8O{X>wpw^5v)*_brQ-B`M20BF?HzZ8~w79xCpV{YE7p0G%J zLu#+tjiqCvHYed6c?sUQEo&?ow6t;GX)E-P!Jbd#qQ=?vOB(l$8QEA6xvcTn>59gk zc^@I|Pw<|y0O7jlO4_yg$8C8o~3`xPQWK?Os$sy+(P1V2>3Vl78C3*iRCe!-B+9AQ+ao_s^UmuKOJH9^j{oPRR4rr%q%S3~n(H|@-ivM2Ji91XWsh1DMu z{04j-EDVYiqs3NtObZ+5;0Yni8wjlo3(Bn4+W+6Dr;rP?ll&JYR1WXz5cKAsup!lZJPJkEmz_8^N21R zpet@dOP_qh`dH*;EUXGr;wPGq6yWwcxVhd7MgJDnhM3~gcshaJt8o94<~3WN;XMU` zvx$_*t2V42xNZ0#$O2DoRq!(SHtjq9OT+ZtY6?awzD?+)8ybI>|ur(xOt_oDLCA9jXqD7|5h ziL5V#KBSqX-uY{NE zgxq-G#cKZ|8Is}5G2!(Y&^DkL8UDC`1j2vNblh)M7;ksP8wjn(Yu4gD?spijsef-W z)n_VWpouEHq*LtVrQX48ss1)k10^|s!Xl~XwLg&f{`aI}?;`(UOWE0NMR;$8)srwU*Jp}^BAl_Rhd zG~GNqak+~&@L*3jFQ%pd(h+F)Pf5d_(?m^|J~?Y@*bI!rJKp53QCyuyiy%>^z7IMJ z-O4>_{;0u*oHW02Ftub`kWX?H%GCr-ucxOfEw6Ly>Sw51(=agks3CD}#+R7oR>g$Y zL}1qf?_-y6?}&o*o^TuU?Szl?n6JuiI})TvK;w``o4ilQv_SFk^+S z7Xw9eb-@LklNR7M8*8o0)wpP5vAK0?{vf_mC^lbp3$YP*Dl9$T312Vz9YnU3CG_+dvy}RRsGgqisXftNij~$opa&E?)*i52tMrEU(R(AH`=X~OBFoX% zV;po`-n-T#QYZV339ESo}#U z-@Vwi@}~YzVu(Tvm#g@-v32z#)Qij1x_XH++%UxIM2lD$=L-9lPep&xS6FtxdBH!} z3ZAK-@p^c37;deGH!GN{BT~0OuR%0YrM8hR9uuXmS-=Hy53QXd3sNd@NZse-_Qg0m zw3wnqz1PS*lm9VBzGXlADRd?eDZw4gyPdE-X zA1`^dGl&cRQ5KUnqrc@hZH|JBD$p-*zSqT;7x$(p!AjIxvQ%7kf!~|#{3p)O1?DY` zmAft;wYsmVYdy-{Q6nR-CPbR^WJk?VcX-&`z4dm%euBNujNYil*4E;7qWFg_&)>*Jh$u8)z|XsiV6a z+=@9pH*&^`+%YpMa#u^aFKe6k1Pb0qKR^25hS|12S(cZj<)ytTTDRz>z{)C10?q!s zb8t;fL21G-PQIc@JSn71^*>Xli=Wzjaktd?^yTX^1iLp%!8!wJTR&0ZHSp#bervJ>v(ser!Ot1|cM^0YpRJ-Q0EXMPuX@f`Lkhp!sDMpT2}Z$E**{esQcU_%bt_44U+ z^v`Q+IR|Y(umZN9=jQotiz_s?uDF+_;3R!+`*}Ax`D^L}?bZgN(ZYNVGUHPBl#KC} z2DG>M)alLv9_yy= z@e0o8-`(yRw`RL%{C;<%8T*E{+P>kM8l$Ba0ZuFp+P7TL@1EcI0=Vp&NY3l9(a@}f zDXHmrckp10NKr42YK&Zy7XvK6s<9zj5)D-xY8sx6N^2t)ygE2GaOhhlZTyL4< z)_P0WKFbsZy|cQ$)J-|f7oNMY)1Wovib(7c0=LKq9mg&5#QC;(?w3Ks&2#-##c@x! z$eP)>R}TrCF7N46Rhm;%j@|`pNE2Etc-?6ZhwC?w-`ZPxugfAVs-81r)X+xv% zyFjynH3k0~qcH3Bja+ZKI!*Z)8fUt_Y+N5+a~`%cl@V)U4N&ys2*sNuw39)xdrc9> z8_E#;j_A69TFlKak(Zh7kFZ4J&Nur5f1me{6+PmkTZIi_w6kWe=o1sh`y=oY)ugQ# z>v6U+mVTzTSrNZk! zh!|V275tY$)JG1nzZ@zm*T`P>fz)FRXIR`!%3a)eS$m%~6L;53 zP;;Pb_-uW7k35Uco5qtlb+OiJS6~DA{4--K_aZ)QmYItiD2du&zSHIoJ{$ zCm2O3EZs``j!W#3uR!mFall%<#L&MOn4rmIz4K*5-jdX3SpE>Cn4c14| zN*ezT-b`zi03UCu;izYL)D7UnwmIpL21Ux6O`&Xw7Ew7zLs}Dw@3lFc+Sdc?hHo?F z`eE%Q%#NICu##avVT>B?RFX~AoHbq$Q>Q4S;$5`#`(2g!71Op(CCczeGoEYN@b@yv z+k0#oiu4k!*VLLDvCyODDTEC7Sm=v1P%S<7T9MAHII$mF_Uo?$=P%dP)lvq4_Sa+p zcI~`A47^7p=CuFqyuJ+QIG9tzcjRDp3U}ohURQ2}d&!$kt1ZpRE}wxr%Sx+hsA=f` z%u|L%x&}H5AxpP;H`zkDihwjmm;VLV8%z6p66gjj;~Sstp3MDNHMUQvOEVpUc7h4b z26GrR&KESrlx1Ce47xY;b@r6Jd&d@i81Dvox_M3Q4w*w2Xp-RXsM@S+eL7F5%hIen zPOe4|dDpO&v)Y8ZBEc7H6Y5SWqOY7)dJSsol{m2Jj-jxI?M%-Yav>#duEUvukxp&> zQ`6gG=iA$E>te@_2BR%29g@+x*lY5Pu3wX798RUPaN3sDuPF(?75o-iqG{zgj33S4 zp#4^|x;Qv5R953udz@i7E%um3@Rs-(*f{KQ7xqZJyL#_?)nO%HIag*pe@W-WLfbm9 zu5`Bf)dbkpqPed>Rp-*NcUg%tJ2#Xr_sVAbH>b;QVP-elhq-YZP#?hovkW-#k&c#o=^#YN_p{RV2;w(ijH;& z{A$^IfpYDaHio=vzoRz1V|)SjYHhGZ*DEIu5Sq4fVgUI9nf}0YV;+DG-1hI+w100r z>Q`5``#;z2??1x+$a|Afc##><8=vS7cUqAe?5IMg8}={gRVmI2*pwUSwC7l~7wX(I z#XuI%^#6tQDTn9$6`|R|#w!HFXMUSCj<&BWbo#pP|+;AZ&Kn&aj%WKE`Rr zEynZ4p5YINZwRjp7h-GMTWz1TecN`27$(Mu*@5R>KMNb$uD1EwZnTAiUG6Gke>Plz zaI@|EwjbKs+MttDfY%6k|15k)EEF@~W{Mm!Pgo$jnkAkVPl!dx+XC&^QyspYhA4Nz zeGfMl*W>9yVlz zCFd#2!Z}YqowIPx?16~==4Y@acL6`({*E~K_pfb2%-^p6=D)4IpszSVh`oJqXqO0j zr4S#(Jvdc}ZjVBrAYABQYZmed=NSX~I3ada3Gp|$p9{Zvq7cu+Jvj;C;nu@d!MzCQ zf%^_lc?j(ec&oo1pnV3mV()K|Z~T4)?$YF7$n{f%_#@mq$iv-_2=M{nzu>Ci`lAdA z+-LahL74x`pMvs!k8ggiJmWxr1nxVy52m57BJQu@7=FiNKdrCt;HOMSx#1rC9lk@@ z?`8N~)$fNhgeZq&d6Zcl%QNwe5Kqt1%d;NvR)1g4Mf<_=`#;YA>G#mMrPassJP042 zKz{SlHt?H~i{A^-mJp)70Js_b@Lx!v9PkPp{xDuE;0T3n%jOqs)ESH+(e`o|^e?!* z(WnnN{w4N6{l}nP0bhi}pCW>IlBMkKs4}0^#5;!F^AcjQn6QbxuM5gA4rqW;p8Um+%Ap8{F@YJa<3bQM@HR zia75>UD@~^I*!g{ME-w;Ji#fW0O4LtMSO5i-Xp}TaA8RBvvklL zutWy;0^EAIy>Rzt2r;e~;qWMW zes{p{0M{DyeC1b(@?xa7(h$rS@nvv7AY4Dd7Qi}158wTaC~ri9-zdTG#4k#^1@s!= z=R@ekpEc_FoF`!e;;lx!`+)lZOC>yE)YBRE-=h2z_#HC_(;w$OE#7xlln;Pk zJi-xYIO0(e0_NYu{2Cdq0qt`c{ue^R5kCiq;?E(!LwHKU_avMap_g~t$D({D;+zr@ zEHCj%aBQzpGTbqQyAOUNLc$RrBE#(lWPPrb;TI!(5&Q~5!V_=lr01tr!iy4CN_bqt zLlV9wVX1^0C0s4xG70k~oGsxL3CBuklW>HD$r2_=7$c!kLO=4#dTWsIFd*CW2MjP1 z+UYo8C%{915RlAY0?2+lS;DnZvY-Dy^z$f;r>J245?>1XJdBgNe~I#EfiDJ~INO={ z0?_vW4nu!g2$%~xangy~Wc-H_zZ3YVkobv@faAFPDpHO+thp@DQVEZP&T+6CINNQj zgvHT%zUO@?$~Q&}u{JuGZ{n-r7|(ba?f}9~f?rlhIO0bnJr(p5zz>JeiGz{W=BJy- z!8UZo*a@K%kBHIfzJH2xbBqv%m|#ALH{$z)-Jk(a5#=oiU)NRaCC>UGeo@AsfcP&1 zKL>hX{v&<{j`edy!YzR8@9O|rz6JQc0PSHw{Vs)HF1{zu_=yjY;d8s``CNAl?S%B_ zb_=FMJXO+6zC^`SA@`sAB5w)woSstv3h)~j-p;-g;*FH zjF0$i&=YzJaRBX-3)}@dan>90N%DJ~ZI({~J~rfg;+b$PXEMT-p}v=(-bTQ$AHor5 zJjCmeAGTNRZF+uUj)?N++l07$TQEPwFT!0zxI+?dlyI2*?i9Wo3BMsB-x2@hc0C{a zB%B7w@!f#3;1a_8v4E`q8u+vReYflRFuW(qe}JF&_Fz7UTjF&3g?BNp$6;QN3(|>S zlynQ|HNek@(21V{{o);%dr+^Hz)yfqob^Hcko?|`?~ed~BjkJH2jEz*>kzI6^=L=A zx4^Fm;fOOH;^X01udm&q=Qs1PC_jXBcHI%oFY%d@z6JETz^8}MiO&Ro#`MNZcpZMM zuYBOFuT{PD_!k}me*wS3UcvZ@zaiaU$7*`1Q4g5YwIN~f1 z@e49sS#N#5&X;hugqae?O1KmITh8knBz#uFV;0^2SqblxFkZsOJ9WB4!VwZCOSmyf zFR!~?l$Rtyph~;5ak=-x2k_IUE-fe`Vi31 z06!H%Cw>BstmjTPQdH%Z$vo4J%Ah^`{2*| zDV6ZVFZ6PZd`*;}g5R-U1j|AE2pqq^EW`CgxH|ZK9ukhYFC-{q34BWko%jYgrZ+i7PjB~LQ9c!W{Yfdo^oXZQdIjh<;3Grm z#D~E#o+UEeN`zYmzeOS8i08|2Wq=&F#e;PIb21?3sVqS9pM3ziK8+o$$GZ%W{m3n0 zo%BBo$bH7hyYz6IB+UJ#UY-rRFkXI%@$$=Hd5BK|eeF;v9>;!hI`D@?Zh}ldXAyN zd=VcF$8uk`>iflgRz3Wrzl!n$@Y`(-h9|yN(p{jJ0xt=n6VDv3=X0cldk{bS+g#wR z&uJ1C4%gFJT#9`T{GJ^iOo#Y;l5PXN0{Ed2I`M;WoUfLQ(BoOT9qo_FX3>aXJj7>$ zUJD4(OL;EfbkK>jzKCbb@Au*R$-uKhz9;U4W4$LJTrui-Hp-n0zc_>=&UlFbfc&t& z8)Uv?knd*r)rRDo_(7ST1L?ho@5(~bBYyC1J-wY08UR^;?*X#@&fl$zaPJHGl{rfGifDapm z{xK@}J#nX`9|V0YaC-=yxK+|mfj$!W;1D|T0!d#6`bywSL+Hdu{#sA}#1>JW`fJQ< zzYeBPJPGte|H1l#_8tH{0d(T5C*nOp&j!S_RBi$64mxqtiQkmz-$eXI;CRB*zK4+9#2M@V=EVNWAmvCQv0#J3TCMIremz6g%(vkl*|eKtxs{T{uX z+e#oWxkrep_XP7n+$!lOKu-mp96~4F502|}G$7~S##BAN=*^Zc2xmGg#N_o@7vQ%O;fOOF@eK@z@~r}7`9=VK1Zco_C(&Qudl`Ha{2J55UgG?Y z_*ZG@-ze{)??m~{H1J)Z6NjXB2k{EfDR&sP0sCvA&9>Q$Nswu@EqVKK&}f# z(tol1eigo73crOR-xHq)ry$=`Ww@OPHywVHL&6bvO8VQNX92f|(1{O|^i!aZ1U@8$ zPTT;;dilnt%fXJz_8o=xJqf>xkoF~B26`0AJqh%;fxiYias2Hdz6$NhcAScKXL~K( zgnYyAVYGXo{fKA7vHhF~$M!mm{(-lz!j5o(_9MPc_Md5>?*hIhr2i1#2&bT)DgoKg zkIQspknTzNRfMEVe4#_npBs?jrvrWgI9}p52}c6{8~zCr?+M6q`DFZ`qu*Rde9a;8 z6K{ZHxoTv&`XJxm!0%#6xrjrPkyh`8fM<|S5nv7AfDFA~om(f$Q!+5mW(4~caRVIN z>vM#!0zB-{!`uH1`2hUhb_Bx{w>WkB#TP|+KPTpCXOK=Dcipx6GXkDRd_EcfM}V9^ zUIS!0MSzWf`GBmq_a4yWwXFpo^Z>@m1HpKSzXr$f+W>0;*8yGud{)9~fH(t~pC$1Y znNJh)=|^4~L-I*{kEA~g`fI>TL+HdSGWC4D0mydS1o#=?%1rdvEbMcz|6U8ff=u*R z;Owu&r-ROMyLF8ypACEp=z(!eJQv>6ieSPQ!!x_c2A_tKe7rVDNk5 zXW-axP6D!i%OqS4$a*aRWPjO(JTaZEfK11grN_JF1+4$@o0=7jmv{o4*6$_!3gNR+ zj=2c`4g5Ywc;YN4@e4AXYrOv5DhcOFxIw}a2@gpamyL5ptb9WDXnzGOBa_bJhU z{{y@Q;R&ZAJoB|p!nG2fknkf3{SuzfhCG(_zXI|x_?^v$JXYqLc)>(HzL|i`#}q)W zw^l%=*By}S;6_018;by$eu;!{1M<6F6ZLe?6pHeF@GG4dOo#Y6IK~q(Nk4~424p%3 zfQ-*3;Yle3vcYr8->3do)*B$}ttTM;1)vI8 z0LXN={8leV!XL4Zg5QSU2FpQw9US-Zt7Z6u684;;e;+kPyd%zIY(FQ;yTi{kMeHTc zdLV9uV>&lwJYM;IorElzlhCd+jt%#rb zPhAH21N=%5f1o_Xt&iyOj*xJ-gexV?lJKU4jS^m#u+LQe`|c8&CA3SpLBiUpkgNV1 z=`9uI4e+a(3b`t9)+_OwaLjkyqk4P}35Pt2`32$P@!c@^4R{pui~Npwk_=bznEu^K z2^%H+{4tC_gx~UeQT`SD&OL_lC%-3tMuv-X>GR&@r!bCO7)P$)yhnT#9P9spgc~JX zC*dkUw)avA-4Z$wZyC~?`84pu2j_^r#MvIim&xz)C7dhaY6&j_vOUg7yhg(4 z$Mtk0B>VyXtcQlVdV1FvU|$Bm+PT5>h);LR^5y9DHA2E9Kn3leEumAw!?~Cz&mipM z$bT;Qu3XHMz_t7XUnl9wZtO>auL_|Pe=kpucc+9U65cmYZ_fdMY`30(Og~z}2ub(J z@Qo6lmvG*EJ^Wk=M*(tuvPe8mLZgIrGTzT6JSXEnCGlet9+I$3!cqx00W$to5?>-= zu7t%homCQk1;}!p0%U*O1E{r+q~l_wR^IEtIgVST|E8yO|Ji`JD>8p7AlKg*K=PyO z7V7k80m)~%0J$F?3&`{?KdIv<0QucPK=QX+0a?ErC7cS#_?=Jc^U%SWkgvec{$y|- zBJO=kr<+{hSCNOtr-F3ivlr`p-y}ftb3+!3cP@Vhx%4c|pYTgrEcOy-e<3~qj(lKu zgya4P^JaNZ_(daJU_V6sho|-Y9+U8}gew8LuYLGwF%W`2x;#h&w^I zApfUs;CvtWXwZpsUrRii-!uK`SigZs@O!!L5;wrHzI==H_W5d&9zOne;5*^>`J!NW z;-5(R5YW#6KNUhJejJYRy#~m880%#C1qi7dsPCQd+Y(aW#C@Q%{6^4k0{4O*s1M=|aLoUb z-|OL4PJ`U=_uxN%9}Gu4Rnm`uZU;Uxgid@29OF3%$nmfT;n_d1PL#g}zg-9)=r6=4 zFVV}H1<3uY1MqXe5r7;I10+lUsOG=wd+bU!Mi60jvPz_#63*ULPl>LT>g9x1|JIOa0~kmbIP z@3faYXBFgiZVmvJkeouTVTs^{%1^gCpG$8kvgC&fWuom%ipZS{1Pd4&X z2ERQa`60gO4|;y)0PtK2grJeCInvlM?K!y$>7ueh_Uv^V7$an zO8N-UKLUO{gid@Z9OKOgWV~)d;Na^uJXYd|m+9%)e+&7+GOT0Eg6R-1h2!^&0U6%{ ziO(hkp8A~r{gH>!ub)GIeJ=Pt@n|@fKLU{DKZf#AE-*!wcjIzB{(L~jp9{$P83IVT z?l~EM65{_HahwT>pZGR7%5~QPGCv1a==m9+Ey~|mf%^q3g83o78%{xaHp*~rge!qx zaY#7gYvGuWRe)MO%JA6FmM?0 zS%e=9zkUcG==a29CA}8(IN&iMbmC?>)|U_M%z3|ZxgOu(NuvBL{61P9jE{H)=$z*r zpdSZ*81%rrPds6zUN2D+HlY4lPTz09@1c%sQU8H*6Tb|)R!*8T_93QaI*gIw9(7JRs{U`cL|IjbpJ+{|WW^r{H(QeXDeO#5l+)S0U|H zK|1k9IQH+$2*+}p9unoX@T)<%Ks^vY56Ap}B*UKo!~>Z5hXBp+KLB_Z_-;V1Kc#?N zkJbWegjJFE@13-KB_?)NSL zYUu*9e;t$GrQ*92@H-On9q|K_J|6TpfbR>T6W=81vq9eqyf}nTd>S0nOOoNvBV024 z5<*5RU!7Artc~{Gt#pFdm5iP^9O_56Jx#o`h=ox&X+09WK)A z-Tol@8~olb3f4REJ#Z}lG8qo?>hc2kEeQ!n+%4%FL8s}Y*&%e|!{FF&d&+R%AY45B zx`%`#Ef?ZfTP0jZh5D-x0$vzGC;luP`$;|^*V}1;jCU*{+TlpC9{+vmI2SF}&du7_UE*)U zFFcfaJFZ0J8s> zWI8QKCklSXkaUPc*NrxAum4%kR|_D^cT&QAfE*7cf7aV+3fg@u{5JeK*iOXPfzJLv z8T3uSSA!lH55!l(v7Tlk9NTT11Lr32n~rdSb|gNQ;ZQ$LK#qqI2v5G{B*Kq^pB3Q) zd<^jv89q*iyNqxN@QV!zN8E?@VgLSIwr?M_?^p1<5YoQHPlC?=oC^9Uz>k9-=-KVfqqZ?036$4 zlMG)B$a*ORx8Gf}07ic%)LpJF3@<|%xCmYZ%8-n#hd;lEtA0y#B z3C{tNpRNQXKYap_{PYn(^3ynf*S>oeko}=X;)a)WKBpNt_v865>)}!)jFWKuCf(l& zsPV^ulxxKU@;jVEYx1NY5T0_S&w(?aXCyo>p}j=+j{&@Za9{mJ$Ik;&UUdqP@~S<6 zlxr;m+|@pMfvD0*tcy7)+h0k zn{~Q16@2Yx^wZ5jI`KCoJqz@=fgcE=6Hk%!Pe30Ad_V}Dcuz_H2K0E~-9zZaYfJR> zWA6b!fIMF=38qin{Hk6*jazkiQNk?}u9naz!_`SxBH^)Zl8@bnbS_~1MLO@n@8CA1 z0~~)lh?l{!z4l4CRl*VpXK&a0*K|PkuPNL0_;#UO)8IF0doVuYegNT;;b%cO;tWT;C+M;G&Ifut@a~`!C!KgB z%E>q2W|Z^)WAFXrqAJt=@pEPlI1Uh_k|ALYh&Cd+^GhSM8b=rbaYeJTw$?$>21F$^ z*IFNUL~2Q`(5ywRB-eFGEwOCV)P-EPT(x#A+jT`(w#O|MW*Eq2CQ;}6zRtM@2Ce7$ zd|t2bAKyQ|=$Y$%|Gv+CUH5&i`?{|)(}BKUz&{ei|C;WJ35Fr`!Y z6vd$*pM#zStwo(QE-!KapGCXt5VtC#{}ir3I@ME-@pvBL{FA8VXDslin%iTG((2Ht&M=sr5p58ElM%b8fG5Z8fzP#Ax2QTUJO3+;Q0-ctHgV-|M1(2n!1p8ilcpVRA+ zUVv~;L^_4-xM-cQg3@#Ej^8TnYni5QQReGsd0g^3wNpV!g$`iTBe*t$*WkDloQl%w&TjxddH9@8wQ z&TUG6HjacIfw;%E_4J3r+c^DUq;E%fOGG+_-^4}zUB`6#@0I%MGVp%X?_pp0eNTN9 zz8~qQ>cBsYw*%p^NT)E36NNLl{O2fdMtE36c?zfEqVd)uUp2;exk=c43*w@YkHS(PEQ>NyqBK85$Zt+c;|X$Q*D z_%uy64I|C+!2vZ;ceTM@(&NkcyGseZ|^Bj;cBGk z?0}sg{-!kuuR%J6>AoqvoXamm`AUSBMwF-UVq7$S4Lc(h-*$Q6GiPJCyz&HNfu<{TQ{Qr@s_V$3^my1WIyziRE%d&3E}4>(kWbli~41K zN2%w?Fs!TZ;JNfpPdyY)Mf&6KA|CQ;K==-%Q<%ny!a6R03FQYOtc@s7;b>fRKi{A~ z9*pm4$YBTC{u=$EFx5lh_i)j8JKk0L^M|3ZXP_PXyFLA(@Hkx5Z@fn&kEe&*&p`Vp z(bn;Z_9<-GrL;eg=@OKq@pK?e<2jD$W4n}g-%Nu%AnxH^J?&EXHBO(4^ou}c&6`G`twDquzM8R8MeEpKNQyEqJH-WCHdUT z?PsC=188ecMEewueotw?1Nn)sKL@4pOhr76XChPMdrG@UQXp4|OMkDYT?#MY^p}uc zj&MmtI)&%rqJCw(uhiqw3%iZ)gP-sB)I;HcNZ$(z^?LUmp#70fVHyVtOI*Ga<+TW3 zk7$R&SI`dKhY$VHWBd$|qsxf9gnSgHdMNx2u6Fe2HBcJYbzJXU)VmS&ycE$d3O~!~ z6-Zx>@REph3P1dT(ry;hFAzuLxCCJu$5N)1A1L>~Tan%hisf_nI8Y6>W@nQ4l~t)5+6H2X?!m*6&jR&T}eRy8!)f`*wZfx zkLC13NWUN9F%3QG6gK0ceswe|^=Jn`k8On9HTLAA@IR6M`XM~8uzr7o@M)w|7=Lel z(}=x0mmh`lUm$!WqCABUIL?U%cDB`;Um*gM1Xm-&+*kf{WIhXPM?PO=bET z?x+3>Ja5s?7WBOu_d{X)y+z?Fq?4agBJOuR!fvF8{gx=a3>V#R5h%^WC5M#ze!d^x zQ^5U{AL_Yp3YUJYq*wRHIsM151Ap9;PT}o`mHT>)=|?C-d`kSY!l(0yCw`q~n*V2| zzMZ!SyQd>h&Yyegqwsl7Pel3!gqtJMDZCdK^}mkkmQR%W9*YxpZ~Fv#`6oT~QTQdK z>;3|LP=)b8cootqOyfo2Wn6wa%2yz~B%(Zp%W)Ckijc1!e6nNQOAzNoJ_=Jk6n-2R z@of|+^=}|3jqle-l>Q~fKyHpej*j&7kHV*MQT>NOsoWl>4$#jKwt|vf>CC5C8$lO< zUIx{Ds?^&WE9_1{T`Zoy9r3pl^$}lNnQH&4^v5URyz;M@+YX{}pWqwKt2>)9YVxd3FG?uA`=?qZ(m!>lbqGx0l0vI9$(kC(}BnTS4((s^#$O z9Ij!SjLIlIk!b=`9n)B*8m6#8%K51iRPHK10Hq+;cbUU}rmajbFg*{7|57uD&v5t@ z(^%%ChN;99L@pPWV=IS^Of#6~GtFUo<9ztv@r^c2&ROg&74 zOfNI_Gi_y>s8-rZV5(yp%hbx$$TWkgfoVR|9Hv=JZA_OjUCMMZ(=w*w5wS`-qE5^- z)QDw8#*->fpHpJ1n0Qspv{tLC)vYRPX8r-ON_|)@SMyknm$<k&qc$(l6$%S=v8%ReLT6l=xgL}}TK`~=BbtIDv= zNKQ+Xqy)cMtCDPPajziBv8EeEw)m1Ue;K46p z9hP2~T&?Q$sw$~ydUdqxf>>H7?Nr&)q%z6yI;yA>w~D94{5mlxF0EI`#xC9|7L+xL zr&O`ltzxEx!LCu&tMSXNdyu15Og7l+#S5wnBKo{ntV9nI)2#L48r3N=(P-R>ut!YH zFdi1mcB&H%#;s_fAUQ)XW$5vn3aT10_JHX0;NC06%T%&ay+&Olxr*Fkqv(-N#v~f2 zA5lpOjiQu*TfvwmNC`Gt#)U@6ZY*X;6&7(%mMdo@P8OsJv#3 zLch45s7kO-Q1a^wlzKeVmGs(&6zcz(Liawd(3&R{dKgshXQ2|#oTY?|m~Lg-$n+G` z8AZ@R(XJnq?px!+J{$d-QKHmSHcuI!;6jD!o>FM-FSwt0c+t33GR;_yeK^WL{wpQ@ z^%Y7z!RM5C!wX8d8kG8*xn2o3f*KI6drgVY`Hj*}evJ~2-heYlwBy{UjKA}BC0+l9 zLd)J%;*a1VN&GzUwo+~nY){mV6Sf$lv9MVXJp~&CjsKcMO8%wTYf(6OM5)Jxy?`F! zMksX@UvomCB~kDP7G)6<7BWT=VKHM`GoN8x1KC>6xE^>V;|su5jCG(dGOhz%$2bx4 z_#4K{fL~|46ZkF0=$NpLu?Obarh2b#yf#O3g>}bH!yAnKFl~F9^aSD zI8BE=KjS@+|1TNq2f%j0cslS|#%qATW_$|xTgK}X;M>KRzHhIcaWm#nC*$+LU5pbG zF`&3l8s~AqQH+DYTE_JKa`BAIFb5MDF9sgOxDNOZ#vb4yj86g^7+(e+$ryqwm>Jh$ zKHtqah&eru@m1jQjO$ZjvkJ!#5rj#MA0LXdF~&=QA7Z={_%X)kfoC$V8U|l8#x-~0 zdw3bw1215F26z$UtH4VcCmQg5zKrQRtt!HKfPWp%GaR-j#u}_gYZ;dUzs9&4coXAB z;9ADxux|a1aTf4)#`IlIyBHS$?_pdCypM4s@E;jp1^yG`oRP2{G2RON8RHAUUohS~ z3O-$ot9}9-9b@`lr++b?Zh_r^aVc;M=aES4N(O47uDDu__ z+?TNqYx6CPGk^y$J_DS{c=6r%Ze+$wfm0dNm+9UWjt9s)C z@VgLfjLYs7glxvI1LrZO@9~(zSZ{+*A>&LuKOSY=2>f%#r+{ZOUYZ3P7h^Z@e8yG4 z<&3L=f5Es8cp2ky+4vr4#`zAMy)kZ{C=I0rU6#`;`*A3Eb4;Ejyw`wKQR_5g2X zd=mI=#+QNLWqcL*ea5YmU?XF!n~d)UXH4GP1-t_N;n z+z8yvcm|%s|7Pq2zQp)2a2sR#K7h-NJ;2{H7N+7%hH)jZTBFDqz1Oc{{5o(P<0HWR z88-vp##lEE&rZfx;1tHwfrl|J10KQH4Qyn*26!~%YT&VqJ-`nzZUxR_90bl~EIbI` z4955_0O3K#R^SZ(u7ggqgz=e&1>u*BZI1|o zn{gKKbBx`jj;|`)mM=*diT8#<7D7i#`(bg7|#G6$T$dmJLAU3q5Ckdgbm!fgD$0pon&EsPCCg7ACBCxLe|UOWefS&XZJ|G=2uIp5ECCvYQU!(9B10An}s zQO3uC|H`--_$1@=!2e*J_#|xcj5A&E>1134e1Y*+U@zmVz#WVei}9TrjK=|AWjq~N z)G9JI12~%TF)nxp`ak39XQBTyUh_-n|BOAr zuQLt;zr~o|;oQcU-pkv;xN#-)|8V{_(Er1EUWES7IA@f+y>j0LrCf$v}}V81woaT&0IvGEP)|BTna3H_h(5#YNS zmu`ms&v+~Fc*b?W4#qoyCoy)`LjPw>ew+_6HvAU)KVuv4Ova0Wix_VOE@nJqEA)TH zOfj2Ss1J^Q+-46Xf zoCkP2WAaPe#n`wL`hPeccpu|Z;6F011^yG`f_I?*GbVql&lp>GLH}o*5Bzt=%Ye@? z-U<9K#xv@n|1%~(qZY=x_n`kXHUR&J@pRxTj4OddjC091ja3bTAz=Iht{R8xW#^hge7h@-I2IFPGCdPHZV;GZf#l4J=1KSt}fwLJW?uGu( zSho-QKV$tz(El0l1%8zAVc?%LcJGJ&&$t=b#kl?e^nb>#gV6sOlb^*e!ukIQ{hx6V zcm?C+2I&8c3xHQM)_n~9pE3ClyvlgbVd(#ilm86;pRpTwD`Vjk=>Loxf!}3J=h^Qw zKL0uNf5umV4>Hd9EA)THZs1P|tEz+}9_as!j{~1z+zk9T#0-*Gd8FxG3^-=P09rr!ve#h8AN;z`Ez+X$tM>HGYjVyyc+^nb?m{pP=9oCEA; zOy9r!9Ao-U;8l$4fL~@z-`PrbF6z$_;0=t0Q_%kz)8W`}8QXyC7?%OlZ+=nPYT)-6 z*8_jZ*aLikvG5P*|BUs(pD?xoA7ktU_Aqt>f5n)5DNYl{x^oiv9OE;<=NZ%YV_alR z?+*JI`+>hB40*Z?9Ar%ILtSG`?mGSE~ z$Qa}530RjHmoCP0jd9{Hpl>t2umrjdF#gMb_et8elY^S z8iC)4z*{5mjtJZkf&UVLPe$OcBXC;;z8--!5UlWh4T!*ZMBw2O*cyTFjldHka83lC z9DyHh^>X9RvS0xyWbOCs>{2>j~^{Bi_-D*|tezy~Ani3t4f2;3Ec2S)Xb&!`BT z8-b@q;720x6A^e;1ctD!lB0aioSSPy)`G(F!ufL+Pde?}_Ry7?Yn~R6ILErXE}j`IvuG1gdrvm(JY{QIS%7(c+>x<>h6R=jonGpYF*(P;LscvcN3N zoQ-8-A=ol|AtY_iQk298;^&q_8pB&U=ve9~kTX3pm#^XJT;UG@yZbLJD* zWw40W3`Bs=EGt}$w8CSX7KjfDgmGiAtELgo#REVDLdDOjN=|CCpU9 zOeM@z!b~O1RKiRp%v8cmCCpU9OeHK-!a^l1RKh|fEL6fmB`j3JLM1Fz!a^miRKiLn ztW?5EC9G7!N+qmR!b&BqRKiLnMpKE=RAMxh7)>QcQ;E@3VlHTJyhZzDsc~$ zxCbRnMwBoS&rQU06Y<ucy1w{TZrct;<<%*ZXupqi02mKxrKOcA)Z@^=N96*g?Mfu zo?D3L7UH>ucy1w{TZrct;<<%*ZXupqi02mKxrKOcA)Z@^=N96*g?Mfuo?D3L7UH>u zcy1w{TZrct;<<%*ZXupqi02mKxrKOcA)Z@^=N96*g?Mfuo?D3L7UH>ucy1w{TZ!jZ z;<=T0ZY7>uiRV`0xs`ZsC7xS}=T_pmm3VF?o?D6MR^qvpcy1-0TZ!jZ;<=T0ZY7>u ziRV`0xs`ZsC7xS}=T_pmm3VF?o?D6MR^qvpcy1-0TZ!jZ;<=T0ZY7>uiRV`0xs`Zs zC7xS}=T_pm6+BNFS6DvVb-#9q=_h8RzUL2%|2<&AJ(>6D^`ng@Bq+&v!i}+75qHlR z&g|6AoH^>jM<-`xJM<4unKnH;i}sgQ^npL}BfgXAIHr#9*z-E&G3U?j(qI&3W=1gozKW;uyp1Es_Mo%s64$hV1;d*>S(+ReyUWsR~@CrxY(JqBz>Jh}uti7yMbKWqCWtlRKcxii$B<8iK|it8|N z9XKlJ{=0chd*6!_b?9*uYfuOE$A~)Sc9&KF_WoqG>HfR%UfguP0m0Y_`J_6WBSfKx zi{UzEEn3uF1Ib74evoYIIX!Yr!S3!qx)*HY7lb2jtiy^rXuYSuAFG4n6LFPdA*c7V znkR`uE6yiqP8Q&zzmL!;UHyQ`et(}F`@}?b_k=gJ%YZmtw1o&iEg(Ej_+UA+Hgg; z8-?ih3{k;)L7_UCO6@wqDd$NR#)Ilpinx^16$%_}o7^ESZtM^w|7@dUSr&4Of`3SG za>si%vjg$M1P44aU4rY+E?dWgDFqZK3hSTAs+$xL%KU9#~$Cu+IFArN@K@r_aA)1?d~?+o#8m) zyA|%WUNK#cM{dNYD|w%e%qz#uJG0iSl5_q#BB!&7%KloZQ_lAY@~P85@HNVzg0qxf z(U#C%_r%j{y;66&?WXj*ksj5ZZall$J1uXI@336k@Y9vJuN;@tS>>vARu$J4qODcy zy=v_q-w`eKJmUmv*RDqkd$i`9m3(0D*@vx=nuB4 zT5ui1wFB4c>VuVNi)s(}8nu<6sCWCcR%1**$jv`|56%)(kw^lpPu{ zS!_$2Kd~)(vOQ#-oYq$P#7eLD*h;TZ0{NNuidS9oidU8AXp5RmVa_vorB|J|673-F zXRmmpkVksV(I#;n&pj~#`B!?QP>+;HWnS?{r>C`5j9lsMlfKd${n)EsVeG43F>icm zzV5Wm-99PlIi%jw{3`ea`IXZf8fZ2kkDONT%eq$_`_B`? zu;);pXkB9uU4Cqr)?v$5d*+plcL;vX_hS7ML20%BU`a`_1KjX`78Ev&X1<1(w1?!s z|2$Crf#eW(HO~tL)_L`ggBPl8p}>m=g$DNrD%V1X)@gU;IV)VNoQGUTovLDO;e_JJ z9b;6AY)vY37x#Bn73#XB$9>TKfmpK8(Kru!cuFYX@=os12;o`NBW3P`VR>3rkM-vOI#!9Jo zKuU)wITO5E9y3}ONzP*(!iX4;n4W$@@6h;buj(B%rZhep|LBh>uK5~$K85x8a<1HZ z-|w^j5ft422uivsm#S0~vSUwA4N5W6SkY2z51gpOS0MZ)2oH`sLxD4`R2#nMqkCS( zIoys>uKt)&{XF$TC~&M*a^!a&b<)_SV4XKRFnU7-*K-SOj=G8cn#+)9pPXl#oTpr~ zG%MYif?O)(dLFszCn|lWwVvu)F6R?>DNh7Xu&7$?$9qccDz)Bm1bG$SzkERHQ&s&f zj$%iGBMRg9T8^?^Z=lpc~W6rsy*OxF>`A>$*x^Z2wgmH|2SXWVZ{`W#ZXDs@chh}u+ z=Y!K2hXP}+=6C16a>c<|yBS`bu44S|9O{{abJJY^-s2k|7=J}b>c;)AYU=$58y$^~u)K$#wePeiN@@Oh>30t4 z`?ghhGVE|WP6Y*9jaQ|uy;EfvcnVKL`ROG1f9u}t{Wht?CykhjwOgRMra z_aC;5Z%vHp*Sy(#_D6DTLk?riZeR228n67+j;ZnX(Vp{u2C6<9@EuCuA96>}^3~!= z)R^Ar+d@wYT#c6=YQ1BgQJ!^`s3R#x!OP|PKJ*>ggdQIK(06n-tzsy%W3VUSJDz^d zyAP$09;F%=`1CQ2zBTgmDikP`OO_&zTG07)NxR!aQ!F03?|YkT@N^8{bF@cj6XI6k z=|;ImA}vZw`a*7K(8y0iy7(1HvA3-lX=~F@&TZ=(XAkWeDYR|DxExKVv1v#B)IPVP ztw%fB6I1-l3rIsd5*#OJpres47WFSphS1Rlsh0dvNv5nP%2satlkjPq?kD40t+#)I zXIE-l)qRa2-NMwit#O*rn88xpmXYd^5zm{1sb4fB=fd%=nR3pYwqYZYbIP!`I^?tt z*0gOKsS4Fk-WS?(H|5^nJiayIcB&h>;<2jV3%$yy2^j~cwpGSSZK)&og(@GZLoO@j zivI+yq6d`wWo>B86I4PQGEY`YWe(P(M^anu$d{Xu1N}ALjvSLTA@ks0x9O&&wdv!8 zw#tzyZT(Et`ZCmOmg}WHQvXyatH-EOj?}jPQ}u1S@x{ocyPfKz9#CCwU*GiHkZv;N zLW@tJ#mV}%ia1r9e&n=fa6`_S+m`xHF1SH`1xH5ag=*$!g=)N6ZQJ7V+TQ)FsBPPP zTdOsur0v~zVncfe&u!cKqAK*!eRL0wHoVP+t8XatiMy!Acp4+r7Kd72q~6*>HSgqv zYCg-w^Ejt%`$$*Y_IGTpnK8Duo%5Y-JL6`x)qR$ZQI|r0xKGp`=q(dHVwoNH2AyVKzb&-0}N%J?3Uu zpHrTvJ$lg~TiZQXuG>fb?SapZ=im3)@#Evd^Ls^oRQJB%;zimQO!kn>-MA+R?+1GJ z20y$f(Dm9AG+xZ_tQTeNUo_4kd449#KRdtkGh1KRpE{2hOmO&}`1(9&Uzf44tdR8m z`ojFq^Ll!U9LIBVs0bwD2~hn^mcdbreL{YxOQ6)7_X)~9_uN-+y)R1E`<7l543B#v zwZ2In((i;cddiZPhY?8adqwqK@+Eb=)iYu~v><3e@g?dO)qq#MI%re6^U<((~kfN^%osZTF7n)-yw!Cl#ZFeSkgw#Afdu>DaX( z@1ZQP&}-wpdk| zS8OkQp?F1MC{TYjzr)$924y$kJ_y;q@5gn){EqZlkRI$aeq0yK-HmTH!we8oN#;ef z&bZ3{{g?S2{(?xk3%5t|hW&Ni%6hT-aqO>EYeRwT3o9M_+lBZiJ^7sjwNzJ3dp=36 zb`tNGW3ZaX@Gd(BJMJr4)K*NpvfqxuKAXbf{dP>dvftKXM;+dA&w!qs8$215+|b>i zTg6<6cI-u)$AUAQLtW4}rDH)`L99n?ZbFQ~A*EaoeBb5>rU{}oEBo@7Sq{A%*VdNb zp_}@cyca(f%o3i->g&?DKEd2O(GNWq@ihjUp2>nnURzJIuj7&}aumY5bT#eLj1Pd)|v=bLxrS&{i<8N2rf_v1!6U!?TiMf>qQJdI^((_=rKogS9G zK}eIj)B2wc$HyVQXAd5YGWT@LcFfuQ4x!hbh9%VP6|{LSyOZQ{RdEIF&9&9om48?T;7s zUpY1`x0UsO4{GWY9O_p#MeLBT2mV{$A+L9JOb^MI6Ff;tW<&t!Q1GJ@f?H7SIRd_~Z?CLxYfVEI3YwexVfJ(!x%C zwTuUllqAs#)#uD6jGd4c4oj!^h1`9FwAIK@YxA`1m%e=CLO)@eqt-5Ynu2M@6znv% ze=o7*BSx>5cW(>6KZ~()VgFi>eT-_Cv-EmkoG+!Mc0N{?p#>P_>j5jm;^^A>FWNi5 zydD_oqt(0Kc0FMDr}X+7k55vsJrNN}Ti~E|~A4Pha9ho3-;(N~v8X zw$rOe+t1m*!%+CM53a-m6q>_D&fQ+L%-9EL`u6)?yE|t;(m>3RnSX2SFCL>j8~d z8@fG?_D^JKqx})>C}AgjJJrHcwh6epex(?|VTB+=$PN}=6R2~*MZI3PFci-Z~ z;*j?5Ltr9@t9@=m+4d#~uNz<%+lyvo$lDzgz^G7+}JT@Tc^k{xb8 zR+$>jZyor$qH5SU5VILO$R{@9iQ(3^XQ?)Oe|GvkpR_|rI~`2WVcl;Khn@~53$W4t zRkbdrrBY*POB99F367{Ms!o$#>w)bpX8$A`?kD{IuQ^2YO+{#+gttV%H(p{s3#=B=u6`c~0iZN#iL5$jJC zw4g?L6>%a=Igd1G@r1|9g*uXAuv*KpVw-;OD(H_Jy{hz$NLvj}-rg1)XA7;3!_E*Z z_#53jm#N?Ge+Ut6+E z+^{-Z@|+IpjWZnA1CNBHooU~DE3j8M`g`o-u}_en)<~DvEOGtyz<|(7hq~s0Z=Lon zakNLc9{76y53NP&;z^y~*p+oCrqBj^TFwpjv`L+fb|Hmwd^~%hv$FQ3H_^=3()uz!Xp6s+mE%;-CVJKwWlz#uSNgFPW@vCy zXZj825VXSdyJ>}))EV9D$sg8n!Y2`=Uz3ir&=KDKk37q|h?brt!!Y;7ZrlEyH|Z+0 zZm7D`<8MljMtaZLK@8T7B@wzp*mCbaNZN3uHbR<6xJPvjtWm^}-dqifFliE#It^yx z3+mTCo7EMn7}ihLC~S~>d#FBce@NfQjddbYYY5Nj3epoWgT)(W@Lw;{e0|$3ZrZ5P zJH&Nl=@ilh|9Z^e>00cC(tVA8^KX_kAeK%HOISsSwUd-r;2O3TXvvi{AC_XWV#_sX`NnVZbIlU2k z3Z6~wHalS#>2KE!8)8Lke-Vv%WOXVG&MH;h+c6dW#+QpbxvO|~gzUS*}p-)7~Gd0DLhq=1qnu;|F znw=svitB;wYvRUf&?jiky&f3&(T~l`pR#`NWAoC`4_u(9HrC2#vg}0*9erJm-uReE zehYqKFWHnOhuZ&PXj11u)(|vCJ$CBNuvNnfobs`=Y$90~6x%E9f8J@g^Uh)<^@=7lx>__ecJy1(r-o8Cdtch|sAIEtZ5K3XSoqc+BP(9er-_MK zo6&aQL>W$j^uATcf>==*?+*b_ZA}h`H(d|BbWy^o=G4~P@%*d0NYAgSt%G86S`#O^ zeXI9h4=hKVIp&BjHiooJoP?C%)Fuz-6L>xzDhc38AMhPsg_e&N`BqOlT--Gx2ai3FL2S?n=@Qdw8S2<$jyQ%l(bxmkZ8ll|z%PrRf zgDxs*gJM?s;+iJ3CXSd6i|6$~9NHsmXNsfB7lS*FlJEGauWwAfZ_18Wv6{S!-N&o2 zqQ2^l#vLEZJLesTGq}U6Hhaxo&=QXJ*?@5}3a z@o?h)OEC`TymuXQ`F74b>(#8D<$HO=Qs4H~OMP1>Eyh?a#@LjxJ$4mVNO}@#+vyBZ z#HGe_R+QJ6u8=Scc2X_b@>XU=wYa0AYgT|CE3;!Zi0z^n>g;+qswQ?PYykBdSh(aT z*P+NKSI>Fh>U+4EJ@Japsd@k5{A))`k zNin_$!t2@1@>*4|mgV(U$m=PrbDB(*EUk~OP|MOP;!ZYu-`jr*68m10BC&gJ4@+!H z+wOLSQ>%CM$m{A&io7N>!zjEX#6Q?)!X%-nY7m3`eH^&{eT?&imoRSe zF{zOKbKa~GI0ePH48!;sFzy z^U*gje(AW=5?{u#a$IkCqem?By?e}zIM@)=mtp=GeNs$0cm$rMm-*6nJd3&G^i{+x z_SH@L(03kbwJ}=E7n(byVPYPoYuhz9%^{jUG>^h_s4+^#b12mL!MkFOMxH&kADKP> z`}}El{dd&|!SnZr=S_GX3Hq{$JcneTk|~$_myuV=wZKtdwBw)eV>g$Axbka(Lq4)! zMYkJ7Cv>>5UM6+hhl^Z#l-lL1pG}siYk}>)NgeB_q>eb)jXcu8k`6W$lFE!|>+ZApO5_m8q|sS0}IPqjiC zETq~d=#6BTk9MRw`d*RRNn`>`s zaUEw|fnK6!QE5?1B3l7zf)plg5cGD$!5*cS<0*erFj*&Br7@y;jU9SoT6y-$FU#R6 zLG~uSaXd7^@}Rs6fu$8Odh(XQ^CWl{4v4Nv9o0NDdd`Z3YJ)?u6yYvu`?&DwSXfSR#=ZJ^_z5LACI$`TT)zz4 zC$0sYuushKJc8H_*xgJFX67SD@>fBPdt%TU`%X)h_ABzr7@p-JTZND!w5*7k7|ci} ze~JR=yc2`QjBA1WS`98Cr68E17lx9hLLBnAW0WHayLimJBv>Qk#4+M%$@()PJNvsY zNA0G0iM`lBw@=h+TUMw<)A($7>uAq@`3UNp;h2hbb3p4s*CS{NeaoQ!Jc9m=&svL< zqep_7$yN5`h8EYgz+<8Ab3yomkcIw|>-SiZm3z4sNDQHT@@tSCh0l7F`KBc-G5^{x z`_*8Vu+X7%t#hpdcmCdz*ZGa@W}f5h2K(Z?&QI*{rhzmGu4l9QyEM+ooj;}5+2r&a zRtP!w+W+^Hq$)hR~X%{M+=4Wb44dkaM*#&4w561C!8ai z{^NYgDZ27HmzOE!NJ4IxPZPru65Xtz;kCe{EmgY(>~n`X zgmngo34MG1$~NrYgpH(o?{8yxLXld>=c>}F0B6c+s{yMUin@ybe_oT>~Dx1urr3#nAhm|twIhlzxrV!5#QOH47vGrX-}!xB=iy!@l_$f$zNND{<$hz~r<39m zh9@`Gp(le*OYItU>fp~V3PYs!SQR*|HlS}GqNb&CO~o!cDJ(4Pu|jImzn}MNrKf-H z4;G@d2BpXK%Axeb&HgRqWAvE~XVMSTN%SzMFjT$E>C`xMDZg|6+j%W8>(a2oIfcI~ z{CA;#mS3@n!{_v!b_4Ho?X9{Iux(cS#;acS8?S16rB`!Il5Ij?J4q*~^ood6tx9n| zGqhHLmz=%udyyIg6zGLa*Tkn9TPTnnliS%wc`cP}K?A>xL zaO}Vm+I>AZjrrC;>hb{s-RCPbQabYHW!X)*0gEhQz4RPi`LfKFQyY31Nk_73QKk0 zkoCS5z8LMKP`{DvodF*P{os{8F;Cy-zOTY3rayX4q*x#g&*e~>Rnr0dY70U+awIt4_onQ%5Dut zVNG{RcfkP4&KZF@A4D*+hFWSs7u4h@G`tbj7wD zSBotxMPW$a5?Vn-i_Y{4PJLtB#n|K7-lnpNv_7V#zU2~kp~aSuMODiR@miqZqLAX; zt#@v56&9;n4ylBe3Y^8BxL)fLQ$%AeqzTp%Ss$vXmmG*SB%!aP95jzpTsedGhgJ;C zeH1zxG&eE*kgFd~#=s_18(i%5kXbY5nf0 zdEi+=zDsBpjc0>WHJ)p-HJhG7AG-!Q$GeUP1)bYP{((vjWDg2IPlfb{JRT}F1ZlT; zpv7%;+;T;IdC(;(-sTZoR%y^uJuKMT{ouyvCdq{vA0?!XDi*EK!jc}(rk!tei+Ui| zm$6t0)1%tcqHN09;O)@=N?wB{{xz@a+1I@4$Da49O6InSBcWB{{7;>f1^<}|q1)hV zwG}?Hx5i~Do)&S>V|Dth3HEYuSKBUXXUn#@b=kX5C{IT6dW*D8S2$+ybLa`|*zx1+ zvR{k4HE{G?Kv<*9)u(!NXKDEcQiRq1&Y2_5(*u4XC;%WPn3wfPB3F~aHT~Ast zuVYz3q_(4Y%}>3-Yd){zv4Y4oy60J@?0V?A{{Q>U|KIt|=XFHe!ectOIj&d7* zWx{?||0jO)R|EISwhgSrdH-+u&0h@+kn7Crcv!aRlYhMIJ0Bza&cnLUJc&;2;6IOR zKstQp(e|x*nAQJ--#pJ>?CX2`PLa?2)xc@lBj6v(u1zcN+PXag4!g;V3i=8>=gEso z*|qg}QVITFyUAl-)nXT0=3!RLUi0b}QTCdD#T!E&^YCo%@tCJw9z5pbaAx*Ic3U)^ zpTQ&E7^iQmgtxm0k9dK0bMT0lz2?ayJ|fR^@T^zMK3K>ziM{5@vmTz8avk$29$xb$ z@S5j(`aA~D`8;mxXFYB8L0i!!)cz~58NK4w$ZaXDPTdc^%cg@<;2lnvIaU&9dLYXr!MXO;2pp8Aa{iJiTkAf-DXsSwt$%?ot)06tSOBCDButJZH%>hpZcVoKruCx>RE{)|c#u*-vpt z@px!nLtvdy_SUomvN^5>E`)>)+rGUTH~`xNMkr~agLIwT&aLc)NB&IxUF5@X&f8ZW z$FLVP>>=7lSI$l2werYc<{M`oe!%@fZs+69AKy#%$k-fLn!d^*v^*zV4J?HP;t%iB zK9S@Haw7^ONt#HK$UBu}$T%y`liRs|yp&2(A&#tSm~!bk)iYVq?P;jtk=ESKuX{b2 zvCHgOs#9eBGwd=IpH)Sg=*_m=&OiJw&*+|Ym(EzpUbZpVSN4*ieJA+eTK0aA&+5h9 zr%AU;)3&Fnqa8yX55n_kqgMlch2cbs2+U62c@r;SBu;2Mb>=o5z$s(E)F7=J?1m_%bEraD91(MiAbrcKbYG&uvfdv6TV(v4|6+%#vhk4#Xo8w&M+{REh>kQZkKgLRr@%B zEE2R1so>qIP`xZ~3f2iY5g9>vh@+5mlK1mmU@EDbg*V19BFnmU53D6IqU#V=l|xu@ zayw(oX!JktmN)E=WgqGKiDZGu?X-87q}8`)1?z2}rL|@?@UajzD9G%Aym6cn%~duyr7qTJ7B+wb{So&k{OQbi96{Xo{b5Y-M^9=B)_O$iEE5& zqispa%A~?}K{dz$Z`C376Y2D_A8-=J>wS6O`e$!kjA0($c6i_x&&3j<#U_Z@r+B== zh|9jOV`wLPpn=B4Fx%kh>wmjT*dV0lzHh_+Rrc79dDx+a*W2)tm@C3}qLKV!Xco}A zMsi>5s1>5s?CY=WlIze58|m4(-hTGWL$I>$a1}UcUNl^j=LM`~ z+g-{!R(Y+$u{`|TEV&w(eo3+6Tn$Y5h~!fx+d9dLQ|uCl4}w(#_Ym_RU!!~v{Z0t5 zY6yDKSOr_h7q~lBd#pOiaT(`3e%~xPj~_GYCEd+4iq_NmgUu!MY<&Ui&u!pJil=$j zR~N+>G%Y#eD(vyD24XKA4jSCUUAdikTt~_*A>|Uz3uev2_?c5@X9cZy7CL*L#9w@y z+j&Q?_C)3{Py(IRwKr_;}{POItLIZBlO#hDC4s46El~l4iC1_p?!J z;cu#&8XTtEw|kXKm7Z{-Pfb%$D2Rm}R&aSP_Ibg8cgSG5T^F^ne_~#X2zyhCqj4hD zD2{pFey&NZ5mG%r*z#GlLcOa)KRqhZ;O_YCicLWS)@upAvD*-fch{i<*)NK3{24W< zYoa$~CCxznf`7~RxSJtO_)&{~NwZs;%b&Xtjh~v>3;$H?=+#sbIQ42E?czKag^*KE zzP9gUWX?2RgoW^>*nFsyv0@Y5f#Wfc~ zC#`A_Q$A?9@T*x4pj1r*W^KBr!R?@Z_|fc7ae@;oDB~*luexy#qMv$4AH2P{Tz-2` zTDR=oWy_X*vKQ8Z#*S~@cr#I)<|t~8etjzLSQ1jv;_3?mM&vxoO1iY9imd4M8pOI0 zYx94JSn$;_QjwF5FC;~+MgPRYmam_Zx+UcwvZN&9t{y_If}3R}m|Uwau{mnGrA4Z- zL0)X&r$?5T?GBQdCftKZMSXak`hb~gm)m~T-6I<{atb66?{QuYm@hovEZ#$RE}F$m zVK{LWyf!92t}7G#i{1Wj_jYx(Pj~#(E;ZALP|k!bcM|8*iCHw#@fXA~Pk4Oa?rm4a zKZ-si8Z|Aao>Hae>h`v) z@$pkG{Y%7{Mys=^&5u>n9h!R|a(s#XhB)R+=qs9gp)DeG7#P-GsU1Do?x6akYYt`A zwrGUt>gXE5@T<#L-ci@|ZC2N$I7EEn_wa@%H`tPFe!-?;jEID#JrpwRnD#yv- z@&5Eo%1OpLBODGMSD9cNRHPnhsZx7>ln$+^S}Ii8!Bk;F6wQ`(wKzI6S_PY4ZpT06Cv7#}d5y!{tb)tEJGbMo97i^5 zT5(6n@7inY=?#1A$nadD_k6norZ$mQ#^LRr@cX{0bV86K4C@LEX_MEyVLfY}5o=zu zC%ZNAXPdpxX>t0wik^>yPv>?#+^e+&{iomNcJzM!57Qq++^AmphU(w{mQDb^3Fe^3 zz3uos`+ z;?DKibp9j6wQKN(FYJ=_QJ39D_|QSN1%1s0wV2zHQ;ZyaUvs^l`lrsc@+qAk3`n<4 z>74!a@s6gbzjlTek+*l$B~8@#S+`F3CEh)es?+gy@KLN{)XGY?R6}bmPE-7}(n{6v zA4u&Atn!hjpxS;kD8XmXqf+5nqir|J=Zsp|2{)xWajyL6R!C01+3dtJnc-1bQhWEX0L9#GBaALb%b3#fHA$j1x&*@BYiKY~% zEjYB6N*jE%Rx36dYUlhn1&AGtI?Hu5s$q!|^y2oAp=|{%4I;FE8g4=)l$f;FT!c@xM>Gq= zD(oFiRO+192+O%*M+^l#A(Hs>^0QmW2=_*fZxX4R=yX*djFKgtboShi8mH1@=<0ZG zcLhE=sMv_9hHY{UYP{R6^lc~lHrE-}ELAR>YpvaaQIqD0rfqiojz9|Do=>L!{Ki{f z+!a{wEi0|xy}MK#-VgS4jIMs#?y0UaS6ATINTGKfx&js6zg_Gnilbk&`P@IWrmy;HaT5%SLq%h1@pgkdy5cd@6i2Hd{@x_*KVgP-SI@Wp#d@FCUESJ zI76>%7@0(Rgu&rG^AYNDwF{bl&F)JvLa|LVLD&#;!ijNzyR`zIL{ZIC9d?x4f^vi8 z_3&^|hv!CuI}}h|%jtBnTq$cSNgiqKak$@Tem-Cw7>g^j1Nv;_di`shog7~WZL~dK zih3owI@clMTmwH7Fie6E;`E@t{;Qzm{xTcf7*b8281FkA-|Wk|Lon=Z5Zmt*L`P2c z`4c&v%>}668p&x|d#Wa;;M})Q#5zBsm_}K9cBktj{AkOp7EN@EOB@6ZN)`R)lg-k) z-0Wo3w!T0Z8d(?JQ*@07>kj5JX}&|{r$8EMz8Mbu#7aX7b{c>AR(aOp2^IYno>o%J z^Qs43ue!c)sa`QapA$v(x~@Q|MOZ6s5UZ*E!WIoyolg0!_UVuUaf7h29`Sa(XRX>m zse0d*)m?#q%Q*}V^@b|=v~~r~%HcBK5&7h>EAUlI^qV-Th1_>wE`Hvkb)+QCX;GDn;8cy$ zd7Ri$|FVCMzji!w!YAg%3u%ICqk2q&Tp&Tz#y562qc707RKmI`UM z;0PP7QQ0j@o&n$^o=rVcX{>YZhfLOEjaKcdEfo#grdg%J&}qS;#sRy3=2}f_FW$`R zY^pyV5JueVfo)+G6+Jj_u#!HA(~DRb2@AFVOiZ?%>*vP8C zf_@j)%*M|@RH~l;Hsrn5_4#f6T2va$uNYcwq!VA^4hhAd&FRe0|4i0QQe?}J%4KkU zRct5Uv)a0e!kS4vP+^hw3TA*86aMOZ_cuUh{sI5Rz*G1Od zO+g`Txl|W@(Y+Wh@W7L*q$EWzEy(llur+RV@@ z`F)?6OfP`@`~Uv>DM@C|%$zyrInQ}+-{-;jCW@;ahSQ~>zzpH>kt{3HROc{){@w!p zc|d;;w|X8Ws{2W6>B2m$AH*oR^W4j64|iicbMk;%j`udm);i68&;a>Li>K#8Npe9X z65Ern7f)aP!AL^x#^EoK+xad+TR_O7_FNSAANs%eDu9VJTeExNB=Y-v3>QT z8PWFw_eDQC#=&1xt^=Z^ zT|eQz_7%qxk^H-y)EDZ-lGTAzb=rFV|Qd(NbHkL8*8J>Fk??^JqkJF;Ei-KXN+ z=dkGAc#n7Qnh~u##-dY@X<_mrJ#a4Sd;cjweMg_O@m^UR#=;q$I_kdmH;(C}Q>N#) zZygmMT|Z$)`>KE|dgn3Lo;KYGU(|Z?s^k6F2MmbX;uG?tLn)r`4n3kD@Ht|Az&KTG zAAf8;ur_o)G5XT<8PRoty!N+`>D$MTV(qsiaIn8qTW!ot|Ccd4)H`Ntd&f-M)1Iqi zFR`&~%m@!(t-phNiOX&XulK^Yj+_)JNo*{QAdMXx11g!B=%1mZk;9n&erD@`_bdj@NAHYaV5TmwZB1V-cLX1v2W{l>7f{YWC(Zs0O zfqTho=G(IpbQ_k6^zQnXPt|i{^4i}GAf5)j*;eIos1P7weEYYxN?~91zZknCsB9FPr%qiuu&P zrhAp%6O@-TYL5%T5p0?(&!rkH6X8 zW+ktb9zKS zuo_>B10i{$TY6E!A!%_#&=HmY9>2@{zkjSadr!gnXNJBz?fgCJ(MG%a>8p=@-UBHz z^f=-iwKcW8ZkXsrEMB|_&syE`(o7Mu7=AB@4z*?)JeEq_-``|T>FNFAMA|{>SZHPs zwaO74xT1QRfNv|xKnK%oqP7=+3h=Dk_i1~AUMvU?Rpcd-UY0qz-RhoDTOy1i&p=6u zAPQ-qA5fecCrI(MdeL3_jb|l$ujf*@(lP#cLB2f_eVnZ3OD4MJ}y^H#TX{8Nqg3&JyOw3kz^E_w&;o(F;rkRAl_P=K>kW6eOK9X%)i zy`GLRUy=viNtJ^h^RNjc>uXC{W~6Z<2CPxDTv}R^U&(UN z;`v}^so=~kQFDi3EtZmXEGj)3{Swb~dvrZ~hZ4iIpPre3G2nOS-AFAShzg~dPN9U> zbe?BrsU2fOqe;5z0gt`Z%l7nyLikmx%2PB421u=$hz`A_RHX_|^ptLQOsStFx?|(& z3}$-L6k4FMBs#U?e!OTcUE_Ei*4|{iy*<@hQeA3tR+o^h#+8yK$OKIf&r0J;mZOE* zepP+4(t@~D187D_BYMGjLyPA+Ogty%$<}Os6#0$Fwox&yAp9MWe*eCWit+0|pRN1* z=JQiUO3~)ijk!sw@0Pv}&@9onFQGltjMBPUj`=_ixrRUbVYMH<*KdavywbZ7{&4bf z&1|(NveEF6o5^=|E?@^{`n^2z4dS!pmyKV`L+x~gjiukiMbo6A0pKuiMTA$@V=mey zC`OvlHi-2qBh(B$+jFGgXRQ?fg*=8wUI`dePX-L;4}oAC1GLTt$RP0U>K6xB`Ckd> zfr9!RxSzjNqV3dIurnh*3ppx%JL#UD;MS_N;OcUm!!roYvvb#@K<@kws3{?sR+Jkw zW{(DP>IooqmH{#KOmJ1zZ-U-kgw}Z~xU+o633qUNIU)!3Zs2%KxgQ2EfSdi(0f8`=C`-&4G8nM`ap${lRPPconKQ4YwBSjA=qXp@vZ>+{CV?|!iI~(&$ z*%%t9vEoh0Gl;P;Pp8qz!Z?*-Bno-I-bimvBSvq6;~1TTJ8=%9^#$7KFbaoSF>*&} zG(HS|p7mky07hVYIo=u=F~a+?qs#D{-VB(qa>Dfe1#kF95$AtVm)OB2>T@r+==~6V zW3Fp*lVzy)ebhWSq-(51u35?|vIfZCktiq4t)J~0b{rP9Iv{7(uS@>uOn1F=%J8yrh|&F z&cyqhIDZw#OK0)`nf%;*cBj$@ubnR5y+h`Y~aiM!0fG6Yt)&$lk7icp1t4#dH zubpdMADlqZ_Ecoc>9qtCF3EruG&nRUDYFSQU^#9&$!O zv`ZwwQm*fCS{P7mi1ucD+M$bV7jm+Gr|-zbuc)--yV)XrmVd_y!h9>9_^RKYW(2n$ z-%}8tOT3S5cf)oF=_)~X&!-)U#fSD6gbS1ie^@u&#J{UQC^0Y8oA7b zCc5LV{CeEab~({UzZT-xchd69gM4gE=Erno9*)49*G+uxT3-3!K6*FWOQuEMQ}%E^ zw)eg1nXo4lcfQn3JD1(>UQALU*^BYchz)*Bc}wIL#3#gV>F3DQS%*2|=A*}pE2eod zSLz<+Ud-?2JV#2$L5d(5??3zHG&Y_kx?PPUlvz;cgM6Q)Wdf=7kbR!=$wZ|;f1=I- z#Y&rjyoonZ-+uPpmlsn;AKydy%lhAS#OV}7;$|ANAmcQ`Dv|=LNG*;yk-?4ju&A_f zA0o7sBU!N)s!fMlsnOBjgV!Yu z@1`672BO4 zUYEaO%vq)Zc#(Lfo}d0_CRAgrV>D?XUbVb#y+|1f7+WJQFD-_I#H#IBeRKpo)V$n7 zNStC3<6OCB+P~<$}tF)_>{Q;3*evVit1N z85Buz9OOYe-j?pmHahU6>S>uI8}_qzOpCk$Kk)A!=-b;_nztpMEeADsOR1j_P17Hu z;XTOGH7$>*9CWDCXH+^DofcUy%+DqV;w`}S4%eaJNX zmjm@&i)eQytQN`YFa@~Zn=MTET8_7`7Qco~!`YTxbg0Wa`4K1aPR<5RJ4+58$QIj@ z%P-u3U8_>>A1-3gDZF)PqCaECl1oWOoG<77&+%`JxZs)#emWof=2Gy=xxre6Uy>FK zTs7C&{8v1OECP_1Kx5iI8M>S1>U#Z~j{c4N;r?YQ{WHT7PzDXZQ0pgpSE=;%h}zdL z%5EKsU2!M%7j25D&%FP0o_f2z{0+ZR5A1aIqmTxGmoB2ck$OKrSX-pN!9OkF`q}gr z%P|_n?e|^%Cd&S(z6Zratf+flyiu|89EN3{yPaFdjk~{jm2%`VA%%+^(j z-1r|iA3+}5KZ=y$k{f>tIw5Ca8N*k%RNyrIb8Pt+l94@gbyU|ITGw>vNw zW%Q=>4%GLjfbmA3%ORauv=RSDbG*MQo+~B0$t>s58#wrc(B28etwp#@YgCdq%w>#@+?TLOd3#Dyqlrz2X8jFz>rd?I=?@?@c! zws!!nuDvoXGMo`r_NP_KFAxgdDZ~S(RF&=XF1y6+v{H5mw+{O*9gtpXyWeRvhQnFEJv6_>N(j|@M$6o z5m|;Zn%(W*31Ly8LNgmnQEvtxM%AL9te-@!jebi!q)%93UPbSobU9$PL4LMHdSp~3 z`L~4-Y!;wDIL%%|BLa(!(svqd8g&|ReNSRnt+Ph46cYV?+DmzwF5Mtb3qN8fIY!&t zA+I#Vg*eI&9cM*^S&?`~ezd`@(44YQhfgcg2OGW2$?pdD=JUe8u|x70J-V3dbi3=i z*b+mNN4PA_hwjX$CHQdUS@p=zaRh#5AD#UvzH^XG*7dvz>ErDyKH$dOu>St8__4Wf z&UKdvKurmd9FpX`aE1L}C1dA~EzS!sv(vvz@vpkutNv8lfAF_UPZqMOErE%FtHt#UJBIL{VCG12b7?lFoj4ksH0qF|<*K z{Wn=06j~*J2@lhQIfFva%C(+Bz7mfQxIKO26x*QCQ?d`W3b2pGDhMcCp?=@~|NZ*h zZ<_DAT6VfD{i)Y(FfFXcYHE6BtztpD*Ry6C`bkm~z7Z#=Q)J}vbNSsnP-o(FPYZNw zk_lMolRtAMQR!juA;gK3MYEUx0F9P_Op4$=g-YI>JhXHDU^@faO;*}bcF`QPtNvgM za!ZiB0ohxLpvHPsGJ;)}exlSY5JW=R5t>bvE{4~qJdtw`wcdi~YIu4Y@2GUDe%H>O zchPa}ZtC&BDaT2+US7u`qBT14Us-R@5O#da*aA?+-|!vYyBm+ zxU)Ww&eQCvnMU(O{V|4i*4OqO8=Zp4OZE8cz4FgH&=-62x)J#@1hhjyeJ8ufq zLaK`^BkEA|9>;Kls1HI?CJX%*v6v(;JO>PMqS)f$-U5Pmo&GJ|PJR~)S-WROLKXiL zxl)mn!UW^EJ5Nz&czyhk5$WS{*bWaH58LY)k~?qjp5P-&yS+ON_IQhS*_l@NidEZsNiH2OqCCdi!h2Kzg zHGRhv^&JmF+q)ty`<@eEYhOSf!#CX}3rb41mYjzrat-{B z&y$2uTr#gP;>;6DOaeo!59xfWJj+6pWWmEJREgYUKqjkvRWE*oDBK1EIyZU9%FdZTl><8VEY#ZI4_ z^p9(K;`l#n&k!R|RM4K*%M%rC|FSfqjmC>|fnhZ9nTKi)bgw$tk=9uG-Q)I>4JkM6 zT;MiX*``2(Xvl($ito2!)xBZS;;F5Wn{UlVe~YvuhwCW@ncBowX6k_jjq>wWHtt7? zT`CFp%)))Ld+!+zCSG;VOToDR%RL)pFQN(?@o(eBn_LHBxi$3YFF7M89y}19tDn`Q z?|1;eUrRRlPyA}H-vNy7%vOi4G8v|qCmJrAM$_E($(#2!C~{}RT)Xng?o~hSzqEtT z;=j}ewO+`b_7zKJ<>M*$<0#0K5iZNQN1`OBtRT^p4QT}O! zmIdEP)&*dYNsi%nZ}qUT-;}ybXTkQ((k@Gh{>K*7VUMJ9Dvg}`?&>z*wNe{(J*)I{ z40Z>i?WpukJ+}aQerIqg-xJ1fS1#<2z;HtO&lSpyfLeokFt9;rboo zVPyMsS8=z#?D!iZRT*23KCPK%7yqkcO1~WWn#7{&GMhHie;?oTL+`t~_kDHiR)}I` z14F*(Dq5*5V@qJl(Pd70M|!)JBYy*oF!VY^?AM9>OJZa(A4hg6elxOR8pT<2D6^lb zdwJK(h;ak*#;V^%Rsq;4F$#yubT#l)AmXqVwgN#o=y&O8B(66OcgXyRe>WC~lySHP zF+_i9;y1$+wb3?_%h8XkYog~ZFC%M*?ca_HXh8YTIz<~FvOf_2BK}?re8q6sR~}?r z-B%=Q&sJE0h&Ne!7-m13>mymlhQEnhmk{TMy$#5UYWQ)~nq=^0E%CrA^&qTLSELy| z=iwdUk7GUzq5Zg%(T1Zp^uQa3&zv3}zPCK!NI*SiW9XM?t)4#*kJ}Y#7M|XJRjj;t zNM2e?{lWbq5vekQF?4f3U!Pti8Hzah`;d}}dW@)^L-e3pyLQ#Pm*8r1&Bw)f!j*37 z-+we0i;tCFkuG*~c>ixoi{bIfgmhALkrUhts9kIKd5p`vW_knq{Uut`yBA+MU+RTT z=!*1Cw?U*cf9a-o8V4(~F(l#cbZ&QdvA9?KSLyt+E7IrP+x;%X`O;sA20wBlf}Ykm za`5~s($4O0O1F9(&RO|cK(;OC=*kLQNj-a2Imi1gx}yBT{PheJ$x#oxThtzIz}~bW zkZ^T2zEorq8?_3@uSmOarz_HGCq2%72o>BN75kwZ+u8>1MGQXAsLJT${~q{YIPL+$25>!o(It5aJw z{=kIzgTDAG;`{fu*)~&;?9J34XwtQy(|GC=J`=C*)hB2q=(h)_s zHtGB?8kIh0a}%jo5m)hF=g_HV7nJTD{e{(f9t1C$5-*|f5 zxxgd~Y-`n@!3ThzRX3Efp18}@TM=4)U79J^glb920e+6&HaNf+7Pv4@&tSx0J0leK zFhsO)Q&HLC9mG`A=$NNPZ+HRNz^rM};nO*wC=sy{h;QF9%7Gos-2TR0h(>zB5gq(a zB6NSq|FC6zR%mM93BO81Wkqm1I5=5i4(xm)xC4^JPD?}cXTVEwRAUX&hNT=<9mppa z6psE8@H_TMftXwtpwq)x1cS&@c&_g!(Jah9)Tkt}UR>zt;S z8>yFQ*t@d{3#* z70W(;3;L#~{2^eG9ujlK2JXOnvqf2265I(pr}j2`y!711@LYRnr&oJ2<*I90M0yn) zElUsK?6aNE?tTexOgW%x<;+EmhypRyvCIb>O*Ncn$08MX@|9d%-H0kITv9>vAmqAh zeS&3R)-71=ex2^Uyp&Xb(^T-fDbP*ki1jSZdhlP091?5{p|C zxgmIGqM7^TY+sIt{Bt`NeGSfo_!Hk@?BlW&7o;a9BA1?mxJv&Nh$~#dtwBtYsXiHU z0Aa2u`|wfM=glw6w<_`Q&vu%3XD+6h&MvR%3592NdhO#pbv>6tmeMrwJ%4plCS>ty zJ`;0fq#6L7-0pGZ>7u8c>oHz>7|2lCe{Z{zxtR4B!>nicPJHge=kjV7a5+sEFDMkk z>_G7FzG86@JfI1nLf~N(EKHwGQZH7Ll|iv7tQ5uYZxrn@V=$wZD=_w0 zwmk?GGdIzj^7r?gvdKNOb+z%K?vXvH374F|4W;BW*FR z7Yg!$_u-7#K{Hd!I+$*a`>0#(m9}0g>%5{X(yi!ZiOSkwo{BxL$T!Ln4FS=M@QI;& z?3fKlKt=al>P7b?G}=Kg|CDQR{k8$uCklW6))jeiz_ASB%Ww0-tL!rH|He-DV=Evj zT#^3py$iAbwDxGV;`yQ7i1=f5E_i)j9mBO686jXEWI)y|h3sp~AY{$bK}W=J`t88i zP^Ou<*E};L=Oz-;=CV@RCEc!~Y=-COqE_D#c{Y6Xh=IEXXJZG>MwU*t@jJ~OcXEWU zVPo8gI5BsOSE@P?s)pZ4u}j6`5qXmPi2R86hpeB*w+ z4_7Gjs9U2VaAaw9n;0JB!g0fIomn1_CqsNiLEvOqmnsOH_j(aHvQ*LL0ex^esRxllIruJwrZ4R z-qh%dl%HG-?-10Huq%;evh?Xb+OI<&7_pAt zRqmzXeq^c6FNU9jPOV(|0E!sC8@o{;uKhkg0v=DaC2DB-G zu0WqZk9|IjPsgGd4yf~T?h$uG3;UNW8Jc3@Z&siPwmaF$W)s;wkj2CXc<*P)OZ~EAy(FLFM+e>vyKWwv8=1+tB*h% zhlODb(2u&G$by>*z;SwCq@8t)f{qbP^@7;)@y-rT$Fe^{#fzwJlUU^XN0aNS{C}hc zs@0tFEvJ9<%d4Ivt`l;69oNLac9(C>2q0uQS$eY-xL}^_1}EOz%pT}o(QWWo0^7n~ zdI{*36tuFS)p{j^z)$2Xfo-fFLFkoCpjEK`mE!{B6+*9I{*~VqvAlPf|5pArnyIYC zCvL$0-#R9BzMr$WBJsi;1s}j|CR*n_EsL6fUqQ5?=A&1%!UBbs)mSGDzal_3(C{ng z2jEw}l<%vZ?XoC{KQDB8S-P#2Ff8}g3X3?K@Yqm6mTmz?Lc^|5OwE$evv$I;q_nam zbJGTSGH6%BvG9#FmOI?8$S@h!54zXw*WKZ-?`q>I-_ zu@J3Yyy)vivB=Ul7m43-o`EP9TF1KGwncXp9OZr3mHc@W4@tC1v6e)eWOqtLT=p<=dlxLW z*}09wGg-I@*l-s5$?|va?f+lA`X3{YcrrK<$5uP`McE*D9Of9C-h@>Dbf!1-C-2zM zAOrS~*^mO5EG@i9alKjCozC^BN9JAJ>l>b#0$V%_?R@*)8~XpJJ%g^fB8~ix;zgkK z8Id2yT;p?vzZkHO)A%vr56Iobpm*Qcn~jr@tf-NJ-v<@j0}K5czaQxRJr5OyihF-O zat6J9sPR9B=;-I*Tu{LI*WQ+x0GQ)HXGyZ5?c{JOLE*X_9H?Y+Npd+*uS zqTcge+_QE*J>?%Q6Y0*C8a`ExbEux!zwTqe0w>Z=73U3&2p12f8waFEaqKdo5+yBIke*K{( z6&^Kue;ye&M^w_W7h29Dx9EL$F;?JU?C?Q``b7A{_fHQ}eIkv|5sqjRIBrx*2absR zvs(gpP0gJBGG>Lc!_zKEsG^tgrk{AA%T6HPS259Y6no+X%2jk6R>?_-C&*36THF&I zBkEB(k@m?Lg-!kY4I@)#C9%n|s1%f$hhBUgW(~N7lL|P@Y))GlhrIS>j?mbdi&# z=@g^xrOYs@Y)kvJHYQkG{c@U8K2yqLx=P7@n+R;4p<^jT2T5vfe_4J^*+nTQl_Z^L zCmHcOude2gqSvIWMr2nS+rY20LXzVVF~~75{`hKGGtut2cgJE^cv~JM2QMgr+Y(s1 z6Sk56dD^)p@I>qC_LMCoy*-D@PS97(7Q_gs6`l6L!olUPa4_UZmQTdi8RbPz1zjo? zmy#7qM5cbAldOuz`~Bb%NUVQRnXoyhmCLuNKT{tV`iqx>MEBF&a) zIfJW^RotAlu$@P3slf?UX9xM;@HC4#v;1Rlr70i3AN;Gb`lp=bgv@z^(7&U~Z~io5 z;^s~8dCF(+BTNK#m@*q;#_-*y+w@2ozj>SA%x|)58Fr8zV03LP!;TH{50b>UzrAqb z9*P|ODsV$@J{^5CMF-kM|H9g7ndP2&I~MCUu5eZ&dz^DOt6Aw9Sw9=s{xitcNYeQr z)s1Rjq2_26G~2nNCLe(+ zM^^7~g=>`~np~-BQ1P_aDyv?lRPHV6&1*qblWK1yy(giOa-MumT2%Ov0ryB0&V1_% z7YsNyR5<=^E$H>vLF}>Iv=6^0Ptdpd9 zL08zfOvwRea)tlCRCc2OdW^pHH%`Gr*u0>OOqe^@-=I`Z4I(*mx?vwa7YxAv?Qjbv(R6?WTk%x84lR^JC!^e`i`aWOcb=; zSS7ZND3_#%1E}SYbS_Y0QENJ=$XVJ6={R=3PtMfiSJj3`4?KMD{?$3z;RUrwtMES!4-D9}pR+deSHg zjxs`1j1je!q;K2K1tw6A=WJB9(H88h*cHvGTzecv(woO2gEZN1DH0^sCL0l9S;&x$=qA1r^WWx-RJ;4rl1gu; ze*DJ@{?j+VS{}d{?yEt=fAz0zN3lOgpQ(_f+_s8EEK_bWf-^`` zHgtbU%EX=i+?e2_d!^(1&?Z;d6YB}`*zJ*|8!%Gyi?1CiGw5c+f8Skk!|+=?=Tek=ABpN z|I1M1<^=R}$W^;S2WsdDK$ygPQGrXj<7FIK20A@ee=gv%&<-a_EBuMbRFgUaRzLLm z*M5#-M^K&l&)=)nnd4Da<3T3lT?gj>OB4pbJc zzV<*~$zTsxa=@*dn>Vi^U_riOt><3c`T6VI*{k`Bdt(sR)UCwG%vaiu1l_KW(XAVE zSvcShBvp{iInmbw1Zf`V;6GZyCx1VC?`yJI(W_GwnoTLvsMOe8*p^xjJx8J7G*GH+ z?f$R~SuZ}i$G3!K&c(=V8*qn2;q15Cs!4)` zmO6rVY4T47o4 z`Jj5HYpi}x$!sFj`3KMBk0-}hzsLDT35S6Q8o@F6)0dSZMvHw^T00+WcN5TDp|8H9 zrwU32iiZ*DF(p_CNhl>7a?|&ASd4I1*?Z$pJ0nveHPd*UU!X~uWIf6Jc36l{n$YN5 zY-gJ0j=fvCD*ZJ4(U1>W`kM<_DCdfM0cpK-Uubwnt(94cWZn=MNqS%JQ9bSBRgWzo zdUJ+9t+@Kxv_9J}IKw+C`qL99_;75)fMbJ%lNV@|(_ocgAtU_Wk~HF?GrV{}D^94s z&?n~@L}&Q^*m+f^;xWTxW@|E)Gm@%Ufv+c14PL0pR4pw7Wh!5bCR6p_KbF^h>KQ4% z1aFP8qlPi;s())?8eH&JzaETSF8iF5D{_kWLb%s%1^{Of3ft`ho> z?hkx;+4RNRJv+kl%_v=(Rhi{kz> ztn%f${m$^a1CFbFlj7R?kL_CGZQ)9#vTk`?P9*{)}p|DaQmX(Q6#gUAev z7}{6l`MpwYL^^?*^%1Etcv9YAy?qaTKN3_W+lcgeFoa!BZn|X;jm>tMW@R7lcViRv z?0%8?xZFy|`#^O>Q2!6Tw4sj`s~p21+5C?6ju4RM5ot@%2@g^4v0iBb@@{xw{hxvI zF0Eh6%oddvLl!Y9Su)ZU4xl0j^7lD=b$``2qF#SA;;pFL-vHjjiw=3{&9mHGNuHZ5 zZT397Y`S?yWYnxf;qjb?y`#0e5^^-l7%61Tiy;3WpJqS~sDvol@+r&sWAo-KS1%GX z+~gajtRnsIlL>#oD))s2^XF$g8j*e!%!I`tB8^7%#2PjZBX<*yM5GZIZyiOAD|kPO zMHb3w?~F)^L3g=^mY{1Qk^yb2V|;~d=-|f+CYsB>2N)l!&cNb)BB6SQH8%zfmM0ON z!HlPn0ct2J(-4wOKj|r#7O&FPtYE;+5<=+GMwk5ot)LPC2HmTG?R z4JE5cpb6UTh&cH7sqXt67uq4y4?hansnnCr;QIY=hVXMB3olU80agy(cUNP8N5u0e zMBCg{1RP}5xqxu=Tp-PlJqS^!+bp=38CXWehW8<2=mqG@6{zAVAg*ao@E|g-WM#vy zZy~*hYR@mUa8sWOddo9{?_xw6dQo^Mn;P)M20TrPX;R~M+^4U!-=oOubJFS4J%9cM z^|va-+b2#u(Q9WzJkKJ^S(1pXC2Ydjv$P|O7e4@+33*sM*`s#s4!PoJtONPVN+3}M zHj8%TYCDcR7d?g8nmNGg9t8pe8E$JPj9?S=J&-w&Q?B_8oyE)mM|(;hZ*_^trhQhK zD;&94=1iryov4%=rDKJN6{((vxQ<1d#;N*z5qn+CMo$u-n42>fG1!dOky9Re&;59c zZudO*#FAgRC%PvhTTb7ac-Xx>IWO{4PhQK`^<3)enwz_%IDZ{-b=1wZl?!_!(uy`F*yeSWT`zwQ_rzG;N@kF^7!g|U87F(bQ|KE{!x}zj(Qb6WY@zw$UYQE?2Xixqul6Rpc*-S!7nhA48ZMAU9+7RgycD{}q~=Vb|&8I#Ri0zF96b|}s{GcE=_xgPm_VzP_&8bR+zS%A*n{-$6syp` zhBQ56Qk100fTABCcEotn3zXNNt*aJamZvK_9#vt8ApajpD?1j7B72p~MD|(qyx5TMO$DFGrDKQE9>z5ql`@es}t{${4K+DWiNu`We**ths=k^4E*q z02V7N{_FgH`Tt47r^*DRV{Z4M{@aLiEqq94o#6T!^0chAg5hLY8Rl6Or3*l$m6INS%uR!q7-w@}5Hc{#u)lws09PL|b|B|%+VFwyz zu3eBH8GlVL=-CTJ(%OkW`eIpBRy)IFBh z>-Y{c+lZLx9l-o#iho2l_O0S_SIQaW{58Dlz`kWZTt6C_M^|!1BK%ufd^E5jwjvA?lTJX{P)XWQeyaIU}^SO0j_0bK^I-#M8+0 z;lq=VBV{E}x-X&L3Nycpz4sB(>ALN--mgRKUk;*8tJp8Zr(D^mDL)O`0CCE)(%Sh6 zm_MZ_y0hG^h6y9i4DzuFz{TB#9x8q_W3v(WD0WT~kGjsDj`!6C^B_GMEx95cIewac z=R(5UEB?cE=!{i~riav`UK)ssL}yr+E_2ILmwxu2m09ysVO=+ zD%}Ykk+Q+xFLJ;paF(bvF{<}JhkUGFU_=D00U`APk)#j3f_-L%cKS6nO=Pb)kVO&x zMO39)r+5azr{-jVb9kag+>G5C?g0l!G>e3rv?YwV7*uJGdAz3ScXz};<@ zML_|3+ZuQ^|EA31sOHY#UzKkuXK;_~O&IO{@NGaQ%p%@DH&~TT^FgZt?@|ctTM^Cv zj(VZJ?mRcye9Onp3&mNeycx?fcLbck--j;^?f>Ye=lrui3f}WZ1tpdXVNB`2ukW!( zIB;0KKr}!-8>eBA_R8QOA;svJc z86{5%mv^&Yto?Q~fhA9kHJLCzalpZ@G%*eULAB3-J`GuK_| z+2$5X3QFB2XpKEkho~Nva|?~Da0LCa)RulzN@t7n$Y6o^ne}<y3#83o4vR1j=*l`sQD~R6HVGZr}DW&Ft;llgc~A0jY?>g zNu)Oq2cDgUr&4Cz1QvVdnG>9N@?z5H;*=WLK$#GlS5eBPaowg4lBOn7Z;;}$qnvmV z%f7juPv=_;_!S|Z=cev0&vVAIx}_#Nb%-)R28{%~-PHW3QehW44PbUE1*bMyI`kH9 zlO7enypRlN{9blMJAb~LZLDg||@a@9%r_!^aCXet>Ju#1k}rVC)zf;|<7isqltA zo)E9_gju~60Er`HfFn%0PSw8~lg-EqMLa>P22MO7aX??M|jhSinlKn+n|3mt( zV1*-?6^@{-VBOg@3Z}zy+7WrXqW@^9tEYN@t(zjxXl2v=ft{KFEFS!B7X0S0w)?7j zXE!o|yyms@e4ccyZSJ~jJIO5cuI*@>Fzd4uon}~g+u)xlaEK1 z+GhBNo8>9CX8F$Q_-=jIO|6EcGw#*b; ztwWFJ48`8oC4Cvy)&k*;X*G07HBoNk8?m}x0@i`2&5eH_gU6nB=q|~Px`k>@%zTd{ z5+t+%a>5t6y`I6IH3Mp4(ONzBymIthVEA?K zN^w}Edun%A?|heXXZ5a$W=CZEb@tYm!H?Xrrmn_)sixjibv?q)f3RPTQ1tnVW(iSigmy_Mzh@h3zkRv>h%{QSl{(+LPGwm~WT&W9-0hB(dVVe$}|{zM2p+)@wiJ%=X!Q{ zX5|y_@42```K@M+=z{;>aaH|4l^>B^P?P1B6VI=$R_c-7*pKID${%DHN?otEPTs#LlimI$%!O)V+C0qU^ry;Kz?nzk_|wWeey3l;M3|q*Z*Gj$-YRkk0Ubq-^e~=eo*!q z`UCoqBRsj{TFT73Mjvv72M_4$_2`mj>uP=Ij{aJX!tryjs}23Ae>;Wy-*jDVXn-uG zv?E!nPaD!f8=@E}$WmdqyZ`8Ft3W8BJ)r;@$<}yk-B;^IBNg4qUAncj;5s@{k{O<~ zm`*gW#SNY4d`v!iq~*}#L$w|A)zfvWnj++uK<+MXW9!e!^p(-0HD`f)Z|06vF>jZKVmqjt@SVh2@ z*CtSPmFvkxzi>63m?Q3*XNPv+2TrI< z`TvdLr&t?~{~vOYP0IneE)x469Z^nSrbI-*IEcv zT8~*0dTG5+BGR2btmzHmr2~4R(Yh-C5UsS?=2xI;Fot%b(Uy9@(=ORU~6H4w+k_aY+2o1E%!q z0mDcSfDXW=k{+PR|3ufcE6|<>+9uoRs2`U1PeH;siu)4BrwW)v+h>tCPt|;>uBFQH zw_au&_x94Z0NSpAr=0kGr&RvwK-z}v5xz25rR(d<9;EN)&Ev|wy>xwV&=Edj?N2k; zqw5vo6h*%8MEyUKI`Xk!lgwe8M;5O>ZPyBmn5ykwt>ez@++CvCy_j$){9{{auG`ZI z{J$eS_fb4wUjd2#H%?fXN#+mdUL~D;4w?TYj`AX-Ei_=Ndry_h9pU%^y}z2`wVL;u zte?WM%Urn(Rk|VLR@L@);%V z=@ZDhWOo+##Dz~LyW6)-H~No1!S@V9UCT+u;>d?^oa@m){1q~o9e;wJ69;=U&M&6# zFmeR<+ri4 z^mcAkRe+m{HmQ_7MnCa=pn#U`eyt@L7L)#(^KG(LS&uYl12pGOsV8dKD3Z?NeK%jV zBhg-QIHoywN}nMYx3*X0d-5Xh%>^y|fbL8(6h>(-H*bl^mDR#a8PjU;WH7kEEV#zM& zMH~ZKX(Pv!9H%sXq)#F{=1ZPmldM7>?oR2Vf6X4Yd6OwaWb4)3CSP3M?l3g6O*VLT z{NOOTQK~NAimK>D1DDB%z3F1ie!|9+Pn?BriB*W{ln(4ubGP!l(?+Gk(}cXU=-GB< z=PxZI+wxX-pUzeXU&9i#p?O%Pv>9l9_QGn2xGcm!QQid|>Md>c%-X}oR%Cb|?u7qe z@GPCo-MZEDf+KRGQ(Ea4l)9zMJ(TqX-Wd<^SccFyvX^kVOKp{o%O`sM6A1(39ah0x zf%wSi4$MFDNwpVyr;OR5`1?MKgnrFeqxRHfZtQAD@o`{;ki$w>Z4VDirr6p4j?8$R z`Sr+vVV9@;yg}C2?Sx;0UH*L~9TAix^etu7w8%HJK8v)VvQ)7M{|lnatUo^*`E(Xt zLoH0Pwq{(5bHCJ%r`&vRxSabe^1X7U9#sJaEF&o z?WBRR4}ChG7L$U{qcy!b?)2iex##EBB7?*HMn_w6_RLUy2T(zz zVJA!bg=k)9?%`pR$H+Xv##+`>{gt>S9|i445tU`)eYVMh$9k*HY4RJm;(*aIbm!n* z$6!AxhR^a?U?@I$d=9oW!mEBPV8tg7Oj0GjC*r%_V%~8q5N|nlaS#{3WB6My1Pn)C z2ckyfwRMM@5mb<^6OJ^20@5uVOD$&uW()O+s*97=C2k4iW%E`~rEB|%|LU-* zIL4z|B#bI0Xgoz5j~D9x5HRc-36$6$0)wg{f=*9*X+v(PItAx{YNLdP69!&f~3 zx~!DH!tav-R`rqROT5de04u^fWuFeP^5V8Bcii7Lb;PG-zb%V@<%NK`D(>ZDfkEZR z0*0bv0bTa705ZE*V|~a8y%9R9Cu(Jx#obV5b(4Ql+YedROw^M#RbwuYMQW+3Q6AI4 z#&V-~*~z|hd!x<+|GpWzw%O2QnmBTauET9)qhV7}u&#&!pqyBUnC7Ow9^lHX6JZNV z6|!Fs@N-@d=n7v4o;5Ez9M$cHVK)+{=fVCu2KMND=oge>3>F^qYRGHG4ikLq+Y(MT z0eNTUZl%0J(@6^-`;zOUCUsQ%;-1b0hFC_4L&g6M@Y#O|=&Q&UN60fmk#$OMUnH;Y zvY;s&V@SF65s6ZpO_5BWV+1jpidF3pMpd=^v3eEn6=%m>mv_L{cNSj!_kyhaY=A3b zHS>`-{cON+)E(Thi!$Kb+wQ)@9bAnPbiY9rpGSkMixvjAAI*Xt@OQy?cV*%^Wx?(K zvjJVX5ZqSez)Zz&M4g{18zT397hG}liQr-jaSjxicG)BfZrE4hP?^0(BNG5AcN_ z23yLP2Xtk<{OBsXRbE)l3afEM)o#@(Sz$FGyH#Fflk5N8Zj~2KxsG+XBJ-d|=b}L7?B%N$m>l~I7dEvJQ+(px4NXq{q%T=fJdP{%HRj2eyOJ4ZV0r$}~ z6m5O1RP0tmk$ZE~0R7}?=;H21UXd0dDZ*DVBWAI>p1q3Oz{ap@*r$$SC8a7>E3#I} zI>gREe#_E9ese^V6~7-@ph&L05f-e2N-Q90=X4#l#CMBcfKFq!w;p6EPVFjsZ@P-h zo#fQ)SI;{>SL|12k>oIv!pMe|hTV2)4>FM`zC5yF@ro2SrH9|RDYpOCVt+QZX!?u} zu?1wm$_r1v|Nq^7bqVqRdEqNRunTMByuOaBp4oX*1x9+^CFw}JimPLxPrZm}heS6D z`@tpYQ{3skJn~LJV&d|`Dmslk6_yUW?rEo`!v>_%ec?OG@^NftsKInJa$6ZmZG>AY z!Jm1jH3!+IblPuf=fV3`1eGkb_{*sj4Zt@p!MKZ?>HG?v@H04FQ^|NErDaH;NipQX$K$&e09gFEy zB%S_GGL0%%!*71t$u^J{r;V`2E3~ybI7U507vwoyB5HbEMzgm)rB@!k8S-eKKPTlH z?`F~fi=3(dc}+$Q9}@a{sm3j;nxB$oVFaE|`8Kn+1oE?>q5XJEz=3sOfW?=GpL?+4 zVaNG9kewrowR;mHSU;3m+lyYqqBm8Corun(rf1eVR7RNFzH)kg)HcBzh?`ni>WtP+ zcaR-y?%Gm{nc$+6?m94+Lq;nvazS_lgQgBb7M-n_jUFX3Cw-3;=`(*QRkNo~_iq|^!x4+%}`-4y3sjaiM zF@9zOKfAp*s^XHAtVC6`drb;QGl8Q)pN0P0>^%*gl%nDiI#Lq#)#k2OD`@=qYIBE& zv9xS80z(+`F?8n9pZN4O6g`LU{^h8s^D~NaM*W-}4rr01p=8UsZRS@J5 zosRnz!KRNovfL;ma)la^Q)76*Sm=xnnT|Y}cRdf?_#s(8a*bmLq9?e!Y;M>=j3=7( zV>86H;Hc{TabJ$Jfl{*^QQLH`eau~?&tl}Sxi;STuO_-q-?e6`scX$LjE$R{h`bE9 zU`B+Mj@M71hVE!Y4evcIkGDaN8l&$vgmjM4jfN1%HU*4R6Y(s{X0i$S67U_LCh|^D zkBJV8z~c+?c?T?5Q<3>|5n?q!f!kq?eFGXcthbPj^p+1C$4?*!^DW4qCEO}b6b&0W zr>Vm7AkhV}PVeT~ZPrzqV1w=$4FF>XIaFe%(VPd06ght93h1^j{u_VSdytOh$( zSIsl5!wp3HL*i#jKJH5r7tl>OQ*ZNJlD=!>ZjGO8=@@A-O$ary8gtj0XJ{@`t*3 zsc(a-0TicjNvdhnO)#8!=pq;UUES}!{6FwsXHM87lddB#X!b$UDhK-C7lM1~*ihtK zyCnS*b_I6J-HrCJ2l!bWH?y?+ar^<~<0o6g7{%5An}gzkfdx&8NNWi5imyhL&Bbnv z|DA1l;n`Ec6U}6q>vmG)8rkOTkynZbS{%q9|72Pwaru7nYWBzz1N3=~rz`JZp!iyC z457bVH=hsr>34Y6?8&={b0jvQw#_dsXW$|Be$=5)GB%TJqU{DZoVG{w1D>YJX+N9` z2A+&+R2f@Kes_;YaQDAkQWN|W@WRhm{N*Islf-{X+B?7}{uTU;wVqv;{-Z*NOh z-WOTxEo9X}OaO2kN!e+O&INvyO?tFF6>+ac&?#b?O|r%JD5CVuT2#?ZO|_-IJ)kF4 z{F>P*xSV0V2~=fqm$Ni{=QEw)|Ng#TcKnX>*OqdVf9_oE@Sosoih(^)>@hu{e_Q|k zSlw}kll87%t&{LUFTP02qoL8$J013Z@xq7|KDdE)Bkyo=$V;YIYUEM<_M^Wl`1Hdo zhA5t48{(Jmt2-vA)a}Cz4ytYl?^t2#5^_+XdKbpz_a1z^`>3g7w!yH@)Wwu~SzL$5 zd|&ucWFnaA2UeUuH_t?kmOLjLa}1ek64w=k-5eWD=aOH}b0*-*+MGvn;_>OtnVn-< zXX|I> zPstA|89JV9wr(tl=orh;zjhWj?t=WJjc0+m!*0r0m*54T)4}%9kd7y0+ zqGU#3y*5Kb&qdsILY3)Os`$rE{sdKksO2|^sW|{@KUhYdSN){pfn?zCE@YPnE#+d{C zM+au%>5B=-8cpApUL+c(oF#)dC2wvB@A1Aat`p-oSvG_E^q?^Y+Y!o9g3b_y%`vjW zg3DNHutEt_V8`wUG!kVMEPKdx;~A5GwUN$K?Aon28l4!!RLWU~V-&NX?2dFT`4A|l z2F(^KF#wnNysHtE@*whT7K*MGY_kc z8Iepyh;AyY!KgijaV3ih@fG60a}fuB5E!sim=DYY@gw9BueB`2A4Y4RgiK?z+ibfC zgLO&rw?KM<^^(;~5W@LrXXFTsnIIUiFfQe`it;be`TsqX^ zc}GX{dSs8_fcn2I<485u7=Ko_H((xk%B4NCnJ74EW04pS4B;<;r13JtMyr#?SIbX0 z3}YH!j3?V^lpf1>wb_1#`BEf$niEKD%KT9O1Rt&ce(kd`Qn-e&0~Hu6s=bPF29-CU zW)d?u;5%C2oQiIxd+2kxI~03Zhn{`X^eAwa#SMt&cg1a_obrR3P2xOp^=$4AaTY_1 z%S~%ikY~sLJO=~}su5q3#ZZEFmz&I+ zO|K8C;U@mf^=#)0@6^FMKV(x5o;T8VNt)L)6#0?@D`4$C*dOVJv%-+KY~{qs^g_PHChmWM0I8DGf1%NFzp!5D+0? zK)~?Q5HQ-*MuL_Ypeb@Fr5p<$N>M3?P-@Xq8W9mGwW(4Xky=Wr4KG!S9z;sX^Sx%@ z!)(y@oZs*He4f9aftBmJ*1g{Ly4PNN?X~Bnv}NLbPH*3TCx?zhJH%Dv`?nn%lFYjZpW*I?PvRtX>+;X!{57` zZ}SMqOIPl(_wdP&Wqck{PcKe=@F4pWpWquQ#B1Ii{$8&uZaX$S_DhoE-ei3I>zmPC|3crZz z8(TE==FYS)7Ia?Cr+L!&-H6q(9n?PTpz66EPp@^4Kpx-l;JdwVoex?W!<}~*+{zhs z!JZV;zTuxJ-18<+!Ea59=>6r%QT;y9SBB-~KuE0b4acna>*Eci)rzLc&-1^QPOMdPRD;K>I-k)c5G3SFZ9)+*B!Xxcv6>?@auo=dck_?fbX8+^h8p(rQFNE z#!e@BzjEEX&L?X4bpm`beHXLS<%d+VYVq?c2;INn<6D4wd_ljJ)5bh?1@G$m4LS9G z2K_pN&a+C+LCstA|CPFKT>Se6e4p=yRPoXO8g*Y9q( zy1&hxbo|AngJ<%HyW;O%-cyQ$X9`hx)mi<=M5t%&mI&ENj`N()aqc9L$fgc6Qt3E_3Z)mDZMNeaB@7`hbf4q=hV(P0InLd);EcmpsNg7g z9pjJ}=W8oL=;W{bZezZ_x0q9+^Y@=%5w6f3AEjdD_4Q9W&Q}AD(*hnP@QUpZ3f=QQ za|m-mzVgdBFdct!8h#1!8Z(l7ZYxATlQP<9|Rv# zc^hlV_W=0_w-9b8OeTDr@NW;&hhHPE&dvL6@HylEww8Qr9Onjb8o`cFzNgKrS{#%$fO^MmA{xSX8g1^8q1npPPx~j`f z$~XC0+Pj4@3;uxMzUpbOJLdRaE$4xsQZM);g1utx{qrw4Mn=y+!0TG$CdTDU%n3U< zj$M>Va9=7jcK(1d?x=p*u4&^uc^N;06A5vSIz0mS+CjS!jd{2JqhX{w$yQleebPZ0i2y5S%n)t-@J>2qFpS^~r+ zd(umv_qpW_#-4;e>r0d;z1i=!bG2a_1N}|PJ&b-2Y&YzBnOn|)Hm9ZAWsZ|{S)v^2 zsM(XT=c02wcIj-whm^Mud;{DJ{u+GR^h)v{f^)LBJOM5vzcjBk(hm{3Bsnw6NtgB} z?f$LPGM>1Rq?1-U>B6p#!vnM#>0RA)@8h2g;;!nNNGIK$Aph|Gg%~CozE2s7Uq*h7 zhjp(yEi17#lRrK_q#q;5U$ZUU9@5PtuF8{6dWxlc3siq@v-A&5&09|FNA;%cy|kj0hz1OXp!vXHseNIW2|gT0Si&PdCH2sZ;%Q0Mzk4M*CXn@1FGUI^rIpebOply4uq3>fyF?-iuDlo*s^~qer5h z(kspWB=#rJ%RP4KJhM9-XFfXDW0#)IdPwaVXE=akl;1X><+mpdml-ZFeD#xVyJo)N zw7f>#o=+y)C7qe(+B2{R(;O!wEn$}q5Hz2iC2ee8v-DG28PC|-Jn5x(V6W=Q{Ea{M zptoU{R)0xvwET<6zXiSClV5r*A$FXkJ4rv+&|kI0m6A?cYIms4`uK0)ECpN%T(V+!|~)%f3>02U+;X{Eq}ywPRl9cj(s{& zzVuZYRzLRi42~xwVVBM!sQd!bRdD_4NxCB929r)&$0I$+(w+J@cb-0MxX18m!)imv z@C5lZuOBjeHPhAG4c8g2G#p{L=`(J+Rff%mCwjZbQ@Yt{IoaEBI(jFLNBSW48ZH7$ z@b6*te(ch`)=2NR{MF=t6}`igUwS)1{jrI3rTBB%Pn?#m#BCs*w91iQPtbh#gkdeH z^Li>MzmE^P?R;yK(=v(r#|9Jal+GpSeA@L{H{HAz{Lekoj?X62Ngw*0+n&9KOUWny zC--svf1Y@a1AfvghJE_D<@Nk8r=>4xdiP0`Cq2pR2R1q_Q_$l*cImMM9mfd6^Ok-v z>61RsdeDKv)Y1yUQto-SezaPEFQ@->j zg8K6@!;CDqKh9By{InnZ0Q-EyJW%~Jh;-_g9K%srZoj;}fq8|vky(j;ksd)%IW?AU z7wH}(Zl))l^fI%r$G!r+#ABE4e!cTt^m>O!1}SU&Edq<~qxL zU^{v*c4_%ndb{PXBmYkHR!@HEEdJWUCVV89roCzyG7l0o{6|v z%rR=b^&wvUm|-|P>bC3DYNurcaYfNYyQJ&P-io~*UF)$+*AO%x<=)_y<0oC-4XjUY zNR%Vp2m5+(9_L*@uore|^_O(O^7kcwPjoj=e(A0R^?MubX``Qy&@Ua-y_@z(s~qX= z1oig@Yj-o{Z6a>Hr`^)Ct-ihAcUo#FyUJ6ablm{AzUhWLh*SS9K&$^&3~-Nk?GsMR zD&m$6NF1;9A+wLeegu8MW0!vPMmPVn|KYSeb|d=@Hzx8+*I-`@YTm8`XJePvaY|3c z-obI#u5ntb(3RMwWtT3u@()w~c=Sk5`O+f@>>bR=Bi*6bY5yw58F2$hC#`a%QwZwM zez|UcTx+<4c=bmvTK%yx*FBykl(U(*r*jj>BR$UShp<(hv6Jmp*Iu0_^9}r#*J*BiPdh zx&AnTK7?Ib{Um(=`EqPKaFxNe(TF_eJj9r>a7J|2hXvwBzzlu6|4buoQJ<;^8~2)Euiu(viMq%d*~S}t=`qt zyPA5Id+L=wobUES*)pf)L_Y1vPxOQIk(=H6-ZI>Cvzxy9+pG(S+jVmyz4Q)Cw*llq z*^HH-`fncjGT!8R@g3GP#8s0|n%5fXnFRLyW{f9Y@o#AddM5VxzL|6dLF2X@+z$>1bv+nt@qI1-RgW|86W7O+UpkYZdA+-(>qok)i0kG_ zCw+E^+l~V4=Z8?|kVHGA-!}Uw?8nfDJ$C752{FHe+SjVJ^gBskM_i33y>un^67@Is z>F5gV(&`uK!G*3LuPSu?mxTX%5|>h#@Sk)tLH;{${wu(LNyMG?_)mHi<)~keG&?Qh z&?6})-p|q{1Rwph5Y#xYvwBzHw|e6EWWa@SBi(nX+rCUt>3f2I0K1w#W97U9{+9TC zre6g$J~vwVQz(BkaqB(hORpv9xK@x(<8U4AeuB7Vq>GPB=`i)F-v@!Ofd$|p@W4>_ zcqe_!X?csd{X-MSEB!n{`Idvf0jn+j9@5VvZl))_^u8juys1lA*A zE2#P4X{7<{LCq5tpz0Y6o&bZO`fYZxTkhUPtP_e^Cln{jm98Kt{YdaHU;)?xh7AMY zQS|v?Zv6UTZh6ZVJ1rZCdvaK!Jn3;}KZLykUFNY%SKs2cs}hvoiorL)LAN;1Z5z%v zOmSa5n7HsQ&QsDFuhIeR8c%&Wj-Kdl*yH0%`XKGpdAtYIytI=z9sl#7>RC11&A<9F z=1a<5F+7o9`cZ<$O&zHItu)L7)vr-d<7MPm+tT#MV%JX{Kx5F?W)cr^{ zsQZ)sr27+kn_=(U-SVdxmKiQFTxQs6xcYYXS7~S30;gp?ajR};f7RM8T{zM$uMenp zWPrNPzK>1ymZNo_IKtA6Gkg$K`l%z`@<%i{Ei;L$9GNIzdI#YQ>5hOZw;fb}o-^!q zhpTJAnEejtIcLA)WX^Y54ieXPhx3%Q`dRv%+0Q-fv^Zs~zdUy7EP~qG3sgPXhGn4I zJKQk0%&liTw}Xn`0lp6wmb>+iFL%pd z`E~Yrh#OU&C|`OcLHFq;mcGvLEyI1}&*yqLsorThKwKO7rPUA8doA5gP~|*tdb8nr z!zGlHN&hXR9m|M&jB=z^j`T^Zrgj7bXgb4mPt*P}Zo8wD zmrUGwPkGX11o>w;DF2ijK4G}raEW0ZsPywpS5m&(zqyXEd|sOe>ft0%eT7SY~y#I2f?C|CNRrE4=hY{*-&rq==Y8{J*Is8>YM5_0$S`b} zZ0Mij=4&_IX1LdIr{Qc%chK-P!_|h%4eO^k&*l7%aX8y)Sx8*n6z3^vUTdTa?{(|T zHSA|tYPc4ZKUSJvZ@Ay^Rl{AN`eDnxZhh6WoR;T_+jMWDKIuNw-1-iI>M#Fv7f)Gy zCR+X4({SN^%#-_AS54*p5BISjaUb&}TJ4t}Z}w%_r=mxB?9z*>-14dory6#j;rj73 zTJ3n-;@>jd56aKGOm8<_Z(E;Ot+ zoNYJ-RQaP!7aI;T9B1tqX}AH@aV-Ni9;?Baf6Ttya0jUKc$?`dHE#Ql&2iUR6`+o% z6x8}D489Kb0#*L=)vjIy#_o4PrLQu~u6E~{?)Nw?VdDB!C*~RH?KQ5wu7dps>VCc^ zVVCZAzq@|R0JSbVGtYT0_$K=ul}^jKd0bQHIZsJzJV>7;Xgzm`bXxC^8_&K6aqXmw zum7cY6V%RT!-a;WpzcqvC4VyQd+YB`OCRETl3!Z)r_urJg>SGQ#BudPCu5gZJEWQa zV(Z#T6S$9<%RR>2#BoW#N>KefK=uDdOTUEln~8hclU{nI*`LJz1bVs0F5OH}d9|SK zBgb0$!=xWi+$c|a>0<1UF-T(pw2?e_^eg?#wv$!)hHTzc!Ihx~tj4 zl+zuZz5zOQ&U5~6 zu>RuthM%}|v_o3wi}XQj=lC%^@1YI*J?)fUP0)Sd5>WHNeDdhHYeCg>_#xLX?V$SM zRZ#uE{vr4HmyP1SmbkSKCE6>!(Ck~VFG4Ty*rf*$l5Wmk0NMawg^;v9$et2=f1P$p#?l2 zS&&FCT}@EE6_)NG=_-jE=Se4BPEb2Wf@)WurFU-UeKy49deTcr2`V=W{2l3&NuN%e zx}M{FC+>U$?=M1Y9+p1c;69&v%WxB@{L4Tturro`|Dc=&U=q4ccCZFid&Yw5&*2R` ze|?j4AODRcu8?}9d99Jo$DWNoguNI&2)nfG(gO(Ux89`F`Q2+I*A3!&kuH9|OQ#cb z-Rug=pYP9i`?;9(XYkp{`H6m(K4f<8t6Pqs4|?p<`w2SU9iZm@RhE7==~olC!joS5 zG3=V>w_kr|Oq>!>#ncj&nNa$9Cej;IH^`OK-xieyYK~4ZR+Fyx*kP z64Wm%NvD2T$2fU{xaFja_lxv~N8NsC9>H_5M=9sgL_bKcC8%BFLG6F^1GQf7YWVCU zZvIDaW8L`(_kWKh@=FgVs2%-4^;a)Y{q@c_-Fzc%<$W;UWG?(>BA@hrvroZ(0R5WB zF8wM&<9jFRblg+#;QB^fE9v6>Aib5K_HVHC>%ep972xF>r{F&Hqu^U$J@^N(3RJ#H zp!P`ySv{wyXE1RCJoQLli(URn<~+|tUxhvHFX`?C^@E>u>i<0YJDE6#ba8)3vyT(?bv3y;@{giWNp^F6K=G68jVA*tdBs(GKY>vj^y>9CXlQm%f&ue%WN{ z7E#U?;x>5FNiQ(_v)CU)*Lm#HH3XIO>SDJ&Gl%hh!^ONWWO1TB(z^({pKAq`{&`U2 zYrW-LM!pTit@Y%SW?wiq9@b-Dfo}HLrR&VT9s7LrJda&^5JB}hmTpin>lxzCE=sgd zx?_=xeb{%P!rM zp!4BmlY2g}Z{2dPiFPz4&IjqY2x@;jsP+0DQ0;miRJ&G^U+dW?$^Qg#%gG;K-%2ke zD1W)7J3_i~#PRJC7t%==n*9v+VsyU8F5R7=aeKPkbv`Vl9QkD}es3mjKIO#yB>fO}jsJDnA4S(U;=V`_u797IB%L_Df%D>60y8lyoV?`90~R-^M>0->;f~ zd7jqt8gV>dyKtUJZ^f?h{22D_=q=de<6HWzrEY(YFJSyH<$AR=kzab3+2>*3gWln> zOK&Hrzc-Uk{r3>_)w9HHBwf5erJo>ZoIPsk>p}HPHK^Nxw~y-wta0(Jz&Z*F(g$Yy6=# z-%0m+-0hcM9Cz=>@yp|hev$4;Q2QOjafZ7)A`&TCH(vO*aGxjCuM?H4wB(o2=iO)Ns&wn?OU;3Tzy5*0+eu_37^Vp?Z z&0dFnCwiO5F3tO_V*RnpaEoEJVTIwVPq^uJ7|u3a`F+=a&EKaTjK2-kvy8Y!->02u zjVtMe1oiuT!y3cchPkUr$v6pv8b8^q-SPsImqT2i)rs@x4*We>v)b*K3Tyw{QQjv*+*nWhrAJ{` zIR_XgNjhm>YowPFbpDKd z(k<^;n9mJS&xj`zwFFxj#%r~|4t6~^TZ8bo2XxUzS*D0{wVq(k6rpfg6i-01Gk*+qznIm_bL1!QI2#5 z_WX6+*VC^-bQ*SPUTdVgTK*{cQ_zklzw|lkQNM1dJ)7vq*XaKP#BHWM(ke&#X@dGS zd!5^!K!45~>d9J{XpeM|pmwJlb_3Oa=c!BBZ0*@V{$<21^0Y@fE{XR z@8wUq?cx33Ei0*K=~IdJNY5wyopf1-JFWiOZ1w@DtJPD#^y;VG`j;3UAfNho16uvN z<7v0P2m3lLyNKKVbfP}#BW5qh-hqD0W0zj~jGO=I>)CI9hW>vhkzcwVdvpW)D-O>C z(Y4s6b==a^Eq@#NXQL}U`K2oe>c0V`J4t^%+0SViPFz3INvj;`AVK}d`}||`3h(=G z=}BC7PkGY4Hn{d))YqGKT)QFBUg@3$jgPLN#_xVBZwl!TkpDGLdD7`Wa@*%O97#U) z?*O#=H`}o2M{d2RKTrFK%l}cLUg?L-z8(8~^n)I|^gM#v)pw&?&Q+xAw~_1E#zZ;N z>DVj(i}D$-ndqyqORJxx{g!_K`MaUdd+L!sOFioE=V?#i0P`f{YCmxsNhhsxq}LJD z-y{CZZO=QO<9wu^;s2Frk8~kH?H&NCKMz{{6@6GQQeV5L{nFW+-1@IIb>qJYU9O-QA zC7U_V>CY%Sh+SI!Al=jQ2dO6m9q{CrP9dm2_tTzy`mca-be6jJ&>m@(BfW#5{v5E` zZBJL)Gl+V^n-lGk&L*hcy+HNnZmYkB^n0mmr>B1DZa;SGKTUeIV>+nu*@t-bXNKY6 zA6q|MM?VlZ=*NkArK`=p2m3tqOpjf9IzjEq+2WRyLb~V{+PfuDj&v{V+rXvF2YtYv z*rnAE(#e)TO#bfZBu{?n^VFl`I7NFZ=)XyfqqD@lLwlrEj`T6b(Vmr{`gN6+yE>D7 zYRXyRDOdU-vv0>fAN`=mEmL8+UaY@~d-U1F@k_Uw zy$<_M^fr%OdMiQwIQFM*IedPxW&BU6|EGy^q>HhqZKXW=u@s$;U0TO49k%?*)RT+O z_T-n&BB&oTNmoz5l{0>V#9d1|X_X`0)6#VZ)sJt}KHYDfwekyS-+9?QvaN2vtjpl~NL=&QM88NcvUFQ5-2&2W zBW{Z)o%AM4*JgMclpo(VJn@`6eg=VR-;w8C-0(9Ory7OtpQk?QHiE`uD;QhPS^5p6 zUq;*_PkQNtzi`{P+prrbKb{8Fzb6d4wz};aa5dMVR*t?k(Jtx1WSy?M(q^jp}ad99It&GJtt|9ExGPdlY-h%d9hAPS~Xn zl1A;{Yq-bKSAT+WiG7zRz4TV>xqE0g&+l8&o3TstS|h#I@-HX<)95EW`K4D9eE4k< z=}zLOa{7H4aSKT&t#YIn5ahQ}pxQSWRR1RLvE#Xt=ifP0mCZr zSLFAD?}E#ByPsps-R+j=r@eW^MRzC4lTK-~@_y~&TJQ|{pMTZWgF!#@OCM0>rv1vT zx0_+nUdQ<>@kyZCwcwZT{pxhuDZlomeEBicu&B*#&uKsFQQ`{P677**Y4#_vKY?EE zu}jwy)L&`8aodv&s(tEM)t?4x-tK0)-SC9PA2yv%Vx`aKkJ9%xonu&N@qgU4 zI>TDS8pGL!Rfbayw;66V++w)NaD(A`!;^+5K()Wa^bx~D7JtyN-EbeMerhwl*Ys|~ zEY}bIpkbzAhN1b>&wAPUv(HHh7^WB|8%#8kT^u{x+2V`R~s+%rne2Txht! zu->o^R5`VVH5NbHu*z_%VXNUbQ2Dl+-eP)_;ReI?hHF9o`&XM@WqO5S28GFkT>IEP}WMkwDo{y_a_ti1P4pENvS*kO1C4T=*4O7gI$%cMIAeJva zj-#e?46_YO42umfJsw5yL}<2MyZ|_ZhYs?ls(P_&<%;VjH)GhWUnhhE;}B z4J!>R3>O&I8`c@t8m=;2VYtk&+3=uYyWu{=Hp4T9?;D;pJYksL#qB43UmS<*4;ZEx zMh$ZevkkKhOALz*3k~xP>kMlRYYb-_`gZ#^`P;js=Vq<&&92PZmvrdzcHaqKdVNxE zv^i;gmm^8h+>&j+b4h!<_@lvAOwEuie`d<2%UZkoxA;yZolDAy=C1WEN=nbp%vj_r z%{b|+4Cb6mN>27i^YV%-@^|{$d>i~7RFb{hS9;LrFU{EGTjujG^wnl;^KDH^-|NfH zIG9|w`m*wz^wqxP~B$QPu8k5no1D{w7~X(g`1J-s)T6JLyXgMz{D@CGGR2=j3ca z@Ajo<=j`;&Uf(4>D`%~56~4*lh%@QNQwffrp zhptS|DQ!>kr?vY0X&eg9O!KGZYZ`Yt7`rKq)>bC-0NC|%$N3ajje}S4eWoBrC!P3{ z-5Jne%2jUqvYu{yPCDy%;`e^qwYT@;J{6tb+r<-~bK@8Fb#YTR`(fBK!)`gNZ*cRs z4RCSiU}2JT;$}B}N2yy*^PR4}Vw8(p$GKR4x5bZlaUrO7)=qM@{~lLo8&(-EG+b?% zIhkkR)Vmebakow7{+e>K?sLn@uX6jReYT5dzUpFCty}&fE?(-l!mqp7@eudlMBs}&HQ&)ZvwN>_204h6>dFQ-*xp{;APa4`#rb+bN|EIV_5us zH-6JoZv6VE-F#bF^r@bTXI%_zb+LV$TknP!-Sn$;Kc@EVam#7uULX_gyynKY8xFsW z{c&Gx2syVI-_3X`Gp@OEjPW+cmcEBl={w-}7|#T!8ZQQC7(WD78!zq3cd;7Z0 z_ zSbX3!jA!GeoYS%I2~~RdbrzreS)L^r*Y}Wp!T8C~aSjBD#Z8qbFhGF}V+vT=Q< zR-y5%EWVG|cs_iD@hbQo#uvfMjjxB_ZCu|)HOcq__`Swg!}%`K*l{)Y<$IuwzYo{< zlByrlzu-7uGhPg@H@=g3>k;F7;rgyqrPp^Xeam<|e3|i-FY>+O#*5)k7+(PYPvd*x z>y2mk<&= z4;a_?2mQhL0_I?yS30hR@T11t;qMrKAO4>4wKuZQU_3XM_evPggC}tvQ8_!|mmAL; z$eeGy9NxpYz5}MG@sgVy=hMb3;hDyB^7!s%J`ce#-bN_y@-K!p|8$31@i4##rDE_Ro!H!>=@+2M-u8hF@*G z1b(gYMexrUUk?u&-vYnh_%?Vy<8APW@%Q1n_Qd9mGQNx3xIU*}U_1vt%yu;vjVHq=8`tOZryDPZ&oZvh-Pahehd*GP&%rwn89xVa zFkU!@Jx$~K?D}HkCz3|tKx53+u?}Pu|_(Aw#mXF@6Mo!g$&^_EwE& z!cQA-h5z07Le`cn24nu6%DVD0x^S zEaUnN`xlM-;bG$|;5QntpTv7vjq9`KL*i-RMaFl+zhe9lywrFH{7&P^ta-;855OlF z&w*DO&xKDjJ{vyM_#*fm<7?ry#&^TNZal4$_nH_#3IC??Rg-yEVLWdNzY$=(9KPJR zJ}dq`<2CTr#!te3V7&NV-eY8Z8T`MDuZM3jz5)K6@k8+K#+_-7^P=%=_-^A>@Lw6< z3xCac2mE)&Q>ODQ!gvt=N8?%Ww~c4Rj~ma2pERCxAKwLRytImELB`AAoyM2J{oUO0 zmNtW-XS{eOamFj)pESM&o^E_A{NIeX!FwAYH_LH8Z`|SDCEIuhJY-y-6&_%GZH?pH zWPIHHj+1Yk&!Rd*jW2``H=g@|7(T{$DSW)~O87m-o8eQ9XFSOI-+0;A zSpOR@tz-QkPY_@TwD|BdTC?kkPwKF<2zxZXd! z)_4W{DdT#7@J8eN;6FBg4!+g+ie;?-jc0KWx5Icl{AJ^H%US;$&*lE^H^vvj_Z!by z!TR5L_IFwT8($6ov+>gJvHmwc4*st3sqpuWAB2BseBmnA|Hj+kU9NP;STXmE$;OkP zVEu2rihIbbjIV}&%6R^2*8j$9;nx|jgZD9B5C4Mk+%>HKjqClJHyF=Y%lhAV6#nIS z8hD}c74Tb(m;8YBzj3`^@ebp`AF}?B$HVV7J{3O6_>~U{x{zG6zhNEdOzDd z`_aB-JoqEl|Hkv+-!)zVf5P}O_= zBjdT7SpOR@h5yueJ$#$-P4E|tk85H5Z(Q#$`la!-AG7{9o(=!4@pAY9;|t+`FkZ5S z^}lhwujZ)n%4b>s8?S@EXM8REjPZT&e;BX*DeM0fo5!}Y{x@C;PcdE(?_qocyr*%! zr{dGb_rWub^Mu>^obgP!exo3^?s}f}zwxY}vHmyS3Lj{EFMP1^`faTLjUR#+8|OFn zoLh}g-Ol>oxZYne+W3)Qu>LoG4nEO%W-IG|<7M#sjHm5j{cl|F8<=Z+-%G6jjpyxR z{cn6Ne1Y+-ms$TC?|?5cuIJc~8&7Xz{ck)6{vXCG;7=Og1plFU(n2TsHP-*eQ{XMe zGvWMpL@doA_|J?Vfwvkz1Aoc5^E&H)GkTKV`fb{(1!`Pr&^Sm9qX9=Q^tIbHCDfGdy5i-xGGVac4j4f8&|( z&ls(z{w3r3&5Z)%`u&Gt#`T*7CB`$`S^pa^ zg^w~`3%|?wLU@Jo)$qy2+u+lUcfe;EKLf8ZuJ50Gz<56VA>;bqj|SsthXTWzF z*Y{q$VqBjIe$}`>NA$Wl<3OKTZa1#aIq7%86tB-B95$}c9sI?(J_DlRlFE(a?8jJ# z$N7BLImVM0(*MTaf0S#4@c`+Yjh|V-n$vjhw_A zW-0X=ui)Bxqw&%qT;q%v7qTugo;#OywechObG{qj0=MUX)DrvN1nttsUVL`z0=H*+ zvG`9UCE|N~_&^UI>EZW!_}4w$o}0zW+~A3S*~8!TaC=@BOMeCDZoHit9&XRVVrjnY zi7)l=3J?FPhcEWBK!{9X^A<>3!|_@f^FA0FP~;V*gkn;y>lfG!++ z_sbIedJhkK_>CU^B@Zv~a0WuXHypRlonSvyIiaBR)(HiZC)V6Nv8rm)#Cz@=IeE_H z8t0#y@Ax=l;pBT}RZbrEai*f`$&)`?8c9nhR!^KUxrU#`{HU2q{+an-G&64cNE81_ z^FG?Nvg(O5=TuFs@wAwhj&gsxGttbEll5z!lW(r3+dN4vnjCk`np=I3$5J+V#_U;_ zCXYpucqD}orp-!J5I0>^&dpON&YU^9s^Ec|$#cSY(Bm^J<6UQ_qN<7aUJ^z5LuzWO zr%jrBp{14>OX0+tiB@SM@|KxX;w?!;mQ0>`Z_U(?q%EB_XBrKk`4L0eM;da=oFVtr zOq&<)6f1h<0^nl-Ve za$4n-m`P&p%($E}tFm%hM20yvm6NNhXN%5_$1)aXhMfubFb&M2+wYmhNSa(r8vYPJ zW%k?}Vy47S=c>w>rE1o_p&Vzz49hZO@{D_CKY*S*Ltc%MIhqrQ04L0zSi|W)Gc>@N zFniYh{Hd8r4^>W>!Pu;wP&s){P4z6YUmQJc<}^yUIF1J1cWK<*nI98Nh1LJ0;F#%S z3XjFp+L{Mua|E={?T$Iq807rn9H5_^33KOEP3HF(t8#Ke`V-cli2g+N=LY>5pg%Y2 zPpW5mJtja)gzG-_ziaa)gy5tQ=wG2rEZeIl{^j zR*r~rM3f_<91-P+C`Uv&BFYg_j)-zZl!K2c@<%zM$`MtLsB%P=BdQ!x<%lXrR5@-? zjvJKY2IaUxIc`vn8$;YJS3lo)d|L-Kh@J`c&~A^AKcpNHi0 zkbEAJ&qMNgNInnA=OOt#B%g=m^N@TVlFvi(c}PAF$>$;YJS3lo)d|L-Kh@J`c&~A^AKcpNHi0kbEAJ&qMNgNInnA=OOt#B%g=m z^N@TVlFvi(c}PAF$>$;YJS3lo)d|L-Kh@ zJ`c&~A^AKcpNHi0kbEAJ&qMNgNInnA=OOt#B%g=n^RRp#me0fTd00LV%jaSFJS?Aw z<@2z79+uC;@_AT356kCa`8+J2hvoCId>)q1!}57pJ`c<1Vfj2PpNHl1uzVht&%^S0 zSUwNS=VAFgET4zv^RRp#me0fTd00LV%jaSFJS?Aw<@2z79+uC;@_AT356kCa`8+J2 zhvoCId>)q1!}57pJ`c<1Vfj2PpNHl1uzVht&%^S0SUwNS=VAFgET4zv^RRp#me0fT zd00LV%jaSFJS?Aw<@2z79+uC;@_AT356kCa`8+J2hvoCId>)q1!}57pJ`c<1Vfj2P zpNHl1uzVht&%^S0SUwNS=VAFgET4zv^RRp#me0fTc|<;s$mbFHJR+Y*)a{Bl3AfK99)f5&1kKpGV~Lh)a{Bl3AfK99)f5&1kKpGV~Lh)a{Bl3AfK99)f5&1kKpGV~Lh)n0qw;xFK99=hQTaS7 zpGW2MsC*uk&!h5rR6dW&=TZ4QDxXK?^Qe3tmCvK{c~m}+%I8t}JSv|@<@2a~9+l6d z@_AG~kILs!`8+D0N9FUVd>)n0qw;xFK99=hQTaS7pGW2MsC*uk&!hOf8?Wo~CRX1w zb#S-OhWdwdG7~Q@00VJDZsf+y%o}n-7+f<~xdG8AaRUcf;)-q)CiJ^w%&mn(i!$%H zZDiTdLTxax#S?q!eZZfA3G)m`8_qCXWcZ9BTePwCe=t04n9TiYtUSYr;c!E}H(d4y z4D}vtY3RoHJ*_l-hn1zc`H<;EOXR&f)f=-z=5vzFs~1*YlS{*r^pOehKHXEk( z{EfZbGVYl*v1-Ch6?JJDN#xfa;Pd}*+nM!ApYsLMvCVtoHDN;4G!A!SRn(2Tw2Zxf z=NT!c1su8B5r3%<;;##3b{zVzw`%Rm9ubD3`qn=|3W6Qma#&}-d4x_ z|E5!hN=c_rz4_fdmD&-x@R~58a7fvZ3({#Nr#?$iOBu>ro9c5CNA9NkXn$N>MzWuN zAag4doqSQuhW3<16)|RI(6ij_^SZPh0mfwo@w>aZWvsuF_eY~I9v|G;PtXnSgeh~i zk8;Ve%%%+aBa6H`merv8g-1UZUK1v$3`W_Ek1wOf%E-1dR#+Jt?(x@z36ti`i3{-3 zcGOZv(Cl7+s4elA`hzV6-i_hDE-hmfW#pLcpO-Ov*0h-%i#2A#gj;Sc#FI~q=;btv|( zrm|p0Q%NwhDXG!dRN!wM;%_QQZXA-_RFKj*B&De!&^RQ}RFKvUo-Q^SiK zN0T$%=?rXD7dGrnzw5=(6d14q3n{QT^MyZN)>NB!F4X$o(@hQ4mu5-3Yr>18XhTy? zFr#x`UPo1DXJ=zgFq6W@ADw_`9_48w2AUasFEwx#;d_}(JgNO(ubzcy3dY25?w^Dx z@Tc~FvAR69{|oouA(@=ozaa~I^-xGQ-m(M~olYTh zJ}H?dc?Ef+Dx_yqLuTwa8cw84PwNbXT94AZ&U>jndaC1!VjWjR$E7zGrIUSlN^1WR zDRZxd7Ey3PU~Z}?g;MZ>Yuv0OVqIJmh*d{XMQQJ)j@^AKbxhL<>TDX7@m^}*9gn>B zK##_@-*hw__Fd6NMC*I0<%F@Vr&7n0cI*qvSgb#V#1;h_P9&d7?conj#XY>UsiXV( z&d!FNf!~h{5PRB(;-ZX(ohj2ZN0)ZIcdoN@O!@T8&inlxe}Oj^<&}?J+|Z3{O6jPM`$^n%r?a@> zEZ50Kf3UMA7#+2^K~EP;;dz}SQyO+MXjASQKjH3p+cK7$R(CzZjJHwp=@ zZ@iG2bq?Dz-5DQ8=TS;iQF`Ntub)ZH`t^EW>H-?>G@gCo@MT>_X|NX+re^JbFLfNl zrhFH^XlshOAds;h<8VgTm=TPI@?WNA;id8dKM^}fo+!rAMjJ~N_`n>Qif)tg7nm^Uy`W14o?kMmh#=q z5Nb`z)5C6hD0A+F)UsbO>|S7eHwNf~AXV zg2|)q>?k`M8!#MO;lszYR138>ob8$V&2P%@3m(_1%Gcg`sCgM=EOCV9}+lcoT~_#gnop9ghIk?gwj!?@9em*KclUCT;0T4 z>8LK|G23@DQKu*^ras>bbp&<0@{RcviZ^07Bc;B~_&>Tko^t3Hust7F-{_9o5LeSa zjd~)kZulfBcGjtsUqw*!IW17AyCY^z88~VhO5$9P@3%&TRMW z*Sr+_06rJhnqZ)*VP+>z3gG#ShC{v&+8bJvbfRY{jOrX&>0a;B?izn|=9$h;cNC=9 zD5whtPZtKWd`*`(J=2e3YCgjR^1`8{(vAYsHtu42V{W@~6lLXgmQFo>9cEu^X-Ckt zUjCbo#><(wrYDuUNk&d>JQ;6jYy!=^P*F}(!(1kfrpLd^-_YsfT?giEu9;pma97>w z+izd&|B04xr6W7`d_cVoO4FFPsWXf-4{zZmDNoC=#JW$mDw|s{(JG_9?$gx?r|5cN_;ds^eka z3fdSAcKjO8H$C$f6LQnz2lyL$MJHIe@pny04Lke;PtNVLcqlDZg6ntFWt??cjjSFT z>$JRx&D2N#ak{f}$iSm@dyZ!m&%GvAd;#zFz#};gJ7cHi(I+03cioAd_c8vzST8-U zre3;U8g<772LwW=L$4gart#zphy0B@x{NMu9G=|Rt;?{|#^RL5Vt>(1_h(igZfYou z6+>IK^5T$Vi?2k_U3^8%-~caN5#Q$~>tn8n_uZ~5Vrj?8|Gwyo_(vq34d{yaTR1b{ z%lhf!`Y*p+l=7>+d%qNFZ7fP|EDAIhrM+B~&RWgAM5cW}2VS^DZr7-Zjn0cMk%={5 zX-BOJkFWXeL)f)Fo3p3Xy>ezS#psBy==chD4*HSS)JHBSazl8Z^ zE|FC1ggR44zj^ehFuOSF%D6(31Wf&9k2sAVzVJtX~xBvvWvW*#$pPI#`Z%h%F z&GOwu>IybS7YSGCnpafJg(+s!b*HGrRXs|A86R+#$F5VXg?fx*1)Rhcuqk$RNlT7Z z6T2v;G>t0NgwR;n%`aqPPiZXtcSqN(t!jB{;h9d_h^=HGm^%L^`u8R-3N_d1s>0!x z-r2GA@3GD}-n|I}SCo>Zqe&!-pWw|p!*|x(0NWDO#Mv}>@vyTEXOiw8tt+1v#(#g8 zQE{!gRzA(=>ei@hT*=W*+%s^14P8zOwRpR*pbTn3`SbT;4PZf8%F3-OSQ@*aHe4P{ zKBkC{)7o@Q!!L-qH1ER~@)pPP(xqcZ__YpwnbiLW>(7#4Ib(w!sie!Rf|(tkcT0X_ zTyUHY(w6zqs$igI3e}VanMzn`xQ+X@dvGPesZ`EFVf>ERs+2!v!5k}c->LY4vSbaE z1v5!n7KD#wEkU}vhwFlM^1EBh@oH79yNH^ej2qVIff6-lJY$AYQg^(Z%4uF|)=P9R zA@KOSxPhcQQnU7uZUk$kagZ^r@>su=E5w%1r&4cMkG50uo0K&!SWC^dsyTH*u2vHl z^~Arq&88b+P-iEydy>GLM zu#xZ=f);}B5K^;#M*~?$Ok@ib z2J2F@=o2NTUn&+i{3(_sKr6?*>Q>T5&cc{)3xiche@ml?pbuj?DpN-{eTOASOjKc@ z0Tf5$Xv)VHrxxl|kNM-01~M*mEUEEhQGx%fCZ>+Y#oW|PJ$eJ}WA>TKsYcY88ud<( z*x!$oWQP~)tsZqjD&ueEQNFfGM;NPDoy_s8TR*b~R9oOA%EFT2ZN%@osN~9Y~_cD ztPEDfTBBCPTC*oUaKPA%A72=hH+A)K+Y*n+k44B$mZA{KASdytVkoB%bVNzR ziF&ODnEP$w*BZ1WnAS9qlRx#Fiy4`ebPHP~O{b5to$-x-J=KnBxQZooYX9AHMlBxp zVZ#|8V|`v$YX2c=bBF4Xy2h-xq}+cwwGRm#zd5!4ErI*{N~9bgK)RH<|Hj|I+)t5u z2%8=a%2BP|6jeX(lT8iB-e)F?UEgE7DaVI3`5L=64T)`~3}J04W#3ckJ&&<I<&_v7wyrJtLc+8h>?ro_KbhAdo+eqt;a2lphS+q z|0YKXIfm$#zQL`i$B?q7A;pbD*tAI;UBVS?4mSV4Sv}v$EdS3}&y4YxwKyfs2(}!P znjR{QZ8?Ts?Vz8 z97geW>|So3${$uvR4(hqVfmrI9?w`D{gDo*%l+lI-#%Sy#As(u$E`}(bto+%Z5G)F zzEr(`lN-NO zdZg**^3Ld{j^7hgL~S?Cp~JMam{Z>I3UQ6&bPuIl)}Ntly`I4;H2?Ha?R3qgHudWj z9pA$q>$ucM#>AR1*R#_V_!~tP2GdBu@f|cXt`eOqzXX>Gf@) z*FN}LlYcN~e>1~&C`GValt%g<-5Sqy8CuAVi*}fZ?~xUYAl03mLgfKs0!>2;n^JO{ zhL$(onb+jcZA@v7H>ELUer&ByJ8!x*bMDZlp?Ud@L-TGLnqPhS%R^(Ml1hh`Qqs^e ziW*v)+J9)t{oOAdYyY9eb5b9kTc-Nsk(8KB1$hI()cOC-(oVx;AV6Zm^-RRb18Tbe*Xw!wNn^qdLm|ep*j!KJeeXgX+2$iJyB08 zDwC*gsr~mmG+reS6*YeBMR*-5c>X&}1W^sFs$XOHfhWfR-c z?s?=ce`9?9|CF0?_%XS2$^FK7&V2W9FTDnGmcFPvL9RSJ=22&qbX-Q3__n~$4xZo! zut(G7GF_O^j^A23y+r%u2mY*G0XC!ACuhiImUiqWW(>1g@n4uyMs-}Hq4}xB4Y^2n zz6r85&dpG4zR7mC#uNJ!d2Tznx34%|bRBcWh6{7WEDE-{;ybaqVqPX2NR^tlDm#vx zh|Lwb?p%?rgE@LVsW?Z1njLC-&^bwq8`6(3s><-rgxy254|KHp_p$BVF)u8BeC;3Q zwsJPL10$FfbO%3XL@qJ91dhma?+IVw#xEtB~V~Q{_)EMg`{Ysd{&@t@x>n$702e6%U>4Zvs(=nvGxQslyt8= z4iej1C};CvOhawV`q7*2;tMTHrzIu9^w29!MQO1<(D0%WV`x&3%ZKn-rr~A(z!$0y z@#SmTbksN|lk7Ryk(yaI*fdky4vNoOoY&lVHuMU|b993{)08vQWN8OGGd9yiJNg`t z&oqU>a_yxt(~L_xIy*MgkN<~d zug1nLoN(DMG#2H08# zYHGOgV=`WRb{BVi{;=olKF|5~F=zMf(~Gs|ckYjup54cY(UY&GOj4ZP*W1}WADKA2 z=hGIQmY%cwXbTzRJ9ih(arWjM-!>+9M`i4_V09#+y0%SrG?g6Tm&<8 zyrI9Qjy{cN|FiHA!zSyU)U5MI^)@Yy;G^!EK+hr=!mMxu{%@xC-<7)H92sLbkz>ZE z@PsI}Y;S7WP9EU+=UmoVRCt`zFV?eU=*i_{&b>UH&7Y@HGa|pKh%0wdUel=Dfo-Wv z)>9Cv*bwah>(m7+QO|LA?=-f3_k#~P15ySS6@FcJH9A{${^|i|x%<>8<>jJ$lG4yz zB`(T4K7Mi2?BB=A^I3A8rQ^8+j%Qiiu)$40Q9E`grIuk2Jg&IZJNKlHVnFuL^`pnk zd|T?KX1&39Rt0l7wkTbC zIZskk`|qs2TivS*zfy5q8yLnc z({O@^Xw?}IeK0n5k2j3f$)>N3Zs~tB-$YJm9`t{Y}GC8i%Gd4GT05 z4Kxi)YaFUsl1GfUI&-gt4W+(e$u)N>t)%E4WZkdl2#ZxxQ7(@aV)CJw%o{sqal=fm z3rsiv4{z@RA6Hc_@J^CxJB6V$fuRJ5FzA3O1W7PrLIR{|W)ezTCTZS=QZZs}E0%Po zt&oPMJ1J~7187lE@Noo>C}=$vmA6S-I^`jjfK+{i}zLf$pKk&X7->H8tHmx(K^_SuEnTrfs$K0*TywK{dN)R`cPNCyD7_6cOyXV)T;z>dyN02niyM6y~Q-T5%$} zN~C7|b1CMup89V{76BWV`kbSp1Zjfw0J{rR>~}*ox)5PizY6Qw*pTY@K;bS$lw@-u zOA@l13!+}Ixo~1eZ7x*G=7J&#wQcsN{+cBT%R-e}t$0e21if9I!}o-`NW(#s%}(<# z2jhHBUgM%ee4hw+%kbq0*_S*&26XXbEaPldY@&#|N?rUdP&vW;^=2VQQj5lG-fYb_<3B(Frzx+{}OhW{veli}U+ zBejMfH^bW{YrfUC+j2v;cF;+y7RvOJ72Yn}miN&uu;r2AwjD88X{Tp)#29f`$6s3E z&fA$z4L=eCJWmydM@4@GY`x#CGu!^C+c(0y-BsZ~_T5THnxh@`#O|ddx{bW>bE*%4 zs_=8Zb>{)7R-3=7?djMl6lNA=3NrDh?e)7gxcaOx9ejH9OM(2j0u>m%U44k-e=wa43y_!fvGYP@dIB#2lkw8QtmQ%yqoSQ<>x+V zKJ_@jQ%r5qwuw$b<2XR3V*6ucv5YR-IwYu!bIE>8mUxg@GlzGso>JZ6>BnGNs)`A0 zXoKk|*`SYjqM!i({QUDqTRQ9_X`%s5H!xOWYamCVSH{tC*%9ujS~AXct^S-b5Xkfk zgPaeyvz~we_FZUoAP&E;6Iz$Chg@POK80e&pVSL7*Ysq>BY>2ijR@wESGRgS(=s;y zNk*@Rk^Trj9roe)Kq^3wGs^MLXr<*N=}*H8tEUb;Bf?*UI|?!PWLCN}EB%?3-ppAY z_R~~6R_YKVWd%H#y8Oe>$7a&n0{aFgNL#XsK9hRm8IgPOU~FH&OcXKa!F`cq^Wqms z|EAkd@mcrmY#J8Fh7gv#3*oxv7ShGbC%B;9#^4|WsCqkwJ7bX~b}X>dnhT|;n{Pu$ zi}TY@iaIPT#GR~FlarJwacwy`MN>w%^r(CvmiKgpi7$g2P{Sjnz9d+nyya>^u0j_n zflxtfNst#Yqu{tTyZXXF-*538@T{4(^tBM~1+R4~Rc!$iA!amh`AGEw&zes!eKceh zSn!hikRK{uV6}Q=?>XQ+i~X_4d6qoIrsH;y^>qt4?hN!coz+>($m`(4+eAm+mhwM6EB%@l!(lxu7wC(c7$1X98 zce1}FtaLU|sWfbi`|Q5^m5z-_&fcise50UQ^f9*JrQ*UsnNAVEG|b{bn)RFYXVMfK zhyJkixq6Amcij>}YwU@r%Tnr^2KD8NfFn>J$S{(oP+65XV9fdwZt49 z>h925ricy>g@*LQts(L9BIP=@qfZCt%K&Z)U89l<+hy$+3P?;<4z5qek4Z*kTF#T* zeJosTofN0O55>;zfcu|R&)0>nqXss>*#y^uqwJH*&iZ01!{lrK_9-<2XFnP6QT@7+ zQmQ3USeW(LB~-jqTT_NY-&b{Z8AZAgHuR89CvUUcD|F4;lVEIvUAzkP2KzF$xtFP> zJu3AH=7=?C|9c@@XL|2)Z1`Opns%tsY@Y0J;HQaB8fc|bR8IQZDjS#pl^AK*Y3!xK z9VAaLsD|7QdJ9FnoiD6wvsp^YNO18K_ zG@zqwQazYbvgtsy3>zq?GYj!+ES%34LJXKtbW3)wdO`uvwac^Se2AYY-~6+hsrh|~ za@IOYzB2g-JQn0d?eD&u$v7U{!S1flY{`Y$+NF<$3T9CK<(|ytZma!6RDQWPQ{HT! zPo;Bk;R3e%?}x@X{8k*hOAv_Ho@aidJYx+lL zO|e7bkJ^6Sl7kClt!2U>_p6up-_i{5U2SO=@oy_?LM%$7Ek&211=aq)+_O$Y5H+x||lkY4Pk6M|q^IoSnj#;g8BPh)2A{ z6Wn>pBf@iDVn%u=c*Ofk{*n3ytPOuZt*oEV%hD&yv=@BwM#|2Gr#s&6yoqDH_^RxH z+CL2C-SL4Q@M0$HgWLXlA9%@m1$*)z#seO|dUVNyyx);-dg|Z{g>_@z@5-~jSUl^+ z|DEnDpZB^DBT~8HABASFVgUXRFpT@WL;_uw+1xw8&@~u}aC!*P6IeGmN@crV&Z=aMpZ*kG- z^{w`wNa*Z_GzOK(B)}yQQ8+bWM4^iifn}_X(+yk>YDv_TpcNC!6AmTl$tuE*l`(ZS z)nHaQ1dI z&BYR@p=@=rStL&NwQg1;#U#3p-pd~#n^cyqew_@6zeX~@dLR)0xO`(NwS`X92>vPS zzF(wlQ9%&5%QFUz=2NROp!Ho}wMQv@(Y6-*-QTG`r@iJLZ8hsw3W64Ca^VUs0r5iQ zL5Wv0mTZNOPQ!WqKxPfvZwpj4$9kMTtNfAS5pkj_j0Dq1Tls3gZ<^4T`s+jBrMoYk zTWBIUJF}fNT_H@MQHHEz5TB-5m$|{zPg}@f4eoT2xkJ@_ibb^~WDRE1EaN#AbRrC= zXW~2`|84Fk#rv}YTrPBfUX`8*-MLl{apuBWf$<93#m>|v$y_)#B4dq@!|?@CLbuxO zN^+u@A!0^?7jFzFu14IcE#&#?-6ZfXoRN zfa!VhFKSqPl5YwY9JkK_ zW{Rt}^gUDcR_s`+iFUNuk7>?_ec&x^AK0Yq1MLgU%~oTXRa??nHM(Zu(ot_gT`AW6 zx{6DEXViN8nyk5Hv(33>R-I(%pMnaO{-o7cO2&k@ESqt*cg3@b`l+I+iR#>==+t(; zLZf=}G*K*^TfelfY^-`d@Ug3|9*Wk;PC}>B2el?LIa;(XDB+?dDqOTwg^Pw1Vf{L! zV6qsT>;Gl69U_BZhZ8-Dlft5hCqO`jDln0f#hS%QA!>h$tVY&Dq1`@(*JLCJ-es(r z(Eo{!93(LQPWJizM&;(L!i&eHWvDhFWc`qEO~?dPZbXF00Tt2HHC@i)Q$P3wRKZk+ zM+Q!|+C5lm?xm26h02eG(nr$NTrYo!O!1PoeFe1pn#Z!}6iA;syJ|H4hqkt*BiJ)n zmD-=*>OhqQGD^nyjPXE5OkwAn4W&yXZf~;~!CLII$SxeMp~RYMHn1_^H5)?7hBBFe zWJ3iVaU~ln<%e1(8>SsyYs@yivebTg3(aRWj$OP=+SkR=-&j-P@(ndJ$bI4I(Ckm~ zr19j(GfsxTnG>BF9Sumx? zYV(j)ZM*Kh;u#p)l2J;Eu_?wb-G^5lo7SbG&3HiNskGSMF7=Js_R8Q815CqN2XV%VQ&X#5Z z=CV*xcJCaYk0NX%vhiCaYOGm*Zj;%1Zq#~6YRQ)E?xHkSR9(FO+*Y!5F8zjN30Ujp z8bz~yW{;7LZNus%wm^^7TsuALN!EJv-XI9Pxy+3Ex6p|C_G5`pj7=Y$TPqLgX@i&4 zW-{+(?r_JZ7;{V9TZ~Ie-p<+$`yKv2q)b`NL#uU@GH+Seg>D8ku#Yw3*4D)9t@7Fw zs<*C@&Y6#dAO9lpf}<8B|7v`zpGeI)~g#M*{`aomgCJ#l6-V zKfv8d99R!n+f=oG)vx*vgC90l^@loF{lr+^U%~n2)ZdBVId_CC>*?LvOMRD(yy<&+1b8sINfpxRn^hkYV%)?2b3O7~ zYpCfo%s$5xYtRz?b1CrZ!Od*ybH)!alZ=C=xz~K_v~b|~Hfd%j&1{fnPIp>C3V9j& z)oD4cJnEZ;;|aOgghS*+RL!>Wp-Uq;<5=#;VFL0m{K04;c1sbf@?HI*%Pyt&m#ux= zdnt>A1rKwX=LG;^*Rs&EKlg_g@p~Dwxrlk}O;<}5|6!rTH)U*KeG@P+ySRL3OBCMT z^BYA>1lQBh=kXOrzM;8ZiyhB)@gHRlJIY6J_ieV~0c%asX{_+^tZ+dr)Ob`s0Hn*x zHf6mzq=D&f1!?933et4DZoY{IYt3b0`o&b9dC_^u^&>Yfc$m8dvUc5HKcqV8>>g-{ zHk0N1$Z$S1F|%nkx$Uo#%d$#&v^JFRmk3@&l|rjx-SEEH^r~}X-l_q2Y+7yA_WpGF zzVUKhx^{D~2nD(FBdcX!2ZcAc!6l3%sWLWWOg@7hqz~6`Q)KrW{3$s$n{ZE|>P_~Ix{&df>#CXkQ`li$HKB=<(*$ZjmFtm(|9CG~)=c;sNdKI^ z*V?zdm&q`%6TZzz-^6q+evW?Fr_qR*JM^o_2FH@pi6m1mF>1I-@{yVVssk1lym{#@qyk?Bdi7MLNU zXoL|XGuR$}J7`OV`DR)?=2WbsNbtwW%C0Q3*GaR4v0lM%`OcIaHZHY6C5w#RZ?iz2 zwk2+7=Gw_a|6~HtCX_lJOHIQusk>oi2T!5~d+}Gpd}7K)sq3_bBjI^(^>M?-1WlLuFLsAwgu!41;ZnC#P_I@*Kp(A>jI*DBVPXq{4P?mZ1F z2@`2<8-4FYNWRzV^IBK<^9ZRg#BtoJ2^m*;GQMGh{u&PouXBCn3ahOV#(x;z-bT&M z3~f%;Q~hsLJrzGLF(=OqH@39Mj(;XzZ;#$bd*s^cPgHz|qOO8?J;PcOuzw_9Hk(rk zHZY&PQ~sl$eu7%^lXZH>I0j4Zhb8xU%`31vYkqGT$8BbI{d{r5oN5`Bhi5N6ujIXk z{#w%*4-ySb8Fn%kOP!M5GA~Y?A@OzMoB_5>DqQ|Nmv8u6GD50A;9AEc;;HWpm+u=R zzF!?k^q-fhR)deNlvVkqEV}*Dy_f3q$){Hp!!5} z@AT(l#|~#!AFV>bRl)D+Ec8;;FQ?{fNlbr?J&R$Pa3m?jzXKy#Is;i3`9%Cw}dP-f@qy4`TayrQh!JGkEHKse{09TpS+(<;k4A!sAnhiw!9EgZiJ=6bM)id#r zCL(!cFT(}CxmiI)U%h?jJ+#KwkV)>X{?V8772L5}1P`*D4`Vx1g!kIm^kmp02%j!6;~wk&k>4=zX%Lrs=U)v?)?~^v(IPV} zLp0vYk6jjxwk3>jznjSj%?`-FK_n^aQ#73EZOSZn+i(4v?qF0>#OZ8ye&VuqTa)Q? z+kfVhjD?YtRGf7CWd_aCko~ZHnFhKZMS|L#KN8X}y~^6sL|t(fPhfNKmC}y`R#N^m z)&rq>UaY%9jr=gZWt=V`Ul9FjNhzqSx8Uc{!7JRE_>oEZB-OUq^J$jRMUVz1WA){Y z{acf>z4F}=8QMDRSvr&Xp82{`5r$M=;~tIQA2;8c{svCx6lrP6+#PDA9+`$Wf5kMQ zbWnZj20rc|3N`VG|AMg|A(^x(3G9m5Ny+=qSrTXtOJoB%@5%%+hC zSghTAv+j@aXc^1-t86Q&?=dCjs8xo0Pt|L_VVSF>psTEqQ}9O=>=GXLT^{I9`K!i2 zy(?apo}G|8mZ(pVwi2W*W(}6(!r``8NEwB(mG&2OF+5ls`+qCduB^8g=(K&2y1dv7 zmFBk3(`jH>5QZ;fO_h?w$t=F>y1*7dl5Vp+W1o-Uv%>koSFWQU4m{o^=YuJsN|3CvCgAejfbR>po*8TW zF5lHjp37y%rhW%L-m@*{@Zoe{zZ4QU3*o! zuKAwthTXjE3zxWkLwk5qLPFea|8bL02fx`SGByw@Gjtj|<@B)Cd=zcBgAgt&?zh$i z*rENFAe?wuuKj-?Z8d5A$ssjI3`J5HdB8Azb)1(}pW$|a zc6-S$6wahSMl*{9WQu6B81sR5mA`K)qxY=xP`mVG5q{nM((~$hjlS!ufOrLnL$ar4 zwH4#^_*P!*IBE)e8^z(ct>{{JzIAu#c7}9~yL@(aQ)p5DUs8BIx{SB-;}z1P`K0)( zZ!3EwT}&~<&esJmwJ7<3DtW2tzHoNc-}`r`q#olfcl?Zr1&>n&-MZj-In#ZE6#Gkh zx$%}KUOBPM$E-H5vBzV!dGY#71pK%fwz!=JP51B2*5A3M@GcCTs*@7S$Nq&L3uC3p zSvzR4AY9bk!oEc4e=?bZxJVus#&-ili)NuL=IQLIf6{Mp*jfzj70egNd-LZGefC{L z$cc-!>&+Wd;}v2hJ{)Edz7vfV@0}a9Hm!}q4XSu(&t#4gtWXbQb$4R)|RM7 zYNeDOgw#K9(0BF8PA^Ndy^J4~J|`=Qx$UEMG9q9vgsS#!B#Do!7i?l+^DK?ngZhWj ztxbD`7Q`P+?{diE;8r1+il2;I?V(@HZ2;UWVp)oZlwNitXDexLbaJ{GTM!I1WZb8? z@4)ejuD}k5Hmd2adUn;P`%bLdwxT?JEMcxLkvhJZ{nS^y%3ZZhJn6@)oThbIWH_}k zAu^l@if$z^9FC`#CTx zSdfLg&~kf1o#HMip={tdSb%A02QM(5RGKBBswal`edQ=~F9$@up$_XKNm<3sN97$O&@Kvg$(kE4=wF#q@_DRL^{X90Kr(&OI77S~We`G=a zwZHmPwd9V83-b|uMYyDpFladyunR^;W{F#%+d=!{2_>L>38Pmv)8$HRSf-vj^C^#V zMd49arbmkJrK4Ke=WoQi&|8IT{$Wr@vfdI?#TIg4P5B71MI_zXue*L*DZlA6eI;VZ zq@pf2XR$o-2BA1p@esJw*ZSn8zP86N^|e1r{Y+K2>?Ai0yK1F{?R`nyFa1kqG`B@F zEy658uO5sjf|pPld&6CtzPuVc%DgMImNhW8E>j}QSJW5vjD5zjq2it>pqzRf@m=6s zOl>c8x)tEwLt2hLWXpZ-=G0^a5JeUfZ`%Yv+AgSMFB=+i<+}I z$(dhxew8WAYVcQWHn;d9f3Q{sP^x?z-?XN6fT75J?d%4B>Q1I<8os<0D9UA4BMaat z#*RlO4tU0uZda_#xU$FX}u*3N6Uilv&}nQ$JzTWKLFD6jwHfN_cU= z)B8Mg0txT1%(gYhE0Y41TIJjLL}D=G1at6;m>6^x4G2gbTIAL{>xhUEffjBabc5K z10l|eFc;ty-fo}8XY8fEjS;oJ5p=$yh4?$uQI$~)5(jkF!^xUAfMp^8fmOt+OQ10E z)juP@8=F0*AJe}Aw^|K#jvMoY)n(y4W;B2x?Fo`s!R{|G-{smcfzgC-#~tV**Y|4)U=cF8=iJSS2x%Gtq5A{s6I*Ex2LXWAn7JBTr z*558HL7Z-6v%ow6Jd~YQ=gu1uNPrSm;{N>?`Xhz3ei{&_wNHXPWivpfM+1&=fNBHz z{OR%IhSA+CKH}ZW{A_D?d;box#%Z>@{_pUVt}U88cm7MI2xr(z$^rGpW=a?Q)OmCB zh94B=;H`3`R_9avdqbtjfw;SQ`-gff=FR5qpT;(KH4=|+-NnEOiT&y%-K5Yc*%bT< zNimg#ExWMkCvM~6d}GwZxovLWx&TFFOsB1Oj|&J{+M-d1;; zn#bLglX%!y(^tr6OJ*h^>q$m6Gw$nYU5!UgQPcG@K3H|<4*ucH++D9Gt?7E1B}vDp zi!_c4~%Xb8{xUtwm3BSot&D_&7*5uBHa^S{vFGf1Ej^9eUI^@{tJ)L)=L^p{h7!1$%$sZw%f7A}0BPP` zucc(^L87AaOuB-I7`9Hl_h1)2G2#BbG+z+mbX+#7yWC%;^$}JZTr6}+^)D1~PzkYs z?!F{0D`knmZ>4~_gCg{iY+Z5y9!f8--EOhD;s{dczCm@+FCIUS(Z#wi?(@tX$KVdQXUWDJL(N(Oky)ltCNHuAuObR!s|hG?f&mW)U|d(UGz#t zf-E6bMS@_)%!nyg;%(e!bdiHw*>j|ex_=Cd=fcv%QWlJd@^2PVb zBP~kHX4GSe*~4uemU&TRQ>QQP3A44~I2spI!%0{XVrPm|empFg=(oQE5@A(Em3ep_ z!u6>-_3E+Izpp1%4qPGcq5I7eb~|;?s4SCp}F+c+G8{-E^$R%5N9tkjlF*rp^GZ z2gXn1MGRA?;3p#WQTxe%h$D81Ox!jejcp>$2Sr{>{fc)5U+@YQ5@NOYR&{$~VJpA6 zc>xWO&2PM&KfpN$iv+Prm4HAygRQu;o97ZvJLegC8=^7BPYHZpQ-6{S2l+OMuV5>_ zO(3gm_I{Nb69YIN5n%Xx=0qABSj4GYz44jsaP@PRNP9h)7J`fit;V8sHoeJ z?vV7^n#-20Vmh)zuencZw$N;uo!}ThEv0=ixJeaI3~wkZoH^vrw1{;`$YzKL$G5(% z{6~$MbadrmzyhSRdn65wk>k<2M*&e$Ekn|yW$I8(akwA1gs>KgfqHYmoiOIYu9Y;4 zK_g$y{6GIGGcPYpz0C_w_4yt4pLk-8#CVP+S7^KarSGyXN%GLIG)aD!oViY<=479%DZpN%FhaSVD1h@lOk<-VEDS6UI=_wy2k5bs{Ip!=&4OCrJ6IA8M z0o*G(AoN%WbJWX1`Vw`mR!b|N-JnJ0#d5T5W+uC(BbW#jo$kjWfK8kOoC3L(ep^Gv z$^58uK@=zn}2$S^iH=MU*ScpkdVq?G%b>ndM;)^4P)QsUb|u{Z6N~~TZ8FO@|lc9d`$ltFDCG= zwhY0=c$lf>-2NRn%i!A-4V@M(PEWPIDw8ih0uouzU}gOf-~^qPWaNKnY-wgWKAMFf z#MV;Bu24Bf8MwA0IJ(;twfBFU&OhuJ{Yu0T#)3u=db=VeT6hvArqlX4V5f2^jm8i8 zLSBND(6=Y^HUlrE>hM92?EAm|=1-J|gf+E!No^YzLa$T5IGR+`)&4?jD!X5}jq))h zGdI0qFte84#~&MhRY{d?_C@rJPYy)k8>#?-b&eWaYajv_rJlm#FgYGfdQZo(x-p0T z`3!{HMkFqyNJJ*S_$98dOU_0rY!B&UDn=7V5HA6Ba#r_9!YE^`81h_w0+|A_R8<(j z!?LA>4oS5o*$_rfPB?T?0TN1ip_*R%IpBekS)38ae8U5zE1WqV2@MFZ+8mqR+}vV} zR7UM{H!#^?%&F>=hfk-dPjJVN@d=h1!M=i9DHQogm6wx3F=@ajB!ELO)_5iBG9oG! zCSJv{`4bRVregWSfL3{JIo%it6h1e&M_~!KD@^rWJ;G{myTS$h2$uLxbbW!xEBPmE z5aGhLiGL6BkAQsk%n*I{`My3vogTXm=9s>M8IYIrdFk$IkJ`oHNxC;Tr`O&~>*=WE zgxZK;(4L;@ytSt}?Zqn+i0t2sD^W=L`-vIc`XzuaXmf(os(G~_1lc?6SFja~$24kg z!Qhm4CSgNBsynrvJX(4ZI>KrUPPs{VV3(4tI|qNyoU(Ye;z8q1i5tOq4YFhDPOU$v zhApa0wuTK(4N8Jcbxx>3_CejLdeUT>vQFg?cma#=XR85n&l*c5?2|e|y zH4LcN93gyT!krEY;~NMHn#;E1zd6oI1p=*hAluImMpga@^z*H$rWP!QJu0wrHJg37J5LQhq8I{+U?8slYfXs)y@+YYpLgW zMv1$`0>Dw9YmD7M))1~7zs>&$ zev?o+M8{Lv8xRq)kR$y``6F=`PuFp{6@EvL3E37~yQ51jcl0{vmvn=x&@B%GIlnS} zICvt0f!jL2BJ1T#+p^; zdU%$$+oenZ9;xPz(;|+J%?VXtMWhU*Fm7kHP`F15BE_7x*R;8S2sC)QV&CtJgHn;- z`ZPs-B`<&1ZVjy zPYPF!QO=7l&Kn`D>c=@yn>|LHLtMb|usDTJ3@^bxYt=7*OiL-dh-U%{KN(LnU&a6YW)v z4(ufEtSMCD+t^&p(Vc`Fg(+TtHM7%Izn;H-@6|G`>-8x7O;^KsyPDSXSLTs-q(0vH zEAPtqxI^5^)GM)X?=R=^Xtp3(zSW=QAM(ZdMaEHBU zMt{o?W7ebp2YQ(^`n^;Tn>6|k#a8Bed>iMga}}=$sPoQ~bumGVL)bVy2t`_o$uVWy z32oL9gFtK4PQ4_ys7ez->`y$JSOL~w4Iv(r#_l2iLGsbhpw$P}eEr0lZy2w6NNOId z@f7OUQk>i;=|;i#1EB$HL9sapS}|~;HD}!NUNu%JyQOTY78641XSh-OM824Fiu9hV zd1b)5fbSQ`%;;&EqoxH0)+M)40(ioby+YXT6Ut{k^R4~w-6vHU2YkK@cNl*;0N3#@ zOnrf+$?n_Gx1e!l%oSUbSlSA^YqOu%2a#3JDehaW5^g8K+?KeKV-GVciaYE}{*uXX z+Cp@J{aqLs`m0Kfk$Q0_k8SpUQDD5U)|JjGj&--aJu#;>nwW!@RHSM^=g|f*r&r@@ z4}{ilyu);Iq@+I%an<&<^B93~RSaLCbCDGi7fa6<*e6N59b!U(9$zQdVEZ<9coG#~ z7wAzrcKumEgDrRD346p62`;Af5P#|)Iup#d$o?PlExd>WvPwiM+|s2IZzWMA%FHTJ zWCC{gi@Io%vuASx3huy zD}_dhMW9D`L_2D3bHxhBu=vU+&uz*O@aLK}^LxdwdZ{Ql&6C4`WY_Qns> zZ)&+Su7M-Xht~2Txn7AHV_D=)Wf6s^l+|awea@4nU2~?TYJn3Q;k`ZTlRH6Yt!h(laF^Da$zf0yRO859+h&SjSY{c(BSFbR! zhny?cUYQdAhN3Kx;jzD3sc>bn;EX->8d^}>GVe%Rd!a#COj1uVNjIiaBsq6cmgu<= znVayB%|z&8^Fpowb;64hHD%VOI|!qki4MON!8(>FW`vU5WZ_1t{~)0NXDmOMffsmugMN`2$Rl{m-)$yn<6^~joF&>pOq9q=+)y0Fvqqb)Lo{dMXka*OJ|64q&48{Cy z`sni$g97_bNNUPA^gq01Lr<44XHneN^5xd*7>AxTt$MO=Dy{^Ls~%y*a%Hg9A~uIk zSWHh9WzVE6fv3J-ZD-yZ$iJr$PF1~qS@IccyhJX+jn5aFJdu0r_|WGq)^2NwsMn{)hM5lOO&fN=o=+1UQ`#v#?8L&bs46~3@( zIk|0=n%>RGhvLo@RyD3c=`V?1i^kM>{XetY|4L?)`3;cJ8d>u$=LliTT+@ zrN-70In_4DHpYtDF1n&)QU|V`6qDL>sF+kOvuCXu%UBnqo`OycY7LKiIH#pQMR=jm zB8a~*;U4=OHi6(0Iu11eHQrYrIxa{6urXdt;!xR=5{Qi2y%0CqAIi-zBmIK;+ThD# z3S*TP4}s10OY0Z|8*%L&w)LuNV>n}NkV@mz)vno+y!4L-pAcSm(rf0bR;j=V2rbe9 zq1&t@B_4DKvQ3oxccvc3uNZPm)j#4v8#)aYbLGz(1Kfmx&I9Y|Xwi5WXi+u{w8y@X zPBGd6Oo* zQt$(oy|7_fvSG2x*n~k86@{i%V5uC$aG1GcJgRimY*<7L=>dBiyFocKn^^@*2$)x| zWkwffHq|nFwlyeO-Z5!^!vX#R;25X~BmISntlLHy={6lkn!RwgixQ?Gc{8dU`7QOz z{j&JD6h?N2)eU96dQJ9gAd8 z#8DX^037Ukq3vfW0}A>+s^}wf+p41ztE$I4aLZL{TmWmE{qEo7+)muRW#cKlEM14} za@Js`keQ8tBoR4a99X@QIKrK%D;nbiDIQ(M%Lm5myMX#cV(0qjgvcCv>#U-iErsxJT&V)b!7C z`*M^DbA+;tcMmG)?6Q<9GmPvx0ZmSnY7{%ixnohPO*%@ow5l>*($S%U-g@kT0TuMt zBw?z3JX?{b$ziGwiP)ngcXR(}=0p)=VpEnM)jx&EG>8BGW{sjp4*&ItW19I(vD}*viq+n# z2pI#OOnjx)7A4A)km{!K0NOVQpgoQNS~DEYhEzA%mrTAV7o0OJi3|Q1bl9xSy`NsT5*1w6l&OpA@gWY1O}MP!a%Y^&$#hU)@P1aCA-dwnQ&-u(+Ou-Qg1KGH zqb6X}9Y})KRA~HoM6e3tg#b%P@Wq{k0vrDbvq(l|*!( zO#la>z-jL3J*!itUk1UR{HN;NbeA?2u;Akdj2Q}1LOC63s zS1>s|ws$m~-8*8yO+@L$du%T?7+Z^czruNupu-;f?JF2FZPFIIJA@hL;{~Oe(k#dG zxFFp0KJmy8QJBSyRL7(JJ1t|zx-8*>er3eTQw=4ntng0UtqjMV8lq(Gdu-BsFYkJ! zStkx`nVC%`gRqK5$$S4xYiZ2b&0igt`(vg*CoWea{&V7TRY9K=)S*pmq^hvorio#> zP^sLo+#dUnUxCI#mNFHU_70vYMp$mt3CnG=AJA$0BJFvxV}wet?6L2VGV<^}{gEcI z?BVVU-4;5Tc~M2^UMQGrNXM8F`lG(3mQqGx-{33#wG5VF5HWsN=vH2=q0r6zkiCa3 zpye!CjkW3HYe|8HlUjxWzJmPnUYc@a!V>_*CI=GGaoLlpg0;0TcGbFS6|MjPZ>K@R;frt2$Ml+!v5|H4CkiM9dz(CflY27c= zZ%xxN$zlLt85XfHy#V*J$#J*f^)kLKXPIKs35J*rzU@`Pw@n`^_*PMmStyC`6MP$` zoz}7MSZohxmmr=Ez7PofdA2#^5ml^2=`E~FuL}<~z@Tc-( zrPe&=nh4Tw?O(GZ0bWav~QwT>;5x&w5+pNJ2Wdj%dst6?ypuaUKT%AdZ+XEN`6CfTJEy+ z(GZOOr^dfV_$q3%HmT<5+s6G?BLnH{5g<4Yb>BF$Vl9@B;dIQ}@u(MBt~= z-+~SAt)$K~s|S|Fyr@7IJan~h7}qUVqiO6z&}*J$%gH2(IyVRM_^5?Cbhd10Vv&7d zg-T9FI>BhyzDP@+avJTh|I7!oCS*UWGjKM@%H4VKUGY7n zCL*z@{X;2fbC@4B;@M75+cM0-VU&UmS1$hgKVD{cC#Wt>S~ zts7)#4oN;I)j8{GTpl-GS?utTSZFoIgGK`3rs>+W)nG2Pk>iiTfU+1Z@M1Rp`ju=W zQr*r}h%AZKF*@5}Kh~!vizVHVzD(&+5HqSpuPZ~XboVv8HQdO?mLCr>{m#UHuLIEL@)W=T1ExiBIpvXEs`L@)`6>~cqG z*LdGzI7wu>8)|2gW;*UX-YVqmW!+8f_QXIx-7b;MBH{q zYNXDjo~?R`A07)BraV4Db_w;%JrAW!E3zut@w%Wf*_(zNkgFZ#m6MFeTH;l1U6A@^ zM|vS(Lff=2!)fMU7fYn#eP;mw%Y`&)7#XuPsQZruKX({g7m3Q|Xb?RP@GT21w~WhH z!Sgs-4ITGV9v!Pz>UhA5Ej>Izh-6(#?bM@TQ!WXDe8=R%wVWb>lntiBly7`XS+gd5 zoRNBrk1X}#?}N?u@zj^Sy84KmO45s)RR%dBZU2`U5rV$iAZ)wRY*=i5d6~6o34IhK zevZ$$TNNr`n5gad%6BviYEcL@!!U%pIw0SE2>35-5LcL(+0e+(YO|qfF(mIQjDb7d7z5kv zt0-bNET@#P5&;Qy@?4w!kAE?LqEk&_355g^C?_)JT-dfsCzQG5L%a52V-_ zlQ}lNwbYqwdMyW1Zl+uwVffo61fS_(M0gfEU8|sL`uHZ<4)&{m&ZlGL6aFnoo~6hy zH_AvlWLsL!Z-T>o!dTr(pnh&AX2{tYD2z@G&n|Pu9sgYVnA{H!jeUS?jE_MbQjxls zaVVLb2C3=$`bSOV?zhJ#@@8_;tF_Xr(S6b@JwuEx+>;EqGS6Ja*2!Pj;O(KC$R>tJ zO2{L|6Uc_h-0pP9Skvi>mqr%{r6I(GZ10cNove*lq-U`4RNVK>D%p4{TKNRkOTD>W z&vI3!Chf!X=kUR8QlIgxE4^iKtH9MboEfiVed)OgI1!G~jpC^Nlcj2G`@)~+RN$x( zFvOhc_6D6SI#@M{*cN)}I_}$??_+yl$dnGzDrPy<1JRmX`?luAr|%a#GvCIJwN1&U z8r1o)T7%o2xA*-6ZUkjtxE(hfeF>3oXq3_r`*}fM$qq>Rlkk{?@i_2=dwXJo3uNb9 z<`vwY*)^{yJp&7~KZXzVIuO@%A| z6>GyaeRM59=Wu1TmY&m4k*~$o>ZzB@GqT%fZBp50aJ-+a$)Zd%t5NKnr2Nbmp*!J5 zmIr(}`32OeF_aGX^aV0vsq?&0EJtfb*)IcjI7%61(+qP~;bH;nlRidWag|)8nk<9k$Qj~>MXX~)T=h)Q&l6l9MA_Q%K;hAZna}6 z5uM{b!~;7hy}o}lkCyc)i?7dypP+NpIy#5SzZdds8Ip67&Y`~U(sd=NKg0@+N$Q-ZM-O6>rtIYRpsoyi#J*Ft-{een#IJB{~jBi)E+a!RjhNMT)@S+NfB zOzKs|pCg_-ne{aiuX^kp_)}b}ehGI0As3DA@pHxt4#G$Hek~TD$TG8id3r`Nvg{*_ zGZ|UFogX1AiZ!>{L)2+1ml!&_uRg>@i|7o(D3R%jgaD%KaR@I5x@Q9LnFS-3~GZz<%Z`j_7@h^+XE$ zSFdzMumyvO2x797jv#h4Lgk?Zu~B&H`v_vWJB`Gi@t;6*(C&oin-8-lM_C&d3nh@) z%Owi_(=CagiyS}#=fBY%Yxp`|&0!gcHLp~z$X-*Xd=|0!5s{5QQ%g`*@*K(RR5zxJ zI0gl69C7pzF-b&0oFEqSPUvqB@8Y&rl2|V-^Tg)(>z`9opL$%HT`!GS*=Eu==UrL2 zAg6Kn3dX4qvksW*SFMz9Sk0dF{@aJUT-*)_7ZueDRyRLJ7Gq$wD>l!HEXx~#TaU;E z2@S?>KiU)(HXP&4%9jZ`BdhTF+Wa6Pg8guQxu|vXO7n6S;8lcZD5B( zEglj?QDV($sUL$nZEZ+0l`=>D_FYt|<|KZE@a}C3vA%X;CIQ6HR)X?);ZssW03fZf z509G$!tjnhgjyOc;rUS!i^nquQXOPw0LyJ3%7IuaV%^5-ex*!~XPc2K%hp-q)ahGS zP9N1NX7sR3^>Qk`RE!=0m$|g}eO7u6@cV|{WKP%WZXmi#Pkiexqf+r*kkYz_t(3^E z;qLJo#7>>t^=YYFsWUevFLf;`iH>^atxO+Z9izO37))=>hFa^+&CIdML9;!ii|)Ce zcZbMb|9N^`PkNTfoeAEw*=I@$Wbqr*D9P_frAB84K@A#2wngMf74c{qQj4xuE2GkY zs%Ot4wSvh=zZ9WQ*YI<40qoX{^g{d|2nq9b#k`khAB6EcCL2YJv|qn1dHUzXYS@@T(mM_Ik+q#3#(*I{e;h`b~5bzSabr?);UP4!gt&4 zlUeBxg|vq`17`XU(LPeH;22jnsK8Pb@U-wP%^^Q?G0U3NJo2KcLZkh|oXYtdu{?|U zS(Wwfst@7YtaV_)68EmxUf|>@eH_&bMM-r$)d*)+edN{B+N^Cx+;HU}k`fdSw@ga; ztDM8K0IQGt2AtC(63Y|`$dzaPY0i79C8UKr2Z>^s<=faE6axcGAh{NfzR(^>$`z}E z&)wp%7ouO@JHm;Y8=N?f(kdERBR4H=lv_re?JmdSTM1QeQly>uT8Y9awc;fSubeZ< zA%l6pQ|tG9`dm0InnicrJ0ec=;9ZAlN>1(BQEM;*ZnlQ%`~Sej?_q$XTT}^|TUSI! zm{iA0*BmaJ^uCg(Q0;!}kU^&p*(gK_cZ+jKMXBiyC2DjR{%*b{5K`X_ZD6mc>`mWA zUjJSle0>o|QztpUS@;{wYoZWt2>s2G2-|r=$Y!NG#UDl5D7? zpG-_U7RJK%N~;~4<|Jbf3EP<+?0u(7K#?*oGLH*EAug)@j9SJKrY;f#;;9~d8E+9f z!U*PLk^aisq?9{O9Dm!5m_MTHeGM}@@z*jFzTjvkc(y)YFlZx zZGDqe41`gwa0&Nl>}Wc=!!eiIU5>dFw>;gmbP-Lq$9yFiGP|ss^>+^X(Q*cTPJbku z737jTIvIq~o2{o96k-odPPNG0?ZBSOem~dFlKGbW9yQYYuf-hUoSk?r`^Y3tP~Z{oX-eD}h+u92Uh~D1a4O#ZC2_)~WnmoHwb=tVw9wXudY` zv~7c?k>7t9Wqu2g^IVwEGa)r}r?AbQB66kF(seQWw(7jd!L8DF zZCw#RB8hoH3E4MiUS52)tk`t>!tSha@rCxxiY7)@jB59oFg|X)@TX5P-(!&(582!8 zlT>azrK0vs9#s}}=731Wa-dORGdqnd*biA?k1!}IsCrLbjNWd)+@;AS5N8*%ycvNRbf19e{Hq}^DjK^&< ztYrT5$uM1jm%l`>XbXIoaXlF|=~h%B9NQH}=g?}HLluHSL`FU6yFwMbfcv!v{n)R! z%glT7>*A+@2*aLUO|k6iLQ2GCk0KkBjQ@K;*A4l03qN z^ePHMz-ibXlBNV{@j4k;DVP{<Lv41tv;cGV#T_^{#r(8rtwh z)mkS?B-Tk0`Q*EI7=X?dE0FH~-N@;34yXiU-Yy$E|dmThaxv2{G<+iCWe6f&Z;~JLax> zES_Kfy!o7>LmGaZ*bLq*u%$@TSViBgrQUvX!ZOY1t3H9B|6)C%7mcoCK?@uh=rG-Ja;WKJUzEPRSo zc5FE7xCXTRq#||00t^PIvm~#D#g}%&rf4+5(g5Z?zLGyO^~3+Qx1(R-Of4u(n;$WF2uXelc$!bH9>UJzN#JOGroCy6U~QN4T8~0 z-~6`{t-r*fBF((`LFvsar?3rTDuk{&FtDfU@4lg5QCZc$d_zCuclZ(C(0}vW-kcp4 zeP=EC%Z+*b?ZtK`XW!=X3-G5_%aUr8U1IG_80`QJ6GoT0Y%j>)-jKgN-q5AwWd;y4H;)o5{+NiO9IK9o{RgDcT_|%sm_2(A z^9^rj@>M6DC10jc@}9iI-TArtj{WVZqV(vquC;I>OI}A!qZax-`kJV`mkz$?&i8#C zk0lPup1#@ltIeiS)8TRr?Tm3(mu2+xJmu$15z!<;pe|}RP)E8-f)lw~j<1ixu*OdLSufhyYd5kagQe24akKn0~ z(L_Ce;dw%DjqkJrr8sXqcA->&PtKtiv@Z)6CGP{Eu_ZO1F#tEl(ttj7jD|FKw{gey-Nu%G9*YA|?tlI7ynnz2@ z!0G$J{>{)3c-h$`8BgTNt1I6(q+t39&po`Y_5I)+^|O8^KT?bom2Yh}U%vUz{m-8C zu4${~b1|x{qKb+QQ3(=@$OX6D7A`S*>VjLZTyTpE*=)bu!akdjfK7-XlP|lK$o1@* z7|^j~Z4F)EoVi-D=B}>P)?8!pypIDgV(e=lp}GiYh|b7{o$Svea?FrP;c9axg#*Hv4y+?ghdPP2 z4t+J^^$o4nSvm4f~QnotIF+6Xcf36P#Nn zq5V)XW!vF)$zpDq^z(ZHIcTibi5g&rD(5jn2*j1ve3@Jw+aFQW4fy2_04-R-j1mYF)!VN(QIQB`>;l}s4FruBYFE+mE2rm0m(+{BdMjBIa%mvv4Z{e7QTrF9 zbaf_-pFv@8Wa)rBa3HAhU0wlU^2klX{Y;3+csZ9&YiULlB5prFi9Pd&08{DDbo*ke zRK4*Hi=CO9&4LJ=_^so$Yd^h!&N3A;M39Avf}m@`3>HK?u9QUZL#;gcHE-u#n;tyz zk6G$I+lb-6!iu-2V@7JKBei{){xuFo0@osCt9yCpB2ZxxX`yqQ_`knC2*HS)=2?{43^J7jHfyA!-=XigtA?e zpSqaL#<-{IzoRZj8zlZ9T9R`hl|LD&lFS;62MWr%`Ls6dLW+zwrd?u#4^UHK>Z zG+miSy)-P_J-bPSt^}YfjxSC>3|=`5LQcGVg(;&(rEcVjlOWBMLc2>e()a_Ql79xc z&g_|^1@p_S_6nszb-5uAoe1Dvvbu6QzglV-;h5|XBp~7mkuL<9ilHr7E==!@Qnl&5 znlKZ;#>0A>2C%+YR!3@t2q^FeExqJy0wcY?b?j$l@NH_0oy1sVGO&xq;gvGIGelw0 z&LXqOmKDxFG^aNnLFSl@kvXLeJopd#p1LiP<{j1Utx6|=6WE}sUUzFY_E z{RD{sKeB#^q*ao1yd=S-M^_e-bekl3a z7zaETa<1IJX@;`8nN{!8OrO(CafnvjOjBV)rDco{bR5x?sm{@@9G{;hHTId5AH%e% zO^3v^)_3mp2}C=X<{!hff7Uz3wzcO8!kyWu5bm5T!hMwYafIV^gF_*l)1&O3|1zv- z2xt2u#|lrUh{ipOmK@ysF^S+_5x7@@A3}jhAEL>WXUehq`)_%Und0}(wYBQ^Ht%dK ziJt)o6bj0jT#+*E;1;+Kvp+U=-x)Jj`UK}Hd}nN3IfXeS#~CwLh{-|mV|jgeh5o?T zzKLpOe?zu_|oMEEDEY5q43>)ZIA*+PKxh zvk%q@u@ufWpApQH5vcC7in;ZU!>)G9&|D%1Qj9h3{FS(%&K>K@)O1jKRAzTe6Pkm> zkFhqW9LjZMoPk{U&b`vfF|h7f5^RgwCsDAwi<3&>(+H%y1(@Qbn+hBsCK9ERO}ES*gJWSVZ5oap*|bsw3QSB)LzasGTOJ&EXfyR~nNK83eIQHvPvgf3q96l*txT&ti-%L-$r)P{H;eJ7Wz-&jD2<{10hUYNN@hboSQbVRsi$v z0ukm|x~3vN7IJ={+;YTaM<^d2>lia2%;;(Ia1%=5iC_^N#akV5S}sD7utSd)^o~@Rp%?grG2>u8(pcK?A#g>@ zeT_=)TV3Jc$tjadPQsH4yQnq?PY&nh{WM6C=r=r8t;D*%dS0yGsh81J93-hU$jyDH z!;)5&$g60OPw~C)I3AwmAu9=bMH0lYtt7$6A`_4+^*=1WsESB}xe6o|&P8dQ?~Pr> z^2}$=xQA>$bnE@azR~|o>dWej6}IO*jkJ!8=je-Sauwc=cX=FdwZ1r6x_ju{G2FE7 z{2D0r@1?r{-h4n2(%m41yDm-``ylJ!=i)50d6{?6giIeG-HDBz-9b5hZr_}bfSGqN zuNpIFQ7_{V>23pw%^WEQhY^YBzl2394zmxf?jmj>Q=CM2dEgh;91-FQ=ExfcKrpFm zj%aU4>4l=6T>w^fVU+AdmMirBVs`ZfMOnr{(OPp(W%V2p&*rF!jDQ=(RonV*sd@`u zvVwie7W*a5`rsvRficDTDWdqqkiF%@V~cSPJn`T1lnRE8rKM8w6GwnI-kUS2m8ZL3q^nm{5%gCsMD23D=Sc zCWx#hnD9+us$-bY$~Arp6XYTX!GsEh31z$qAi!TW&rDBu5FkC6g?^7#7}10La2&{<|1jB&IOEYiFwTLf z>5$py&%*T|k>xySM6SpZqD#{!SSfR|j4z$sl#rvjyq|^Bc(Qg9u2AqG$*qhjb$zkX zzj_Lp$Jo)$lZy(PO@J0;vWg{@_PVMuh!MG_Tgpw6YZkwAFJ9?ma*Y>&L9RKUg2_m! z%XP+_u8S8->zTOQ{-jhFsSxQsADb^=^g{t7L)DAG^9H7)aag*KY7Zgsj9W)4?7vh} zVOF~QJui+ll=UZy3sK9rY9*W-!644qVmsLjO4Q{&q)WXAgnPg0fH;Z( zdOCB=9-ZjvSS*3d|A)PEkE^Om|NlO4R8$l&GBm@3qM@mwl3|)25bu{zNx@+R6%7*= z71PQTMI8w#Ei1=vYD}3mW!KTt#JiR?mR+o|(sByY8k@?z6~Fhh_p>>hEj9Cf{a)YS zU%&HW@!6ket@W(6o^{!4t$pK?>jMUhn#f}B3nU#DP-(vATpqKu<3AwVY4Y@2iypjp zba+Ysc>imXj`exz(bBd0OJMorQbd0qjM+7_k!=u`D7Uqg0N$|Ca`urh=@OcNb{MqF z1>j6(YEfZ}j#AZpV=tZM{~UAqpM7)1ZPsa!4%O45A|?o{ z79F7Jl*re3q++T!i>38f@4&GOEhb1ERmyr`bVW0X*Pq)%c%tBunz5U}qQo%!BM z-b>$<#xkrf9f4LV{j{VBz=4nQbZe|{1}_+&^N!k%V_=u|9e2y6S$+9Ee_4%gCQr>i4qN=1!#Lgr36Q1YNPB zP-4pKNGeXyM5X7BqT$nXf0CXn8(sb2gw&~!jN!F(S~*Lbb0IqGnSPBtvjO7 zN-11kIbMD;qSX6fFxxE={_JG=3i|1!9yf)|syF5P0-o3WQo>trryL$fL*kk6m$eSYUzO7 z(z(1g1bGI#?%|`)^0H`rQo7JvCq%6AK-hZAR_TIou>*z8rl>mrw$x zb>XBYGrM#-10|g;?thlNa*69~(avx_U|F;;r0BeF_I)K@c@vy;#n454$xB?1$>xPN zBP^wYSD6zhzPmRzY)MyhEyODg{l3E74O!B@p9s}cONNg&>)48dFogtpk`vzc# z0iT8QIx`x^eTv`+J5epT@!~kgIk6o4$c0;RbXorYfY^N2*Zg83R)!CW?HLxwH?Y&i zeU3X47PptH*?bmMF6%}Fm9AWjepr*RIPN!07k7vNyw`gS=8RywEPUm1D+sr5rvDws z+E8=mF33cSC=n^$~CVw>_ov{&0#pKut$PZl9D}S0g~V zv~50_Tu<+5v(CM-Bq4Vh4`9VGgx->B=01%$RI_=UX&#ex>mG7>UPjyy+l{JVN0Uw= zs~4|2GtlUfH;<}lZ&f(*Je(c5plXh{>Uut^T-CK|w%2-{RKC3XES{<_un*>%2cs(9 zH}5vGuA@t?7oJ*92hf=j570tr=)69~I^bP9=0Sw)Fh`7vV>6SQ2rp^ZNm>uJKUP`~ z?-}5nVNS^y0S0iQlz?)BOIoa4O_PUWWPK#B0F&8aHYjK9Trj-yEGZCu03bNTFi!WL z!u+M6q;AQ)xP0z%=IcbixxGpZbCQI*eAIO)X*vZ-yr<$*uRJF{_ftN|3g&wbed-rM z?x+4JAls~uL@L?uGb<(iQ-2)tRK_*A3pLT&q2=Lct{MJ@%Pw6UyNOJvjzR->OH}+M z#@!drc{9Igx1Xzm@pK0H_T3U52IE<#l9af7+9+kkm~!%#cSrbus zebS!7JO9J{4%n1k1yu#%`4yY4t*Yu%#C@-9<20T56*oYcpQiaU;uV$qNX4E((GF8P zAK=5K;WC8s>Y=&T2>N|ekD(zmBQP5?5AH*&;4`ONIk1M*!yg}*!aRi6&l#Ua#(#W3 zFfCrb)d+;!JPN)qCEjGI)U2dux5_!G{a4*x88_N&kEzQRJd|@%aX~{)B((jh!9x>5)&5aD#nw ztSCc%haP>gBGVB*iu&mhy#Q_9pXiu{Ff6+7Hz?}E1n@tuc0_h>P&d)PoXe5_!4B%3l7-*eK@I2)Ove7h4r)ks zR;qVUqa=yI4yqZ#wbH142lev~)(&a~y_jiJI7|&Y<>c!K~m?HlVrqTv;TQ%``$TxN*WO zEvRFZ+!<0bIJ9JBBpa&o#^~oc^@pd_W6Lx{_C#g=Uf0}5jqqMkbZ4aOp|XL>CMwZF zSo-qST_Sj8e^kN;5nf!1$BQTH+Rz?-WS6v$)zJF5c1fqxu9h5iYU^ST@Y^vYu? zeCWhX(0p@G-IH78(kolqytm5Qo69u6qmvxUof0jI&V|hWy#HnvB0L_G5ERz0UqbhN zvl>s{7mguH)9NzTp4{#8v z1uvST5^jo$HE)WFlbfQFI}|7JdCl!~6Za6Nqm15D%5ZP4gL%AZSfp&(%@4oW`PXZ?ov~PTo-XD~oT5+F9xCU&BxGj&dl;y}gkG%AgW2(oq$Y zB)M(*q|EzN+FbmZ;2bispJfw#Wb6#r2M^|g~Me^MPG%$Ve z1}~Vy>k4Zmu5m+)7s(5et)w5e;|h6XBI?#Kzz z`X?_TVM|UiNs;$$mvz8`H&?8=K$fj~!mO(hT4Qwq??Js4H8N1mvu~IfVy#o`v6}Zn zV`y`{AVf(Y77)vMADE8eiAeTw$ADB2rZbRlH+Au<JIyq}sOC;6iFI+fW!gvA}BQ%^KHS5@}GkH*4QQACH$VR0Ymh$2P!o>VX@W~FWJqRKPe>~h_Eu6>{UgU29jyF2qN=Sp6?N1CX!*3f%3r$JV1f3c~g z$5I)Q=J8Dwv%)xLjARx2W|B07bKcqaOMgRKLwKBk zySiy|v9qE}hRPIP*6Tibl#NiuJ3CYNyDFQ|S7a(GUA{ZsT-=(nh5 zH)RdvuRQZ&-HnTbkK0E_r_7C0JZK17J)|i;HBzzE)f4=(BnLDJzAAUK8Gv5Ms z3;4JMwQt^X(tbb78ydivrfkLXX!X18xZAwFENs~hkXy4eSg)H;(o$M^k`}%3CH_!y zWWF@HWMH`Y{6?s?p-7I3ckcKiyd9U7DV1ErjW13O4Q6a0U2kc^J=`4>8Mb_m)h2ot zb04F2zUtkW>N3dUlt-wf458eM@hxR|;Y~q@4H4Z5Vuv zO^WIzNL9p+%6Vaxw01>}C<;`YU7*=aBbHfv;w ztuCMJ1N7wl;YUS^RnF=&3*Fj08B4u|Q|6NkHcLsb=||bxAL7h;ZK>SZH74{=a1olH zNB2I&0hK1t7)Tk-U6P?j(H?Iy6PNBg=e1X zDoQqr&cxT5%r``hpl8YE&zJ@{`pB0&X@88e9HdM%$T2%nK#z;?Zt7>oqVvhFbj?pg zg_cGx^6cndIqykHt~jLlwDaqHzmcN@ozF=7%9b46hVGlBhwR|-403RU9Ul6+%u&tE zCCk2(wyTUm45?!0F|6ZFA+{eQH?x}g6k@ig{obVC&GAmp-tOPcTWaPwzIcCzqs0U+ zN}1nQWyLR}OPtkWyw#hRsDJp?E6M&Jc&?mLaCj)qB*tte;oha>KM5nt^|R0ibttXJ zzDaUPk~EAX>>8p##)Whl;%itmDb%xvud(q3vZb_c@4Uw)`Qo?|FYf|3^JzBe3@VtI zY$)nw>xFyB4f+;#Hjyu4Cd)h=e|ogH^18cN=gJ10ePbSbiK8lZ%wr-}G>kK65ueGe ztM`PYkICh9)O%EgK_s(3?W){CJG?ahLNk6ZSNwhQQUH&Uk*;dC5Xw@sqH+s*WH_nO za2Pp~aY=pUgNO7Iz9OSC$t2Y=z=9 zl5w=OsE^6gdF$Z?OOA$&J=o_6IEHJRyE)!%H8pO-N#|8*RaQW(mhpi|z+{@68G{cB zq#q26LqUd|8aW4L&}q^?2EW4ni{pQS*z6&)`vT3<8R zF@n=7GgwonA7;|jyVM(Dnbo_r)H~cU_Kdk zl1gq4>HhA#*Q{ZuuQbxWR8NOPvKD9Xl5l-PdEoB(zB251WRrw0%8V)`Z28ZYUF9i( z4y?xI(N60|$V*0(S#xDH$@bM8O|<7<61uM$x=XF0Ym5+`%kB3Z$bOSn=gabSZF#J4 zs{oZL!(66JTq@<|1$b=2gBtNuTDJR{P{|vI84nXni~3`2&Of-!9i{hw#A8yNP1qdc zngG`U%-!esijSI^7a-Ul=88u=|A)kK%+t?BH&sWMw>qFapL7~aQ;kjINFK64$Wt-b zR|m{wi#d>zGnsrVnYoT+IF@8aSI5?5=AwKUy@qr$B^|3|*`-BU)gga7oe|Ku2}UbYzReoLFDQ4QR4zQ!HrSq^@>6g0hFDg;EoI z4h}XXHk)Jf`eO!bAG3zI29`m)mD##X_K@}SAzB<;XcpI+!^fGDWe$lLH%Bg=$z0wF zlnAkB{WNkImy^u2Q#l?In@d23aaskfN7EGEkA48C#I<|mE)^=c*B*rjEX3YSQ+wGfiC4oZujs18gg$S%F# zPP4S=Ez2i_u1Dh`jV8pGZ#UC?_^(5xzvAUcdHPQ-3=xJ4U;L-5(wPsH7VR>1WH5$U zPg@*f$0kRdsfMSUi5qCa)}`hl44U^JwjwgU(p99b+ia?jz zr4{d7X65t|RY215ORj8bXtid*vo`o&qD=sJF<_o>U?c8t^xJyui_uv)?Hn(+#Eq1vnOYiNJ%9YA}K0`r0b+v9Y3EaCDJS;zI51{@#hhLp2q(J@h@#0rUGf=qCf@!O95HYrcp?py1GAmDF1z@ zU+US%j>){R<-;!qIz5{vj!u8+8t5*r1u_5QOyyid{no7KCNp$it)XkXF3RJ^9=7Xh zeGa?m8islIWriA8nI;d53iSo?l&E#zJQtrMONJ#({>T*e8&bTuK?vUcd(4lT`E^NA z1-Y^|__;WM`K#~aa^4|W9?qU?fnGg*38L3E>^)rW5M=I{Q<56OyOG+ICRZ)lIXimc zdBbNI3z{;A%qhAfGYP zGI(o!MK=<+8VQYwek^M)o}TI-!hVZv0OpkNRyiiEOzq>)XLLU_yME;Z98@7N%XT;2 z;qWnD3({fUNuKnbVfLT)drI#g)yNut+3ulp>O1c%_Jr99hS2imI%LUXRzMGnX!A&3 z9bvwZXYup7g+WGfCHaW!e8$}B^qi@`_uX@Ymb|y%bn(m2=NdsqUo)4H@PTWlI_ zKuGaTA#bqH&8Ld^40|yj*#;Mc7WYq;Z-Dw!d4Te7xkj*2(Ve_t9Y!zmcoX?CGufYW zWc&W9Vlk@-mNW&UT+=_5ckyGd&fKlTyGhx}2(1vj4bPyR{TL-Fb0{t0!AwOoP7 z_4?)8%&8TJ8}rTZISC8*dXHiM-J$9+s^T>~`l77I^r)HT)KrJ=?82IF#~NL6hq-IY zqdG*6vYxxUW-y$KlF1z}pOj?=lm<|a zF77I9kKF1D+B9hbB!Pz0rQW}itX#72WA-7mv&7P#D~k7)-k)DzhSKm>T!N;L43JQj zk4eze#pkKh0Vn^6FX!R#5$u<+DIlv~*#Ti^BvaP^@8jNPEy?uJdN}qO? zG`mn5uNfYi;CYkUPA^#`V=Ma&+1N6*B=Q9g(~BdQUc-m=7B4jl8R2+^bTRiOFO`5? zViAzLW|n^^v&FtcED7(|XPnABe75s}4cz8g{O^T3&B%RIxf^11>ZlPF9j(If&7Mu> zG_zF37J2D#_~KF-g(P9@>1B@~B4tVXzLX_q)^%6l@1=d^Hn1Gt3sljYrr!BX!XIQlg`T%GlnuAW}q$O1cLW@-69huxpUDPqF>4Mdq~vW zw_pBBI$M_gO8Tbp5wd&u&TSQMrJ`z$(rr2A9>aRib1#)ll;w2KErn|UQ)Jw5d5-#B~7MDswrdZTa32EKmo&O4Jgu|BA7n=!U z-V}4Kw%AOKB63G&_#e4ECzChs+RUUVV|SI(FU(Sl@rI_T@mIYj<26NnlXy2XHJ1A% zEe4tS8DO;Iwn>utH=3l6!XgB zL&1GhO(oui^nR2NnRk*YNhZ-Ud(777%lhu6Pi7*{rdVl=ciCafACRRe-zUU6TpUU* z=*+0aXtfqD&uC^=%;g+MW$?v1Yf)Itz_Wv|ND?5Z@_J$y?Ve|zf300K^BO|w!urfy zWa=8ksO7CZR+rZz5rH>JEZdr=Us{4YrbaDTQVr5>Nr{RmZW-18c0ko zPO2mjCh`(V)FqM@mq=o(j$1j9gi9ArwsN8Q^E>i4xJLde|49wmU&Y{RUk3~tZ+>Gn zO5Sg1l%&h60uo}YQh8TdsJv0^Fr0g{na;*@E1_N*tvYVrThF4F_>okUTuW)2`-IXNpYiir_sr-Jeyn6V|HYz_g!i$N*+$(>4xxX?#tA*R0nX7l%x`#9EmzAF}txP^?(pQ51u3ZoJF8%hEwLkCO)N6C}l9R(Xf7iP#UkZ9!?9K0E zE1v(}a#OSaUcZS(xT4tE{3)L9WzmrGQTIN3OPy_>^e*Xfv}oC@*MZP(@JN;GE#~!N-E5y_w!M-Xq?ob<*nG zSLaY2!+50Xm8y5DzOHI!bTI}SInG3CJw8?Sebt$&3srTD21b}+-n>_9I9qtJ>KB=D zRe6jc7GpB&`u*SS#!O=zZoDzs$TNIKt}(%wZA>=`$lEmYXjUx>{(Jkl?d9euFLWl* zA;>Uf(sDEII3Af4p$zv6ZV2|f2X`2Ed0lQ0#3d(<9GTTG{RZE_X%nW;oH=nqfloxf z+a}JOHF^3pUtE_?3BJj*d^0D`m_2#s#GH0U<>SvW##i3;JZCswl89Tc8A!E|HYsC> zImY?d{JGdK;}rR>n#;)fNrYjl;!9sQ#)$)c_HN9nAvf>t^L&ndwJh#bh z;OsAD<4&+W(+RYG`8#Ahv%(GNBjE-`8^*8}lG_a?uJ<2ZHYS-#{>qqn!AH<#jWN})K_s$ zag%U!-dNBZw|&i@n{7uIWhwW|SS-2gBO1>S(`g3H9^;RF*g4KHx}Bq5vatv_2A78`$M5be z!&t%b`}jG32Pb1ooWzTpP&3|qWUG-$yse<~w`L~oZiW?a=Il%3y@^cXEuU%_hw=?$ z?{vd>ZVG)j51rEtBa-7dWcF|GXNK_}F6VQ~b_}~*Ag||tySxcU8A)*7Pw8K{cL=*k z!@og%w|xqalNa1$Ul_($Uy={>x5fRIJ=g!!F#3H%I|84>?I!Hp-wE%dtUhRC&5$nqsPAilSe!r(&XFf?~X42gNwW zSWwdQDQ}@XN-Mel0mtCX)$EL5DQI9IViF<&tcly;q~n4_4jm0Kkr3tr)L#^R6MPCO0h!mgyJ#9-HPRkTNO7eKBBlQ6sdpQ`ce z;eU}#N&4S`lD{uK0sqgKkG+kMsCD+MKc8S{7}vv( z1*1`UC&GyStC62}$Xg(%jW!AfB_BT$w+Hy%_s05zYH!iE2S?j(bc;hihIP_dci(3ggMgO)?&gWzW!{CdK5CR~gw{$9fNME>HxXm4s? ziN6wV=UezdTsOk?RJ_9Be}eVh{_g_e*TEOUk7@fXfZvIe@{M%J`ym^u@~z*}t~C9d zSud>n)~csFkw-b?<5b=Qd7?ueqw+e);~es@Pg#0)pEA~0{36w8tP}mu!Us6Qt%3U; zd>Pzs7ZUys_#w5Y@ZoT0`kgeL2v@wfP8#bsJJNj=?#$oflUDw6kUPscK;=Hn3rGM+J4*!<$vyS+Kz6|Jl>I?Sp;C6b#+q(Q4!R_?KKNxPO zC;X=`0_h3g0k_i=-V|=@f8e-be0w~gPxzN`Tc7X`;CB9n{|#>I6aGBhwukULaUSeB zUGXOT3w69pMmA98T@~XL!w6@e_k)ntSNRXeEPbCI3#79P*$41tYFFX?;dZ`T!Iym= zXy^It`JDMIz(4!UFj~M3ZNI14---J)z{BA=M+1DwQQ9}oL%A+~9LUFmA2Yt=ME(pI z!+5e3l=DF^#W=+uj~HclbIfm@?<4kz;4kZWQut>_0_O?gM~+x}-zD5`N4O`D%lUMi zVjIP;h$Hd`!S3L_9~ot-k~2-OQ02EH&vwXfRe3rn=jCo6)y&7$$PDT58++lg$~{xp6TExKM3fX|2}(^@OkPl{8qSKpTgV0?RF--E_|*d{EzPk z^o#%3@B)YbZn%Bk7XOWKr~ZfF`Ht|T2qX2n`mlv}f>OT&4r4!##jd;GV{Qk3MC(U* zm&1W{gm=*RZ3y?QBivQUrJjCxuV(lY#1VVH>ykg_;4KKV5XTy!Q9qZwBm9JpFA}aH zPTJ$I?^<~D-9UO2EDE0E} zoAeWorCx3TAA=XFy@fx3llWjj0Xs?jWL$gXmpSAQa*d!7 za?$(!fk1eX?}6LxOZea5N3~vrKL-!d_9lEOE)I3KgQ9P!Lti|yB$bCb@Z{j}6z0WA~i$?4s{7?AX`4WCB&P)%CA^odCsqf~BKkqflI&duUXYS>u zLU@kaPx$e@fpQD~2Tt_8Pq+d{xC6+gUA+W~eT!Z3?m#Z~AMTJRAZzO27xr+ya8Dq; zQuyCo{CRk;mS6m*;v_#m@3!z$P|EqtZli1`$5O5o@KJb*=2!SCoWv_dp5~A*K`!~f zd3PWm5`F+q^54=SZ?}ua& z!S99V;UvG`?y&GM7(@JZJB+f;97{QG0PlzUv^>J^#(9WWggo9MzZ1EXbMTHpx)Qz@ zPRbeSkbBCx4vCX;jw=s@7x@6V-QI-vhQF`n6y6eU+d+5}iSO|Le!FFd6N)>x2lPLQ zY?*_PgD2pmJfq&Qusaw-zUsbVltpvQZ{4D8hH-YAQ5K@*6@GeKK(Fv`aH8i+!bLg4 z{R6q!-hrAwgvCBuV1@bBJ-^7Vs);i>qx6tm8_e4Gu`LSO_y|GT@tySI-`4NZw zZ(9O-CEQc+P%WqMRq$gM0(ut0?Q)BM22Sjm;?R?XEY`t;;s4ld*`tZ#Pk*!Qvg>a~ z*(V%JJ~P2r;0Lw*!k@*79sh#-s6+lJ^6sQN^>2avOZaS@*yVbMJmyu}KXPdwzj7=% z`qe-@@$Uh*%O$)s-0n|id}Q5-S4Z*UD;EFgm4Ke-klp9tbKpa8lK;?`t^B_BlEohd zrCm&a$tYXIv6R0qI1XN>!B}NBK#2C9#4cnj}!Z>S1c5N-G|CS*3ZG4!7psG z%2lCwSaG-F3yODdGRjt~o)lt-)~o4{5)?^{xmd;e_7{ zx7R7cC&2A>hwz(lqQ|dT2Y*}N-e0U7Z4@9en{xJGu{)_)9^VFHcpT3*HF# zgQBk~DE+Xm`hWEoYn=E8DC5B{Q1tv2jDgQl-dQn3v9OHxrsa(;<8c}IY6l+x-{jza z_yz~RPPp1Z;&&`#ek3W=-h~_Z+v98gdc*kF6M^wU_z}20j~Bk%<-Zwj*R%MqfZO#Y zd@y{4=0|vYSNup<_z%_x@+;vt!|m})__OdypyXfp1{W`L@wG1gh>PD3cj_;3`FDid z?I@h|{<_wxw{@V@{~c=s@f$x%8&Lm-$Zfld{QToJ!yS3t((|}t6VQ*o`i~oBt91O4 zcvX+p9Um9w_bWO;Gf122;S7T=6%!^sgmcZ^Er6+#yFk zmLZq(+!R-O87@A6aM6TI1f~DhRepxN3E%T*&HTQET=MytOFqNJv*1p9rV{UR^d-2$ zwUcm;eE;%@RsLn5q?4-HNwE~T97aa3jC-T#4toZkVG5F5~TY#~kqK)p-&2p4aNKD!-=|AjvX z@-O_SKU?kKTTt@(Pf+w82BjUmLwINWU4O2b-s^;ua=%14yB&z$jmWzrf6$d)F>*;~ z0Vw@qlFL6+{Rg__NpO2S7yT^>C-qv_74E|7K)p)1cZFXNa~kU&gztpg{XqCuoaCoW z@ivEl%0tXgU3@g*;z)N8a@#Hvt~+wETLN<14kB;8I$$s15gNa~ruQ?lfyAo-n}hFx zvEZ9v8}K<$^gaaM04{x~roOy~YUX1&;iNtWBX{Pbz3OS>kiYjJ^K3_a;h`F?o-16% zgMoA;+!qf9@*(^vPSX8|aCW*Pe+no0Uk!Ex+kz2bL&Dkhkg|&T&I2|10eI>~YK?jm z|CixI9Q=s~jIwa4IrU$NzdbLI@Z(+n_3$6yi2vg%V||K)pIlW_-%@l)yO<2dklzuY zl&>!F?0kQ|lIsNbTl17sP{QA)IQagW;d&vL@vegn1f-MNQU-9Al*x&s{ zifOEq^!@~Qra!>NI}%U&MMIa|fZOAp=y?t&`F&8aWMx3_q`Usn zEb(W$cpmy!>v>K5GvT&9gpWdxw6kHVuO}$u*!8Y>9b9_lbwpCXEeL0~U(r_wc@N~L z@2#26ar_dI?{~@HKrVKD#wA~gTi4EHXBPJ&E4>$i+@SaV&P(yFB2(ri6Vx7k`RyQm<=}+x0Kuijj+b79+Ro zP2{td2ilSFER8=Jypiz9pp3g+K^b@3fHLl01e7u0at#>T4$KbXdgs;|c z_q)OsxWY}lJ5UaZmy45pPjJO+f?UR{pNp+^+CEV769!5>BrOZ%_b*FXBQ3N1-~W@< zUot@nKS=RPuo>}!z({cC(wgyCqC?8L5V>8>4NDB;kI1Dy(m<&nKPc&UQ1pRU!oMY~ z#IIOVGk*CJqimUuC!+5)_;Lq-8JU#-c~`vE$eSWx21bE%!M5N~!rArr#$v`Ytc(zj#?DZFMK&p#+h5d6!6nU z0sj|?SGLIVzXufm8^KQC4+{hRR>FM+x7TOF-@Pk4feCOvIhR?;r zHQxS0%idc-(enZ*c3f3hGyYWdAE*Aq@$aeQh3FpuKYhWF-#X!S;P!Pp;omH<^dAF7 z|G@>^@8F2P9d6#tsPSKf+xrC)|9*{Mtnp{#Z||3ie=a=Hq3;i___-#F zPd;`dx7(@6H_s2WGvUv}?RF;oX_x;K=#l!m%O#)ViZ_FBQtmugdTFkF4I`Y?!|A&$ zJfT<)b|?N{!6xA3yNvY@(r3iplCN=h8D-geJ{8^xf4lyLw^lt-V0+@9pI6iWG+`v& zk6rTZ$Yoq#?~*?Rx6gN?f2_lQ7(7k&b#%yg-O2rrcLv(C@PEPW_ALA`+|IY~131ao zv!K-1Q~2BUm5pq{oq_riemmT*FX6LrV)tpTcteq2joj~uek;wTu6Hd(HTjF8;>t+<$a2px-|?kdN58+zW^kyEIZX z6wl1D>bDXU|IZcQm=oCd3qiJ4<*O7sE5<5bPI#jZ8fae<{-@h4eV-^ERJ`xDfZo%y zdA3XCd5U8dPv9r{`AG3e#Z`)bQp`}iK`|7RcK=g>75}*60mTi9s}z@jL8LQJd7fgn z;%LR5py<6;c_YQ2XIbg|b5@|89z|A;6TJ_p|J{o7RbHSxM{%U$b&8+PwDuo&fzs|C z2E`r^f}(es^1BqLff6obW}qHMAWOl?y0sf9@j8K`r?p}vDDkRh1mc~Y!8is#r0wZw zzG3_Yp8bo}zJ$M!AMk%~I_o{S?JqoedcYnD@V`w9@O9G+V;=m+3zoiA_*k6eD+Kul zO?TB){Nd@EuJE%{jrFMxei}Z&!7Jc?2Y(Ch)VB+s=)T4*u6Xc2Kq7i~q_z?)P)}cZ1vdUYo*L0k`!DkA~a&geOi3*zqd( zOSf8fyF>A2#X*X_!1mN{2gT-!6_c62%fkWm*H0#MA309!zaNx#veMyyH?li%(vGI8 ze|Lv}7i6(Gi65r;Q?A9o$;G}}pPwW9K;^p~^6ki8Rrz1Q1k!l`{|*{&IkNfcf2(39 z{`R`RFS229`@A5$r-qADZ0-mjjEo0~s>2IEe~Xph4;5ckT&$R{c#Gl~#R0b%>pz)f zwc~$+DK74J@t!W8=;8@39`E99T)eL4V=rk-y}hEiMsfb6fSnFa~w2%db{Bgg0~fhr0ZC5JvPo4qi=tb(|RJXJ>QpK;91d363S)UvdKeUrykeU6qef z9H8h|?5Y??yiW+vZ=J-8b#b4Iw{Y<&7msxDa2F4C@emiUnou)8tH_hseGqcHJ;sX+ zZqG}89?yOePVDfy;;Uc~e1q~w74K8b2jj3`uJV!NxlW?}bqKOv>K_jVp(hR${Z}bA zQ9M1)(o+G7|KV`~JqM9(Q~6&NH{hSC^TK|}9#;9ipo}B)l+RS0s5sK0SK=k8JRG?_ z&VQE8Gv099PQw3=^N_FgiVrE?k{t-=Lw2LeLlu9{vf`aod{J?|;zNq<6h9lw`AO~f zkFl(`9DEOaD^BeFoJ0N;vIlUIzY{@E)rQ)rZsQ*9F+d}HYj?Z z0egbe{}4F;boc}NYB&iu5R`CNf|8$qj0vQFU<~6pPVDg(DE?&`0X_F*peH@R2c^?S z;dcHnjAlJPI>29oZ^Vh7TakxoIVT{y2`BTe1oe+l3?^K-uICSp;{Jb}kj&|_Z;1v$O34YAM zA01)sA5H=LlFmTN?)5}=Kdve9 z=Ym&&BVGPq-(>BV90Hr+zu=}o{NTZ~F_qT`Bk?~n%(CZwL#_RbXNFkm-V2KVR4^J$ zQn?S5^kWCt)cdbNhT%v4`5?-#?I;}CAzU})+rT#9)5;%EEC#PcK34h7py)|;>4_U; zlodMkwt_Eq@D{}DO}r~y@eJfq$WITn_MP?&4CH&(0IoYC7yCa5N;xNkQts&ukD%oD2E|xV@*4$;9q#EHi1#9jZzis!(*u-r-r}E> zds#{#Txc@yq(CnE??P_ZqsV8Z1ne(77jD~M_;{CpCVC{DZZ3JeD_(m7Nq*b7(hGOx zs{!F8zZd#g^>F}{@!?6Z3AhN9dbvX}qYvjv>Ym>^$=~okMp>+$hlRJ$a1n&F=M^Fk zMlNSK~CUSH&5 z-$Y0J>yVxF2hv~Ki*>GxuO$=GZ(1NfqT{B7`?a@GcIa2jKEf+qyxhf~aq*Jgf&CeY zKgQ*s;^J@js+rFVHwO5I8yN>&{B~p>>^M^KGR31kt?RBkdIsVrAp4Vx=OZ)YgE5q! zx9^$uYpS@HMkVdHtVcjk3bKcA(r@l`$=mcW%HoL2Z{2E6gjc{LwLJ?DbA%U8qpdy< z3I7l$>FonaqR}%q1kx4%M{v@)}gKBIQa_yatr=g@MvO&L@&DjwPQ< zQTQF)tN9fEWnv&*;k$7JZ}hZFz6Lpejc#?wZ$Z%C!GFA-^}UP#{rZ6ZJMdrQ;wA7C z7p(k=|45va=TJAJtlVyo6l4odJqLkN@f%$fv~r0w?8v#UU@hmik0aH)s@te6^NSHf1Rh~BOBr1E#apa9K{~5fYR?5DIW-my?^XzVRpwrI%}`tK0oA`u2ECuc78>E zs)J>p_d6KtPiekIUW}Y6Mx!anH>=%5el2pQ42|j{-{g?5#81*+21oWU8-|nk&D!w1C&!Yn`(hEm*J{3m z2RY&iKaP|Ba~PEPPsaxIivJq*pXrcKM>fL2`@pZnNja`;&Hk3Qk2hO!{i8K@(eYCF zMVy5D9F%fB)ygP)gJa2WbMVjbxtd?$D{vCN82K`Xd_HpN57QMVfRg_-P{xTatpeo` zJ#BDO&L3g|@(Lg8E;7*-d8SLA2DkgKgiC_k?MHYb-0ttfFN4QwxrKY+J_kR7OzQ6~ z#hr?e_yYO41KC&y?+g#YN%_~e4D6d`M)R&f7oQ5Z#~1P65KX*1dBTYfj;6XW=m#uQ~E;hr9`Lv9~NP#NJJ1K~CjR?;a2#axbMf z_w~q{JNUO(upYsQ{pL5da2zOhPXZ-=SH+f!jT9>{H_G~PEcMdra-O4tUldKs4__Wg zSNz|^$++>n;!-dXJ{y$urx8C?>rcW@!ga?#4V3!3&ZVy{a*6*-WFVcrk?gBE_#F5s z2miJSb2_*^P6}`A;zuv5=|2~4+fTyh!|i$zJ{W%Sf)txqwZIeLc0CKf25yg6!W+Wv z@lE)zP0;U%e?Yj-N4|*Qy*7jq`z&$E?}FRuNVo#HeI6E`?BcO-`@Agvzed!IKOJu8 zv$7%e6&}c!@O-$_KOwB9|K;$rS`G>Cf!pZ`Ki0Tr_}z_-vO?|m;=j`6KMn3oFB|UE zH_*is;GdyK>Ou6yyLbl|k8|-@7x%e%3m1=a@kkdBh1>Nb`3Z9Qf8NNNAKu-lX1F-y z{b|=;y3@4a@^Yekbytguey(1|3gCJ_>H%ZzR08hPxWMJ?|8GFx(zT zguj84_V!K#3ts?Z(7zJw1};_{*T5)ykYlm;k@~FD;HR|z3%?pa>F-y9(vL$y>BryJ z543LycMK=}_$7yY8L~VF?+$N;lYBK%tO~LAo4*J#%I0Z*c?{V;_*@6y;qrf-Frw!r z#l>Jt(w_t}q%=xbKA3p-aV+Jl2PQf62v2mRBfJYPh4_&!d1F_+BlWEEzNYwWy?{L) zLv{~N>^#vSAB!vnC;9HK*h#UqVw7Sd#URC>>ss+nDIQn+Q1LCrZHmt;u2Z~Uaf#v_ z#mS0)P#mh*Td}iZTwUHLQ^&I7YB1KteJpiWqkNYP@q4EzISjkKCE-dpGEmnLfi2OT}7WosNn)2)6_IXvp zT?@C*3;zagpO1vU3AfMV!neWg^SJOwang@hC{7oD9hb%+BahXb zA}AU3p@m_3yeWzOEInR4k7p8Vs+aXVN9A`U@XE=^a9mfwijvsX#mpP7~aU5@V93OBT|J`x?iR1XV zTmbsS&jIKI+x+}d&6!Ev16IPUH^PIVmTIF9E! zj_+|CKkYc);W$3(IR2;O_#4OZX~*#y$1!7Ibs4GHLZ@Tf&irM`S1|Z6Mpkm_(5&Q% z;|ltW%gY--Zo-ri6K72ZI|(W+TOztX2 zv--*Z*|vZhoz}(3nm}isj`1c;r#Dc+x=kTu}mqO)aO{F=_&B~cLt6=7I0@oBzo;F#+*9b~( zrnqIZr_~}IM{E5J#imy+%1t?WEVwpQ4ls}`mSMhVjGm0UcuuOf5 zT@?o@<|y8-xJpsxDZe?8|Nn8m#^6o2tl6D8HU7UI=c#Uk_Sn^KVbaikr#)UDPs8mT z-?eKO+NIBz-5_uB`0?Xr&B~fIx6`ErUUriog&K3k7`7eRjhbC$l-{X7QctwmqnWGpi- zFF`yn)ff4*CGROl;wO7Zs@mjCDQo<)vQ!_7Ir+KsmwMKcuTUlO41!aK+vL^yIgRXPx<5*uH}_y@}1?id;|Z{$KXGC z+AS(_>r1_8d2~^KpGOrdck4FKX<0J5XHA+d=l!gdq_iXr3@lT9Y(ts9bo7b+Qu}+% zxvs1+oj*$-H9WO;eJfo0vQ=NY>SH^v#_v-5)y$vnF&N=mk@@J8!))|Ts-ejuSy?$U z`>OuA%ezTu@P*dil2KU4(zk*^KTwO+(XrQr>722r*%G%tyPsy;0YGQ_t64kJe#g(6 z64Nr5ps@T<9^%+52ycEF2flB76XW+9{WC|IP20JGVdm zOR%x>$D6i4eP%Q+IB4S!!Jdt*1veUv3g1J=s-HN%*=xKU1V0vh`9_aXI6~z6oA}Rd zXl0x$uUmfO+Qx-9mIr|iz`Fh|$Aj;DQNpZkYn*G!7Q}}^#!88MJh3z{M$U?@zI7aCORnG#}(98(Ff}qX|28f zZQ01O;X~jtA^vSa;k}}LzE*_m6zxOKUS(RVQ2(|8K3}wFMQpTxcT6;E%CuJDmQ47q z@R&&dwm8Bx;#l$=HlL(b7bK1B2FA9yDF2q+=Ej!n#>7Lv;fssj zAz_>Nx5c1a@|q;PDIDFxcLZ6wZ958LhY?~A>QAvv z^^QE-#$vbJ#)g@uue??CxF*K7R-~82vFIA#)YwK@eko|>-%=FRI=X$x_~^;_B{iEI z9pfJ#J#J|OSt748IL;oywikY$kXxf;qSSw^v2EFW&q&Fi$J096(<~-BhA`D}NIQGH zvCW-tyIhB>s;+R>YgHfP-5-O!wArAIUxpc5mObh3zJcv2pNDcv`)kN6)Wfjtyr7X4 z_#dN91yT0>^^Jr^V11+Ty}O(Hx0JN?Z#hMt8249{Q$7#%eNW8W&4+m!HwZdmT-(WO zB;3>1fA0Ca{D+H1`M1R;wvG;(J9+DbxR~fA4gA}RqFP74b9q?F?yLOU_Fm=h_?HlG z%dg|wL{D0Mb;~`?jE;M*O6vG^T-)f0tFCUj_i|6iy;mi}ZU3N-i}Bmj%)f2Is@Rr$ z8wGb<(!}2}IiyYWc=QC#ZMQWU8~wG85wYFVaLPE^f#f~8JUco$Bs+STF*o}2I>wA= zXb+3Y+oF30cgzWijV|=Hihi40%8F=Ht;t^zc1>xqei+k($R%U^+X{VcqS;O~{l%}W z#b1WKQ^y$I>DQ|3ME~MZ#Bp9T1@?c!Rd3P{v$3btk%Y73!z1nbH4OTUY@O0qd|r^g;tK)kAHGnK ze&GuTX`jAG+6;XtF3Q;QTWv?i3K>V7V?%|Eb8Q%h+W5D4D&?)-HOD4rpQ+eg-rPdyVajNd-E%Gjg>LsV%N*2BvnXTJJHst-URJq!|ZUje4K#Kldf|E%`jI=M(1Ek!$a0 zSsi{ob!IPUfhn?pSHOAJrT55F+mK_un0`Wus&NVkmq zTq4Vrh<7$z$u0_Vl z=#sds>n@SeX)lq5)gq%`ToR6H=_Rt-`erO2n@+?H!DZtLaLaIy;5OlQdr48Eiqi?%fbEE?L*Rtlu@NCX)E$bUI21p-f zK9gPFnsa!V&ln*w(H=Q>ugEda-}EDMzEPW>Q9drZ0doTBYX;}nadnx;5ZA}~Yg{Xi zkHpM3=K)dUqGj$7B6_ddFiiSDLwGxxPjDWSJoq@z%+i2$4k=f{$^Z$6}TCJj5 zMF%qnOpa?Eom8hm%a{^x? zk;Sy5yefvC(}K+e8PoSUW6R!sF*BjFh1d^AkHvn?%aR zT!wwLNYOREK6Bsjp42IGS>n_od~4<;cKE*}qJ!|#hYiw+A)M$oOufG7I_QgSv2K{8 zn=E1Q3&wAJBKpkm+@*-!g7KGdeVKc(-iZvB{MBzwI-R0};$o}A77}k_RC~%58@+_` zNSqF4+LWW6hNK*YP&=C{Dy1~{>9!%Y&D>@gG?_}m# zF}U${>vuFjnFFsCWG+=GIL6raZPy)nwavS|^x-Jn09+nU`gl360vAPp&3EtMRmT?qSWxI>lbE zu|{99nE6SZuH*hU)^7i4j?cWQ);azL=IqSxnU@s4SHc{BIdlAPnG-x0bD;S&=Jnh= zkYLQcb?d?g13PwNUVm>F|GDoX8kg*DF)3Uq{eskMz?ZjMX5cB5GJ)u)7>lHW8Vmz2|@&9iA ze6Y6pGx?SA(LFb_$Mraau`b4#v7a@lk2$oA2eNJ)3=g9J2m4rWFz$|LOf!7e+O+;v z#_-;Z_d6Ny?RBoKGg~p&9>91xPL7#xOIv8oIl%909qliRiGG&wGIrYQ-WZK9;bP2i zG0|)sn0ox=!JHG~C-ZCD73$hl<~;U1y?3~=g>8*U?oX3>+7^R#fRA;6!8*Vv>txme zKJvyU2x|b4bwC{JfDqOJVjDR>*!l%Uw>2L#x_s@hX$ZE8Gxhub*VY;#vE<96Z8F|} zF6%Di=|M@xO4e8ra?U7XUi^FcwQXY8uRV7>o3;Jv^S5k&`mAH`=2%0kKg-_D<@Fc~ z;Lfoi9-#Gjy)zAA+621mDil-#YP+H#y_?W3@TZO#aeE8ysD}%<&*t+;0*=LF6ydKehU^|O9p7Phuk2f#n9Jw%PNJkIztcC2M zEE;Rs_gU;Mdnk+8XAyhft=#a(L>Ep9?5hNy|GzsgY;oDw+*iQ{((iIP-^DTB$X;JA zWj8Y9>$AqHRdtOB@q3&-i9YnlZ1F?>Tj!Dg7yB^MHYD#0uxB>;m;8Cy6R^vX>nca? zFw0-cVuz9P$oTSm?eX{b794xu4Fh}M9|W`a4R?;8=H7R-?tL#>)U#anz85X*S=;#O z%ukK+ZT$cKUUv-VeMz4+mpLxH#aI@{_^=$5u}l2yG3%s`VZr7Y);c;5F5`oYT@BUG z4!c~>EO)*EKqJ*=b1ImvgkNTV!4#=Y6LQ++$m9b7AHgm+X;WLw=_;=KM~ca+`pW z?CUnQ_HVMA`?obmC1*^D(nczf1mtOLrK+t9Yn{pe(4o4@`w(LUCCHROXt9(SpH z*#B&7lli!PE_SY`FB$We*4(4^h1MMNeqtX-_Na5&qizavUR|-Qsej8utvoANck!$& zj`nZiJlutSwrveqpM|jgZiuZzSszK*p&}=qf!{T)-)!E{)3Cuw9zA(0s6oSZW2S74 zDX09@=^&{m)>zC{BeGZ@Kk;i-+QJ3{IvTM?%VgGKJ|n1QVL!%r+lC>z=9+pTYj+Q8 z!$Ri9F$3C1OB<-zxq6M9%Xk1U+V3@5lKQrVjHQd%KN#Pv73=2?rrZeWXzdy3+TP43 zYiB8+l*`WVGmiXvsK<30O?8o$IaNnhz`@o^#Jy=1sD1{TSmb?dN~ICmllDjEl6|=Ko>u-Q%OK?|lEy zcM{+`lK@Ewx1ckVpmqYr3PlT^woC$AE@~C2KlJR83E1w0hzhGNpqNQePhijzrR}cl zLxQ&5fW2C6&7R~3=$;qVx|0^ug`bB$u~oS+HUt;e&>&Q z%*^-lxxVkWkKo<^1m(|sc`i2JbjGguXz%zk;2^(UB#(CS|9=PCC0R^18LOw@M-z>y zf__Q9l8zzysS3K{|KG}f%Qq%^w&(+BjK9{R87}+6o}MB{NuN;v*gLFj)UQ|2hvUwx zk&Q|;r8f8NB3HP{)9=^eJpTMawBU69s(xJ-T-ge)NSBpeMn0?k;GVD7UOvLiDnzes zWiCDFdSjpk@&UCVzg@g99=(gtJ>YOAeok~1bG;8wr@pDCE?{vn!GGbff zvw9Ew{~GeuU6amfa3SF9q+Dg~#)Gu6|OmX7SDDU@Je(ODZt-zcp67MM?J9>6w)WwhN+l0-ebaWT6DjRGyHe^$B^U%)# z=f0h}*u^$na_1?}Ikfd~sJ2Ev)>`byRWmNb&U<<6&_r{hm}@VL_Nr?hLw$a<*%^XwRTZm4-+XC9ab_T-_SLB7#5HMX40i@m(?vY2F|eH&(V^?+-K7^g{8qVu6E zIaN{Q>NfH69^|Yio=s#YIg^TF1(O@IzdpHi9X>PJ`SuosUU>80?Z4r}@YmsN7C|}5 z!d!uaAGLt^6VDg+4lp+n*h&L)lYqItf{A-#z#Mt9OLcu;9=sI+KSy)$=EIWUt{^A0L?bLh5y(!upsjbmI36_e0rDEbEJeV8m`PI>u zkxBb}<&4^x-l?joi`_M*u4W~_@8aVW&Wjm$eynFy!Rnrm)vn$L9G-jT?#nGK0+$E! zV6oV3$id=J{WU~;fN4`AI6U=!k-M6A1%IdBTX^Q4%U?h9JC~0Y4weZ1^bCFF@StVn zyqZmA^J_+c4@t)TcD8PHOE?+RJnjiyTvHX97kirDYtMZ2@_GEeCvr*6kNE%E=u2YT zDn46t6xe)(wH@(PRrCvITpas4u&H(Y`p+)v`b7*_9$y>NdQ^^_ACo+q9GQ&O^4;Uq z#y-c`rm-gb`JXbi7OrJ)YX4cHi~PpYeZcqr0DOnn$F35L3%>UO-`k(9&Q5kF7hPL~ z9j;(fW6@hDm#()kKJg>Lxajpz^mUMIwm2Z0VWW5WFWF2qd^nx*rIF%YvZr~mQ|-iD z4f_94W32u}dZ+Xs#Sf&Q%aWsG}%X;n$UVUF+J9JevbY~>ArWt)j^vBl$ z^etHN?a=@D5oN?Udf`wpO~Cwsze6%&ozo^6QS#xwfP6Rx`Orars@jKK&AhY7de;9@ zMa;xC4P8@bPmQLzUc)+XV&ATSCv1D>{A`Hd+lbjI+;C%874|Z0`{k=n_e|^5SX}~p z;fC|NWW(3Hht{9hM3uX^O^DNnXuGT6qF!LIzs)BpZS#Q}D3UpAJ%9+T{>e)rB7t3Qno=8Ze< zf4#u8UG0RQdxh97*}m+#E{w$&N97 zefmk+Togz6MnPoHjZSgo+t@KKFBpy8<;E__SF&Mjh5vXql#SzZuoocgz)8Pg%u?O2r`-|pj=^xQ@%|}cJqY7A z)-}(Ln861kzOVuqKTNEvU|e{2C?{XYMz#-rD!bV!cCsPj0*A7R>D{A4z{syTn$U-( zd>T3&ZTd|vmai4rKIWJii?JVUhHuRsVP?n{oq;d-KI=yQJBnFB530ldQi~l%HcZ); zWsj7fIS!8#AD6viC;NkApuE}g^4ztNrc<^Be-E`^`UL%K!Le#w4d9!6vzOA3KfmP3 zlNaQ-(F4xE$-1;M{xp3z^KRyz+L{*pOa6QL?*wIld^?dJgI`ZOb16}sW&W)ii*XyL z@q5Kg)3&nCv|V3j+P+Zb`4I%et&stnxIaIA2m4|iA9@XZ{QRi+?DMqv9mxk;i6QAA zhQxjAZ;z$#G`kNKcswXoX8L-kG(48R)7c$dcfU`y5!4SipYgTJ(`P(#`6A+oiijx^ z|4xrI-Syan8X~3}7_o9<26;wn4NW3e06A20SuRGc7`;OAUAiwFQ1V(W|4lCLEW)!% z^a;&Z`tfu=ZY6ROYaUiRdG8wOL&Uc0`S=hqXNQ1Ae~#4ID6gz0whKD1STn_3Wr!`3 zpQHf$mg26iCzdOT?fvXY=SFwQ=0#l5N}gYdp0bH=y}Pm{RFmWzEpFIYqF6CDCFrT- z-{5b?*>B7CX`)?6U@Jxpc@{XAjpw)IZ!|#n;|;eSPl4N-Uz%s0c-XYb=O$lUk$i_d zlg!;0oaf8lZn4+ybgdt}GIB<;vuH%-Aoki>#q={8x?gia(T;DGCj0gh5AiMRSL9ff z?sOtnuJGf}v+&7$-O9>u^sjL#zF2X@<+SZ=FHj(X%P ze=cF(=>=~a#u)gf+aH6T-;bx)0BgB5I_j;_L1MjSm*}J2Y++$-4RPr;K7I20*v@(p z?_Ii=^?H;!(>L<^MGrOIn0{Pb(}`U1SG(pFl!WHUhdhE9id4c{nL2WhsLkp zKEDgT|KlOv7fvM6XE?d5JEiXt*n#yO#@5TX-=5Z>1^LIer+stTH}+1l&ApWeDlT(I zM}AyjX2r>Y7|nS10z1>8-?BBAFy67mQGx zqN#?+x|*rrZ5+I<0*6VSi?y4QTwK+tSRC9gVf|zye#IHx_$cyF9Dn?1{tGUY3(-5O zwPLOlYOF^N8fxEy{NDUA`Aj*v`TyoK4USv+zxhn&^!ZGo;u`T9@nHYs^WqQG`}1;{ z;=|-JEx;Ziyc2y5<}xY1^$pFQ+n2=xAy#6mwpoa!$oCu{B^4(1-4vvf}E=i^Py&nMT`{D@fE>$jZ~`$ef!^NY!K z{6CAFCfyHle_r5z0r%$z?ibe7@$S#7v9D~aiv1!yqUNqK*go+M)fN`S9Prx1^P_w)tdQ(Ntj-VV+#lwuue7fUbUOSk6 zXy37pe?j(pD08d>Ui^__WUBu4V`MZh_}~fjRco_T4uV^P-NjkiF9*ujmncN8C^m!h zgYsnQq1N*hJ?)EwZ4vObq3w6XE6a|s895`19^u;&?yG#Z;ymJ&??YzFgn;{HtGf)g zJ8OSjR@0S1|Iz<%!xx=p*cqf7EjzDkQCM*uMU4(}fZA_I`=>rm2VWXnFMV14hW3s^XXm27w7|!1z*c&% z<3_}bR-ZgM%XP`wg9pap3FjJ{-${So|I_u!z4H9~!FlJyeEzei8~;!|poiQM!Le}S zg5h#Wz!CI=GWovqVSGQhvkTnW4ux0o(mbh>0ll5hyKc(GF@eM(oP%MMB zpUq5mwnogX);#=Z=UNayWJhZ~FWK1v-az2Cy^B86%g3!u_BqfU>z5->RBoU^CXT*VZV&eK< zA9d$r?#9TD%7*m&CSGr2cC_xTb<$hQtsbcMj?n)?bTaNOA#U*C;exPYHfC;~#+a%O zR8%{W$Yx-F*M{4=Y)svT3uS9A-3*>9KTdoibI<(M@kA=BarkYkJ=y)J3EQT4yG`g} z{qc6pcW}I&d0{B}L_E(Rd>33jI+R?+dH6%OK5zOJ5PY`nHoV726ZGjkI?2mn4 zVH{80>0RgimCjaRJq^saVrNgEC*GDROS4~1_-ftL(3V$Oll=RV+x}Z%t=xo{tKy!Y z(_T$y8^R#fX zb2)iQ``On7ZGV;ZolTzkxLxyhdpJlw2{_nNU}!?PCBMlee9?1XQrowalhRb+HZ}sg ze!u^42K{PW^t(#GpMp=GQF37hd{CQ$!%PGl<)~!eL}&bB&zA1xt*DL7kqr4f;pdLg?#wrm)w)KWdr{RcGHTW47}FUezkcy#Y}#gGk&kDs$bFj` zt87J2KGs@+eQK8GDms&4zIIH^(O;|QSgSKwtKD}dAEv1|6p^>-ZqDr@#S zV_FU!sRZs1Gu~ANq3jmNJTZ6ur;gip&-B_oMB7?3PkzeHk8u>hbC&O)eRR^MEfs&m z`t4->inM-=v$xF5+FX_FoJJo1@-vZxT(h)@eTU`^%4ZD|tFyS&EPWln*(YgF>!cbm zs_&w;z(zTvi2E-+70*5qGK+Oz*L%hSAB;hJGyUHJCUWpOy%-vp8^gXL#sEyX#r-gG zPE;BBPnnTp>PL>9lxytSt9 zhQe9B2kb1?J6m|Cc7GyUgEqUw##xUIYw z8*A1~!=LyDJZI7Vdn>lm&epJb;oe^?U-#n;)7LdJ#wKWcRor zd8H{fE^_;#qD31+<1}`SFE^()x73>x^Lg^XhUzC1@QN66&9lIh<`tW2*2pi@-_BpE zodR!MbJ_PbXELrcXEyrds$^VuFF*d{4b{gz-8_;TSCL(ZCqk1Jzx34X>=Xav==H8H56L(rG_Re!&c>kvT9mDxITTh%k`8;{ETlt(p-mJd;k8^XMxK^9ZSak(0tWK?79TwXjO1V6SD0BMUY38oo$LR^b)OdY;8Vzdrl%-2Q+crFCNG9c z_J0?DjFbCs-_6CN*?DYC-Y;LA=9L;_`jW-w@s3bQS$YY4`R9MTq;*_oZAZwBygLrM z`}4moYxw2PwMRnX$OVo&&9hM?`wnQ|!@wdi=)9kL5&YP;lGq07H*EVl-=bqbmk%Af zoC=xFII?3e?e&_H3*Gf+mi3M^orepe*yzh~ReH4JmXSGm;KqW^-igo)WP+Ye`#+Ux z+4QNNP5bz6`P8k*5b5*KVR-keCi%%DA+MguLTE-}qdO&CWh`v@>y?5BdHdw~^X<6D zQ-cL~wPX7`M{*UVz&-g&IP1Z-X=_J*>EOBFATP?Vvju%CvhJ4So00ccojmzO%qefI zgzrM*tdGTc+sg%Qr(TWT({=1Ro#WQcQhr%~A9f4@-AQA)&hD`{Mwe%O`Cs3XbCz;e47ynY*0^|enkE5O}cyJLlX_l^UIQ|=8 zE6Z3^i^gy-3!dtCJM-529&PgcHNm#E*2DQJHBOBwe@@@GzA+CEQ}lxkpuAmh0K8~k z!S=j&u|+V>q~}vB&)4mJUsP~hl5_ociMwV$l6}%YBgj?ErL_F+( z9ohwlV^gq)QR zLF@XIyC8ie#P!NaD~NCAzghoK1AFg=pLx*1KM?G{;B0W7{qg6LGOh$GJY6+5!LRI#U)qX?Dj7OyBRQHIRNbIcI@MeDcfYEqoNj zWEB~7FH=5+Z}8dbo>sq^?D0Rr-(lqHSCPN%{uQ8yBTuJt@adX8OLm3aX+Cd13@rXU z7vD!w-cE4M)5BO}9|jGz4@T3c*3aR$#tXmhi><%txL^0cj)8Yl0sG|XWY!=}2;#nE zY#;Zhf&1|1$Khq2%ZU+aXT5!VpYGW+?3n!V7@qfk$Dk4ZUJF;|nv%7msp0iw%Uq{q zW&*j|y}}S<6kRLZLKykQb)w$&^JQyt_T=N3U_`R1#yfX@a?K(1ij~;O7T{aa-W-8; zivGCBiL=mucZMR3`_Si7e^<*|BA0~*Xsv8Qw;&Vgn)Iw7-O&5Jf4j3{mSZj$4?l9T zuN|PyWA6Qay-YO5Z@WnQbK|^j^K#{NN%Rf|%*UGnki=!xd))tZ#OBLrV? z0C&$u+6&C?M#iuHyt^ZD0l2>gnKBe!T)w|L3vP>sTRPp5(=#8*?Y6jf+wo`M!5*$nuy#BvlYwjPdAQaNp85FI!9BD7 z!sD?!%~Pw1&EH$s50`r;ekyromBH?ge%T|s z0e@SJtfK4e$-z;j=oQqZa!NiP4Q)H$)|4tNnM-{t>R}NB_!c#sj!}o{%1C9j^pa3) z*0ib7t0HId|D5ROmnCcdIWjAH^%b=>OX^JQ*a8!K?7FhpxXVYxUb}Q;?7u7;6)U;K z#70HSV;@@=?;1tiO9}nGmN=jMuG!I>9b&u+P1|d;&yS8OHQjXwre_jvoTl~IYp>G;?uE}szl#JZ1n zy@@^KBtFr_@b|IcjQW3b|9uZux?{82H{neQ>ojWG$D(htMsMZT=dF!JQ%2b}IzM|P z2M^P!-4R@$eEjfj3c``zsf!;=RhiwK!^nDi`cKftt@U-V-KvfJc7$gs^w11+-$j3O ziIo!{%YPrZr7HSh9QAp4Fpg4rcA~XEC?m!{SJy{<`0@Utp`yP(z)ww%-NE*e1Nn0i zWQ={GmC@;wQqiz#4?t7>{5s`y_Hge2bv5PZ8H@a}Abbita=y>%T z((gNrDS=J@^&jr6sJdp=`;`fD$M7vT_s7ytXndOeJ`7E5mp-pGL^pmFdxB(9e=Ri@ z@qzrD#(bLz&#_061%_U0)8*+np?w$jW!OfJAGVg(i=e=4T23S3Xz;ZAXH zjb7!Rm|N;zR$i8-o}6*Km|(%)cIly?F>T|?$x!}F8vVe|&#sqeYtj0ozcH^`wh-kT z_&IbbU4%=H;)G`?Y!1?#A#G zCl0R-@2_yr@c2n^{j_$sV3YYr?JnJ|PA{TNnV{h0quX-W#G{i;>Hy)opYL6YK z+6Os<#(zS;70h`JsYz4=HXTlc9JIcl4KflCNiFJ8nKnY51qx- z(d!ITQ@WZp37+Yr0i@Ic*E=iSKaV~?-s|)EGyQuv*?XIcKO37*+Zx*=#q;^?jq_=K*Ckd)w=&kB zR4UFr+&H!GN{SHa~K;~n)RIP z6g0LYH!H@)t|v9zYz}H}J<+|i)=P?uy|rA;JX)xYrabd`#E@zIR?z2-tk-Jhw&|Ki zZ%vmq+HtqI^J1H>xiNR`#=y1JUGsDAYkqo8JhzcK$=9SfGVx^1p^{jkI_i4MSGJrO zvsz?IU!DxYSWr&!bu8ZwCVtsFP54sYSOtFlW*YbqfVo!am&w_rsaMd~OFe~Nbgne^ zsYx5lr~D)OyPj_yX|6m{u<^0V%Mw|^y`JIB*;$fbw{Y($GU=+?X<56L_!R_~9mpPn z*KaPW&b|T6Lz8FiTHo09E82S#{`(g2iA;HA=#nHkv0mQ))5s_*SdXXfHt&C&T%oPu zNY?YiQJ+k9GTJ!G`@iJM6%V;#<|X<#z$DY z6muHRkCVTieh$$B7hO4IE?96!!Bz4%y7=YrS$A$i7S&vPu<2+$6pxpgV7g@!_HnzF zH5wn_ulTO}6w~GVZ-cZ@_-=qrONaQ50iU8@hV|98ovv9UTczYL>Ix#y#$MWqAH4q> zwk=)L{Sol`c78Liep;k4`uVFtdX!J+{8*^qyZLb=w_+0vwvoQt?B?7F>Q=_Ae3J?} z{l9h1moK1aGt>72e>oeK2b^|bAIra&Pm_ai;VCXSZ{leb+zBDwZ4P>3Pzl5-ny7=9vlHreYzeTvve8x zKsBb+<;cCji;Mk4{V3+$U@KCLxN2ORNr@2aQ}HK3by z5WgY()EaiEU1*nZa5uk&zx7;C(1z^N31pd9ZLF(c!m~m5>v7dX=yBl8A>!?X8`=lo z-!YUf7sN?^+k8swcNS-~7g`&A9QnwA7YDi<2R01x?YgdK^7l)76l^ygqTPZk9>bU3 zC;YPG8UlCy`chWrV}GoT4}Rv`_!O@sUd!GcEUu}mexsnouT62{+fMP~W@kmTjvPA4 zC6&l?PwPLw>kI3s$CS#+6Y;LCPEpZ)8)M_{tG&F)V5|G-r%TGVI%5}C*WOq3lZ_?g z>JFTjmHkSwUd6;$z=JHURo`jZ>==`8vn$Hk9Y>#ASN%)Iqj?+nvF_jJj9$E-F+9v? zUP-MT*F450nde)K;oFR%&b>Z*aS7-BaBXVw#j$TQrj?9!CA<{BPxmIS%ci6;ek(A> zpI{%Wl-%Uci+Es-yE3kGVIqywk)cLo57O9ud1$Cqk9 zSM-+MkCMg3jK8|}OGR(8CpT>zJEiWx$Gv^YvnwjETjN&!NQa!G1bC?0EP~As3!kcY z_F-Uis5M-igGI}NN_lndU=wk9qd?IBf(;2F|cUm+5|DQs#k-|O`h&_#=HVs zA?wjrNsc47yg|0(EU@@Nd-f+RfB7crzIZucmFvz^4j6UwiyDstYv1B`6EyZ) z`?q4B9i7#Cg0~RsUP~>F8s_kDVEntW%YF+xt?by=ex0+|`{Va$8nhZ3IN}TF1Dw%w z1lf*w0%AKu(B=YcZSJ0S(Z?a^qW6AI{vqe7>ETJ833p-{QH=*zpMby+=HOXPSOL zqPpq+Nu5oX7h4%~I9}-c=2kp>NlogW`J72X-uOkPdu0`H3oO-NNbW80T1Wnn;9GMH zL;sTT23zk=vWMlx@F~Siiyx&f0#9crzzyn*^G=*+8=1Qwx08N&S!V}wXzIcN`DitA zZ2iN@-PA8QYJdr|D<=z7)$VoF*|4*ZX=-GCyX>IO4*U0xDB)jGF{y#>Z z)m$^a+zLOpbEVJxYxy$kVE*Zyo%p~^WG33vUP+(Imyt}|baM|}IIo}fsqVOc-JCVd z{oHlssObH{eS0!GJl^K(NY+*V((6*wOm}eHdN*#o{qBFBYuf+df9X5!xzJW@2-xDw zh!Za3{IRm3`W|ZE-^js*g(2i1yQaiND-NU|wsP0&L$&>NAoAPyLY!sgT=%~NuinSW*9IR!;?B2j9!)H664nAFc$f53!<<%BP*rK0Z3z>*sv(=|VrQ1>j9M>G0p@ z9dYqJ@H`_Lxb?Hg9dN{6V?Bq`6@1>3s>`#bgbZ;K@~!1;DTfDaDWWIohfSyapiST4 z1D$NTGqgKhHL==-7B($1otD?(6KZb#@{*qqoO`&0bwqdYYzDp`(c&0$uzV5Tr0<|^ zKNNqMtK19dQvDv&Df^RtU;hKsxt!lwey{j8WBNN|V>MU6$LyH--;}$*@-cq@zux_o z{(b)2F)+URALMj5eyc9MuKR1u_)nqPsR~QW?X&5Ns5u0UixB5)Yl1<;QkNL)4A?S| zc_PR>3z2hHU8ou%ZewCUF*{DANOkc2K8BjJzi$3B8plv$alsK{Yx?U5rzQ;80#odx z+R=XJx2HbcC*|w(;w;0oBR#5;kF8k(FG^CIQ|-fFPAnM7UXsgpl=XJN0bNJ_KVg;; zw+5Yd!2|c3=&W6`s@8Tc|{(H0dyd~ns@Z9evG^6Y+mqUpt- zocQ7cKXyhJ?Hy6nnEl?p74q3BW-ZM91ox}CkK9<~esg5CoqGrEZgi&f7MwA&I?&!U zo?poGi+Eo9Hn$d9M2uk=UCF(5VUe@uOv_8_g>$r#K;L`H${^HpU_BQCU+y=PoeQ}B zw11s7vDcYPn(No=`s1uUzwKCyxNEL>&g{Gj}DXM*TSG=a9vRf~gcX?DWh`XG2-06F=+@|GW<2wGU@Q zzL$fe55$Wh;7l;Jr%*IXlJLWEZ2K>6+-lJJ2@4WHoRYL0>E?*${mf-NCjqgRzw1 z``b8iN;9=({WdLqpxp-AJ3wq0@te^qJ~mb}!THGYnIsm+RFkR~x#Oh>XPd;+(ceDx zm?L*|l_~&R0WhWYH9sIb?;Ce{5kPNN5hsq(H$Q<7dK@Ws~ z%=LH#JKVVF%s$ne;F@#=fBw(hIeCZH>d2~wb-yES;m;naE>g}IftB8vSzm8m&X}@m zh;`X~d+aK`Uk$&jWL=139dd27CflFhZr7yYW65>TypNxRn2||Oo3{6`xxP|n*6hN@ zaOyfyv-;_Qbvj)v=1_2(W=~_pFf>DBg795mW;%nkv7YBdAI011_t;p*e!2N{FyAlZ zegpWB-5EUb<)ZqT^o@V{Vfqfri5lmv;EC^_@NJdq|1kR~o~b_lbJx|5|E%}hlSeIV z=4g98{RaCG&J71Y)&>uc^Xv3E@>kH`P_BItyF&mVs^=apy<&VfW$>#ulZjqA3+ zd__wm)S(tV6@PLLOv{R{dUN(_Kd)oHse3r11zEsB7N|srQyuHnBI$SjUV7jDdc4lr z-9zoiP%!SpvDUzc@%;VQo@>wQfp6i%D#i06C#2v*nv>Q`^LF8fN2rf6ot#8$Yxl20 zw`$n<%#OL_!yZBI=X-`J-SVK`Nl}B={S~Y zq3i&9CjxI=0M8eHR9>mf%Kh1jr`NCU`Z*uJ?}80AI-`tQ9N3MC0k3vS zi~O;8we%PZ_D8#B^wor)K=p3IjLX6#*Mj3y4clqwo3_P#6oXOMc4y7XFW*^nH+9?Y z(i)T(*t+h9IEK2CkgZ!U|9@R>o#u_G?fQZ~JU_p@(AI4?iR+1%;QYAaf#<7<7my6A zoMPmk6CwDhcnarForuR*Mr*s^hdN&uKfGN>#;kE>p)o0wJW*X@UU;2#tNhQ)u_5Mk zeQdSF8k8=l-OE^u6zyuB)4)Bg-=jI1XgYJdl#k|GfA5_&Et(^9(>~wL=ZlR0Kk}($ zyv>CpV*3k6SsSZlQ^sv=^ok*sZ_&zFYKJlT<0B?xNj0@Tc=m-V##fh%4bVFI<6F%5 zKFqv_qrEm~Xq<9}B*zrMw+GHlP)rx`xP2(~+pZ;tybpYq-?&wB4)LR#TaukEW1p!I z&G6#|Te%kAXr?^z6!q|bVWxnyOaALPGwUxylNU>`4bm4sPSLh!Z7I+kWSbOu{fFj} z4|XSU?3+R}g_B9-cjXAJX6<#a^%7_Uv>;4v!z$voY#Zpiwtd>jZ!>e^|K{)y{DXN5ct`$i1_pR z$ydqtt6Ixdn_4U86ok;ZCYSmA(axWmYyJHyADn(WBIJWJmYnWyb<*~CPG0c2+#2<- z&Dvz=`vsHtB#1Hb$CTM%-?KSCt-1R??s107!Rn^u1Fw^7ihaW7^XvW1oX_{k_3J($ zr+@Zw+lO-c>Elpnh@5`<6TB|p|EZ&YLr(uB@^fN-PX9Tza}sZk`lMi>b?gE3ljH;1 zr`HSCKDLNGT707JMZ0%7_?a7CbhK(l_CbHo#6!ROtCO=1Yk%)wP+frR+Yej7fCpO> zbFejW%Fcpt;~8&HoU#jf_KV~@rGYU+ez(Tg%bXSG@MLHX53$cNrSe)THyt5t*M56-XNfUNf+@~dr5^-SaC zR8Po!+U8W3Wj=i?wxwI{#CF5kwM}b@WiA@9)Aak>nO86PXPl|grhAaJ?lax)iPVct zTTJKc%;O08*jvcQhK7`>&JQ}hmyg|IY|f}JV;&Hm;Kvu7S-N<2v22O`xzXYo|K|DE zj0Jn)DSO)k55=|$4l-u^9(E4kj_jz|82@$it{Fpr-Zi-NZ<2RSKZVehcIKnK(7b|j zq>qczgDrm{{@mQ<=`^9ONt-u@OhPTkf>=`Zq?6cdsX^Y4Bgl<8d}WWlKDcLnDgJeR z`~ADf3bJ3yPU+ipy|$1?SpTFaN88v9e{70h6K%+Yq0{Dlk0bB9DO7%RsJw5x*8#gJ zzB=+0i8qHwh#wt=M=IwqOdSAXnjcTGhJOAxIy!k{xx7N2SN@^iSM31B2xtw{KO_I! zF&FAR{rA5WBjeA(-vj=atA4!3= z4ZnixC7#VFxSpQAJrEz%A^2xa1S9Q?+sjiveXfAo@XxBUdQqSa`DQh5$yYWOEVmBL zQ6rn5^<8-~is#ey*Zw)y9y`-P>_p^x-+H`_Tl;s+HF01w9Q-I$$P-q(k<}WMex~$T%UG!ayRy#=gu`n&&A1GZYGy` zp~G**#qi(8hVw1o2zK0u!kJ8`JNlUim8+1mzac9n`=*}Fz5x@5u)(SC6ma9u(U1M~ z&$$~mkD2uxl*i2a2lJTM^Iy5lipg}DgX|H?VeUa*(SF)IW_XMCQTNmE7(W)%&t>ii z5K3?AQk9E+?q{;O=Y2zvo6G;Qn;{ zeMjd$w01FP-7bIz$hMGHKJzWPeC7q{Ho<)61+p6rpU)h`>5jVOm9cBgnt9mdT8Hza zhLYXo*kd%VL@2XXIJpa3U2B!OGWH8+&5F_FX|FxAEOqszk^{DTHl_ew$XzdH9k7jI zCpgtt>%XJ-6}OlPA)DmMZv!}w-=4Pe?+0_y#eeeYLj66-?hJE@Q+G3tUat4?*L3Tw zjdo=9;Q#0FsRsXF%71@;YQvX>^gDHr>HaUs67^dG?UO$le7~JMh~WR{u+IelUn)CO zZk0f1kEftj#0(W)8dbP5V3b9oSRfFWG?HNX@N@-=*$U3I5od`DFP0Q9r%Zu^R59 zHrJ_LK4;PPNBz7-+rxeGY3FaL{h)IhgL&_gN5nTaqvs^9ba#aRI@!4!zeY3bBtKJx zb&TVC+Qd4|XT2-k!dN|anilp#XSf(_B8o=}gE#I%XdSuon_Hmou<3hpLF`8*X z@aRjj`#og;2zymjQWGW4W83ZUoMO&Smz~nu0Sm%Khpt`KC3~~}=kE!2Q-E~~5g z0`G2$(8l(v*oq`)h?~L~xhCDs%10?Pw!U>U?T8M2Q*BHmhHwt>gH6$%Q+}T5Zk>s2 zNdF!1RTG~cy$qOnaHi?Bu^RDnZQazJ{6B-Su=kw{up_01uoc<0btW%%D_^!XTkSK* z7s^HU?Ma?L!L!M?o|EkBMMi7B5dSqk=q%%Fo+drYvps2Ctrwb3t-sEhRGrqj*gszV z&B>)FL*tL8c;2u3=kK}BRt=u3dOBZK3w!U?QucC$WsA83+A|dV3GPl(3ogjt+yQ#` zuTV$MgV|oIt9W(fK9Bz3&uciFLoeeEjKj*WqPvH=?%VX`x4F8=JboA(zt&c=vv6AV z)((K{+P4Eg8B@)*em#4VezV8&WA=iplFNehAnu~;72t0OWjZIUOB~nz-jI>b(%;6* z;NrVa%ckPT|0^HJ`>k3pzP-=C_R6)qzm0Pbx1D2ojrgZOf5o;OsA8V0vyPQkp zOddE#Rk3>e6c4iPi})kRbH^s`^OX2x;)8UbbASHnVBv0uF;-H8?$z7n=Tc6V2ao%( zaqPq9>i3zyu0a`LE9)DatA$zCH2)ph2%5APIhvGzU%KsIaz7vDa(Zw z-2YXdNp`RLjOkvqzdye6MCGJaHh-bM&UD9l&X+TYW9hC66Q41Jtfn>f?G>No{)$VI z-7AO%dbKFo-3b4z=j^C3w7YV0Wwibr(_K$H;-~p>pZRB2-N3laB;sSV&%l!@nHeoo zZWL!zrI3$C6H}voLPk1~)R|VfXWMhAi1ylA+!RFly9gy978nXN|yc-`qDSH9-^*!)&!QAy_*KF7&eHy)2w4ieI zl&tGaS}eK|%H^3hg(f$qEZz3t;qO+M&W=_12B>-G!;SyF3%$nwE&I0F=+Ui?>#*;V z<8wp@1v6As2CyDNb{(duZjmNiRg=U6qJ9C#CYzu>7CJ0l~ zwes())?xlxIi8Je;5>KDH7Wb*Y|a~lKA2A>(Kq>?@7Y)$LyonHN zro6zw7_eua3PZvh!Lsbg=&$A4L!WlI@TGm_=m*s9m9IxQlLS|Me&b>*^FP8Bf-g>4 z$9>?+cCPzvO3$0cnzcTR%uG$7!1!!ig6USCUqD~efi1n~pP?u@*@IP2HjR%pvrLs2 z51q4TZ~sSpNAlTF^TqmXGA;MmI=GpZyFc~iC4*%#`LmPAbFz^oh%rA!o*QExPa@AH z3NoGLIe9KYy+8K7pMHw2eweZDqkgx1z(>HT%@32yhHa<8WY#uNBY1O*>0Ad*i_JA_ z6~peLcg1R#+4wBCspP8gTvN8Ws;pXm2Y2$mgB=B#W37u!-_8e3=U1;beP6l6*f@Eg zZ)rU7Ijf@a|1LU{^T`9N(y!3zmhPng{B;K(%BIo&iy`;I*85ot;##a;hK@81+PL!} z&nG2X=J%6%5S{9N>%X{8x)kuUwD~Io|8amU)$X8hoZfr3zqic z^Vqv-ApUMR`WxIUf874}PXGuhn%g0c3WvT!&w`xGc)~Xp=YX~Tg3SK{u!TN<-@~A z;SnE3&a`q4`hx6Vim^G)-U%;shK%ITK>x$aIGdTfN23Sp#vRykZbPnd!Tr}I*W~eA z(dP}k>(8}E&*nWN+Pj8le3~lVUu*mB2#==r=4fh9j;8iNQ-A#tX)1iWpQcV6hNg;6 zo&;8gqKS2q({eQN2sE)&;|o}hTTHFK7wT5D4iUa5cUk=;Y08htv7;EJq zJv*oSKTCW7{rLNnKkgtbM%3SFbmtE%n+_*i2o{5DD*fj`$l6Zrxe?g&tX?!5ed0L! zgs(RtPtJTDJ>v7!MSpaO=a0X>Y}p0}-6%OaoRtm)UFp1t_`=%J73e|XJUwVqV+z>y z=~(Au=s}UXq4l6=lb!qFnf^Kq(uE%ObfG7JabNE6znh>hwZPDkf{HzwllIQfO;8QS z;Jg#eub25X$^Qc1)Seh*o+Rz^+;;u<D!3;eo%haYP=7t@ z7n-YFI?+X+=5TNLzL4R9&&uzOtlFpg3aul04|Y8A-;cTG$z!p&SMy741;-$sBb%~d zqA6n)mzpmD=(5 zT>E5mAvNC|Khqu$mB*JqR*g~TDkXFKlY-7?_-$!Du>iu06n$&_YSUjo8xP9fC@1T= zTwHDBES+lktBtGjG2DDj(qtZXB;CE{b-mh$R-zmIbmz8dN%TcMh9P+ zpLZVIoBld1K0j0bA#;?Cc=LG~o708BymSj|B020RbLQ;2J=9-ZBiXu{e4UnylUXYlMyL=pZ4nJOS-IV9{D84jPm|QJ*#|Cvm-^93_C?s!c z1pVrNn9oSAeT>ifhT5@D#iOc=RvNiES~b>vDG3uw7_36Lw z#|^R{isgTUH6Q9;xlzpkjm4AS`kztl7F}acW!L&JG($L0zooExI9yynoa{pK(;CQ6 z3*#3{5RX<(PFZ4dYf%~;Uf4eN%Ib{;SIyl$dQB^|xR;zsU6(ArynXDNEnExk|B

    mToN2G;_Q9zfoOf#Lji2km|0>|0bK?8Z)3&9s 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

    +8|`Fe}5he^`<0O^nL5??QI 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 + + + + Debug + iPhoneSimulator + Exe + {3F082D0B-A964-43D7-BDF7-C256D76A50D0} + osu.iOS + osu.iOS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} + osu.Game + + + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58} + osu.Game.Resources + + + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3} + osu.Game.Rulesets.Catch + + + {48F4582B-7687-4621-9CBE-5C24197CB536} + osu.Game.Rulesets.Mania + + + {C92A607B-1FDD-4954-9F92-03FF547D9080} + osu.Game.Rulesets.Osu + + + {F167E17A-7DE6-4AF5-B920-A5112296C695} + osu.Game.Rulesets.Taiko + + + + + + + + + \ No newline at end of file diff --git a/osu.sln b/osu.sln index bf1b6d60e1..0737a9fbd8 100644 --- a/osu.sln +++ b/osu.sln @@ -3,34 +3,38 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27004.2006 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game", "osu.Game\osu.Game.csproj", "{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game", "osu.Game\osu.Game.csproj", "{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Resources", "osu-resources\osu.Game.Resources\osu.Game.Resources.csproj", "{D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Osu", "osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj", "{C92A607B-1FDD-4954-9F92-03FF547D9080}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Osu", "osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj", "{C92A607B-1FDD-4954-9F92-03FF547D9080}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Catch", "osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj", "{58F6C80C-1253-4A0E-A465-B8C85EBEADF3}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Catch", "osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj", "{58F6C80C-1253-4A0E-A465-B8C85EBEADF3}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Taiko", "osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj", "{F167E17A-7DE6-4AF5-B920-A5112296C695}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Taiko", "osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj", "{F167E17A-7DE6-4AF5-B920-A5112296C695}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Mania", "osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj", "{48F4582B-7687-4621-9CBE-5C24197CB536}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Mania", "osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj", "{48F4582B-7687-4621-9CBE-5C24197CB536}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Tests", "osu.Game.Tests\osu.Game.Tests.csproj", "{54377672-20B1-40AF-8087-5CF73BF3953A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Desktop", "osu.Desktop\osu.Desktop.csproj", "{419659FD-72EA-4678-9EB8-B22A746CED70}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Desktop", "osu.Desktop\osu.Desktop.csproj", "{419659FD-72EA-4678-9EB8-B22A746CED70}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Tests", "osu.Game.Tests\osu.Game.Tests.csproj", "{28F040EA-536D-442B-B0CA-004075182EB6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Catch.Tests", "osu.Game.Rulesets.Catch.Tests\osu.Game.Rulesets.Catch.Tests.csproj", "{3AD63355-D6B1-4365-8D31-5652C989BEF1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Catch.Tests", "osu.Game.Rulesets.Catch.Tests\osu.Game.Rulesets.Catch.Tests.csproj", "{608C7588-9CAE-4443-A431-96FC9DE72A25}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Mania.Tests", "osu.Game.Rulesets.Mania.Tests\osu.Game.Rulesets.Mania.Tests.csproj", "{7E9E9C34-B204-406B-82E2-E01E900699CD}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Mania.Tests", "osu.Game.Rulesets.Mania.Tests\osu.Game.Rulesets.Mania.Tests.csproj", "{BE8F48E4-7EEE-4C13-B8ED-7705BD9EE443}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Taiko.Tests", "osu.Game.Rulesets.Taiko.Tests\osu.Game.Rulesets.Taiko.Tests.csproj", "{B698561F-FB28-46B1-857E-3CA7B92F9D70}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Taiko.Tests", "osu.Game.Rulesets.Taiko.Tests\osu.Game.Rulesets.Taiko.Tests.csproj", "{4F0BF44A-7ECA-49A6-B0BB-B676657D9896}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Osu.Tests", "osu.Game.Rulesets.Osu.Tests\osu.Game.Rulesets.Osu.Tests.csproj", "{6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Osu.Tests", "osu.Game.Rulesets.Osu.Tests\osu.Game.Rulesets.Osu.Tests.csproj", "{DECCCC75-67AD-4C3D-BB84-FD0E01323511}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU + Debug|iPhoneSimulator = Debug|iPhoneSimulator + Release|iPhone = Release|iPhone + Release|iPhoneSimulator = Release|iPhoneSimulator + Debug|iPhone = Debug|iPhone EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -57,30 +61,30 @@ Global {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.Build.0 = Debug|Any CPU {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.ActiveCfg = Release|Any CPU {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.Build.0 = Release|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|Any CPU.Build.0 = Release|Any CPU {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|Any CPU.Build.0 = Debug|Any CPU {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|Any CPU.ActiveCfg = Release|Any CPU {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|Any CPU.Build.0 = Release|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|Any CPU.Build.0 = Release|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|Any CPU.Build.0 = Release|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|Any CPU.Build.0 = Release|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|Any CPU.Build.0 = Release|Any CPU + {28F040EA-536D-442B-B0CA-004075182EB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28F040EA-536D-442B-B0CA-004075182EB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28F040EA-536D-442B-B0CA-004075182EB6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28F040EA-536D-442B-B0CA-004075182EB6}.Release|Any CPU.Build.0 = Release|Any CPU + {608C7588-9CAE-4443-A431-96FC9DE72A25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {608C7588-9CAE-4443-A431-96FC9DE72A25}.Debug|Any CPU.Build.0 = Debug|Any CPU + {608C7588-9CAE-4443-A431-96FC9DE72A25}.Release|Any CPU.ActiveCfg = Release|Any CPU + {608C7588-9CAE-4443-A431-96FC9DE72A25}.Release|Any CPU.Build.0 = Release|Any CPU + {BE8F48E4-7EEE-4C13-B8ED-7705BD9EE443}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE8F48E4-7EEE-4C13-B8ED-7705BD9EE443}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE8F48E4-7EEE-4C13-B8ED-7705BD9EE443}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE8F48E4-7EEE-4C13-B8ED-7705BD9EE443}.Release|Any CPU.Build.0 = Release|Any CPU + {4F0BF44A-7ECA-49A6-B0BB-B676657D9896}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F0BF44A-7ECA-49A6-B0BB-B676657D9896}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F0BF44A-7ECA-49A6-B0BB-B676657D9896}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F0BF44A-7ECA-49A6-B0BB-B676657D9896}.Release|Any CPU.Build.0 = Release|Any CPU + {DECCCC75-67AD-4C3D-BB84-FD0E01323511}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DECCCC75-67AD-4C3D-BB84-FD0E01323511}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DECCCC75-67AD-4C3D-BB84-FD0E01323511}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DECCCC75-67AD-4C3D-BB84-FD0E01323511}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -92,29 +96,10 @@ Global Policies = $0 $0.TextStylePolicy = $1 $1.EolMarker = Windows - $1.inheritsSet = VisualStudio - $1.inheritsScope = text/plain $1.scope = text/x-csharp + $1.FileWidth = 80 + $1.TabsToSpaces = True $0.CSharpFormattingPolicy = $2 - $2.IndentSwitchSection = True - $2.NewLinesForBracesInProperties = True - $2.NewLinesForBracesInAccessors = True - $2.NewLinesForBracesInAnonymousMethods = True - $2.NewLinesForBracesInControlBlocks = True - $2.NewLinesForBracesInAnonymousTypes = True - $2.NewLinesForBracesInObjectCollectionArrayInitializers = True - $2.NewLinesForBracesInLambdaExpressionBody = True - $2.NewLineForElse = True - $2.NewLineForCatch = True - $2.NewLineForFinally = True - $2.NewLineForMembersInObjectInit = True - $2.NewLineForMembersInAnonymousTypes = True - $2.NewLineForClausesInQuery = True - $2.SpacingAfterMethodDeclarationName = False - $2.SpaceAfterMethodCallName = False - $2.SpaceBeforeOpenSquareBracket = False - $2.inheritsSet = Mono - $2.inheritsScope = text/x-csharp $2.scope = text/x-csharp EndGlobalSection EndGlobal From 8c1f8ee5963ee305f23e7318a8d1d5e5cddf7dd2 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 31 Jan 2019 12:24:47 +0100 Subject: [PATCH 024/623] restart correctly when leaving player (without noises) --- osu.Game/Screens/Play/Player.cs | 3 +++ osu.Game/Screens/Select/SongSelect.cs | 11 +++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 71b7b77e5d..eccb93fb8e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -385,6 +385,9 @@ namespace osu.Game.Screens.Play if ((!AllowPause || HasFailed || !ValidForResume || pauseContainer?.IsPaused != false || RulesetContainer?.HasReplayLoaded != false) && (!pauseContainer?.IsResuming ?? true)) { + // Detaching so seeking in SongSelect while we are fading out doesn't cause noises because we are trying to stay in sync + adjustableClock.ChangeSource(new StopwatchClock()); + // In the case of replays, we may have changed the playback rate. applyRateFromMods(); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 3d232d9d73..e2a3ec691c 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -411,7 +411,7 @@ namespace osu.Game.Screens.Select } if (this.IsCurrentScreen()) - ensurePlayingSelected(true); + ensurePlayingSelected(); UpdateBeatmap(Beatmap.Value); } @@ -485,7 +485,7 @@ namespace osu.Game.Screens.Select if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) { UpdateBeatmap(Beatmap.Value); - ensurePlayingSelected(); + ensurePlayingSelected(true); } base.OnResuming(last); @@ -575,17 +575,16 @@ namespace osu.Game.Screens.Select beatmap.Track.Looping = true; } - private void ensurePlayingSelected(bool preview = false) + private void ensurePlayingSelected(bool restart = false) { Track track = Beatmap.Value.Track; - if (!track.IsRunning) + if (!track.IsRunning || restart) { // Ensure the track is added to the TrackManager, since it is removed after the player finishes the map. // Using AddItemToList rather than AddItem so that it doesn't attempt to register adjustment dependencies more than once. Game.Audio.Track.AddItemToList(track); - if (preview) - track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; + track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; track.Restart(); } } From 484b5dedbc4f7aa084358da048f5e33917ac532e Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 31 Jan 2019 13:36:02 +0100 Subject: [PATCH 025/623] fix merge mistakes --- osu-resources | 1 - .../Application.cs~HEAD | 15 --------------- .../Application.cs~master | 15 --------------- osu.sln | 4 ---- 4 files changed, 35 deletions(-) delete mode 160000 osu-resources delete mode 100644 osu.Game.Rulesets.Catch.Tests.iOS/Application.cs~HEAD delete mode 100644 osu.Game.Rulesets.Catch.Tests.iOS/Application.cs~master diff --git a/osu-resources b/osu-resources deleted file mode 160000 index 677897728f..0000000000 --- a/osu-resources +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 677897728f4332fa1200e0280ca02c4b987c6c47 diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs~HEAD b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs~HEAD deleted file mode 100644 index 44817c1304..0000000000 --- a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs~HEAD +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using UIKit; - -namespace osu.Game.Rulesets.Catch.Tests.iOS -{ - public class Application - { - public static void Main(string[] args) - { - UIApplication.Main(args, null, "AppDelegate"); - } - } -} diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs~master b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs~master deleted file mode 100644 index 44817c1304..0000000000 --- a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs~master +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using UIKit; - -namespace osu.Game.Rulesets.Catch.Tests.iOS -{ - public class Application - { - public static void Main(string[] args) - { - UIApplication.Main(args, null, "AppDelegate"); - } - } -} diff --git a/osu.sln b/osu.sln index 7a418d41d4..3c38309d86 100644 --- a/osu.sln +++ b/osu.sln @@ -29,10 +29,6 @@ Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU - Debug|iPhoneSimulator = Debug|iPhoneSimulator - Release|iPhone = Release|iPhone - Release|iPhoneSimulator = Release|iPhoneSimulator - Debug|iPhone = Debug|iPhone EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU From f25c61bee487035b16b3dc3d8e035d8199849a72 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 31 Jan 2019 13:50:31 +0100 Subject: [PATCH 026/623] remove blank line --- osu.iOS/Application.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.iOS/Application.cs b/osu.iOS/Application.cs index 8a5cfcdbe8..cb75e5c159 100644 --- a/osu.iOS/Application.cs +++ b/osu.iOS/Application.cs @@ -13,4 +13,3 @@ namespace osu.iOS } } } - From 559960036e61edf1caefb5670fe9c939dad0ef59 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Tue, 5 Feb 2019 14:48:35 +0300 Subject: [PATCH 027/623] update top score design --- .../Graphics/Containers/OsuHoverContainer.cs | 10 +- .../BeatmapSet/Scores/ClickableUsername.cs | 15 +- .../BeatmapSet/Scores/DrawableScore.cs | 16 +- .../BeatmapSet/Scores/DrawableTopScore.cs | 422 ++++++++++++------ .../BeatmapSet/Scores/ScoresContainer.cs | 7 + 5 files changed, 328 insertions(+), 142 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs index 276b0f9dd1..091499b7cd 100644 --- a/osu.Game/Graphics/Containers/OsuHoverContainer.cs +++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs @@ -1,12 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osuTK.Graphics; +using System.Collections.Generic; namespace osu.Game.Graphics.Containers { @@ -16,17 +16,19 @@ namespace osu.Game.Graphics.Containers protected Color4 IdleColour = Color4.White; + protected const float FADE_DURATION = 500; + protected virtual IEnumerable EffectTargets => new[] { Content }; protected override bool OnHover(HoverEvent e) { - EffectTargets.ForEach(d => d.FadeColour(HoverColour, 500, Easing.OutQuint)); + EffectTargets.ForEach(d => d.FadeColour(HoverColour, FADE_DURATION, Easing.OutQuint)); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - EffectTargets.ForEach(d => d.FadeColour(IdleColour, 500, Easing.OutQuint)); + EffectTargets.ForEach(d => d.FadeColour(IdleColour, FADE_DURATION, Easing.OutQuint)); base.OnHoverLost(e); } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs b/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs index 0eb8b325d3..e2aade986d 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs @@ -3,16 +3,20 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; +using System.Collections.Generic; namespace osu.Game.Overlays.BeatmapSet.Scores { public class ClickableUsername : OsuHoverContainer { - private readonly OsuSpriteText text; + private readonly SpriteText text; + protected override IEnumerable EffectTargets => new[] { text }; + private UserProfileOverlay profile; private User user; @@ -24,7 +28,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (user == value) return; user = value; - text.Text = user.Username; + OnUserUpdate(user); } } @@ -41,12 +45,17 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public ClickableUsername() { AutoSizeAxes = Axes.Both; - Child = text = new OsuSpriteText + Child = text = new SpriteText { Font = @"Exo2.0-BoldItalic", }; } + protected virtual void OnUserUpdate(User user) + { + text.Text = user.Username; + } + [BackgroundDependencyLoader(true)] private void load(UserProfileOverlay profile) { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index 1f50385adc..609524f170 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,6 +15,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Users; +using osuTK; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -56,13 +56,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Size = new Vector2(30, 20), Margin = new MarginPadding { Left = 60 } }, - new ClickableUsername - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - User = score.User, - Margin = new MarginPadding { Left = 100 } - }, + //new ClickableUsername + //{ + // Anchor = Anchor.CentreLeft, + // Origin = Anchor.CentreLeft, + // User = score.User, + // Margin = new MarginPadding { Left = 100 } + //}, modsContainer = new ScoreModsContainer { Anchor = Anchor.CentreLeft, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index c9551cf6f8..2e2bb78634 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -1,13 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; -using osuTK.Graphics; +using Humanizer; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -19,29 +19,38 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Users; +using osuTK; +using osuTK.Graphics; +using System.Collections.Generic; namespace osu.Game.Overlays.BeatmapSet.Scores { public class DrawableTopScore : Container { private const float fade_duration = 100; - private const float height = 200; + private const float height = 100; private const float avatar_size = 80; private const float margin = 10; private readonly Box background; - private readonly Box bottomBackground; - private readonly Box middleLine; private readonly UpdateableAvatar avatar; private readonly DrawableFlag flag; private readonly ClickableUsername username; - private readonly OsuSpriteText rankText; - private readonly OsuSpriteText date; + private readonly SpriteText rankText; + private readonly SpriteText date; private readonly DrawableRank rank; - private readonly InfoColumn totalScore; - private readonly InfoColumn accuracy; - private readonly InfoColumn statistics; - private readonly ScoreModsContainer modsContainer; + + private readonly AutoSizeInfoColumn totalScore; + private readonly MediumInfoColumn accuracy; + private readonly MediumInfoColumn maxCombo; + + private readonly SmallInfoColumn hitGreat; + private readonly SmallInfoColumn hitGood; + private readonly SmallInfoColumn hitMeh; + private readonly SmallInfoColumn hitMiss; + private readonly SmallInfoColumn pp; + + private readonly ModsInfoColumn modsInfo; private APIScoreInfo score; public APIScoreInfo Score @@ -54,153 +63,296 @@ namespace osu.Game.Overlays.BeatmapSet.Scores avatar.User = username.User = score.User; flag.Country = score.User.Country; - date.Text = $@"achieved {score.Date:MMM d, yyyy}"; + date.Text = $@"achieved {score.Date.Humanize()}"; rank.UpdateRank(score.Rank); totalScore.Value = $@"{score.TotalScore:N0}"; accuracy.Value = $@"{score.Accuracy:P2}"; - statistics.Value = $"{score.Statistics[HitResult.Great]}/{score.Statistics[HitResult.Good]}/{score.Statistics[HitResult.Meh]}"; + maxCombo.Value = $@"{score.MaxCombo:N0}x"; - modsContainer.Clear(); - foreach (Mod mod in score.Mods) - modsContainer.Add(new ModIcon(mod) - { - AutoSizeAxes = Axes.Both, - Scale = new Vector2(0.45f), - }); + hitGreat.Value = $"{score.Statistics[HitResult.Great]}"; + hitGood.Value = $"{score.Statistics[HitResult.Good]}"; + hitMeh.Value = $"{score.Statistics[HitResult.Meh]}"; + hitMiss.Value = $"{score.Statistics[HitResult.Miss]}"; + pp.Value = $@"{score.PP:N0}"; + + modsInfo.ClearMods(); + modsInfo.Mods = score.Mods; } } public DrawableTopScore() { RelativeSizeAxes = Axes.X; - Height = height; - CornerRadius = 5; - BorderThickness = 4; + AutoSizeAxes = Axes.Y; + CornerRadius = 3; Masking = true; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.2f), + Radius = 1, + Offset = new Vector2(0, 1), + }; Children = new Drawable[] { background = new Box { RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, //used for correct border representation - }, - avatar = new UpdateableAvatar - { - Size = new Vector2(avatar_size), - Masking = true, - CornerRadius = 5, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Offset = new Vector2(0, 2), - Radius = 1, - }, - Margin = new MarginPadding { Top = margin, Left = margin } - }, - flag = new DrawableFlag - { - Size = new Vector2(30, 20), - Position = new Vector2(margin * 2 + avatar_size, height / 4), - }, - username = new ClickableUsername - { - Origin = Anchor.BottomLeft, - TextSize = 30, - Position = new Vector2(margin * 2 + avatar_size, height / 4), - Margin = new MarginPadding { Bottom = 4 } - }, - rankText = new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.BottomRight, - Text = "#1", - TextSize = 40, - Font = @"Exo2.0-BoldItalic", - Y = height / 4, - Margin = new MarginPadding { Right = margin } - }, - date = new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Y = height / 4, - Margin = new MarginPadding { Right = margin } + Colour = Color4.White, }, new Container { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(margin), Children = new Drawable[] { - bottomBackground = new Box { RelativeSizeAxes = Axes.Both }, - middleLine = new Box + new FillFlowContainer { - RelativeSizeAxes = Axes.X, - Height = 1, - }, - rank = new DrawableRank(ScoreRank.F) - { - Origin = Anchor.BottomLeft, - Size = new Vector2(avatar_size, 40), - FillMode = FillMode.Fit, - Y = height / 4, - Margin = new MarginPadding { Left = margin } - }, - new FillFlowContainer - { - Origin = Anchor.BottomLeft, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - Position = new Vector2(height / 2, height / 4), Direction = FillDirection.Horizontal, - Spacing = new Vector2(15, 0), - Children = new[] + Spacing = new Vector2(margin, 0), + Children = new Drawable[] { - totalScore = new InfoColumn("Score"), - accuracy = new InfoColumn("Accuracy"), - statistics = new InfoColumn("300/100/50"), - }, + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 2), + Children = new Drawable[] + { + rankText = new SpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Text = "#1", + TextSize = 20, + Font = @"Exo2.0-BoldItalic", + }, + rank = new DrawableRank(ScoreRank.F) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(30), + FillMode = FillMode.Fit, + }, + } + }, + avatar = new UpdateableAvatar + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(avatar_size), + Masking = true, + CornerRadius = 5, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.25f), + Offset = new Vector2(0, 2), + Radius = 1, + }, + }, + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 3), + Children = new Drawable[] + { + username = new ClickableTopScoreUsername + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + TextSize = 20, + }, + date = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + TextSize = 10, + }, + flag = new DrawableFlag + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(20, 13), + }, + } + } + } }, - modsContainer = new ScoreModsContainer + new Container { - AutoSizeAxes = Axes.Y, - Width = 80, - Position = new Vector2(height / 2, height / 4), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(margin, 0), + Children = new Drawable[] + { + totalScore = new AutoSizeInfoColumn("Total Score"), + accuracy = new MediumInfoColumn("Accuracy"), + maxCombo = new MediumInfoColumn("Max Combo"), + hitGreat = new SmallInfoColumn("300", 20), + hitGood = new SmallInfoColumn("100", 20), + hitMeh = new SmallInfoColumn("50", 20), + hitMiss = new SmallInfoColumn("miss", 20), + pp = new SmallInfoColumn("pp", 20), + modsInfo = new ModsInfoColumn("mods"), + } + } } } - }, + } }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - background.Colour = bottomBackground.Colour = colours.Gray4; - middleLine.Colour = colours.Gray2; - date.Colour = colours.Gray9; - BorderColour = rankText.Colour = colours.Yellow; + date.Colour = rankText.Colour = colours.ContextMenuGray; } protected override bool OnHover(HoverEvent e) { - background.FadeIn(fade_duration, Easing.OutQuint); + background.FadeColour(Color4.WhiteSmoke, fade_duration, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - background.FadeOut(fade_duration, Easing.OutQuint); + background.FadeColour(Color4.White, fade_duration, Easing.OutQuint); base.OnHoverLost(e); } - private class InfoColumn : FillFlowContainer + private class ClickableTopScoreUsername : ClickableUsername { - private readonly OsuSpriteText headerText; - private readonly OsuSpriteText valueText; + private Box underscore; + + public ClickableTopScoreUsername() + { + Add(new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = 1, + Position = new Vector2(0, TextSize / 2 + 1.5f), + Depth = 1, + Child = underscore = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + IdleColour = colours.ContextMenuGray; + HoverColour = underscore.Colour = colours.Blue; + } + + protected override bool OnHover(HoverEvent e) + { + underscore.FadeIn(FADE_DURATION, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + underscore.FadeOut(FADE_DURATION, Easing.OutQuint); + base.OnHoverLost(e); + } + } + + private class DrawableInfoColumn : FillFlowContainer + { + private readonly SpriteText headerText; + private const float header_text_size = 12; + + public DrawableInfoColumn(string header) + { + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + Spacing = new Vector2(0, 2); + Children = new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.X, + Height = header_text_size, + Child = headerText = new SpriteText + { + TextSize = 12, + Text = header.ToUpper(), + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + Height = 3, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.LightGray, + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + headerText.Colour = colours.ContextMenuGray; + } + } + + private class ModsInfoColumn : DrawableInfoColumn + { + private readonly FillFlowContainer modsContainer; + + public IEnumerable Mods + { + set + { + foreach (Mod mod in value) + modsContainer.Add(new ModIcon(mod) + { + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.3f), + }); + } + } + + public ModsInfoColumn(string header) : base(header) + { + AutoSizeAxes = Axes.Both; + Add(modsContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + }); + } + + public void ClearMods() => modsContainer.Clear(); + } + + private class TextInfoColumn : DrawableInfoColumn + { + private readonly SpriteText valueText; public string Value { @@ -213,31 +365,47 @@ namespace osu.Game.Overlays.BeatmapSet.Scores get { return valueText.Text; } } - public InfoColumn(string header) + public TextInfoColumn(string header, float valueTextSize = 25) : base(header) { - AutoSizeAxes = Axes.Both; - Direction = FillDirection.Vertical; - Spacing = new Vector2(0, 3); - Children = new Drawable[] + Add(valueText = new SpriteText { - headerText = new OsuSpriteText - { - TextSize = 14, - Text = header, - Font = @"Exo2.0-Bold", - }, - valueText = new OsuSpriteText - { - TextSize = 25, - Font = @"Exo2.0-RegularItalic", - } - }; + TextSize = valueTextSize, + Font = @"Exo2.0-Light", + }); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - headerText.Colour = colours.Gray9; + valueText.Colour = colours.ContextMenuGray; + } + } + + private class AutoSizeInfoColumn : TextInfoColumn + { + public AutoSizeInfoColumn(string header, float valueTextSize = 25) : base(header, valueTextSize) + { + AutoSizeAxes = Axes.Both; + } + } + + private class MediumInfoColumn : TextInfoColumn + { + private const float width = 70; + + public MediumInfoColumn(string header, float valueTextSize = 25) : base(header, valueTextSize) + { + Width = width; + } + } + + private class SmallInfoColumn : TextInfoColumn + { + private const float width = 40; + + public SmallInfoColumn(string header, float valueTextSize = 25) : base(header, valueTextSize) + { + Width = width; } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index ab34d2ba1d..ac3068c2c8 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -12,6 +12,8 @@ using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -98,6 +100,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores AutoSizeAxes = Axes.Y; Children = new Drawable[] { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, new FillFlowContainer { Anchor = Anchor.TopCentre, From f560dde157acd3bb83cf417fd6d10a8d67088576 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Tue, 5 Feb 2019 15:26:45 +0300 Subject: [PATCH 028/623] Add some flow for info containers --- .../BeatmapSet/Scores/DrawableTopScore.cs | 54 ++++++++++++++----- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 2e2bb78634..d061e176e0 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -193,23 +193,49 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - AutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.7f, Child = new FillFlowContainer { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(margin, 0), + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Spacing = new Vector2(margin), Children = new Drawable[] { - totalScore = new AutoSizeInfoColumn("Total Score"), - accuracy = new MediumInfoColumn("Accuracy"), - maxCombo = new MediumInfoColumn("Max Combo"), - hitGreat = new SmallInfoColumn("300", 20), - hitGood = new SmallInfoColumn("100", 20), - hitMeh = new SmallInfoColumn("50", 20), - hitMiss = new SmallInfoColumn("miss", 20), - pp = new SmallInfoColumn("pp", 20), - modsInfo = new ModsInfoColumn("mods"), + new FillFlowContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(margin, 0), + Children = new Drawable[] + { + hitGreat = new SmallInfoColumn("300", 20), + hitGood = new SmallInfoColumn("100", 20), + hitMeh = new SmallInfoColumn("50", 20), + hitMiss = new SmallInfoColumn("miss", 20), + pp = new SmallInfoColumn("pp", 20), + modsInfo = new ModsInfoColumn("mods"), + } + }, + new FillFlowContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(margin, 0), + Children = new Drawable[] + { + totalScore = new AutoSizeInfoColumn("Total Score"), + accuracy = new MediumInfoColumn("Accuracy"), + maxCombo = new MediumInfoColumn("Max Combo"), + } + }, } } } @@ -248,7 +274,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Height = 1, - Position = new Vector2(0, TextSize / 2 + 1.5f), + Position = new Vector2(0, TextSize / 2 - 1), Depth = 1, Child = underscore = new Box { From c85dc1a2362434298d57661f4cd87e8f71f4e049 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Tue, 5 Feb 2019 19:32:33 +0300 Subject: [PATCH 029/623] update another scores design --- .../Visual/TestCaseBeatmapSetOverlay.cs | 8 +- ...eUsername.cs => ClickableUserContainer.cs} | 29 +- .../BeatmapSet/Scores/DrawableScore.cs | 278 ++++++++++++++---- .../BeatmapSet/Scores/DrawableTopScore.cs | 46 ++- .../BeatmapSet/Scores/ScoreTextLine.cs | 118 ++++++++ .../BeatmapSet/Scores/ScoresContainer.cs | 18 +- 6 files changed, 397 insertions(+), 100 deletions(-) rename osu.Game/Overlays/BeatmapSet/Scores/{ClickableUsername.cs => ClickableUserContainer.cs} (60%) create mode 100644 osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs index b98014b866..20609dc595 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs @@ -1,9 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Beatmaps; @@ -13,6 +10,9 @@ using osu.Game.Overlays.BeatmapSet.Buttons; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets; using osu.Game.Users; +using System; +using System.Collections.Generic; +using System.Linq; namespace osu.Game.Tests.Visual { @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual public override IReadOnlyList RequiredTypes => new[] { typeof(Header), - typeof(ClickableUsername), + typeof(ClickableUserContainer), typeof(DrawableScore), typeof(DrawableTopScore), typeof(ScoresContainer), diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs b/osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs similarity index 60% rename from osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs rename to osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs index e2aade986d..2f402bfa74 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; @@ -12,11 +13,8 @@ using System.Collections.Generic; namespace osu.Game.Overlays.BeatmapSet.Scores { - public class ClickableUsername : OsuHoverContainer + public abstract class ClickableUserContainer : Container { - private readonly SpriteText text; - protected override IEnumerable EffectTargets => new[] { text }; - private UserProfileOverlay profile; private User user; @@ -28,33 +26,16 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (user == value) return; user = value; - OnUserUpdate(user); + OnUserChange(user); } } - public float TextSize - { - set - { - if (text.TextSize == value) return; - text.TextSize = value; - } - get { return text.TextSize; } - } - - public ClickableUsername() + public ClickableUserContainer() { AutoSizeAxes = Axes.Both; - Child = text = new SpriteText - { - Font = @"Exo2.0-BoldItalic", - }; } - protected virtual void OnUserUpdate(User user) - { - text.Text = user.Username; - } + protected abstract void OnUserChange(User user); [BackgroundDependencyLoader(true)] private void load(UserProfileOverlay profile) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index 609524f170..6f6fd22a13 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -16,62 +17,63 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Users; using osuTK; +using osuTK.Graphics; +using System.Collections.Generic; namespace osu.Game.Overlays.BeatmapSet.Scores { public class DrawableScore : Container { private const int fade_duration = 100; - private const float side_margin = 20; + private const float text_size = 14; private readonly Box background; + private readonly Box hoveredBackground; + private readonly SpriteText rank; + private readonly SpriteText scoreText; + private readonly SpriteText accuracy; + private readonly SpriteText maxCombo; + private readonly SpriteText hitGreat; + private readonly SpriteText hitGood; + private readonly SpriteText hitMeh; + private readonly SpriteText hitMiss; + private readonly SpriteText pp; + + private readonly ClickableScoreUsername username; + + private readonly APIScoreInfo score; + private Color4 backgroundColor; public DrawableScore(int index, APIScoreInfo score) { - ScoreModsContainer modsContainer; + FillFlowContainer modsContainer; + + this.score = score; RelativeSizeAxes = Axes.X; - Height = 30; + Height = 25; CornerRadius = 3; Masking = true; Children = new Drawable[] { background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + hoveredBackground = new Box { RelativeSizeAxes = Axes.Both, Alpha = 0, }, - new OsuSpriteText + rank = new SpriteText { Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Origin = Anchor.CentreRight, Text = $"#{index + 1}", - Font = @"Exo2.0-RegularItalic", - Margin = new MarginPadding { Left = side_margin } - }, - new DrawableFlag(score.User.Country) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(30, 20), - Margin = new MarginPadding { Left = 60 } - }, - //new ClickableUsername - //{ - // Anchor = Anchor.CentreLeft, - // Origin = Anchor.CentreLeft, - // User = score.User, - // Margin = new MarginPadding { Left = 100 } - //}, - modsContainer = new ScoreModsContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Width = 0.06f, - RelativePositionAxes = Axes.X, - X = 0.42f + TextSize = text_size, + X = ScoreTextLine.RANK_POSITION, + Font = @"Exo2.0-Bold", + Colour = Color4.Black, }, new DrawableRank(score.Rank) { @@ -79,64 +81,232 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Origin = Anchor.CentreLeft, Size = new Vector2(30, 20), FillMode = FillMode.Fit, - RelativePositionAxes = Axes.X, - X = 0.55f + X = 45 }, - new OsuSpriteText + scoreText = new SpriteText { Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, + Origin = Anchor.CentreLeft, Text = $@"{score.TotalScore:N0}", - Font = @"Venera", - RelativePositionAxes = Axes.X, - X = 0.75f, - FixedWidth = true, + X = ScoreTextLine.SCORE_POSITION, + Colour = Color4.Black, + TextSize = text_size, }, - new OsuSpriteText + accuracy = new SpriteText { Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, + Origin = Anchor.CentreLeft, Text = $@"{score.Accuracy:P2}", - Font = @"Exo2.0-RegularItalic", - RelativePositionAxes = Axes.X, - X = 0.85f + X = ScoreTextLine.ACCURACY_POSITION, + TextSize = text_size, }, - new OsuSpriteText + new DrawableFlag(score.User.Country) { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Text = $"{score.Statistics[HitResult.Great]}/{score.Statistics[HitResult.Good]}/{score.Statistics[HitResult.Meh]}", - Font = @"Exo2.0-RegularItalic", - Margin = new MarginPadding { Right = side_margin } + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(20, 13), + X = 230, + }, + username = new ClickableScoreUsername + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + User = score.User, + X = ScoreTextLine.PLAYER_POSITION, + Colour = Color4.Black, + }, + maxCombo = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = $@"{score.MaxCombo:N0}x", + RelativePositionAxes = Axes.X, + X = ScoreTextLine.MAX_COMBO_POSITION, + TextSize = text_size, + Colour = Color4.Black, + }, + hitGreat = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = $"{score.Statistics[HitResult.Great]}", + RelativePositionAxes = Axes.X, + X = ScoreTextLine.HIT_GREAT_POSITION, + TextSize = text_size, + Colour = Color4.Black, + }, + hitGood = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = $"{score.Statistics[HitResult.Good]}", + RelativePositionAxes = Axes.X, + X = ScoreTextLine.HIT_GOOD_POSITION, + TextSize = text_size, + Colour = Color4.Black, + }, + hitMeh = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = $"{score.Statistics[HitResult.Meh]}", + RelativePositionAxes = Axes.X, + X = ScoreTextLine.HIT_MEH_POSITION, + TextSize = text_size, + Colour = Color4.Black, + }, + hitMiss = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = $"{score.Statistics[HitResult.Miss]}", + RelativePositionAxes = Axes.X, + X = ScoreTextLine.HIT_MISS_POSITION, + TextSize = text_size, + Colour = Color4.Black, + }, + pp = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = $@"{score.PP:N0}", + RelativePositionAxes = Axes.X, + X = ScoreTextLine.PP_POSITION, + TextSize = text_size, + Colour = Color4.Black, + }, + modsContainer = new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = ScoreTextLine.MODS_POSITION, }, }; + if (index == 0) + scoreText.Font = @"Exo2.0-Bold"; + + accuracy.Colour = (score.Accuracy == 1) ? Color4.Green : Color4.Black; + + hitGreat.Colour = (score.Statistics[HitResult.Great] == 0) ? Color4.Gray : Color4.Black; + hitGood.Colour = (score.Statistics[HitResult.Good] == 0) ? Color4.Gray : Color4.Black; + hitMeh.Colour = (score.Statistics[HitResult.Meh] == 0) ? Color4.Gray : Color4.Black; + hitMiss.Colour = (score.Statistics[HitResult.Miss] == 0) ? Color4.Gray : Color4.Black; + + background.Colour = backgroundColor = (index % 2 == 0) ? Color4.WhiteSmoke : Color4.White; + + foreach (Mod mod in score.Mods) modsContainer.Add(new ModIcon(mod) { AutoSizeAxes = Axes.Both, - Scale = new Vector2(0.35f), + Scale = new Vector2(0.3f), }); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - background.Colour = colours.Gray4; + hoveredBackground.Colour = colours.Gray4; } protected override bool OnHover(HoverEvent e) { - background.FadeIn(fade_duration, Easing.OutQuint); + hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); + rank.FadeColour(Color4.White, fade_duration, Easing.OutQuint); + scoreText.FadeColour(Color4.White, fade_duration, Easing.OutQuint); + accuracy.FadeColour(Color4.White, fade_duration, Easing.OutQuint); + username.FadeColour(Color4.White, fade_duration, Easing.OutQuint); + maxCombo.FadeColour(Color4.White, fade_duration, Easing.OutQuint); + pp.FadeColour(Color4.White, fade_duration, Easing.OutQuint); + + if (score.Statistics[HitResult.Great] != 0) + hitGreat.FadeColour(Color4.White, fade_duration, Easing.OutQuint); + + if (score.Statistics[HitResult.Good] != 0) + hitGood.FadeColour(Color4.White, fade_duration, Easing.OutQuint); + + if (score.Statistics[HitResult.Meh] != 0) + hitMeh.FadeColour(Color4.White, fade_duration, Easing.OutQuint); + + if (score.Statistics[HitResult.Miss] != 0) + hitMiss.FadeColour(Color4.White, fade_duration, Easing.OutQuint); + return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - background.FadeOut(fade_duration, Easing.OutQuint); + hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); + rank.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); + scoreText.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); + username.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); + accuracy.FadeColour((score.Accuracy == 1) ? Color4.Green : Color4.Black, fade_duration, Easing.OutQuint); + maxCombo.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); + pp.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); + + if (score.Statistics[HitResult.Great] != 0) + hitGreat.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); + + if (score.Statistics[HitResult.Good] != 0) + hitGood.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); + + if (score.Statistics[HitResult.Meh] != 0) + hitMeh.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); + + if (score.Statistics[HitResult.Miss] != 0) + hitMiss.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); + base.OnHoverLost(e); } protected override bool OnClick(ClickEvent e) => true; + + private class ClickableScoreUsername : ClickableUserContainer + { + private readonly SpriteText text; + private readonly SpriteText textBold; + + public ClickableScoreUsername() + { + Add(text = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + TextSize = text_size, + }); + + Add(textBold = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + TextSize = text_size, + Font = @"Exo2.0-Bold", + Alpha = 0, + }); + } + + protected override void OnUserChange(User user) + { + text.Text = textBold.Text = user.Username; + } + + protected override bool OnHover(HoverEvent e) + { + textBold.FadeIn(fade_duration, Easing.OutQuint); + text.FadeOut(fade_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + textBold.FadeOut(fade_duration, Easing.OutQuint); + text.FadeIn(fade_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index d061e176e0..7d82b05099 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly Box background; private readonly UpdateableAvatar avatar; private readonly DrawableFlag flag; - private readonly ClickableUsername username; + private readonly ClickableTopScoreUsername username; private readonly SpriteText rankText; private readonly SpriteText date; private readonly DrawableRank rank; @@ -262,44 +262,70 @@ namespace osu.Game.Overlays.BeatmapSet.Scores base.OnHoverLost(e); } - private class ClickableTopScoreUsername : ClickableUsername + private class ClickableTopScoreUsername : ClickableUserContainer { - private Box underscore; + private const float fade_duration = 500; + + private readonly Box underscore; + private readonly Container underscoreContainer; + private readonly SpriteText text; + + private Color4 hoverColour; + + public float TextSize + { + set + { + if (text.TextSize == value) return; + text.TextSize = value; + } + get { return text.TextSize; } + } public ClickableTopScoreUsername() { - Add(new Container + Add(underscoreContainer = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Height = 1, - Position = new Vector2(0, TextSize / 2 - 1), - Depth = 1, Child = underscore = new Box { RelativeSizeAxes = Axes.Both, Alpha = 0, } }); + Add(text = new SpriteText + { + Font = @"Exo2.0-BoldItalic", + Colour = Color4.Black, + }); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - IdleColour = colours.ContextMenuGray; - HoverColour = underscore.Colour = colours.Blue; + hoverColour = underscore.Colour = colours.Blue; + underscoreContainer.Position = new Vector2(0, TextSize / 2 - 1); + } + + protected override void OnUserChange(User user) + { + text.Text = user.Username; } protected override bool OnHover(HoverEvent e) { - underscore.FadeIn(FADE_DURATION, Easing.OutQuint); + text.FadeColour(hoverColour, fade_duration, Easing.OutQuint); + underscore.FadeIn(fade_duration, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - underscore.FadeOut(FADE_DURATION, Easing.OutQuint); + text.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); + underscore.FadeOut(fade_duration, Easing.OutQuint); base.OnHoverLost(e); } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs new file mode 100644 index 0000000000..d8655b4882 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs @@ -0,0 +1,118 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class ScoreTextLine : Container + { + private const float text_size = 12; + + public const float RANK_POSITION = 30; + public const float SCORE_POSITION = 90; + public const float ACCURACY_POSITION = 170; + public const float PLAYER_POSITION = 270; + public const float MAX_COMBO_POSITION = 0.5f; + public const float HIT_GREAT_POSITION = 0.6f; + public const float HIT_GOOD_POSITION = 0.65f; + public const float HIT_MEH_POSITION = 0.7f; + public const float HIT_MISS_POSITION = 0.75f; + public const float PP_POSITION = 0.8f; + public const float MODS_POSITION = 0.9f; + + public ScoreTextLine() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Children = new Drawable[] + { + new ScoreText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Text = "rank".ToUpper(), + X = RANK_POSITION, + }, + new ScoreText + { + Text = "score".ToUpper(), + X = SCORE_POSITION, + }, + new ScoreText + { + Text = "accuracy".ToUpper(), + X = ACCURACY_POSITION, + }, + new ScoreText + { + Text = "player".ToUpper(), + X = PLAYER_POSITION, + }, + new ScoreText + { + Text = "max combo".ToUpper(), + X = MAX_COMBO_POSITION, + RelativePositionAxes = Axes.X, + }, + new ScoreText + { + Text = "300", + RelativePositionAxes = Axes.X, + X = HIT_GREAT_POSITION, + }, + new ScoreText + { + Text = "100".ToUpper(), + RelativePositionAxes = Axes.X, + X = HIT_GOOD_POSITION, + }, + new ScoreText + { + Text = "50".ToUpper(), + RelativePositionAxes = Axes.X, + X = HIT_MEH_POSITION, + }, + new ScoreText + { + Text = "miss".ToUpper(), + RelativePositionAxes = Axes.X, + X = HIT_MISS_POSITION, + }, + new ScoreText + { + Text = "pp".ToUpper(), + RelativePositionAxes = Axes.X, + X = PP_POSITION, + }, + new ScoreText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + Text = "mods".ToUpper(), + X = MODS_POSITION, + RelativePositionAxes = Axes.X, + }, + }; + } + + private class ScoreText : SpriteText + { + public ScoreText() + { + TextSize = text_size; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.ContextMenuGray; + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index ac3068c2c8..5fd1084a14 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -1,19 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osuTK; +using osuTK.Graphics; using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; -using osu.Framework.Graphics.Shapes; -using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -90,7 +90,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (scoreCount < 2) return; - for (int i = 1; i < scoreCount; i++) + flow.Add(new ScoreTextLine()); + + for (int i = 0; i < scoreCount; i++) flow.Add(new DrawableScore(i, scores.ElementAt(i))); } From 9cca11fb0d71c81f930cb48dd95a6ff9962cdd8f Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Wed, 6 Feb 2019 01:48:43 +0300 Subject: [PATCH 030/623] Warning fixes --- .../BeatmapSet/Scores/ClickableUserContainer.cs | 6 +----- osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs | 9 ++------- .../Overlays/BeatmapSet/Scores/DrawableTopScore.cs | 12 +++++------- osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs | 1 - 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs index 2f402bfa74..621e1ee1f2 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs @@ -4,12 +4,8 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Users; -using System.Collections.Generic; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -30,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - public ClickableUserContainer() + protected ClickableUserContainer() { AutoSizeAxes = Axes.Both; } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index 6f6fd22a13..c2f198d3ee 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -8,17 +8,14 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; -using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Users; using osuTK; using osuTK.Graphics; -using System.Collections.Generic; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -27,7 +24,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private const int fade_duration = 100; private const float text_size = 14; - private readonly Box background; private readonly Box hoveredBackground; private readonly SpriteText rank; private readonly SpriteText scoreText; @@ -42,11 +38,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly ClickableScoreUsername username; private readonly APIScoreInfo score; - private Color4 backgroundColor; public DrawableScore(int index, APIScoreInfo score) { FillFlowContainer modsContainer; + Box background; this.score = score; @@ -196,8 +192,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores hitMeh.Colour = (score.Statistics[HitResult.Meh] == 0) ? Color4.Gray : Color4.Black; hitMiss.Colour = (score.Statistics[HitResult.Miss] == 0) ? Color4.Gray : Color4.Black; - background.Colour = backgroundColor = (index % 2 == 0) ? Color4.WhiteSmoke : Color4.White; - + background.Colour = (index % 2 == 0) ? Color4.WhiteSmoke : Color4.White; foreach (Mod mod in score.Mods) modsContainer.Add(new ModIcon(mod) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 7d82b05099..48b824390d 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -10,10 +10,8 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; -using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -264,7 +262,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private class ClickableTopScoreUsername : ClickableUserContainer { - private const float fade_duration = 500; + private const float username_fade_duration = 500; private readonly Box underscore; private readonly Container underscoreContainer; @@ -317,15 +315,15 @@ namespace osu.Game.Overlays.BeatmapSet.Scores protected override bool OnHover(HoverEvent e) { - text.FadeColour(hoverColour, fade_duration, Easing.OutQuint); - underscore.FadeIn(fade_duration, Easing.OutQuint); + text.FadeColour(hoverColour, username_fade_duration, Easing.OutQuint); + underscore.FadeIn(username_fade_duration, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - text.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); - underscore.FadeOut(fade_duration, Easing.OutQuint); + text.FadeColour(Color4.Black, username_fade_duration, Easing.OutQuint); + underscore.FadeOut(username_fade_duration, Easing.OutQuint); base.OnHoverLost(e); } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs index d8655b4882..53a477b908 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Localisation; using osu.Game.Graphics; namespace osu.Game.Overlays.BeatmapSet.Scores From f43ee6b6a36f12c54de0a2c941c27a01bcb85626 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Fri, 8 Feb 2019 19:06:46 +0300 Subject: [PATCH 031/623] update drawable top score inline with the latest design --- .../BeatmapSet/Scores/DrawableTopScore.cs | 85 ++++++++----------- .../BeatmapSet/Scores/ScoresContainer.cs | 7 -- 2 files changed, 35 insertions(+), 57 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 48b824390d..487194c819 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -30,6 +30,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private const float avatar_size = 80; private const float margin = 10; + private OsuColour colours; + + private Color4 backgroundIdleColour => colours.Gray3; + private Color4 backgroundHoveredColour => colours.Gray4; + private readonly Box background; private readonly UpdateableAvatar avatar; private readonly DrawableFlag flag; @@ -38,8 +43,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly SpriteText date; private readonly DrawableRank rank; - private readonly AutoSizeInfoColumn totalScore; - private readonly MediumInfoColumn accuracy; + private readonly AutoSizedInfoColumn totalScore; + private readonly AutoSizedInfoColumn accuracy; private readonly MediumInfoColumn maxCombo; private readonly SmallInfoColumn hitGreat; @@ -56,7 +61,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores get { return score; } set { - if (score == value) return; + if (score == value) + return; score = value; avatar.User = username.User = score.User; @@ -83,7 +89,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - CornerRadius = 3; + CornerRadius = 10; Masking = true; EdgeEffect = new EdgeEffectParameters { @@ -97,7 +103,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores background = new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.White, }, new Container { @@ -115,31 +120,20 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Spacing = new Vector2(margin, 0), Children = new Drawable[] { - new FillFlowContainer + rankText = new SpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 2), - Children = new Drawable[] - { - rankText = new SpriteText - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Text = "#1", - TextSize = 20, - Font = @"Exo2.0-BoldItalic", - }, - rank = new DrawableRank(ScoreRank.F) - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Size = new Vector2(30), - FillMode = FillMode.Fit, - }, - } + Text = "#1", + TextSize = 30, + Font = @"Exo2.0-BoldItalic", + }, + rank = new DrawableRank(ScoreRank.F) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(40), + FillMode = FillMode.Fit, }, avatar = new UpdateableAvatar { @@ -229,8 +223,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Spacing = new Vector2(margin, 0), Children = new Drawable[] { - totalScore = new AutoSizeInfoColumn("Total Score"), - accuracy = new MediumInfoColumn("Accuracy"), + totalScore = new AutoSizedInfoColumn("Total Score"), + accuracy = new AutoSizedInfoColumn("Accuracy"), maxCombo = new MediumInfoColumn("Max Combo"), } }, @@ -245,18 +239,21 @@ namespace osu.Game.Overlays.BeatmapSet.Scores [BackgroundDependencyLoader] private void load(OsuColour colours) { - date.Colour = rankText.Colour = colours.ContextMenuGray; + this.colours = colours; + + rankText.Colour = colours.Yellow; + background.Colour = backgroundIdleColour; } protected override bool OnHover(HoverEvent e) { - background.FadeColour(Color4.WhiteSmoke, fade_duration, Easing.OutQuint); + background.FadeColour(backgroundHoveredColour, fade_duration, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - background.FadeColour(Color4.White, fade_duration, Easing.OutQuint); + background.FadeColour(backgroundIdleColour, fade_duration, Easing.OutQuint); base.OnHoverLost(e); } @@ -274,7 +271,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { set { - if (text.TextSize == value) return; + if (text.TextSize == value) + return; text.TextSize = value; } get { return text.TextSize; } @@ -297,7 +295,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Add(text = new SpriteText { Font = @"Exo2.0-BoldItalic", - Colour = Color4.Black, }); } @@ -322,7 +319,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores protected override void OnHoverLost(HoverLostEvent e) { - text.FadeColour(Color4.Black, username_fade_duration, Easing.OutQuint); + text.FadeColour(Color4.White, username_fade_duration, Easing.OutQuint); underscore.FadeOut(username_fade_duration, Easing.OutQuint); base.OnHoverLost(e); } @@ -348,6 +345,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { TextSize = 12, Text = header.ToUpper(), + Font = @"Exo2.0-Bold", } }, new Container @@ -362,12 +360,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } }; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - headerText.Colour = colours.ContextMenuGray; - } } private class ModsInfoColumn : DrawableInfoColumn @@ -420,20 +412,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Add(valueText = new SpriteText { TextSize = valueTextSize, - Font = @"Exo2.0-Light", }); } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - valueText.Colour = colours.ContextMenuGray; - } } - private class AutoSizeInfoColumn : TextInfoColumn + private class AutoSizedInfoColumn : TextInfoColumn { - public AutoSizeInfoColumn(string header, float valueTextSize = 25) : base(header, valueTextSize) + public AutoSizedInfoColumn(string header, float valueTextSize = 25) : base(header, valueTextSize) { AutoSizeAxes = Axes.Both; } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 5fd1084a14..5211f1398f 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -4,14 +4,12 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osuTK; -using osuTK.Graphics; using System.Collections.Generic; using System.Linq; @@ -102,11 +100,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores AutoSizeAxes = Axes.Y; Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, new FillFlowContainer { Anchor = Anchor.TopCentre, From 7a3ae0f479bebb101ad8b00828419ff0eae5291d Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Fri, 8 Feb 2019 19:50:02 +0300 Subject: [PATCH 032/623] update drawable score inline with the latest design --- .../BeatmapSet/Scores/DrawableScore.cs | 75 ++++--------------- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 58 ++++++++++++++ .../BeatmapSet/Scores/ScoreTextLine.cs | 16 +--- .../BeatmapSet/Scores/ScoresContainer.cs | 22 +++--- 4 files changed, 87 insertions(+), 84 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index c2f198d3ee..1076fe6449 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -25,6 +25,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private const float text_size = 14; private readonly Box hoveredBackground; + private readonly Box background; + private readonly SpriteText rank; private readonly SpriteText scoreText; private readonly SpriteText accuracy; @@ -39,10 +41,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly APIScoreInfo score; - public DrawableScore(int index, APIScoreInfo score) + public DrawableScore(int index, APIScoreInfo score, int maxModsAmount) { FillFlowContainer modsContainer; - Box background; this.score = score; @@ -69,7 +70,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores TextSize = text_size, X = ScoreTextLine.RANK_POSITION, Font = @"Exo2.0-Bold", - Colour = Color4.Black, }, new DrawableRank(score.Rank) { @@ -85,7 +85,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Origin = Anchor.CentreLeft, Text = $@"{score.TotalScore:N0}", X = ScoreTextLine.SCORE_POSITION, - Colour = Color4.Black, TextSize = text_size, }, accuracy = new SpriteText @@ -109,7 +108,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Origin = Anchor.CentreLeft, User = score.User, X = ScoreTextLine.PLAYER_POSITION, - Colour = Color4.Black, }, maxCombo = new SpriteText { @@ -119,7 +117,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores RelativePositionAxes = Axes.X, X = ScoreTextLine.MAX_COMBO_POSITION, TextSize = text_size, - Colour = Color4.Black, }, hitGreat = new SpriteText { @@ -129,7 +126,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores RelativePositionAxes = Axes.X, X = ScoreTextLine.HIT_GREAT_POSITION, TextSize = text_size, - Colour = Color4.Black, }, hitGood = new SpriteText { @@ -139,7 +135,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores RelativePositionAxes = Axes.X, X = ScoreTextLine.HIT_GOOD_POSITION, TextSize = text_size, - Colour = Color4.Black, }, hitMeh = new SpriteText { @@ -149,7 +144,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores RelativePositionAxes = Axes.X, X = ScoreTextLine.HIT_MEH_POSITION, TextSize = text_size, - Colour = Color4.Black, }, hitMiss = new SpriteText { @@ -159,7 +153,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores RelativePositionAxes = Axes.X, X = ScoreTextLine.HIT_MISS_POSITION, TextSize = text_size, - Colour = Color4.Black, }, pp = new SpriteText { @@ -169,34 +162,35 @@ namespace osu.Game.Overlays.BeatmapSet.Scores RelativePositionAxes = Axes.X, X = ScoreTextLine.PP_POSITION, TextSize = text_size, - Colour = Color4.Black, }, modsContainer = new FillFlowContainer { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreLeft, Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = ScoreTextLine.MODS_POSITION, + X = -30 * maxModsAmount, }, }; if (index == 0) scoreText.Font = @"Exo2.0-Bold"; - accuracy.Colour = (score.Accuracy == 1) ? Color4.Green : Color4.Black; + accuracy.Colour = (score.Accuracy == 1) ? Color4.LightGreen : Color4.White; - hitGreat.Colour = (score.Statistics[HitResult.Great] == 0) ? Color4.Gray : Color4.Black; - hitGood.Colour = (score.Statistics[HitResult.Good] == 0) ? Color4.Gray : Color4.Black; - hitMeh.Colour = (score.Statistics[HitResult.Meh] == 0) ? Color4.Gray : Color4.Black; - hitMiss.Colour = (score.Statistics[HitResult.Miss] == 0) ? Color4.Gray : Color4.Black; + hitGreat.Colour = (score.Statistics[HitResult.Great] == 0) ? Color4.Gray : Color4.White; + hitGood.Colour = (score.Statistics[HitResult.Good] == 0) ? Color4.Gray : Color4.White; + hitMeh.Colour = (score.Statistics[HitResult.Meh] == 0) ? Color4.Gray : Color4.White; + hitMiss.Colour = (score.Statistics[HitResult.Miss] == 0) ? Color4.Gray : Color4.White; - background.Colour = (index % 2 == 0) ? Color4.WhiteSmoke : Color4.White; + if (index % 2 == 0) + background.Alpha = 0; foreach (Mod mod in score.Mods) modsContainer.Add(new ModIcon(mod) { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, Scale = new Vector2(0.3f), }); @@ -206,55 +200,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private void load(OsuColour colours) { hoveredBackground.Colour = colours.Gray4; + background.Colour = colours.Gray3; } protected override bool OnHover(HoverEvent e) { hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); - rank.FadeColour(Color4.White, fade_duration, Easing.OutQuint); - scoreText.FadeColour(Color4.White, fade_duration, Easing.OutQuint); - accuracy.FadeColour(Color4.White, fade_duration, Easing.OutQuint); - username.FadeColour(Color4.White, fade_duration, Easing.OutQuint); - maxCombo.FadeColour(Color4.White, fade_duration, Easing.OutQuint); - pp.FadeColour(Color4.White, fade_duration, Easing.OutQuint); - - if (score.Statistics[HitResult.Great] != 0) - hitGreat.FadeColour(Color4.White, fade_duration, Easing.OutQuint); - - if (score.Statistics[HitResult.Good] != 0) - hitGood.FadeColour(Color4.White, fade_duration, Easing.OutQuint); - - if (score.Statistics[HitResult.Meh] != 0) - hitMeh.FadeColour(Color4.White, fade_duration, Easing.OutQuint); - - if (score.Statistics[HitResult.Miss] != 0) - hitMiss.FadeColour(Color4.White, fade_duration, Easing.OutQuint); - return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); - rank.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); - scoreText.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); - username.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); - accuracy.FadeColour((score.Accuracy == 1) ? Color4.Green : Color4.Black, fade_duration, Easing.OutQuint); - maxCombo.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); - pp.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); - - if (score.Statistics[HitResult.Great] != 0) - hitGreat.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); - - if (score.Statistics[HitResult.Good] != 0) - hitGood.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); - - if (score.Statistics[HitResult.Meh] != 0) - hitMeh.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); - - if (score.Statistics[HitResult.Miss] != 0) - hitMiss.FadeColour(Color4.Black, fade_duration, Easing.OutQuint); - base.OnHoverLost(e); } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs new file mode 100644 index 0000000000..90947364e4 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . 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 System.Collections.Generic; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class ScoreTable : FillFlowContainer + { + private IEnumerable scores; + public IEnumerable Scores + { + set + { + scores = value; + + int maxModsAmount = 0; + foreach (var s in scores) + { + var scoreModsAmount = s.Mods.Length; + if (scoreModsAmount > maxModsAmount) + maxModsAmount = scoreModsAmount; + } + + Add(new ScoreTextLine(maxModsAmount)); + + + int index = 0; + foreach (var s in scores) + Add(new DrawableScore(index++, s, maxModsAmount)); + } + get + { + return scores; + } + } + + public ScoreTable() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + } + + public void ClearScores() + { + scores = null; + foreach (var s in this) + { + if (s is DrawableScore) + Remove(s); + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs index 53a477b908..43255683c4 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs @@ -23,9 +23,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public const float HIT_MEH_POSITION = 0.7f; public const float HIT_MISS_POSITION = 0.75f; public const float PP_POSITION = 0.8f; - public const float MODS_POSITION = 0.9f; - public ScoreTextLine() + public ScoreTextLine(int maxModsAmount) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -91,11 +90,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }, new ScoreText { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreLeft, Text = "mods".ToUpper(), - X = MODS_POSITION, - RelativePositionAxes = Axes.X, + X = -30 * maxModsAmount, }, }; } @@ -106,12 +104,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { TextSize = text_size; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = colours.ContextMenuGray; - } } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 5211f1398f..8f3414c292 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -20,7 +20,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private const int spacing = 15; private const int fade_duration = 200; - private readonly FillFlowContainer flow; + private readonly ScoreTable scoreTable; + private readonly DrawableTopScore topScore; private readonly LoadingAnimation loadingAnimation; @@ -76,22 +77,19 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (scoreCount == 0) { topScore.Hide(); - flow.Clear(); + scoreTable.ClearScores(); return; } topScore.Score = scores.FirstOrDefault(); topScore.Show(); - flow.Clear(); + scoreTable.ClearScores(); if (scoreCount < 2) return; - flow.Add(new ScoreTextLine()); - - for (int i = 0; i < scoreCount; i++) - flow.Add(new DrawableScore(i, scores.ElementAt(i))); + scoreTable.Scores = scores; } public ScoresContainer() @@ -113,13 +111,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Children = new Drawable[] { topScore = new DrawableTopScore(), - flow = new FillFlowContainer + scoreTable = new ScoreTable { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 1), - }, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } } }, loadingAnimation = new LoadingAnimation From 13c154a0b37f2c4c89f66d697dab122ec9a754d4 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Fri, 8 Feb 2019 20:07:21 +0300 Subject: [PATCH 033/623] Fix background colour and a bug with removing scores --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 7 ++----- .../Overlays/BeatmapSet/Scores/ScoresContainer.cs | 12 +++++++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 90947364e4..7de13b7204 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using System.Collections.Generic; +using System.Linq; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -48,11 +49,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public void ClearScores() { scores = null; - foreach (var s in this) - { - if (s is DrawableScore) - Remove(s); - } + Clear(); } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 8f3414c292..2a9e76874e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -4,12 +4,15 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osuTK; +using osuTK.Graphics; using System.Collections.Generic; using System.Linq; @@ -20,6 +23,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private const int spacing = 15; private const int fade_duration = 200; + private readonly Box background; private readonly ScoreTable scoreTable; private readonly DrawableTopScore topScore; @@ -98,6 +102,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores AutoSizeAxes = Axes.Y; Children = new Drawable[] { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, new FillFlowContainer { Anchor = Anchor.TopCentre, @@ -127,9 +135,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(APIAccess api, OsuColour colours) { this.api = api; + + background.Colour = colours.Gray2; updateDisplay(); } From bd1f4f954955bf426225e67d9db270c275135483 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Fri, 8 Feb 2019 20:35:07 +0300 Subject: [PATCH 034/623] Small design fixes --- .../BeatmapSet/Scores/DrawableScore.cs | 2 +- .../BeatmapSet/Scores/DrawableTopScore.cs | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index 1076fe6449..cb9d89dc2c 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -176,7 +176,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (index == 0) scoreText.Font = @"Exo2.0-Bold"; - accuracy.Colour = (score.Accuracy == 1) ? Color4.LightGreen : Color4.White; + accuracy.Colour = (score.Accuracy == 1) ? Color4.GreenYellow : Color4.White; hitGreat.Colour = (score.Statistics[HitResult.Great] == 0) ? Color4.Gray : Color4.White; hitGood.Colour = (score.Statistics[HitResult.Good] == 0) ? Color4.Gray : Color4.White; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 487194c819..903e86423b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -169,7 +169,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - TextSize = 10, + TextSize = 15, + Font = @"Exo2.0-Bold", }, flag = new DrawableFlag { @@ -327,9 +328,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private class DrawableInfoColumn : FillFlowContainer { - private readonly SpriteText headerText; private const float header_text_size = 12; + private readonly SpriteText headerText; + private readonly Box line; + public DrawableInfoColumn(string header) { AutoSizeAxes = Axes.Y; @@ -351,15 +354,20 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new Container { RelativeSizeAxes = Axes.X, - Height = 3, - Child = new Box + Height = 2, + Child = line = new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.LightGray, } } }; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + line.Colour = colours.Gray5; + } } private class ModsInfoColumn : DrawableInfoColumn From 107e0a5239be8e629d4c0a5498fd49ff03e082be Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Fri, 8 Feb 2019 20:43:11 +0300 Subject: [PATCH 035/623] Warning fixes --- .../BeatmapSet/Scores/DrawableScore.cs | 31 +++++++------------ .../BeatmapSet/Scores/DrawableTopScore.cs | 7 ++--- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 -- .../BeatmapSet/Scores/ScoreTextLine.cs | 2 -- .../BeatmapSet/Scores/ScoresContainer.cs | 1 - 5 files changed, 14 insertions(+), 29 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index cb9d89dc2c..d37bbe5db0 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -27,25 +27,16 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly Box hoveredBackground; private readonly Box background; - private readonly SpriteText rank; - private readonly SpriteText scoreText; - private readonly SpriteText accuracy; - private readonly SpriteText maxCombo; - private readonly SpriteText hitGreat; - private readonly SpriteText hitGood; - private readonly SpriteText hitMeh; - private readonly SpriteText hitMiss; - private readonly SpriteText pp; - - private readonly ClickableScoreUsername username; - - private readonly APIScoreInfo score; - public DrawableScore(int index, APIScoreInfo score, int maxModsAmount) { - FillFlowContainer modsContainer; + SpriteText accuracy; + SpriteText scoreText; + SpriteText hitGreat; + SpriteText hitGood; + SpriteText hitMeh; + SpriteText hitMiss; - this.score = score; + FillFlowContainer modsContainer; RelativeSizeAxes = Axes.X; Height = 25; @@ -62,7 +53,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores RelativeSizeAxes = Axes.Both, Alpha = 0, }, - rank = new SpriteText + new SpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreRight, @@ -102,14 +93,14 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Size = new Vector2(20, 13), X = 230, }, - username = new ClickableScoreUsername + new ClickableScoreUsername { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, User = score.User, X = ScoreTextLine.PLAYER_POSITION, }, - maxCombo = new SpriteText + new SpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -154,7 +145,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores X = ScoreTextLine.HIT_MISS_POSITION, TextSize = text_size, }, - pp = new SpriteText + new SpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 903e86423b..d2421ec606 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -330,10 +330,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { private const float header_text_size = 12; - private readonly SpriteText headerText; private readonly Box line; - public DrawableInfoColumn(string header) + protected DrawableInfoColumn(string header) { AutoSizeAxes = Axes.Y; Direction = FillDirection.Vertical; @@ -344,7 +343,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { AutoSizeAxes = Axes.X, Height = header_text_size, - Child = headerText = new SpriteText + Child = new SpriteText { TextSize = 12, Text = header.ToUpper(), @@ -415,7 +414,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores get { return valueText.Text; } } - public TextInfoColumn(string header, float valueTextSize = 25) : base(header) + protected TextInfoColumn(string header, float valueTextSize = 25) : base(header) { Add(valueText = new SpriteText { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 7de13b7204..1b20b2c382 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using System.Collections.Generic; -using System.Linq; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -28,7 +27,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Add(new ScoreTextLine(maxModsAmount)); - int index = 0; foreach (var s in scores) Add(new DrawableScore(index++, s, maxModsAmount)); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs index 43255683c4..04934185a1 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs @@ -1,11 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; namespace osu.Game.Overlays.BeatmapSet.Scores { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 2a9e76874e..2783b0483b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -12,7 +12,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osuTK; -using osuTK.Graphics; using System.Collections.Generic; using System.Linq; From 105053e91b01bfb86a8af60b82fa25b2a7acb344 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Sat, 9 Feb 2019 00:56:41 +0300 Subject: [PATCH 036/623] TestCase fix --- osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs | 8 ++++++-- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 5 ++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs index 321a38d087..bcc1774729 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs @@ -17,10 +17,11 @@ using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; +using NUnit.Framework; namespace osu.Game.Tests.Visual { - [System.ComponentModel.Description("in BeatmapOverlay")] + [Description("in BeatmapOverlay")] public class TestCaseBeatmapScoresContainer : OsuTestCase { private readonly IEnumerable scores; @@ -160,11 +161,12 @@ namespace osu.Game.Tests.Visual Accuracy = 0.6543, }, }; - foreach(var s in scores) + foreach (var s in scores) { s.Statistics.Add(HitResult.Great, RNG.Next(2000)); s.Statistics.Add(HitResult.Good, RNG.Next(2000)); s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); + s.Statistics.Add(HitResult.Miss, RNG.Next(2000)); } anotherScores = new[] @@ -277,6 +279,7 @@ namespace osu.Game.Tests.Visual s.Statistics.Add(HitResult.Great, RNG.Next(2000)); s.Statistics.Add(HitResult.Good, RNG.Next(2000)); s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); + s.Statistics.Add(HitResult.Miss, RNG.Next(2000)); } topScoreInfo = new APIScoreInfo @@ -304,6 +307,7 @@ namespace osu.Game.Tests.Visual topScoreInfo.Statistics.Add(HitResult.Great, RNG.Next(2000)); topScoreInfo.Statistics.Add(HitResult.Good, RNG.Next(2000)); topScoreInfo.Statistics.Add(HitResult.Meh, RNG.Next(2000)); + topScoreInfo.Statistics.Add(HitResult.Miss, RNG.Next(2000)); } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 2783b0483b..180f4a949c 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -73,6 +73,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private void updateDisplay() { + scoreTable.ClearScores(); + loading = false; var scoreCount = scores?.Count() ?? 0; @@ -80,15 +82,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (scoreCount == 0) { topScore.Hide(); - scoreTable.ClearScores(); return; } topScore.Score = scores.FirstOrDefault(); topScore.Show(); - scoreTable.ClearScores(); - if (scoreCount < 2) return; From 04e57d7d3dcd2dc7423a6ef077437abafcfeb7e9 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Sat, 9 Feb 2019 06:23:58 +0300 Subject: [PATCH 037/623] Refactor to make things more flexible --- .../BeatmapSet/Scores/DrawableScore.cs | 242 ++++++++---------- .../BeatmapSet/Scores/DrawableTopScore.cs | 2 +- .../BeatmapSet/Scores/ScoreTableLine.cs | 183 +++++++++++++ .../BeatmapSet/Scores/ScoreTextLine.cs | 146 ++++------- 4 files changed, 341 insertions(+), 232 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapSet/Scores/ScoreTableLine.cs diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index d37bbe5db0..8741fd8dfe 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -22,24 +22,15 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public class DrawableScore : Container { private const int fade_duration = 100; - private const float text_size = 14; + private const int text_size = 14; private readonly Box hoveredBackground; private readonly Box background; public DrawableScore(int index, APIScoreInfo score, int maxModsAmount) { - SpriteText accuracy; - SpriteText scoreText; - SpriteText hitGreat; - SpriteText hitGood; - SpriteText hitMeh; - SpriteText hitMiss; - - FillFlowContainer modsContainer; - RelativeSizeAxes = Axes.X; - Height = 25; + AutoSizeAxes = Axes.Y; CornerRadius = 3; Masking = true; Children = new Drawable[] @@ -53,138 +44,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores RelativeSizeAxes = Axes.Both, Alpha = 0, }, - new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, - Text = $"#{index + 1}", - TextSize = text_size, - X = ScoreTextLine.RANK_POSITION, - Font = @"Exo2.0-Bold", - }, - new DrawableRank(score.Rank) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(30, 20), - FillMode = FillMode.Fit, - X = 45 - }, - scoreText = new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = $@"{score.TotalScore:N0}", - X = ScoreTextLine.SCORE_POSITION, - TextSize = text_size, - }, - accuracy = new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = $@"{score.Accuracy:P2}", - X = ScoreTextLine.ACCURACY_POSITION, - TextSize = text_size, - }, - new DrawableFlag(score.User.Country) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(20, 13), - X = 230, - }, - new ClickableScoreUsername - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - User = score.User, - X = ScoreTextLine.PLAYER_POSITION, - }, - new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = $@"{score.MaxCombo:N0}x", - RelativePositionAxes = Axes.X, - X = ScoreTextLine.MAX_COMBO_POSITION, - TextSize = text_size, - }, - hitGreat = new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = $"{score.Statistics[HitResult.Great]}", - RelativePositionAxes = Axes.X, - X = ScoreTextLine.HIT_GREAT_POSITION, - TextSize = text_size, - }, - hitGood = new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = $"{score.Statistics[HitResult.Good]}", - RelativePositionAxes = Axes.X, - X = ScoreTextLine.HIT_GOOD_POSITION, - TextSize = text_size, - }, - hitMeh = new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = $"{score.Statistics[HitResult.Meh]}", - RelativePositionAxes = Axes.X, - X = ScoreTextLine.HIT_MEH_POSITION, - TextSize = text_size, - }, - hitMiss = new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = $"{score.Statistics[HitResult.Miss]}", - RelativePositionAxes = Axes.X, - X = ScoreTextLine.HIT_MISS_POSITION, - TextSize = text_size, - }, - new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = $@"{score.PP:N0}", - RelativePositionAxes = Axes.X, - X = ScoreTextLine.PP_POSITION, - TextSize = text_size, - }, - modsContainer = new FillFlowContainer - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreLeft, - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - X = -30 * maxModsAmount, - }, + new DrawableScoreData(index, score, maxModsAmount), }; - if (index == 0) - scoreText.Font = @"Exo2.0-Bold"; - - accuracy.Colour = (score.Accuracy == 1) ? Color4.GreenYellow : Color4.White; - - hitGreat.Colour = (score.Statistics[HitResult.Great] == 0) ? Color4.Gray : Color4.White; - hitGood.Colour = (score.Statistics[HitResult.Good] == 0) ? Color4.Gray : Color4.White; - hitMeh.Colour = (score.Statistics[HitResult.Meh] == 0) ? Color4.Gray : Color4.White; - hitMiss.Colour = (score.Statistics[HitResult.Miss] == 0) ? Color4.Gray : Color4.White; - - if (index % 2 == 0) + if (index % 2 != 0) background.Alpha = 0; - - foreach (Mod mod in score.Mods) - modsContainer.Add(new ModIcon(mod) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Scale = new Vector2(0.3f), - }); } [BackgroundDependencyLoader] @@ -251,5 +115,103 @@ namespace osu.Game.Overlays.BeatmapSet.Scores base.OnHoverLost(e); } } + + private class DrawableScoreData : ScoreTableLine + { + public DrawableScoreData(int index, APIScoreInfo score, int maxModsAmount) : base(maxModsAmount) + { + SpriteText scoreText; + SpriteText accuracy; + SpriteText hitGreat; + SpriteText hitGood; + SpriteText hitMeh; + SpriteText hitMiss; + + FillFlowContainer modsContainer; + + RankContainer.Add(new SpriteText + { + Text = $"#{index + 1}", + Font = @"Exo2.0-Bold", + TextSize = text_size, + }); + DrawableRankContainer.Add(new DrawableRank(score.Rank) + { + Size = new Vector2(30, 20), + FillMode = FillMode.Fit, + }); + ScoreContainer.Add(scoreText = new SpriteText + { + Text = $@"{score.TotalScore:N0}", + TextSize = text_size, + }); + AccuracyContainer.Add(accuracy = new SpriteText + { + Text = $@"{score.Accuracy:P2}", + TextSize = text_size, + }); + FlagContainer.Add(new DrawableFlag(score.User.Country) + { + Size = new Vector2(20, 13), + }); + PlayerContainer.Add(new ClickableScoreUsername + { + User = score.User, + }); + MaxComboContainer.Add(new SpriteText + { + Text = $@"{score.MaxCombo:N0}x", + TextSize = text_size, + }); + HitGreatContainer.Add(hitGreat = new SpriteText + { + Text = $"{score.Statistics[HitResult.Great]}", + TextSize = text_size, + }); + HitGoodContainer.Add(hitGood = new SpriteText + { + Text = $"{score.Statistics[HitResult.Good]}", + TextSize = text_size, + }); + HitMehContainer.Add(hitMeh = new SpriteText + { + Text = $"{score.Statistics[HitResult.Meh]}", + TextSize = text_size, + }); + HitMissContainer.Add(hitMiss = new SpriteText + { + Text = $"{score.Statistics[HitResult.Miss]}", + TextSize = text_size, + }); + PPContainer.Add(new SpriteText + { + Text = $@"{score.PP:N0}", + TextSize = text_size, + }); + ModsContainer.Add(modsContainer = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + }); + + if (index == 0) + scoreText.Font = @"Exo2.0-Bold"; + + accuracy.Colour = (score.Accuracy == 1) ? Color4.GreenYellow : Color4.White; + hitGreat.Colour = (score.Statistics[HitResult.Great] == 0) ? Color4.Gray : Color4.White; + hitGood.Colour = (score.Statistics[HitResult.Good] == 0) ? Color4.Gray : Color4.White; + hitMeh.Colour = (score.Statistics[HitResult.Meh] == 0) ? Color4.Gray : Color4.White; + hitMiss.Colour = (score.Statistics[HitResult.Miss] == 0) ? Color4.Gray : Color4.White; + + foreach (Mod mod in score.Mods) + modsContainer.Add(new ModIcon(mod) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.3f), + }); + } + } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index d2421ec606..7623153710 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -188,7 +188,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Width = 0.7f, + Width = 0.65f, Child = new FillFlowContainer { AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableLine.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableLine.cs new file mode 100644 index 0000000000..d57225b541 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableLine.cs @@ -0,0 +1,183 @@ +// Copyright (c) ppy Pty Ltd . 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; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class ScoreTableLine : GridContainer + { + private const float rank_position = 30; + private const float drawable_rank_position = 45; + private const float score_position = 90; + private const float accuracy_position = 170; + private const float flag_position = 220; + private const float player_position = 250; + + private const float max_combo_position = 0.1f; + private const float hit_great_position = 0.3f; + private const float hit_good_position = 0.45f; + private const float hit_meh_position = 0.6f; + private const float hit_miss_position = 0.75f; + private const float pp_position = 0.9f; + + protected readonly Container RankContainer; + protected readonly Container DrawableRankContainer; + protected readonly Container ScoreContainer; + protected readonly Container AccuracyContainer; + protected readonly Container FlagContainer; + protected readonly Container PlayerContainer; + protected readonly Container MaxComboContainer; + protected readonly Container HitGreatContainer; + protected readonly Container HitGoodContainer; + protected readonly Container HitMehContainer; + protected readonly Container HitMissContainer; + protected readonly Container PPContainer; + protected readonly Container ModsContainer; + + public ScoreTableLine(int maxModsAmount) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + RowDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 25), + }; + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 300), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }; + Content = new[] + { + new Drawable[] + { + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + RankContainer = new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + X = rank_position, + }, + DrawableRankContainer = new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + X = drawable_rank_position, + }, + ScoreContainer = new Container + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + X = score_position, + }, + AccuracyContainer = new Container + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + X = accuracy_position, + }, + FlagContainer = new Container + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + X = flag_position, + }, + PlayerContainer = new Container + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + X = player_position, + } + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + MaxComboContainer = new Container + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = max_combo_position, + }, + HitGreatContainer = new Container + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = hit_great_position, + }, + HitGoodContainer = new Container + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = hit_good_position, + }, + HitMehContainer = new Container + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = hit_meh_position, + }, + HitMissContainer = new Container + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = hit_miss_position, + }, + PPContainer = new Container + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = pp_position, + } + } + }, + new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = ModsContainer = new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + X = -30 * maxModsAmount, + } + } + } + }; + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs index 04934185a1..b7c9742764 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs @@ -1,107 +1,71 @@ // Copyright (c) ppy Pty Ltd . 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.Framework.Graphics.Sprites; namespace osu.Game.Overlays.BeatmapSet.Scores { - public class ScoreTextLine : Container + public class ScoreTextLine : ScoreTableLine { private const float text_size = 12; - public const float RANK_POSITION = 30; - public const float SCORE_POSITION = 90; - public const float ACCURACY_POSITION = 170; - public const float PLAYER_POSITION = 270; - public const float MAX_COMBO_POSITION = 0.5f; - public const float HIT_GREAT_POSITION = 0.6f; - public const float HIT_GOOD_POSITION = 0.65f; - public const float HIT_MEH_POSITION = 0.7f; - public const float HIT_MISS_POSITION = 0.75f; - public const float PP_POSITION = 0.8f; - - public ScoreTextLine(int maxModsAmount) + public ScoreTextLine(int maxModsAmount) : base(maxModsAmount) { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Children = new Drawable[] + RankContainer.Add(new SpriteText { - new ScoreText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, - Text = "rank".ToUpper(), - X = RANK_POSITION, - }, - new ScoreText - { - Text = "score".ToUpper(), - X = SCORE_POSITION, - }, - new ScoreText - { - Text = "accuracy".ToUpper(), - X = ACCURACY_POSITION, - }, - new ScoreText - { - Text = "player".ToUpper(), - X = PLAYER_POSITION, - }, - new ScoreText - { - Text = "max combo".ToUpper(), - X = MAX_COMBO_POSITION, - RelativePositionAxes = Axes.X, - }, - new ScoreText - { - Text = "300", - RelativePositionAxes = Axes.X, - X = HIT_GREAT_POSITION, - }, - new ScoreText - { - Text = "100".ToUpper(), - RelativePositionAxes = Axes.X, - X = HIT_GOOD_POSITION, - }, - new ScoreText - { - Text = "50".ToUpper(), - RelativePositionAxes = Axes.X, - X = HIT_MEH_POSITION, - }, - new ScoreText - { - Text = "miss".ToUpper(), - RelativePositionAxes = Axes.X, - X = HIT_MISS_POSITION, - }, - new ScoreText - { - Text = "pp".ToUpper(), - RelativePositionAxes = Axes.X, - X = PP_POSITION, - }, - new ScoreText - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreLeft, - Text = "mods".ToUpper(), - X = -30 * maxModsAmount, - }, - }; - } - - private class ScoreText : SpriteText - { - public ScoreText() + Text = @"rank".ToUpper(), + TextSize = text_size, + }); + ScoreContainer.Add(new SpriteText { - TextSize = text_size; - } + Text = @"score".ToUpper(), + TextSize = text_size, + }); + AccuracyContainer.Add(new SpriteText + { + Text = @"accuracy".ToUpper(), + TextSize = text_size, + }); + PlayerContainer.Add(new SpriteText + { + Text = @"player".ToUpper(), + TextSize = text_size, + }); + MaxComboContainer.Add(new SpriteText + { + Text = @"max combo".ToUpper(), + TextSize = text_size, + }); + HitGreatContainer.Add(new SpriteText + { + Text = "300".ToUpper(), + TextSize = text_size, + }); + HitGoodContainer.Add(new SpriteText + { + Text = "100".ToUpper(), + TextSize = text_size, + }); + HitMehContainer.Add(new SpriteText + { + Text = "50".ToUpper(), + TextSize = text_size, + }); + HitMissContainer.Add(new SpriteText + { + Text = @"miss".ToUpper(), + TextSize = text_size, + }); + PPContainer.Add(new SpriteText + { + Text = @"pp".ToUpper(), + TextSize = text_size, + }); + ModsContainer.Add(new SpriteText + { + Text = @"mods".ToUpper(), + TextSize = text_size, + }); } } } From 4d489da03280c51ca1ff2b88f9d00d4dcdf2e715 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Sun, 10 Feb 2019 04:16:56 +0300 Subject: [PATCH 038/623] small visual improvements --- .../BeatmapSet/Scores/DrawableTopScore.cs | 12 ++--- .../BeatmapSet/Scores/ScoreTableLine.cs | 2 +- .../BeatmapSet/Scores/ScoreTextLine.cs | 48 +++++++++---------- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 7623153710..e4486b1514 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -200,8 +200,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { new FillFlowContainer { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(margin, 0), @@ -210,15 +210,15 @@ namespace osu.Game.Overlays.BeatmapSet.Scores hitGreat = new SmallInfoColumn("300", 20), hitGood = new SmallInfoColumn("100", 20), hitMeh = new SmallInfoColumn("50", 20), - hitMiss = new SmallInfoColumn("miss", 20), + hitMiss = new SmallInfoColumn("misses", 20), pp = new SmallInfoColumn("pp", 20), modsInfo = new ModsInfoColumn("mods"), } }, new FillFlowContainer { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(margin, 0), @@ -347,7 +347,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { TextSize = 12, Text = header.ToUpper(), - Font = @"Exo2.0-Bold", + Font = @"Exo2.0-Black", } }, new Container diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableLine.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableLine.cs index d57225b541..5c4723eae1 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableLine.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableLine.cs @@ -173,7 +173,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Anchor = Anchor.CentreRight, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - X = -30 * maxModsAmount, + X = -30 * ((maxModsAmount == 0) ? 1 : maxModsAmount), } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs index b7c9742764..9fd12df3a8 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs @@ -7,65 +7,63 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { public class ScoreTextLine : ScoreTableLine { - private const float text_size = 12; - public ScoreTextLine(int maxModsAmount) : base(maxModsAmount) { - RankContainer.Add(new SpriteText + RankContainer.Add(new ScoreText { Text = @"rank".ToUpper(), - TextSize = text_size, }); - ScoreContainer.Add(new SpriteText + ScoreContainer.Add(new ScoreText { Text = @"score".ToUpper(), - TextSize = text_size, }); - AccuracyContainer.Add(new SpriteText + AccuracyContainer.Add(new ScoreText { Text = @"accuracy".ToUpper(), - TextSize = text_size, }); - PlayerContainer.Add(new SpriteText + PlayerContainer.Add(new ScoreText { Text = @"player".ToUpper(), - TextSize = text_size, }); - MaxComboContainer.Add(new SpriteText + MaxComboContainer.Add(new ScoreText { Text = @"max combo".ToUpper(), - TextSize = text_size, }); - HitGreatContainer.Add(new SpriteText + HitGreatContainer.Add(new ScoreText { Text = "300".ToUpper(), - TextSize = text_size, }); - HitGoodContainer.Add(new SpriteText + HitGoodContainer.Add(new ScoreText { Text = "100".ToUpper(), - TextSize = text_size, }); - HitMehContainer.Add(new SpriteText + HitMehContainer.Add(new ScoreText { Text = "50".ToUpper(), - TextSize = text_size, }); - HitMissContainer.Add(new SpriteText + HitMissContainer.Add(new ScoreText { - Text = @"miss".ToUpper(), - TextSize = text_size, + Text = @"misses".ToUpper(), }); - PPContainer.Add(new SpriteText + PPContainer.Add(new ScoreText { Text = @"pp".ToUpper(), - TextSize = text_size, }); - ModsContainer.Add(new SpriteText + ModsContainer.Add(new ScoreText { Text = @"mods".ToUpper(), - TextSize = text_size, }); } + + private class ScoreText : SpriteText + { + private const float text_size = 12; + + public ScoreText() + { + TextSize = text_size; + Font = @"Exo2.0-Black"; + } + } } } From 3c999d64d451db3ab3163cc6100790f967a9a11c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Mar 2019 16:09:21 +0900 Subject: [PATCH 039/623] Fix post-merge errors --- .../Profile/Header/CenterHeaderContainer.cs | 20 +++++++++---------- .../Profile/Header/ProfileHeaderTabControl.cs | 16 +++++++-------- osu.Game/Overlays/Profile/ProfileHeader.cs | 10 ++++------ 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs index 30671487d3..fc330a03a9 100644 --- a/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -108,8 +108,7 @@ namespace osu.Game.Overlays.Profile.Header { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - TextSize = 16, - Font = "Exo2.0-Bold" + Font = OsuFont.GetFont(weight: FontWeight.Bold) } } } @@ -182,10 +181,9 @@ namespace osu.Game.Overlays.Profile.Header }, levelBadgeText = new OsuSpriteText { - TextSize = 20, - Font = "Exo2.0-Medium", Anchor = Anchor.Centre, Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium) } } }, @@ -215,8 +213,7 @@ namespace osu.Game.Overlays.Profile.Header { Anchor = Anchor.BottomRight, Origin = Anchor.TopRight, - Font = "Exo2.0-Bold", - TextSize = 12, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold) } } }, @@ -248,9 +245,12 @@ namespace osu.Game.Overlays.Profile.Header } }; - DetailsVisible.ValueChanged += newValue => expandButtonIcon.Icon = newValue ? FontAwesome.fa_chevron_down : FontAwesome.fa_chevron_up; - DetailsVisible.ValueChanged += newValue => hiddenDetailContainer.Alpha = newValue ? 1 : 0; - DetailsVisible.ValueChanged += newValue => expandedDetailContainer.Alpha = newValue ? 0 : 1; + DetailsVisible.ValueChanged += visible => + { + expandButtonIcon.Icon = visible.NewValue ? FontAwesome.fa_chevron_down : FontAwesome.fa_chevron_up; + hiddenDetailContainer.Alpha = visible.NewValue ? 1 : 0; + expandedDetailContainer.Alpha = visible.NewValue ? 0 : 1; + }; } private void updateDisplay() diff --git a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs index fd7124f20f..a8e50e00fe 100644 --- a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs +++ b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; @@ -36,7 +37,7 @@ namespace osu.Game.Overlays.Profile.Header } } - public MarginPadding Padding + public new MarginPadding Padding { get => TabContainer.Padding; set => TabContainer.Padding = value; @@ -78,7 +79,7 @@ namespace osu.Game.Overlays.Profile.Header accentColour = value; bar.Colour = value; - if (!Active) text.Colour = value; + if (!Active.Value) text.Colour = value; } } @@ -96,8 +97,7 @@ namespace osu.Game.Overlays.Profile.Header Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, Text = value, - TextSize = 14, - Font = "Exo2.0-Bold", + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold) }, bar = new Circle { @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.Profile.Header protected override bool OnHover(HoverEvent e) { - if (!Active) + if (!Active.Value) onActivated(true); return base.OnHover(e); } @@ -121,7 +121,7 @@ namespace osu.Game.Overlays.Profile.Header { base.OnHoverLost(e); - if (!Active) + if (!Active.Value) OnDeactivated(); } @@ -134,7 +134,7 @@ namespace osu.Game.Overlays.Profile.Header { text.FadeColour(AccentColour, 120, Easing.InQuad); bar.ResizeHeightTo(0, 120, Easing.InQuad); - text.Font = "Exo2.0-Medium"; + text.Font = text.Font.With(Typeface.Exo, weight: FontWeight.Medium); } private void onActivated(bool fake = false) @@ -142,7 +142,7 @@ namespace osu.Game.Overlays.Profile.Header text.FadeColour(Color4.White, 120, Easing.InQuad); bar.ResizeHeightTo(7.5f, 120, Easing.InQuad); if (!fake) - text.Font = "Exo2.0-Bold"; + text.Font = text.Font.With(Typeface.Exo, weight: FontWeight.Bold); } } } diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 21f1989606..fdb515270b 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -15,6 +14,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; +using osu.Framework.Bindables; namespace osu.Game.Overlays.Profile { @@ -145,7 +145,7 @@ namespace osu.Game.Overlays.Profile infoTabControl.AddItem("Modding"); centerHeaderContainer.DetailsVisible.BindTo(DetailsVisible); - DetailsVisible.ValueChanged += newValue => detailHeaderContainer.Alpha = newValue ? 0 : 1; + DetailsVisible.ValueChanged += visible => detailHeaderContainer.Alpha = visible.NewValue ? 0 : 1; } [BackgroundDependencyLoader] @@ -224,13 +224,11 @@ namespace osu.Game.Overlays.Profile }, title = new OsuSpriteText { - Font = "Exo2.0-Bold", - TextSize = big ? 14 : 12, + Font = OsuFont.GetFont(size: big ? 14 : 12, weight: FontWeight.Bold) }, content = new OsuSpriteText { - Font = "Exo2.0-Light", - TextSize = big ? 40 : 18, + Font = OsuFont.GetFont(size: big ? 40 : 18, weight: FontWeight.Light) }, new Container //Add a minimum size to the FillFlowContainer { From e3d463a141269e4909b49ed6bf4c277de84dab44 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Mar 2019 16:30:56 +0900 Subject: [PATCH 040/623] Formatting fixes --- .../Graphics/Containers/OsuHoverContainer.cs | 2 +- .../Profile/Header/BottomHeaderContainer.cs | 9 ++++--- .../Profile/Header/CenterHeaderContainer.cs | 8 ++++-- .../Profile/Header/DetailHeaderContainer.cs | 8 ++++-- .../Profile/Header/MedalHeaderContainer.cs | 6 ++++- .../Profile/Header/TopHeaderContainer.cs | 6 ++++- osu.Game/Users/User.cs | 3 +++ osu.Game/Users/UserStatistics.cs | 25 +++++++++++++------ 8 files changed, 49 insertions(+), 18 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs index f94fb2540c..1e0b56dae3 100644 --- a/osu.Game/Graphics/Containers/OsuHoverContainer.cs +++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs @@ -33,7 +33,7 @@ namespace osu.Game.Graphics.Containers [BackgroundDependencyLoader] private void load(OsuColour colours) { - if(HoverColour == default) + if (HoverColour == default) HoverColour = colours.Yellow; } diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index df0409272f..7bb1b8acc2 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -24,13 +24,17 @@ namespace osu.Game.Overlays.Profile.Header private Color4 linkBlue, communityUserGrayGreenLighter; private User user; + public User User { get => user; set { - if (user == value) return; + if (user == value) + return; + user = value; + updateDisplay(); } } @@ -131,9 +135,8 @@ namespace osu.Game.Overlays.Profile.Header }); } else - { bottomLinkContainer.AddText(" " + content, bold); - } + addSpacer(bottomLinkContainer); } diff --git a/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs index fc330a03a9..ca615ccf4b 100644 --- a/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs @@ -48,13 +48,17 @@ namespace osu.Game.Overlays.Profile.Header private APIAccess apiAccess { get; set; } private User user; + public User User { get => user; set { - if (user == value) return; + if (user == value) + return; + user = value; + updateDisplay(); } } @@ -183,7 +187,7 @@ namespace osu.Game.Overlays.Profile.Header { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium) + Font = OsuFont.GetFont(size: 20) } } }, diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index 124299b0ba..a7d21bcdf3 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -26,13 +26,17 @@ namespace osu.Game.Overlays.Profile.Header private RankGraph rankGraph; private User user; + public User User { get => user; set { - if (user == value) return; + if (user == value) + return; + user = value; + updateDisplay(); } } @@ -181,7 +185,7 @@ namespace osu.Game.Overlays.Profile.Header totalPlayTimeTooltip.TooltipText = (user?.Statistics?.PlayTime ?? 0) / 3600 + " hours"; foreach (var scoreRankInfo in scoreRankInfos) - scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount.GetForScoreRank(scoreRankInfo.Key) ?? 0; + scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0; detailGlobalRank.Content = user?.Statistics?.Ranks.Global?.ToString("#,##0") ?? "-"; detailCountryRank.Content = user?.Statistics?.Ranks.Country?.ToString("#,##0") ?? "-"; diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs index 5a54270b80..45c1e1e208 100644 --- a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -22,13 +22,17 @@ namespace osu.Game.Overlays.Profile.Header private FillFlowContainer badgeFlowContainer; private User user; + public User User { get => user; set { - if (user == value) return; + if (user == value) + return; + user = value; + updateDisplay(); } } diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 4186d08729..13a7951170 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -28,13 +28,17 @@ namespace osu.Game.Overlays.Profile.Header private const float avatar_size = 110; private User user; + public User User { get => user; set { - if (user == value) return; + if (user == value) + return; + user = value; + updateDisplay(); } } diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index d8e1ec81b5..314684069a 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -186,10 +186,13 @@ namespace osu.Game.Users { [Description("Keyboard")] Keyboard, + [Description("Mouse")] Mouse, + [Description("Tablet")] Tablet, + [Description("Touch Screen")] Touch, } diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index a69cd794b3..752534a80d 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -77,16 +77,25 @@ namespace osu.Game.Users [JsonProperty(@"a")] public int A; - public int GetForScoreRank(ScoreRank rank) + public int this[ScoreRank rank] { - switch (rank) + get { - case ScoreRank.XH: return SSPlus; - case ScoreRank.X: return SS; - case ScoreRank.SH: return SPlus; - case ScoreRank.S: return S; - case ScoreRank.A: return A; - default: throw new ArgumentException($"API does not return {rank.ToString()}"); + switch (rank) + { + case ScoreRank.XH: + return SSPlus; + case ScoreRank.X: + return SS; + case ScoreRank.SH: + return SPlus; + case ScoreRank.S: + return S; + case ScoreRank.A: + return A; + default: + throw new ArgumentException($"API does not return {rank.ToString()}"); + } } } } From 22423f60d42efe96c985289daa01fbc6900bd566 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Mar 2019 19:27:42 +0900 Subject: [PATCH 041/623] Fix DI not working --- osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs b/osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs index 6f164890f9..49ffa2d63f 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual typeof(ProfileHeader), typeof(RankGraph), typeof(LineGraph), - typeof(SupporterIcon) + typeof(ProfileHeaderTabControl), }; [Resolved] From e5e454ddcda5dda65221723e75116cd56dcc08e8 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 7 Mar 2019 16:17:12 +0900 Subject: [PATCH 042/623] Don't perform lookup of beatmap stats unless an online id is present --- osu.Game/Screens/Select/BeatmapDetails.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 604d7a132b..d9334d65ac 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -175,7 +175,7 @@ namespace osu.Game.Screens.Select private void updateStatistics() { - if (Beatmap == null) + if (Beatmap?.OnlineBeatmapID == null) { clearStats(); return; From 054db480897a6fbea1a41f6ec90c5a7c9f3435c9 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 7 Mar 2019 16:59:43 +0900 Subject: [PATCH 043/623] Move online id null check to only bypass metrics lookup --- osu.Game/Screens/Select/BeatmapDetails.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index d9334d65ac..07db073edb 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -175,7 +175,7 @@ namespace osu.Game.Screens.Select private void updateStatistics() { - if (Beatmap?.OnlineBeatmapID == null) + if (Beatmap == null) { clearStats(); return; @@ -188,7 +188,7 @@ namespace osu.Game.Screens.Select tags.Text = Beatmap.Metadata.Tags; var requestedBeatmap = Beatmap; - if (requestedBeatmap.Metrics == null) + if (requestedBeatmap.Metrics == null && requestedBeatmap.OnlineBeatmapID != null) { var lookup = new GetBeatmapDetailsRequest(requestedBeatmap); lookup.Success += res => From 8c0e325d8bc54ae802668c483ffdf30a6acbdd0f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Mar 2019 16:40:37 +0900 Subject: [PATCH 044/623] Reduce + make beatmap scores testcase work --- .../Visual/TestCaseBeatmapScoresContainer.cs | 147 +----------------- 1 file changed, 6 insertions(+), 141 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs index 4ad5b2dc35..072bbff4cf 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs @@ -13,9 +13,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Users; using System.Collections.Generic; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using NUnit.Framework; @@ -67,6 +65,7 @@ namespace osu.Game.Tests.Visual new OsuModHardRock(), }, Rank = ScoreRank.XH, + MaxCombo = 1234, TotalScore = 1234567890, Accuracy = 1, }, @@ -89,6 +88,7 @@ namespace osu.Game.Tests.Visual new OsuModFlashlight(), }, Rank = ScoreRank.S, + MaxCombo = 1234, TotalScore = 1234789, Accuracy = 0.9997, }, @@ -110,6 +110,7 @@ namespace osu.Game.Tests.Visual new OsuModHidden(), }, Rank = ScoreRank.B, + MaxCombo = 1234, TotalScore = 12345678, Accuracy = 0.9854, }, @@ -130,6 +131,7 @@ namespace osu.Game.Tests.Visual new OsuModDoubleTime(), }, Rank = ScoreRank.C, + MaxCombo = 1234, TotalScore = 1234567, Accuracy = 0.8765, }, @@ -146,6 +148,7 @@ namespace osu.Game.Tests.Visual }, }, Rank = ScoreRank.F, + MaxCombo = 1234, TotalScore = 123456, Accuracy = 0.6543, }, @@ -159,145 +162,7 @@ namespace osu.Game.Tests.Visual s.Statistics.Add(HitResult.Miss, RNG.Next(2000)); } - IEnumerable anotherScores = new[] - { - new APIScoreInfo - { - User = new User - { - Id = 4608074, - Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - }, - Rank = ScoreRank.S, - TotalScore = 1234789, - Accuracy = 0.9997, - }, - new APIScoreInfo - { - User = new User - { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - new OsuModHardRock(), - }, - Rank = ScoreRank.XH, - TotalScore = 1234567890, - Accuracy = 1, - }, - new APIScoreInfo - { - User = new User - { - Id = 7151382, - Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, - }, - Rank = ScoreRank.F, - TotalScore = 123456, - Accuracy = 0.6543, - }, - new APIScoreInfo - { - User = new User - { - Id = 1014222, - Username = @"eLy", - Country = new Country - { - FullName = @"Japan", - FlagName = @"JP", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - }, - Rank = ScoreRank.B, - TotalScore = 12345678, - Accuracy = 0.9854, - }, - new APIScoreInfo - { - User = new User - { - Id = 1541390, - Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - }, - Rank = ScoreRank.C, - TotalScore = 1234567, - Accuracy = 0.8765, - }, - }; - foreach (var s in anotherScores) - { - s.Statistics.Add(HitResult.Great, RNG.Next(2000)); - s.Statistics.Add(HitResult.Good, RNG.Next(2000)); - s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); - s.Statistics.Add(HitResult.Miss, RNG.Next(2000)); - } - - var topScoreInfo = new APIScoreInfo - { - User = new User - { - Id = 2705430, - Username = @"Mooha", - Country = new Country - { - FullName = @"France", - FlagName = @"FR", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModFlashlight(), - new OsuModHardRock(), - }, - Rank = ScoreRank.B, - TotalScore = 987654321, - Accuracy = 0.8487, - }; - topScoreInfo.Statistics.Add(HitResult.Great, RNG.Next(2000)); - topScoreInfo.Statistics.Add(HitResult.Good, RNG.Next(2000)); - topScoreInfo.Statistics.Add(HitResult.Meh, RNG.Next(2000)); - topScoreInfo.Statistics.Add(HitResult.Miss, RNG.Next(2000)); + scoresContainer.Scores = scores; } [BackgroundDependencyLoader] From a40ffcc6920d5bc25f0782727a1b967e1b1fc951 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Mar 2019 16:44:39 +0900 Subject: [PATCH 045/623] Apply formatting adjustments --- .../Visual/TestCaseBeatmapScoresContainer.cs | 8 ++++-- .../Scores/ClickableUserContainer.cs | 1 - .../BeatmapSet/Scores/DrawableScore.cs | 3 ++- .../BeatmapSet/Scores/DrawableTopScore.cs | 25 +++++++++++++------ .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 6 ++--- .../BeatmapSet/Scores/ScoreTextLine.cs | 3 ++- 6 files changed, 29 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs index 072bbff4cf..ead743a283 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs @@ -26,10 +26,9 @@ namespace osu.Game.Tests.Visual public TestCaseBeatmapScoresContainer() { - Container container; ScoresContainer scoresContainer; - Child = container = new Container + Child = new Container { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -65,6 +64,7 @@ namespace osu.Game.Tests.Visual new OsuModHardRock(), }, Rank = ScoreRank.XH, + PP = 200, MaxCombo = 1234, TotalScore = 1234567890, Accuracy = 1, @@ -88,6 +88,7 @@ namespace osu.Game.Tests.Visual new OsuModFlashlight(), }, Rank = ScoreRank.S, + PP = 190, MaxCombo = 1234, TotalScore = 1234789, Accuracy = 0.9997, @@ -110,6 +111,7 @@ namespace osu.Game.Tests.Visual new OsuModHidden(), }, Rank = ScoreRank.B, + PP = 180, MaxCombo = 1234, TotalScore = 12345678, Accuracy = 0.9854, @@ -131,6 +133,7 @@ namespace osu.Game.Tests.Visual new OsuModDoubleTime(), }, Rank = ScoreRank.C, + PP = 170, MaxCombo = 1234, TotalScore = 1234567, Accuracy = 0.8765, @@ -148,6 +151,7 @@ namespace osu.Game.Tests.Visual }, }, Rank = ScoreRank.F, + PP = 160, MaxCombo = 1234, TotalScore = 123456, Accuracy = 0.6543, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs index 3f1c8f56f5..cf1c3d7fcf 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Game.Graphics; using osu.Game.Users; namespace osu.Game.Overlays.BeatmapSet.Scores diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index 8741fd8dfe..19e999547b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -118,7 +118,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private class DrawableScoreData : ScoreTableLine { - public DrawableScoreData(int index, APIScoreInfo score, int maxModsAmount) : base(maxModsAmount) + public DrawableScoreData(int index, APIScoreInfo score, int maxModsAmount) + : base(maxModsAmount) { SpriteText scoreText; SpriteText accuracy; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index e4486b1514..3712f38d02 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -56,13 +56,15 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly ModsInfoColumn modsInfo; private APIScoreInfo score; + public APIScoreInfo Score { - get { return score; } + get => score; set { if (score == value) return; + score = value; avatar.User = username.User = score.User; @@ -274,9 +276,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { if (text.TextSize == value) return; + text.TextSize = value; } - get { return text.TextSize; } + get => text.TextSize; } public ClickableTopScoreUsername() @@ -386,7 +389,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - public ModsInfoColumn(string header) : base(header) + public ModsInfoColumn(string header) + : base(header) { AutoSizeAxes = Axes.Both; Add(modsContainer = new FillFlowContainer @@ -409,12 +413,14 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { if (valueText.Text == value) return; + valueText.Text = value; } - get { return valueText.Text; } + get => valueText.Text; } - protected TextInfoColumn(string header, float valueTextSize = 25) : base(header) + protected TextInfoColumn(string header, float valueTextSize = 25) + : base(header) { Add(valueText = new SpriteText { @@ -425,7 +431,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private class AutoSizedInfoColumn : TextInfoColumn { - public AutoSizedInfoColumn(string header, float valueTextSize = 25) : base(header, valueTextSize) + public AutoSizedInfoColumn(string header, float valueTextSize = 25) + : base(header, valueTextSize) { AutoSizeAxes = Axes.Both; } @@ -435,7 +442,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { private const float width = 70; - public MediumInfoColumn(string header, float valueTextSize = 25) : base(header, valueTextSize) + public MediumInfoColumn(string header, float valueTextSize = 25) + : base(header, valueTextSize) { Width = width; } @@ -445,7 +453,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { private const float width = 40; - public SmallInfoColumn(string header, float valueTextSize = 25) : base(header, valueTextSize) + public SmallInfoColumn(string header, float valueTextSize = 25) + : base(header, valueTextSize) { Width = width; } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 1b20b2c382..d69bc39bc6 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -11,6 +11,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public class ScoreTable : FillFlowContainer { private IEnumerable scores; + public IEnumerable Scores { set @@ -31,10 +32,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores foreach (var s in scores) Add(new DrawableScore(index++, s, maxModsAmount)); } - get - { - return scores; - } + get => scores; } public ScoreTable() diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs index 9fd12df3a8..9e201bd98d 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs @@ -7,7 +7,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { public class ScoreTextLine : ScoreTableLine { - public ScoreTextLine(int maxModsAmount) : base(maxModsAmount) + public ScoreTextLine(int maxModsAmount) + : base(maxModsAmount) { RankContainer.Add(new ScoreText { From 9a05643ec980732837124698e535396c34ab73bf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Mar 2019 17:18:54 +0900 Subject: [PATCH 046/623] Minor refactorings --- .../BeatmapSet/Scores/DrawableScore.cs | 2 + .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 45 +++--- .../BeatmapSet/Scores/ScoresContainer.cs | 140 +++++++++--------- 3 files changed, 97 insertions(+), 90 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index 19e999547b..a2a9f9a01b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -31,8 +31,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + CornerRadius = 3; Masking = true; + Children = new Drawable[] { background = new Box diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index d69bc39bc6..4723fcaf2b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -5,47 +5,50 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using System.Collections.Generic; +using System.Linq; namespace osu.Game.Overlays.BeatmapSet.Scores { - public class ScoreTable : FillFlowContainer + public class ScoreTable : CompositeDrawable { - private IEnumerable scores; + private readonly FillFlowContainer scoresFlow; + + public ScoreTable() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = scoresFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical + }; + } public IEnumerable Scores { set { - scores = value; + scoresFlow.Clear(); + + if (value == null || !value.Any()) + return; int maxModsAmount = 0; - foreach (var s in scores) + foreach (var s in value) { var scoreModsAmount = s.Mods.Length; if (scoreModsAmount > maxModsAmount) maxModsAmount = scoreModsAmount; } - Add(new ScoreTextLine(maxModsAmount)); + scoresFlow.Add(new ScoreTextLine(maxModsAmount)); int index = 0; - foreach (var s in scores) - Add(new DrawableScore(index++, s, maxModsAmount)); + foreach (var s in value) + scoresFlow.Add(new DrawableScore(index++, s, maxModsAmount)); } - get => scores; - } - - public ScoreTable() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Direction = FillDirection.Vertical; - } - - public void ClearScores() - { - scores = null; - Clear(); } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index d99f391d21..6c90e192ac 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -17,7 +17,7 @@ using System.Linq; namespace osu.Game.Overlays.BeatmapSet.Scores { - public class ScoresContainer : Container + public class ScoresContainer : CompositeDrawable { private const int spacing = 15; private const int fade_duration = 200; @@ -28,77 +28,15 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly DrawableTopScore topScore; private readonly LoadingAnimation loadingAnimation; - private bool loading - { - set => loadingAnimation.FadeTo(value ? 1 : 0, fade_duration); - } - - private IEnumerable scores; - private BeatmapInfo beatmap; - - public IEnumerable Scores - { - get => scores; - set - { - getScoresRequest?.Cancel(); - scores = value; - - updateDisplay(); - } - } - - private GetScoresRequest getScoresRequest; - private APIAccess api; - - public BeatmapInfo Beatmap - { - get => beatmap; - set - { - beatmap = value; - - Scores = null; - - if (beatmap?.OnlineBeatmapID.HasValue != true) - return; - - loading = true; - - getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); - getScoresRequest.Success += r => Schedule(() => Scores = r.Scores); - api.Queue(getScoresRequest); - } - } - - private void updateDisplay() - { - scoreTable.ClearScores(); - - loading = false; - - var scoreCount = scores?.Count() ?? 0; - - if (scoreCount == 0) - { - topScore.Hide(); - return; - } - - topScore.Score = scores.FirstOrDefault(); - topScore.Show(); - - if (scoreCount < 2) - return; - - scoreTable.Scores = scores; - } + [Resolved] + private APIAccess api { get; set; } public ScoresContainer() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Children = new Drawable[] + + InternalChildren = new Drawable[] { background = new Box { @@ -135,12 +73,76 @@ namespace osu.Game.Overlays.BeatmapSet.Scores [BackgroundDependencyLoader] private void load(APIAccess api, OsuColour colours) { - this.api = api; - background.Colour = colours.Gray2; updateDisplay(); } + private bool loading + { + set => loadingAnimation.FadeTo(value ? 1 : 0, fade_duration); + } + + private GetScoresRequest getScoresRequest; + + private IEnumerable scores; + + public IEnumerable Scores + { + get => scores; + set + { + getScoresRequest?.Cancel(); + scores = value; + + updateDisplay(); + } + } + + private BeatmapInfo beatmap; + + public BeatmapInfo Beatmap + { + get => beatmap; + set + { + beatmap = value; + + Scores = null; + + if (beatmap?.OnlineBeatmapID.HasValue != true) + return; + + loading = true; + + getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); + getScoresRequest.Success += r => Schedule(() => Scores = r.Scores); + api.Queue(getScoresRequest); + } + } + + private void updateDisplay() + { + scoreTable.Scores = Enumerable.Empty(); + + loading = false; + + var scoreCount = scores?.Count() ?? 0; + + if (scoreCount == 0) + { + topScore.Hide(); + return; + } + + topScore.Score = scores.FirstOrDefault(); + topScore.Show(); + + if (scoreCount < 2) + return; + + scoreTable.Scores = scores; + } + protected override void Dispose(bool isDisposing) { getScoresRequest?.Cancel(); From 800007c3782b11ebce37a0ac695e763345b9a84e Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 8 Mar 2019 18:17:50 +0900 Subject: [PATCH 047/623] Set DummyWorkingBeatmap difficulties to 0 for better fallback display --- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 0aa1697bf8..f9df025be8 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -26,7 +26,12 @@ namespace osu.Game.Beatmaps Title = "no beatmaps available!" }, BeatmapSet = new BeatmapSetInfo(), - BaseDifficulty = new BeatmapDifficulty(), + BaseDifficulty = new BeatmapDifficulty + { + DrainRate = 0, + CircleSize = 0, + OverallDifficulty = 0, + }, Ruleset = new DummyRulesetInfo() }) { From 192c257aac669402ac70bd34f341badaac07ad36 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 8 Mar 2019 18:32:39 +0900 Subject: [PATCH 048/623] Add test steps for BeatmapDetailArea --- .../Visual/TestCaseBeatmapDetailArea.cs | 133 +++++++++++++++++- 1 file changed, 132 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs index c23075a127..ff39ad5c0d 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Game.Beatmaps; using osu.Game.Screens.Select; using osuTK; @@ -14,12 +16,141 @@ namespace osu.Game.Tests.Visual { public TestCaseBeatmapDetailArea() { - Add(new BeatmapDetailArea + BeatmapDetailArea detailsArea; + Add(detailsArea = new BeatmapDetailArea { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(550f, 450f), }); + + AddStep("all metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap + { + BeatmapInfo = + { + Version = "All Metrics", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has all the metrics", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 7, + DrainRate = 1, + OverallDifficulty = 5.7f, + ApproachRate = 3.5f, + }, + StarDifficulty = 5.3f, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + } + } + ); + + AddStep("all except source", () => detailsArea.Beatmap = new DummyWorkingBeatmap + { + BeatmapInfo = + { + Version = "All Metrics", + Metadata = new BeatmapMetadata + { + Tags = "this beatmap has all the metrics", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 7, + DrainRate = 1, + OverallDifficulty = 5.7f, + ApproachRate = 3.5f, + }, + StarDifficulty = 5.3f, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + } + }); + + AddStep("ratings", () => detailsArea.Beatmap = new DummyWorkingBeatmap + { + BeatmapInfo = + { + Version = "Only Ratings", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has ratings metrics but not retries or fails", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 6, + DrainRate = 9, + OverallDifficulty = 6, + ApproachRate = 6, + }, + StarDifficulty = 4.8f, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + }, + } + }); + + AddStep("fails+retries", () => detailsArea.Beatmap = new DummyWorkingBeatmap + { + BeatmapInfo = + { + Version = "Only Retries and Fails", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has retries and fails but no ratings", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 3.7f, + DrainRate = 6, + OverallDifficulty = 6, + ApproachRate = 7, + }, + StarDifficulty = 2.91f, + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + } + }); + + AddStep("null metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap + { + BeatmapInfo = + { + Version = "No Metrics", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has no metrics", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 5, + DrainRate = 5, + OverallDifficulty = 5.5f, + ApproachRate = 6.5f, + }, + StarDifficulty = 1.97f, + } + }); + + AddStep("null beatmap", () => detailsArea.Beatmap = null); } } } From 8e5816805c11c67a3546952bfbef51a1dcdddfa0 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 8 Mar 2019 18:44:35 +0900 Subject: [PATCH 049/623] Fix showing outdated data for non-online beatmaps --- osu.Game/Screens/Select/BeatmapDetails.cs | 70 +++++++---------------- 1 file changed, 21 insertions(+), 49 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 07db073edb..6057ba382b 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -175,21 +175,21 @@ namespace osu.Game.Screens.Select private void updateStatistics() { + advanced.Beatmap = Beatmap; + description.Text = Beatmap?.Version; + source.Text = Beatmap?.Metadata?.Source; + tags.Text = Beatmap?.Metadata?.Tags; + if (Beatmap == null) { - clearStats(); + ratingsContainer.FadeOut(transition_duration); + failRetryContainer.FadeOut(transition_duration); return; } - ratingsContainer.FadeIn(transition_duration); - advanced.Beatmap = Beatmap; - description.Text = Beatmap.Version; - source.Text = Beatmap.Metadata.Source; - tags.Text = Beatmap.Metadata.Tags; - - var requestedBeatmap = Beatmap; - if (requestedBeatmap.Metrics == null && requestedBeatmap.OnlineBeatmapID != null) + if (Beatmap.Metrics == null && Beatmap.OnlineBeatmapID != null) { + var requestedBeatmap = Beatmap; var lookup = new GetBeatmapDetailsRequest(requestedBeatmap); lookup.Success += res => { @@ -198,39 +198,35 @@ namespace osu.Game.Screens.Select return; requestedBeatmap.Metrics = res; - Schedule(() => displayMetrics(res)); + Schedule(() => updateMetrics(res)); }; - lookup.Failure += e => Schedule(() => displayMetrics(null)); - + lookup.Failure += e => Schedule(() => updateMetrics(null)); api.Queue(lookup); loading.Show(); } - - displayMetrics(requestedBeatmap.Metrics, false); + else + { + updateMetrics(Beatmap.Metrics); + } } - private void displayMetrics(BeatmapMetrics metrics, bool failOnMissing = true) + private void updateMetrics(BeatmapMetrics metrics) { var hasRatings = metrics?.Ratings?.Any() ?? false; var hasRetriesFails = (metrics?.Retries?.Any() ?? false) && (metrics.Fails?.Any() ?? false); - if (failOnMissing) loading.Hide(); - if (hasRatings) { ratings.Metrics = metrics; - ratings.FadeIn(transition_duration); + ratingsContainer.FadeIn(transition_duration); } - else if (failOnMissing) + else { ratings.Metrics = new BeatmapMetrics { Ratings = new int[10], }; - } - else - { - ratings.FadeTo(0.25f, transition_duration); + ratingsContainer.FadeTo(0.25f, transition_duration); } if (hasRetriesFails) @@ -238,41 +234,17 @@ namespace osu.Game.Screens.Select failRetryGraph.Metrics = metrics; failRetryContainer.FadeIn(transition_duration); } - else if (failOnMissing) + else { failRetryGraph.Metrics = new BeatmapMetrics { Fails = new int[100], Retries = new int[100], }; + failRetryContainer.FadeOut(transition_duration); } - else - { - failRetryContainer.FadeTo(0.25f, transition_duration); - } - } - - private void clearStats() - { - description.Text = null; - source.Text = null; - tags.Text = null; - - advanced.Beatmap = new BeatmapInfo - { - StarDifficulty = 0, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 0, - DrainRate = 0, - OverallDifficulty = 0, - ApproachRate = 0, - }, - }; loading.Hide(); - ratingsContainer.FadeOut(transition_duration); - failRetryContainer.FadeOut(transition_duration); } private class DetailBox : Container From af1c54d995f10429fff030f1393511a95304fc28 Mon Sep 17 00:00:00 2001 From: jorolf Date: Fri, 8 Mar 2019 23:44:01 +0100 Subject: [PATCH 050/623] add ScreenTitle class --- .../Graphics/UserInterface/ScreenTitle.cs | 73 +++++++++++++++++++ osu.Game/Screens/Multi/Header.cs | 41 ++--------- 2 files changed, 79 insertions(+), 35 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/ScreenTitle.cs diff --git a/osu.Game/Graphics/UserInterface/ScreenTitle.cs b/osu.Game/Graphics/UserInterface/ScreenTitle.cs new file mode 100644 index 0000000000..d931d2561a --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ScreenTitle.cs @@ -0,0 +1,73 @@ +// Copyright (c) ppy Pty Ltd . 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.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + public class ScreenTitle : CompositeDrawable, IHasAccentColour + { + private readonly SpriteIcon iconSprite; + private readonly OsuSpriteText titleText, pageText; + + public FontAwesome Icon + { + get => iconSprite.Icon; + set => iconSprite.Icon = value; + } + + public string Title + { + get => titleText.Text; + set => titleText.Text = value; + } + + public string Page + { + get => pageText.Text; + set => pageText.Text = value; + } + + public Color4 AccentColour + { + get => pageText.Colour; + set => pageText.Colour = value; + } + + public ScreenTitle() + { + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + iconSprite = new SpriteIcon + { + Size = new Vector2(25), + Anchor = Anchor.TopLeft, + Origin = Anchor.TopRight, + Margin = new MarginPadding { Right = 10 }, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new[] + { + titleText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 25), + }, + pageText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 25), + } + } + } + }; + } + } +} diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 0e958bf523..668b2f5995 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -7,10 +7,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.SearchableList; -using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Multi @@ -19,7 +17,7 @@ namespace osu.Game.Screens.Multi { public const float HEIGHT = 121; - private readonly OsuSpriteText screenType; + private readonly ScreenTitle title; private readonly HeaderBreadcrumbControl breadcrumbs; public Header(ScreenStack stack) @@ -40,39 +38,12 @@ namespace osu.Game.Screens.Multi Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, Children = new Drawable[] { - new FillFlowContainer + title = new ScreenTitle { Anchor = Anchor.CentreLeft, Origin = Anchor.BottomLeft, - Position = new Vector2(-35f, 5f), - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10f, 0f), - Children = new Drawable[] - { - new SpriteIcon - { - Size = new Vector2(25), - Icon = FontAwesome.fa_osu_multi, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - new OsuSpriteText - { - Text = "multiplayer ", - Font = OsuFont.GetFont(size: 25) - }, - screenType = new OsuSpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.Light, size: 25) - }, - }, - }, - }, + Icon = FontAwesome.fa_osu_multi, + Title = "multiplayer ", }, breadcrumbs = new HeaderBreadcrumbControl(stack) { @@ -87,7 +58,7 @@ namespace osu.Game.Screens.Multi breadcrumbs.Current.ValueChanged += scren => { if (scren.NewValue is IMultiplayerSubScreen multiScreen) - screenType.Text = multiScreen.ShortTitle.ToLowerInvariant(); + title.Page = multiScreen.ShortTitle.ToLowerInvariant(); }; breadcrumbs.Current.TriggerChange(); @@ -96,7 +67,7 @@ namespace osu.Game.Screens.Multi [BackgroundDependencyLoader] private void load(OsuColour colours) { - screenType.Colour = colours.Yellow; + title.AccentColour = colours.Yellow; breadcrumbs.StripColour = colours.Green; } From 2525f5bcb787955ab3a315c62143e76bd26825b7 Mon Sep 17 00:00:00 2001 From: jorolf Date: Sat, 9 Mar 2019 23:58:14 +0100 Subject: [PATCH 051/623] Apply most suggestions --- osu.Game.Tests/Visual/TestCaseUserProfile.cs | 13 -- .../Visual/TestCaseUserProfileHeader.cs | 8 +- .../Profile/Header/BottomHeaderContainer.cs | 26 +-- .../Profile/Header/CenterHeaderContainer.cs | 138 +++------------- .../Profile/Header/DetailHeaderContainer.cs | 40 ++--- .../Overlays/Profile/Header/DrawableBadge.cs | 46 ++++++ .../Profile/Header/MedalHeaderContainer.cs | 63 ++------ .../Profile/Header/OverlinedInfoContainer.cs | 63 ++++++++ .../Profile/Header/ProfileHeaderButton.cs | 50 ++++++ .../Profile/Header/ProfileHeaderTabControl.cs | 8 +- .../Profile/Header/ProfileMessageButton.cs | 57 +++++++ .../Overlays/Profile/Header/SupporterIcon.cs | 48 +++--- .../Profile/Header/TopHeaderContainer.cs | 34 ++-- osu.Game/Overlays/Profile/ProfileHeader.cs | 153 +++--------------- osu.Game/Overlays/UserProfileOverlay.cs | 4 +- osu.Game/Users/UserCoverBackground.cs | 47 ++++-- osu.Game/Users/UserPanel.cs | 4 +- 17 files changed, 375 insertions(+), 427 deletions(-) create mode 100644 osu.Game/Overlays/Profile/Header/DrawableBadge.cs create mode 100644 osu.Game/Overlays/Profile/Header/OverlinedInfoContainer.cs create mode 100644 osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs create mode 100644 osu.Game/Overlays/Profile/Header/ProfileMessageButton.cs diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs index d0c4e495b8..3abb2584ce 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs @@ -87,8 +87,6 @@ namespace osu.Game.Tests.Visual AddStep("Show offline dummy", () => profile.ShowUser(TEST_USER, false)); - checkSupporterTag(false); - AddStep("Show null dummy", () => profile.ShowUser(new User { Username = @"Null", @@ -104,8 +102,6 @@ namespace osu.Game.Tests.Visual CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" }, api.IsLoggedIn)); - checkSupporterTag(true); - AddStep("Show flyte", () => profile.ShowUser(new User { Username = @"flyte", @@ -118,15 +114,6 @@ namespace osu.Game.Tests.Visual AddStep("Show without reload", profile.Show); } - private void checkSupporterTag(bool isSupporter) - { - AddUntilStep(() => profile.Header.User != null, "wait for load"); - if (isSupporter) - AddAssert("is supporter", () => profile.Header.SupporterTag.Alpha == 1); - else - AddAssert("no supporter", () => profile.Header.SupporterTag.Alpha == 0); - } - private class TestUserProfileOverlay : UserProfileOverlay { public new ProfileHeader Header => base.Header; diff --git a/osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs b/osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs index 49ffa2d63f..8f36b4dda8 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs @@ -33,9 +33,9 @@ namespace osu.Game.Tests.Visual header = new ProfileHeader(); Add(header); - AddStep("Show offline dummy", () => header.User = TestCaseUserProfile.TEST_USER); + AddStep("Show offline dummy", () => header.User.Value = TestCaseUserProfile.TEST_USER); - AddStep("Show null dummy", () => header.User = new User + AddStep("Show null dummy", () => header.User.Value = new User { Username = "Null" }); @@ -65,11 +65,11 @@ namespace osu.Game.Tests.Visual if (api.IsLoggedIn) { var request = new GetUserRequest(fallback.Id); - request.Success += user => header.User = user; + request.Success += user => header.User.Value = user; api.Queue(request); } else - header.User = fallback; + header.User.Value = fallback; }); } } diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 7bb1b8acc2..1f9d741b8d 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -17,32 +18,21 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Profile.Header { - public class BottomHeaderContainer : Container + public class BottomHeaderContainer : CompositeDrawable { private LinkFlowContainer bottomTopLinkContainer; private LinkFlowContainer bottomLinkContainer; private Color4 linkBlue, communityUserGrayGreenLighter; - private User user; - - public User User - { - get => user; - set - { - if (user == value) - return; - - user = value; - - updateDisplay(); - } - } + public readonly Bindable User = new Bindable(); [BackgroundDependencyLoader] private void load(OsuColour colours) { - Children = new Drawable[] + AutoSizeAxes = Axes.Y; + User.ValueChanged += e => updateDisplay(e.NewValue); + + InternalChildren = new Drawable[] { new Box { @@ -76,7 +66,7 @@ namespace osu.Game.Overlays.Profile.Header communityUserGrayGreenLighter = colours.CommunityUserGrayGreenLighter; } - private void updateDisplay() + private void updateDisplay(User user) { void bold(SpriteText t) => t.Font = @"Exo2.0-Bold"; void addSpacer(OsuTextFlowContainer textFlow) => textFlow.AddArbitraryDrawable(new Container { Width = 15 }); diff --git a/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs index ca615ccf4b..b5e04bee9e 100644 --- a/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs @@ -1,75 +1,48 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; -using osu.Game.Online.Chat; using osu.Game.Users; using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.Profile.Header { - public class CenterHeaderContainer : Container + public class CenterHeaderContainer : CompositeDrawable { - public readonly BindableBool DetailsVisible = new BindableBool(); + public Action DetailsVisibilityAction; + private bool detailsVisible; private OsuSpriteText followerText; - private ProfileHeaderButton messageButton; private OsuSpriteText levelBadgeText; private Bar levelProgressBar; private OsuSpriteText levelProgressText; - private ProfileHeader.OverlinedInfoContainer hiddenDetailGlobal, hiddenDetailCountry; + private OverlinedInfoContainer hiddenDetailGlobal, hiddenDetailCountry; - [Resolved(CanBeNull = true)] - private ChannelManager channelManager { get; set; } - - [Resolved(CanBeNull = true)] - private UserProfileOverlay userOverlay { get; set; } - - [Resolved(CanBeNull = true)] - private ChatOverlay chatOverlay { get; set; } - - [Resolved] - private APIAccess apiAccess { get; set; } - - private User user; - - public User User - { - get => user; - set - { - if (user == value) - return; - - user = value; - - updateDisplay(); - } - } + public readonly Bindable User = new Bindable(); [BackgroundDependencyLoader] private void load(OsuColour colours, TextureStore textures) { Container hiddenDetailContainer, expandedDetailContainer; SpriteIcon expandButtonIcon; + ProfileHeaderButton detailsToggleButton; + Height = 60; + User.ValueChanged += e => updateDisplay(e.NewValue); - Children = new Drawable[] + InternalChildren = new Drawable[] { new Box { @@ -118,21 +91,9 @@ namespace osu.Game.Overlays.Profile.Header } } }, - messageButton = new ProfileHeaderButton + new ProfileMessageButton { - Alpha = 0, - RelativeSizeAxes = Axes.Y, - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.fa_envelope, - FillMode = FillMode.Fit, - Size = new Vector2(50, 14) - }, - } + User = { BindTarget = User } }, } }, @@ -143,12 +104,11 @@ namespace osu.Game.Overlays.Profile.Header RelativeSizeAxes = Axes.Y, Padding = new MarginPadding { Vertical = 10 }, Width = UserProfileOverlay.CONTENT_X_MARGIN, - Child = new ProfileHeaderButton + Child = detailsToggleButton = new ProfileHeaderButton { RelativeSizeAxes = Axes.Y, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Action = DetailsVisible.Toggle, Children = new Drawable[] { expandButtonIcon = new SpriteIcon @@ -233,12 +193,12 @@ namespace osu.Game.Overlays.Profile.Header Margin = new MarginPadding { Right = 50 }, Children = new[] { - hiddenDetailGlobal = new ProfileHeader.OverlinedInfoContainer + hiddenDetailGlobal = new OverlinedInfoContainer { Title = "Global Ranking", LineColour = colours.Yellow }, - hiddenDetailCountry = new ProfileHeader.OverlinedInfoContainer + hiddenDetailCountry = new OverlinedInfoContainer { Title = "Country Ranking", LineColour = colours.Yellow @@ -249,76 +209,26 @@ namespace osu.Game.Overlays.Profile.Header } }; - DetailsVisible.ValueChanged += visible => + detailsToggleButton.Action = () => { - expandButtonIcon.Icon = visible.NewValue ? FontAwesome.fa_chevron_down : FontAwesome.fa_chevron_up; - hiddenDetailContainer.Alpha = visible.NewValue ? 1 : 0; - expandedDetailContainer.Alpha = visible.NewValue ? 0 : 1; + detailsVisible = !detailsVisible; + expandButtonIcon.Icon = detailsVisible ? FontAwesome.fa_chevron_down : FontAwesome.fa_chevron_up; + hiddenDetailContainer.Alpha = detailsVisible ? 1 : 0; + expandedDetailContainer.Alpha = detailsVisible ? 0 : 1; + DetailsVisibilityAction(detailsVisible); }; } - private void updateDisplay() + private void updateDisplay(User user) { followerText.Text = user.FollowerCount?.Length > 0 ? user.FollowerCount[0].ToString("#,##0") : "0"; - if (!user.PMFriendsOnly && apiAccess.LocalUser.Value.Id != user.Id) - { - messageButton.Show(); - messageButton.Action = () => - { - channelManager?.OpenPrivateChannel(user); - userOverlay?.Hide(); - chatOverlay?.Show(); - }; - } - else - { - messageButton.Hide(); - } - levelBadgeText.Text = user.Statistics?.Level.Current.ToString() ?? "0"; levelProgressBar.Length = user.Statistics?.Level.Progress / 100f ?? 0; levelProgressText.Text = user.Statistics?.Level.Progress.ToString("0'%'"); - hiddenDetailGlobal.Content = user?.Statistics?.Ranks.Global?.ToString("#,##0") ?? "-"; - hiddenDetailCountry.Content = user?.Statistics?.Ranks.Country?.ToString("#,##0") ?? "-"; - } - - private class ProfileHeaderButton : OsuHoverContainer - { - private readonly Box background; - private readonly Container content; - - protected override Container Content => content; - - protected override IEnumerable EffectTargets => new[] { background }; - - public ProfileHeaderButton() - { - HoverColour = Color4.Black.Opacity(0.75f); - IdleColour = Color4.Black.Opacity(0.7f); - AutoSizeAxes = Axes.X; - - base.Content.Add(new CircularContainer - { - Masking = true, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - }, - content = new Container - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 10 }, - } - } - }); - } + hiddenDetailGlobal.Content = user.Statistics?.Ranks.Global?.ToString("#,##0") ?? "-"; + hiddenDetailCountry.Content = user.Statistics?.Ranks.Country?.ToString("#,##0") ?? "-"; } } } diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index a7d21bcdf3..19894f0301 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -17,34 +18,23 @@ using osuTK; namespace osu.Game.Overlays.Profile.Header { - public class DetailHeaderContainer : Container + public class DetailHeaderContainer : CompositeDrawable { private ProfileHeader.HasTooltipContainer totalPlayTimeTooltip; - private ProfileHeader.OverlinedInfoContainer totalPlayTimeInfo, medalInfo, ppInfo; + private OverlinedInfoContainer totalPlayTimeInfo, medalInfo, ppInfo; private readonly Dictionary scoreRankInfos = new Dictionary(); - private ProfileHeader.OverlinedInfoContainer detailGlobalRank, detailCountryRank; + private OverlinedInfoContainer detailGlobalRank, detailCountryRank; private RankGraph rankGraph; - private User user; - - public User User - { - get => user; - set - { - if (user == value) - return; - - user = value; - - updateDisplay(); - } - } + public readonly Bindable User = new Bindable(); [BackgroundDependencyLoader] private void load(OsuColour colours) { - Children = new Drawable[] + AutoSizeAxes = Axes.Y; + User.ValueChanged += e => updateDisplay(e.NewValue); + + InternalChildren = new Drawable[] { new Box { @@ -79,18 +69,18 @@ namespace osu.Game.Overlays.Profile.Header { AutoSizeAxes = Axes.Both, TooltipText = "0 hours", - Child = totalPlayTimeInfo = new ProfileHeader.OverlinedInfoContainer + Child = totalPlayTimeInfo = new OverlinedInfoContainer { Title = "Total Play Time", LineColour = colours.Yellow, }, }, - medalInfo = new ProfileHeader.OverlinedInfoContainer + medalInfo = new OverlinedInfoContainer { Title = "Medals", LineColour = colours.GreenLight, }, - ppInfo = new ProfileHeader.OverlinedInfoContainer + ppInfo = new OverlinedInfoContainer { Title = "pp", LineColour = colours.Red, @@ -135,12 +125,12 @@ namespace osu.Game.Overlays.Profile.Header Spacing = new Vector2(0, 20), Children = new Drawable[] { - detailGlobalRank = new ProfileHeader.OverlinedInfoContainer(true, 110) + detailGlobalRank = new OverlinedInfoContainer(true, 110) { Title = "Global Ranking", LineColour = colours.Yellow, }, - detailCountryRank = new ProfileHeader.OverlinedInfoContainer(false, 110) + detailCountryRank = new OverlinedInfoContainer(false, 110) { Title = "Country Ranking", LineColour = colours.Yellow, @@ -154,7 +144,7 @@ namespace osu.Game.Overlays.Profile.Header }; } - private void updateDisplay() + private void updateDisplay(User user) { medalInfo.Content = user?.Achievements?.Length.ToString() ?? "0"; ppInfo.Content = user?.Statistics?.PP?.ToString("#,##0") ?? "0"; diff --git a/osu.Game/Overlays/Profile/Header/DrawableBadge.cs b/osu.Game/Overlays/Profile/Header/DrawableBadge.cs new file mode 100644 index 0000000000..75a8f4e415 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/DrawableBadge.cs @@ -0,0 +1,46 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header +{ + public class DrawableBadge : CompositeDrawable, IHasTooltip + { + public static readonly Vector2 DRAWABLE_BADGE_SIZE = new Vector2(86, 40); + + private readonly Badge badge; + + public DrawableBadge(Badge badge) + { + this.badge = badge; + Size = DRAWABLE_BADGE_SIZE; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) + { + InternalChild = new Sprite + { + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Texture = textures.Get(badge.ImageUrl), + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + InternalChild.FadeInFromZero(200); + } + + public string TooltipText => badge.Description; + } +} diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs index 45c1e1e208..69b1203b9f 100644 --- a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -2,14 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Users; using osuTK; @@ -17,31 +15,20 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Profile.Header { - public class MedalHeaderContainer : Container + public class MedalHeaderContainer : CompositeDrawable { private FillFlowContainer badgeFlowContainer; - private User user; - - public User User - { - get => user; - set - { - if (user == value) - return; - - user = value; - - updateDisplay(); - } - } + public readonly Bindable User = new Bindable(); [BackgroundDependencyLoader] private void load(OsuColour colours) { Alpha = 0; - Children = new Drawable[] + AutoSizeAxes = Axes.Y; + User.ValueChanged += e => updateDisplay(e.NewValue); + + InternalChildren = new Drawable[] { new Box { @@ -76,9 +63,9 @@ namespace osu.Game.Overlays.Profile.Header }; } - private void updateDisplay() + private void updateDisplay(User user) { - var badges = User.Badges; + var badges = user.Badges; badgeFlowContainer.Clear(); if (badges?.Length > 0) { @@ -100,37 +87,5 @@ namespace osu.Game.Overlays.Profile.Header Hide(); } } - - private class DrawableBadge : CompositeDrawable, IHasTooltip - { - public static readonly Vector2 DRAWABLE_BADGE_SIZE = new Vector2(86, 40); - - private readonly Badge badge; - - public DrawableBadge(Badge badge) - { - this.badge = badge; - Size = DRAWABLE_BADGE_SIZE; - } - - [BackgroundDependencyLoader] - private void load(LargeTextureStore textures) - { - InternalChild = new Sprite - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Texture = textures.Get(badge.ImageUrl), - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - InternalChild.FadeInFromZero(200); - } - - public string TooltipText => badge.Description; - } } } diff --git a/osu.Game/Overlays/Profile/Header/OverlinedInfoContainer.cs b/osu.Game/Overlays/Profile/Header/OverlinedInfoContainer.cs new file mode 100644 index 0000000000..6d15f96eea --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/OverlinedInfoContainer.cs @@ -0,0 +1,63 @@ +// Copyright (c) ppy Pty Ltd . 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.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header +{ + public class OverlinedInfoContainer : CompositeDrawable + { + private readonly Circle line; + private readonly OsuSpriteText title, content; + + public string Title + { + set => title.Text = value; + } + + public string Content + { + set => content.Text = value; + } + + public Color4 LineColour + { + set => line.Colour = value; + } + + public OverlinedInfoContainer(bool big = false, int minimumWidth = 60) + { + AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + line = new Circle + { + RelativeSizeAxes = Axes.X, + Height = 4, + }, + title = new OsuSpriteText + { + Font = OsuFont.GetFont(size: big ? 14 : 12, weight: FontWeight.Bold) + }, + content = new OsuSpriteText + { + Font = OsuFont.GetFont(size: big ? 40 : 18, weight: FontWeight.Light) + }, + new Container //Add a minimum size to the FillFlowContainer + { + Width = minimumWidth, + } + } + }; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs b/osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs new file mode 100644 index 0000000000..6d9ab7a4d8 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header +{ + public class ProfileHeaderButton : OsuHoverContainer + { + private readonly Box background; + private readonly Container content; + + protected override Container Content => content; + + protected override IEnumerable EffectTargets => new[] { background }; + + public ProfileHeaderButton() + { + HoverColour = Color4.Black.Opacity(0.75f); + IdleColour = Color4.Black.Opacity(0.7f); + AutoSizeAxes = Axes.X; + + base.Content.Add(new CircularContainer + { + Masking = true, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + content = new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 10 }, + } + } + }); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs index a8e50e00fe..e7c9676550 100644 --- a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs +++ b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs @@ -93,11 +93,11 @@ namespace osu.Game.Overlays.Profile.Header { text = new OsuSpriteText { - Margin = new MarginPadding { Bottom = 15 }, + Margin = new MarginPadding { Bottom = 10 }, Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, Text = value, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold) + Font = OsuFont.GetFont() }, bar = new Circle { @@ -134,7 +134,7 @@ namespace osu.Game.Overlays.Profile.Header { text.FadeColour(AccentColour, 120, Easing.InQuad); bar.ResizeHeightTo(0, 120, Easing.InQuad); - text.Font = text.Font.With(Typeface.Exo, weight: FontWeight.Medium); + text.Font = text.Font.With(weight: FontWeight.Medium); } private void onActivated(bool fake = false) @@ -142,7 +142,7 @@ namespace osu.Game.Overlays.Profile.Header text.FadeColour(Color4.White, 120, Easing.InQuad); bar.ResizeHeightTo(7.5f, 120, Easing.InQuad); if (!fake) - text.Font = text.Font.With(Typeface.Exo, weight: FontWeight.Bold); + text.Font = text.Font.With(weight: FontWeight.Bold); } } } diff --git a/osu.Game/Overlays/Profile/Header/ProfileMessageButton.cs b/osu.Game/Overlays/Profile/Header/ProfileMessageButton.cs new file mode 100644 index 0000000000..a14c0324d7 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/ProfileMessageButton.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . 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.Graphics; +using osu.Game.Graphics; +using osu.Game.Online.API; +using osu.Game.Online.Chat; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header +{ + public class ProfileMessageButton : ProfileHeaderButton + { + public readonly Bindable User = new Bindable(); + + [Resolved(CanBeNull = true)] + private ChannelManager channelManager { get; set; } + + [Resolved(CanBeNull = true)] + private UserProfileOverlay userOverlay { get; set; } + + [Resolved(CanBeNull = true)] + private ChatOverlay chatOverlay { get; set; } + + [Resolved] + private APIAccess apiAccess { get; set; } + + public ProfileMessageButton() + { + Content.Alpha = 0; + RelativeSizeAxes = Axes.Y; + + Child = new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.fa_envelope, + FillMode = FillMode.Fit, + Size = new Vector2(50, 14) + }; + + Action = () => + { + if (!Content.IsPresent) return; + + channelManager?.OpenPrivateChannel(User.Value); + userOverlay?.Hide(); + chatOverlay?.Show(); + }; + + User.ValueChanged += e => Content.Alpha = !e.NewValue.PMFriendsOnly && apiAccess.LocalUser.Value.Id != e.NewValue.Id ? 1 : 0; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs index d7e25e32c2..04d6db8b3c 100644 --- a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs @@ -10,10 +10,11 @@ using osu.Game.Graphics; namespace osu.Game.Overlays.Profile.Header { - public class SupporterIcon : CircularContainer, IHasTooltip + public class SupporterIcon : CompositeDrawable, IHasTooltip { private readonly Box background; private readonly FillFlowContainer iconContainer; + private readonly CircularContainer content; public string TooltipText => "osu!supporter"; @@ -23,11 +24,11 @@ namespace osu.Game.Overlays.Profile.Header { if (value == 0) { - Hide(); + content.Hide(); } else { - Show(); + content.Show(); iconContainer.Clear(); for (int i = 0; i < value; i++) { @@ -38,43 +39,38 @@ namespace osu.Game.Overlays.Profile.Header Icon = FontAwesome.fa_heart, }); } + + iconContainer.Padding = new MarginPadding { Horizontal = DrawHeight / 2 }; } } } public SupporterIcon() { - Masking = true; AutoSizeAxes = Axes.X; - Hide(); - Children = new Drawable[] + InternalChild = content = new CircularContainer { - background = new Box { RelativeSizeAxes = Axes.Both }, - iconContainer = new FillFlowContainer + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Alpha = 0, + Children = new Drawable[] { - Direction = FillDirection.Horizontal, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Height = 0.6f, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + background = new Box { RelativeSizeAxes = Axes.Both }, + iconContainer = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Height = 0.6f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } } }; } - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - bool invalid = base.Invalidate(invalidation, source, shallPropagate); - - if ((invalidation & Invalidation.DrawSize) != 0) - { - iconContainer.Padding = new MarginPadding { Horizontal = DrawHeight / 2 }; - } - - return invalid; - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 13a7951170..8e4d72c702 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -14,9 +15,9 @@ using osuTK; namespace osu.Game.Overlays.Profile.Header { - public class TopHeaderContainer : Container + public class TopHeaderContainer : CompositeDrawable { - public SupporterIcon SupporterTag; + private SupporterIcon supporterTag; private UpdateableAvatar avatar; private OsuSpriteText usernameText; private ExternalLinkButton openUserExternally; @@ -27,26 +28,15 @@ namespace osu.Game.Overlays.Profile.Header private const float avatar_size = 110; - private User user; - - public User User - { - get => user; - set - { - if (user == value) - return; - - user = value; - - updateDisplay(); - } - } + public readonly Bindable User = new Bindable(); [BackgroundDependencyLoader] private void load(OsuColour colours) { - Children = new Drawable[] + Height = 150; + User.ValueChanged += e => updateDisplay(e.NewValue); + + InternalChildren = new Drawable[] { new Box { @@ -109,7 +99,7 @@ namespace osu.Game.Overlays.Profile.Header TextSize = 18, Font = "Exo2.0-Regular" }, - SupporterTag = new SupporterIcon + supporterTag = new SupporterIcon { Height = 20, Margin = new MarginPadding { Top = 5 } @@ -161,14 +151,14 @@ namespace osu.Game.Overlays.Profile.Header }; } - private void updateDisplay() + private void updateDisplay(User user) { - avatar.User = User; + avatar.User = user; usernameText.Text = user.Username; openUserExternally.Link = $@"https://osu.ppy.sh/users/{user.Id}"; userFlag.Country = user.Country; userCountryText.Text = user.Country?.FullName ?? "Alien"; - SupporterTag.SupporterLevel = user.SupportLevel; + supporterTag.SupporterLevel = user.SupportLevel; titleText.Text = user.Title; titleText.Colour = OsuColour.FromHex(user.Colour ?? "fff"); diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index fdb515270b..988fa3afd9 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -2,43 +2,31 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osuTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; using osu.Framework.Bindables; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Profile { public class ProfileHeader : Container { - private readonly Container coverContainer; - private readonly OsuSpriteText coverInfoText; + private readonly UserCoverBackground coverContainer; + private readonly ScreenTitle coverTitle; private readonly ProfileHeaderTabControl infoTabControl; - private readonly TopHeaderContainer topHeaderContainer; - public SupporterIcon SupporterTag => topHeaderContainer.SupporterTag; - - private readonly CenterHeaderContainer centerHeaderContainer; - public readonly BindableBool DetailsVisible = new BindableBool(); - - private readonly DetailHeaderContainer detailHeaderContainer; - private readonly MedalHeaderContainer medalHeaderContainer; - private readonly BottomHeaderContainer bottomHeaderContainer; - private const float cover_height = 150; private const float cover_info_height = 75; public ProfileHeader() { + CenterHeaderContainer centerHeaderContainer; + DetailHeaderContainer detailHeaderContainer; Container expandedDetailContainer; FillFlowContainer hiddenDetailContainer, headerDetailContainer; SpriteIcon expandButtonIcon; @@ -48,19 +36,10 @@ namespace osu.Game.Overlays.Profile Children = new Drawable[] { - coverContainer = new Container + coverContainer = new UserCoverBackground { RelativeSizeAxes = Axes.X, Height = cover_height, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.1f), Color4.Black.Opacity(0.75f)) - }, - } }, new Container { @@ -73,25 +52,10 @@ namespace osu.Game.Overlays.Profile Depth = -float.MaxValue, Children = new Drawable[] { - new FillFlowContainer + coverTitle = new ScreenTitle { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - new OsuSpriteText - { - Text = "Player ", - Font = "Exo2.0-Regular", - TextSize = 30 - }, - coverInfoText = new OsuSpriteText - { - Text = "Info", - Font = "Exo2.0-Regular", - TextSize = 30 - } - } + Title = "Player ", + Page = "Info" }, infoTabControl = new ProfileHeaderTabControl { @@ -112,30 +76,30 @@ namespace osu.Game.Overlays.Profile Direction = FillDirection.Vertical, Children = new Drawable[] { - topHeaderContainer = new TopHeaderContainer + new TopHeaderContainer { RelativeSizeAxes = Axes.X, - Height = 150, + User = { BindTarget = User }, }, centerHeaderContainer = new CenterHeaderContainer { RelativeSizeAxes = Axes.X, - Height = 60, + User = { BindTarget = User }, }, detailHeaderContainer = new DetailHeaderContainer { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + User = { BindTarget = User }, }, - medalHeaderContainer = new MedalHeaderContainer + new MedalHeaderContainer { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + User = { BindTarget = User }, }, - bottomHeaderContainer = new BottomHeaderContainer + new BottomHeaderContainer { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + User = { BindTarget = User }, }, } } @@ -144,99 +108,28 @@ namespace osu.Game.Overlays.Profile infoTabControl.AddItem("Info"); infoTabControl.AddItem("Modding"); - centerHeaderContainer.DetailsVisible.BindTo(DetailsVisible); - DetailsVisible.ValueChanged += visible => detailHeaderContainer.Alpha = visible.NewValue ? 0 : 1; + centerHeaderContainer.DetailsVisibilityAction = visible => detailHeaderContainer.Alpha = visible ? 0 : 1; + User.ValueChanged += e => updateDisplay(e.NewValue); } [BackgroundDependencyLoader] private void load(OsuColour colours, TextureStore textures) { - coverInfoText.Colour = colours.CommunityUserGreen; + coverTitle.AccentColour = colours.CommunityUserGreen; infoTabControl.AccentColour = colours.CommunityUserGreen; } - private User user; + public Bindable User = new Bindable(); - public User User + private void updateDisplay(User user) { - get => user; - set - { - medalHeaderContainer.User = detailHeaderContainer.User = bottomHeaderContainer.User = - centerHeaderContainer.User = topHeaderContainer.User = user = value; - updateDisplay(); - } - } - - private void updateDisplay() - { - coverContainer.RemoveAll(d => d is UserCoverBackground); - LoadComponentAsync(new UserCoverBackground(user) - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(200), - Depth = float.MaxValue, - }, coverContainer.Add); + coverContainer.User = user; } public class HasTooltipContainer : Container, IHasTooltip { public string TooltipText { get; set; } } - - public class OverlinedInfoContainer : CompositeDrawable - { - private readonly Circle line; - private readonly OsuSpriteText title, content; - - public string Title - { - set => title.Text = value; - } - - public string Content - { - set => content.Text = value; - } - - public Color4 LineColour - { - set => line.Colour = value; - } - - public OverlinedInfoContainer(bool big = false, int minimumWidth = 60) - { - AutoSizeAxes = Axes.Both; - InternalChild = new FillFlowContainer - { - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - line = new Circle - { - RelativeSizeAxes = Axes.X, - Height = 4, - }, - title = new OsuSpriteText - { - Font = OsuFont.GetFont(size: big ? 14 : 12, weight: FontWeight.Bold) - }, - content = new OsuSpriteText - { - Font = OsuFont.GetFont(size: big ? 40 : 18, weight: FontWeight.Light) - }, - new Container //Add a minimum size to the FillFlowContainer - { - Width = minimumWidth, - } - } - }; - } - } } } diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 60e2921127..9b6e34d399 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -81,7 +81,7 @@ namespace osu.Game.Overlays Show(); - if (user.Id == Header?.User?.Id) + if (user.Id == Header?.User.Value?.Id) return; userReq?.Cancel(); @@ -167,7 +167,7 @@ namespace osu.Game.Overlays private void userLoadComplete(User user) { - Header.User = user; + Header.User.Value = user; if (user.ProfileOrder != null) { diff --git a/osu.Game/Users/UserCoverBackground.cs b/osu.Game/Users/UserCoverBackground.cs index 4c72762498..d037f809b4 100644 --- a/osu.Game/Users/UserCoverBackground.cs +++ b/osu.Game/Users/UserCoverBackground.cs @@ -1,30 +1,51 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; +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.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osuTK.Graphics; namespace osu.Game.Users { - public class UserCoverBackground : Sprite + public class UserCoverBackground : ModelBackedDrawable { - private readonly User user; - - public UserCoverBackground(User user) + public User User { - this.user = user; + get => Model; + set => Model = value; } - [BackgroundDependencyLoader] - private void load(LargeTextureStore textures) - { - if (textures == null) - throw new ArgumentNullException(nameof(textures)); + [Resolved] + private LargeTextureStore textures { get; set; } - if (!string.IsNullOrEmpty(user.CoverUrl)) - Texture = textures.Get(user.CoverUrl); + protected override Drawable CreateDrawable(User user) + { + if (user == null) + { + return new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.1f), Color4.Black.Opacity(0.75f)) + }; + } + else + { + return new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = textures.Get(user.CoverUrl), + FillMode = FillMode.Fill, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + OnLoadComplete = d => d.FadeInFromZero(400), + }; + } } } } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index cebbc74d50..def967e69b 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -73,12 +73,12 @@ namespace osu.Game.Users Children = new Drawable[] { - new DelayedLoadWrapper(new UserCoverBackground(user) + new DelayedLoadWrapper(new UserCoverBackground { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - FillMode = FillMode.Fill, + User = user, OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out) }, 300) { RelativeSizeAxes = Axes.Both }, new Box From 2df57c3a2963c646e55d3b8268b2254fc3e49862 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 11 Mar 2019 11:08:04 +0900 Subject: [PATCH 052/623] Give Multiplayer its own background stack --- osu.Game/Screens/Multi/Multiplayer.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index dd01ae4160..513b949b35 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -50,6 +50,9 @@ namespace osu.Game.Screens.Multi [Cached(Type = typeof(IRoomManager))] private RoomManager roomManager; + [Cached] + private BackgroundScreenStack backgroundScreenStack; + [Resolved] private OsuGameBase game { get; set; } @@ -95,7 +98,11 @@ namespace osu.Game.Screens.Multi { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = Header.HEIGHT }, - Child = screenStack = new ScreenStack(loungeSubScreen = new LoungeSubScreen()) { RelativeSizeAxes = Axes.Both } + Children = new[] + { + backgroundScreenStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }, + screenStack = new ScreenStack(loungeSubScreen = new LoungeSubScreen()) { RelativeSizeAxes = Axes.Both }, + } }, new Header(screenStack), createButton = new HeaderButton From 315788c97535344f84988c12a63777fbc5032620 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 Mar 2019 15:11:01 +0900 Subject: [PATCH 053/623] Rename a few classes --- osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs | 6 +++--- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- .../Scores/{ScoreTextLine.cs => ScoreTableHeader.cs} | 4 ++-- .../Scores/{ScoreTableLine.cs => ScoreTableRow.cs} | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) rename osu.Game/Overlays/BeatmapSet/Scores/{ScoreTextLine.cs => ScoreTableHeader.cs} (94%) rename osu.Game/Overlays/BeatmapSet/Scores/{ScoreTableLine.cs => ScoreTableRow.cs} (98%) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index a2a9f9a01b..d7396c8839 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores RelativeSizeAxes = Axes.Both, Alpha = 0, }, - new DrawableScoreData(index, score, maxModsAmount), + new ScoreRow(index, score, maxModsAmount), }; if (index % 2 != 0) @@ -118,9 +118,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - private class DrawableScoreData : ScoreTableLine + private class ScoreRow : ScoreTableRow { - public DrawableScoreData(int index, APIScoreInfo score, int maxModsAmount) + public ScoreRow(int index, APIScoreInfo score, int maxModsAmount) : base(maxModsAmount) { SpriteText scoreText; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 4723fcaf2b..5d248c5501 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -43,7 +43,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores maxModsAmount = scoreModsAmount; } - scoresFlow.Add(new ScoreTextLine(maxModsAmount)); + scoresFlow.Add(new ScoreTableHeader(maxModsAmount)); int index = 0; foreach (var s in value) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeader.cs similarity index 94% rename from osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs rename to osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeader.cs index 9e201bd98d..544bf34ec5 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTextLine.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeader.cs @@ -5,9 +5,9 @@ using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.BeatmapSet.Scores { - public class ScoreTextLine : ScoreTableLine + public class ScoreTableHeader : ScoreTableRow { - public ScoreTextLine(int maxModsAmount) + public ScoreTableHeader(int maxModsAmount) : base(maxModsAmount) { RankContainer.Add(new ScoreText diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableLine.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs similarity index 98% rename from osu.Game/Overlays/BeatmapSet/Scores/ScoreTableLine.cs rename to osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs index 5c4723eae1..3a48a0cca1 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableLine.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Overlays.BeatmapSet.Scores { - public class ScoreTableLine : GridContainer + public class ScoreTableRow : GridContainer { private const float rank_position = 30; private const float drawable_rank_position = 45; @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores protected readonly Container PPContainer; protected readonly Container ModsContainer; - public ScoreTableLine(int maxModsAmount) + public ScoreTableRow(int maxModsAmount) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; From 0b64af5e0211b10a1db88d7c4bd37cdcb44095cc Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 11 Mar 2019 16:51:43 +0900 Subject: [PATCH 054/623] Put multiplayer background inside a parallax container --- osu.Game/Screens/Multi/Multiplayer.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 513b949b35..ee7244b0cd 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -38,6 +38,7 @@ namespace osu.Game.Screens.Multi private readonly OsuButton createButton; private readonly LoungeSubScreen loungeSubScreen; private readonly ScreenStack screenStack; + private ParallaxContainer backgroundParallax; private readonly IBindable isIdle = new BindableBool(); @@ -98,9 +99,13 @@ namespace osu.Game.Screens.Multi { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = Header.HEIGHT }, - Children = new[] + Children = new CompositeDrawable[] { - backgroundScreenStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }, + backgroundParallax = new ParallaxContainer + { + RelativeSizeAxes = Axes.Both, + Child = backgroundScreenStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }, + }, screenStack = new ScreenStack(loungeSubScreen = new LoungeSubScreen()) { RelativeSizeAxes = Axes.Both }, } }, From aeae6143c18999a97c2ab32ee9179181f81bea25 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 11 Mar 2019 17:47:03 +0900 Subject: [PATCH 055/623] Remove unnecessary variable --- osu.Game/Screens/Multi/Multiplayer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index ee7244b0cd..123ef7b1f7 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -38,7 +38,6 @@ namespace osu.Game.Screens.Multi private readonly OsuButton createButton; private readonly LoungeSubScreen loungeSubScreen; private readonly ScreenStack screenStack; - private ParallaxContainer backgroundParallax; private readonly IBindable isIdle = new BindableBool(); @@ -101,7 +100,7 @@ namespace osu.Game.Screens.Multi Padding = new MarginPadding { Top = Header.HEIGHT }, Children = new CompositeDrawable[] { - backgroundParallax = new ParallaxContainer + new ParallaxContainer { RelativeSizeAxes = Axes.Both, Child = backgroundScreenStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }, From 6a1e60009999f23cfc0f19338d0c7d38bf97a8bd Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 11 Mar 2019 19:48:07 +0900 Subject: [PATCH 056/623] Create new OsuScreenStack for use in Multiplayer --- osu.Game/Screens/Multi/Multiplayer.cs | 10 +--------- osu.Game/Screens/OsuScreenStack.cs | 28 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Screens/OsuScreenStack.cs diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 123ef7b1f7..db5372a027 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -50,9 +50,6 @@ namespace osu.Game.Screens.Multi [Cached(Type = typeof(IRoomManager))] private RoomManager roomManager; - [Cached] - private BackgroundScreenStack backgroundScreenStack; - [Resolved] private OsuGameBase game { get; set; } @@ -100,12 +97,7 @@ namespace osu.Game.Screens.Multi Padding = new MarginPadding { Top = Header.HEIGHT }, Children = new CompositeDrawable[] { - new ParallaxContainer - { - RelativeSizeAxes = Axes.Both, - Child = backgroundScreenStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }, - }, - screenStack = new ScreenStack(loungeSubScreen = new LoungeSubScreen()) { RelativeSizeAxes = Axes.Both }, + screenStack = new OsuScreenStack(loungeSubScreen = new LoungeSubScreen()) { RelativeSizeAxes = Axes.Both }, } }, new Header(screenStack), diff --git a/osu.Game/Screens/OsuScreenStack.cs b/osu.Game/Screens/OsuScreenStack.cs new file mode 100644 index 0000000000..4d92a41dc7 --- /dev/null +++ b/osu.Game/Screens/OsuScreenStack.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Game.Graphics.Containers; +using osu.Game.Screens.Multi.Lounge; + +namespace osu.Game.Screens +{ + public class OsuScreenStack : ScreenStack + { + [Cached] + private BackgroundScreenStack backgroundScreenStack; + + public OsuScreenStack(IScreen baseScreen) + : base(baseScreen) + { + backgroundScreenStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }; + InternalChild = new ParallaxContainer + { + RelativeSizeAxes = Axes.Both, + Child = backgroundScreenStack, + }; + } + } +} From 63b9fa58ef410ba0018692a68cd0f89ca6957d6e Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 11 Mar 2019 19:52:28 +0900 Subject: [PATCH 057/623] Cleanup --- osu.Game/Screens/Multi/Multiplayer.cs | 5 +---- osu.Game/Screens/OsuScreenStack.cs | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index db5372a027..37f4322d85 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -95,10 +95,7 @@ namespace osu.Game.Screens.Multi { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = Header.HEIGHT }, - Children = new CompositeDrawable[] - { - screenStack = new OsuScreenStack(loungeSubScreen = new LoungeSubScreen()) { RelativeSizeAxes = Axes.Both }, - } + Child = screenStack = new OsuScreenStack(loungeSubScreen = new LoungeSubScreen()) { RelativeSizeAxes = Axes.Both } }, new Header(screenStack), createButton = new HeaderButton diff --git a/osu.Game/Screens/OsuScreenStack.cs b/osu.Game/Screens/OsuScreenStack.cs index 4d92a41dc7..da3211a210 100644 --- a/osu.Game/Screens/OsuScreenStack.cs +++ b/osu.Game/Screens/OsuScreenStack.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Graphics.Containers; -using osu.Game.Screens.Multi.Lounge; namespace osu.Game.Screens { From 81d9e391f4914feec96e5e1acdffc96600d61c1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 00:04:19 +0900 Subject: [PATCH 058/623] Preload main menu background --- osu.Game/Screens/Menu/MainMenu.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 234fb808c3..5403f7c702 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -40,7 +40,9 @@ namespace osu.Game.Screens.Menu [Resolved] private GameHost host { get; set; } - protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(); + private BackgroundScreenDefault background; + + protected override BackgroundScreen CreateBackground() => background; [BackgroundDependencyLoader(true)] private void load(OsuGame game = null) @@ -89,6 +91,7 @@ namespace osu.Game.Screens.Menu buttons.OnDirect = game.ToggleDirect; } + LoadComponentAsync(background = new BackgroundScreenDefault()); preloadSongSelect(); } From e9ab329e930c874064860f204f82ae9711d1045e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 00:05:05 +0900 Subject: [PATCH 059/623] Fix backgrounds not correctly handling initial async load --- .../Backgrounds/BackgroundScreenBeatmap.cs | 44 +++++++++++-------- .../Backgrounds/BackgroundScreenDefault.cs | 24 +++++----- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 13d1ff39ef..49e21334ca 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -36,28 +36,34 @@ namespace osu.Game.Screens.Backgrounds beatmap = value; - Schedule(() => - { - LoadComponentAsync(new BeatmapBackground(beatmap), b => Schedule(() => - { - float newDepth = 0; - if (Background != null) - { - newDepth = Background.Depth + 1; - Background.FinishTransforms(); - Background.FadeOut(250); - Background.Expire(); - } - - b.Depth = newDepth; - fadeContainer.Add(Background = b); - Background.BlurSigma = BlurTarget; - StoryboardReplacesBackground.BindTo(fadeContainer.StoryboardReplacesBackground); - })); - }); + Schedule(() => { LoadComponentAsync(new BeatmapBackground(beatmap), b => Schedule(() => backgroundLoaded(b))); }); } } + private void backgroundLoaded(BeatmapBackground b) + { + float newDepth = 0; + if (Background != null) + { + newDepth = Background.Depth + 1; + Background.FinishTransforms(); + Background.FadeOut(250); + Background.Expire(); + } + + b.Depth = newDepth; + fadeContainer.Add(Background = b); + Background.BlurSigma = BlurTarget; + StoryboardReplacesBackground.BindTo(fadeContainer.StoryboardReplacesBackground); + } + + [BackgroundDependencyLoader] + private void load() + { + if (beatmap != null) + backgroundLoaded(new BeatmapBackground(beatmap)); + } + public BackgroundScreenBeatmap(WorkingBeatmap beatmap = null) { Beatmap = beatmap; diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 87a6b5d591..e015f95ff8 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Backgrounds currentDisplay = RNG.Next(0, background_count); - Next(); + display(createBackground()); } private void display(Background newBackground) @@ -51,19 +51,21 @@ namespace osu.Game.Screens.Backgrounds public void Next() { nextTask?.Cancel(); - nextTask = Scheduler.AddDelayed(() => - { - Background background; + nextTask = Scheduler.AddDelayed(() => { LoadComponentAsync(createBackground(), display); }, 100); + } - if (user.Value?.IsSupporter ?? false) - background = new SkinnedBackground(skin.Value, backgroundName); - else - background = new Background(backgroundName); + private Background createBackground() + { + Background background; - background.Depth = currentDisplay; + if (user.Value?.IsSupporter ?? false) + background = new SkinnedBackground(skin.Value, backgroundName); + else + background = new Background(backgroundName); - LoadComponentAsync(background, display); - }, 100); + background.Depth = currentDisplay; + + return background; } private class SkinnedBackground : Background From f3ab5070b97e1845c8af1a7270f30058f081c790 Mon Sep 17 00:00:00 2001 From: jorolf Date: Mon, 11 Mar 2019 18:37:36 +0100 Subject: [PATCH 060/623] apply suggestions --- .../Graphics/UserInterface/ScreenTitle.cs | 3 ++- osu.Game/Screens/Multi/Header.cs | 22 +++++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ScreenTitle.cs b/osu.Game/Graphics/UserInterface/ScreenTitle.cs index d931d2561a..d7cba06d9d 100644 --- a/osu.Game/Graphics/UserInterface/ScreenTitle.cs +++ b/osu.Game/Graphics/UserInterface/ScreenTitle.cs @@ -26,7 +26,7 @@ namespace osu.Game.Graphics.UserInterface set => titleText.Text = value; } - public string Page + public string Section { get => pageText.Text; set => pageText.Text = value; @@ -55,6 +55,7 @@ namespace osu.Game.Graphics.UserInterface { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Spacing = new Vector2(6, 0), Children = new[] { titleText = new OsuSpriteText diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 668b2f5995..bb4acc6007 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -17,11 +17,11 @@ namespace osu.Game.Screens.Multi { public const float HEIGHT = 121; - private readonly ScreenTitle title; private readonly HeaderBreadcrumbControl breadcrumbs; public Header(ScreenStack stack) { + ScreenTitle title; RelativeSizeAxes = Axes.X; Height = HEIGHT; @@ -38,12 +38,12 @@ namespace osu.Game.Screens.Multi Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, Children = new Drawable[] { - title = new ScreenTitle + title = new MultiHeaderTitle { Anchor = Anchor.CentreLeft, Origin = Anchor.BottomLeft, Icon = FontAwesome.fa_osu_multi, - Title = "multiplayer ", + Title = "multiplayer", }, breadcrumbs = new HeaderBreadcrumbControl(stack) { @@ -55,10 +55,10 @@ namespace osu.Game.Screens.Multi }, }; - breadcrumbs.Current.ValueChanged += scren => + breadcrumbs.Current.ValueChanged += screen => { - if (scren.NewValue is IMultiplayerSubScreen multiScreen) - title.Page = multiScreen.ShortTitle.ToLowerInvariant(); + if (screen.NewValue is IMultiplayerSubScreen multiScreen) + title.Section = multiScreen.ShortTitle.ToLowerInvariant(); }; breadcrumbs.Current.TriggerChange(); @@ -67,10 +67,18 @@ namespace osu.Game.Screens.Multi [BackgroundDependencyLoader] private void load(OsuColour colours) { - title.AccentColour = colours.Yellow; breadcrumbs.StripColour = colours.Green; } + private class MultiHeaderTitle : ScreenTitle + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Yellow; + } + } + private class HeaderBreadcrumbControl : ScreenBreadcrumbControl { public HeaderBreadcrumbControl(ScreenStack stack) From f91e4a1fdd0783976594915228b5f9bc49c48d4e Mon Sep 17 00:00:00 2001 From: jorolf Date: Mon, 11 Mar 2019 19:10:37 +0100 Subject: [PATCH 061/623] make ScreenTitle abstract and properties protected --- osu.Game/Graphics/UserInterface/ScreenTitle.cs | 10 +++++----- osu.Game/Screens/Multi/Header.cs | 13 +++++++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ScreenTitle.cs b/osu.Game/Graphics/UserInterface/ScreenTitle.cs index d7cba06d9d..14c87bb59d 100644 --- a/osu.Game/Graphics/UserInterface/ScreenTitle.cs +++ b/osu.Game/Graphics/UserInterface/ScreenTitle.cs @@ -9,24 +9,24 @@ using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { - public class ScreenTitle : CompositeDrawable, IHasAccentColour + public abstract class ScreenTitle : CompositeDrawable, IHasAccentColour { private readonly SpriteIcon iconSprite; private readonly OsuSpriteText titleText, pageText; - public FontAwesome Icon + protected FontAwesome Icon { get => iconSprite.Icon; set => iconSprite.Icon = value; } - public string Title + protected string Title { get => titleText.Text; set => titleText.Text = value; } - public string Section + protected string Section { get => pageText.Text; set => pageText.Text = value; @@ -38,7 +38,7 @@ namespace osu.Game.Graphics.UserInterface set => pageText.Colour = value; } - public ScreenTitle() + protected ScreenTitle() { AutoSizeAxes = Axes.Both; diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index bb4acc6007..0f945d107f 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Multi public Header(ScreenStack stack) { - ScreenTitle title; + MultiHeaderTitle title; RelativeSizeAxes = Axes.X; Height = HEIGHT; @@ -42,8 +42,6 @@ namespace osu.Game.Screens.Multi { Anchor = Anchor.CentreLeft, Origin = Anchor.BottomLeft, - Icon = FontAwesome.fa_osu_multi, - Title = "multiplayer", }, breadcrumbs = new HeaderBreadcrumbControl(stack) { @@ -58,7 +56,7 @@ namespace osu.Game.Screens.Multi breadcrumbs.Current.ValueChanged += screen => { if (screen.NewValue is IMultiplayerSubScreen multiScreen) - title.Section = multiScreen.ShortTitle.ToLowerInvariant(); + title.Screen = multiScreen; }; breadcrumbs.Current.TriggerChange(); @@ -72,9 +70,16 @@ namespace osu.Game.Screens.Multi private class MultiHeaderTitle : ScreenTitle { + public IMultiplayerSubScreen Screen + { + set => Section = value.ShortTitle.ToLowerInvariant(); + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { + Title = "multiplayer"; + Icon = FontAwesome.fa_osu_multi; AccentColour = colours.Yellow; } } From 5ba8388e5468230b1c6064a46e5e0ee95f14c2e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 12:55:54 +0900 Subject: [PATCH 062/623] Add load check to avoid double-loading --- osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 49e21334ca..0d13cb5de2 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; @@ -36,6 +36,9 @@ namespace osu.Game.Screens.Backgrounds beatmap = value; + // load will be completed in async load. + if (LoadState < LoadState.Ready) return; + Schedule(() => { LoadComponentAsync(new BeatmapBackground(beatmap), b => Schedule(() => backgroundLoaded(b))); }); } } From cc416187602b31e795a64ad6dc0aca0648c5a7bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 12:56:01 +0900 Subject: [PATCH 063/623] Reorganise class --- .../Backgrounds/BackgroundScreenBeatmap.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 0d13cb5de2..781f64a792 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; @@ -26,6 +26,20 @@ namespace osu.Game.Screens.Backgrounds protected virtual UserDimContainer CreateFadeContainer() => new UserDimContainer { RelativeSizeAxes = Axes.Both }; + public BackgroundScreenBeatmap(WorkingBeatmap beatmap = null) + { + Beatmap = beatmap; + InternalChild = fadeContainer = CreateFadeContainer(); + fadeContainer.EnableUserDim.BindTo(EnableUserDim); + } + + [BackgroundDependencyLoader] + private void load() + { + if (beatmap != null) + backgroundLoaded(new BeatmapBackground(beatmap)); + } + public virtual WorkingBeatmap Beatmap { get => beatmap; @@ -60,20 +74,6 @@ namespace osu.Game.Screens.Backgrounds StoryboardReplacesBackground.BindTo(fadeContainer.StoryboardReplacesBackground); } - [BackgroundDependencyLoader] - private void load() - { - if (beatmap != null) - backgroundLoaded(new BeatmapBackground(beatmap)); - } - - public BackgroundScreenBeatmap(WorkingBeatmap beatmap = null) - { - Beatmap = beatmap; - InternalChild = fadeContainer = CreateFadeContainer(); - fadeContainer.EnableUserDim.BindTo(EnableUserDim); - } - public override bool Equals(BackgroundScreen other) { var otherBeatmapBackground = other as BackgroundScreenBeatmap; From 1954eaca4c884452a3861a052f4b2b5740655942 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 14:01:27 +0900 Subject: [PATCH 064/623] Populate an initial beatmap --- osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 781f64a792..9704be15f4 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Backgrounds backgroundLoaded(new BeatmapBackground(beatmap)); } - public virtual WorkingBeatmap Beatmap + public WorkingBeatmap Beatmap { get => beatmap; set diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 5d4ead69d6..e297c0e343 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Select ///

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 z�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

protected readonly Container FooterPanels; - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(); + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); protected readonly BeatmapCarousel Carousel; private readonly BeatmapInfoWedge beatmapInfoWedge; From 00feb34a3d159badc04067fdab024a4a223dbd67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 14:02:49 +0900 Subject: [PATCH 065/623] Perform load even if default beatmap --- osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 9704be15f4..d7aa095440 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -36,8 +36,7 @@ namespace osu.Game.Screens.Backgrounds [BackgroundDependencyLoader] private void load() { - if (beatmap != null) - backgroundLoaded(new BeatmapBackground(beatmap)); + backgroundLoaded(new BeatmapBackground(beatmap)); } public WorkingBeatmap Beatmap From 059397ac5000e313ba4821991ef546b92733c55c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 12 Mar 2019 14:40:13 +0900 Subject: [PATCH 066/623] Remove unnecessary early return for maching beatmap IDs --- .../Beatmaps/IO/ImportBeatmapTest.cs | 55 +++++++++++++++++++ osu.Game/Beatmaps/BeatmapManager.cs | 14 ++--- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 0f65f7f82e..baa71dab40 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -242,6 +242,61 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [Test] + public void TestImportWithDuplicateBeatmapIDs() + { + //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"TestImportWithDuplicateBeatmapID")) + { + try + { + var osu = loadOsu(host); + + var metadata = new BeatmapMetadata + { + Artist = "SomeArtist", + AuthorString = "SomeAuthor" + }; + + BeatmapDifficulty difficulty = null; + + var toImport = new BeatmapSetInfo + { + OnlineBeatmapSetID = 1, + Metadata = metadata, + Beatmaps = new List + { + new BeatmapInfo + { + OnlineBeatmapID = 2, + Metadata = metadata, + BaseDifficulty = difficulty + }, + new BeatmapInfo + { + OnlineBeatmapID = 2, + Metadata = metadata, + Status = BeatmapSetOnlineStatus.Loved, + BaseDifficulty = difficulty + } + } + }; + + var manager = osu.Dependencies.Get(); + + var imported = manager.Import(toImport); + + Assert.NotNull(imported); + Assert.AreEqual(null, imported.Beatmaps[0].OnlineBeatmapID); + Assert.AreEqual(null, imported.Beatmaps[1].OnlineBeatmapID); + } + finally + { + host.Exit(); + } + } + } + [Test] [NonParallelizable] [Ignore("Binding IPC on Appveyor isn't working (port in use). Need to figure out why")] diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 711aa0b79b..28a258fd00 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -105,11 +105,14 @@ namespace osu.Game.Beatmaps validateOnlineIds(beatmapSet); foreach (BeatmapInfo b in beatmapSet.Beatmaps) - fetchAndPopulateOnlineValues(b, beatmapSet.Beatmaps); + fetchAndPopulateOnlineValues(b); } protected override void PreImport(BeatmapSetInfo beatmapSet) { + if (beatmapSet.Beatmaps.Any(b => b.BaseDifficulty == null)) + throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}."); + // check if a set already exists with the same online id, delete if it does. if (beatmapSet.OnlineBeatmapSetID != null) { @@ -382,7 +385,7 @@ namespace osu.Game.Beatmaps /// The other beatmaps contained within this set. /// Whether to re-query if the provided beatmap already has populated values. /// True if population was successful. - private bool fetchAndPopulateOnlineValues(BeatmapInfo beatmap, IEnumerable otherBeatmaps, bool force = false) + private bool fetchAndPopulateOnlineValues(BeatmapInfo beatmap, bool force = false) { if (api?.State != APIState.Online) return false; @@ -405,13 +408,6 @@ namespace osu.Game.Beatmaps beatmap.Status = res.Status; beatmap.BeatmapSet.Status = res.BeatmapSet.Status; - - if (otherBeatmaps.Any(b => b.OnlineBeatmapID == res.OnlineBeatmapID)) - { - Logger.Log("Another beatmap in the same set already mapped to this ID. We'll skip adding it this time.", LoggingTarget.Database); - return false; - } - beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; beatmap.OnlineBeatmapID = res.OnlineBeatmapID; From 00191ca94053a88560ee0a64f153844b3ab07382 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 12 Mar 2019 15:24:35 +0900 Subject: [PATCH 067/623] Actually set the beatmap difficulty --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index baa71dab40..f020c2a805 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -246,7 +246,7 @@ namespace osu.Game.Tests.Beatmaps.IO public void TestImportWithDuplicateBeatmapIDs() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"TestImportWithDuplicateBeatmapID")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDuplicateBeatmapID")) { try { @@ -258,7 +258,7 @@ namespace osu.Game.Tests.Beatmaps.IO AuthorString = "SomeAuthor" }; - BeatmapDifficulty difficulty = null; + var difficulty = new BeatmapDifficulty(); var toImport = new BeatmapSetInfo { From e3567a55074de50602bce615d5ab0a9b45412e29 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 12 Mar 2019 16:03:25 +0900 Subject: [PATCH 068/623] Make OsuGame use OsuScreenStack too --- osu.Game/OsuGame.cs | 29 +++++++++-------------------- osu.Game/Screens/OsuScreenStack.cs | 25 ++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index cf231f19ce..dc83f1f9d6 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -87,11 +87,7 @@ namespace osu.Game public readonly Bindable OverlayActivationMode = new Bindable(); - private BackgroundScreenStack backgroundStack; - - private ParallaxContainer backgroundParallax; - - private ScreenStack screenStack; + private readonly OsuScreenStack screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; private VolumeOverlay volume; private OnScreenDisplay onscreenDisplay; private OsuLogo osuLogo; @@ -390,12 +386,7 @@ namespace osu.Game RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - backgroundParallax = new ParallaxContainer - { - RelativeSizeAxes = Axes.Both, - Child = backgroundStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }, - }, - screenStack = new ScreenStack { RelativeSizeAxes = Axes.Both }, + screenStack, logoContainer = new Container { RelativeSizeAxes = Axes.Both }, } }, @@ -407,17 +398,17 @@ namespace osu.Game idleTracker = new GameIdleTracker(6000) }); - dependencies.Cache(backgroundStack); - screenStack.ScreenPushed += screenPushed; screenStack.ScreenExited += screenExited; - loadComponentSingleFile(osuLogo, logoContainer.Add); - - loadComponentSingleFile(new Loader + loadComponentSingleFile(osuLogo, logo => { - RelativeSizeAxes = Axes.Both - }, screenStack.Push); + logoContainer.Add(logo); + screenStack.Push(new Loader + { + RelativeSizeAxes = Axes.Both + }); + }); loadComponentSingleFile(Toolbar = new Toolbar { @@ -777,8 +768,6 @@ namespace osu.Game if (newScreen is IOsuScreen newOsuScreen) { - backgroundParallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * newOsuScreen.BackgroundParallaxAmount; - OverlayActivationMode.Value = newOsuScreen.InitialOverlayActivationMode; if (newOsuScreen.HideOverlaysOnEnter) diff --git a/osu.Game/Screens/OsuScreenStack.cs b/osu.Game/Screens/OsuScreenStack.cs index da3211a210..d6d6272e0e 100644 --- a/osu.Game/Screens/OsuScreenStack.cs +++ b/osu.Game/Screens/OsuScreenStack.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Graphics.Containers; @@ -13,15 +14,33 @@ namespace osu.Game.Screens [Cached] private BackgroundScreenStack backgroundScreenStack; + private ParallaxContainer parallaxContainer; + + public OsuScreenStack() + { + initializeStack(); + } + public OsuScreenStack(IScreen baseScreen) : base(baseScreen) { - backgroundScreenStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }; - InternalChild = new ParallaxContainer + initializeStack(); + } + + private void initializeStack() + { + InternalChild = parallaxContainer = new ParallaxContainer { RelativeSizeAxes = Axes.Both, - Child = backgroundScreenStack, + Child = backgroundScreenStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }, }; + + ScreenPushed += setParallax; + } + + private void setParallax(IScreen prev, IScreen next) + { + parallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * ((IOsuScreen)next).BackgroundParallaxAmount; } } } From 921c4ce2424abd53dcd08daf5b25de54e5087219 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 12 Mar 2019 16:33:35 +0900 Subject: [PATCH 069/623] Make tests use new OsuScreenStack --- .../Visual/TestCaseBackgroundScreenBeatmap.cs | 21 +++---------------- osu.Game.Tests/Visual/TestCaseMultiHeader.cs | 2 +- osu.Game.Tests/Visual/TestCasePlayerLoader.cs | 8 ++----- .../Visual/TestCaseScreenBreadcrumbControl.cs | 4 ++-- osu.Game/Tests/OsuTestBrowser.cs | 1 + osu.Game/Tests/Visual/ScreenTestCase.cs | 8 ++----- 6 files changed, 11 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index 5484824c5b..7d486fc56c 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -9,7 +9,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Input.States; @@ -54,7 +53,7 @@ namespace osu.Game.Tests.Visual private BeatmapManager manager; private RulesetStore rulesets; - private ScreenStackCacheContainer screenStackContainer; + private OsuScreenStack screenStack; [BackgroundDependencyLoader] private void load(GameHost host) @@ -85,8 +84,8 @@ namespace osu.Game.Tests.Visual manager.Delete(manager.GetAllUsableBeatmapSets()); var temp = TestResources.GetTestBeatmapForImport(); manager.Import(temp); - Child = screenStackContainer = new ScreenStackCacheContainer { RelativeSizeAxes = Axes.Both }; - screenStackContainer.ScreenStack.Push(songSelect = new DummySongSelect()); + Child = screenStack = new OsuScreenStack() { RelativeSizeAxes = Axes.Both }; + screenStack.Push(songSelect = new DummySongSelect()); }); } @@ -354,20 +353,6 @@ namespace osu.Game.Tests.Visual } } - private class ScreenStackCacheContainer : Container - { - [Cached] - private BackgroundScreenStack backgroundScreenStack; - - public readonly ScreenStack ScreenStack; - - public ScreenStackCacheContainer() - { - Add(backgroundScreenStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }); - Add(ScreenStack = new ScreenStack { RelativeSizeAxes = Axes.Both }); - } - } - private class DimAccessiblePlayerLoader : PlayerLoader { public VisualSettings VisualSettingsPos => VisualSettings; diff --git a/osu.Game.Tests/Visual/TestCaseMultiHeader.cs b/osu.Game.Tests/Visual/TestCaseMultiHeader.cs index f7802e2d08..d7d8073a33 100644 --- a/osu.Game.Tests/Visual/TestCaseMultiHeader.cs +++ b/osu.Game.Tests/Visual/TestCaseMultiHeader.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual { int index = 0; - ScreenStack screenStack = new ScreenStack(new TestMultiplayerSubScreen(index)) { RelativeSizeAxes = Axes.Both }; + OsuScreenStack screenStack = new OsuScreenStack(new TestMultiplayerSubScreen(index)) { RelativeSizeAxes = Axes.Both }; Children = new Drawable[] { diff --git a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs index 244f553e97..0e6a237325 100644 --- a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs @@ -14,15 +14,11 @@ namespace osu.Game.Tests.Visual public class TestCasePlayerLoader : ManualInputManagerTestCase { private PlayerLoader loader; - private readonly ScreenStack stack; - - [Cached] - private BackgroundScreenStack backgroundStack; + private readonly OsuScreenStack stack; public TestCasePlayerLoader() { - InputManager.Add(backgroundStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }); - InputManager.Add(stack = new ScreenStack { RelativeSizeAxes = Axes.Both }); + InputManager.Add(stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }); } [BackgroundDependencyLoader] diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs index 204f4a493d..697d2405ba 100644 --- a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs +++ b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs @@ -19,14 +19,14 @@ namespace osu.Game.Tests.Visual public class TestCaseScreenBreadcrumbControl : OsuTestCase { private readonly ScreenBreadcrumbControl breadcrumbs; - private readonly ScreenStack screenStack; + private readonly OsuScreenStack screenStack; public TestCaseScreenBreadcrumbControl() { OsuSpriteText titleText; IScreen startScreen = new TestScreenOne(); - screenStack = new ScreenStack(startScreen) { RelativeSizeAxes = Axes.Both }; + screenStack = new OsuScreenStack(startScreen) { RelativeSizeAxes = Axes.Both }; Children = new Drawable[] { diff --git a/osu.Game/Tests/OsuTestBrowser.cs b/osu.Game/Tests/OsuTestBrowser.cs index 71b0b02fa6..7af925f7ee 100644 --- a/osu.Game/Tests/OsuTestBrowser.cs +++ b/osu.Game/Tests/OsuTestBrowser.cs @@ -6,6 +6,7 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Graphics; +using osu.Game.Screens; using osu.Game.Screens.Backgrounds; namespace osu.Game.Tests diff --git a/osu.Game/Tests/Visual/ScreenTestCase.cs b/osu.Game/Tests/Visual/ScreenTestCase.cs index 79c57ad9f4..dd8aed8af3 100644 --- a/osu.Game/Tests/Visual/ScreenTestCase.cs +++ b/osu.Game/Tests/Visual/ScreenTestCase.cs @@ -13,17 +13,13 @@ namespace osu.Game.Tests.Visual ///
public abstract class ScreenTestCase : OsuTestCase { - private readonly ScreenStack stack; - - [Cached] - private BackgroundScreenStack backgroundStack; + private readonly OsuScreenStack stack; protected ScreenTestCase() { Children = new Drawable[] { - backgroundStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }, - stack = new ScreenStack { RelativeSizeAxes = Axes.Both } + stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both } }; } From ee7169a62919680061e0963bfc3f396aac62d540 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 17:26:16 +0900 Subject: [PATCH 070/623] Use new non-immediate suspend logic in BackgroundScreenStack --- osu.Game/Screens/BackgroundScreenStack.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs index b010a70e66..5f82329496 100644 --- a/osu.Game/Screens/BackgroundScreenStack.cs +++ b/osu.Game/Screens/BackgroundScreenStack.cs @@ -11,6 +11,7 @@ namespace osu.Game.Screens public class BackgroundScreenStack : ScreenStack { public BackgroundScreenStack() + : base(false) { Scale = new Vector2(1.06f); RelativeSizeAxes = Axes.Both; From 8230d5b52e788b031c09e2110cbaac85fcbf3257 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 17:27:20 +0900 Subject: [PATCH 071/623] Ensure initial blur is set on song select background creation We do not want the blur transition to play here --- osu.Game/Screens/BlurrableBackgroundScreen.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/BlurrableBackgroundScreen.cs b/osu.Game/Screens/BlurrableBackgroundScreen.cs index d19e699acb..5520abfe6e 100644 --- a/osu.Game/Screens/BlurrableBackgroundScreen.cs +++ b/osu.Game/Screens/BlurrableBackgroundScreen.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens protected Vector2 BlurTarget; - public TransformSequence BlurTo(Vector2 sigma, double duration, Easing easing = Easing.None) + public TransformSequence BlurTo(Vector2 sigma, double duration = 0, Easing easing = Easing.None) { BlurTarget = sigma; return Background?.BlurTo(BlurTarget, duration, easing); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index e297c0e343..66e900c9fd 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; @@ -61,7 +61,12 @@ namespace osu.Game.Screens.Select /// protected readonly Container FooterPanels; - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); + protected override BackgroundScreen CreateBackground() + { + var background = new BackgroundScreenBeatmap(); + background.BlurTo(background_blur); + return background; + } protected readonly BeatmapCarousel Carousel; private readonly BeatmapInfoWedge beatmapInfoWedge; From 3c2d8cad0acbbdc5374dacaf0392931f86b665f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Mar 2019 17:32:40 +0900 Subject: [PATCH 072/623] Add better async logic for ScreenWithBeatmapBackground --- .../Backgrounds/BackgroundScreenBeatmap.cs | 28 ++++++++++++------- .../Play/ScreenWithBeatmapBackground.cs | 2 +- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index d7aa095440..54ce005469 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -36,9 +37,13 @@ namespace osu.Game.Screens.Backgrounds [BackgroundDependencyLoader] private void load() { - backgroundLoaded(new BeatmapBackground(beatmap)); + var background = new BeatmapBackground(beatmap); + LoadComponent(background); + switchBackground(background); } + private CancellationTokenSource cancellationSource; + public WorkingBeatmap Beatmap { get => beatmap; @@ -49,14 +54,18 @@ namespace osu.Game.Screens.Backgrounds beatmap = value; - // load will be completed in async load. - if (LoadState < LoadState.Ready) return; + Schedule(() => + { + if ((Background as BeatmapBackground)?.Beatmap == beatmap) + return; - Schedule(() => { LoadComponentAsync(new BeatmapBackground(beatmap), b => Schedule(() => backgroundLoaded(b))); }); + cancellationSource?.Cancel(); + LoadComponentAsync(new BeatmapBackground(beatmap), switchBackground, (cancellationSource = new CancellationTokenSource()).Token); + }); } } - private void backgroundLoaded(BeatmapBackground b) + private void switchBackground(BeatmapBackground b) { float newDepth = 0; if (Background != null) @@ -75,25 +84,24 @@ namespace osu.Game.Screens.Backgrounds public override bool Equals(BackgroundScreen other) { - var otherBeatmapBackground = other as BackgroundScreenBeatmap; - if (otherBeatmapBackground == null) return false; + if (!(other is BackgroundScreenBeatmap otherBeatmapBackground)) return false; return base.Equals(other) && beatmap == otherBeatmapBackground.Beatmap; } protected class BeatmapBackground : Background { - private readonly WorkingBeatmap beatmap; + public readonly WorkingBeatmap Beatmap; public BeatmapBackground(WorkingBeatmap beatmap) { - this.beatmap = beatmap; + Beatmap = beatmap; } [BackgroundDependencyLoader] private void load(TextureStore textures) { - Sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg1"); + Sprite.Texture = Beatmap?.Background ?? textures.Get(@"Backgrounds/bg1"); } } } diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs index 328aa1d18e..3aabceaaef 100644 --- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs +++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Play /// Called when background elements require updates, usually due to a user changing a setting. /// /// - protected virtual void UpdateBackgroundElements() + protected void UpdateBackgroundElements() { if (!this.IsCurrentScreen()) return; From 9368081b99bf214d620a88c17ddde8b8e1e00372 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 12 Mar 2019 17:33:16 +0900 Subject: [PATCH 073/623] Fix TestCasePlayer --- osu.Game/OsuGame.cs | 4 +++- osu.Game/Tests/Visual/TestCasePlayer.cs | 12 ++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index dc83f1f9d6..ad2980d818 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -87,7 +87,7 @@ namespace osu.Game public readonly Bindable OverlayActivationMode = new Bindable(); - private readonly OsuScreenStack screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; + private OsuScreenStack screenStack; private VolumeOverlay volume; private OnScreenDisplay onscreenDisplay; private OsuLogo osuLogo; @@ -199,6 +199,8 @@ namespace osu.Game LocalConfig.BindWith(OsuSetting.VolumeInactive, inactiveVolumeAdjust); IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true); + + screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; } private ExternalLinkOpener externalLinkOpener; diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index 5ff798c40d..1c4e147def 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -108,17 +108,13 @@ namespace osu.Game.Tests.Visual Player?.Exit(); - var player = CreatePlayer(r); + var p = Player = CreatePlayer(r); - playerWeakReferences.Add(player); + playerWeakReferences.Add(p); - LoadComponentAsync(player, p => - { - Player = p; - LoadScreen(p); - }); + LoadScreen(Player); - return player; + return p; } protected virtual Player CreatePlayer(Ruleset ruleset) => new Player From e6a55cd6744e289addd6b86943e6c83ef6080bce Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 12 Mar 2019 17:45:16 +0900 Subject: [PATCH 074/623] Fix TestCaseEditor as well --- osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs | 2 +- osu.Game/Screens/OsuScreenStack.cs | 1 - osu.Game/Tests/OsuTestBrowser.cs | 1 - osu.Game/Tests/Visual/EditorTestCase.cs | 2 +- osu.Game/Tests/Visual/ScreenTestCase.cs | 2 -- 5 files changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index 7d486fc56c..3ede48c49c 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual manager.Delete(manager.GetAllUsableBeatmapSets()); var temp = TestResources.GetTestBeatmapForImport(); manager.Import(temp); - Child = screenStack = new OsuScreenStack() { RelativeSizeAxes = Axes.Both }; + Child = screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; screenStack.Push(songSelect = new DummySongSelect()); }); } diff --git a/osu.Game/Screens/OsuScreenStack.cs b/osu.Game/Screens/OsuScreenStack.cs index d6d6272e0e..2eea7bebbb 100644 --- a/osu.Game/Screens/OsuScreenStack.cs +++ b/osu.Game/Screens/OsuScreenStack.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Tests/OsuTestBrowser.cs b/osu.Game/Tests/OsuTestBrowser.cs index 7af925f7ee..71b0b02fa6 100644 --- a/osu.Game/Tests/OsuTestBrowser.cs +++ b/osu.Game/Tests/OsuTestBrowser.cs @@ -6,7 +6,6 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Graphics; -using osu.Game.Screens; using osu.Game.Screens.Backgrounds; namespace osu.Game.Tests diff --git a/osu.Game/Tests/Visual/EditorTestCase.cs b/osu.Game/Tests/Visual/EditorTestCase.cs index 67a1cb6de3..96e70e018e 100644 --- a/osu.Game/Tests/Visual/EditorTestCase.cs +++ b/osu.Game/Tests/Visual/EditorTestCase.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual { Beatmap.Value = new TestWorkingBeatmap(ruleset.RulesetInfo, null); - LoadComponentAsync(new Editor(), LoadScreen); + LoadScreen(new Editor()); } } } diff --git a/osu.Game/Tests/Visual/ScreenTestCase.cs b/osu.Game/Tests/Visual/ScreenTestCase.cs index dd8aed8af3..a8286f6d7b 100644 --- a/osu.Game/Tests/Visual/ScreenTestCase.cs +++ b/osu.Game/Tests/Visual/ScreenTestCase.cs @@ -1,9 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Screens; using osu.Game.Screens; namespace osu.Game.Tests.Visual From 2e1b274fdaab12a51d19ec5022efe9542d1a6f86 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 12 Mar 2019 17:59:11 +0900 Subject: [PATCH 075/623] No need for player var --- osu.Game/Tests/Visual/TestCasePlayer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index 1c4e147def..cae6478232 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -108,13 +108,13 @@ namespace osu.Game.Tests.Visual Player?.Exit(); - var p = Player = CreatePlayer(r); + Player = CreatePlayer(r); - playerWeakReferences.Add(p); + playerWeakReferences.Add(Player); LoadScreen(Player); - return p; + return Player; } protected virtual Player CreatePlayer(Ruleset ruleset) => new Player From f0114d776db14f2c71e89eedefc710e9956921e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Mar 2019 12:56:47 +0900 Subject: [PATCH 076/623] Use interface to access API Allows for better testability. --- .../Visual/TestCaseMatchLeaderboard.cs | 2 +- ...stCaseUpdateableBeatmapBackgroundSprite.cs | 2 +- osu.Game.Tests/Visual/TestCaseUserProfile.cs | 4 +- osu.Game/Beatmaps/BeatmapManager.cs | 4 +- osu.Game/Online/API/APIAccess.cs | 2 +- osu.Game/Online/API/APIRequest.cs | 11 +++-- osu.Game/Online/API/DummyAPIAccess.cs | 32 +++++++++++++-- osu.Game/Online/API/IAPIProvider.cs | 40 +++++++++++++++++++ osu.Game/Online/API/IOnlineComponent.cs | 2 +- osu.Game/Online/Leaderboards/Leaderboard.cs | 6 +-- osu.Game/OsuGameBase.cs | 1 - .../Overlays/AccountCreation/ScreenEntry.cs | 4 +- .../Overlays/AccountCreation/ScreenWarning.cs | 4 +- osu.Game/Overlays/AccountCreationOverlay.cs | 4 +- .../BeatmapSet/Buttons/DownloadButton.cs | 2 +- .../BeatmapSet/Scores/ScoresContainer.cs | 4 +- osu.Game/Overlays/BeatmapSetOverlay.cs | 4 +- osu.Game/Overlays/DirectOverlay.cs | 4 +- .../Profile/Sections/PaginatedContainer.cs | 4 +- .../Sections/Recent/DrawableRecentActivity.cs | 4 +- .../Sections/General/LoginSettings.cs | 8 ++-- osu.Game/Overlays/SocialOverlay.cs | 6 +-- .../Overlays/Toolbar/ToolbarUserButton.cs | 4 +- osu.Game/Overlays/UserProfileOverlay.cs | 4 +- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- osu.Game/Screens/Menu/Disclaimer.cs | 4 +- .../Multi/Lounge/Components/RoomInspector.cs | 2 +- osu.Game/Screens/Multi/Multiplayer.cs | 4 +- .../Screens/Multi/Play/TimeshiftPlayer.cs | 2 +- osu.Game/Screens/Multi/RoomManager.cs | 2 +- osu.Game/Screens/Play/Player.cs | 4 +- osu.Game/Screens/Select/BeatmapDetails.cs | 4 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- 33 files changed, 127 insertions(+), 61 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs index 42a886a5a3..484a212a38 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs index 506121efd7..ac90c264c4 100644 --- a/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual private BeatmapManager beatmaps { get; set; } [BackgroundDependencyLoader] - private void load(OsuGameBase osu, APIAccess api, RulesetStore rulesets) + private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets) { Bindable beatmapBindable = new Bindable(); diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs index 726134294e..46ee74b69f 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual public class TestCaseUserProfile : OsuTestCase { private readonly TestUserProfileOverlay profile; - private APIAccess api; + private IAPIProvider api; public override IReadOnlyList RequiredTypes => new[] { @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 711aa0b79b..0ef8098a76 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -64,7 +64,7 @@ namespace osu.Game.Beatmaps private readonly BeatmapStore beatmaps; - private readonly APIAccess api; + private readonly IAPIProvider api; private readonly AudioManager audioManager; @@ -72,7 +72,7 @@ namespace osu.Game.Beatmaps private readonly List currentDownloads = new List(); - public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, AudioManager audioManager, GameHost host = null, + public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null, WorkingBeatmap defaultBeatmap = null) : base(storage, contextFactory, new BeatmapStore(contextFactory), host) { diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 5593abf348..3d861e44bf 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -22,7 +22,7 @@ namespace osu.Game.Online.API private readonly OsuConfigManager config; private readonly OAuth authentication; - public string Endpoint = @"https://osu.ppy.sh"; + public string Endpoint => @"https://osu.ppy.sh"; private const string client_id = @"5"; private const string client_secret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"; diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 2781a5709b..96f3b85272 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -61,9 +61,12 @@ namespace osu.Game.Online.API private Action pendingFailure; - public void Perform(APIAccess api) + public void Perform(IAPIProvider api) { - API = api; + if (!(api is APIAccess apiAccess)) + throw new NotSupportedException($"A {nameof(APIAccess)} is required to perform requests."); + + API = apiAccess; if (checkAndScheduleFailure()) return; @@ -71,7 +74,7 @@ namespace osu.Game.Online.API WebRequest = CreateWebRequest(); WebRequest.Failed += Fail; WebRequest.AllowRetryOnTimeout = false; - WebRequest.AddHeader("Authorization", $"Bearer {api.AccessToken}"); + WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}"); if (checkAndScheduleFailure()) return; @@ -85,7 +88,7 @@ namespace osu.Game.Online.API if (checkAndScheduleFailure()) return; - api.Schedule(delegate { Success?.Invoke(); }); + API.Schedule(delegate { Success?.Invoke(); }); } public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled")); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 096ab5d8c8..af2fe51b38 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -11,14 +11,16 @@ namespace osu.Game.Online.API public Bindable LocalUser { get; } = new Bindable(new User { Username = @"Dummy", - Id = 1, + Id = 1001, }); public bool IsLoggedIn => true; - public void Update() - { - } + public string ProvidedUsername => LocalUser.Value.Username; + + public string Endpoint => "https://test.com"; + + public APIState State => LocalUser.Value.Id == 1 ? APIState.Offline : APIState.Online; public virtual void Queue(APIRequest request) { @@ -26,6 +28,28 @@ namespace osu.Game.Online.API public void Register(IOnlineComponent component) { + // todo: add support } + + public void Unregister(IOnlineComponent component) + { + // todo: add support + } + + public void Login(string username, string password) + { + LocalUser.Value = new User + { + Username = @"Dummy", + Id = 1, + }; + } + + public void Logout() + { + LocalUser.Value = new GuestUser(); + } + + public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password) => null; } } diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index e4533ecb3d..7c1f850943 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -18,6 +18,19 @@ namespace osu.Game.Online.API /// bool IsLoggedIn { get; } + /// + /// The last username provided by the end-user. + /// May not be authenticated. + /// + string ProvidedUsername { get; } + + /// + /// The URL endpoint for this API. Does not include a trailing slash. + /// + string Endpoint { get; } + + APIState State { get; } + /// /// Queue a new request. /// @@ -29,5 +42,32 @@ namespace osu.Game.Online.API /// /// The component to register. void Register(IOnlineComponent component); + + /// + /// Unregisters a component to receive state changes. + /// + /// The component to unregister. + void Unregister(IOnlineComponent component); + + /// + /// Attempt to login using the provided credentials. This is a non-blocking operation. + /// + /// The user's username. + /// The user's password. + void Login(string username, string password); + + /// + /// Log out the current user. + /// + void Logout(); + + /// + /// Create a new user account. This is a blocking operation. + /// + /// The email to create the account with. + /// The username to create the account with. + /// The password to create the account with. + /// Any errors encoutnered during account creation. + RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password); } } diff --git a/osu.Game/Online/API/IOnlineComponent.cs b/osu.Game/Online/API/IOnlineComponent.cs index fb40bfd1e6..da6b784759 100644 --- a/osu.Game/Online/API/IOnlineComponent.cs +++ b/osu.Game/Online/API/IOnlineComponent.cs @@ -5,6 +5,6 @@ namespace osu.Game.Online.API { public interface IOnlineComponent { - void APIStateChanged(APIAccess api, APIState state); + void APIStateChanged(IAPIProvider api, APIState state); } } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index f5b6e185c7..ac1666f8ed 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -174,12 +174,12 @@ namespace osu.Game.Online.Leaderboards }; } - private APIAccess api; + private IAPIProvider api; private ScheduledDelegate pendingUpdateScores; [BackgroundDependencyLoader(true)] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; api?.Register(this); @@ -195,7 +195,7 @@ namespace osu.Game.Online.Leaderboards private APIRequest getScoresRequest; - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { if (state == APIState.Online) UpdateScores(); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 643a673faf..7d55d19e50 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -152,7 +152,6 @@ namespace osu.Game API = new APIAccess(LocalConfig); - dependencies.Cache(API); dependencies.CacheAs(API); var defaultBeatmap = new DummyWorkingBeatmap(this); diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index 2fc00edbc1..13d8df098f 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.AccountCreation private OsuTextBox emailTextBox; private OsuPasswordTextBox passwordTextBox; - private APIAccess api; + private IAPIProvider api; private ShakeContainer registerShake; private IEnumerable characterCheckText; @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.AccountCreation private GameHost host; [BackgroundDependencyLoader] - private void load(OsuColour colours, APIAccess api, GameHost host) + private void load(OsuColour colours, IAPIProvider api, GameHost host) { this.api = api; this.host = host; diff --git a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs index 4e2cc1ea00..be417f4aac 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.AccountCreation { private OsuTextFlowContainer multiAccountExplanationText; private LinkFlowContainer furtherAssistance; - private APIAccess api; + private IAPIProvider api; private const string help_centre_url = "/help/wiki/Help_Centre#login"; @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.AccountCreation } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, APIAccess api, OsuGame game, TextureStore textures) + private void load(OsuColour colours, IAPIProvider api, OsuGame game, TextureStore textures) { this.api = api; diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index bc780538d5..e8e44c206e 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(OsuColour colours, APIAccess api) + private void load(OsuColour colours, IAPIProvider api) { api.Register(this); @@ -96,7 +96,7 @@ namespace osu.Game.Overlays this.FadeOut(100); } - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { switch (state) { diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs index edd886f0f2..bbbcff0558 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons } [BackgroundDependencyLoader] - private void load(APIAccess api, BeatmapManager beatmaps) + private void load(IAPIProvider api, BeatmapManager beatmaps) { FillFlowContainer textSprites; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 6c65d491af..3dd03fcea6 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } private GetScoresRequest getScoresRequest; - private APIAccess api; + private IAPIProvider api; public BeatmapInfo Beatmap { @@ -129,7 +129,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; updateDisplay(); diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 55c21b7fc9..c49268bc16 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays private readonly Header header; - private APIAccess api; + private IAPIProvider api; private RulesetStore rulesets; private readonly ScrollContainer scroll; @@ -101,7 +101,7 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(APIAccess api, RulesetStore rulesets) + private void load(IAPIProvider api, RulesetStore rulesets) { this.api = api; this.rulesets = rulesets; diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index a3d25a7a1c..34edbbcc8b 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays { private const float panel_padding = 10f; - private APIAccess api; + private IAPIProvider api; private RulesetStore rulesets; private readonly FillFlowContainer resultCountsContainer; @@ -164,7 +164,7 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(OsuColour colours, APIAccess api, RulesetStore rulesets, PreviewTrackManager previewTrackManager) + private void load(OsuColour colours, IAPIProvider api, RulesetStore rulesets, PreviewTrackManager previewTrackManager) { this.api = api; this.rulesets = rulesets; diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 497d6c3fc4..46c65b9db7 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections protected readonly Bindable User = new Bindable(); - protected APIAccess Api; + protected IAPIProvider Api; protected APIRequest RetrievalRequest; protected RulesetStore Rulesets; @@ -84,7 +84,7 @@ namespace osu.Game.Overlays.Profile.Sections } [BackgroundDependencyLoader] - private void load(APIAccess api, RulesetStore rulesets) + private void load(IAPIProvider api, RulesetStore rulesets) { Api = api; Rulesets = rulesets; diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs index 7e721ac807..8fab29e42c 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent { public class DrawableRecentActivity : DrawableProfileRow { - private APIAccess api; + private IAPIProvider api; private readonly APIRecentActivity activity; @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 026424e1ff..d6738250f9 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -58,14 +58,14 @@ namespace osu.Game.Overlays.Settings.Sections.General } [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuColour colours, APIAccess api) + private void load(OsuColour colours, IAPIProvider api) { this.colours = colours; api?.Register(this); } - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { form = null; @@ -194,7 +194,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { private TextBox username; private TextBox password; - private APIAccess api; + private IAPIProvider api; public Action RequestHide; @@ -205,7 +205,7 @@ namespace osu.Game.Overlays.Settings.Sections.General } [BackgroundDependencyLoader(permitNulls: true)] - private void load(APIAccess api, OsuConfigManager config, AccountCreationOverlay accountCreation) + private void load(IAPIProvider api, OsuConfigManager config, AccountCreationOverlay accountCreation) { this.api = api; Direction = FillDirection.Vertical; diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 3464058abb..daf3d1c576 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays { public class SocialOverlay : SearchableListOverlay, IOnlineComponent { - private APIAccess api; + private IAPIProvider api; private readonly LoadingAnimation loading; private FillFlowContainer panels; @@ -89,7 +89,7 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; api.Register(this); @@ -193,7 +193,7 @@ namespace osu.Game.Overlays } } - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { switch (state) { diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index d47f3a7b16..8d1910fc19 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -43,12 +43,12 @@ namespace osu.Game.Overlays.Toolbar } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { api.Register(this); } - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { switch (state) { diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 1ff1c2ce91..48ce055975 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays private ProfileSection lastSection; private ProfileSection[] sections; private GetUserRequest userReq; - private APIAccess api; + private IAPIProvider api; protected ProfileHeader Header; private SectionsContainer sectionsContainer; private ProfileTabControl tabs; @@ -56,7 +56,7 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 1f68818669..3df4ef9059 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -97,7 +97,7 @@ namespace osu.Game.Screens.Menu private OsuGame game { get; set; } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [Resolved(CanBeNull = true)] private NotificationOverlay notifications { get; set; } diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 89f4f92092..7a80a686b1 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Menu } [BackgroundDependencyLoader] - private void load(OsuColour colours, APIAccess api) + private void load(OsuColour colours, IAPIProvider api) { InternalChildren = new Drawable[] { diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs index fd9c8d7b35..5798fce457 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs @@ -246,7 +246,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } private GetRoomScoresRequest request; diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index dd01ae4160..f38fc4e3ff 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Multi private OsuGameBase game { get; set; } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [Resolved(CanBeNull = true)] private OsuLogo logo { get; set; } @@ -163,7 +163,7 @@ namespace osu.Game.Screens.Multi this.Push(new PlayerLoader(player)); } - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { if (state != APIState.Online) forcefullyExit(); diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index 6b88403b9e..d5b8f1f0c8 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Multi.Play private readonly PlaylistItem playlistItem; [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [Resolved] private IBindable ruleset { get; set; } diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index c15a8471a1..385cbe20e5 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Multi private Bindable currentFilter { get; set; } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [Resolved] private RulesetStore rulesets { get; set; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ced0a43679..b11c5e51c9 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Play private RulesetInfo ruleset; - private APIAccess api; + private IAPIProvider api; private SampleChannel sampleRestart; @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Play private GameplayClockContainer gameplayClockContainer; [BackgroundDependencyLoader] - private void load(AudioManager audio, APIAccess api, OsuConfigManager config) + private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) { this.api = api; diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 604d7a132b..751ff164e2 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Select private readonly FailRetryGraph failRetryGraph; private readonly DimmedLoadingAnimation loading; - private APIAccess api; + private IAPIProvider api; private ScheduledDelegate pendingBeatmapSwitch; @@ -160,7 +160,7 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 61370f7ce3..ebb1d78ba0 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Select.Leaderboards private IBindable ruleset { get; set; } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [BackgroundDependencyLoader] private void load() From b1f18481e0b0c008625841abfa07ab73d658e1a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Mar 2019 12:57:01 +0900 Subject: [PATCH 077/623] Show text to supporters telling them they're cool Also adds better tests for disclaimer screen. --- osu.Game.Tests/Visual/TestCaseDisclaimer.cs | 17 ++++- osu.Game/Screens/Menu/Disclaimer.cs | 85 +++++++++++++++------ 2 files changed, 76 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseDisclaimer.cs b/osu.Game.Tests/Visual/TestCaseDisclaimer.cs index 3ceb3eb4bd..f08a2a54ca 100644 --- a/osu.Game.Tests/Visual/TestCaseDisclaimer.cs +++ b/osu.Game.Tests/Visual/TestCaseDisclaimer.cs @@ -2,16 +2,31 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Online.API; using osu.Game.Screens.Menu; +using osu.Game.Users; namespace osu.Game.Tests.Visual { public class TestCaseDisclaimer : ScreenTestCase { + [Cached(typeof(IAPIProvider))] + private readonly DummyAPIAccess api = new DummyAPIAccess(); + [BackgroundDependencyLoader] private void load() { - LoadScreen(new Disclaimer()); + AddStep("load disclaimer", () => LoadScreen(new Disclaimer())); + + AddStep("toggle support", () => + { + api.LocalUser.Value = new User + { + Username = api.LocalUser.Value.Username, + Id = api.LocalUser.Value.Id, + IsSupporter = !api.LocalUser.Value.IsSupporter, + }; + }); } } } diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 7a80a686b1..af8c127a3c 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -1,12 +1,13 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Graphics; @@ -25,16 +26,17 @@ namespace osu.Game.Screens.Menu private SpriteIcon icon; private Color4 iconColour; private LinkFlowContainer textFlow; + private LinkFlowContainer supportFlow; public override bool HideOverlaysOnEnter => true; public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; public override bool CursorVisible => false; - private readonly List supporterDrawables = new List(); private Drawable heart; private const float icon_y = -85; + private const float icon_size = 30; private readonly Bindable currentUser = new Bindable(); @@ -53,19 +55,38 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, Icon = FontAwesome.fa_warning, - Size = new Vector2(30), + Size = new Vector2(icon_size), Y = icon_y, }, - textFlow = new LinkFlowContainer + new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(50), - TextAnchor = Anchor.TopCentre, - Y = -110, + Direction = FillDirection.Vertical, + Y = icon_y + icon_size, Anchor = Anchor.Centre, Origin = Anchor.TopCentre, - Spacing = new Vector2(0, 2), + Children = new Drawable[] + { + textFlow = new LinkFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Spacing = new Vector2(0, 2), + }, + supportFlow = new LinkFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Spacing = new Vector2(0, 2), + }, + } } }; @@ -88,28 +109,42 @@ namespace osu.Game.Screens.Menu textFlow.NewParagraph(); textFlow.NewParagraph(); - supporterDrawables.AddRange(textFlow.AddText("Consider becoming an ", format)); - supporterDrawables.AddRange(textFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", creationParameters: format)); - supporterDrawables.AddRange(textFlow.AddText(" to help support the game", format)); - - supporterDrawables.Add(heart = textFlow.AddIcon(FontAwesome.fa_heart, t => - { - t.Padding = new MarginPadding { Left = 5 }; - t.Font = t.Font.With(size: 12); - t.Colour = colours.Pink; - t.Origin = Anchor.Centre; - }).First()); - iconColour = colours.Yellow; currentUser.BindTo(api.LocalUser); currentUser.BindValueChanged(e => { + supportFlow.Children.ForEach(d => d.FadeOut(500, Easing.OutQuint).Expire()); + if (e.NewValue.IsSupporter) - supporterDrawables.ForEach(d => d.FadeOut(500, Easing.OutQuint).Expire()); + { + supportFlow.AddText("Thank you for supporting osu!", format); + } + else + { + supportFlow.AddText("Consider becoming an ", format); + supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", creationParameters: format); + supportFlow.AddText(" to help support the game", format); + } + + heart = supportFlow.AddIcon(FontAwesome.fa_heart, t => + { + t.Padding = new MarginPadding { Left = 5 }; + t.Font = t.Font.With(size: 12); + t.Origin = Anchor.Centre; + t.Colour = colours.Pink; + }).First(); + + if (IsLoaded) + animateHeart(); }, true); } + private void animateHeart() + { + heart.FlashColour(Color4.White, 750, Easing.OutQuint).Loop(); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -128,7 +163,9 @@ namespace osu.Game.Screens.Menu .MoveToY(icon_y, 160, Easing.InCirc) .RotateTo(0, 160, Easing.InCirc); - supporterDrawables.ForEach(d => d.FadeOut().Delay(2000).FadeIn(500)); + supportFlow.FadeOut().Delay(2000).FadeIn(500); + + animateHeart(); this .FadeInFromZero(500) @@ -136,8 +173,6 @@ namespace osu.Game.Screens.Menu .FadeOut(250) .ScaleTo(0.9f, 250, Easing.InQuint) .Finally(d => this.Push(intro)); - - heart.FlashColour(Color4.White, 750, Easing.OutQuint).Loop(); } } } From 04b88cdd11889d309777a93937066ab943d968c6 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 13 Mar 2019 14:12:05 +0900 Subject: [PATCH 078/623] Add required types for BeatmapDetailArea test case --- osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs index ff39ad5c0d..6cc3982f9c 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -14,6 +16,8 @@ namespace osu.Game.Tests.Visual [System.ComponentModel.Description("PlaySongSelect leaderboard/details area")] public class TestCaseBeatmapDetailArea : OsuTestCase { + public override IReadOnlyList RequiredTypes => new[] { typeof(BeatmapDetails) }; + public TestCaseBeatmapDetailArea() { BeatmapDetailArea detailsArea; From e6449db8e374f33779e1d9326e43c5f6270c7603 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 13 Mar 2019 14:12:36 +0900 Subject: [PATCH 079/623] Null metrics on null beatmap for transition animation --- osu.Game/Screens/Select/BeatmapDetails.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 6057ba382b..46979d0000 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -182,6 +182,7 @@ namespace osu.Game.Screens.Select if (Beatmap == null) { + updateMetrics(null); ratingsContainer.FadeOut(transition_duration); failRetryContainer.FadeOut(transition_duration); return; From de6d8fc6378b77b27c2c99702bfd88fd639e0788 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 13 Mar 2019 16:47:03 +0900 Subject: [PATCH 080/623] Move user blurring into VIsualSettingsContainer --- .../Visual/TestCaseBackgroundScreenBeatmap.cs | 90 ++++++++++--------- ...ontainer.cs => VisualSettingsContainer.cs} | 48 ++++++---- .../Backgrounds/BackgroundScreenBeatmap.cs | 9 +- osu.Game/Screens/Play/Player.cs | 10 +-- osu.Game/Screens/Play/PlayerLoader.cs | 31 ++++--- .../Play/ScreenWithBeatmapBackground.cs | 37 -------- osu.Game/Screens/Ranking/Results.cs | 6 +- osu.Game/Screens/Select/SongSelect.cs | 4 +- 8 files changed, 107 insertions(+), 128 deletions(-) rename osu.Game/Graphics/Containers/{UserDimContainer.cs => VisualSettingsContainer.cs} (53%) diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index 5484824c5b..03078abd3a 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -43,13 +43,13 @@ namespace osu.Game.Tests.Visual typeof(ScreenWithBeatmapBackground), typeof(PlayerLoader), typeof(Player), - typeof(UserDimContainer), + typeof(VisualSettingsContainer), typeof(OsuScreen) }; private DummySongSelect songSelect; - private DimAccessiblePlayerLoader playerLoader; - private DimAccessiblePlayer player; + private TestPlayerLoader playerLoader; + private TestPlayer player; private DatabaseContextFactory factory; private BeatmapManager manager; private RulesetStore rulesets; @@ -91,13 +91,13 @@ namespace osu.Game.Tests.Visual } /// - /// Check if properly triggers background dim previews when a user hovers over the visual settings panel. + /// Check if properly triggers the visual settings preview when a user hovers over the visual settings panel. /// [Test] public void PlayerLoaderSettingsHoverTest() { setupUserSettings(); - AddStep("Start player loader", () => songSelect.Push(playerLoader = new DimAccessiblePlayerLoader(player = new DimAccessiblePlayer()))); + AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer()))); AddUntilStep(() => playerLoader?.IsLoaded ?? false, "Wait for Player Loader to load"); AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent()); AddStep("Trigger background preview", () => @@ -106,16 +106,16 @@ namespace osu.Game.Tests.Visual InputManager.MoveMouseTo(playerLoader.VisualSettingsPos); }); waitForDim(); - AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); waitForDim(); - AddAssert("Screen is undimmed", () => songSelect.IsBackgroundUndimmed()); + AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled()); } /// /// In the case of a user triggering the dim preview the instant player gets loaded, then moving the cursor off of the visual settings: - /// The OnHover of PlayerLoader will trigger, which could potentially trigger an undim unless checked for in PlayerLoader. - /// We need to check that in this scenario, the dim is still properly applied after entering player. + /// The OnHover of PlayerLoader will trigger, which could potentially cause visual settings to be unapplied unless checked for in PlayerLoader. + /// We need to check that in this scenario, the dim and blur is still properly applied after entering player. /// [Test] public void PlayerLoaderTransitionTest() @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual AddStep("Trigger hover event", () => playerLoader.TriggerOnHover()); AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent()); waitForDim(); - AddAssert("Screen is dimmed and unblurred", () => songSelect.IsBackgroundDimmed() && songSelect.IsBackgroundUnblurred()); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } /// @@ -165,24 +165,24 @@ namespace osu.Game.Tests.Visual } /// - /// Check if the is properly accepting user dim changes at all. + /// Check if the is properly accepting user-defined visual changes at all. /// [Test] public void DisableUserDimTest() { performFullSetup(); waitForDim(); - AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); AddStep("EnableUserDim disabled", () => songSelect.DimEnabled.Value = false); waitForDim(); - AddAssert("Screen is undimmed", () => songSelect.IsBackgroundUndimmed()); + AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled()); AddStep("EnableUserDim enabled", () => songSelect.DimEnabled.Value = true); waitForDim(); - AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } /// - /// Check if the fade container retains dim when pausing + /// Check if the visual settings container retains dim and blur when pausing /// [Test] public void PauseTest() @@ -190,14 +190,14 @@ namespace osu.Game.Tests.Visual performFullSetup(true); AddStep("Pause", () => player.CurrentPausableGameplayContainer.Pause()); waitForDim(); - AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); AddStep("Unpause", () => player.CurrentPausableGameplayContainer.Resume()); waitForDim(); - AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } /// - /// Check if the fade container removes user dim when suspending for + /// Check if the visual settings container removes user dim when suspending for /// [Test] public void TransitionTest() @@ -205,11 +205,12 @@ namespace osu.Game.Tests.Visual performFullSetup(); AddStep("Transition to Results", () => player.Push(new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } }))); waitForDim(); - AddAssert("Screen is undimmed and is original background", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent()); + AddAssert("Screen is undimmed, original background retained", () => + songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && songSelect.IsUserBlurDisabled()); } /// - /// Check if background gets undimmed when leaving for + /// Check if background gets undimmed and unblurred when leaving for /// [Test] public void TransitionOutTest() @@ -217,7 +218,7 @@ namespace osu.Game.Tests.Visual performFullSetup(); AddStep("Exit to song select", () => player.Exit()); waitForDim(); - AddAssert("Screen is undimmed", () => songSelect.IsBackgroundUndimmed()); + AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled()); } private void waitForDim() => AddWaitStep(5, "Wait for dim"); @@ -243,7 +244,7 @@ namespace osu.Game.Tests.Visual AddStep("Start player loader", () => { - songSelect.Push(playerLoader = new DimAccessiblePlayerLoader(player = new DimAccessiblePlayer + songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer { AllowPause = allowPause, Ready = true, @@ -261,7 +262,7 @@ namespace osu.Game.Tests.Visual { Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { new OsuModNoFail() }); songSelect.DimLevel.Value = 0.7f; - songSelect.BlurLevel.Value = 0.0f; + songSelect.BlurLevel.Value = 0.4f; }); } @@ -270,7 +271,7 @@ namespace osu.Game.Tests.Visual protected override BackgroundScreen CreateBackground() { FadeAccessibleBackground background = new FadeAccessibleBackground(Beatmap.Value); - DimEnabled.BindTo(background.EnableUserDim); + DimEnabled.BindTo(background.EnableVisualSettings); return background; } @@ -289,10 +290,12 @@ namespace osu.Game.Tests.Visual public bool IsBackgroundDimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1 - (float)DimLevel.Value); - public bool IsBackgroundUnblurred() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0); - public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White; + public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * 25); + + public bool IsUserBlurDisabled() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0); + public bool IsBackgroundInvisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 0; public bool IsBackgroundVisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 1; @@ -314,23 +317,23 @@ namespace osu.Game.Tests.Visual protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); } - private class DimAccessiblePlayer : Player + private class TestPlayer : Player { protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); - protected override UserDimContainer CreateStoryboardContainer() + protected override VisualSettingsContainer CreateStoryboardContainer() { - return new TestUserDimContainer(true) + return new TestVisualSettingsContainer(true) { RelativeSizeAxes = Axes.Both, Alpha = 1, - EnableUserDim = { Value = true } + EnableVisualSettings = { Value = true } }; } public PausableGameplayContainer CurrentPausableGameplayContainer => PausableGameplayContainer; - public UserDimContainer CurrentStoryboardContainer => StoryboardContainer; + public VisualSettingsContainer CurrentStoryboardContainer => StoryboardContainer; // Whether or not the player should be allowed to load. public bool Ready; @@ -339,9 +342,9 @@ namespace osu.Game.Tests.Visual public readonly Bindable ReplacesBackground = new Bindable(); public readonly Bindable IsPaused = new Bindable(); - public bool IsStoryboardVisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha == 1; + public bool IsStoryboardVisible() => ((TestVisualSettingsContainer)CurrentStoryboardContainer).CurrentAlpha == 1; - public bool IsStoryboardInvisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha <= 1; + public bool IsStoryboardInvisible() => ((TestVisualSettingsContainer)CurrentStoryboardContainer).CurrentAlpha <= 1; [BackgroundDependencyLoader] private void load(OsuConfigManager config) @@ -368,12 +371,12 @@ namespace osu.Game.Tests.Visual } } - private class DimAccessiblePlayerLoader : PlayerLoader + private class TestPlayerLoader : PlayerLoader { public VisualSettings VisualSettingsPos => VisualSettings; public BackgroundScreen ScreenPos => Background; - public DimAccessiblePlayerLoader(Player player) + public TestPlayerLoader(Player player) : base(() => player) { } @@ -385,14 +388,15 @@ namespace osu.Game.Tests.Visual private class FadeAccessibleBackground : BackgroundScreenBeatmap { - protected override UserDimContainer CreateFadeContainer() => fadeContainer = new TestUserDimContainer { RelativeSizeAxes = Axes.Both }; + protected override VisualSettingsContainer CreateFadeContainer() => fadeContainer = new TestVisualSettingsContainer { RelativeSizeAxes = Axes.Both }; public Color4 CurrentColour => fadeContainer.CurrentColour; + public float CurrentAlpha => fadeContainer.CurrentAlpha; - public Vector2 CurrentBlur => Background.BlurSigma; + public Vector2 CurrentBlur => fadeContainer.CurrentBlur; - private TestUserDimContainer fadeContainer; + private TestVisualSettingsContainer fadeContainer; public FadeAccessibleBackground(WorkingBeatmap beatmap) : base(beatmap) @@ -400,12 +404,14 @@ namespace osu.Game.Tests.Visual } } - private class TestUserDimContainer : UserDimContainer + private class TestVisualSettingsContainer : VisualSettingsContainer { - public Color4 CurrentColour => DimContainer.Colour; - public float CurrentAlpha => DimContainer.Alpha; + public Color4 CurrentColour => LocalContainer.Colour; + public float CurrentAlpha => LocalContainer.Alpha; - public TestUserDimContainer(bool isStoryboard = false) + public Vector2 CurrentBlur => LocalContainer.BlurSigma; + + public TestVisualSettingsContainer(bool isStoryboard = false) : base(isStoryboard) { } diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/VisualSettingsContainer.cs similarity index 53% rename from osu.Game/Graphics/Containers/UserDimContainer.cs rename to osu.Game/Graphics/Containers/VisualSettingsContainer.cs index 4c8928e122..cd81fa98ea 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/VisualSettingsContainer.cs @@ -6,83 +6,93 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; +using osuTK; using osuTK.Graphics; namespace osu.Game.Graphics.Containers { /// - /// A container that applies user-configured dim levels to its contents. + /// A container that applies user-configured visual settings to its contents. /// This container specifies behavior that applies to both Storyboards and Backgrounds. /// - public class UserDimContainer : Container + public class VisualSettingsContainer : Container { private const float background_fade_duration = 800; private Bindable dimLevel { get; set; } + private Bindable blurLevel { get; set; } + private Bindable showStoryboard { get; set; } /// /// Whether or not user-configured dim levels should be applied to the container. /// - public readonly Bindable EnableUserDim = new Bindable(); + public readonly Bindable EnableVisualSettings = new Bindable(); /// /// Whether or not the storyboard loaded should completely hide the background behind it. /// public readonly Bindable StoryboardReplacesBackground = new Bindable(); - protected Container DimContainer { get; } + protected BufferedContainer LocalContainer { get; } - protected override Container Content => DimContainer; + protected override Container Content => LocalContainer; private readonly bool isStoryboard; + private Vector2 returnBlur; + /// - /// Creates a new . + /// Creates a new . /// - /// Whether or not this instance of UserDimContainer contains a storyboard. + /// Whether or not this instance contains a storyboard. /// /// While both backgrounds and storyboards allow user dim levels to be applied, storyboards can be toggled via - /// and can cause backgrounds to become hidden via . + /// and can cause backgrounds to become hidden via . Storyboards are also currently unable to be blurred. /// /// - public UserDimContainer(bool isStoryboard = false) + public VisualSettingsContainer(bool isStoryboard = false) { this.isStoryboard = isStoryboard; - AddInternal(DimContainer = new Container { RelativeSizeAxes = Axes.Both }); + AddInternal(LocalContainer = new BufferedContainer { RelativeSizeAxes = Axes.Both }); } [BackgroundDependencyLoader] private void load(OsuConfigManager config) { dimLevel = config.GetBindable(OsuSetting.DimLevel); + blurLevel = config.GetBindable(OsuSetting.BlurLevel); showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - EnableUserDim.ValueChanged += _ => updateBackgroundDim(); - dimLevel.ValueChanged += _ => updateBackgroundDim(); - showStoryboard.ValueChanged += _ => updateBackgroundDim(); - StoryboardReplacesBackground.ValueChanged += _ => updateBackgroundDim(); + EnableVisualSettings.ValueChanged += _ => updateVisuals(); + dimLevel.ValueChanged += _ => updateVisuals(); + blurLevel.ValueChanged += _ => updateVisuals(); + showStoryboard.ValueChanged += _ => updateVisuals(); + StoryboardReplacesBackground.ValueChanged += _ => updateVisuals(); } protected override void LoadComplete() { base.LoadComplete(); - updateBackgroundDim(); + updateVisuals(); } - private void updateBackgroundDim() + private void updateVisuals() { if (isStoryboard) { - DimContainer.FadeTo(!showStoryboard.Value || dimLevel.Value == 1 ? 0 : 1, background_fade_duration, Easing.OutQuint); + LocalContainer.FadeTo(!showStoryboard.Value || dimLevel.Value == 1 ? 0 : 1, background_fade_duration, Easing.OutQuint); } else { // The background needs to be hidden in the case of it being replaced by the storyboard - DimContainer.FadeTo(showStoryboard.Value && StoryboardReplacesBackground.Value ? 0 : 1, background_fade_duration, Easing.OutQuint); + LocalContainer.FadeTo(showStoryboard.Value && StoryboardReplacesBackground.Value ? 0 : 1, background_fade_duration, Easing.OutQuint); + + // Only blur if this container contains a background + LocalContainer.BlurTo(EnableVisualSettings.Value ? new Vector2((float)blurLevel.Value * 25) : new Vector2(0), background_fade_duration, Easing.OutQuint); } - DimContainer.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)dimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint); + LocalContainer.FadeColour(EnableVisualSettings.Value ? OsuColour.Gray(1 - (float)dimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 13d1ff39ef..ee393fc13e 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; +using osuTK; namespace osu.Game.Screens.Backgrounds { @@ -18,13 +19,13 @@ namespace osu.Game.Screens.Backgrounds /// /// Whether or not user dim settings should be applied to this Background. /// - public readonly Bindable EnableUserDim = new Bindable(); + public readonly Bindable EnableVisualSettings = new Bindable(); public readonly Bindable StoryboardReplacesBackground = new Bindable(); - private readonly UserDimContainer fadeContainer; + private readonly VisualSettingsContainer fadeContainer; - protected virtual UserDimContainer CreateFadeContainer() => new UserDimContainer { RelativeSizeAxes = Axes.Both }; + protected virtual VisualSettingsContainer CreateFadeContainer() => new VisualSettingsContainer { RelativeSizeAxes = Axes.Both }; public virtual WorkingBeatmap Beatmap { @@ -62,7 +63,7 @@ namespace osu.Game.Screens.Backgrounds { Beatmap = beatmap; InternalChild = fadeContainer = CreateFadeContainer(); - fadeContainer.EnableUserDim.BindTo(EnableUserDim); + fadeContainer.EnableVisualSettings.BindTo(EnableVisualSettings); } public override bool Equals(BackgroundScreen other) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ced0a43679..98429589ca 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -71,13 +71,13 @@ namespace osu.Game.Screens.Play private FailOverlay failOverlay; private DrawableStoryboard storyboard; - protected UserDimContainer StoryboardContainer { get; private set; } + protected VisualSettingsContainer StoryboardContainer { get; private set; } - protected virtual UserDimContainer CreateStoryboardContainer() => new UserDimContainer(true) + protected virtual VisualSettingsContainer CreateStoryboardContainer() => new VisualSettingsContainer(true) { RelativeSizeAxes = Axes.Both, Alpha = 1, - EnableUserDim = { Value = true } + EnableVisualSettings = { Value = true } }; public bool LoadedBeatmapSuccessfully => RulesetContainer?.Objects.Any() == true; @@ -318,7 +318,7 @@ namespace osu.Game.Screens.Play if (enabled.NewValue) initializeStoryboard(true); }; - Background.EnableUserDim.Value = true; + Background.EnableVisualSettings.Value = true; Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); StoryboardContainer.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); @@ -365,7 +365,7 @@ namespace osu.Game.Screens.Play float fadeOutDuration = instant ? 0 : 250; this.FadeOut(fadeOutDuration); - Background.EnableUserDim.Value = false; + Background.EnableVisualSettings.Value = false; storyboardReplacesBackground.Value = false; } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 269baad955..e45421ce4a 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -26,8 +26,9 @@ namespace osu.Game.Screens.Play { public class PlayerLoader : ScreenWithBeatmapBackground { + protected static readonly Vector2 BACKGROUND_BLUR = new Vector2(15); + private readonly Func createPlayer; - private static readonly Vector2 background_blur = new Vector2(15); private Player player; @@ -118,12 +119,16 @@ namespace osu.Game.Screens.Play private void contentIn() { + Background?.BlurTo(BACKGROUND_BLUR, 800, Easing.OutQuint); + content.ScaleTo(1, 650, Easing.OutQuint); content.FadeInFromZero(400); } private void contentOut() { + Background?.BlurTo(new Vector2(0), 800, Easing.OutQuint); + content.ScaleTo(0.7f, 300, Easing.InQuint); content.FadeOut(250); } @@ -133,6 +138,7 @@ namespace osu.Game.Screens.Play base.OnEntering(last); content.ScaleTo(0.7f); + Background?.FadeColour(Color4.White, 800, Easing.OutQuint); contentIn(); @@ -158,11 +164,10 @@ namespace osu.Game.Screens.Play protected override bool OnHover(HoverEvent e) { - // restore our screen defaults if (this.IsCurrentScreen()) { - InitializeBackgroundElements(); - Background.EnableUserDim.Value = false; + Background.BlurTo(BACKGROUND_BLUR, 800, Easing.OutQuint); + Background.EnableVisualSettings.Value = false; } return base.OnHover(e); @@ -172,22 +177,16 @@ namespace osu.Game.Screens.Play { if (GetContainingInputManager()?.HoveredDrawables.Contains(VisualSettings) == true) { - // Update background elements is only being called here because blur logic still exists in Player. - // Will need to be removed when resolving https://github.com/ppy/osu/issues/4322 - UpdateBackgroundElements(); - if (this.IsCurrentScreen()) - Background.EnableUserDim.Value = true; + if (this.IsCurrentScreen() && Background != null) + { + Background.BlurTo(new Vector2(0), 800, Easing.OutQuint); + Background.EnableVisualSettings.Value = true; + } } base.OnHoverLost(e); } - protected override void InitializeBackgroundElements() - { - Background?.FadeColour(Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint); - Background?.BlurTo(background_blur, BACKGROUND_FADE_DURATION, Easing.OutQuint); - } - private void pushWhenLoaded() { if (!this.IsCurrentScreen()) return; @@ -250,7 +249,7 @@ namespace osu.Game.Screens.Play this.FadeOut(150); cancelLoad(); - Background.EnableUserDim.Value = false; + Background.EnableVisualSettings.Value = false; return base.OnExiting(next); } diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs index 328aa1d18e..2ffa1c3da9 100644 --- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs +++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs @@ -17,49 +17,12 @@ namespace osu.Game.Screens.Play protected new BackgroundScreenBeatmap Background => (BackgroundScreenBeatmap)base.Background; - protected const float BACKGROUND_FADE_DURATION = 800; - - #region User Settings - - protected Bindable BlurLevel; protected Bindable ShowStoryboard; - #endregion - [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - BlurLevel = config.GetBindable(OsuSetting.BlurLevel); ShowStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); } - - public override void OnEntering(IScreen last) - { - base.OnEntering(last); - BlurLevel.ValueChanged += _ => UpdateBackgroundElements(); - InitializeBackgroundElements(); - } - - public override void OnResuming(IScreen last) - { - base.OnResuming(last); - InitializeBackgroundElements(); - } - - /// - /// Called once on entering screen. By Default, performs a full call. - /// - protected virtual void InitializeBackgroundElements() => UpdateBackgroundElements(); - - /// - /// Called when background elements require updates, usually due to a user changing a setting. - /// - /// - protected virtual void UpdateBackgroundElements() - { - if (!this.IsCurrentScreen()) return; - - Background?.BlurTo(new Vector2((float)BlurLevel.Value * 25), BACKGROUND_FADE_DURATION, Easing.OutQuint); - } } } diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index 47ac472b9e..eb3ce66547 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -24,6 +24,8 @@ namespace osu.Game.Screens.Ranking { public abstract class Results : OsuScreen { + protected static readonly Vector2 BACKGROUND_BLUR = new Vector2(20); + private Container circleOuterBackground; private Container circleOuter; private Container circleInner; @@ -38,8 +40,6 @@ namespace osu.Game.Screens.Ranking private Container currentPage; - private static readonly Vector2 background_blur = new Vector2(20); - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); private const float overscan = 1.3f; @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Ranking public override void OnEntering(IScreen last) { base.OnEntering(last); - (Background as BackgroundScreenBeatmap)?.BlurTo(background_blur, 2500, Easing.OutQuint); + (Background as BackgroundScreenBeatmap)?.BlurTo(BACKGROUND_BLUR, 2500, Easing.OutQuint); Background.ScaleTo(1.1f, transition_time, Easing.OutQuint); allCircles.ForEach(c => diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 5d4ead69d6..d10e6994d5 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -37,8 +37,8 @@ namespace osu.Game.Screens.Select { public abstract class SongSelect : OsuScreen { + protected static readonly Vector2 BACKGROUND_BLUR = new Vector2(20); private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245); - private static readonly Vector2 background_blur = new Vector2(20); private const float left_area_padding = 20; public readonly FilterControl FilterControl; @@ -556,7 +556,7 @@ namespace osu.Game.Screens.Select if (Background is BackgroundScreenBeatmap backgroundModeBeatmap) { backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.BlurTo(background_blur, 750, Easing.OutQuint); + backgroundModeBeatmap.BlurTo(BACKGROUND_BLUR, 750, Easing.OutQuint); backgroundModeBeatmap.FadeColour(Color4.White, 250); } From 6aa3dc9f554e0d41dd42fc01c4115597574b05b4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com> Date: Wed, 13 Mar 2019 18:56:48 +0900 Subject: [PATCH 081/623] Use non-real domain Co-Authored-By: peppy --- osu.Game/Online/API/DummyAPIAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index af2fe51b38..be3859b1eb 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -18,7 +18,7 @@ namespace osu.Game.Online.API public string ProvidedUsername => LocalUser.Value.Username; - public string Endpoint => "https://test.com"; + public string Endpoint => "http://localhost"; public APIState State => LocalUser.Value.Id == 1 ? APIState.Offline : APIState.Online; From 7d637691d77ccd7d490beff037ee1166daf022f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Mar 2019 19:01:42 +0900 Subject: [PATCH 082/623] Use non-guest user ID for non-guest user --- osu.Game/Online/API/DummyAPIAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index be3859b1eb..0cb49951f7 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -41,7 +41,7 @@ namespace osu.Game.Online.API LocalUser.Value = new User { Username = @"Dummy", - Id = 1, + Id = 1001, }; } From dd6fbccb5614a62ae943ab0ef53584729221038b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Mar 2019 19:57:45 +0900 Subject: [PATCH 083/623] Slight refactoring of order for readability --- osu.Game/Screens/Select/BeatmapDetails.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 46979d0000..2784e4fc3b 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using System.Linq; +using JetBrains.Annotations; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Framework.Threading; @@ -180,15 +181,15 @@ namespace osu.Game.Screens.Select source.Text = Beatmap?.Metadata?.Source; tags.Text = Beatmap?.Metadata?.Tags; - if (Beatmap == null) + // metrics may have been previously fetched + if (Beatmap?.Metrics != null) { - updateMetrics(null); - ratingsContainer.FadeOut(transition_duration); - failRetryContainer.FadeOut(transition_duration); + updateMetrics(Beatmap.Metrics); return; } - if (Beatmap.Metrics == null && Beatmap.OnlineBeatmapID != null) + // metrics may not be fetched but can be + if (Beatmap?.OnlineBeatmapID != null) { var requestedBeatmap = Beatmap; var lookup = new GetBeatmapDetailsRequest(requestedBeatmap); @@ -204,14 +205,13 @@ namespace osu.Game.Screens.Select lookup.Failure += e => Schedule(() => updateMetrics(null)); api.Queue(lookup); loading.Show(); + return; } - else - { - updateMetrics(Beatmap.Metrics); - } + + updateMetrics(null); } - private void updateMetrics(BeatmapMetrics metrics) + private void updateMetrics([CanBeNull] BeatmapMetrics metrics) { var hasRatings = metrics?.Ratings?.Any() ?? false; var hasRetriesFails = (metrics?.Retries?.Any() ?? false) && (metrics.Fails?.Any() ?? false); From 843ede2d52351c8a36bc5c369eccc7fdc60cbc80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Mar 2019 21:58:50 +0900 Subject: [PATCH 084/623] Update framework --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2e945c212d..e2c96effb6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index b25e2a8bb2..288630d1ec 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 69eb4ef9830f41c425c08ac307f5083cd78934b5 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 14 Mar 2019 13:23:31 +0900 Subject: [PATCH 085/623] Change updateMetrics parameter to be optional --- osu.Game/Screens/Select/BeatmapDetails.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 2784e4fc3b..e8ef94c101 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using System.Linq; -using JetBrains.Annotations; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Framework.Threading; @@ -202,16 +201,16 @@ namespace osu.Game.Screens.Select requestedBeatmap.Metrics = res; Schedule(() => updateMetrics(res)); }; - lookup.Failure += e => Schedule(() => updateMetrics(null)); + lookup.Failure += e => Schedule(() => updateMetrics()); api.Queue(lookup); loading.Show(); return; } - updateMetrics(null); + updateMetrics(); } - private void updateMetrics([CanBeNull] BeatmapMetrics metrics) + private void updateMetrics(BeatmapMetrics metrics = null) { var hasRatings = metrics?.Ratings?.Any() ?? false; var hasRetriesFails = (metrics?.Retries?.Any() ?? false) && (metrics.Fails?.Any() ?? false); From 8714902349998aa4ce4985133abedd51a98caa8a Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 14 Mar 2019 14:02:46 +0900 Subject: [PATCH 086/623] Handle all blurring directly from background --- .../Containers/VisualSettingsContainer.cs | 41 +++++++++++++------ .../Backgrounds/BackgroundScreenBeatmap.cs | 5 ++- osu.Game/Screens/Play/PlayerLoader.cs | 14 ++++--- osu.Game/Screens/Ranking/Results.cs | 4 +- osu.Game/Screens/Select/SongSelect.cs | 5 ++- 5 files changed, 46 insertions(+), 23 deletions(-) diff --git a/osu.Game/Graphics/Containers/VisualSettingsContainer.cs b/osu.Game/Graphics/Containers/VisualSettingsContainer.cs index cd81fa98ea..3d9e340d5f 100644 --- a/osu.Game/Graphics/Containers/VisualSettingsContainer.cs +++ b/osu.Game/Graphics/Containers/VisualSettingsContainer.cs @@ -1,11 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; +using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Screens.Backgrounds; using osuTK; using osuTK.Graphics; @@ -19,6 +24,8 @@ namespace osu.Game.Graphics.Containers { private const float background_fade_duration = 800; + public Action ContentLoadComplete = () => { }; + private Bindable dimLevel { get; set; } private Bindable blurLevel { get; set; } @@ -35,13 +42,13 @@ namespace osu.Game.Graphics.Containers /// public readonly Bindable StoryboardReplacesBackground = new Bindable(); - protected BufferedContainer LocalContainer { get; } + protected Container LocalContainer { get; } protected override Container Content => LocalContainer; private readonly bool isStoryboard; - private Vector2 returnBlur; + public Bindable AddedBlur = new Bindable(); /// /// Creates a new . @@ -55,7 +62,7 @@ namespace osu.Game.Graphics.Containers public VisualSettingsContainer(bool isStoryboard = false) { this.isStoryboard = isStoryboard; - AddInternal(LocalContainer = new BufferedContainer { RelativeSizeAxes = Axes.Both }); + AddInternal(LocalContainer = new Container { RelativeSizeAxes = Axes.Both }); } [BackgroundDependencyLoader] @@ -64,20 +71,21 @@ namespace osu.Game.Graphics.Containers dimLevel = config.GetBindable(OsuSetting.DimLevel); blurLevel = config.GetBindable(OsuSetting.BlurLevel); showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - EnableVisualSettings.ValueChanged += _ => updateVisuals(); - dimLevel.ValueChanged += _ => updateVisuals(); - blurLevel.ValueChanged += _ => updateVisuals(); - showStoryboard.ValueChanged += _ => updateVisuals(); - StoryboardReplacesBackground.ValueChanged += _ => updateVisuals(); + EnableVisualSettings.ValueChanged += _ => UpdateVisuals(); + dimLevel.ValueChanged += _ => UpdateVisuals(); + blurLevel.ValueChanged += _ => UpdateVisuals(); + showStoryboard.ValueChanged += _ => UpdateVisuals(); + StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals(); + AddedBlur.ValueChanged += _ => UpdateVisuals(); } protected override void LoadComplete() { base.LoadComplete(); - updateVisuals(); + UpdateVisuals(); } - private void updateVisuals() + public void UpdateVisuals() { if (isStoryboard) { @@ -88,8 +96,17 @@ namespace osu.Game.Graphics.Containers // The background needs to be hidden in the case of it being replaced by the storyboard LocalContainer.FadeTo(showStoryboard.Value && StoryboardReplacesBackground.Value ? 0 : 1, background_fade_duration, Easing.OutQuint); - // Only blur if this container contains a background - LocalContainer.BlurTo(EnableVisualSettings.Value ? new Vector2((float)blurLevel.Value * 25) : new Vector2(0), background_fade_duration, Easing.OutQuint); + foreach (Drawable c in LocalContainer) + { + // Only blur if this container contains a background + // We can't blur the container like we did with the dim because buffered containers add considerable draw overhead. + // As a result, this blurs the background directly. + ((Background)c)?.BlurTo(EnableVisualSettings.Value + ? new Vector2(AddedBlur.Value + (float)blurLevel.Value * 25) + : new Vector2(AddedBlur.Value), background_fade_duration, Easing.OutQuint); + + Logger.Log("Enable visual settings: " + EnableVisualSettings.Value + " Added blur is: " + AddedBlur.Value); + } } LocalContainer.FadeColour(EnableVisualSettings.Value ? OsuColour.Gray(1 - (float)dimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index ee393fc13e..3670ac7663 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; -using osuTK; namespace osu.Game.Screens.Backgrounds { @@ -23,6 +22,8 @@ namespace osu.Game.Screens.Backgrounds public readonly Bindable StoryboardReplacesBackground = new Bindable(); + public readonly Bindable AddedBlur = new Bindable(); + private readonly VisualSettingsContainer fadeContainer; protected virtual VisualSettingsContainer CreateFadeContainer() => new VisualSettingsContainer { RelativeSizeAxes = Axes.Both }; @@ -52,6 +53,7 @@ namespace osu.Game.Screens.Backgrounds b.Depth = newDepth; fadeContainer.Add(Background = b); + fadeContainer.UpdateVisuals(); Background.BlurSigma = BlurTarget; StoryboardReplacesBackground.BindTo(fadeContainer.StoryboardReplacesBackground); })); @@ -64,6 +66,7 @@ namespace osu.Game.Screens.Backgrounds Beatmap = beatmap; InternalChild = fadeContainer = CreateFadeContainer(); fadeContainer.EnableVisualSettings.BindTo(EnableVisualSettings); + fadeContainer.AddedBlur.BindTo(AddedBlur); } public override bool Equals(BackgroundScreen other) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index e45421ce4a..6eee681e67 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Play { public class PlayerLoader : ScreenWithBeatmapBackground { - protected static readonly Vector2 BACKGROUND_BLUR = new Vector2(15); + private const float background_blur = 15; private readonly Func createPlayer; @@ -119,7 +119,7 @@ namespace osu.Game.Screens.Play private void contentIn() { - Background?.BlurTo(BACKGROUND_BLUR, 800, Easing.OutQuint); + Background.AddedBlur.Value = background_blur; content.ScaleTo(1, 650, Easing.OutQuint); content.FadeInFromZero(400); @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Play private void contentOut() { - Background?.BlurTo(new Vector2(0), 800, Easing.OutQuint); + Background.AddedBlur.Value = 0; content.ScaleTo(0.7f, 300, Easing.InQuint); content.FadeOut(250); @@ -166,7 +166,7 @@ namespace osu.Game.Screens.Play { if (this.IsCurrentScreen()) { - Background.BlurTo(BACKGROUND_BLUR, 800, Easing.OutQuint); + Background.AddedBlur.Value = background_blur; Background.EnableVisualSettings.Value = false; } @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Play { if (this.IsCurrentScreen() && Background != null) { - Background.BlurTo(new Vector2(0), 800, Easing.OutQuint); + Background.AddedBlur.Value = 0; Background.EnableVisualSettings.Value = true; } } @@ -239,6 +239,8 @@ namespace osu.Game.Screens.Play public override void OnSuspending(IScreen next) { + Background.EnableVisualSettings.Value = true; + base.OnSuspending(next); cancelLoad(); } @@ -249,7 +251,7 @@ namespace osu.Game.Screens.Play this.FadeOut(150); cancelLoad(); - Background.EnableVisualSettings.Value = false; + Background.EnableVisualSettings.Value = true; return base.OnExiting(next); } diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index eb3ce66547..a30534a5d3 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Ranking { public abstract class Results : OsuScreen { - protected static readonly Vector2 BACKGROUND_BLUR = new Vector2(20); + private const float background_blur = 20; private Container circleOuterBackground; private Container circleOuter; @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Ranking public override void OnEntering(IScreen last) { base.OnEntering(last); - (Background as BackgroundScreenBeatmap)?.BlurTo(BACKGROUND_BLUR, 2500, Easing.OutQuint); + ((BackgroundScreenBeatmap)Background).AddedBlur.Value = background_blur; Background.ScaleTo(1.1f, transition_time, Easing.OutQuint); allCircles.ForEach(c => diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index d10e6994d5..22e4b2ffa2 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -37,8 +37,8 @@ namespace osu.Game.Screens.Select { public abstract class SongSelect : OsuScreen { - protected static readonly Vector2 BACKGROUND_BLUR = new Vector2(20); private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245); + private const float background_blur = 20; private const float left_area_padding = 20; public readonly FilterControl FilterControl; @@ -556,8 +556,9 @@ namespace osu.Game.Screens.Select if (Background is BackgroundScreenBeatmap backgroundModeBeatmap) { backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.BlurTo(BACKGROUND_BLUR, 750, Easing.OutQuint); + backgroundModeBeatmap.AddedBlur.Value = background_blur; backgroundModeBeatmap.FadeColour(Color4.White, 250); + Logger.Log("blur updated!"); } beatmapInfoWedge.Beatmap = beatmap; From 71d79f0a3914a2f858514e9b173e4f4db93a36aa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 Mar 2019 15:20:09 +0900 Subject: [PATCH 087/623] Fix ticks not being generated at the minimum tick distance --- osu.Game/Rulesets/Objects/SliderEventGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index a0f9d0a481..7cda7485f9 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects for (var d = tickDistance; d <= length; d += tickDistance) { - if (d > length - minDistanceFromEnd) + if (d >= length - minDistanceFromEnd) break; var pathProgress = d / length; From 8cdfb1fd610bed0896cce606178a0854887370fe Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 14 Mar 2019 16:09:17 +0900 Subject: [PATCH 088/623] Remove BlurrableBackgroundScreen, rework tests --- .../Visual/TestCaseBackgroundScreenBeatmap.cs | 21 ++++++++++------- .../Containers/VisualSettingsContainer.cs | 16 ++++++++----- osu.Game/Screens/BackgroundScreen.cs | 3 +++ .../Backgrounds/BackgroundScreenBeatmap.cs | 4 ++-- .../Backgrounds/BackgroundScreenCustom.cs | 2 +- .../Backgrounds/BackgroundScreenDefault.cs | 2 +- osu.Game/Screens/BlurrableBackgroundScreen.cs | 23 ------------------- osu.Game/Screens/Play/PlayerLoader.cs | 10 ++++---- osu.Game/Screens/Ranking/Results.cs | 4 ++-- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- 10 files changed, 40 insertions(+), 49 deletions(-) delete mode 100644 osu.Game/Screens/BlurrableBackgroundScreen.cs diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index 03078abd3a..efaa752283 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -109,7 +109,7 @@ namespace osu.Game.Tests.Visual AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); waitForDim(); - AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled()); + AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect()); } /// @@ -203,10 +203,11 @@ namespace osu.Game.Tests.Visual public void TransitionTest() { performFullSetup(); - AddStep("Transition to Results", () => player.Push(new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } }))); + var results = new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } }); + AddStep("Transition to Results", () => player.Push(results)); waitForDim(); - AddAssert("Screen is undimmed, original background retained", () => - songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && songSelect.IsUserBlurDisabled()); + AddAssert("Screen is undimmed, original background retained", () => + songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect()); } /// @@ -218,7 +219,7 @@ namespace osu.Game.Tests.Visual performFullSetup(); AddStep("Exit to song select", () => player.Exit()); waitForDim(); - AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled()); + AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect()); } private void waitForDim() => AddWaitStep(5, "Wait for dim"); @@ -300,6 +301,8 @@ namespace osu.Game.Tests.Visual public bool IsBackgroundVisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 1; + public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR); + /// /// Make sure every time a screen gets pushed, the background doesn't get replaced /// @@ -315,6 +318,8 @@ namespace osu.Game.Tests.Visual } protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); + + public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR); } private class TestPlayer : Player @@ -383,6 +388,8 @@ namespace osu.Game.Tests.Visual public void TriggerOnHover() => OnHover(new HoverEvent(new InputState())); + public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR); + protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); } @@ -394,7 +401,7 @@ namespace osu.Game.Tests.Visual public float CurrentAlpha => fadeContainer.CurrentAlpha; - public Vector2 CurrentBlur => fadeContainer.CurrentBlur; + public Vector2 CurrentBlur => Background.BlurSigma; private TestVisualSettingsContainer fadeContainer; @@ -409,8 +416,6 @@ namespace osu.Game.Tests.Visual public Color4 CurrentColour => LocalContainer.Colour; public float CurrentAlpha => LocalContainer.Alpha; - public Vector2 CurrentBlur => LocalContainer.BlurSigma; - public TestVisualSettingsContainer(bool isStoryboard = false) : base(isStoryboard) { diff --git a/osu.Game/Graphics/Containers/VisualSettingsContainer.cs b/osu.Game/Graphics/Containers/VisualSettingsContainer.cs index 3d9e340d5f..c45b130044 100644 --- a/osu.Game/Graphics/Containers/VisualSettingsContainer.cs +++ b/osu.Game/Graphics/Containers/VisualSettingsContainer.cs @@ -24,8 +24,6 @@ namespace osu.Game.Graphics.Containers { private const float background_fade_duration = 800; - public Action ContentLoadComplete = () => { }; - private Bindable dimLevel { get; set; } private Bindable blurLevel { get; set; } @@ -50,6 +48,10 @@ namespace osu.Game.Graphics.Containers public Bindable AddedBlur = new Bindable(); + public Vector2 BlurTarget => EnableVisualSettings.Value + ? new Vector2(AddedBlur.Value + (float)blurLevel.Value * 25) + : new Vector2(AddedBlur.Value); + /// /// Creates a new . /// @@ -71,7 +73,11 @@ namespace osu.Game.Graphics.Containers dimLevel = config.GetBindable(OsuSetting.DimLevel); blurLevel = config.GetBindable(OsuSetting.BlurLevel); showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - EnableVisualSettings.ValueChanged += _ => UpdateVisuals(); + EnableVisualSettings.ValueChanged += _ => + { + Logger.Log("Oh fuck"); + UpdateVisuals(); + }; dimLevel.ValueChanged += _ => UpdateVisuals(); blurLevel.ValueChanged += _ => UpdateVisuals(); showStoryboard.ValueChanged += _ => UpdateVisuals(); @@ -101,9 +107,7 @@ namespace osu.Game.Graphics.Containers // Only blur if this container contains a background // We can't blur the container like we did with the dim because buffered containers add considerable draw overhead. // As a result, this blurs the background directly. - ((Background)c)?.BlurTo(EnableVisualSettings.Value - ? new Vector2(AddedBlur.Value + (float)blurLevel.Value * 25) - : new Vector2(AddedBlur.Value), background_fade_duration, Easing.OutQuint); + ((Background)c)?.BlurTo(BlurTarget, background_fade_duration, Easing.OutQuint); Logger.Log("Enable visual settings: " + EnableVisualSettings.Value + " Added blur is: " + AddedBlur.Value); } diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index bbe162cf7c..7eca192a6b 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -5,12 +5,15 @@ using System; using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Game.Graphics.Backgrounds; using osuTK; namespace osu.Game.Screens { public abstract class BackgroundScreen : Screen, IEquatable { + protected Background Background; + protected BackgroundScreen() { Anchor = Anchor.Centre; diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 3670ac7663..3e3315d03a 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -11,7 +11,7 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Backgrounds { - public class BackgroundScreenBeatmap : BlurrableBackgroundScreen + public class BackgroundScreenBeatmap : BackgroundScreen { private WorkingBeatmap beatmap; @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Backgrounds b.Depth = newDepth; fadeContainer.Add(Background = b); fadeContainer.UpdateVisuals(); - Background.BlurSigma = BlurTarget; + Background.BlurSigma = fadeContainer.BlurTarget; StoryboardReplacesBackground.BindTo(fadeContainer.StoryboardReplacesBackground); })); }); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs index 0cb41bc562..538f347737 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Backgrounds public BackgroundScreenCustom(string textureName) { this.textureName = textureName; - AddInternal(new Background(textureName)); + AddInternal(Background = new Background(textureName)); } public override bool Equals(BackgroundScreen other) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 87a6b5d591..73590b3318 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -13,7 +13,7 @@ using osu.Game.Users; namespace osu.Game.Screens.Backgrounds { - public class BackgroundScreenDefault : BlurrableBackgroundScreen + public class BackgroundScreenDefault : BackgroundScreen { private int currentDisplay; private const int background_count = 5; diff --git a/osu.Game/Screens/BlurrableBackgroundScreen.cs b/osu.Game/Screens/BlurrableBackgroundScreen.cs deleted file mode 100644 index d19e699acb..0000000000 --- a/osu.Game/Screens/BlurrableBackgroundScreen.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) ppy Pty Ltd . 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.Transforms; -using osu.Game.Graphics.Backgrounds; -using osuTK; - -namespace osu.Game.Screens -{ - public abstract class BlurrableBackgroundScreen : BackgroundScreen - { - protected Background Background; - - protected Vector2 BlurTarget; - - public TransformSequence BlurTo(Vector2 sigma, double duration, Easing easing = Easing.None) - { - BlurTarget = sigma; - return Background?.BlurTo(BlurTarget, duration, easing); - } - } -} diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 6eee681e67..4200a7390d 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Play { public class PlayerLoader : ScreenWithBeatmapBackground { - private const float background_blur = 15; + protected const float BACKGROUND_BLUR = 15; private readonly Func createPlayer; @@ -119,7 +119,8 @@ namespace osu.Game.Screens.Play private void contentIn() { - Background.AddedBlur.Value = background_blur; + Background.AddedBlur.Value = BACKGROUND_BLUR; + Background.EnableVisualSettings.Value = false; content.ScaleTo(1, 650, Easing.OutQuint); content.FadeInFromZero(400); @@ -128,6 +129,7 @@ namespace osu.Game.Screens.Play private void contentOut() { Background.AddedBlur.Value = 0; + Background.EnableVisualSettings.Value = true; content.ScaleTo(0.7f, 300, Easing.InQuint); content.FadeOut(250); @@ -166,7 +168,7 @@ namespace osu.Game.Screens.Play { if (this.IsCurrentScreen()) { - Background.AddedBlur.Value = background_blur; + Background.AddedBlur.Value = BACKGROUND_BLUR; Background.EnableVisualSettings.Value = false; } @@ -251,7 +253,7 @@ namespace osu.Game.Screens.Play this.FadeOut(150); cancelLoad(); - Background.EnableVisualSettings.Value = true; + Background.EnableVisualSettings.Value = false; return base.OnExiting(next); } diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index a30534a5d3..bd8daa6b03 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Ranking { public abstract class Results : OsuScreen { - private const float background_blur = 20; + protected const float BACKGROUND_BLUR = 20; private Container circleOuterBackground; private Container circleOuter; @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Ranking public override void OnEntering(IScreen last) { base.OnEntering(last); - ((BackgroundScreenBeatmap)Background).AddedBlur.Value = background_blur; + ((BackgroundScreenBeatmap)Background).AddedBlur.Value = BACKGROUND_BLUR; Background.ScaleTo(1.1f, transition_time, Easing.OutQuint); allCircles.ForEach(c => diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 22e4b2ffa2..6551c70de3 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Select public abstract class SongSelect : OsuScreen { private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245); - private const float background_blur = 20; + protected const float BACKGROUND_BLUR = 20; private const float left_area_padding = 20; public readonly FilterControl FilterControl; @@ -556,7 +556,7 @@ namespace osu.Game.Screens.Select if (Background is BackgroundScreenBeatmap backgroundModeBeatmap) { backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.AddedBlur.Value = background_blur; + backgroundModeBeatmap.AddedBlur.Value = BACKGROUND_BLUR; backgroundModeBeatmap.FadeColour(Color4.White, 250); Logger.Log("blur updated!"); } From 8fc90bb9a03247b3c4ca77a6bde37639fefa716a Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 14 Mar 2019 16:26:34 +0900 Subject: [PATCH 089/623] Remove unused usings --- osu.Game/Graphics/Containers/VisualSettingsContainer.cs | 3 --- osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs | 3 --- 2 files changed, 6 deletions(-) diff --git a/osu.Game/Graphics/Containers/VisualSettingsContainer.cs b/osu.Game/Graphics/Containers/VisualSettingsContainer.cs index c45b130044..ebbd975231 100644 --- a/osu.Game/Graphics/Containers/VisualSettingsContainer.cs +++ b/osu.Game/Graphics/Containers/VisualSettingsContainer.cs @@ -1,16 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; -using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Backgrounds; -using osu.Game.Screens.Backgrounds; using osuTK; using osuTK.Graphics; diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs index 2ffa1c3da9..174e39f3cb 100644 --- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs +++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs @@ -3,11 +3,8 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Screens; using osu.Game.Configuration; using osu.Game.Screens.Backgrounds; -using osuTK; namespace osu.Game.Screens.Play { From 3208f1dde8b2ac139edbe6a350d70343e8d1bfcf Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 14 Mar 2019 16:40:25 +0900 Subject: [PATCH 090/623] Fix potential transitiontest failure on local unit tests --- osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index efaa752283..2eb479b505 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -205,6 +205,7 @@ namespace osu.Game.Tests.Visual performFullSetup(); var results = new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } }); AddStep("Transition to Results", () => player.Push(results)); + AddUntilStep(results.IsCurrentScreen, "Wait for results is current"); waitForDim(); AddAssert("Screen is undimmed, original background retained", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect()); From e31680e3730a04a27dacd019476a41b002e12bac Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 14 Mar 2019 16:05:54 -0700 Subject: [PATCH 091/623] Address styling issues on CodeFactor --- osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs | 1 - osu.iOS/Application.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs index c38cd19b42..026442f328 100644 --- a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs @@ -13,7 +13,6 @@ namespace osu.Game.Migrations protected override void Down(MigrationBuilder migrationBuilder) { - } } } diff --git a/osu.iOS/Application.cs b/osu.iOS/Application.cs index 8a5cfcdbe8..cb75e5c159 100644 --- a/osu.iOS/Application.cs +++ b/osu.iOS/Application.cs @@ -13,4 +13,3 @@ namespace osu.iOS } } } - From 8c6caf0b18383b015a79f14976b668270c00214e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Mar 2019 01:03:13 +0900 Subject: [PATCH 092/623] Update framework --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e2c96effb6..c56d50ae15 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 288630d1ec..fedc20397d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 771d676ba1a8baed1661985929598598893b5a33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Mar 2019 13:47:11 +0900 Subject: [PATCH 093/623] Split RulesetInputManager out to FrameStabilityContainer --- .../Rulesets/UI/FrameStabilityContainer.cs | 161 ++++++++++++++++++ osu.Game/Rulesets/UI/RulesetContainer.cs | 14 +- osu.Game/Rulesets/UI/RulesetInputManager.cs | 140 +-------------- 3 files changed, 178 insertions(+), 137 deletions(-) create mode 100644 osu.Game/Rulesets/UI/FrameStabilityContainer.cs diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs new file mode 100644 index 0000000000..921ed46d0e --- /dev/null +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -0,0 +1,161 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Input.Handlers; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.UI +{ + /// + /// A container which consumes a parent gameplay clock and standardises frame counts for children. + /// Will ensure a minimum of 40 frames per clock second is maintained, regardless of any system lag or seeks. + /// + public class FrameStabilityContainer : Container, IHasReplayHandler + { + public FrameStabilityContainer() + { + RelativeSizeAxes = Axes.Both; + gameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); + } + + #region Clock control + + private readonly ManualClock manualClock; + + private readonly FramedClock framedClock; + + [Cached] + private GameplayClock gameplayClock; + + private IFrameBasedClock parentGameplayClock; + + [BackgroundDependencyLoader(true)] + private void load(GameplayClock clock) + { + if (clock != null) + parentGameplayClock = clock; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + setClock(); + } + + /// + /// Whether we are running up-to-date with our parent clock. + /// If not, we will need to keep processing children until we catch up. + /// + private bool requireMoreUpdateLoops; + + /// + /// Whether we are in a valid state (ie. should we keep processing children frames). + /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. + /// + private bool validState; + + protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState; + + private bool isAttached => ReplayInputHandler != null; + + private const int max_catch_up_updates_per_frame = 50; + + private const double sixty_frame_time = 1000.0 / 60; + + public override bool UpdateSubTree() + { + requireMoreUpdateLoops = true; + validState = true; + + int loops = 0; + + while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame) + { + updateClock(); + + if (validState) + { + base.UpdateSubTree(); + UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat); + } + } + + return true; + } + + private void updateClock() + { + if (parentGameplayClock == null) + setClock(); // LoadComplete may not be run yet, but we still want the clock. + + validState = true; + + manualClock.Rate = parentGameplayClock.Rate; + manualClock.IsRunning = parentGameplayClock.IsRunning; + + var newProposedTime = parentGameplayClock.CurrentTime; + + try + { + if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) + { + newProposedTime = manualClock.Rate > 0 + ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) + : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); + } + + if (!isAttached) + { + manualClock.CurrentTime = newProposedTime; + } + else + { + double? newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime); + + if (newTime == null) + { + // we shouldn't execute for this time value. probably waiting on more replay data. + validState = false; + + requireMoreUpdateLoops = true; + manualClock.CurrentTime = newProposedTime; + return; + } + + manualClock.CurrentTime = newTime.Value; + } + + requireMoreUpdateLoops = manualClock.CurrentTime != parentGameplayClock.CurrentTime; + } + finally + { + // The manual clock time has changed in the above code. The framed clock now needs to be updated + // to ensure that the its time is valid for our children before input is processed + framedClock.ProcessFrame(); + } + } + + private void setClock() + { + // in case a parent gameplay clock isn't available, just use the parent clock. + if (parentGameplayClock == null) + parentGameplayClock = Clock; + + Clock = gameplayClock; + ProcessCustomClock = false; + } + + #endregion + + #region IHasReplayHandler + + public ReplayInputHandler ReplayInputHandler { get; set; } + + #endregion + } +} diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index ed5f23dc7f..d8813631dc 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -132,6 +132,8 @@ namespace osu.Game.Rulesets.UI protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; + protected FrameStabilityContainer FrameStabilityContainer; + public Score ReplayScore { get; private set; } /// @@ -149,7 +151,11 @@ namespace osu.Game.Rulesets.UI throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); ReplayScore = replayScore; - ReplayInputManager.ReplayInputHandler = replayScore != null ? CreateReplayInputHandler(replayScore.Replay) : null; + + var handler = replayScore != null ? CreateReplayInputHandler(replayScore.Replay) : null; + + ReplayInputManager.ReplayInputHandler = handler; + FrameStabilityContainer.ReplayInputHandler = handler; HasReplayLoaded.Value = ReplayInputManager.ReplayInputHandler != null; } @@ -243,7 +249,6 @@ namespace osu.Game.Rulesets.UI Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo); KeyBindingInputManager = CreateInputManager(); - KeyBindingInputManager.RelativeSizeAxes = Axes.Both; applyBeatmapMods(Mods); } @@ -262,7 +267,10 @@ namespace osu.Game.Rulesets.UI InternalChildren = new Drawable[] { - KeyBindingInputManager, + FrameStabilityContainer = new FrameStabilityContainer + { + Child = KeyBindingInputManager, + }, Overlays = new Container { RelativeSizeAxes = Axes.Both } }; diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 87220a37e8..e303166774 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -12,7 +11,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges.Events; using osu.Framework.Input.States; -using osu.Framework.Timing; using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; @@ -41,7 +39,12 @@ namespace osu.Game.Rulesets.UI protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) { InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique); - gameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); + } + + [BackgroundDependencyLoader(true)] + private void load(OsuConfigManager config) + { + mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); } #region Action mapping (for replays) @@ -85,137 +88,6 @@ namespace osu.Game.Rulesets.UI #endregion - #region Clock control - - private readonly ManualClock manualClock; - - private readonly FramedClock framedClock; - - [Cached] - private GameplayClock gameplayClock; - - private IFrameBasedClock parentGameplayClock; - - [BackgroundDependencyLoader(true)] - private void load(OsuConfigManager config, GameplayClock clock) - { - mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); - - if (clock != null) - parentGameplayClock = clock; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - setClock(); - } - - /// - /// Whether we are running up-to-date with our parent clock. - /// If not, we will need to keep processing children until we catch up. - /// - private bool requireMoreUpdateLoops; - - /// - /// Whether we are in a valid state (ie. should we keep processing children frames). - /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. - /// - private bool validState; - - protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState; - - private bool isAttached => replayInputHandler != null && !UseParentInput; - - private const int max_catch_up_updates_per_frame = 50; - - private const double sixty_frame_time = 1000.0 / 60; - - public override bool UpdateSubTree() - { - requireMoreUpdateLoops = true; - validState = true; - - int loops = 0; - - while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame) - { - updateClock(); - - if (validState) - { - base.UpdateSubTree(); - UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat); - } - } - - return true; - } - - private void updateClock() - { - if (parentGameplayClock == null) - setClock(); // LoadComplete may not be run yet, but we still want the clock. - - validState = true; - - manualClock.Rate = parentGameplayClock.Rate; - manualClock.IsRunning = parentGameplayClock.IsRunning; - - var newProposedTime = parentGameplayClock.CurrentTime; - - try - { - if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) - { - newProposedTime = manualClock.Rate > 0 - ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) - : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); - } - - if (!isAttached) - { - manualClock.CurrentTime = newProposedTime; - } - else - { - double? newTime = replayInputHandler.SetFrameFromTime(newProposedTime); - - if (newTime == null) - { - // we shouldn't execute for this time value. probably waiting on more replay data. - validState = false; - - requireMoreUpdateLoops = true; - manualClock.CurrentTime = newProposedTime; - return; - } - - manualClock.CurrentTime = newTime.Value; - } - - requireMoreUpdateLoops = manualClock.CurrentTime != parentGameplayClock.CurrentTime; - } - finally - { - // The manual clock time has changed in the above code. The framed clock now needs to be updated - // to ensure that the its time is valid for our children before input is processed - framedClock.ProcessFrame(); - } - } - - private void setClock() - { - // in case a parent gameplay clock isn't available, just use the parent clock. - if (parentGameplayClock == null) - parentGameplayClock = Clock; - - Clock = gameplayClock; - ProcessCustomClock = false; - } - - #endregion - #region Setting application (disables etc.) private Bindable mouseDisabled; From c496f6e56bc6440ba91f12a52971f4e8f4b4c65e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 17 Mar 2019 13:43:23 +0900 Subject: [PATCH 094/623] Fix usages of OnLoadComplete --- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 2 +- .../UpdateableBeatmapBackgroundSprite.cs | 2 +- .../Drawables/UpdateableBeatmapSetCover.cs | 7 +++++-- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 7 +++++-- .../Overlays/Chat/Tabs/PrivateChannelTabItem.cs | 7 +++++-- osu.Game/Overlays/MedalSplash/DrawableMedal.cs | 2 +- osu.Game/Overlays/Profile/ProfileHeader.cs | 7 +++++-- .../Multi/Match/Components/Participants.cs | 16 +++++++++++----- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 11 ++++++++--- osu.Game/Users/UpdateableAvatar.cs | 2 +- osu.Game/Users/UserPanel.cs | 7 +++++-- 12 files changed, 49 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index d0f50c6af2..c6dd0a86a0 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Catch.UI if (lastPlateableFruit.IsLoaded) action(); else - lastPlateableFruit.OnLoadComplete = _ => action(); + lastPlateableFruit.OnLoadComplete += _ => action(); } if (result.IsHit && fruit.CanBePlated) diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index ec75c1a1fb..ce7811fc0d 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -51,7 +51,7 @@ namespace osu.Game.Beatmaps.Drawables drawable.Anchor = Anchor.Centre; drawable.Origin = Anchor.Centre; drawable.FillMode = FillMode.Fill; - drawable.OnLoadComplete = d => d.FadeInFromZero(400); + drawable.OnLoadComplete += d => d.FadeInFromZero(400); return drawable; } diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs index 367b63d6d1..c7c4c1fb1e 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs @@ -67,16 +67,19 @@ namespace osu.Game.Beatmaps.Drawables if (beatmapSet != null) { + BeatmapSetCover cover; + Add(displayedCover = new DelayedLoadWrapper( - new BeatmapSetCover(beatmapSet, coverType) + cover = new BeatmapSetCover(beatmapSet, coverType) { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out), }) ); + + cover.OnLoadComplete += d => d.FadeInFromZero(400, Easing.Out); } } } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 34981bf849..c5602fc4ad 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -64,6 +64,8 @@ namespace osu.Game.Online.Leaderboards statisticsLabels = GetStatistics(score).Select(s => new ScoreComponentLabel(s)).ToList(); + Avatar innerAvatar; + Children = new Drawable[] { new Container @@ -109,12 +111,11 @@ namespace osu.Game.Online.Leaderboards Children = new[] { avatar = new DelayedLoadWrapper( - new Avatar(user) + innerAvatar = new Avatar(user) { RelativeSizeAxes = Axes.Both, CornerRadius = corner_radius, Masking = true, - OnLoadComplete = d => d.FadeInFromZero(200), EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, @@ -214,6 +215,8 @@ namespace osu.Game.Online.Leaderboards }, }, }; + + innerAvatar.OnLoadComplete += d => d.FadeInFromZero(200); } public override void Show() diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs index 8cedde82ff..8111ac7394 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -28,6 +28,8 @@ namespace osu.Game.Overlays.Chat.Tabs if (value.Type != ChannelType.PM) throw new ArgumentException("Argument value needs to have the targettype user!"); + Avatar avatar; + AddRange(new Drawable[] { new Container @@ -49,11 +51,10 @@ namespace osu.Game.Overlays.Chat.Tabs Anchor = Anchor.Centre, Origin = Anchor.Centre, Masking = true, - Child = new DelayedLoadWrapper(new Avatar(value.Users.First()) + Child = new DelayedLoadWrapper(avatar = new Avatar(value.Users.First()) { RelativeSizeAxes = Axes.Both, OpenOnClick = { Value = false }, - OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint), }) { RelativeSizeAxes = Axes.Both, @@ -63,6 +64,8 @@ namespace osu.Game.Overlays.Chat.Tabs }, }); + avatar.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); + Text.X = ChatOverlay.TAB_AREA_HEIGHT; TextBold.X = ChatOverlay.TAB_AREA_HEIGHT; } diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index eeb42ec991..431ae98c2c 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -109,7 +109,7 @@ namespace osu.Game.Overlays.MedalSplash s.Font = s.Font.With(size: 16); }); - medalContainer.OnLoadComplete = d => + medalContainer.OnLoadComplete += d => { unlocked.Position = new Vector2(0f, medalContainer.DrawSize.Y / 2 + 10); infoFlow.Position = new Vector2(0f, unlocked.Position.Y + 90); diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 2641a0551d..c41d977701 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -335,9 +335,12 @@ namespace osu.Game.Overlays.Profile Anchor = Anchor.Centre, Origin = Anchor.Centre, FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(200), Depth = float.MaxValue, - }, coverContainer.Add); + }, background => + { + coverContainer.Add(background); + background.FadeInFromZero(200); + }); if (user.IsSupporter) SupporterTag.Show(); diff --git a/osu.Game/Screens/Multi/Match/Components/Participants.cs b/osu.Game/Screens/Multi/Match/Components/Participants.cs index 2d6099c65d..09d25572ec 100644 --- a/osu.Game/Screens/Multi/Match/Components/Participants.cs +++ b/osu.Game/Screens/Multi/Match/Components/Participants.cs @@ -52,12 +52,18 @@ namespace osu.Game.Screens.Multi.Match.Components Participants.BindValueChanged(participants => { - usersFlow.Children = participants.NewValue.Select(u => new UserPanel(u) + usersFlow.Children = participants.NewValue.Select(u => { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Width = 300, - OnLoadComplete = d => d.FadeInFromZero(60), + var panel = new UserPanel(u) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 300, + }; + + panel.OnLoadComplete += d => d.FadeInFromZero(60); + + return panel; }).ToList(); }, true); } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 389f614ef2..bfd1d3d236 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -577,7 +577,7 @@ namespace osu.Game.Screens.Select else { float y = currentY; - d.OnLoadComplete = _ => performMove(y, setY); + d.OnLoadComplete += _ => performMove(y, setY); } break; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index e01149ebc8..51ca9902d2 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -49,11 +49,16 @@ namespace osu.Game.Screens.Select.Carousel Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => - new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) + { + var background = new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { RelativeSizeAxes = Axes.Both, - OnLoadComplete = d => d.FadeInFromZero(1000, Easing.OutQuint), - }, 300, 5000 + }; + + background.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint); + + return background; + }, 300, 5000 ), new FillFlowContainer { diff --git a/osu.Game/Users/UpdateableAvatar.cs b/osu.Game/Users/UpdateableAvatar.cs index cefb91797b..7259468674 100644 --- a/osu.Game/Users/UpdateableAvatar.cs +++ b/osu.Game/Users/UpdateableAvatar.cs @@ -57,9 +57,9 @@ namespace osu.Game.Users var avatar = new Avatar(user) { RelativeSizeAxes = Axes.Both, - OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint), }; + avatar.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); avatar.OpenOnClick.BindTo(OpenOnClick); Add(displayedAvatar = new DelayedLoadWrapper(avatar)); diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 4dfde04e07..65062dc58e 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -59,6 +59,8 @@ namespace osu.Game.Users FillFlowContainer infoContainer; + UserCoverBackground coverBackground; + AddInternal(content = new Container { RelativeSizeAxes = Axes.Both, @@ -73,13 +75,12 @@ namespace osu.Game.Users Children = new Drawable[] { - new DelayedLoadWrapper(new UserCoverBackground(user) + new DelayedLoadWrapper(coverBackground = new UserCoverBackground(user) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out) }, 300) { RelativeSizeAxes = Axes.Both }, new Box { @@ -181,6 +182,8 @@ namespace osu.Game.Users } }); + coverBackground.OnLoadComplete += d => d.FadeInFromZero(400, Easing.Out); + if (user.IsSupporter) { infoContainer.Add(new SupporterIcon From 9ca4d9d4d1acff69ae673621b7fdcec0b9b92178 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 10:48:44 +0900 Subject: [PATCH 095/623] Remove regions --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 921ed46d0e..161e7aecb4 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -23,8 +23,6 @@ namespace osu.Game.Rulesets.UI gameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); } - #region Clock control - private readonly ManualClock manualClock; private readonly FramedClock framedClock; @@ -150,12 +148,6 @@ namespace osu.Game.Rulesets.UI ProcessCustomClock = false; } - #endregion - - #region IHasReplayHandler - public ReplayInputHandler ReplayInputHandler { get; set; } - - #endregion } } From eac7672c6b964acf2972539bc99e8e402b848155 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 18 Mar 2019 14:03:54 +0900 Subject: [PATCH 096/623] Clean up debug logging --- osu.Game/Graphics/Containers/VisualSettingsContainer.cs | 8 +------- osu.Game/Screens/Select/SongSelect.cs | 1 - 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Containers/VisualSettingsContainer.cs b/osu.Game/Graphics/Containers/VisualSettingsContainer.cs index ebbd975231..edc8d4394a 100644 --- a/osu.Game/Graphics/Containers/VisualSettingsContainer.cs +++ b/osu.Game/Graphics/Containers/VisualSettingsContainer.cs @@ -70,11 +70,7 @@ namespace osu.Game.Graphics.Containers dimLevel = config.GetBindable(OsuSetting.DimLevel); blurLevel = config.GetBindable(OsuSetting.BlurLevel); showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - EnableVisualSettings.ValueChanged += _ => - { - Logger.Log("Oh fuck"); - UpdateVisuals(); - }; + EnableVisualSettings.ValueChanged += _ => UpdateVisuals(); dimLevel.ValueChanged += _ => UpdateVisuals(); blurLevel.ValueChanged += _ => UpdateVisuals(); showStoryboard.ValueChanged += _ => UpdateVisuals(); @@ -105,8 +101,6 @@ namespace osu.Game.Graphics.Containers // We can't blur the container like we did with the dim because buffered containers add considerable draw overhead. // As a result, this blurs the background directly. ((Background)c)?.BlurTo(BlurTarget, background_fade_duration, Easing.OutQuint); - - Logger.Log("Enable visual settings: " + EnableVisualSettings.Value + " Added blur is: " + AddedBlur.Value); } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 6551c70de3..385c4e0a0d 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -558,7 +558,6 @@ namespace osu.Game.Screens.Select backgroundModeBeatmap.Beatmap = beatmap; backgroundModeBeatmap.AddedBlur.Value = BACKGROUND_BLUR; backgroundModeBeatmap.FadeColour(Color4.White, 250); - Logger.Log("blur updated!"); } beatmapInfoWedge.Beatmap = beatmap; From 0024a0bdb2974864a4c5c892c497ef5185787790 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 18 Mar 2019 14:35:03 +0900 Subject: [PATCH 097/623] Remove unused using --- osu.Game/Graphics/Containers/VisualSettingsContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/VisualSettingsContainer.cs b/osu.Game/Graphics/Containers/VisualSettingsContainer.cs index edc8d4394a..1ed4f0e23e 100644 --- a/osu.Game/Graphics/Containers/VisualSettingsContainer.cs +++ b/osu.Game/Graphics/Containers/VisualSettingsContainer.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Graphics.Backgrounds; using osuTK; From 6a26972284d9cc6d89cea1263331fedb28510c0d Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 18 Mar 2019 15:25:54 +0900 Subject: [PATCH 098/623] DI gameplay clock for Storyboards --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index f5e1f612ca..12507b215e 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Game.IO; +using osu.Game.Screens.Play; namespace osu.Game.Storyboards.Drawables { @@ -56,8 +57,11 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(FileStore fileStore) + private void load(FileStore fileStore, GameplayClock clock) { + if (Clock != null) + Clock = clock; + dependencies.Cache(new TextureStore(new TextureLoaderStore(fileStore.Store), false, scaleAdjust: 1)); foreach (var layer in Storyboard.Layers) From 05147768d3100d3e429df27b62acaf5ee4ee1128 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 18 Mar 2019 15:50:34 +0900 Subject: [PATCH 099/623] Permit nulls --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 12507b215e..ffd9b71254 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -56,7 +56,7 @@ namespace osu.Game.Storyboards.Drawables }); } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(FileStore fileStore, GameplayClock clock) { if (Clock != null) From 8df47bc23edd9c63c8c008c55f4247c1ae402009 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 16:39:34 +0900 Subject: [PATCH 100/623] Increase flexibility of player test cases --- .../TestCaseAutoJuiceStream.cs | 2 +- .../TestCaseBananaShower.cs | 3 +- .../TestCaseCatchPlayer.cs | 3 +- .../TestCaseCatchStacker.cs | 3 +- .../TestCaseHyperDash.cs | 20 +-- .../TestCaseHitCircleLongCombo.cs | 3 +- .../TestCaseOsuPlayer.cs | 3 +- osu.Game.Tests/Visual/TestCaseAllPlayers.cs | 12 -- osu.Game.Tests/Visual/TestCaseAutoplay.cs | 10 +- .../Visual/TestCasePlayerReferenceLeaking.cs | 56 ++++++++ osu.Game.Tests/Visual/TestCaseReplay.cs | 10 +- osu.Game/Tests/Visual/AllPlayersTestCase.cs | 94 +++++++++++++ osu.Game/Tests/Visual/PlayerTestCase.cs | 60 ++++++++ osu.Game/Tests/Visual/TestCasePlayer.cs | 131 ------------------ 14 files changed, 239 insertions(+), 171 deletions(-) delete mode 100644 osu.Game.Tests/Visual/TestCaseAllPlayers.cs create mode 100644 osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs create mode 100644 osu.Game/Tests/Visual/AllPlayersTestCase.cs create mode 100644 osu.Game/Tests/Visual/PlayerTestCase.cs delete mode 100644 osu.Game/Tests/Visual/TestCasePlayer.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs index 9319fb3dfb..fbb2db33b0 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs @@ -13,7 +13,7 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Tests { - public class TestCaseAutoJuiceStream : TestCasePlayer + public class TestCaseAutoJuiceStream : PlayerTestCase { public TestCaseAutoJuiceStream() : base(new CatchRuleset()) diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs index 9e1c44ba40..1bae55102f 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs @@ -8,11 +8,12 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.UI; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseBananaShower : Game.Tests.Visual.TestCasePlayer + public class TestCaseBananaShower : PlayerTestCase { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs index 8f9dd73b80..5b242d05d7 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs @@ -2,11 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer + public class TestCaseCatchPlayer : PlayerTestCase { public TestCaseCatchPlayer() : base(new CatchRuleset()) diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs index 1e3d60d968..5a16a23a4e 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs @@ -4,11 +4,12 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer + public class TestCaseCatchStacker : PlayerTestCase { public TestCaseCatchStacker() : base(new CatchRuleset()) diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs index 7451986a8b..a7e7f0ab14 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs @@ -1,22 +1,28 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseHyperDash : Game.Tests.Visual.TestCasePlayer + public class TestCaseHyperDash : PlayerTestCase { public TestCaseHyperDash() : base(new CatchRuleset()) { } + [BackgroundDependencyLoader] + private void load() + { + AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash); + } + protected override IBeatmap CreateBeatmap(Ruleset ruleset) { var beatmap = new Beatmap @@ -28,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Tests } }; - // Should produce a hperdash + // Should produce a hyper-dash beatmap.HitObjects.Add(new Fruit { StartTime = 816, X = 308 / 512f, NewCombo = true }); beatmap.HitObjects.Add(new Fruit { StartTime = 1008, X = 56 / 512f, }); @@ -38,11 +44,5 @@ namespace osu.Game.Rulesets.Catch.Tests return beatmap; } - - protected override void AddCheckSteps(Func player) - { - base.AddCheckSteps(player); - AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash); - } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs index f5fe36b56a..8d097ff1c1 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs @@ -4,12 +4,13 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Visual; using osuTK; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseHitCircleLongCombo : Game.Tests.Visual.TestCasePlayer + public class TestCaseHitCircleLongCombo : PlayerTestCase { public TestCaseHitCircleLongCombo() : base(new OsuRuleset()) diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs index 9f13c19390..720c3c66fe 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs @@ -2,11 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseOsuPlayer : Game.Tests.Visual.TestCasePlayer + public class TestCaseOsuPlayer : PlayerTestCase { public TestCaseOsuPlayer() : base(new OsuRuleset()) diff --git a/osu.Game.Tests/Visual/TestCaseAllPlayers.cs b/osu.Game.Tests/Visual/TestCaseAllPlayers.cs deleted file mode 100644 index a5decaa9fb..0000000000 --- a/osu.Game.Tests/Visual/TestCaseAllPlayers.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using NUnit.Framework; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseAllPlayers : TestCasePlayer - { - } -} diff --git a/osu.Game.Tests/Visual/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/TestCaseAutoplay.cs index 61339a6af8..c25088f8a4 100644 --- a/osu.Game.Tests/Visual/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/TestCaseAutoplay.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.ComponentModel; using System.Linq; using osu.Game.Rulesets; @@ -11,7 +10,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { [Description("Player instantiated with an autoplay mod.")] - public class TestCaseAutoplay : TestCasePlayer + public class TestCaseAutoplay : AllPlayersTestCase { protected override Player CreatePlayer(Ruleset ruleset) { @@ -24,11 +23,10 @@ namespace osu.Game.Tests.Visual }; } - protected override void AddCheckSteps(Func player) + protected override void AddCheckSteps() { - base.AddCheckSteps(player); - AddUntilStep(() => ((ScoreAccessiblePlayer)player()).ScoreProcessor.TotalScore.Value > 0, "score above zero"); - AddUntilStep(() => ((ScoreAccessiblePlayer)player()).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0), "key counter counted keys"); + AddUntilStep(() => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0, "score above zero"); + AddUntilStep(() => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0), "key counter counted keys"); } private class ScoreAccessiblePlayer : Player diff --git a/osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs b/osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs new file mode 100644 index 0000000000..04fb863f18 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Lists; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + public class TestCasePlayerReferenceLeaking : AllPlayersTestCase + { + private readonly WeakList workingWeakReferences = new WeakList(); + + private readonly WeakList playerWeakReferences = new WeakList(); + + protected override void AddCheckSteps() + { + AddUntilStep(() => + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + int count = 0; + + workingWeakReferences.ForEachAlive(_ => count++); + return count == 1; + }, "no leaked beatmaps"); + + AddUntilStep(() => + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + int count = 0; + + playerWeakReferences.ForEachAlive(_ => count++); + return count == 1; + }, "no leaked players"); + } + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock clock) + { + var working = base.CreateWorkingBeatmap(beatmap, clock); + workingWeakReferences.Add(working); + return working; + } + + protected override Player CreatePlayer(Ruleset ruleset) + { + var player = base.CreatePlayer(ruleset); + playerWeakReferences.Add(player); + return player; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs index c34190d567..93a20b9425 100644 --- a/osu.Game.Tests/Visual/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/TestCaseReplay.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.ComponentModel; using System.Linq; using osu.Game.Rulesets; @@ -12,7 +11,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { [Description("Player instantiated with a replay.")] - public class TestCaseReplay : TestCasePlayer + public class TestCaseReplay : AllPlayersTestCase { protected override Player CreatePlayer(Ruleset ruleset) { @@ -21,11 +20,10 @@ namespace osu.Game.Tests.Visual return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap)); } - protected override void AddCheckSteps(Func player) + protected override void AddCheckSteps() { - base.AddCheckSteps(player); - AddUntilStep(() => ((ScoreAccessibleReplayPlayer)player()).ScoreProcessor.TotalScore.Value > 0, "score above zero"); - AddUntilStep(() => ((ScoreAccessibleReplayPlayer)player()).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0), "key counter counted keys"); + AddUntilStep(() => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0, "score above zero"); + AddUntilStep(() => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0), "key counter counted keys"); } private class ScoreAccessibleReplayPlayer : ReplayPlayer diff --git a/osu.Game/Tests/Visual/AllPlayersTestCase.cs b/osu.Game/Tests/Visual/AllPlayersTestCase.cs new file mode 100644 index 0000000000..507848730f --- /dev/null +++ b/osu.Game/Tests/Visual/AllPlayersTestCase.cs @@ -0,0 +1,94 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + /// + /// A base class which runs test for all available rulesets. + /// Steps to be run for each ruleset should be added via . + /// + public abstract class AllPlayersTestCase : RateAdjustedBeatmapTestCase + { + protected Player Player; + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + Add(new Box + { + RelativeSizeAxes = Framework.Graphics.Axes.Both, + Colour = Color4.Black, + Depth = int.MaxValue + }); + + foreach (var r in rulesets.AvailableRulesets) + { + Player p = null; + AddStep(r.Name, () => p = loadPlayerFor(r)); + AddUntilStep(() => + { + if (p?.IsLoaded == true) + { + p = null; + return true; + } + + return false; + }, "player loaded"); + + AddCheckSteps(); + } + } + + protected abstract void AddCheckSteps(); + + protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); + + protected virtual WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock clock) => + new TestWorkingBeatmap(beatmap, Clock); + + private Player loadPlayerFor(RulesetInfo ri) + { + Ruleset.Value = ri; + var r = ri.CreateInstance(); + + var beatmap = CreateBeatmap(r); + var working = CreateWorkingBeatmap(beatmap, Clock); + + Beatmap.Value = working; + Beatmap.Value.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; + + Player?.Exit(); + Player = null; + + var player = CreatePlayer(r); + + LoadComponentAsync(player, p => + { + Player = p; + LoadScreen(p); + }); + + return player; + } + + protected virtual Player CreatePlayer(Ruleset ruleset) => new Player + { + AllowPause = false, + AllowLeadIn = false, + AllowResults = false, + }; + } +} diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs new file mode 100644 index 0000000000..cdf53a362b --- /dev/null +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + public abstract class PlayerTestCase : RateAdjustedBeatmapTestCase + { + private readonly Ruleset ruleset; + + protected Player Player; + + protected PlayerTestCase(Ruleset ruleset) + { + this.ruleset = ruleset; + + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Depth = int.MaxValue + }); + + AddStep(ruleset.RulesetInfo.Name, loadPlayer); + AddUntilStep(() => Player.IsLoaded, "player loaded"); + } + + protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); + + private void loadPlayer() + { + var beatmap = CreateBeatmap(ruleset); + + Beatmap.Value = new TestWorkingBeatmap(beatmap, Clock); + Beatmap.Value.Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; + + LoadComponentAsync(Player = CreatePlayer(ruleset), p => + { + Player = p; + LoadScreen(p); + }); + } + + protected virtual Player CreatePlayer(Ruleset ruleset) => new Player + { + AllowPause = false, + AllowLeadIn = false, + AllowResults = false, + }; + } +} diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs deleted file mode 100644 index 5ff798c40d..0000000000 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) ppy Pty Ltd . 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.Graphics.Shapes; -using osu.Framework.Lists; -using osu.Framework.Screens; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play; -using osu.Game.Tests.Beatmaps; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual -{ - public abstract class TestCasePlayer : RateAdjustedBeatmapTestCase - { - private readonly Ruleset ruleset; - - protected Player Player; - - protected TestCasePlayer(Ruleset ruleset) - { - this.ruleset = ruleset; - } - - protected TestCasePlayer() - { - } - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - Add(new Box - { - RelativeSizeAxes = Framework.Graphics.Axes.Both, - Colour = Color4.Black, - Depth = int.MaxValue - }); - - if (ruleset != null) - { - Player p = null; - AddStep(ruleset.RulesetInfo.Name, () => p = loadPlayerFor(ruleset)); - AddCheckSteps(() => p); - } - else - { - foreach (var r in rulesets.AvailableRulesets) - { - Player p = null; - AddStep(r.Name, () => p = loadPlayerFor(r)); - AddCheckSteps(() => p); - - AddUntilStep(() => - { - p = null; - - GC.Collect(); - GC.WaitForPendingFinalizers(); - int count = 0; - - workingWeakReferences.ForEachAlive(_ => count++); - return count == 1; - }, "no leaked beatmaps"); - - AddUntilStep(() => - { - GC.Collect(); - GC.WaitForPendingFinalizers(); - int count = 0; - - playerWeakReferences.ForEachAlive(_ => count++); - return count == 1; - }, "no leaked players"); - } - } - } - - protected virtual void AddCheckSteps(Func player) - { - AddUntilStep(() => player().IsLoaded, "player loaded"); - } - - protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); - - private readonly WeakList workingWeakReferences = new WeakList(); - private readonly WeakList playerWeakReferences = new WeakList(); - - private Player loadPlayerFor(RulesetInfo ri) - { - Ruleset.Value = ri; - return loadPlayerFor(ri.CreateInstance()); - } - - private Player loadPlayerFor(Ruleset r) - { - var beatmap = CreateBeatmap(r); - var working = new TestWorkingBeatmap(beatmap, Clock); - - workingWeakReferences.Add(working); - - Beatmap.Value = working; - Beatmap.Value.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; - - Player?.Exit(); - - var player = CreatePlayer(r); - - playerWeakReferences.Add(player); - - LoadComponentAsync(player, p => - { - Player = p; - LoadScreen(p); - }); - - return player; - } - - protected virtual Player CreatePlayer(Ruleset ruleset) => new Player - { - AllowPause = false, - AllowLeadIn = false, - AllowResults = false, - }; - } -} From acc133896ba4acfb29badc842d4f7531e2733825 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 18 Mar 2019 18:19:59 +0900 Subject: [PATCH 101/623] Correct null check --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index ffd9b71254..1182cacfc1 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -59,7 +59,7 @@ namespace osu.Game.Storyboards.Drawables [BackgroundDependencyLoader(true)] private void load(FileStore fileStore, GameplayClock clock) { - if (Clock != null) + if (clock != null) Clock = clock; dependencies.Cache(new TextureStore(new TextureLoaderStore(fileStore.Store), false, scaleAdjust: 1)); From bb8171b88a1fdd95ef90a4f4ef7bfe448b6c40dc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Mar 2019 18:36:16 +0900 Subject: [PATCH 102/623] Fix HR mod being applied to non-fruit objects --- osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index 0cfa3e98f7..c04afb4ddb 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -20,6 +20,9 @@ namespace osu.Game.Rulesets.Catch.Mods public void ApplyToHitObject(HitObject hitObject) { + if (!(hitObject is Fruit)) + return; + var catchObject = (CatchHitObject)hitObject; float position = catchObject.X; From a26e237e261ec83883962f0b971da2242222193b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Mar 2019 18:59:38 +0900 Subject: [PATCH 103/623] Fix relative position being compared to time --- osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index c04afb4ddb..e96a9c7441 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Catch.Mods return; } - if (Math.Abs(diff) < timeDiff / 3d) + if (Math.Abs(diff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d) { if (diff > 0) { From a81461ba1222a4ba232591fab088c9b3a34a110b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 19:43:55 +0900 Subject: [PATCH 104/623] Add ability to test without nofail enabled --- osu.Game/Tests/Visual/PlayerTestCase.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index cdf53a362b..4833d0ba0f 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Linq; @@ -36,12 +36,16 @@ namespace osu.Game.Tests.Visual protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); + protected virtual bool AllowFail => false; + private void loadPlayer() { var beatmap = CreateBeatmap(ruleset); Beatmap.Value = new TestWorkingBeatmap(beatmap, Clock); - Beatmap.Value.Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; + + if (!AllowFail) + Beatmap.Value.Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; LoadComponentAsync(Player = CreatePlayer(ruleset), p => { From 15dd132c92e13ee2fb41f723a270d65a441d8d89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 19:44:14 +0900 Subject: [PATCH 105/623] Use SetUpSteps attribute --- osu.Game/Tests/Visual/PlayerTestCase.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index 4833d0ba0f..ad01d82281 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -1,9 +1,10 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -29,7 +30,11 @@ namespace osu.Game.Tests.Visual Colour = Color4.Black, Depth = int.MaxValue }); + } + [SetUpSteps] + public void SetUpSteps() + { AddStep(ruleset.RulesetInfo.Name, loadPlayer); AddUntilStep(() => Player.IsLoaded, "player loaded"); } From 465c95e952cceb7ff62b496861728b6d81e4b5b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Mar 2019 14:20:10 +0900 Subject: [PATCH 106/623] Refactor pause logic so GameplayClockContainer is in control --- .../Screens/Play/GameplayClockContainer.cs | 7 ++++- .../Screens/Play/PausableGameplayContainer.cs | 26 ++++++++---------- osu.Game/Screens/Play/Player.cs | 27 +++++++++++-------- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 3c2cec1d94..1e4c9aba92 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -118,11 +118,16 @@ namespace osu.Game.Screens.Play // This accounts for the audio clock source potentially taking time to enter a completely stopped state adjustableClock.Seek(adjustableClock.CurrentTime); adjustableClock.Start(); + IsPaused.Value = false; } public void Seek(double time) => adjustableClock.Seek(time); - public void Stop() => adjustableClock.Stop(); + public void Stop() + { + adjustableClock.Stop(); + IsPaused.Value = true; + } public void ResetLocalAdjustments() { diff --git a/osu.Game/Screens/Play/PausableGameplayContainer.cs b/osu.Game/Screens/Play/PausableGameplayContainer.cs index 99f0083b55..6363b92a8f 100644 --- a/osu.Game/Screens/Play/PausableGameplayContainer.cs +++ b/osu.Game/Screens/Play/PausableGameplayContainer.cs @@ -41,8 +41,8 @@ namespace osu.Game.Screens.Play public Action OnRetry; public Action OnQuit; - public Action Stop; - public Action Start; + public Action RequestPause; + public Action RequestResume; /// /// Creates a new . @@ -70,15 +70,12 @@ namespace osu.Game.Screens.Play }; } - public void Pause(bool force = false) => Schedule(() => // Scheduled to ensure a stable position in execution order, no matter how it was called. + public void Pause() => Schedule(() => // Scheduled to ensure a stable position in execution order, no matter how it was called. { - if (!CanPause && !force) return; - - if (IsPaused.Value) return; + if (!CanPause) return; // stop the seekable clock (stops the audio eventually) - Stop?.Invoke(); - IsPaused.Value = true; + RequestPause?.Invoke(); pauseOverlay.Show(); @@ -89,14 +86,13 @@ namespace osu.Game.Screens.Play { if (!IsPaused.Value) return; - IsResuming = false; - lastPauseActionTime = Time.Current; - - IsPaused.Value = false; - - Start?.Invoke(); - pauseOverlay.Hide(); + + RequestResume?.Invoke(() => + { + IsResuming = false; + lastPauseActionTime = Time.Current; + }); } private OsuGameBase game; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b11c5e51c9..7637cfe869 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -111,10 +111,14 @@ namespace osu.Game.Screens.Play Retries = RestartCount, OnRetry = restart, OnQuit = performUserRequestedExit, - Start = gameplayClockContainer.Start, - Stop = gameplayClockContainer.Stop, + RequestResume = completion => + { + gameplayClockContainer.Start(); + completion(); + }, + RequestPause = gameplayClockContainer.Stop, IsPaused = { BindTarget = gameplayClockContainer.IsPaused }, - CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value, + CheckCanPause = () => CanPause, Children = new[] { StoryboardContainer = CreateStoryboardContainer(), @@ -337,6 +341,9 @@ namespace osu.Game.Screens.Play base.OnSuspending(next); } + public bool CanPause => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value + && (PausableGameplayContainer?.IsPaused.Value == false || PausableGameplayContainer?.IsResuming == true); + public override bool OnExiting(IScreen next) { if (onCompletionEvent != null) @@ -346,18 +353,16 @@ namespace osu.Game.Screens.Play return true; } - if ((!AllowPause || HasFailed || !ValidForResume || PausableGameplayContainer?.IsPaused.Value != false || RulesetContainer?.HasReplayLoaded.Value != false) && (!PausableGameplayContainer?.IsResuming ?? true)) + if (LoadedBeatmapSuccessfully && CanPause) { - gameplayClockContainer.ResetLocalAdjustments(); - - fadeOut(); - return base.OnExiting(next); + PausableGameplayContainer?.Pause(); + return true; } - if (LoadedBeatmapSuccessfully) - PausableGameplayContainer?.Pause(); + gameplayClockContainer.ResetLocalAdjustments(); - return true; + fadeOut(); + return base.OnExiting(next); } private void fadeOut(bool instant = false) From bcaff9f7b47efdc3c0087609daef32e48110e1ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 00:46:15 +0900 Subject: [PATCH 107/623] Add basic pause tests --- osu.Game.Tests/Visual/TestCasePause.cs | 47 +++++++++++++++++++ .../Screens/Play/GameplayClockContainer.cs | 6 +-- osu.Game/Screens/Play/Player.cs | 26 +++++----- 3 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 osu.Game.Tests/Visual/TestCasePause.cs diff --git a/osu.Game.Tests/Visual/TestCasePause.cs b/osu.Game.Tests/Visual/TestCasePause.cs new file mode 100644 index 0000000000..6966eb3de9 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCasePause.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + public class TestCasePause : TestCasePlayer + { + public TestCasePause() + : base(new OsuRuleset()) + { + } + + protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer(); + + protected override void AddCheckSteps(Func player) + { + PausePlayer pausable() => (PausePlayer)player(); + + base.AddCheckSteps(player); + //AddUntilStep(() => pausable().ScoreProcessor.TotalScore.Value > 0, "score above zero"); + + AddStep("pause", () => pausable().PausableGameplayContainer.Pause()); + AddAssert("clock stopped", () => !pausable().GameplayClockContainer.GameplayClock.IsRunning); + + AddStep("resume", () => pausable().PausableGameplayContainer.Resume()); + AddUntilStep(() => pausable().GameplayClockContainer.GameplayClock.IsRunning, "clock started"); + + AddStep("pause too soon", () => pausable().PausableGameplayContainer.Pause()); + AddAssert("clock not stopped", () => pausable().GameplayClockContainer.GameplayClock.IsRunning); + } + + private class PausePlayer : Player + { + public new PausableGameplayContainer PausableGameplayContainer => base.PausableGameplayContainer; + + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + } + } +} diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 1e4c9aba92..deac5e02bf 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -18,7 +18,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Screens.Play { /// - /// Encapsulates gameplay timing logic and provides a for children. + /// Encapsulates gameplay timing logic and provides a for children. /// public class GameplayClockContainer : Container { @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Play /// The final clock which is exposed to underlying components. /// [Cached] - private readonly GameplayClock gameplayClock; + public readonly GameplayClock GameplayClock; private Bindable userAudioOffset; @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Play offsetClock = new FramedOffsetClock(platformOffsetClock); // the clock to be exposed via DI to children. - gameplayClock = new GameplayClock(offsetClock); + GameplayClock = new GameplayClock(offsetClock); } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 7637cfe869..3d60a44c85 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -82,7 +82,7 @@ namespace osu.Game.Screens.Play public bool LoadedBeatmapSuccessfully => RulesetContainer?.Objects.Any() == true; - private GameplayClockContainer gameplayClockContainer; + protected GameplayClockContainer GameplayClockContainer { get; private set; } [BackgroundDependencyLoader] private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) @@ -102,9 +102,9 @@ namespace osu.Game.Screens.Play if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - InternalChild = gameplayClockContainer = new GameplayClockContainer(working, AllowLeadIn, RulesetContainer.GameplayStartTime); + InternalChild = GameplayClockContainer = new GameplayClockContainer(working, AllowLeadIn, RulesetContainer.GameplayStartTime); - gameplayClockContainer.Children = new Drawable[] + GameplayClockContainer.Children = new Drawable[] { PausableGameplayContainer = new PausableGameplayContainer { @@ -113,11 +113,11 @@ namespace osu.Game.Screens.Play OnQuit = performUserRequestedExit, RequestResume = completion => { - gameplayClockContainer.Start(); + GameplayClockContainer.Start(); completion(); }, - RequestPause = gameplayClockContainer.Stop, - IsPaused = { BindTarget = gameplayClockContainer.IsPaused }, + RequestPause = GameplayClockContainer.Stop, + IsPaused = { BindTarget = GameplayClockContainer.IsPaused }, CheckCanPause = () => CanPause, Children = new[] { @@ -141,15 +141,15 @@ namespace osu.Game.Screens.Play HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working) { HoldToQuit = { Action = performUserRequestedExit }, - PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = gameplayClockContainer.UserPlaybackRate } } }, + PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, KeyCounter = { Visible = { BindTarget = RulesetContainer.HasReplayLoaded } }, - RequestSeek = gameplayClockContainer.Seek, + RequestSeek = GameplayClockContainer.Seek, Anchor = Anchor.Centre, Origin = Anchor.Centre }, new SkipOverlay(RulesetContainer.GameplayStartTime) { - RequestSeek = gameplayClockContainer.Seek + RequestSeek = GameplayClockContainer.Seek }, } }, @@ -171,7 +171,7 @@ namespace osu.Game.Screens.Play }; // bind clock into components that require it - RulesetContainer.IsPaused.BindTo(gameplayClockContainer.IsPaused); + RulesetContainer.IsPaused.BindTo(GameplayClockContainer.IsPaused); if (ShowStoryboard.Value) initializeStoryboard(false); @@ -295,7 +295,7 @@ namespace osu.Game.Screens.Play if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) return false; - gameplayClockContainer.Stop(); + GameplayClockContainer.Stop(); HasFailed = true; failOverlay.Retries = RestartCount; @@ -329,7 +329,7 @@ namespace osu.Game.Screens.Play storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable; - gameplayClockContainer.Restart(); + GameplayClockContainer.Restart(); PausableGameplayContainer.Alpha = 0; PausableGameplayContainer.FadeIn(750, Easing.OutQuint); @@ -359,7 +359,7 @@ namespace osu.Game.Screens.Play return true; } - gameplayClockContainer.ResetLocalAdjustments(); + GameplayClockContainer.ResetLocalAdjustments(); fadeOut(); return base.OnExiting(next); From f13003c53b7957cf76be0042a85f7d99512349cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 11:12:47 +0900 Subject: [PATCH 108/623] Simplify and localise storyboard logic in Player.cs --- osu.Game/Screens/Play/Player.cs | 48 ++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3d60a44c85..0e8bedefb0 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -70,9 +70,32 @@ namespace osu.Game.Screens.Play protected HUDOverlay HUDOverlay { get; private set; } private FailOverlay failOverlay; + #region Storyboard + private DrawableStoryboard storyboard; protected UserDimContainer StoryboardContainer { get; private set; } + private void initializeStoryboard(bool asyncLoad) + { + if (StoryboardContainer == null || storyboard != null) + return; + + if (!ShowStoryboard.Value) + return; + + var beatmap = Beatmap.Value; + + storyboard = beatmap.Storyboard.CreateDrawable(); + storyboard.Masking = true; + + if (asyncLoad) + LoadComponentAsync(storyboard, StoryboardContainer.Add); + else + StoryboardContainer.Add(storyboard); + } + + #endregion + protected virtual UserDimContainer CreateStoryboardContainer() => new UserDimContainer(true) { RelativeSizeAxes = Axes.Both, @@ -173,7 +196,7 @@ namespace osu.Game.Screens.Play // bind clock into components that require it RulesetContainer.IsPaused.BindTo(GameplayClockContainer.IsPaused); - if (ShowStoryboard.Value) + // load storyboard as part of player's load if we can initializeStoryboard(false); // Bind ScoreProcessor to ourselves @@ -317,10 +340,7 @@ namespace osu.Game.Screens.Play .Delay(250) .FadeIn(250); - ShowStoryboard.ValueChanged += enabled => - { - if (enabled.NewValue) initializeStoryboard(true); - }; + ShowStoryboard.ValueChanged += _ => initializeStoryboard(true); Background.EnableUserDim.Value = true; @@ -376,22 +396,6 @@ namespace osu.Game.Screens.Play protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !PausableGameplayContainer.IsPaused.Value; - private void initializeStoryboard(bool asyncLoad) - { - if (StoryboardContainer == null || storyboard != null) - return; - - var beatmap = Beatmap.Value; - - storyboard = beatmap.Storyboard.CreateDrawable(); - storyboard.Masking = true; - - if (asyncLoad) - LoadComponentAsync(storyboard, StoryboardContainer.Add); - else - StoryboardContainer.Add(storyboard); - } - protected virtual Results CreateResults(ScoreInfo score) => new SoloResults(score); } } From 536b5e0dab579ec7e55ee09d08397f30cfcb13b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 11:48:11 +0900 Subject: [PATCH 109/623] Remove PausableGameplayContainer --- .../Visual/TestCaseBackgroundScreenBeatmap.cs | 6 +- .../Visual/TestCaseGameplayMenuOverlay.cs | 6 +- osu.Game.Tests/Visual/TestCasePause.cs | 28 ++- .../Screens/Play/PausableGameplayContainer.cs | 133 ------------ osu.Game/Screens/Play/PauseOverlay.cs | 29 +++ osu.Game/Screens/Play/Player.cs | 190 ++++++++++++------ 6 files changed, 183 insertions(+), 209 deletions(-) delete mode 100644 osu.Game/Screens/Play/PausableGameplayContainer.cs create mode 100644 osu.Game/Screens/Play/PauseOverlay.cs diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index 5484824c5b..d62ae07f6a 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -188,10 +188,10 @@ namespace osu.Game.Tests.Visual public void PauseTest() { performFullSetup(true); - AddStep("Pause", () => player.CurrentPausableGameplayContainer.Pause()); + AddStep("Pause", () => player.Pause()); waitForDim(); AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); - AddStep("Unpause", () => player.CurrentPausableGameplayContainer.Resume()); + AddStep("Unpause", () => player.Resume()); waitForDim(); AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); } @@ -328,8 +328,6 @@ namespace osu.Game.Tests.Visual }; } - public PausableGameplayContainer CurrentPausableGameplayContainer => PausableGameplayContainer; - public UserDimContainer CurrentStoryboardContainer => StoryboardContainer; // Whether or not the player should be allowed to load. diff --git a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs index 93a059d214..c5ad57fec9 100644 --- a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs @@ -17,15 +17,15 @@ namespace osu.Game.Tests.Visual [Description("player pause/fail screens")] public class TestCaseGameplayMenuOverlay : ManualInputManagerTestCase { - public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PausableGameplayContainer) }; + public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseOverlay) }; private FailOverlay failOverlay; - private PausableGameplayContainer.PauseOverlay pauseOverlay; + private PauseOverlay pauseOverlay; [BackgroundDependencyLoader] private void load() { - Add(pauseOverlay = new PausableGameplayContainer.PauseOverlay + Add(pauseOverlay = new PauseOverlay { OnResume = () => Logger.Log(@"Resume"), OnRetry = () => Logger.Log(@"Retry"), diff --git a/osu.Game.Tests/Visual/TestCasePause.cs b/osu.Game.Tests/Visual/TestCasePause.cs index 6966eb3de9..622a12da81 100644 --- a/osu.Game.Tests/Visual/TestCasePause.cs +++ b/osu.Game.Tests/Visual/TestCasePause.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; @@ -16,6 +17,8 @@ namespace osu.Game.Tests.Visual { } + protected override bool AllowFail => true; + protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer(); protected override void AddCheckSteps(Func player) @@ -25,23 +28,36 @@ namespace osu.Game.Tests.Visual base.AddCheckSteps(player); //AddUntilStep(() => pausable().ScoreProcessor.TotalScore.Value > 0, "score above zero"); - AddStep("pause", () => pausable().PausableGameplayContainer.Pause()); + AddStep("pause", () => pausable().Pause()); AddAssert("clock stopped", () => !pausable().GameplayClockContainer.GameplayClock.IsRunning); + AddAssert("pause overlay shown", () => pausable().PauseOverlayVisible); - AddStep("resume", () => pausable().PausableGameplayContainer.Resume()); - AddUntilStep(() => pausable().GameplayClockContainer.GameplayClock.IsRunning, "clock started"); + AddStep("resume", () => pausable().Resume()); + AddAssert("pause overlay hidden", () => !pausable().PauseOverlayVisible); - AddStep("pause too soon", () => pausable().PausableGameplayContainer.Pause()); + AddStep("pause too soon", () => pausable().Pause()); AddAssert("clock not stopped", () => pausable().GameplayClockContainer.GameplayClock.IsRunning); + AddAssert("pause overlay hidden", () => !pausable().PauseOverlayVisible); + + AddUntilStep(() => pausable().HasFailed, "wait for fail"); + + AddAssert("fail overlay shown", () => pausable().FailOverlayVisible); + + AddStep("try to pause", () => pausable().Pause()); + + AddAssert("pause overlay hidden", () => !pausable().PauseOverlayVisible); + AddAssert("fail overlay still shown", () => pausable().FailOverlayVisible); } private class PausePlayer : Player { - public new PausableGameplayContainer PausableGameplayContainer => base.PausableGameplayContainer; - public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + public bool FailOverlayVisible => FailOverlay.State == Visibility.Visible; + + public bool PauseOverlayVisible => PauseOverlay.State == Visibility.Visible; } } } diff --git a/osu.Game/Screens/Play/PausableGameplayContainer.cs b/osu.Game/Screens/Play/PausableGameplayContainer.cs deleted file mode 100644 index 6363b92a8f..0000000000 --- a/osu.Game/Screens/Play/PausableGameplayContainer.cs +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osuTK.Graphics; - -namespace osu.Game.Screens.Play -{ - /// - /// A container which handles pausing children, displaying an overlay blocking its children during paused state. - /// - public class PausableGameplayContainer : Container - { - public readonly BindableBool IsPaused = new BindableBool(); - - public Func CheckCanPause; - - private const double pause_cooldown = 1000; - private double lastPauseActionTime; - - private readonly PauseOverlay pauseOverlay; - - private readonly Container content; - - protected override Container Content => content; - - public int Retries - { - set => pauseOverlay.Retries = value; - } - - public bool CanPause => (CheckCanPause?.Invoke() ?? true) && Time.Current >= lastPauseActionTime + pause_cooldown; - public bool IsResuming { get; private set; } - - public Action OnRetry; - public Action OnQuit; - - public Action RequestPause; - public Action RequestResume; - - /// - /// Creates a new . - /// - public PausableGameplayContainer() - { - RelativeSizeAxes = Axes.Both; - - InternalChildren = new[] - { - content = new Container - { - RelativeSizeAxes = Axes.Both - }, - pauseOverlay = new PauseOverlay - { - OnResume = () => - { - IsResuming = true; - this.Delay(400).Schedule(Resume); - }, - OnRetry = () => OnRetry(), - OnQuit = () => OnQuit(), - } - }; - } - - public void Pause() => Schedule(() => // Scheduled to ensure a stable position in execution order, no matter how it was called. - { - if (!CanPause) return; - - // stop the seekable clock (stops the audio eventually) - RequestPause?.Invoke(); - - pauseOverlay.Show(); - - lastPauseActionTime = Time.Current; - }); - - public void Resume() - { - if (!IsPaused.Value) return; - - pauseOverlay.Hide(); - - RequestResume?.Invoke(() => - { - IsResuming = false; - lastPauseActionTime = Time.Current; - }); - } - - private OsuGameBase game; - - [BackgroundDependencyLoader] - private void load(OsuGameBase game) - { - this.game = game; - } - - protected override void Update() - { - // eagerly pause when we lose window focus (if we are locally playing). - if (!game.IsActive.Value && CanPause) - Pause(); - - base.Update(); - } - - public class PauseOverlay : GameplayMenuOverlay - { - public Action OnResume; - - public override string Header => "paused"; - public override string Description => "you're not going to do what i think you're going to do, are ya?"; - - protected override Action BackAction => () => InternalButtons.Children.First().Click(); - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AddButton("Continue", colours.Green, () => OnResume?.Invoke()); - AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); - AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); - } - } - } -} diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs new file mode 100644 index 0000000000..6cc6027a03 --- /dev/null +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . 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.Game.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play +{ + public class PauseOverlay : GameplayMenuOverlay + { + public Action OnResume; + + public override string Header => "paused"; + public override string Description => "you're not going to do what i think you're going to do, are ya?"; + + protected override Action BackAction => () => InternalButtons.Children.First().Click(); + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AddButton("Continue", colours.Green, () => OnResume?.Invoke()); + AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); + AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0e8bedefb0..018ff900ee 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -56,8 +56,6 @@ namespace osu.Game.Screens.Play [Resolved] private ScoreManager scoreManager { get; set; } - protected PausableGameplayContainer PausableGameplayContainer { get; private set; } - private RulesetInfo ruleset; private IAPIProvider api; @@ -68,7 +66,6 @@ namespace osu.Game.Screens.Play protected RulesetContainer RulesetContainer { get; private set; } protected HUDOverlay HUDOverlay { get; private set; } - private FailOverlay failOverlay; #region Storyboard @@ -127,57 +124,47 @@ namespace osu.Game.Screens.Play InternalChild = GameplayClockContainer = new GameplayClockContainer(working, AllowLeadIn, RulesetContainer.GameplayStartTime); - GameplayClockContainer.Children = new Drawable[] + GameplayClockContainer.Children = new[] { - PausableGameplayContainer = new PausableGameplayContainer + StoryboardContainer = CreateStoryboardContainer(), + new ScalingContainer(ScalingMode.Gameplay) { - Retries = RestartCount, - OnRetry = restart, - OnQuit = performUserRequestedExit, - RequestResume = completion => + Child = new LocalSkinOverrideContainer(working.Skin) { - GameplayClockContainer.Start(); - completion(); - }, - RequestPause = GameplayClockContainer.Stop, - IsPaused = { BindTarget = GameplayClockContainer.IsPaused }, - CheckCanPause = () => CanPause, - Children = new[] - { - StoryboardContainer = CreateStoryboardContainer(), - new ScalingContainer(ScalingMode.Gameplay) - { - Child = new LocalSkinOverrideContainer(working.Skin) - { - RelativeSizeAxes = Axes.Both, - Child = RulesetContainer - } - }, - new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Breaks = working.Beatmap.Breaks - }, - // display the cursor above some HUD elements. - RulesetContainer.Cursor?.CreateProxy() ?? new Container(), - HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working) - { - HoldToQuit = { Action = performUserRequestedExit }, - PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, - KeyCounter = { Visible = { BindTarget = RulesetContainer.HasReplayLoaded } }, - RequestSeek = GameplayClockContainer.Seek, - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }, - new SkipOverlay(RulesetContainer.GameplayStartTime) - { - RequestSeek = GameplayClockContainer.Seek - }, + RelativeSizeAxes = Axes.Both, + Child = RulesetContainer } }, - failOverlay = new FailOverlay + new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Breaks = working.Beatmap.Breaks + }, + // display the cursor above some HUD elements. + RulesetContainer.Cursor?.CreateProxy() ?? new Container(), + HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working) + { + HoldToQuit = { Action = performUserRequestedExit }, + PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, + KeyCounter = { Visible = { BindTarget = RulesetContainer.HasReplayLoaded } }, + RequestSeek = GameplayClockContainer.Seek, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }, + new SkipOverlay(RulesetContainer.GameplayStartTime) + { + RequestSeek = GameplayClockContainer.Seek + }, + FailOverlay = new FailOverlay + { + OnRetry = restart, + OnQuit = performUserRequestedExit, + }, + PauseOverlay = new PauseOverlay + { + OnResume = Resume, + Retries = RestartCount, OnRetry = restart, OnQuit = performUserRequestedExit, }, @@ -197,7 +184,7 @@ namespace osu.Game.Screens.Play RulesetContainer.IsPaused.BindTo(GameplayClockContainer.IsPaused); // load storyboard as part of player's load if we can - initializeStoryboard(false); + initializeStoryboard(false); // Bind ScoreProcessor to ourselves ScoreProcessor.AllJudged += onCompletion; @@ -313,6 +300,14 @@ namespace osu.Game.Screens.Play return score; } + protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value; + + protected virtual Results CreateResults(ScoreInfo score) => new SoloResults(score); + + #region Fail Logic + + protected FailOverlay FailOverlay { get; private set; } + private bool onFail() { if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) @@ -321,11 +316,87 @@ namespace osu.Game.Screens.Play GameplayClockContainer.Stop(); HasFailed = true; - failOverlay.Retries = RestartCount; - failOverlay.Show(); + + // There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer) + // could process an extra frame after the GameplayClock is stopped. + // In such cases we want the fail state to precede a user triggered pause. + if (PauseOverlay.State == Visibility.Visible) + PauseOverlay.Hide(); + + FailOverlay.Retries = RestartCount; + FailOverlay.Show(); return true; } + #endregion + + #region Pause Logic + + public bool IsResuming { get; private set; } + + /// + /// The amount of gameplay time after which a second pause is allowed. + /// + private const double pause_cooldown = 1000; + + protected PauseOverlay PauseOverlay { get; private set; } + + private double? lastPauseActionTime; + + private bool canPause => + // must pass basic screen conditions (beatmap loaded, instance allows pause) + LoadedBeatmapSuccessfully && AllowPause && ValidForResume + // replays cannot be paused and exit immediately + && !RulesetContainer.HasReplayLoaded.Value + // cannot pause if we are already in a fail state + && !HasFailed + // cannot pause if already paused (and not in the process of resuming) + && (GameplayClockContainer.IsPaused.Value == false || IsResuming) + // cannot pause too soon after previous pause + && (!lastPauseActionTime.HasValue || GameplayClockContainer.GameplayClock.CurrentTime >= lastPauseActionTime + pause_cooldown); + + private bool canResume => + // cannot resume from a non-paused state + GameplayClockContainer.IsPaused.Value + // cannot resume if we are already in a fail state + && !HasFailed + // already resuming + && !IsResuming; + + protected override void Update() + { + base.Update(); + + // eagerly pause when we lose window focus (if we are locally playing). + if (!Game.IsActive.Value) + Pause(); + } + + public void Pause() + { + if (!canPause) return; + + GameplayClockContainer.Stop(); + PauseOverlay.Show(); + lastPauseActionTime = GameplayClockContainer.GameplayClock.CurrentTime; + } + + public void Resume() + { + if (!canResume) return; + + //todo: add resume request support to ruleset + IsResuming = true; + + GameplayClockContainer.Start(); + PauseOverlay.Hide(); + IsResuming = false; + } + + #endregion + + #region Screen Logic + public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -350,9 +421,7 @@ namespace osu.Game.Screens.Play storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable; GameplayClockContainer.Restart(); - - PausableGameplayContainer.Alpha = 0; - PausableGameplayContainer.FadeIn(750, Easing.OutQuint); + GameplayClockContainer.FadeInFromZero(750, Easing.OutQuint); } public override void OnSuspending(IScreen next) @@ -361,9 +430,6 @@ namespace osu.Game.Screens.Play base.OnSuspending(next); } - public bool CanPause => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value - && (PausableGameplayContainer?.IsPaused.Value == false || PausableGameplayContainer?.IsResuming == true); - public override bool OnExiting(IScreen next) { if (onCompletionEvent != null) @@ -373,9 +439,9 @@ namespace osu.Game.Screens.Play return true; } - if (LoadedBeatmapSuccessfully && CanPause) + if (LoadedBeatmapSuccessfully && canPause) { - PausableGameplayContainer?.Pause(); + Pause(); return true; } @@ -394,8 +460,6 @@ namespace osu.Game.Screens.Play storyboardReplacesBackground.Value = false; } - protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !PausableGameplayContainer.IsPaused.Value; - - protected virtual Results CreateResults(ScoreInfo score) => new SoloResults(score); + #endregion } } From 9433a977479792bc35b470ca8720e446df69c8cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 14:40:53 +0900 Subject: [PATCH 110/623] Add resume requesting support and fix exit scenarios --- osu.Game/Rulesets/UI/RulesetContainer.cs | 7 ++++++ osu.Game/Screens/Play/Player.cs | 27 ++++++++++++++++++------ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index d8813631dc..c522118962 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -130,6 +130,13 @@ namespace osu.Game.Rulesets.UI /// The input manager. public abstract PassThroughInputManager CreateInputManager(); + /// + /// Invoked when the interactive user requests resuming from a paused state. + /// Allows potentially delaying the resume process until an interaction is performed. + /// + /// The action to run when resuming is to be completed. + public void RequestResume(Action continueResume) => continueResume(); + protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; protected FrameStabilityContainer FrameStabilityContainer; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 018ff900ee..b53ed8ae17 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -352,8 +352,10 @@ namespace osu.Game.Screens.Play && !HasFailed // cannot pause if already paused (and not in the process of resuming) && (GameplayClockContainer.IsPaused.Value == false || IsResuming) - // cannot pause too soon after previous pause - && (!lastPauseActionTime.HasValue || GameplayClockContainer.GameplayClock.CurrentTime >= lastPauseActionTime + pause_cooldown); + && (!pauseCooldownActive || IsResuming); + + private bool pauseCooldownActive => + lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; private bool canResume => // cannot resume from a non-paused state @@ -376,6 +378,7 @@ namespace osu.Game.Screens.Play { if (!canPause) return; + IsResuming = false; GameplayClockContainer.Stop(); PauseOverlay.Show(); lastPauseActionTime = GameplayClockContainer.GameplayClock.CurrentTime; @@ -385,12 +388,20 @@ namespace osu.Game.Screens.Play { if (!canResume) return; - //todo: add resume request support to ruleset IsResuming = true; - - GameplayClockContainer.Start(); PauseOverlay.Hide(); - IsResuming = false; + + // time-based conditions may allow instant resume. + if (GameplayClockContainer.GameplayClock.CurrentTime < Beatmap.Value.Beatmap.HitObjects.First().StartTime) + completeResume(); + else + RulesetContainer.RequestResume(completeResume); + + void completeResume() + { + GameplayClockContainer.Start(); + IsResuming = false; + } } #endregion @@ -445,6 +456,10 @@ namespace osu.Game.Screens.Play return true; } + if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value) + // still want to block if we are within the cooldown period and not already paused. + return true; + GameplayClockContainer.ResetLocalAdjustments(); fadeOut(); From 4f075f4740b8592a5d1c1426ca13cf1054da042b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 14:57:06 +0900 Subject: [PATCH 111/623] Add more comprehensive testing --- osu.Game.Tests/Visual/TestCasePause.cs | 13 ++++++++++++- osu.Game/Screens/Play/Player.cs | 8 ++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCasePause.cs b/osu.Game.Tests/Visual/TestCasePause.cs index 622a12da81..f658dbee16 100644 --- a/osu.Game.Tests/Visual/TestCasePause.cs +++ b/osu.Game.Tests/Visual/TestCasePause.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; @@ -26,7 +27,6 @@ namespace osu.Game.Tests.Visual PausePlayer pausable() => (PausePlayer)player(); base.AddCheckSteps(player); - //AddUntilStep(() => pausable().ScoreProcessor.TotalScore.Value > 0, "score above zero"); AddStep("pause", () => pausable().Pause()); AddAssert("clock stopped", () => !pausable().GameplayClockContainer.GameplayClock.IsRunning); @@ -47,6 +47,17 @@ namespace osu.Game.Tests.Visual AddAssert("pause overlay hidden", () => !pausable().PauseOverlayVisible); AddAssert("fail overlay still shown", () => pausable().FailOverlayVisible); + + AddStep("restart", () => pausable().Restart()); + + AddUntilStep(() => + { + pausable().Pause(); + return pausable().PauseOverlayVisible; + }, "keep trying to pause"); + + AddStep("exit", () => pausable().Exit()); + AddUntilStep(() => !pausable().IsCurrentScreen(), "player exited"); } private class PausePlayer : Player diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b53ed8ae17..162350e088 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -158,14 +158,14 @@ namespace osu.Game.Screens.Play }, FailOverlay = new FailOverlay { - OnRetry = restart, + OnRetry = Restart, OnQuit = performUserRequestedExit, }, PauseOverlay = new PauseOverlay { OnResume = Resume, Retries = RestartCount, - OnRetry = restart, + OnRetry = Restart, OnQuit = performUserRequestedExit, }, new HotkeyRetryOverlay @@ -175,7 +175,7 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; fadeOut(true); - restart(); + Restart(); }, } }; @@ -246,7 +246,7 @@ namespace osu.Game.Screens.Play this.Exit(); } - private void restart() + public void Restart() { if (!this.IsCurrentScreen()) return; From f56e8d9bfe26b9d7ca85111d6489f5d884985892 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 19:44:21 +0900 Subject: [PATCH 112/623] Make tests better --- osu.Game.Tests/Visual/TestCasePause.cs | 95 +++++++++++++++----------- 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCasePause.cs b/osu.Game.Tests/Visual/TestCasePause.cs index f658dbee16..f53177e86a 100644 --- a/osu.Game.Tests/Visual/TestCasePause.cs +++ b/osu.Game.Tests/Visual/TestCasePause.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; +using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Rulesets; @@ -11,56 +11,69 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { - public class TestCasePause : TestCasePlayer + public class TestCasePause : PlayerTestCase { + protected new PausePlayer Player => (PausePlayer)base.Player; + public TestCasePause() : base(new OsuRuleset()) { } + [Test] + public void TestPauseResume() + { + AddStep("pause", () => Player.Pause()); + AddAssert("clock stopped", () => !Player.GameplayClockContainer.GameplayClock.IsRunning); + AddAssert("pause overlay shown", () => Player.PauseOverlayVisible); + + AddStep("resume", () => Player.Resume()); + AddAssert("pause overlay hidden", () => !Player.PauseOverlayVisible); + } + + [Test] + public void TestPauseTooSoon() + { + AddStep("pause", () => Player.Pause()); + AddAssert("clock stopped", () => !Player.GameplayClockContainer.GameplayClock.IsRunning); + AddStep("resume", () => Player.Resume()); + AddAssert("clock started", () => Player.GameplayClockContainer.GameplayClock.IsRunning); + AddStep("pause too soon", () => Player.Pause()); + AddAssert("clock not stopped", () => Player.GameplayClockContainer.GameplayClock.IsRunning); + AddAssert("pause overlay hidden", () => !Player.PauseOverlayVisible); + } + + [Test] + public void TestPauseAfterFail() + { + AddUntilStep(() => Player.HasFailed, "wait for fail"); + + AddAssert("fail overlay shown", () => Player.FailOverlayVisible); + + AddStep("try to pause", () => Player.Pause()); + + AddAssert("pause overlay hidden", () => !Player.PauseOverlayVisible); + AddAssert("fail overlay still shown", () => Player.FailOverlayVisible); + } + + [Test] + public void TestExitFromPause() + { + AddUntilStep(() => + { + Player.Pause(); + return Player.PauseOverlayVisible; + }, "keep trying to pause"); + + AddStep("exit", () => Player.Exit()); + AddUntilStep(() => !Player.IsCurrentScreen(), "player exited"); + } + protected override bool AllowFail => true; protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer(); - protected override void AddCheckSteps(Func player) - { - PausePlayer pausable() => (PausePlayer)player(); - - base.AddCheckSteps(player); - - AddStep("pause", () => pausable().Pause()); - AddAssert("clock stopped", () => !pausable().GameplayClockContainer.GameplayClock.IsRunning); - AddAssert("pause overlay shown", () => pausable().PauseOverlayVisible); - - AddStep("resume", () => pausable().Resume()); - AddAssert("pause overlay hidden", () => !pausable().PauseOverlayVisible); - - AddStep("pause too soon", () => pausable().Pause()); - AddAssert("clock not stopped", () => pausable().GameplayClockContainer.GameplayClock.IsRunning); - AddAssert("pause overlay hidden", () => !pausable().PauseOverlayVisible); - - AddUntilStep(() => pausable().HasFailed, "wait for fail"); - - AddAssert("fail overlay shown", () => pausable().FailOverlayVisible); - - AddStep("try to pause", () => pausable().Pause()); - - AddAssert("pause overlay hidden", () => !pausable().PauseOverlayVisible); - AddAssert("fail overlay still shown", () => pausable().FailOverlayVisible); - - AddStep("restart", () => pausable().Restart()); - - AddUntilStep(() => - { - pausable().Pause(); - return pausable().PauseOverlayVisible; - }, "keep trying to pause"); - - AddStep("exit", () => pausable().Exit()); - AddUntilStep(() => !pausable().IsCurrentScreen(), "player exited"); - } - - private class PausePlayer : Player + protected class PausePlayer : Player { public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; From 27a92e017c99f6963604c24e0d85d457e24ec1ff Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 19 Mar 2019 13:06:14 +0900 Subject: [PATCH 113/623] rename back to UserDimContainer --- .../Visual/TestCaseBackgroundScreenBeatmap.cs | 26 +++++++++---------- ...ttingsContainer.cs => UserDimContainer.cs} | 20 +++++++------- .../Backgrounds/BackgroundScreenBeatmap.cs | 4 +-- osu.Game/Screens/Play/Player.cs | 4 +-- 4 files changed, 27 insertions(+), 27 deletions(-) rename osu.Game/Graphics/Containers/{VisualSettingsContainer.cs => UserDimContainer.cs} (80%) diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index 2eb479b505..4549f1663d 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual typeof(ScreenWithBeatmapBackground), typeof(PlayerLoader), typeof(Player), - typeof(VisualSettingsContainer), + typeof(UserDimContainer), typeof(OsuScreen) }; @@ -165,7 +165,7 @@ namespace osu.Game.Tests.Visual } /// - /// Check if the is properly accepting user-defined visual changes at all. + /// Check if the is properly accepting user-defined visual changes at all. /// [Test] public void DisableUserDimTest() @@ -327,9 +327,9 @@ namespace osu.Game.Tests.Visual { protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); - protected override VisualSettingsContainer CreateStoryboardContainer() + protected override UserDimContainer CreateStoryboardContainer() { - return new TestVisualSettingsContainer(true) + return new TestUserDimContainer(true) { RelativeSizeAxes = Axes.Both, Alpha = 1, @@ -339,7 +339,7 @@ namespace osu.Game.Tests.Visual public PausableGameplayContainer CurrentPausableGameplayContainer => PausableGameplayContainer; - public VisualSettingsContainer CurrentStoryboardContainer => StoryboardContainer; + public UserDimContainer CurrentStoryboardContainer => StoryboardContainer; // Whether or not the player should be allowed to load. public bool Ready; @@ -348,9 +348,9 @@ namespace osu.Game.Tests.Visual public readonly Bindable ReplacesBackground = new Bindable(); public readonly Bindable IsPaused = new Bindable(); - public bool IsStoryboardVisible() => ((TestVisualSettingsContainer)CurrentStoryboardContainer).CurrentAlpha == 1; + public bool IsStoryboardVisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha == 1; - public bool IsStoryboardInvisible() => ((TestVisualSettingsContainer)CurrentStoryboardContainer).CurrentAlpha <= 1; + public bool IsStoryboardInvisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha <= 1; [BackgroundDependencyLoader] private void load(OsuConfigManager config) @@ -396,7 +396,7 @@ namespace osu.Game.Tests.Visual private class FadeAccessibleBackground : BackgroundScreenBeatmap { - protected override VisualSettingsContainer CreateFadeContainer() => fadeContainer = new TestVisualSettingsContainer { RelativeSizeAxes = Axes.Both }; + protected override UserDimContainer CreateFadeContainer() => fadeContainer = new TestUserDimContainer { RelativeSizeAxes = Axes.Both }; public Color4 CurrentColour => fadeContainer.CurrentColour; @@ -404,7 +404,7 @@ namespace osu.Game.Tests.Visual public Vector2 CurrentBlur => Background.BlurSigma; - private TestVisualSettingsContainer fadeContainer; + private TestUserDimContainer fadeContainer; public FadeAccessibleBackground(WorkingBeatmap beatmap) : base(beatmap) @@ -412,12 +412,12 @@ namespace osu.Game.Tests.Visual } } - private class TestVisualSettingsContainer : VisualSettingsContainer + private class TestUserDimContainer : UserDimContainer { - public Color4 CurrentColour => LocalContainer.Colour; - public float CurrentAlpha => LocalContainer.Alpha; + public Color4 CurrentColour => DimContainer.Colour; + public float CurrentAlpha => DimContainer.Alpha; - public TestVisualSettingsContainer(bool isStoryboard = false) + public TestUserDimContainer(bool isStoryboard = false) : base(isStoryboard) { } diff --git a/osu.Game/Graphics/Containers/VisualSettingsContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs similarity index 80% rename from osu.Game/Graphics/Containers/VisualSettingsContainer.cs rename to osu.Game/Graphics/Containers/UserDimContainer.cs index 1ed4f0e23e..68c9c89226 100644 --- a/osu.Game/Graphics/Containers/VisualSettingsContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -16,7 +16,7 @@ namespace osu.Game.Graphics.Containers /// A container that applies user-configured visual settings to its contents. /// This container specifies behavior that applies to both Storyboards and Backgrounds. /// - public class VisualSettingsContainer : Container + public class UserDimContainer : Container { private const float background_fade_duration = 800; @@ -36,9 +36,9 @@ namespace osu.Game.Graphics.Containers /// public readonly Bindable StoryboardReplacesBackground = new Bindable(); - protected Container LocalContainer { get; } + protected Container DimContainer { get; } - protected override Container Content => LocalContainer; + protected override Container Content => DimContainer; private readonly bool isStoryboard; @@ -49,7 +49,7 @@ namespace osu.Game.Graphics.Containers : new Vector2(AddedBlur.Value); /// - /// Creates a new . + /// Creates a new . /// /// Whether or not this instance contains a storyboard. /// @@ -57,10 +57,10 @@ namespace osu.Game.Graphics.Containers /// and can cause backgrounds to become hidden via . Storyboards are also currently unable to be blurred. /// /// - public VisualSettingsContainer(bool isStoryboard = false) + public UserDimContainer(bool isStoryboard = false) { this.isStoryboard = isStoryboard; - AddInternal(LocalContainer = new Container { RelativeSizeAxes = Axes.Both }); + AddInternal(DimContainer = new Container { RelativeSizeAxes = Axes.Both }); } [BackgroundDependencyLoader] @@ -87,14 +87,14 @@ namespace osu.Game.Graphics.Containers { if (isStoryboard) { - LocalContainer.FadeTo(!showStoryboard.Value || dimLevel.Value == 1 ? 0 : 1, background_fade_duration, Easing.OutQuint); + DimContainer.FadeTo(!showStoryboard.Value || dimLevel.Value == 1 ? 0 : 1, background_fade_duration, Easing.OutQuint); } else { // The background needs to be hidden in the case of it being replaced by the storyboard - LocalContainer.FadeTo(showStoryboard.Value && StoryboardReplacesBackground.Value ? 0 : 1, background_fade_duration, Easing.OutQuint); + DimContainer.FadeTo(showStoryboard.Value && StoryboardReplacesBackground.Value ? 0 : 1, background_fade_duration, Easing.OutQuint); - foreach (Drawable c in LocalContainer) + foreach (Drawable c in DimContainer) { // Only blur if this container contains a background // We can't blur the container like we did with the dim because buffered containers add considerable draw overhead. @@ -103,7 +103,7 @@ namespace osu.Game.Graphics.Containers } } - LocalContainer.FadeColour(EnableVisualSettings.Value ? OsuColour.Gray(1 - (float)dimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint); + DimContainer.FadeColour(EnableVisualSettings.Value ? OsuColour.Gray(1 - (float)dimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 3e3315d03a..901d1aa2e5 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -24,9 +24,9 @@ namespace osu.Game.Screens.Backgrounds public readonly Bindable AddedBlur = new Bindable(); - private readonly VisualSettingsContainer fadeContainer; + private readonly UserDimContainer fadeContainer; - protected virtual VisualSettingsContainer CreateFadeContainer() => new VisualSettingsContainer { RelativeSizeAxes = Axes.Both }; + protected virtual UserDimContainer CreateFadeContainer() => new UserDimContainer { RelativeSizeAxes = Axes.Both }; public virtual WorkingBeatmap Beatmap { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 77f69eb80e..042711d820 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -71,9 +71,9 @@ namespace osu.Game.Screens.Play private FailOverlay failOverlay; private DrawableStoryboard storyboard; - protected VisualSettingsContainer StoryboardContainer { get; private set; } + protected UserDimContainer StoryboardContainer { get; private set; } - protected virtual VisualSettingsContainer CreateStoryboardContainer() => new VisualSettingsContainer(true) + protected virtual UserDimContainer CreateStoryboardContainer() => new UserDimContainer(true) { RelativeSizeAxes = Axes.Both, Alpha = 1, From df37973e842028b9bb834599343ab4ff151958f5 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 19 Mar 2019 13:13:19 +0900 Subject: [PATCH 114/623] Move showstoryboard into player --- osu.Game/Screens/Play/Player.cs | 7 +++++-- osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs | 8 -------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 042711d820..12977aaae3 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -73,6 +73,8 @@ namespace osu.Game.Screens.Play private DrawableStoryboard storyboard; protected UserDimContainer StoryboardContainer { get; private set; } + private Bindable showStoryboard; + protected virtual UserDimContainer CreateStoryboardContainer() => new UserDimContainer(true) { RelativeSizeAxes = Axes.Both, @@ -97,6 +99,7 @@ namespace osu.Game.Screens.Play sampleRestart = audio.Sample.Get(@"Gameplay/restart"); mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); + showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); ScoreProcessor = RulesetContainer.CreateScoreProcessor(); if (!ScoreProcessor.Mode.Disabled) @@ -169,7 +172,7 @@ namespace osu.Game.Screens.Play // bind clock into components that require it RulesetContainer.IsPaused.BindTo(gameplayClockContainer.IsPaused); - if (ShowStoryboard.Value) + if (showStoryboard.Value) initializeStoryboard(false); // Bind ScoreProcessor to ourselves @@ -313,7 +316,7 @@ namespace osu.Game.Screens.Play .Delay(250) .FadeIn(250); - ShowStoryboard.ValueChanged += enabled => + showStoryboard.ValueChanged += enabled => { if (enabled.NewValue) initializeStoryboard(true); }; diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs index 174e39f3cb..9eda1be818 100644 --- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs +++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs @@ -13,13 +13,5 @@ namespace osu.Game.Screens.Play protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); protected new BackgroundScreenBeatmap Background => (BackgroundScreenBeatmap)base.Background; - - protected Bindable ShowStoryboard; - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - ShowStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - } } } From dc26e90a8dad211abce21888944145f9b8add92a Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 19 Mar 2019 13:16:06 +0900 Subject: [PATCH 115/623] Remove unused usings --- osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs index 9eda1be818..d7d2c97598 100644 --- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs +++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs @@ -1,9 +1,6 @@ // Copyright (c) ppy Pty Ltd . 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.Game.Configuration; using osu.Game.Screens.Backgrounds; namespace osu.Game.Screens.Play From 1b696ade50b6b4c202f28aac0fc9fb1b9655084d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Mar 2019 14:10:39 +0900 Subject: [PATCH 116/623] Refactor to reduce code complexity --- .../Mods/CatchModHardRock.cs | 104 ++++++++++-------- 1 file changed, 61 insertions(+), 43 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index e96a9c7441..ad7e762f6e 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Catch.Mods public override double ScoreMultiplier => 1.12; public override bool Ranked => true; - private float lastStartX; - private int lastStartTime; + private float? lastPosition; + private double lastStartTime; public void ApplyToHitObject(HitObject hitObject) { @@ -26,69 +26,87 @@ namespace osu.Game.Rulesets.Catch.Mods var catchObject = (CatchHitObject)hitObject; float position = catchObject.X; - int startTime = (int)hitObject.StartTime; + double startTime = hitObject.StartTime; - if (lastStartX == 0) + if (lastPosition == null) { - lastStartX = position; + lastPosition = position; lastStartTime = startTime; + return; } - float diff = lastStartX - position; - int timeDiff = startTime - lastStartTime; + float positionDiff = lastPosition.Value - position; + double timeDiff = startTime - lastStartTime; if (timeDiff > 1000) { - lastStartX = position; + lastPosition = position; lastStartTime = startTime; return; } - if (diff == 0) + if (positionDiff == 0) { - bool right = RNG.NextBool(); - - float rand = Math.Min(20, (float)RNG.NextDouble(0, timeDiff / 4d)) / CatchPlayfield.BASE_WIDTH; - - if (right) - { - if (position + rand <= 1) - position += rand; - else - position -= rand; - } - else - { - if (position - rand >= 0) - position -= rand; - else - position += rand; - } - + applyRandomOffset(ref position, timeDiff / 4d); catchObject.X = position; - return; } - if (Math.Abs(diff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d) - { - if (diff > 0) - { - if (position - diff > 0) - position -= diff; - } - else - { - if (position - diff < 1) - position -= diff; - } - } + if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d) + applyOffset(ref position, positionDiff); catchObject.X = position; - lastStartX = position; + lastPosition = position; lastStartTime = startTime; } + + /// + /// Applies a random offset in a random direction to a position, ensuring that the final position remains within the boundary of the playfield. + /// + /// The position which the offset should be applied to. + /// The maximum offset, cannot exceed 20px. + private void applyRandomOffset(ref float position, double maxOffset) + { + bool right = RNG.NextBool(); + float rand = Math.Min(20, (float)RNG.NextDouble(0, maxOffset)) / CatchPlayfield.BASE_WIDTH; + + if (right) + { + // Clamp to the right bound + if (position + rand <= 1) + position += rand; + else + position -= rand; + } + else + { + // Clamp to the left bound + if (position - rand >= 0) + position -= rand; + else + position += rand; + } + } + + /// + /// Applies an offset to a position, ensuring that the final position remains within the boundary of the playfield. + /// + /// The position which the offset should be applied to. + /// The amount to offset by. + private void applyOffset(ref float position, float amount) + { + if (amount > 0) + { + if (position - amount > 0) + position -= amount; + } + else + { + if (position - amount < 1) + position -= amount; + } + } } } From 5b07cce3cb4cf888194f98d49de0517717aede12 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Mar 2019 14:12:10 +0900 Subject: [PATCH 117/623] Invert unintuitive variable --- osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index ad7e762f6e..275c9a500c 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.Mods return; } - float positionDiff = lastPosition.Value - position; + float positionDiff = position - lastPosition.Value; double timeDiff = startTime - lastStartTime; if (timeDiff > 1000) @@ -99,13 +99,15 @@ namespace osu.Game.Rulesets.Catch.Mods { if (amount > 0) { - if (position - amount > 0) - position -= amount; + // Clamp to the right bound + if (position + amount < 1) + position += amount; } else { - if (position - amount < 1) - position -= amount; + // Clamp to the left bound + if (position + amount > 0) + position += amount; } } } From 5d9477e1e4935df5357c86f30a260f79aef67526 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Mar 2019 15:35:14 +0900 Subject: [PATCH 118/623] Fix difficulty calculation using the pre-mod catcher size --- .../Difficulty/CatchDifficultyCalculator.cs | 8 +++++++- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index f3b88bd928..502aeac84d 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -23,11 +23,17 @@ namespace osu.Game.Rulesets.Catch.Difficulty protected override int SectionLength => 750; - private readonly float halfCatchWidth; + private float halfCatchWidth; public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { + } + + protected override void PreProcess(IBeatmap beatmap, Mod[] mods, double clockRate) + { + base.PreProcess(beatmap, mods, clockRate); + var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty); halfCatchWidth = catcher.CatchWidth * 0.5f; diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index db8bdde6bb..c404fc6909 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -63,6 +63,8 @@ namespace osu.Game.Rulesets.Difficulty private DifficultyAttributes calculate(IBeatmap beatmap, Mod[] mods, double clockRate) { + PreProcess(beatmap, mods, clockRate); + var skills = CreateSkills(beatmap); if (!beatmap.HitObjects.Any()) @@ -99,6 +101,13 @@ namespace osu.Game.Rulesets.Difficulty return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); } + /// + /// Computes any values to be used for difficulty calculation, prior to difficulty calculation taking place. + /// + protected virtual void PreProcess(IBeatmap beatmap, Mod[] mods, double clockRate) + { + } + /// /// Creates all combinations which adjust the difficulty. /// From 57727ac184443ea6c6336fc78830a969222bc77d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Mar 2019 15:53:27 +0900 Subject: [PATCH 119/623] Remove preprocess until a later point in time --- .../Difficulty/CatchDifficultyCalculator.cs | 26 +++++++++---------- .../Difficulty/DifficultyCalculator.cs | 9 ------- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 502aeac84d..a3d5165df5 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -23,24 +23,13 @@ namespace osu.Game.Rulesets.Catch.Difficulty protected override int SectionLength => 750; - private float halfCatchWidth; + private float? halfCatchWidth; public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } - protected override void PreProcess(IBeatmap beatmap, Mod[] mods, double clockRate) - { - base.PreProcess(beatmap, mods, clockRate); - - var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty); - halfCatchWidth = catcher.CatchWidth * 0.5f; - - // We're only using 80% of the catcher's width to simulate imperfect gameplay. - halfCatchWidth *= 0.8f; - } - protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { if (beatmap.HitObjects.Count == 0) @@ -60,6 +49,15 @@ namespace osu.Game.Rulesets.Catch.Difficulty protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { + if (halfCatchWidth == null) + { + var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty); + halfCatchWidth = catcher.CatchWidth * 0.5f; + + // We're only using 80% of the catcher's width to simulate imperfect gameplay. + halfCatchWidth *= 0.8f; + } + CatchHitObject lastObject = null; foreach (var hitObject in beatmap.HitObjects.OfType()) @@ -74,14 +72,14 @@ namespace osu.Game.Rulesets.Catch.Difficulty { // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations. case Fruit fruit: - yield return new CatchDifficultyHitObject(fruit, lastObject, clockRate, halfCatchWidth); + yield return new CatchDifficultyHitObject(fruit, lastObject, clockRate, halfCatchWidth.Value); lastObject = hitObject; break; case JuiceStream _: foreach (var nested in hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet))) { - yield return new CatchDifficultyHitObject(nested, lastObject, clockRate, halfCatchWidth); + yield return new CatchDifficultyHitObject(nested, lastObject, clockRate, halfCatchWidth.Value); lastObject = nested; } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index c404fc6909..db8bdde6bb 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -63,8 +63,6 @@ namespace osu.Game.Rulesets.Difficulty private DifficultyAttributes calculate(IBeatmap beatmap, Mod[] mods, double clockRate) { - PreProcess(beatmap, mods, clockRate); - var skills = CreateSkills(beatmap); if (!beatmap.HitObjects.Any()) @@ -101,13 +99,6 @@ namespace osu.Game.Rulesets.Difficulty return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); } - /// - /// Computes any values to be used for difficulty calculation, prior to difficulty calculation taking place. - /// - protected virtual void PreProcess(IBeatmap beatmap, Mod[] mods, double clockRate) - { - } - /// /// Creates all combinations which adjust the difficulty. /// From dd60e3f1c46e619b5cc454bdeb17897e3943f8da Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Mar 2019 15:59:04 +0900 Subject: [PATCH 120/623] Fix halfCatchWidth not being reset between runs --- .../Difficulty/CatchDifficultyCalculator.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index a3d5165df5..810a7bef97 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -23,8 +23,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty protected override int SectionLength => 750; - private float? halfCatchWidth; - public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { @@ -49,15 +47,15 @@ namespace osu.Game.Rulesets.Catch.Difficulty protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { - if (halfCatchWidth == null) - { - var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty); - halfCatchWidth = catcher.CatchWidth * 0.5f; + float halfCatchWidth; - // We're only using 80% of the catcher's width to simulate imperfect gameplay. - halfCatchWidth *= 0.8f; + using (var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty)) + { + halfCatchWidth = catcher.CatchWidth * 0.5f; + halfCatchWidth *= 0.8f; // We're only using 80% of the catcher's width to simulate imperfect gameplay. } + CatchHitObject lastObject = null; foreach (var hitObject in beatmap.HitObjects.OfType()) @@ -72,14 +70,14 @@ namespace osu.Game.Rulesets.Catch.Difficulty { // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations. case Fruit fruit: - yield return new CatchDifficultyHitObject(fruit, lastObject, clockRate, halfCatchWidth.Value); + yield return new CatchDifficultyHitObject(fruit, lastObject, clockRate, halfCatchWidth); lastObject = hitObject; break; case JuiceStream _: foreach (var nested in hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet))) { - yield return new CatchDifficultyHitObject(nested, lastObject, clockRate, halfCatchWidth.Value); + yield return new CatchDifficultyHitObject(nested, lastObject, clockRate, halfCatchWidth); lastObject = nested; } From d2007cfb3813bcdd836a2cb1e5fac518d5d72bda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Mar 2019 16:10:28 +0900 Subject: [PATCH 121/623] Fix weird transition --- osu.Game/Screens/Menu/Disclaimer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index af8c127a3c..9f0c75f0ad 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Menu currentUser.BindTo(api.LocalUser); currentUser.BindValueChanged(e => { - supportFlow.Children.ForEach(d => d.FadeOut(500, Easing.OutQuint).Expire()); + supportFlow.Children.ForEach(d => d.FadeOut().Expire()); if (e.NewValue.IsSupporter) { @@ -137,6 +137,9 @@ namespace osu.Game.Screens.Menu if (IsLoaded) animateHeart(); + + if (supportFlow.IsPresent) + supportFlow.FadeInFromZero(500); }, true); } From e36ad3eb6b8969279076994e01045cb55383485d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Mar 2019 16:11:22 +0900 Subject: [PATCH 122/623] Add initial alpha in case --- osu.Game/Screens/Menu/Disclaimer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 9f0c75f0ad..e6a90f76c0 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -84,6 +84,7 @@ namespace osu.Game.Screens.Menu TextAnchor = Anchor.TopCentre, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, + Alpha = 0, Spacing = new Vector2(0, 2), }, } From 0fbc049f8dbdc1832d33b40a8bff54ea2f0d20de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Mar 2019 16:25:34 +0900 Subject: [PATCH 123/623] Remove newline --- osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 810a7bef97..b4998347f4 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -55,7 +55,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty halfCatchWidth *= 0.8f; // We're only using 80% of the catcher's width to simulate imperfect gameplay. } - CatchHitObject lastObject = null; foreach (var hitObject in beatmap.HitObjects.OfType()) From 025a2661126a66335d0702a6b5bc5073182b48cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Mar 2019 17:02:03 +0900 Subject: [PATCH 124/623] Update framework --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c56d50ae15..c02207702c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index fedc20397d..2633da77b3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From def15645f78ca495f55d8f6d006e101786670524 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Mar 2019 17:24:26 +0900 Subject: [PATCH 125/623] Update framework --- .../TestCaseSliderInput.cs | 6 +++--- osu.Game.Tests/Visual/TestCaseAutoplay.cs | 4 ++-- .../Visual/TestCaseBackgroundScreenBeatmap.cs | 10 +++++----- .../Visual/TestCaseBeatmapCarousel.cs | 8 ++++---- .../Visual/TestCaseBeatmapInfoWedge.cs | 6 +++--- .../Visual/TestCaseChannelTabControl.cs | 4 ++-- osu.Game.Tests/Visual/TestCaseChatLink.cs | 2 +- .../Visual/TestCaseHoldForMenuButton.cs | 6 +++--- .../Visual/TestCaseHoldToConfirmOverlay.cs | 2 +- osu.Game.Tests/Visual/TestCaseIdleTracker.cs | 12 ++++++------ .../Visual/TestCaseLoaderAnimation.cs | 16 ++++++++-------- .../Visual/TestCaseMatchSettingsOverlay.cs | 2 +- osu.Game.Tests/Visual/TestCaseMods.cs | 10 +++++----- .../Visual/TestCasePlaySongSelect.cs | 18 +++++++++--------- osu.Game.Tests/Visual/TestCasePlayerLoader.cs | 8 ++++---- osu.Game.Tests/Visual/TestCaseReplay.cs | 4 ++-- .../Visual/TestCaseScreenBreadcrumbControl.cs | 2 +- osu.Game.Tests/Visual/TestCaseSongProgress.cs | 16 ++++++++-------- ...estCaseUpdateableBeatmapBackgroundSprite.cs | 8 ++++---- osu.Game.Tests/Visual/TestCaseUserProfile.cs | 2 +- osu.Game/Tests/Visual/TestCasePlayer.cs | 10 +++++----- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 23 files changed, 81 insertions(+), 81 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs index 57effe01f1..2f33982d41 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs @@ -354,9 +354,9 @@ namespace osu.Game.Rulesets.Osu.Tests judgementResults = new List(); }); - AddUntilStep(() => Beatmap.Value.Track.CurrentTime == 0, "Beatmap at 0"); - AddUntilStep(() => currentPlayer.IsCurrentScreen(), "Wait until player is loaded"); - AddUntilStep(() => allJudgedFired, "Wait for all judged"); + AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); + AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); + AddUntilStep("Wait for all judged", () => allJudgedFired); } private class ScoreAccessibleReplayPlayer : ReplayPlayer diff --git a/osu.Game.Tests/Visual/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/TestCaseAutoplay.cs index 61339a6af8..09a745c913 100644 --- a/osu.Game.Tests/Visual/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/TestCaseAutoplay.cs @@ -27,8 +27,8 @@ namespace osu.Game.Tests.Visual protected override void AddCheckSteps(Func player) { base.AddCheckSteps(player); - AddUntilStep(() => ((ScoreAccessiblePlayer)player()).ScoreProcessor.TotalScore.Value > 0, "score above zero"); - AddUntilStep(() => ((ScoreAccessiblePlayer)player()).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0), "key counter counted keys"); + AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)player()).ScoreProcessor.TotalScore.Value > 0); + AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)player()).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0)); } private class ScoreAccessiblePlayer : Player diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index 5484824c5b..81aba9bee5 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual { setupUserSettings(); AddStep("Start player loader", () => songSelect.Push(playerLoader = new DimAccessiblePlayerLoader(player = new DimAccessiblePlayer()))); - AddUntilStep(() => playerLoader?.IsLoaded ?? false, "Wait for Player Loader to load"); + AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false); AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent()); AddStep("Trigger background preview", () => { @@ -220,7 +220,7 @@ namespace osu.Game.Tests.Visual AddAssert("Screen is undimmed", () => songSelect.IsBackgroundUndimmed()); } - private void waitForDim() => AddWaitStep(5, "Wait for dim"); + private void waitForDim() => AddWaitStep("Wait for dim", 5); private void createFakeStoryboard() => AddStep("Create storyboard", () => { @@ -249,14 +249,14 @@ namespace osu.Game.Tests.Visual Ready = true, })); }); - AddUntilStep(() => playerLoader.IsLoaded, "Wait for Player Loader to load"); + AddUntilStep("Wait for Player Loader to load", () => playerLoader.IsLoaded); AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); - AddUntilStep(() => player.IsLoaded, "Wait for player to load"); + AddUntilStep("Wait for player to load", () => player.IsLoaded); } private void setupUserSettings() { - AddUntilStep(() => songSelect.Carousel.SelectedBeatmap != null, "Song select has selection"); + AddUntilStep("Song select has selection", () => songSelect.Carousel.SelectedBeatmap != null); AddStep("Set default user settings", () => { Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { new OsuModNoFail() }); diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index 618d8376c0..956d84618c 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual carousel.BeatmapSetsChanged = () => changed = true; carousel.BeatmapSets = beatmapSets; }); - AddUntilStep(() => changed, "Wait for load"); + AddUntilStep("Wait for load", () => changed); } private void ensureRandomFetchSuccess() => @@ -214,7 +214,7 @@ namespace osu.Game.Tests.Visual checkSelected(3, 2); AddStep("Un-filter (debounce)", () => carousel.Filter(new FilterCriteria())); - AddUntilStep(() => !carousel.PendingFilterTask, "Wait for debounce"); + AddUntilStep("Wait for debounce", () => !carousel.PendingFilterTask); checkVisibleItemCount(diff: false, count: set_count); checkVisibleItemCount(diff: true, count: 3); @@ -327,13 +327,13 @@ namespace osu.Game.Tests.Visual AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First())); checkSelected(1); - AddUntilStep(() => + AddUntilStep("Remove all", () => { if (!carousel.BeatmapSets.Any()) return true; carousel.RemoveBeatmapSet(carousel.BeatmapSets.Last()); return false; - }, "Remove all"); + }); checkNoSelection(); } diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs index 31bb8b64a3..0d77ac666b 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs @@ -56,11 +56,11 @@ namespace osu.Game.Tests.Visual // select part is redundant, but wait for load isn't selectBeatmap(Beatmap.Value.Beatmap); - AddWaitStep(3); + AddWaitStep("wait for select", 3); AddStep("hide", () => { infoWedge.State = Visibility.Hidden; }); - AddWaitStep(3); + AddWaitStep("wait for hide", 3); AddStep("show", () => { infoWedge.State = Visibility.Visible; }); @@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : new TestWorkingBeatmap(b); }); - AddUntilStep(() => infoWedge.Info != infoBefore, "wait for async load"); + AddUntilStep("wait for async load", () => infoWedge.Info != infoBefore); } private IBeatmap createTestBeatmap(RulesetInfo ruleset) diff --git a/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs b/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs index 749303b1bb..e90b5f5372 100644 --- a/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs +++ b/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual AddStep("set second channel", () => channelTabControl.Current.Value = channelTabControl.Items.Skip(1).First()); AddAssert("selector tab is inactive", () => !channelTabControl.ChannelSelectorActive.Value); - AddUntilStep(() => + AddUntilStep("remove all channels", () => { var first = channelTabControl.Items.First(); if (first.Name == "+") @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual channelTabControl.RemoveChannel(first); return false; - }, "remove all channels"); + }); AddAssert("selector tab is active", () => channelTabControl.ChannelSelectorActive.Value); } diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index b2ec2c9b47..ecab64ccf3 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual Scheduler.AddDelayed(() => newLine.Message = new DummyMessage(completeText ?? text), delay); }); - AddUntilStep(() => textContainer.All(line => line.Message is DummyMessage), $"wait for msg #{echoCounter}"); + AddUntilStep($"wait for msg #{echoCounter}", () => textContainer.All(line => line.Message is DummyMessage)); } } diff --git a/osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs b/osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs index 5ee1340044..a4fadbd3db 100644 --- a/osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs +++ b/osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs @@ -32,9 +32,9 @@ namespace osu.Game.Tests.Visual var text = holdForMenuButton.Children.OfType().First(); AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(holdForMenuButton)); - AddUntilStep(() => text.IsPresent && !exitAction, "Text visible"); + AddUntilStep("Text visible", () => text.IsPresent && !exitAction); AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One)); - AddUntilStep(() => !text.IsPresent && !exitAction, "Text is not visible"); + AddUntilStep("Text is not visible", () => !text.IsPresent && !exitAction); AddStep("Trigger exit action", () => { @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual AddAssert("action not triggered", () => !exitAction); AddStep("Trigger exit action", () => InputManager.PressButton(MouseButton.Left)); - AddUntilStep(() => exitAction, $"{nameof(holdForMenuButton.Action)} was triggered"); + AddUntilStep($"{nameof(holdForMenuButton.Action)} was triggered", () => exitAction); } } } diff --git a/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs b/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs index f66bf34875..c9a7e9c39f 100644 --- a/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual AddStep("start confirming", () => overlay.Begin()); - AddUntilStep(() => fired, "wait until confirmed"); + AddUntilStep("wait until confirmed", () => fired); } private class TestHoldToConfirmOverlay : ExitConfirmOverlay diff --git a/osu.Game.Tests/Visual/TestCaseIdleTracker.cs b/osu.Game.Tests/Visual/TestCaseIdleTracker.cs index 8e8c4e38ae..a7a1831ba7 100644 --- a/osu.Game.Tests/Visual/TestCaseIdleTracker.cs +++ b/osu.Game.Tests/Visual/TestCaseIdleTracker.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual { AddStep("move mouse to top left", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre)); - AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle"); + AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle); AddStep("nudge mouse", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre + new Vector2(1))); @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual AddAssert("check idle", () => !box3.IsIdle); AddAssert("check idle", () => !box4.IsIdle); - AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle"); + AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle); } [Test] @@ -96,13 +96,13 @@ namespace osu.Game.Tests.Visual AddStep("move mouse", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre)); AddAssert("check not idle", () => !box1.IsIdle && !box2.IsIdle && !box3.IsIdle && !box4.IsIdle); - AddUntilStep(() => box1.IsIdle, "Wait for idle"); + AddUntilStep("Wait for idle", () => box1.IsIdle); AddAssert("check not idle", () => !box2.IsIdle && !box3.IsIdle && !box4.IsIdle); - AddUntilStep(() => box2.IsIdle, "Wait for idle"); + AddUntilStep("Wait for idle", () => box2.IsIdle); AddAssert("check not idle", () => !box3.IsIdle && !box4.IsIdle); - AddUntilStep(() => box3.IsIdle, "Wait for idle"); + AddUntilStep("Wait for idle", () => box3.IsIdle); - AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle"); + AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle); } private class IdleTrackingBox : CompositeDrawable diff --git a/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs b/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs index 2088f97580..3803764194 100644 --- a/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs +++ b/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs @@ -25,30 +25,30 @@ namespace osu.Game.Tests.Visual bool logoVisible = false; AddStep("almost instant display", () => Child = loader = new TestLoader(250)); - AddUntilStep(() => + AddUntilStep("loaded", () => { logoVisible = loader.Logo?.Alpha > 0; return loader.Logo != null && loader.ScreenLoaded; - }, "loaded"); + }); AddAssert("logo not visible", () => !logoVisible); AddStep("short load", () => Child = loader = new TestLoader(800)); - AddUntilStep(() => + AddUntilStep("loaded", () => { logoVisible = loader.Logo?.Alpha > 0; return loader.Logo != null && loader.ScreenLoaded; - }, "loaded"); + }); AddAssert("logo visible", () => logoVisible); - AddUntilStep(() => loader.Logo?.Alpha == 0, "logo gone"); + AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0); AddStep("longer load", () => Child = loader = new TestLoader(1400)); - AddUntilStep(() => + AddUntilStep("loaded", () => { logoVisible = loader.Logo?.Alpha > 0; return loader.Logo != null && loader.ScreenLoaded; - }, "loaded"); + }); AddAssert("logo visible", () => logoVisible); - AddUntilStep(() => loader.Logo?.Alpha == 0, "logo gone"); + AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0); } private class TestLoader : Loader diff --git a/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs index a320fc88fa..11c7d3ef70 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual settings.ApplyButton.Action.Invoke(); }); - AddUntilStep(() => !settings.ErrorText.IsPresent, "error not displayed"); + AddUntilStep("error not displayed", () => !settings.ErrorText.IsPresent); } private class TestRoomSettings : MatchSettingsOverlay diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs index 99bc10d8cc..cb7e783bee 100644 --- a/osu.Game.Tests/Visual/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/TestCaseMods.cs @@ -208,22 +208,22 @@ namespace osu.Game.Tests.Visual { checkLabelColor(Color4.White); selectNext(mod); - AddWaitStep(1, "wait for changing colour"); + AddWaitStep("wait for changing colour", 1); checkLabelColor(colour); selectPrevious(mod); - AddWaitStep(1, "wait for changing colour"); + AddWaitStep("wait for changing colour", 1); checkLabelColor(Color4.White); } private void testRankedText(Mod mod) { - AddWaitStep(1, "wait for fade"); + AddWaitStep("wait for fade", 1); AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0); selectNext(mod); - AddWaitStep(1, "wait for fade"); + AddWaitStep("wait for fade", 1); AddAssert("check for unranked", () => modSelect.UnrankedLabel.Alpha != 0); selectPrevious(mod); - AddWaitStep(1, "wait for fade"); + AddWaitStep("wait for fade", 1); AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0); } diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index 5fa818472c..4a2cf24c6d 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -112,10 +112,10 @@ namespace osu.Game.Tests.Visual createSongSelect(); AddAssert("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap); - AddUntilStep(() => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap, "dummy shown on wedge"); + AddUntilStep("dummy shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap); addManyTestMaps(); - AddWaitStep(3); + AddWaitStep("wait for select", 3); AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); } @@ -125,7 +125,7 @@ namespace osu.Game.Tests.Visual { createSongSelect(); addManyTestMaps(); - AddWaitStep(3); + AddWaitStep("wait for add", 3); AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); @@ -142,7 +142,7 @@ namespace osu.Game.Tests.Visual createSongSelect(); changeRuleset(2); importForRuleset(0); - AddUntilStep(() => songSelect.Carousel.SelectedBeatmap == null, "no selection"); + AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null); } [Test] @@ -152,13 +152,13 @@ namespace osu.Game.Tests.Visual changeRuleset(2); importForRuleset(2); importForRuleset(1); - AddUntilStep(() => songSelect.Carousel.SelectedBeatmap.RulesetID == 2, "has selection"); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2); changeRuleset(1); - AddUntilStep(() => songSelect.Carousel.SelectedBeatmap.RulesetID == 1, "has selection"); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 1); changeRuleset(0); - AddUntilStep(() => songSelect.Carousel.SelectedBeatmap == null, "no selection"); + AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null); } [Test] @@ -196,7 +196,7 @@ namespace osu.Game.Tests.Visual { createSongSelect(); addManyTestMaps(); - AddUntilStep(() => songSelect.Carousel.SelectedBeatmap != null, "has selection"); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null); bool startRequested = false; @@ -225,7 +225,7 @@ namespace osu.Game.Tests.Visual private void createSongSelect() { AddStep("create song select", () => LoadScreen(songSelect = new TestSongSelect())); - AddUntilStep(() => songSelect.IsCurrentScreen(), "wait for present"); + AddUntilStep("wait for present", () => songSelect.IsCurrentScreen()); } private void addManyTestMaps() diff --git a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs index 244f553e97..2bc416f7f4 100644 --- a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs @@ -37,15 +37,15 @@ namespace osu.Game.Tests.Visual AllowResults = false, }))); - AddUntilStep(() => loader.IsCurrentScreen(), "wait for current"); + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); - AddUntilStep(() => !loader.IsCurrentScreen(), "wait for no longer current"); + AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); AddStep("exit loader", () => loader.Exit()); - AddUntilStep(() => !loader.IsAlive, "wait for no longer alive"); + AddUntilStep("wait for no longer alive", () => !loader.IsAlive); AddStep("load slow dummy beatmap", () => { @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual Scheduler.AddDelayed(() => slow.Ready = true, 5000); }); - AddUntilStep(() => !loader.IsCurrentScreen(), "wait for no longer current"); + AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); } protected class SlowLoadPlayer : Player diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs index c34190d567..5e23b21521 100644 --- a/osu.Game.Tests/Visual/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/TestCaseReplay.cs @@ -24,8 +24,8 @@ namespace osu.Game.Tests.Visual protected override void AddCheckSteps(Func player) { base.AddCheckSteps(player); - AddUntilStep(() => ((ScoreAccessibleReplayPlayer)player()).ScoreProcessor.TotalScore.Value > 0, "score above zero"); - AddUntilStep(() => ((ScoreAccessibleReplayPlayer)player()).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0), "key counter counted keys"); + AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)player()).ScoreProcessor.TotalScore.Value > 0); + AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)player()).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0)); } private class ScoreAccessibleReplayPlayer : ReplayPlayer diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs index 204f4a493d..dad684689e 100644 --- a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs +++ b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual } private void pushNext() => AddStep(@"push next screen", () => ((TestScreen)screenStack.CurrentScreen).PushNext()); - private void waitForCurrent() => AddUntilStep(() => screenStack.CurrentScreen.IsCurrentScreen(), "current screen"); + private void waitForCurrent() => AddUntilStep("current screen", () => screenStack.CurrentScreen.IsCurrentScreen()); private abstract class TestScreen : OsuScreen { diff --git a/osu.Game.Tests/Visual/TestCaseSongProgress.cs b/osu.Game.Tests/Visual/TestCaseSongProgress.cs index cdb1cd2286..511272a5ae 100644 --- a/osu.Game.Tests/Visual/TestCaseSongProgress.cs +++ b/osu.Game.Tests/Visual/TestCaseSongProgress.cs @@ -46,23 +46,23 @@ namespace osu.Game.Tests.Visual Origin = Anchor.TopLeft, }); - AddWaitStep(5); + AddWaitStep("wait some", 5); AddAssert("ensure not created", () => graph.CreationCount == 0); AddStep("display values", displayNewValues); - AddWaitStep(5); - AddUntilStep(() => graph.CreationCount == 1, "wait for creation count"); + AddWaitStep("wait some", 5); + AddUntilStep("wait for creation count", () => graph.CreationCount == 1); AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); - AddWaitStep(5); - AddUntilStep(() => graph.CreationCount == 1, "wait for creation count"); + AddWaitStep("wait some", 5); + AddUntilStep("wait for creation count", () => graph.CreationCount == 1); AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); - AddWaitStep(5); - AddUntilStep(() => graph.CreationCount == 1, "wait for creation count"); + AddWaitStep("wait some", 5); + AddUntilStep("wait for creation count", () => graph.CreationCount == 1); AddRepeatStep("New Values", displayNewValues, 5); - AddWaitStep(5); + AddWaitStep("wait some", 5); AddAssert("ensure debounced", () => graph.CreationCount == 2); } diff --git a/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs index ac90c264c4..0981b482a1 100644 --- a/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs @@ -36,18 +36,18 @@ namespace osu.Game.Tests.Visual api.Queue(req); AddStep("load null beatmap", () => beatmapBindable.Value = null); - AddUntilStep(() => backgroundSprite.ChildCount == 1, "wait for cleanup..."); + AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1); AddStep("load imported beatmap", () => beatmapBindable.Value = imported.Beatmaps.First()); - AddUntilStep(() => backgroundSprite.ChildCount == 1, "wait for cleanup..."); + AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1); if (api.IsLoggedIn) { - AddUntilStep(() => req.Result != null, "wait for api response"); + AddUntilStep("wait for api response", () => req.Result != null); AddStep("load online beatmap", () => beatmapBindable.Value = new BeatmapInfo { BeatmapSet = req.Result?.ToBeatmapSet(rulesets) }); - AddUntilStep(() => backgroundSprite.ChildCount == 1, "wait for cleanup..."); + AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1); } else { diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs index 46ee74b69f..aa0bd37449 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs @@ -108,7 +108,7 @@ namespace osu.Game.Tests.Visual private void checkSupporterTag(bool isSupporter) { - AddUntilStep(() => profile.Header.User != null, "wait for load"); + AddUntilStep("wait for load", () => profile.Header.User != null); if (isSupporter) AddAssert("is supporter", () => profile.Header.SupporterTag.Alpha == 1); else diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index 5ff798c40d..fed56ba4d1 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual AddStep(r.Name, () => p = loadPlayerFor(r)); AddCheckSteps(() => p); - AddUntilStep(() => + AddUntilStep("no leaked beatmaps", () => { p = null; @@ -65,9 +65,9 @@ namespace osu.Game.Tests.Visual workingWeakReferences.ForEachAlive(_ => count++); return count == 1; - }, "no leaked beatmaps"); + }); - AddUntilStep(() => + AddUntilStep("no leaked players", () => { GC.Collect(); GC.WaitForPendingFinalizers(); @@ -75,14 +75,14 @@ namespace osu.Game.Tests.Visual playerWeakReferences.ForEachAlive(_ => count++); return count == 1; - }, "no leaked players"); + }); } } } protected virtual void AddCheckSteps(Func player) { - AddUntilStep(() => player().IsLoaded, "player loaded"); + AddUntilStep("player loaded", () => player().IsLoaded); } protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c56d50ae15..c02207702c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index fedc20397d..2633da77b3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 8f5e02cbe55535d3745607af8714dcbaf28026ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Mar 2019 17:41:22 +0900 Subject: [PATCH 126/623] Fix a couple of missed cases --- osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs index d6a3361cf2..9e70df91b6 100644 --- a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual setState(Visibility.Hidden); AddRepeatStep(@"add many simple", sendManyNotifications, 3); - AddWaitStep(5); + AddWaitStep("wait some", 5); checkProgressingCount(0); @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual AddAssert("Displayed count is 33", () => manager.UnreadCount.Value == 33); - AddWaitStep(10); + AddWaitStep("wait some", 10); checkProgressingCount(0); From a8e20722866540391a71400e44e16bdae3ea24c3 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 19 Mar 2019 20:15:28 +0900 Subject: [PATCH 127/623] Make blurtarget private, improve documentation --- .../Visual/TestCaseBackgroundScreenBeatmap.cs | 2 +- .../Graphics/Containers/UserDimContainer.cs | 38 +++++++++++-------- .../Backgrounds/BackgroundScreenBeatmap.cs | 5 +-- osu.Game/Screens/Play/Player.cs | 2 +- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index 4549f1663d..756f16abc2 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -333,7 +333,7 @@ namespace osu.Game.Tests.Visual { RelativeSizeAxes = Axes.Both, Alpha = 1, - EnableVisualSettings = { Value = true } + EnableUserDim = { Value = true } }; } diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 68c9c89226..3593394495 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -20,31 +20,37 @@ namespace osu.Game.Graphics.Containers { private const float background_fade_duration = 800; - private Bindable dimLevel { get; set; } - - private Bindable blurLevel { get; set; } - - private Bindable showStoryboard { get; set; } - /// /// Whether or not user-configured dim levels should be applied to the container. /// - public readonly Bindable EnableVisualSettings = new Bindable(); + public readonly Bindable EnableUserDim = new Bindable(); /// /// Whether or not the storyboard loaded should completely hide the background behind it. /// public readonly Bindable StoryboardReplacesBackground = new Bindable(); + /// + /// The amount of blur to be applied to the background in addition to user-specified blur. + /// + /// + /// Used in contexts where there can potentially be both user and screen-specified blurring occuring at the same time, such as in + /// + public Bindable AddedBlur = new Bindable(); + + private Bindable dimLevel { get; set; } + + private Bindable blurLevel { get; set; } + + private Bindable showStoryboard { get; set; } + protected Container DimContainer { get; } protected override Container Content => DimContainer; private readonly bool isStoryboard; - public Bindable AddedBlur = new Bindable(); - - public Vector2 BlurTarget => EnableVisualSettings.Value + private Vector2 blurTarget => EnableUserDim.Value ? new Vector2(AddedBlur.Value + (float)blurLevel.Value * 25) : new Vector2(AddedBlur.Value); @@ -69,7 +75,7 @@ namespace osu.Game.Graphics.Containers dimLevel = config.GetBindable(OsuSetting.DimLevel); blurLevel = config.GetBindable(OsuSetting.BlurLevel); showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - EnableVisualSettings.ValueChanged += _ => UpdateVisuals(); + EnableUserDim.ValueChanged += _ => UpdateVisuals(); dimLevel.ValueChanged += _ => UpdateVisuals(); blurLevel.ValueChanged += _ => UpdateVisuals(); showStoryboard.ValueChanged += _ => UpdateVisuals(); @@ -83,7 +89,7 @@ namespace osu.Game.Graphics.Containers UpdateVisuals(); } - public void UpdateVisuals() + public void UpdateVisuals(bool instant = false) { if (isStoryboard) { @@ -97,13 +103,13 @@ namespace osu.Game.Graphics.Containers foreach (Drawable c in DimContainer) { // Only blur if this container contains a background - // We can't blur the container like we did with the dim because buffered containers add considerable draw overhead. - // As a result, this blurs the background directly. - ((Background)c)?.BlurTo(BlurTarget, background_fade_duration, Easing.OutQuint); + // We can't blur the container like we did with the dim because buffered containers add considerable draw overhead. As a result, this blurs the background directly. + // We need to support instant blurring here in the case of SongSelect, where blurring shouldn't be from 0 every time the beatmap is changed. + ((Background)c)?.BlurTo(blurTarget, instant ? 0 : background_fade_duration, Easing.OutQuint); } } - DimContainer.FadeColour(EnableVisualSettings.Value ? OsuColour.Gray(1 - (float)dimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint); + DimContainer.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)dimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 901d1aa2e5..1baa711f86 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -53,8 +53,7 @@ namespace osu.Game.Screens.Backgrounds b.Depth = newDepth; fadeContainer.Add(Background = b); - fadeContainer.UpdateVisuals(); - Background.BlurSigma = fadeContainer.BlurTarget; + fadeContainer.UpdateVisuals(true); StoryboardReplacesBackground.BindTo(fadeContainer.StoryboardReplacesBackground); })); }); @@ -65,7 +64,7 @@ namespace osu.Game.Screens.Backgrounds { Beatmap = beatmap; InternalChild = fadeContainer = CreateFadeContainer(); - fadeContainer.EnableVisualSettings.BindTo(EnableVisualSettings); + fadeContainer.EnableUserDim.BindTo(EnableVisualSettings); fadeContainer.AddedBlur.BindTo(AddedBlur); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 12977aaae3..fc24e55de2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -79,7 +79,7 @@ namespace osu.Game.Screens.Play { RelativeSizeAxes = Axes.Both, Alpha = 1, - EnableVisualSettings = { Value = true } + EnableUserDim = { Value = true } }; public bool LoadedBeatmapSuccessfully => RulesetContainer?.Objects.Any() == true; From 68f28326a235438c987ba7141879cecfbcddd28b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Mar 2019 20:21:31 +0900 Subject: [PATCH 128/623] Refactor RulesetContainer for readability --- .../UI/CatchRulesetContainer.cs | 2 +- .../UI/ManiaRulesetContainer.cs | 2 +- .../UI/OsuRulesetContainer.cs | 2 +- .../UI/TaikoRulesetContainer.cs | 2 +- osu.Game/Rulesets/UI/RulesetContainer.cs | 313 +++++++++--------- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 6 files changed, 157 insertions(+), 166 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs index f421969449..8ee8ec258e 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, GetVisualRepresentation); - public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); + protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); public override DrawableHitObject GetVisualRepresentation(CatchHitObject h) { diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index d8b7dc0381..03e262df5d 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Mania.UI public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns; - public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); + protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); public override DrawableHitObject GetVisualRepresentation(ManiaHitObject h) { diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs index 81482a9a01..88f7155b47 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override Playfield CreatePlayfield() => new OsuPlayfield(); - public override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); + protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); public override DrawableHitObject GetVisualRepresentation(OsuHitObject h) { diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs index 7a73f4bd2a..40e147df2c 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Taiko.UI public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); - public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); + protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo); diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index d8813631dc..960e05f669 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -25,44 +25,32 @@ using osu.Game.Replays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.UI { /// - /// Base RulesetContainer. Doesn't hold objects. + /// RulesetContainer that applies conversion to Beatmaps. Does not contain a Playfield + /// and does not load drawable hit objects. /// - /// Should not be derived - derive instead. + /// Should not be derived - derive instead. /// /// - public abstract class RulesetContainer : Container, IProvideCursor + /// The type of HitObject contained by this RulesetContainer. + public abstract class RulesetContainer : RulesetContainer, IProvideCursor, ICanAttachKeyCounter + where TObject : HitObject { /// /// The selected variant. /// public virtual int Variant => 0; - /// - /// The input manager for this RulesetContainer. - /// - internal IHasReplayHandler ReplayInputManager => KeyBindingInputManager as IHasReplayHandler; - /// /// The key conversion input manager for this RulesetContainer. /// public PassThroughInputManager KeyBindingInputManager; - /// - /// Whether a replay is currently loaded. - /// - public readonly BindableBool HasReplayLoaded = new BindableBool(); - - public abstract IEnumerable Objects { get; } - - /// - /// The point in time at which gameplay starts, including any required lead-in for display purposes. - /// Defaults to two seconds before the first . Override as necessary. - /// - public virtual double GameplayStartTime => Objects.First().StartTime - 2000; + public override double GameplayStartTime => Objects.First().StartTime - 2000; private readonly Lazy playfield; @@ -76,25 +64,34 @@ namespace osu.Game.Rulesets.UI /// public Container Overlays { get; protected set; } - public CursorContainer Cursor => Playfield.Cursor; + public override CursorContainer Cursor => Playfield.Cursor; public bool ProvidingUserCursor => Playfield.Cursor != null && !HasReplayLoaded.Value; protected override bool OnHover(HoverEvent e) => true; // required for IProvideCursor - public readonly Ruleset Ruleset; - protected IRulesetConfigManager Config { get; private set; } private OnScreenDisplay onScreenDisplay; /// - /// A visual representation of a . + /// Creates a ruleset visualisation for the provided ruleset and beatmap. /// /// The ruleset being repesented. - protected RulesetContainer(Ruleset ruleset) + /// The beatmap to create the hit renderer for. + protected RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap) + : base(ruleset) { - Ruleset = ruleset; + Debug.Assert(workingBeatmap != null, "RulesetContainer initialized with a null beatmap."); + + RelativeSizeAxes = Axes.Both; + + Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo); + + mods = workingBeatmap.Mods.Value; + applyBeatmapMods(mods); + + KeyBindingInputManager = CreateInputManager(); playfield = new Lazy(CreatePlayfield); IsPaused.ValueChanged += paused => @@ -106,6 +103,63 @@ namespace osu.Game.Rulesets.UI }; } + public override void SetReplayScore(Score replayScore) + { + if (!(KeyBindingInputManager is IHasReplayHandler replayInputManager)) + throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); + + var handler = (ReplayScore = replayScore) != null ? CreateReplayInputHandler(replayScore.Replay) : null; + + replayInputManager.ReplayInputHandler = handler; + frameStabilityContainer.ReplayInputHandler = handler; + + HasReplayLoaded.Value = replayInputManager.ReplayInputHandler != null; + + if (replayInputManager.ReplayInputHandler != null) + replayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; + } + + /// + /// Creates and adds drawable representations of hit objects to the play field. + /// + private void loadObjects() + { + foreach (TObject h in Beatmap.HitObjects) + addRepresentation(h); + + Playfield.PostProcess(); + + foreach (var mod in mods.OfType()) + mod.ApplyToDrawableHitObjects(Playfield.HitObjectContainer.Objects); + } + + /// + /// Creates and adds the visual representation of a to this . + /// + /// The to add the visual representation for. + private void addRepresentation(TObject hitObject) + { + var drawableObject = GetVisualRepresentation(hitObject); + + if (drawableObject == null) + return; + + drawableObject.OnNewResult += (_, r) => OnNewResult?.Invoke(r); + drawableObject.OnRevertResult += (_, r) => OnRevertResult?.Invoke(r); + + Playfield.Add(drawableObject); + } + + /// + /// Creates a DrawableHitObject from a HitObject. + /// + /// The HitObject to make drawable. + /// The DrawableHitObject. + public abstract DrawableHitObject GetVisualRepresentation(TObject h); + + public void Attach(KeyCounterCollection keyCounter) => + (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter); + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -122,48 +176,15 @@ namespace osu.Game.Rulesets.UI return dependencies; } - public abstract ScoreProcessor CreateScoreProcessor(); - /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. /// /// The input manager. - public abstract PassThroughInputManager CreateInputManager(); + protected abstract PassThroughInputManager CreateInputManager(); protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; - protected FrameStabilityContainer FrameStabilityContainer; - - public Score ReplayScore { get; private set; } - - /// - /// Whether the game is paused. Used to block user input. - /// - public readonly BindableBool IsPaused = new BindableBool(); - - /// - /// Sets a replay to be used, overriding local input. - /// - /// The replay, null for local input. - public virtual void SetReplayScore(Score replayScore) - { - if (ReplayInputManager == null) - throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); - - ReplayScore = replayScore; - - var handler = replayScore != null ? CreateReplayInputHandler(replayScore.Replay) : null; - - ReplayInputManager.ReplayInputHandler = handler; - FrameStabilityContainer.ReplayInputHandler = handler; - - HasReplayLoaded.Value = ReplayInputManager.ReplayInputHandler != null; - } - - /// - /// Creates the cursor. May be null if the doesn't provide a custom cursor. - /// - protected virtual CursorContainer CreateCursor() => null; + private FrameStabilityContainer frameStabilityContainer; /// /// Creates a Playfield. @@ -171,29 +192,6 @@ namespace osu.Game.Rulesets.UI /// The Playfield. protected abstract Playfield CreatePlayfield(); - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (Config != null) - { - onScreenDisplay?.StopTracking(this, Config); - Config = null; - } - } - } - - /// - /// RulesetContainer that applies conversion to Beatmaps. Does not contain a Playfield - /// and does not load drawable hit objects. - /// - /// Should not be derived - derive instead. - /// - /// - /// The type of HitObject contained by this RulesetContainer. - public abstract class RulesetContainer : RulesetContainer - where TObject : HitObject - { /// /// Invoked when a has been applied by a . /// @@ -205,69 +203,30 @@ namespace osu.Game.Rulesets.UI public event Action OnRevertResult; /// - /// The Beatmap + /// The beatmap. /// public Beatmap Beatmap; - /// - /// All the converted hit objects contained by this hit renderer. - /// public override IEnumerable Objects => Beatmap.HitObjects; /// /// The mods which are to be applied. /// - protected IEnumerable Mods; - - /// - /// The this was created with. - /// - protected readonly WorkingBeatmap WorkingBeatmap; + private readonly IEnumerable mods; public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); - protected override Container Content => content; - private Container content; - - /// - /// Whether to assume the beatmap passed into this is for the current ruleset. - /// Creates a hit renderer for a beatmap. - /// - /// The ruleset being repesented. - /// The beatmap to create the hit renderer for. - protected RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap) - : base(ruleset) - { - Debug.Assert(workingBeatmap != null, "RulesetContainer initialized with a null beatmap."); - - WorkingBeatmap = workingBeatmap; - // ReSharper disable once PossibleNullReferenceException - Mods = workingBeatmap.Mods.Value; - - RelativeSizeAxes = Axes.Both; - - Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo); - - KeyBindingInputManager = CreateInputManager(); - - applyBeatmapMods(Mods); - } - [BackgroundDependencyLoader] private void load(OsuConfigManager config) { KeyBindingInputManager.AddRange(new Drawable[] { - content = new Container - { - RelativeSizeAxes = Axes.Both, - }, Playfield }); InternalChildren = new Drawable[] { - FrameStabilityContainer = new FrameStabilityContainer + frameStabilityContainer = new FrameStabilityContainer { Child = KeyBindingInputManager, }, @@ -275,7 +234,7 @@ namespace osu.Game.Rulesets.UI }; // Apply mods - applyRulesetMods(Mods, config); + applyRulesetMods(mods, config); loadObjects(); } @@ -309,51 +268,83 @@ namespace osu.Game.Rulesets.UI mod.ReadFromConfig(config); } - public override void SetReplayScore(Score replayScore) + protected override void Dispose(bool isDisposing) { - base.SetReplayScore(replayScore); + base.Dispose(isDisposing); - if (ReplayInputManager?.ReplayInputHandler != null) - ReplayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; + if (Config != null) + { + onScreenDisplay?.StopTracking(this, Config); + Config = null; + } + } + } + + /// + /// Base RulesetContainer. Doesn't hold objects. + /// + /// Should not be derived - derive instead. + /// + /// + public abstract class RulesetContainer : CompositeDrawable + { + /// + /// Whether a replay is currently loaded. + /// + public readonly BindableBool HasReplayLoaded = new BindableBool(); + + /// + /// Whether the game is paused. Used to block user input. + /// + public readonly BindableBool IsPaused = new BindableBool(); + + /// + /// The associated ruleset. + /// + public readonly Ruleset Ruleset; + + /// + /// Creates a ruleset visualisation for the provided ruleset. + /// + /// The ruleset. + protected RulesetContainer(Ruleset ruleset) + { + Ruleset = ruleset; } /// - /// Creates and adds drawable representations of hit objects to the play field. + /// All the converted hit objects contained by this hit renderer. /// - private void loadObjects() - { - foreach (TObject h in Beatmap.HitObjects) - AddRepresentation(h); - - Playfield.PostProcess(); - - foreach (var mod in Mods.OfType()) - mod.ApplyToDrawableHitObjects(Playfield.HitObjectContainer.Objects); - } + public abstract IEnumerable Objects { get; } /// - /// Creates and adds the visual representation of a to this . + /// The point in time at which gameplay starts, including any required lead-in for display purposes. + /// Defaults to two seconds before the first . Override as necessary. /// - /// The to add the visual representation for. - internal void AddRepresentation(TObject hitObject) - { - var drawableObject = GetVisualRepresentation(hitObject); - - if (drawableObject == null) - return; - - drawableObject.OnNewResult += (_, r) => OnNewResult?.Invoke(r); - drawableObject.OnRevertResult += (_, r) => OnRevertResult?.Invoke(r); - - Playfield.Add(drawableObject); - } + public abstract double GameplayStartTime { get; } /// - /// Creates a DrawableHitObject from a HitObject. + /// The currently loaded replay. Usually null in the case of a local player. /// - /// The HitObject to make drawable. - /// The DrawableHitObject. - public abstract DrawableHitObject GetVisualRepresentation(TObject h); + public Score ReplayScore { get; protected set; } + + /// + /// The cursor being displayed by the . May be null if no cursor is provided. + /// + public abstract CursorContainer Cursor { get; } + + /// + /// Sets a replay to be used, overriding local input. + /// + /// The replay, null for local input. + public abstract void SetReplayScore(Score replayScore); + + /// + /// Create a for the associated ruleset and link with this + /// . + /// + /// A score processor. + public abstract ScoreProcessor CreateScoreProcessor(); } /// @@ -371,7 +362,7 @@ namespace osu.Game.Rulesets.UI protected new TPlayfield Playfield => (TPlayfield)base.Playfield; /// - /// Creates a hit renderer for a beatmap. + /// Creates a ruleset visualisation for the provided ruleset and beatmap. /// /// The ruleset being repesented. /// The beatmap to create the hit renderer for. diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 4fd0572c1a..1f6a72e726 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -145,7 +145,7 @@ namespace osu.Game.Screens.Play protected virtual void BindRulesetContainer(RulesetContainer rulesetContainer) { - (rulesetContainer.KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(KeyCounter); + (rulesetContainer as ICanAttachKeyCounter)?.Attach(KeyCounter); replayLoaded.BindTo(rulesetContainer.HasReplayLoaded); From 3af3baf5e61eca0475ddf5c085f0a7f9cbf82815 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 19 Mar 2019 20:22:21 +0900 Subject: [PATCH 129/623] Fix merge --- osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index 8967e95782..263cfe0fc8 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual public void PlayerLoaderSettingsHoverTest() { setupUserSettings(); - AddStep("Start player loader", () => songSelect.Push(playerLoader = new DimAccessiblePlayerLoader(player = new DimAccessiblePlayer()))); + AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer()))); AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false); AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent()); AddStep("Trigger background preview", () => @@ -205,7 +205,7 @@ namespace osu.Game.Tests.Visual performFullSetup(); var results = new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } }); AddStep("Transition to Results", () => player.Push(results)); - AddUntilStep(results.IsCurrentScreen, "Wait for results is current"); + AddUntilStep("Wait for results is current", results.IsCurrentScreen); waitForDim(); AddAssert("Screen is undimmed, original background retained", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect()); From baea7230bc581277d5d94302c7f0cc8f9ea925ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Mar 2019 23:44:15 +0900 Subject: [PATCH 130/623] Rename RulesetContainer to DrawableRuleset --- .../TestCaseBananaShower.cs | 2 +- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- .../Mods/CatchModFlashlight.cs | 6 +-- .../Scoring/CatchScoreProcessor.cs | 4 +- ...etContainer.cs => DrawableCatchRuleset.cs} | 4 +- ...ntainer.cs => DrawableManiaEditRuleset.cs} | 4 +- .../Edit/ManiaHitObjectComposer.cs | 14 +++---- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../Scoring/ManiaScoreProcessor.cs | 4 +- ...etContainer.cs => DrawableManiaRuleset.cs} | 4 +- ...Container.cs => DrawableOsuEditRuleset.cs} | 4 +- .../Edit/OsuHitObjectComposer.cs | 4 +- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 6 +-- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 6 +-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../Scoring/OsuScoreProcessor.cs | 4 +- ...esetContainer.cs => DrawableOsuRuleset.cs} | 4 +- .../TestCaseTaikoPlayfield.cs | 32 +++++++------- .../Mods/TaikoModFlashlight.cs | 6 +-- .../Scoring/TaikoScoreProcessor.cs | 4 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- ...etContainer.cs => DrawableTaikoRuleset.cs} | 4 +- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- .../Visual/TestCaseBackgroundScreenBeatmap.cs | 2 +- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 2 +- .../Rulesets/Edit/EditRulesetContainer.cs | 32 +++++++------- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 32 +++++++------- .../Mods/IApplicableToRulesetContainer.cs | 10 ++--- osu.Game/Rulesets/Mods/ModAutoplay.cs | 4 +- osu.Game/Rulesets/Mods/ModFlashlight.cs | 8 ++-- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 10 ++--- ...RulesetContainer.cs => DrawableRuleset.cs} | 42 +++++++++---------- ...ntainer.cs => DrawableScrollingRuleset.cs} | 10 ++--- osu.Game/Screens/Edit/Editor.cs | 2 +- .../Screens/Multi/Components/ModeTypeInfo.cs | 10 ++--- osu.Game/Screens/Play/HUDOverlay.cs | 16 +++---- osu.Game/Screens/Play/Player.cs | 36 ++++++++-------- osu.Game/Screens/Play/ReplayPlayer.cs | 2 +- osu.Game/Screens/Play/SongProgress.cs | 4 +- 40 files changed, 175 insertions(+), 175 deletions(-) rename osu.Game.Rulesets.Catch/UI/{CatchRulesetContainer.cs => DrawableCatchRuleset.cs} (93%) rename osu.Game.Rulesets.Mania/Edit/{ManiaEditRulesetContainer.cs => DrawableManiaEditRuleset.cs} (83%) rename osu.Game.Rulesets.Mania/UI/{ManiaRulesetContainer.cs => DrawableManiaRuleset.cs} (96%) rename osu.Game.Rulesets.Osu/Edit/{OsuEditRulesetContainer.cs => DrawableOsuEditRuleset.cs} (82%) rename osu.Game.Rulesets.Osu/UI/{OsuRulesetContainer.cs => DrawableOsuRuleset.cs} (92%) rename osu.Game.Rulesets.Taiko/UI/{TaikoRulesetContainer.cs => DrawableTaikoRuleset.cs} (95%) rename osu.Game/Rulesets/UI/{RulesetContainer.cs => DrawableRuleset.cs} (89%) rename osu.Game/Rulesets/UI/Scrolling/{ScrollingRulesetContainer.cs => DrawableScrollingRuleset.cs} (92%) diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs index 9e1c44ba40..83608f7e68 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(DrawableBananaShower), typeof(CatchRuleset), - typeof(CatchRulesetContainer), + typeof(DrawableCatchRuleset), }; public TestCaseBananaShower() diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index af8206d95a..5140135f80 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch { public class CatchRuleset : Ruleset { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new CatchRulesetContainer(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableCatchRuleset(this, beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 82cda7df47..77f41affee 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -21,10 +21,10 @@ namespace osu.Game.Rulesets.Catch.Mods private CatchPlayfield playfield; - public override void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public override void ApplyToDrawableRuleset(DrawableRuleset rrawableRuleset) { - playfield = (CatchPlayfield)rulesetContainer.Playfield; - base.ApplyToRulesetContainer(rulesetContainer); + playfield = (CatchPlayfield)rrawableRuleset.Playfield; + base.ApplyToDrawableRuleset(rrawableRuleset); } private class CatchFlashlight : Flashlight diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index e1fda1a7b3..05ee4fc52d 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Catch.Scoring { public class CatchScoreProcessor : ScoreProcessor { - public CatchScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) + public CatchScoreProcessor(DrawableRuleset rrawableRuleset) + : base(rrawableRuleset) { } diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs similarity index 93% rename from osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs rename to osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 8ee8ec258e..63220e9379 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -17,13 +17,13 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Catch.UI { - public class CatchRulesetContainer : ScrollingRulesetContainer + public class DrawableCatchRuleset : DrawableScrollingRuleset { protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Constant; protected override bool UserScrollSpeedAdjustment => false; - public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { Direction.Value = ScrollingDirection.Down; diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs similarity index 83% rename from osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs rename to osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs index 89e531fd9f..acafaffee6 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs @@ -10,11 +10,11 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.Edit { - public class ManiaEditRulesetContainer : ManiaRulesetContainer + public class DrawableManiaEditRuleset : DrawableManiaRuleset { public new IScrollingInfo ScrollingInfo => base.ScrollingInfo; - public ManiaEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 3dbbd132a6..56c9471462 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Edit [Cached(Type = typeof(IManiaHitObjectComposer))] public class ManiaHitObjectComposer : HitObjectComposer, IManiaHitObjectComposer { - protected new ManiaEditRulesetContainer RulesetContainer { get; private set; } + protected new DrawableManiaEditRuleset DrawableRuleset { get; private set; } public ManiaHitObjectComposer(Ruleset ruleset) : base(ruleset) @@ -32,23 +32,23 @@ namespace osu.Game.Rulesets.Mania.Edit /// /// The screen-space position. /// The column which intersects with . - public Column ColumnAt(Vector2 screenSpacePosition) => RulesetContainer.GetColumnByPosition(screenSpacePosition); + public Column ColumnAt(Vector2 screenSpacePosition) => DrawableRuleset.GetColumnByPosition(screenSpacePosition); private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - public int TotalColumns => ((ManiaPlayfield)RulesetContainer.Playfield).TotalColumns; + public int TotalColumns => ((ManiaPlayfield)DrawableRuleset.Playfield).TotalColumns; - protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap) { - RulesetContainer = new ManiaEditRulesetContainer(ruleset, beatmap); + DrawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap); // This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it - dependencies.CacheAs(RulesetContainer.ScrollingInfo); + dependencies.CacheAs(DrawableRuleset.ScrollingInfo); - return RulesetContainer; + return DrawableRuleset; } protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 2b6b7377ae..a4a10f1742 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania { public class ManiaRuleset : Ruleset { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableManiaRuleset(this, beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index cf3d0734fb..018f257d8f 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -92,8 +92,8 @@ namespace osu.Game.Rulesets.Mania.Scoring { } - public ManiaScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) + public ManiaScoreProcessor(DrawableRuleset rrawableRuleset) + : base(rrawableRuleset) { } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs similarity index 96% rename from osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs rename to osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 03e262df5d..5090dd491e 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -28,7 +28,7 @@ using osuTK; namespace osu.Game.Rulesets.Mania.UI { - public class ManiaRulesetContainer : ScrollingRulesetContainer + public class DrawableManiaRuleset : DrawableScrollingRuleset { public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Bindable configDirection = new Bindable(); - public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { // Generate the bar lines diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs similarity index 82% rename from osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs rename to osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 7886a2393c..50b3eabcf4 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -8,9 +8,9 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit { - public class OsuEditRulesetContainer : OsuRulesetContainer + public class DrawableOsuEditRuleset : DrawableOsuRuleset { - public OsuEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 174321b8b9..dd3925e04f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -26,8 +26,8 @@ namespace osu.Game.Rulesets.Osu.Edit { } - protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) - => new OsuEditRulesetContainer(ruleset, beatmap); + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap) + => new DrawableOsuEditRuleset(ruleset, beatmap); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index a203e23687..57536ec387 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -18,7 +18,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModBlinds : Mod, IApplicableToRulesetContainer, IApplicableToScoreProcessor + public class OsuModBlinds : Mod, IApplicableToDrawableRuleset, IApplicableToScoreProcessor { public override string Name => "Blinds"; public override string Description => "Play with blinds on your screen."; @@ -32,9 +32,9 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1.12; private DrawableOsuBlinds blinds; - public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public void ApplyToDrawableRuleset(DrawableRuleset rrawableRuleset) { - rulesetContainer.Overlays.Add(blinds = new DrawableOsuBlinds(rulesetContainer.Playfield.HitObjectContainer, rulesetContainer.Beatmap)); + rrawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(rrawableRuleset.Playfield.HitObjectContainer, rrawableRuleset.Beatmap)); } public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index efcab28310..a1c96fd44f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -13,7 +13,7 @@ using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModRelax : ModRelax, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToRulesetContainer + public class OsuModRelax : ModRelax, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); @@ -79,10 +79,10 @@ namespace osu.Game.Rulesets.Osu.Mods state.Apply(osuInputManager.CurrentState, osuInputManager); } - public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public void ApplyToDrawableRuleset(DrawableRuleset rrawableRuleset) { // grab the input manager for future use. - osuInputManager = (OsuInputManager)rulesetContainer.KeyBindingInputManager; + osuInputManager = (OsuInputManager)rrawableRuleset.KeyBindingInputManager; osuInputManager.AllowUserPresses = false; } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index d9c046a579..7d3aff7801 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu { public class OsuRuleset : Ruleset { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new OsuRulesetContainer(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableOsuRuleset(this, beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 4f97cc0da5..4186c515db 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Scoring { internal class OsuScoreProcessor : ScoreProcessor { - public OsuScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) + public OsuScoreProcessor(DrawableRuleset rrawableRuleset) + : base(rrawableRuleset) { } diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs similarity index 92% rename from osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs rename to osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 88f7155b47..d91ccdedd5 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -17,11 +17,11 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.UI { - public class OsuRulesetContainer : RulesetContainer + public class DrawableOsuRuleset : DrawableRuleset { protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config; - public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + public DrawableOsuRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs index 00e1b649d9..369cdd49d2 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Tests protected override double TimePerAction => default_duration * 2; private readonly Random rng = new Random(1337); - private TaikoRulesetContainer rulesetContainer; + private DrawableTaikoRuleset drawableRuleset; private Container playfieldContainer; [BackgroundDependencyLoader] @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Height = 768, - Children = new[] { rulesetContainer = new TaikoRulesetContainer(new TaikoRuleset(), beatmap) } + Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap) } }); } @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); } private void addStrongHitJudgement(bool kiai) @@ -154,33 +154,33 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); - ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new TaikoStrongJudgement()) { Type = HitResult.Great }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new TaikoStrongJudgement()) { Type = HitResult.Great }); } private void addMissJudgement() { - ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new TaikoJudgement()) { Type = HitResult.Miss }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new TaikoJudgement()) { Type = HitResult.Miss }); } private void addBarLine(bool major, double delay = scroll_time) { - BarLine bl = new BarLine { StartTime = rulesetContainer.Playfield.Time.Current + delay }; + BarLine bl = new BarLine { StartTime = drawableRuleset.Playfield.Time.Current + delay }; - rulesetContainer.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); + drawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); } private void addSwell(double duration = default_duration) { var swell = new Swell { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, Duration = duration, }; swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - rulesetContainer.Playfield.Add(new DrawableSwell(swell)); + drawableRuleset.Playfield.Add(new DrawableSwell(swell)); } private void addDrumRoll(bool strong, double duration = default_duration) @@ -190,40 +190,40 @@ namespace osu.Game.Rulesets.Taiko.Tests var d = new DrumRoll { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong, Duration = duration, }; d.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - rulesetContainer.Playfield.Add(new DrawableDrumRoll(d)); + drawableRuleset.Playfield.Add(new DrawableDrumRoll(d)); } private void addCentreHit(bool strong) { Hit h = new Hit { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong }; h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - rulesetContainer.Playfield.Add(new DrawableCentreHit(h)); + drawableRuleset.Playfield.Add(new DrawableCentreHit(h)); } private void addRimHit(bool strong) { Hit h = new Hit { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong }; h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - rulesetContainer.Playfield.Add(new DrawableRimHit(h)); + drawableRuleset.Playfield.Add(new DrawableRimHit(h)); } private class TestStrongNestedHit : DrawableStrongNestedHit diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index b99ec57166..c6342f9c34 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -22,10 +22,10 @@ namespace osu.Game.Rulesets.Taiko.Mods private TaikoPlayfield playfield; - public override void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public override void ApplyToDrawableRuleset(DrawableRuleset rrawableRuleset) { - playfield = (TaikoPlayfield)rulesetContainer.Playfield; - base.ApplyToRulesetContainer(rulesetContainer); + playfield = (TaikoPlayfield)rrawableRuleset.Playfield; + base.ApplyToDrawableRuleset(rrawableRuleset); } private class TaikoFlashlight : Flashlight diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 73cd9ba821..762e24326a 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -32,8 +32,8 @@ namespace osu.Game.Rulesets.Taiko.Scoring /// private double hpMissMultiplier; - public TaikoScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) + public TaikoScoreProcessor(DrawableRuleset rrawableRuleset) + : base(rrawableRuleset) { } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 08a56488aa..3e94775eb6 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko { public class TaikoRuleset : Ruleset { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new TaikoRulesetContainer(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableTaikoRuleset(this, beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap); public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs similarity index 95% rename from osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs rename to osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 40e147df2c..54a54a54bd 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -20,13 +20,13 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Taiko.UI { - public class TaikoRulesetContainer : ScrollingRulesetContainer + public class DrawableTaikoRuleset : DrawableScrollingRuleset { protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping; protected override bool UserScrollSpeedAdjustment => false; - public TaikoRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + public DrawableTaikoRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { Direction.Value = ScrollingDirection.Left; diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index cb527adb98..35b941b52b 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class TaikoPlayfield : ScrollingPlayfield { /// - /// Default height of a when inside a . + /// Default height of a when inside a . /// public const float DEFAULT_HEIGHT = 178; diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index 81aba9bee5..32b6195336 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -350,7 +350,7 @@ namespace osu.Game.Tests.Visual Thread.Sleep(1); StoryboardEnabled = config.GetBindable(OsuSetting.ShowStoryboard); ReplacesBackground.BindTo(Background.StoryboardReplacesBackground); - RulesetContainer.IsPaused.BindTo(IsPaused); + DrawableRuleset.IsPaused.BindTo(IsPaused); } } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index f9df025be8..73aa12a3db 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -52,7 +52,7 @@ namespace osu.Game.Beatmaps { public override IEnumerable GetModsFor(ModType type) => new Mod[] { }; - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) { throw new NotImplementedException(); } diff --git a/osu.Game/Rulesets/Edit/EditRulesetContainer.cs b/osu.Game/Rulesets/Edit/EditRulesetContainer.cs index 8992be2da2..a454f72e11 100644 --- a/osu.Game/Rulesets/Edit/EditRulesetContainer.cs +++ b/osu.Game/Rulesets/Edit/EditRulesetContainer.cs @@ -11,14 +11,14 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Edit { - public abstract class EditRulesetContainer : CompositeDrawable + public abstract class EditDrawableRuleset : CompositeDrawable { /// - /// The contained by this . + /// The contained by this . /// public abstract Playfield Playfield { get; } - internal EditRulesetContainer() + internal EditDrawableRuleset() { RelativeSizeAxes = Axes.Both; } @@ -38,21 +38,21 @@ namespace osu.Game.Rulesets.Edit internal abstract DrawableHitObject Remove(HitObject hitObject); } - public class EditRulesetContainer : EditRulesetContainer + public class EditDrawableRuleset : EditDrawableRuleset where TObject : HitObject { - public override Playfield Playfield => rulesetContainer.Playfield; + public override Playfield Playfield => rrawableRuleset.Playfield; - private Ruleset ruleset => rulesetContainer.Ruleset; - private Beatmap beatmap => rulesetContainer.Beatmap; + private Ruleset ruleset => rrawableRuleset.Ruleset; + private Beatmap beatmap => rrawableRuleset.Beatmap; - private readonly RulesetContainer rulesetContainer; + private readonly DrawableRuleset rrawableRuleset; - public EditRulesetContainer(RulesetContainer rulesetContainer) + public EditDrawableRuleset(DrawableRuleset rrawableRuleset) { - this.rulesetContainer = rulesetContainer; + this.rrawableRuleset = rrawableRuleset; - InternalChild = rulesetContainer; + InternalChild = rrawableRuleset; Playfield.DisplayJudgements.Value = false; } @@ -73,10 +73,10 @@ namespace osu.Game.Rulesets.Edit processor?.PostProcess(); // Add visual representation - var drawableObject = rulesetContainer.GetVisualRepresentation(tObject); + var drawableObject = rrawableRuleset.GetVisualRepresentation(tObject); - rulesetContainer.Playfield.Add(drawableObject); - rulesetContainer.Playfield.PostProcess(); + rrawableRuleset.Playfield.Add(drawableObject); + rrawableRuleset.Playfield.PostProcess(); return drawableObject; } @@ -97,8 +97,8 @@ namespace osu.Game.Rulesets.Edit // Remove visual representation var drawableObject = Playfield.AllHitObjects.Single(d => d.HitObject == hitObject); - rulesetContainer.Playfield.Remove(drawableObject); - rulesetContainer.Playfield.PostProcess(); + rrawableRuleset.Playfield.Remove(drawableObject); + rrawableRuleset.Playfield.PostProcess(); return drawableObject; } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 025564e249..c95d6c3f0a 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Edit { public abstract class HitObjectComposer : CompositeDrawable { - public IEnumerable HitObjects => RulesetContainer.Playfield.AllHitObjects; + public IEnumerable HitObjects => DrawableRuleset.Playfield.AllHitObjects; protected readonly Ruleset Ruleset; @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Edit private readonly List layerContainers = new List(); - protected EditRulesetContainer RulesetContainer { get; private set; } + protected EditDrawableRuleset DrawableRuleset { get; private set; } private BlueprintContainer blueprintContainer; @@ -54,8 +54,8 @@ namespace osu.Game.Rulesets.Edit try { - RulesetContainer = CreateRulesetContainer(); - RulesetContainer.Clock = framedClock; + DrawableRuleset = CreateDrawableRuleset(); + DrawableRuleset.Clock = framedClock; } catch (Exception e) { @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Edit Children = new Drawable[] { layerBelowRuleset, - RulesetContainer, + DrawableRuleset, layerAboveRuleset } } @@ -140,27 +140,27 @@ namespace osu.Game.Rulesets.Edit layerContainers.ForEach(l => { - l.Anchor = RulesetContainer.Playfield.Anchor; - l.Origin = RulesetContainer.Playfield.Origin; - l.Position = RulesetContainer.Playfield.Position; - l.Size = RulesetContainer.Playfield.Size; + l.Anchor = DrawableRuleset.Playfield.Anchor; + l.Origin = DrawableRuleset.Playfield.Origin; + l.Position = DrawableRuleset.Playfield.Position; + l.Size = DrawableRuleset.Playfield.Size; }); } /// /// Whether the user's cursor is currently in an area of the that is valid for placement. /// - public virtual bool CursorInPlacementArea => RulesetContainer.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); + public virtual bool CursorInPlacementArea => DrawableRuleset.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); /// /// Adds a to the and visualises it. /// /// The to add. - public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(RulesetContainer.Add(hitObject)); + public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(DrawableRuleset.Add(hitObject)); - public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(RulesetContainer.Remove(hitObject)); + public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(DrawableRuleset.Remove(hitObject)); - internal abstract EditRulesetContainer CreateRulesetContainer(); + internal abstract EditDrawableRuleset CreateDrawableRuleset(); protected abstract IReadOnlyList CompositionTools { get; } @@ -189,9 +189,9 @@ namespace osu.Game.Rulesets.Edit { } - internal override EditRulesetContainer CreateRulesetContainer() - => new EditRulesetContainer(CreateRulesetContainer(Ruleset, Beatmap.Value)); + internal override EditDrawableRuleset CreateDrawableRuleset() + => new EditDrawableRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value)); - protected abstract RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap); + protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap); } } diff --git a/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs b/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs index addb96a4fe..ff00e4f31d 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs @@ -7,15 +7,15 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mods { /// - /// An interface for s that can be applied to s. + /// An interface for s that can be applied to s. /// - public interface IApplicableToRulesetContainer : IApplicableMod + public interface IApplicableToDrawableRuleset : IApplicableMod where TObject : HitObject { /// - /// Applies this to a . + /// Applies this to a . /// - /// The to apply to. - void ApplyToRulesetContainer(RulesetContainer rulesetContainer); + /// The to apply to. + void ApplyToDrawableRuleset(DrawableRuleset rrawableRuleset); } } diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 44c78f8436..dc60f38218 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -11,10 +11,10 @@ using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModAutoplay : ModAutoplay, IApplicableToRulesetContainer + public abstract class ModAutoplay : ModAutoplay, IApplicableToDrawableRuleset where T : HitObject { - public virtual void ApplyToRulesetContainer(RulesetContainer rulesetContainer) => rulesetContainer.SetReplayScore(CreateReplayScore(rulesetContainer.Beatmap)); + public virtual void ApplyToDrawableRuleset(DrawableRuleset rrawableRuleset) => rrawableRuleset.SetReplayScore(CreateReplayScore(rrawableRuleset.Beatmap)); } public abstract class ModAutoplay : Mod, IApplicableFailOverride diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index ab095f417a..8b861496af 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mods } } - public abstract class ModFlashlight : ModFlashlight, IApplicableToRulesetContainer, IApplicableToScoreProcessor + public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor where T : HitObject { public const double FLASHLIGHT_FADE_DURATION = 800; @@ -45,15 +45,15 @@ namespace osu.Game.Rulesets.Mods Combo.BindTo(scoreProcessor.Combo); } - public virtual void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public virtual void ApplyToDrawableRuleset(DrawableRuleset rrawableRuleset) { var flashlight = CreateFlashlight(); flashlight.Combo = Combo; flashlight.RelativeSizeAxes = Axes.Both; flashlight.Colour = Color4.Black; - rulesetContainer.KeyBindingInputManager.Add(flashlight); + rrawableRuleset.KeyBindingInputManager.Add(flashlight); - flashlight.Breaks = rulesetContainer.Beatmap.Breaks; + flashlight.Breaks = rrawableRuleset.Beatmap.Breaks; } public abstract Flashlight CreateFlashlight(); diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 70f15b99bd..feac49ca2c 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets /// The beatmap to create the hit renderer for. /// Unable to successfully load the beatmap to be usable with this ruleset. /// - public abstract RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap); + public abstract DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap); /// /// Creates a to convert a to one that is applicable for this . diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 63e1c93dd5..689512fd70 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -210,15 +210,15 @@ namespace osu.Game.Rulesets.Scoring { } - public ScoreProcessor(RulesetContainer rulesetContainer) + public ScoreProcessor(DrawableRuleset rrawableRuleset) { Debug.Assert(base_portion + combo_portion == 1.0); - rulesetContainer.OnNewResult += applyResult; - rulesetContainer.OnRevertResult += revertResult; + rrawableRuleset.OnNewResult += applyResult; + rrawableRuleset.OnRevertResult += revertResult; - ApplyBeatmap(rulesetContainer.Beatmap); - SimulateAutoplay(rulesetContainer.Beatmap); + ApplyBeatmap(rrawableRuleset.Beatmap); + SimulateAutoplay(rrawableRuleset.Beatmap); Reset(true); if (maxBaseScore == 0 || maxHighestCombo == 0) diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs similarity index 89% rename from osu.Game/Rulesets/UI/RulesetContainer.cs rename to osu.Game/Rulesets/UI/DrawableRuleset.cs index 960e05f669..9fc336ed3e 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -30,14 +30,14 @@ using osu.Game.Screens.Play; namespace osu.Game.Rulesets.UI { /// - /// RulesetContainer that applies conversion to Beatmaps. Does not contain a Playfield + /// DrawableRuleset that applies conversion to Beatmaps. Does not contain a Playfield /// and does not load drawable hit objects. /// - /// Should not be derived - derive instead. + /// Should not be derived - derive instead. /// /// - /// The type of HitObject contained by this RulesetContainer. - public abstract class RulesetContainer : RulesetContainer, IProvideCursor, ICanAttachKeyCounter + /// The type of HitObject contained by this DrawableRuleset. + public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter where TObject : HitObject { /// @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.UI public virtual int Variant => 0; /// - /// The key conversion input manager for this RulesetContainer. + /// The key conversion input manager for this DrawableRuleset. /// public PassThroughInputManager KeyBindingInputManager; @@ -79,10 +79,10 @@ namespace osu.Game.Rulesets.UI /// /// The ruleset being repesented. /// The beatmap to create the hit renderer for. - protected RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap) + protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap workingBeatmap) : base(ruleset) { - Debug.Assert(workingBeatmap != null, "RulesetContainer initialized with a null beatmap."); + Debug.Assert(workingBeatmap != null, "DrawableRuleset initialized with a null beatmap."); RelativeSizeAxes = Axes.Both; @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.UI } /// - /// Creates and adds the visual representation of a to this . + /// Creates and adds the visual representation of a to this . /// /// The to add the visual representation for. private void addRepresentation(TObject hitObject) @@ -253,7 +253,7 @@ namespace osu.Game.Rulesets.UI } /// - /// Applies the active mods to this RulesetContainer. + /// Applies the active mods to this DrawableRuleset. /// /// private void applyRulesetMods(IEnumerable mods, OsuConfigManager config) @@ -261,8 +261,8 @@ namespace osu.Game.Rulesets.UI if (mods == null) return; - foreach (var mod in mods.OfType>()) - mod.ApplyToRulesetContainer(this); + foreach (var mod in mods.OfType>()) + mod.ApplyToDrawableRuleset(this); foreach (var mod in mods.OfType()) mod.ReadFromConfig(config); @@ -281,12 +281,12 @@ namespace osu.Game.Rulesets.UI } /// - /// Base RulesetContainer. Doesn't hold objects. + /// Base DrawableRuleset. Doesn't hold objects. /// - /// Should not be derived - derive instead. + /// Should not be derived - derive instead. /// /// - public abstract class RulesetContainer : CompositeDrawable + public abstract class DrawableRuleset : CompositeDrawable { /// /// Whether a replay is currently loaded. @@ -307,7 +307,7 @@ namespace osu.Game.Rulesets.UI /// Creates a ruleset visualisation for the provided ruleset. /// /// The ruleset. - protected RulesetContainer(Ruleset ruleset) + protected DrawableRuleset(Ruleset ruleset) { Ruleset = ruleset; } @@ -341,18 +341,18 @@ namespace osu.Game.Rulesets.UI /// /// Create a for the associated ruleset and link with this - /// . + /// . /// /// A score processor. public abstract ScoreProcessor CreateScoreProcessor(); } /// - /// A derivable RulesetContainer that manages the Playfield and HitObjects. + /// A derivable DrawableRuleset that manages the Playfield and HitObjects. /// - /// The type of Playfield contained by this RulesetContainer. - /// The type of HitObject contained by this RulesetContainer. - public abstract class RulesetContainer : RulesetContainer + /// The type of Playfield contained by this DrawableRuleset. + /// The type of HitObject contained by this DrawableRuleset. + public abstract class DrawableRuleset : DrawableRuleset where TObject : HitObject where TPlayfield : Playfield { @@ -366,7 +366,7 @@ namespace osu.Game.Rulesets.UI /// /// The ruleset being repesented. /// The beatmap to create the hit renderer for. - protected RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs similarity index 92% rename from osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs rename to osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 7a60e0b021..9d0fbf1e9e 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -20,10 +20,10 @@ using osu.Game.Rulesets.UI.Scrolling.Algorithms; namespace osu.Game.Rulesets.UI.Scrolling { /// - /// A type of that supports a . - /// s inside this will scroll within the playfield. + /// A type of that supports a . + /// s inside this will scroll within the playfield. /// - public abstract class ScrollingRulesetContainer : RulesetContainer, IKeyBindingHandler + public abstract class DrawableScrollingRuleset : DrawableRuleset, IKeyBindingHandler where TObject : HitObject where TPlayfield : ScrollingPlayfield { @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.UI.Scrolling /// /// Provides the default s that adjust the scrolling rate of s - /// inside this . + /// inside this . /// /// private readonly SortedList controlPoints = new SortedList(Comparer.Default); @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.UI.Scrolling [Cached(Type = typeof(IScrollingInfo))] private readonly LocalScrollingInfo scrollingInfo; - protected ScrollingRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + protected DrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { scrollingInfo = new LocalScrollingInfo(); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index f2d2381d20..0ba1e74aca 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Edit { this.host = host; - // TODO: should probably be done at a RulesetContainer level to share logic with Player. + // TODO: should probably be done at a DrawableRuleset level to share logic with Player. var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); diff --git a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs index 8ab23a620b..6080458aec 100644 --- a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs +++ b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Multi.Components private const float height = 30; private const float transition_duration = 100; - private Container rulesetContainer; + private Container drawableRuleset; public ModeTypeInfo() { @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Multi.Components LayoutDuration = 100, Children = new[] { - rulesetContainer = new Container + drawableRuleset = new Container { AutoSizeAxes = Axes.Both, }, @@ -55,11 +55,11 @@ namespace osu.Game.Screens.Multi.Components { if (item?.Beatmap != null) { - rulesetContainer.FadeIn(transition_duration); - rulesetContainer.Child = new DifficultyIcon(item.Beatmap, item.Ruleset) { Size = new Vector2(height) }; + drawableRuleset.FadeIn(transition_duration); + drawableRuleset.Child = new DifficultyIcon(item.Beatmap, item.Ruleset) { Size = new Vector2(height) }; } else - rulesetContainer.FadeOut(transition_duration); + drawableRuleset.FadeOut(transition_duration); } } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 1f6a72e726..def9a97c7e 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Play public Action RequestSeek; - public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working) + public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, WorkingBeatmap working) { RelativeSizeAxes = Axes.Both; @@ -90,10 +90,10 @@ namespace osu.Game.Screens.Play }; BindProcessor(scoreProcessor); - BindRulesetContainer(rulesetContainer); + BindDrawableRuleset(drawableRuleset); - Progress.Objects = rulesetContainer.Objects; - Progress.AllowSeeking = rulesetContainer.HasReplayLoaded.Value; + Progress.Objects = drawableRuleset.Objects; + Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value; Progress.RequestSeek = time => RequestSeek(time); ModDisplay.Current.BindTo(working.Mods); @@ -143,13 +143,13 @@ namespace osu.Game.Screens.Play } } - protected virtual void BindRulesetContainer(RulesetContainer rulesetContainer) + protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) { - (rulesetContainer as ICanAttachKeyCounter)?.Attach(KeyCounter); + (drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter); - replayLoaded.BindTo(rulesetContainer.HasReplayLoaded); + replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); - Progress.BindRulestContainer(rulesetContainer); + Progress.BindRulestContainer(drawableRuleset); } protected override bool OnKeyDown(KeyDownEvent e) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b11c5e51c9..b814eed08c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Play private SampleChannel sampleRestart; protected ScoreProcessor ScoreProcessor { get; private set; } - protected RulesetContainer RulesetContainer { get; private set; } + protected DrawableRuleset DrawableRuleset { get; private set; } protected HUDOverlay HUDOverlay { get; private set; } private FailOverlay failOverlay; @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Play EnableUserDim = { Value = true } }; - public bool LoadedBeatmapSuccessfully => RulesetContainer?.Objects.Any() == true; + public bool LoadedBeatmapSuccessfully => DrawableRuleset?.Objects.Any() == true; private GameplayClockContainer gameplayClockContainer; @@ -98,11 +98,11 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); - ScoreProcessor = RulesetContainer.CreateScoreProcessor(); + ScoreProcessor = DrawableRuleset.CreateScoreProcessor(); if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - InternalChild = gameplayClockContainer = new GameplayClockContainer(working, AllowLeadIn, RulesetContainer.GameplayStartTime); + InternalChild = gameplayClockContainer = new GameplayClockContainer(working, AllowLeadIn, DrawableRuleset.GameplayStartTime); gameplayClockContainer.Children = new Drawable[] { @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Play Start = gameplayClockContainer.Start, Stop = gameplayClockContainer.Stop, IsPaused = { BindTarget = gameplayClockContainer.IsPaused }, - CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value, + CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !DrawableRuleset.HasReplayLoaded.Value, Children = new[] { StoryboardContainer = CreateStoryboardContainer(), @@ -123,7 +123,7 @@ namespace osu.Game.Screens.Play Child = new LocalSkinOverrideContainer(working.Skin) { RelativeSizeAxes = Axes.Both, - Child = RulesetContainer + Child = DrawableRuleset } }, new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) @@ -133,17 +133,17 @@ namespace osu.Game.Screens.Play Breaks = working.Beatmap.Breaks }, // display the cursor above some HUD elements. - RulesetContainer.Cursor?.CreateProxy() ?? new Container(), - HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working) + DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), + HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, working) { HoldToQuit = { Action = performUserRequestedExit }, PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = gameplayClockContainer.UserPlaybackRate } } }, - KeyCounter = { Visible = { BindTarget = RulesetContainer.HasReplayLoaded } }, + KeyCounter = { Visible = { BindTarget = DrawableRuleset.HasReplayLoaded } }, RequestSeek = gameplayClockContainer.Seek, Anchor = Anchor.Centre, Origin = Anchor.Centre }, - new SkipOverlay(RulesetContainer.GameplayStartTime) + new SkipOverlay(DrawableRuleset.GameplayStartTime) { RequestSeek = gameplayClockContainer.Seek }, @@ -167,7 +167,7 @@ namespace osu.Game.Screens.Play }; // bind clock into components that require it - RulesetContainer.IsPaused.BindTo(gameplayClockContainer.IsPaused); + DrawableRuleset.IsPaused.BindTo(gameplayClockContainer.IsPaused); if (ShowStoryboard.Value) initializeStoryboard(false); @@ -198,18 +198,18 @@ namespace osu.Game.Screens.Play try { - RulesetContainer = rulesetInstance.CreateRulesetContainerWith(working); + DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working); } catch (BeatmapInvalidForRulesetException) { - // we may fail to create a RulesetContainer if the beatmap cannot be loaded with the user's preferred ruleset + // we may fail to create a DrawableRuleset if the beatmap cannot be loaded with the user's preferred ruleset // let's try again forcing the beatmap's ruleset. ruleset = beatmap.BeatmapInfo.Ruleset; rulesetInstance = ruleset.CreateInstance(); - RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap.Value); + DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value); } - if (!RulesetContainer.Objects.Any()) + if (!DrawableRuleset.Objects.Any()) { Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Error); return null; @@ -261,7 +261,7 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; var score = CreateScore(); - if (RulesetContainer.ReplayScore == null) + if (DrawableRuleset.ReplayScore == null) scoreManager.Import(score); this.Push(CreateResults(score)); @@ -273,7 +273,7 @@ namespace osu.Game.Screens.Play protected virtual ScoreInfo CreateScore() { - var score = RulesetContainer.ReplayScore?.ScoreInfo ?? new ScoreInfo + var score = DrawableRuleset.ReplayScore?.ScoreInfo ?? new ScoreInfo { Beatmap = Beatmap.Value.BeatmapInfo, Ruleset = ruleset, @@ -346,7 +346,7 @@ namespace osu.Game.Screens.Play return true; } - if ((!AllowPause || HasFailed || !ValidForResume || PausableGameplayContainer?.IsPaused.Value != false || RulesetContainer?.HasReplayLoaded.Value != false) && (!PausableGameplayContainer?.IsResuming ?? true)) + if ((!AllowPause || HasFailed || !ValidForResume || PausableGameplayContainer?.IsPaused.Value != false || DrawableRuleset?.HasReplayLoaded.Value != false) && (!PausableGameplayContainer?.IsResuming ?? true)) { gameplayClockContainer.ResetLocalAdjustments(); diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 3190139378..949b08d98d 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play protected override void LoadComplete() { base.LoadComplete(); - RulesetContainer?.SetReplayScore(score); + DrawableRuleset?.SetReplayScore(score); } protected override ScoreInfo CreateScore() => score.ScoreInfo; diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index e3d6ca16a7..e94652b795 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -109,9 +109,9 @@ namespace osu.Game.Screens.Play replayLoaded.TriggerChange(); } - public void BindRulestContainer(RulesetContainer rulesetContainer) + public void BindRulestContainer(DrawableRuleset drawableRuleset) { - replayLoaded.BindTo(rulesetContainer.HasReplayLoaded); + replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); } private bool allowSeeking; From e0ab40b0821ba12670a7342962cc447c624c3be5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Mar 2019 23:56:12 +0900 Subject: [PATCH 131/623] Rename missed files --- .../Edit/{EditRulesetContainer.cs => EditDrawableRuleset.cs} | 0 ...cableToRulesetContainer.cs => IApplicableToDrawableRuleset.cs} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename osu.Game/Rulesets/Edit/{EditRulesetContainer.cs => EditDrawableRuleset.cs} (100%) rename osu.Game/Rulesets/Mods/{IApplicableToRulesetContainer.cs => IApplicableToDrawableRuleset.cs} (100%) diff --git a/osu.Game/Rulesets/Edit/EditRulesetContainer.cs b/osu.Game/Rulesets/Edit/EditDrawableRuleset.cs similarity index 100% rename from osu.Game/Rulesets/Edit/EditRulesetContainer.cs rename to osu.Game/Rulesets/Edit/EditDrawableRuleset.cs diff --git a/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs similarity index 100% rename from osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs rename to osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs From 3b7a76aa4eefaa713468224323d536eb25b6a9e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2019 11:22:34 +0900 Subject: [PATCH 132/623] Fix typo --- .../Mods/CatchModFlashlight.cs | 6 ++--- .../Scoring/CatchScoreProcessor.cs | 4 ++-- .../Scoring/ManiaScoreProcessor.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 4 ++-- .../Scoring/OsuScoreProcessor.cs | 4 ++-- .../Mods/TaikoModFlashlight.cs | 6 ++--- .../Scoring/TaikoScoreProcessor.cs | 4 ++-- osu.Game/Rulesets/Edit/EditDrawableRuleset.cs | 24 +++++++++---------- .../Mods/IApplicableToDrawableRuleset.cs | 4 ++-- osu.Game/Rulesets/Mods/ModAutoplay.cs | 2 +- osu.Game/Rulesets/Mods/ModFlashlight.cs | 6 ++--- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 10 ++++---- 13 files changed, 41 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 77f41affee..71268d899d 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -21,10 +21,10 @@ namespace osu.Game.Rulesets.Catch.Mods private CatchPlayfield playfield; - public override void ApplyToDrawableRuleset(DrawableRuleset rrawableRuleset) + public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - playfield = (CatchPlayfield)rrawableRuleset.Playfield; - base.ApplyToDrawableRuleset(rrawableRuleset); + playfield = (CatchPlayfield)drawableRuleset.Playfield; + base.ApplyToDrawableRuleset(drawableRuleset); } private class CatchFlashlight : Flashlight diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 05ee4fc52d..af614f95d0 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Catch.Scoring { public class CatchScoreProcessor : ScoreProcessor { - public CatchScoreProcessor(DrawableRuleset rrawableRuleset) - : base(rrawableRuleset) + public CatchScoreProcessor(DrawableRuleset drawableRuleset) + : base(drawableRuleset) { } diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 018f257d8f..5c914d8eac 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -92,8 +92,8 @@ namespace osu.Game.Rulesets.Mania.Scoring { } - public ManiaScoreProcessor(DrawableRuleset rrawableRuleset) - : base(rrawableRuleset) + public ManiaScoreProcessor(DrawableRuleset drawableRuleset) + : base(drawableRuleset) { } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 57536ec387..a1f4dfe1da 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -32,9 +32,9 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1.12; private DrawableOsuBlinds blinds; - public void ApplyToDrawableRuleset(DrawableRuleset rrawableRuleset) + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - rrawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(rrawableRuleset.Playfield.HitObjectContainer, rrawableRuleset.Beatmap)); + drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield.HitObjectContainer, drawableRuleset.Beatmap)); } public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index a1c96fd44f..ec23570f54 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -79,10 +79,10 @@ namespace osu.Game.Rulesets.Osu.Mods state.Apply(osuInputManager.CurrentState, osuInputManager); } - public void ApplyToDrawableRuleset(DrawableRuleset rrawableRuleset) + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { // grab the input manager for future use. - osuInputManager = (OsuInputManager)rrawableRuleset.KeyBindingInputManager; + osuInputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; osuInputManager.AllowUserPresses = false; } } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 4186c515db..2c8bf11016 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Scoring { internal class OsuScoreProcessor : ScoreProcessor { - public OsuScoreProcessor(DrawableRuleset rrawableRuleset) - : base(rrawableRuleset) + public OsuScoreProcessor(DrawableRuleset drawableRuleset) + : base(drawableRuleset) { } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index c6342f9c34..b7db3307ad 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -22,10 +22,10 @@ namespace osu.Game.Rulesets.Taiko.Mods private TaikoPlayfield playfield; - public override void ApplyToDrawableRuleset(DrawableRuleset rrawableRuleset) + public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - playfield = (TaikoPlayfield)rrawableRuleset.Playfield; - base.ApplyToDrawableRuleset(rrawableRuleset); + playfield = (TaikoPlayfield)drawableRuleset.Playfield; + base.ApplyToDrawableRuleset(drawableRuleset); } private class TaikoFlashlight : Flashlight diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 762e24326a..442cca49f8 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -32,8 +32,8 @@ namespace osu.Game.Rulesets.Taiko.Scoring /// private double hpMissMultiplier; - public TaikoScoreProcessor(DrawableRuleset rrawableRuleset) - : base(rrawableRuleset) + public TaikoScoreProcessor(DrawableRuleset drawableRuleset) + : base(drawableRuleset) { } diff --git a/osu.Game/Rulesets/Edit/EditDrawableRuleset.cs b/osu.Game/Rulesets/Edit/EditDrawableRuleset.cs index a454f72e11..cfeb6eec9e 100644 --- a/osu.Game/Rulesets/Edit/EditDrawableRuleset.cs +++ b/osu.Game/Rulesets/Edit/EditDrawableRuleset.cs @@ -41,18 +41,18 @@ namespace osu.Game.Rulesets.Edit public class EditDrawableRuleset : EditDrawableRuleset where TObject : HitObject { - public override Playfield Playfield => rrawableRuleset.Playfield; + public override Playfield Playfield => drawableRuleset.Playfield; - private Ruleset ruleset => rrawableRuleset.Ruleset; - private Beatmap beatmap => rrawableRuleset.Beatmap; + private Ruleset ruleset => drawableRuleset.Ruleset; + private Beatmap beatmap => drawableRuleset.Beatmap; - private readonly DrawableRuleset rrawableRuleset; + private readonly DrawableRuleset drawableRuleset; - public EditDrawableRuleset(DrawableRuleset rrawableRuleset) + public EditDrawableRuleset(DrawableRuleset drawableRuleset) { - this.rrawableRuleset = rrawableRuleset; + this.drawableRuleset = drawableRuleset; - InternalChild = rrawableRuleset; + InternalChild = drawableRuleset; Playfield.DisplayJudgements.Value = false; } @@ -73,10 +73,10 @@ namespace osu.Game.Rulesets.Edit processor?.PostProcess(); // Add visual representation - var drawableObject = rrawableRuleset.GetVisualRepresentation(tObject); + var drawableObject = drawableRuleset.GetVisualRepresentation(tObject); - rrawableRuleset.Playfield.Add(drawableObject); - rrawableRuleset.Playfield.PostProcess(); + drawableRuleset.Playfield.Add(drawableObject); + drawableRuleset.Playfield.PostProcess(); return drawableObject; } @@ -97,8 +97,8 @@ namespace osu.Game.Rulesets.Edit // Remove visual representation var drawableObject = Playfield.AllHitObjects.Single(d => d.HitObject == hitObject); - rrawableRuleset.Playfield.Remove(drawableObject); - rrawableRuleset.Playfield.PostProcess(); + drawableRuleset.Playfield.Remove(drawableObject); + drawableRuleset.Playfield.PostProcess(); return drawableObject; } diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs index ff00e4f31d..b012beb0c0 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mods /// /// Applies this to a . /// - /// The to apply to. - void ApplyToDrawableRuleset(DrawableRuleset rrawableRuleset); + /// The to apply to. + void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset); } } diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index dc60f38218..1c76abbc4b 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModAutoplay : ModAutoplay, IApplicableToDrawableRuleset where T : HitObject { - public virtual void ApplyToDrawableRuleset(DrawableRuleset rrawableRuleset) => rrawableRuleset.SetReplayScore(CreateReplayScore(rrawableRuleset.Beatmap)); + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) => drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap)); } public abstract class ModAutoplay : Mod, IApplicableFailOverride diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 8b861496af..23e928d991 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -45,15 +45,15 @@ namespace osu.Game.Rulesets.Mods Combo.BindTo(scoreProcessor.Combo); } - public virtual void ApplyToDrawableRuleset(DrawableRuleset rrawableRuleset) + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { var flashlight = CreateFlashlight(); flashlight.Combo = Combo; flashlight.RelativeSizeAxes = Axes.Both; flashlight.Colour = Color4.Black; - rrawableRuleset.KeyBindingInputManager.Add(flashlight); + drawableRuleset.KeyBindingInputManager.Add(flashlight); - flashlight.Breaks = rrawableRuleset.Beatmap.Breaks; + flashlight.Breaks = drawableRuleset.Beatmap.Breaks; } public abstract Flashlight CreateFlashlight(); diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 689512fd70..0fddb19a6c 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -210,15 +210,15 @@ namespace osu.Game.Rulesets.Scoring { } - public ScoreProcessor(DrawableRuleset rrawableRuleset) + public ScoreProcessor(DrawableRuleset drawableRuleset) { Debug.Assert(base_portion + combo_portion == 1.0); - rrawableRuleset.OnNewResult += applyResult; - rrawableRuleset.OnRevertResult += revertResult; + drawableRuleset.OnNewResult += applyResult; + drawableRuleset.OnRevertResult += revertResult; - ApplyBeatmap(rrawableRuleset.Beatmap); - SimulateAutoplay(rrawableRuleset.Beatmap); + ApplyBeatmap(drawableRuleset.Beatmap); + SimulateAutoplay(drawableRuleset.Beatmap); Reset(true); if (maxBaseScore == 0 || maxHighestCombo == 0) From 7b6d882ce6508be56126c40ba5adc72d54723eee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2019 11:29:16 +0900 Subject: [PATCH 133/623] Remove double-generic type --- .../UI/DrawableCatchRuleset.cs | 2 +- .../UI/DrawableManiaRuleset.cs | 4 ++- .../UI/DrawableOsuRuleset.cs | 2 +- .../UI/DrawableTaikoRuleset.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 28 ------------------- .../UI/Scrolling/DrawableScrollingRuleset.cs | 7 ++--- 6 files changed, 9 insertions(+), 36 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 63220e9379..406dc10eea 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -17,7 +17,7 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Catch.UI { - public class DrawableCatchRuleset : DrawableScrollingRuleset + public class DrawableCatchRuleset : DrawableScrollingRuleset { protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Constant; diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 5090dd491e..a019401d5b 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -28,8 +28,10 @@ using osuTK; namespace osu.Game.Rulesets.Mania.UI { - public class DrawableManiaRuleset : DrawableScrollingRuleset + public class DrawableManiaRuleset : DrawableScrollingRuleset { + protected new ManiaPlayfield Playfield => (ManiaPlayfield)base.Playfield; + public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; public IEnumerable BarLines; diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index d91ccdedd5..b632e0fb05 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -17,7 +17,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.UI { - public class DrawableOsuRuleset : DrawableRuleset + public class DrawableOsuRuleset : DrawableRuleset { protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config; diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 54a54a54bd..899b91863e 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -20,7 +20,7 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Taiko.UI { - public class DrawableTaikoRuleset : DrawableScrollingRuleset + public class DrawableTaikoRuleset : DrawableScrollingRuleset { protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping; diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 9fc336ed3e..a33f75737b 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -32,9 +32,6 @@ namespace osu.Game.Rulesets.UI /// /// DrawableRuleset that applies conversion to Beatmaps. Does not contain a Playfield /// and does not load drawable hit objects. - /// - /// Should not be derived - derive instead. - /// /// /// The type of HitObject contained by this DrawableRuleset. public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter @@ -347,31 +344,6 @@ namespace osu.Game.Rulesets.UI public abstract ScoreProcessor CreateScoreProcessor(); } - /// - /// A derivable DrawableRuleset that manages the Playfield and HitObjects. - /// - /// The type of Playfield contained by this DrawableRuleset. - /// The type of HitObject contained by this DrawableRuleset. - public abstract class DrawableRuleset : DrawableRuleset - where TObject : HitObject - where TPlayfield : Playfield - { - /// - /// The playfield. - /// - protected new TPlayfield Playfield => (TPlayfield)base.Playfield; - - /// - /// Creates a ruleset visualisation for the provided ruleset and beatmap. - /// - /// The ruleset being repesented. - /// The beatmap to create the hit renderer for. - protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) - { - } - } - public class BeatmapInvalidForRulesetException : ArgumentException { public BeatmapInvalidForRulesetException(string text) diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 9d0fbf1e9e..9c2a06aef0 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -20,12 +20,11 @@ using osu.Game.Rulesets.UI.Scrolling.Algorithms; namespace osu.Game.Rulesets.UI.Scrolling { /// - /// A type of that supports a . - /// s inside this will scroll within the playfield. + /// A type of that supports a . + /// s inside this will scroll within the playfield. /// - public abstract class DrawableScrollingRuleset : DrawableRuleset, IKeyBindingHandler + public abstract class DrawableScrollingRuleset : DrawableRuleset, IKeyBindingHandler where TObject : HitObject - where TPlayfield : ScrollingPlayfield { /// /// The default span of time visible by the length of the scrolling axes. From 45b8bfcfd35777498be373c10683937637ea8ee9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2019 11:31:03 +0900 Subject: [PATCH 134/623] Better protect not-generic DrawableRuleset --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index a33f75737b..77723ba765 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -30,8 +30,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Rulesets.UI { /// - /// DrawableRuleset that applies conversion to Beatmaps. Does not contain a Playfield - /// and does not load drawable hit objects. + /// Displays an interactive ruleset gameplay instance. /// /// The type of HitObject contained by this DrawableRuleset. public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter @@ -278,10 +277,11 @@ namespace osu.Game.Rulesets.UI } /// - /// Base DrawableRuleset. Doesn't hold objects. - /// - /// Should not be derived - derive instead. - /// + /// Displays an interactive ruleset gameplay instance. + /// + /// This type is required only for adding non-generic type to the draw hierarchy. + /// Once IDrawable is a thing, this can also become an interface. + /// /// public abstract class DrawableRuleset : CompositeDrawable { @@ -295,7 +295,7 @@ namespace osu.Game.Rulesets.UI /// public readonly BindableBool IsPaused = new BindableBool(); - /// + /// ~ /// The associated ruleset. /// public readonly Ruleset Ruleset; @@ -304,7 +304,7 @@ namespace osu.Game.Rulesets.UI /// Creates a ruleset visualisation for the provided ruleset. /// /// The ruleset. - protected DrawableRuleset(Ruleset ruleset) + internal DrawableRuleset(Ruleset ruleset) { Ruleset = ruleset; } From 936c3e1ed937e4641a76f733b4ff9b476eac52be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2019 11:34:06 +0900 Subject: [PATCH 135/623] Add safety type check to DrawableScrollingRuleset --- .../Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 9c2a06aef0..3b368652f2 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -69,7 +70,7 @@ namespace osu.Game.Rulesets.UI.Scrolling /// /// Provides the default s that adjust the scrolling rate of s - /// inside this . + /// inside this . /// /// private readonly SortedList controlPoints = new SortedList(Comparer.Default); @@ -166,6 +167,14 @@ namespace osu.Game.Rulesets.UI.Scrolling return false; } + protected override void LoadComplete() + { + base.LoadComplete(); + + if (!(Playfield is ScrollingPlayfield)) + throw new ArgumentException($"{nameof(Playfield)} must be a {nameof(ScrollingPlayfield)} when using {nameof(DrawableScrollingRuleset)}."); + } + public bool OnReleased(GlobalAction action) => false; private class LocalScrollingInfo : IScrollingInfo From 003e36862f4425bb1c197ce968ed5082aab467d2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Mar 2019 12:06:14 +0900 Subject: [PATCH 136/623] Fix JuiceStreams not affecting lastPosition/lastStartTime --- osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index 275c9a500c..cd71bc2e17 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -20,6 +20,13 @@ namespace osu.Game.Rulesets.Catch.Mods public void ApplyToHitObject(HitObject hitObject) { + if (hitObject is JuiceStream stream) + { + lastPosition = stream.EndX; + lastStartTime = stream.EndTime; + return; + } + if (!(hitObject is Fruit)) return; From 8f0440d14cb593dfaa4ab8d10151a0506e5aa2ff Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Mar 2019 12:06:37 +0900 Subject: [PATCH 137/623] Ensure RNG max value cannot go below min value --- osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index cd71bc2e17..060e51e31d 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.Mods private void applyRandomOffset(ref float position, double maxOffset) { bool right = RNG.NextBool(); - float rand = Math.Min(20, (float)RNG.NextDouble(0, maxOffset)) / CatchPlayfield.BASE_WIDTH; + float rand = Math.Min(20, (float)RNG.NextDouble(0, Math.Max(0, maxOffset))) / CatchPlayfield.BASE_WIDTH; if (right) { From 44e02917371cbb6bf27d39f3946e6ccd90f808b8 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 19 Mar 2019 20:47:12 -0700 Subject: [PATCH 138/623] Remove specified depths of overlays --- osu.Game/OsuGame.cs | 36 ++++++++++-------------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index cf231f19ce..9f314c6bdf 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -403,7 +403,7 @@ namespace osu.Game { RelativeSizeAxes = Axes.Both, }, - floatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue }, + floatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, idleTracker = new GameIdleTracker(6000) }); @@ -421,7 +421,6 @@ namespace osu.Game loadComponentSingleFile(Toolbar = new Toolbar { - Depth = -5, OnHome = delegate { CloseAllOverlays(false); @@ -435,20 +434,15 @@ namespace osu.Game loadComponentSingleFile(screenshotManager, Add); //overlay elements - loadComponentSingleFile(direct = new DirectOverlay { Depth = -1 }, overlayContent.Add); - loadComponentSingleFile(social = new SocialOverlay { Depth = -1 }, overlayContent.Add); + loadComponentSingleFile(direct = new DirectOverlay(), overlayContent.Add); + loadComponentSingleFile(social = new SocialOverlay(), overlayContent.Add); loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal); - loadComponentSingleFile(chatOverlay = new ChatOverlay { Depth = -1 }, overlayContent.Add); - loadComponentSingleFile(settings = new MainSettings - { - GetToolbarHeight = () => ToolbarOffset, - Depth = -1 - }, floatingOverlayContent.Add); - loadComponentSingleFile(userProfile = new UserProfileOverlay { Depth = -2 }, overlayContent.Add); - loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay { Depth = -3 }, overlayContent.Add); + loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add); + loadComponentSingleFile(settings = new MainSettings { GetToolbarHeight = () => ToolbarOffset }, floatingOverlayContent.Add); + loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add); + loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add); loadComponentSingleFile(musicController = new MusicController { - Depth = -5, Position = new Vector2(0, Toolbar.HEIGHT), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -457,25 +451,15 @@ namespace osu.Game loadComponentSingleFile(notifications = new NotificationOverlay { GetToolbarHeight = () => ToolbarOffset, - Depth = -4, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, floatingOverlayContent.Add); - loadComponentSingleFile(accountCreation = new AccountCreationOverlay - { - Depth = -6, - }, floatingOverlayContent.Add); + loadComponentSingleFile(accountCreation = new AccountCreationOverlay(), floatingOverlayContent.Add); - loadComponentSingleFile(dialogOverlay = new DialogOverlay - { - Depth = -7, - }, floatingOverlayContent.Add); + loadComponentSingleFile(dialogOverlay = new DialogOverlay(), floatingOverlayContent.Add); - loadComponentSingleFile(externalLinkOpener = new ExternalLinkOpener - { - Depth = -8, - }, floatingOverlayContent.Add); + loadComponentSingleFile(externalLinkOpener = new ExternalLinkOpener(), floatingOverlayContent.Add); dependencies.CacheAs(idleTracker); dependencies.Cache(settings); From a39648edbc2b9496a3b2cb9d5e573d42196242e2 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 19 Mar 2019 20:51:43 -0700 Subject: [PATCH 139/623] Normalize format of single line curly brackets --- osu.Game/OsuGame.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 9f314c6bdf..facc616136 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -399,10 +399,7 @@ namespace osu.Game logoContainer = new Container { RelativeSizeAxes = Axes.Both }, } }, - overlayContent = new Container - { - RelativeSizeAxes = Axes.Both, - }, + overlayContent = new Container { RelativeSizeAxes = Axes.Both }, floatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, idleTracker = new GameIdleTracker(6000) }); @@ -414,10 +411,7 @@ namespace osu.Game loadComponentSingleFile(osuLogo, logoContainer.Add); - loadComponentSingleFile(new Loader - { - RelativeSizeAxes = Axes.Both - }, screenStack.Push); + loadComponentSingleFile(new Loader { RelativeSizeAxes = Axes.Both }, screenStack.Push); loadComponentSingleFile(Toolbar = new Toolbar { From 1f95abf7c79efb99a885357f04390c1eb7e4a8c5 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 19 Mar 2019 21:30:24 -0700 Subject: [PATCH 140/623] Fix regressions caused by removing specified depths - urgentOverlayContent is added so toolbar can be loaded first --- osu.Game/OsuGame.cs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index facc616136..f952f92f81 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -401,6 +401,7 @@ namespace osu.Game }, overlayContent = new Container { RelativeSizeAxes = Axes.Both }, floatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, + urgentOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, idleTracker = new GameIdleTracker(6000) }); @@ -420,7 +421,7 @@ namespace osu.Game CloseAllOverlays(false); menuScreen?.MakeCurrent(); }, - }, floatingOverlayContent.Add); + }, urgentOverlayContent.Add); loadComponentSingleFile(volume = new VolumeOverlay(), floatingOverlayContent.Add); loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add); @@ -435,13 +436,6 @@ namespace osu.Game loadComponentSingleFile(settings = new MainSettings { GetToolbarHeight = () => ToolbarOffset }, floatingOverlayContent.Add); loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add); loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add); - loadComponentSingleFile(musicController = new MusicController - { - Position = new Vector2(0, Toolbar.HEIGHT), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }, floatingOverlayContent.Add); - loadComponentSingleFile(notifications = new NotificationOverlay { GetToolbarHeight = () => ToolbarOffset, @@ -449,11 +443,18 @@ namespace osu.Game Origin = Anchor.TopRight, }, floatingOverlayContent.Add); - loadComponentSingleFile(accountCreation = new AccountCreationOverlay(), floatingOverlayContent.Add); + loadComponentSingleFile(musicController = new MusicController + { + Position = new Vector2(0, Toolbar.HEIGHT), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, floatingOverlayContent.Add); - loadComponentSingleFile(dialogOverlay = new DialogOverlay(), floatingOverlayContent.Add); + loadComponentSingleFile(accountCreation = new AccountCreationOverlay(), urgentOverlayContent.Add); - loadComponentSingleFile(externalLinkOpener = new ExternalLinkOpener(), floatingOverlayContent.Add); + loadComponentSingleFile(dialogOverlay = new DialogOverlay(), urgentOverlayContent.Add); + + loadComponentSingleFile(externalLinkOpener = new ExternalLinkOpener(), urgentOverlayContent.Add); dependencies.CacheAs(idleTracker); dependencies.Cache(settings); @@ -699,6 +700,8 @@ namespace osu.Game private Container floatingOverlayContent; + private Container urgentOverlayContent; + private FrameworkConfigManager frameworkConfig; private ScalingContainer screenContainer; From 65112348041cffc6df4c625c65c914bc03a669f7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Mar 2019 13:53:22 +0900 Subject: [PATCH 141/623] Fix catch spinners not being allowed for conversion --- osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs index 41281e805e..0089d1eb88 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -8,12 +8,14 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// /// Legacy osu!catch Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasCombo + internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasXPosition, IHasCombo { public double EndTime { get; set; } public double Duration => EndTime - StartTime; + public float X => 256; // Required for CatchBeatmapConverter + public bool NewCombo { get; set; } public int ComboOffset { get; set; } From 15637f9c4a9ef0d3bdc1af745223b4523464779b Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 20 Mar 2019 14:17:35 +0900 Subject: [PATCH 142/623] Rework instant blur logic such that updateVisuals doesn't need to be public --- .../Visual/TestCaseBackgroundScreenBeatmap.cs | 2 +- .../Graphics/Containers/UserDimContainer.cs | 47 ++++++++++++------- osu.Game/Screens/BackgroundScreen.cs | 3 -- .../Backgrounds/BackgroundScreenBeatmap.cs | 15 ++++-- .../Backgrounds/BackgroundScreenCustom.cs | 2 +- .../Backgrounds/BackgroundScreenDefault.cs | 8 ++-- osu.Game/Screens/Play/Player.cs | 5 +- osu.Game/Screens/Play/PlayerLoader.cs | 22 ++++----- osu.Game/Screens/Ranking/Results.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 10 files changed, 61 insertions(+), 47 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index 263cfe0fc8..084bb6a02e 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -273,7 +273,7 @@ namespace osu.Game.Tests.Visual protected override BackgroundScreen CreateBackground() { FadeAccessibleBackground background = new FadeAccessibleBackground(Beatmap.Value); - DimEnabled.BindTo(background.EnableVisualSettings); + DimEnabled.BindTo(background.EnableUserDim); return background; } diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 3593394495..d68f607dcd 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -36,7 +37,7 @@ namespace osu.Game.Graphics.Containers /// /// Used in contexts where there can potentially be both user and screen-specified blurring occuring at the same time, such as in /// - public Bindable AddedBlur = new Bindable(); + public readonly Bindable BlurAmount = new Bindable(); private Bindable dimLevel { get; set; } @@ -51,8 +52,10 @@ namespace osu.Game.Graphics.Containers private readonly bool isStoryboard; private Vector2 blurTarget => EnableUserDim.Value - ? new Vector2(AddedBlur.Value + (float)blurLevel.Value * 25) - : new Vector2(AddedBlur.Value); + ? new Vector2(BlurAmount.Value + (float)blurLevel.Value * 25) + : new Vector2(BlurAmount.Value); + + private Background background => DimContainer.Children.OfType().FirstOrDefault(); /// /// Creates a new . @@ -69,27 +72,38 @@ namespace osu.Game.Graphics.Containers AddInternal(DimContainer = new Container { RelativeSizeAxes = Axes.Both }); } + /// + /// Set the blur of the background in this UserDimContainer to our blur target instantly. + /// + /// + /// We need to support instant blurring here in the case of changing beatmap backgrounds, where blurring shouldn't be from 0 every time the beatmap is changed. + /// + public void ApplyInstantBlur() + { + background?.BlurTo(blurTarget, 0, Easing.OutQuint); + } + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { dimLevel = config.GetBindable(OsuSetting.DimLevel); blurLevel = config.GetBindable(OsuSetting.BlurLevel); showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - EnableUserDim.ValueChanged += _ => UpdateVisuals(); - dimLevel.ValueChanged += _ => UpdateVisuals(); - blurLevel.ValueChanged += _ => UpdateVisuals(); - showStoryboard.ValueChanged += _ => UpdateVisuals(); - StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals(); - AddedBlur.ValueChanged += _ => UpdateVisuals(); + EnableUserDim.ValueChanged += _ => updateVisuals(); + dimLevel.ValueChanged += _ => updateVisuals(); + blurLevel.ValueChanged += _ => updateVisuals(); + showStoryboard.ValueChanged += _ => updateVisuals(); + StoryboardReplacesBackground.ValueChanged += _ => updateVisuals(); + BlurAmount.ValueChanged += _ => updateVisuals(); } protected override void LoadComplete() { base.LoadComplete(); - UpdateVisuals(); + updateVisuals(); } - public void UpdateVisuals(bool instant = false) + private void updateVisuals() { if (isStoryboard) { @@ -100,13 +114,10 @@ namespace osu.Game.Graphics.Containers // The background needs to be hidden in the case of it being replaced by the storyboard DimContainer.FadeTo(showStoryboard.Value && StoryboardReplacesBackground.Value ? 0 : 1, background_fade_duration, Easing.OutQuint); - foreach (Drawable c in DimContainer) - { - // Only blur if this container contains a background - // We can't blur the container like we did with the dim because buffered containers add considerable draw overhead. As a result, this blurs the background directly. - // We need to support instant blurring here in the case of SongSelect, where blurring shouldn't be from 0 every time the beatmap is changed. - ((Background)c)?.BlurTo(blurTarget, instant ? 0 : background_fade_duration, Easing.OutQuint); - } + // Only blur if this container contains a background + // We can't blur the container like we did with the dim because buffered containers add considerable draw overhead. + // As a result, this blurs the background directly. + background?.BlurTo(blurTarget, background_fade_duration, Easing.OutQuint); } DimContainer.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)dimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint); diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index 7eca192a6b..bbe162cf7c 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -5,15 +5,12 @@ using System; using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Input.Events; -using osu.Game.Graphics.Backgrounds; using osuTK; namespace osu.Game.Screens { public abstract class BackgroundScreen : Screen, IEquatable { - protected Background Background; - protected BackgroundScreen() { Anchor = Anchor.Centre; diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 1baa711f86..5f86fe8d20 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -13,16 +13,21 @@ namespace osu.Game.Screens.Backgrounds { public class BackgroundScreenBeatmap : BackgroundScreen { + protected Background Background; + private WorkingBeatmap beatmap; /// /// Whether or not user dim settings should be applied to this Background. /// - public readonly Bindable EnableVisualSettings = new Bindable(); + public readonly Bindable EnableUserDim = new Bindable(); public readonly Bindable StoryboardReplacesBackground = new Bindable(); - public readonly Bindable AddedBlur = new Bindable(); + /// + /// The amount of blur to be applied in addition to user-specified blur. + /// + public readonly Bindable BlurAmount = new Bindable(); private readonly UserDimContainer fadeContainer; @@ -53,7 +58,7 @@ namespace osu.Game.Screens.Backgrounds b.Depth = newDepth; fadeContainer.Add(Background = b); - fadeContainer.UpdateVisuals(true); + fadeContainer.ApplyInstantBlur(); StoryboardReplacesBackground.BindTo(fadeContainer.StoryboardReplacesBackground); })); }); @@ -64,8 +69,8 @@ namespace osu.Game.Screens.Backgrounds { Beatmap = beatmap; InternalChild = fadeContainer = CreateFadeContainer(); - fadeContainer.EnableUserDim.BindTo(EnableVisualSettings); - fadeContainer.AddedBlur.BindTo(AddedBlur); + fadeContainer.EnableUserDim.BindTo(EnableUserDim); + fadeContainer.BlurAmount.BindTo(BlurAmount); } public override bool Equals(BackgroundScreen other) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs index 538f347737..0cb41bc562 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Backgrounds public BackgroundScreenCustom(string textureName) { this.textureName = textureName; - AddInternal(Background = new Background(textureName)); + AddInternal(new Background(textureName)); } public override bool Equals(BackgroundScreen other) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 73590b3318..3aeb2f9923 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -15,6 +15,8 @@ namespace osu.Game.Screens.Backgrounds { public class BackgroundScreenDefault : BackgroundScreen { + private Background background; + private int currentDisplay; private const int background_count = 5; @@ -39,10 +41,10 @@ namespace osu.Game.Screens.Backgrounds private void display(Background newBackground) { - Background?.FadeOut(800, Easing.InOutSine); - Background?.Expire(); + background?.FadeOut(800, Easing.InOutSine); + background?.Expire(); - AddInternal(Background = newBackground); + AddInternal(background = newBackground); currentDisplay++; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index fc24e55de2..1ce8bf559d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -321,7 +321,8 @@ namespace osu.Game.Screens.Play if (enabled.NewValue) initializeStoryboard(true); }; - Background.EnableVisualSettings.Value = true; + Background.EnableUserDim.Value = true; + Background.BlurAmount.Value = 0; Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); StoryboardContainer.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); @@ -368,7 +369,7 @@ namespace osu.Game.Screens.Play float fadeOutDuration = instant ? 0 : 250; this.FadeOut(fadeOutDuration); - Background.EnableVisualSettings.Value = false; + Background.EnableUserDim.Value = false; storyboardReplacesBackground.Value = false; } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 4200a7390d..86156e292c 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -119,18 +119,12 @@ namespace osu.Game.Screens.Play private void contentIn() { - Background.AddedBlur.Value = BACKGROUND_BLUR; - Background.EnableVisualSettings.Value = false; - content.ScaleTo(1, 650, Easing.OutQuint); content.FadeInFromZero(400); } private void contentOut() { - Background.AddedBlur.Value = 0; - Background.EnableVisualSettings.Value = true; - content.ScaleTo(0.7f, 300, Easing.InQuint); content.FadeOut(250); } @@ -166,10 +160,12 @@ namespace osu.Game.Screens.Play protected override bool OnHover(HoverEvent e) { + // Acts as an "on hover lost" trigger for the visual settings panel. + // Returns background dim and blur to the values specified by PlayerLoader. if (this.IsCurrentScreen()) { - Background.AddedBlur.Value = BACKGROUND_BLUR; - Background.EnableVisualSettings.Value = false; + Background.BlurAmount.Value = BACKGROUND_BLUR; + Background.EnableUserDim.Value = false; } return base.OnHover(e); @@ -177,12 +173,14 @@ namespace osu.Game.Screens.Play protected override void OnHoverLost(HoverLostEvent e) { + // Acts as an "on hover" trigger for the visual settings panel. + // Preview user-defined background dim and blur when hovered on the visual settings panel. if (GetContainingInputManager()?.HoveredDrawables.Contains(VisualSettings) == true) { if (this.IsCurrentScreen() && Background != null) { - Background.AddedBlur.Value = 0; - Background.EnableVisualSettings.Value = true; + Background.BlurAmount.Value = 0; + Background.EnableUserDim.Value = true; } } @@ -241,7 +239,7 @@ namespace osu.Game.Screens.Play public override void OnSuspending(IScreen next) { - Background.EnableVisualSettings.Value = true; + Background.EnableUserDim.Value = true; base.OnSuspending(next); cancelLoad(); @@ -253,7 +251,7 @@ namespace osu.Game.Screens.Play this.FadeOut(150); cancelLoad(); - Background.EnableVisualSettings.Value = false; + Background.EnableUserDim.Value = false; return base.OnExiting(next); } diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index bd8daa6b03..dafb4c0aad 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Ranking public override void OnEntering(IScreen last) { base.OnEntering(last); - ((BackgroundScreenBeatmap)Background).AddedBlur.Value = BACKGROUND_BLUR; + ((BackgroundScreenBeatmap)Background).BlurAmount.Value = BACKGROUND_BLUR; Background.ScaleTo(1.1f, transition_time, Easing.OutQuint); allCircles.ForEach(c => diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 385c4e0a0d..9985f984a0 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -556,7 +556,7 @@ namespace osu.Game.Screens.Select if (Background is BackgroundScreenBeatmap backgroundModeBeatmap) { backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.AddedBlur.Value = BACKGROUND_BLUR; + backgroundModeBeatmap.BlurAmount.Value = BACKGROUND_BLUR; backgroundModeBeatmap.FadeColour(Color4.White, 250); } From 996464468342286adcf37246787a64eff2683481 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 20 Mar 2019 14:37:32 +0900 Subject: [PATCH 143/623] Fix merge --- osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 18d6fcc317..8b04b545f5 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -39,6 +39,7 @@ namespace osu.Game.Screens.Backgrounds Beatmap = beatmap; InternalChild = fadeContainer = CreateFadeContainer(); fadeContainer.EnableUserDim.BindTo(EnableUserDim); + fadeContainer.BlurAmount.BindTo(BlurAmount); } [BackgroundDependencyLoader] From 5f288650bfc5d92569d71d4b4f31edc0b67ff4bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2019 14:49:33 +0900 Subject: [PATCH 144/623] Fix misses/typos --- .../{EditDrawableRuleset.cs => DrawableEditRuleset.cs} | 10 +++++----- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 8 ++++---- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- osu.Game/Screens/Play/SongProgress.cs | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) rename osu.Game/Rulesets/Edit/{EditDrawableRuleset.cs => DrawableEditRuleset.cs} (93%) diff --git a/osu.Game/Rulesets/Edit/EditDrawableRuleset.cs b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs similarity index 93% rename from osu.Game/Rulesets/Edit/EditDrawableRuleset.cs rename to osu.Game/Rulesets/Edit/DrawableEditRuleset.cs index cfeb6eec9e..76a2e7af12 100644 --- a/osu.Game/Rulesets/Edit/EditDrawableRuleset.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs @@ -11,14 +11,14 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Edit { - public abstract class EditDrawableRuleset : CompositeDrawable + public abstract class DrawableEditRuleset : CompositeDrawable { /// - /// The contained by this . + /// The contained by this . /// public abstract Playfield Playfield { get; } - internal EditDrawableRuleset() + internal DrawableEditRuleset() { RelativeSizeAxes = Axes.Both; } @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Edit internal abstract DrawableHitObject Remove(HitObject hitObject); } - public class EditDrawableRuleset : EditDrawableRuleset + public class DrawableEditRuleset : DrawableEditRuleset where TObject : HitObject { public override Playfield Playfield => drawableRuleset.Playfield; @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Edit private readonly DrawableRuleset drawableRuleset; - public EditDrawableRuleset(DrawableRuleset drawableRuleset) + public DrawableEditRuleset(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c95d6c3f0a..45bf9b8be7 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Edit private readonly List layerContainers = new List(); - protected EditDrawableRuleset DrawableRuleset { get; private set; } + protected DrawableEditRuleset DrawableRuleset { get; private set; } private BlueprintContainer blueprintContainer; @@ -160,7 +160,7 @@ namespace osu.Game.Rulesets.Edit public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(DrawableRuleset.Remove(hitObject)); - internal abstract EditDrawableRuleset CreateDrawableRuleset(); + internal abstract DrawableEditRuleset CreateDrawableRuleset(); protected abstract IReadOnlyList CompositionTools { get; } @@ -189,8 +189,8 @@ namespace osu.Game.Rulesets.Edit { } - internal override EditDrawableRuleset CreateDrawableRuleset() - => new EditDrawableRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value)); + internal override DrawableEditRuleset CreateDrawableRuleset() + => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value)); protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap); } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index def9a97c7e..285e6eab23 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -149,7 +149,7 @@ namespace osu.Game.Screens.Play replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); - Progress.BindRulestContainer(drawableRuleset); + Progress.BindDrawableRuleset(drawableRuleset); } protected override bool OnKeyDown(KeyDownEvent e) diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index e94652b795..94b25e04a3 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -109,7 +109,7 @@ namespace osu.Game.Screens.Play replayLoaded.TriggerChange(); } - public void BindRulestContainer(DrawableRuleset drawableRuleset) + public void BindDrawableRuleset(DrawableRuleset drawableRuleset) { replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); } From c186629b8a2971ba08be2dfd5cb4e55701d83dbb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2019 14:55:38 +0900 Subject: [PATCH 145/623] Reorder class --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 157 ++++++++++++------------ 1 file changed, 80 insertions(+), 77 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 77723ba765..abf0fb2696 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -58,22 +58,40 @@ namespace osu.Game.Rulesets.UI /// /// Place to put drawables above hit objects but below UI. /// - public Container Overlays { get; protected set; } + public Container Overlays { get; private set; } - public override CursorContainer Cursor => Playfield.Cursor; + /// + /// Invoked when a has been applied by a . + /// + public event Action OnNewResult; - public bool ProvidingUserCursor => Playfield.Cursor != null && !HasReplayLoaded.Value; + /// + /// Invoked when a is being reverted by a . + /// + public event Action OnRevertResult; - protected override bool OnHover(HoverEvent e) => true; // required for IProvideCursor + /// + /// The beatmap. + /// + public Beatmap Beatmap; + + public override IEnumerable Objects => Beatmap.HitObjects; protected IRulesetConfigManager Config { get; private set; } + /// + /// The mods which are to be applied. + /// + private readonly IEnumerable mods; + + private FrameStabilityContainer frameStabilityContainer; + private OnScreenDisplay onScreenDisplay; /// /// Creates a ruleset visualisation for the provided ruleset and beatmap. /// - /// The ruleset being repesented. + /// The ruleset being represented. /// The beatmap to create the hit renderer for. protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap workingBeatmap) : base(ruleset) @@ -99,20 +117,42 @@ namespace osu.Game.Rulesets.UI }; } - public override void SetReplayScore(Score replayScore) + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - if (!(KeyBindingInputManager is IHasReplayHandler replayInputManager)) - throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - var handler = (ReplayScore = replayScore) != null ? CreateReplayInputHandler(replayScore.Replay) : null; + onScreenDisplay = dependencies.Get(); - replayInputManager.ReplayInputHandler = handler; - frameStabilityContainer.ReplayInputHandler = handler; + Config = dependencies.Get().GetConfigFor(Ruleset); + if (Config != null) + { + dependencies.Cache(Config); + onScreenDisplay?.BeginTracking(this, Config); + } - HasReplayLoaded.Value = replayInputManager.ReplayInputHandler != null; + return dependencies; + } - if (replayInputManager.ReplayInputHandler != null) - replayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + KeyBindingInputManager.AddRange(new Drawable[] + { + Playfield + }); + + InternalChildren = new Drawable[] + { + frameStabilityContainer = new FrameStabilityContainer + { + Child = KeyBindingInputManager, + }, + Overlays = new Container { RelativeSizeAxes = Axes.Both } + }; + + applyRulesetMods(mods, config); + + loadObjects(); } /// @@ -146,6 +186,22 @@ namespace osu.Game.Rulesets.UI Playfield.Add(drawableObject); } + public override void SetReplayScore(Score replayScore) + { + if (!(KeyBindingInputManager is IHasReplayHandler replayInputManager)) + throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); + + var handler = (ReplayScore = replayScore) != null ? CreateReplayInputHandler(replayScore.Replay) : null; + + replayInputManager.ReplayInputHandler = handler; + frameStabilityContainer.ReplayInputHandler = handler; + + HasReplayLoaded.Value = replayInputManager.ReplayInputHandler != null; + + if (replayInputManager.ReplayInputHandler != null) + replayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; + } + /// /// Creates a DrawableHitObject from a HitObject. /// @@ -156,22 +212,6 @@ namespace osu.Game.Rulesets.UI public void Attach(KeyCounterCollection keyCounter) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter); - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - onScreenDisplay = dependencies.Get(); - - Config = dependencies.Get().GetConfigFor(Ruleset); - if (Config != null) - { - dependencies.Cache(Config); - onScreenDisplay?.BeginTracking(this, Config); - } - - return dependencies; - } - /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. /// @@ -180,61 +220,14 @@ namespace osu.Game.Rulesets.UI protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; - private FrameStabilityContainer frameStabilityContainer; - /// /// Creates a Playfield. /// /// The Playfield. protected abstract Playfield CreatePlayfield(); - /// - /// Invoked when a has been applied by a . - /// - public event Action OnNewResult; - - /// - /// Invoked when a is being reverted by a . - /// - public event Action OnRevertResult; - - /// - /// The beatmap. - /// - public Beatmap Beatmap; - - public override IEnumerable Objects => Beatmap.HitObjects; - - /// - /// The mods which are to be applied. - /// - private readonly IEnumerable mods; - public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - KeyBindingInputManager.AddRange(new Drawable[] - { - Playfield - }); - - InternalChildren = new Drawable[] - { - frameStabilityContainer = new FrameStabilityContainer - { - Child = KeyBindingInputManager, - }, - Overlays = new Container { RelativeSizeAxes = Axes.Both } - }; - - // Apply mods - applyRulesetMods(mods, config); - - loadObjects(); - } - /// /// Applies the active mods to the Beatmap. /// @@ -264,6 +257,16 @@ namespace osu.Game.Rulesets.UI mod.ReadFromConfig(config); } + #region IProvideCursor + + protected override bool OnHover(HoverEvent e) => true; // required for IProvideCursor + + public override CursorContainer Cursor => Playfield.Cursor; + + public bool ProvidingUserCursor => Playfield.Cursor != null && !HasReplayLoaded.Value; + + #endregion + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 86658e357bae27c4c9559227c135301a85d50319 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 20 Mar 2019 14:55:52 +0900 Subject: [PATCH 146/623] Override add instead --- .../Graphics/Containers/UserDimContainer.cs | 22 ++++++++----------- .../Backgrounds/BackgroundScreenBeatmap.cs | 1 - 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index d68f607dcd..22b0175a94 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -55,8 +55,6 @@ namespace osu.Game.Graphics.Containers ? new Vector2(BlurAmount.Value + (float)blurLevel.Value * 25) : new Vector2(BlurAmount.Value); - private Background background => DimContainer.Children.OfType().FirstOrDefault(); - /// /// Creates a new . /// @@ -72,15 +70,13 @@ namespace osu.Game.Graphics.Containers AddInternal(DimContainer = new Container { RelativeSizeAxes = Axes.Both }); } - /// - /// Set the blur of the background in this UserDimContainer to our blur target instantly. - /// - /// - /// We need to support instant blurring here in the case of changing beatmap backgrounds, where blurring shouldn't be from 0 every time the beatmap is changed. - /// - public void ApplyInstantBlur() + public override void Add(Drawable drawable) { - background?.BlurTo(blurTarget, 0, Easing.OutQuint); + // We need to blur instantly here in the case of changing beatmap backgrounds, where blurring shouldn't be from 0 every time the beatmap is changed. + if (drawable is Background b) + b.BlurTo(blurTarget, 0, Easing.OutQuint); + + base.Add(drawable); } [BackgroundDependencyLoader] @@ -114,10 +110,10 @@ namespace osu.Game.Graphics.Containers // The background needs to be hidden in the case of it being replaced by the storyboard DimContainer.FadeTo(showStoryboard.Value && StoryboardReplacesBackground.Value ? 0 : 1, background_fade_duration, Easing.OutQuint); - // Only blur if this container contains a background + // This only works if the background is a direct child of DimContainer. // We can't blur the container like we did with the dim because buffered containers add considerable draw overhead. - // As a result, this blurs the background directly. - background?.BlurTo(blurTarget, background_fade_duration, Easing.OutQuint); + // As a result, this blurs the background directly via the direct children of DimContainer. + DimContainer.Children.OfType().FirstOrDefault()?.BlurTo(blurTarget, background_fade_duration, Easing.OutQuint); } DimContainer.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)dimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 8b04b545f5..111cc9d2c1 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -86,7 +86,6 @@ namespace osu.Game.Screens.Backgrounds b.Depth = newDepth; fadeContainer.Add(Background = b); - fadeContainer.ApplyInstantBlur(); StoryboardReplacesBackground.BindTo(fadeContainer.StoryboardReplacesBackground); } From 2467ece0205dc2b4827269c370892556bddd1ec9 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 20 Mar 2019 14:58:32 +0900 Subject: [PATCH 147/623] store a field --- osu.Game/Graphics/Containers/UserDimContainer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 22b0175a94..61129b43af 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -55,6 +54,8 @@ namespace osu.Game.Graphics.Containers ? new Vector2(BlurAmount.Value + (float)blurLevel.Value * 25) : new Vector2(BlurAmount.Value); + private Background background; + /// /// Creates a new . /// @@ -74,7 +75,10 @@ namespace osu.Game.Graphics.Containers { // We need to blur instantly here in the case of changing beatmap backgrounds, where blurring shouldn't be from 0 every time the beatmap is changed. if (drawable is Background b) + { + background = b; b.BlurTo(blurTarget, 0, Easing.OutQuint); + } base.Add(drawable); } @@ -113,7 +117,7 @@ namespace osu.Game.Graphics.Containers // This only works if the background is a direct child of DimContainer. // We can't blur the container like we did with the dim because buffered containers add considerable draw overhead. // As a result, this blurs the background directly via the direct children of DimContainer. - DimContainer.Children.OfType().FirstOrDefault()?.BlurTo(blurTarget, background_fade_duration, Easing.OutQuint); + background?.BlurTo(blurTarget, background_fade_duration, Easing.OutQuint); } DimContainer.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)dimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint); From f670e4664dd8f1dafd4fa1e38093b17f5106ba90 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 20 Mar 2019 15:06:07 +0900 Subject: [PATCH 148/623] Amend comment --- osu.Game/Graphics/Containers/UserDimContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 61129b43af..beb9653b71 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -73,7 +73,7 @@ namespace osu.Game.Graphics.Containers public override void Add(Drawable drawable) { - // We need to blur instantly here in the case of changing beatmap backgrounds, where blurring shouldn't be from 0 every time the beatmap is changed. + // Make sure we're already at the correct blur target when a background is added to the container. if (drawable is Background b) { background = b; From 1086688e0ad8505efeee32c74a1a6221a7b18d29 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 20 Mar 2019 15:13:59 +0900 Subject: [PATCH 149/623] Fix variable hiding --- .../Screens/Backgrounds/BackgroundScreenDefault.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 7697b9959e..7092ac0c4a 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -58,16 +58,16 @@ namespace osu.Game.Screens.Backgrounds private Background createBackground() { - Background background; + Background newBackground; if (user.Value?.IsSupporter ?? false) - background = new SkinnedBackground(skin.Value, backgroundName); + newBackground = new SkinnedBackground(skin.Value, backgroundName); else - background = new Background(backgroundName); + newBackground = new Background(backgroundName); - background.Depth = currentDisplay; + newBackground.Depth = currentDisplay; - return background; + return newBackground; } private class SkinnedBackground : Background From 6df275c83a4692a63205a84eb54bd8232e79da2d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 Mar 2019 23:39:45 +0900 Subject: [PATCH 150/623] Use fresh mods for each difficulty calculation iteration --- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++-- osu.Game/Rulesets/Mods/Mod.cs | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index db8bdde6bb..f31cce3c5f 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -37,6 +37,8 @@ namespace osu.Game.Rulesets.Difficulty /// A structure describing the difficulty of the beatmap. public DifficultyAttributes Calculate(params Mod[] mods) { + mods = mods.Select(m => m.CreateCopy()).ToArray(); + beatmap.Mods.Value = mods; IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo); @@ -55,9 +57,9 @@ namespace osu.Game.Rulesets.Difficulty foreach (var combination in CreateDifficultyAdjustmentModCombinations()) { if (combination is MultiMod multi) - yield return Calculate(multi.Mods); + yield return Calculate(multi.Mods.Select(m => m.CreateCopy()).ToArray()); else - yield return Calculate(combination); + yield return Calculate(combination.CreateCopy()); } } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 705c5c4ef6..1f9907caa7 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -65,5 +65,10 @@ namespace osu.Game.Rulesets.Mods /// [JsonIgnore] public virtual Type[] IncompatibleMods => new Type[] { }; + + /// + /// Creates a copy of this initialised to a default state. + /// + public virtual Mod CreateCopy() => (Mod)Activator.CreateInstance(GetType()); } } From c510385aa815cb69b57b4c2873709002bd45f57a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Mar 2019 16:46:16 +0900 Subject: [PATCH 151/623] Remove unnecessary allocations --- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index f31cce3c5f..aad55f8a38 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -57,9 +57,9 @@ namespace osu.Game.Rulesets.Difficulty foreach (var combination in CreateDifficultyAdjustmentModCombinations()) { if (combination is MultiMod multi) - yield return Calculate(multi.Mods.Select(m => m.CreateCopy()).ToArray()); + yield return Calculate(multi.Mods); else - yield return Calculate(combination.CreateCopy()); + yield return Calculate(combination); } } From e9034c8a30bbd4b0b412f04d9a1d055b804bf7e6 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 20 Mar 2019 16:50:47 +0900 Subject: [PATCH 152/623] Move blurring logic into Update instead --- osu.Game/Screens/Play/PlayerLoader.cs | 56 ++++++++++++--------------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 86156e292c..a2d433107b 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -2,13 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Screens; using osu.Framework.Threading; @@ -41,6 +39,8 @@ namespace osu.Game.Screens.Play public override bool DisallowExternalBeatmapRulesetChanges => true; + public override bool HandlePositionalInput => true; + private Task loadTask; public PlayerLoader(Func createPlayer) @@ -158,35 +158,6 @@ namespace osu.Game.Screens.Play private bool readyForPush => player.LoadState == LoadState.Ready && IsHovered && GetContainingInputManager()?.DraggedDrawable == null; - protected override bool OnHover(HoverEvent e) - { - // Acts as an "on hover lost" trigger for the visual settings panel. - // Returns background dim and blur to the values specified by PlayerLoader. - if (this.IsCurrentScreen()) - { - Background.BlurAmount.Value = BACKGROUND_BLUR; - Background.EnableUserDim.Value = false; - } - - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - // Acts as an "on hover" trigger for the visual settings panel. - // Preview user-defined background dim and blur when hovered on the visual settings panel. - if (GetContainingInputManager()?.HoveredDrawables.Contains(VisualSettings) == true) - { - if (this.IsCurrentScreen() && Background != null) - { - Background.BlurAmount.Value = 0; - Background.EnableUserDim.Value = true; - } - } - - base.OnHoverLost(e); - } - private void pushWhenLoaded() { if (!this.IsCurrentScreen()) return; @@ -267,6 +238,29 @@ namespace osu.Game.Screens.Play } } + protected override void Update() + { + base.Update(); + + if (!this.IsCurrentScreen()) + return; + + if (Background.BlurAmount.Value != 0 && VisualSettings.IsHovered) + { + // Acts as an "on hover" trigger for the visual settings panel. + // Preview user-defined background dim and blur when hovered on the visual settings panel. + Background.EnableUserDim.Value = true; + Background.BlurAmount.Value = 0; + } + else if (Background.BlurAmount.Value != BACKGROUND_BLUR && !VisualSettings.IsHovered) + { + // Acts as an "on hover lost" trigger for the visual settings panel. + // Returns background dim and blur to the values specified by PlayerLoader. + Background.EnableUserDim.Value = false; + Background.BlurAmount.Value = BACKGROUND_BLUR; + } + } + private class BeatmapMetadataDisplay : Container { private class MetadataLine : Container From a5916f995eb2e9814ef2faa02d11cfc7e8293fa1 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 20 Mar 2019 16:54:42 +0900 Subject: [PATCH 153/623] Use the previous conditional instead --- osu.Game/Screens/Play/PlayerLoader.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index a2d433107b..68335f96ce 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -245,14 +246,14 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; - if (Background.BlurAmount.Value != 0 && VisualSettings.IsHovered) + if (GetContainingInputManager()?.HoveredDrawables.Contains(VisualSettings) == true) { // Acts as an "on hover" trigger for the visual settings panel. // Preview user-defined background dim and blur when hovered on the visual settings panel. Background.EnableUserDim.Value = true; Background.BlurAmount.Value = 0; } - else if (Background.BlurAmount.Value != BACKGROUND_BLUR && !VisualSettings.IsHovered) + else { // Acts as an "on hover lost" trigger for the visual settings panel. // Returns background dim and blur to the values specified by PlayerLoader. From 41d25c7d19d6d7babd2c0194d77fc68bf8dcfc2b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Mar 2019 17:15:21 +0900 Subject: [PATCH 154/623] Fix post-merge errors --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 6c90e192ac..79858a9ccb 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly LoadingAnimation loadingAnimation; [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } public ScoresContainer() { @@ -71,7 +71,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } [BackgroundDependencyLoader] - private void load(APIAccess api, OsuColour colours) + private void load(OsuColour colours) { background.Colour = colours.Gray2; updateDisplay(); From f7016e1d2c81b4ab675c3a654ca3695bbf1265b5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Mar 2019 17:15:38 +0900 Subject: [PATCH 155/623] Rename DrawableScore --- osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs | 2 +- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- .../Scores/{DrawableScore.cs => ScoreTableScore.cs} | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Overlays/BeatmapSet/Scores/{DrawableScore.cs => ScoreTableScore.cs} (98%) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs index 20609dc595..58ccaf9dfa 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual { typeof(Header), typeof(ClickableUserContainer), - typeof(DrawableScore), + typeof(ScoreTableScore), typeof(DrawableTopScore), typeof(ScoresContainer), typeof(AuthorInfo), diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 5d248c5501..aacbc12cd8 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -47,7 +47,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores int index = 0; foreach (var s in value) - scoresFlow.Add(new DrawableScore(index++, s, maxModsAmount)); + scoresFlow.Add(new ScoreTableScore(index++, s, maxModsAmount)); } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScore.cs similarity index 98% rename from osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs rename to osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScore.cs index d7396c8839..a54770fb39 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScore.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet.Scores { - public class DrawableScore : Container + public class ScoreTableScore : Container { private const int fade_duration = 100; private const int text_size = 14; @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly Box hoveredBackground; private readonly Box background; - public DrawableScore(int index, APIScoreInfo score, int maxModsAmount) + public ScoreTableScore(int index, APIScoreInfo score, int maxModsAmount) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; From fbfef844de0e7ba250a08fee75a1780fd7605e84 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 20 Mar 2019 17:18:42 +0900 Subject: [PATCH 156/623] Add test case for resuming PlayerLoader --- .../Visual/TestCaseBackgroundScreenBeatmap.cs | 20 +++++++++++++++++++ osu.Game/Screens/Play/PlayerLoader.cs | 2 -- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index 084bb6a02e..193bc9fb08 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -223,6 +223,26 @@ namespace osu.Game.Tests.Visual AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect()); } + /// + /// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim. + /// + [Test] + public void ResumeFromPlayerTest() + { + performFullSetup(); + AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos)); + AddStep("Resume PlayerLoader", () => + { + player.ValidForResume = false; + player.RestartRequested?.Invoke(); + player.Exit(); + }); + waitForDim(); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); + AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect()); + } + private void waitForDim() => AddWaitStep("Wait for dim", 5); private void createFakeStoryboard() => AddStep("Create storyboard", () => diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 68335f96ce..92ff57b799 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -211,8 +211,6 @@ namespace osu.Game.Screens.Play public override void OnSuspending(IScreen next) { - Background.EnableUserDim.Value = true; - base.OnSuspending(next); cancelLoad(); } From b5e8eb2a4a55a27f431c73bba5c549283d9e55df Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 20 Mar 2019 17:31:06 +0900 Subject: [PATCH 157/623] Wait for dim --- osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index 193bc9fb08..5f5d84af42 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -240,6 +240,7 @@ namespace osu.Game.Tests.Visual waitForDim(); AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); + waitForDim(); AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect()); } From 7254b765b0e5e768473a3b0baf662cc1510c7d4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2019 19:26:24 +0900 Subject: [PATCH 158/623] Fix gameplay cursor showing in editor --- osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 50b3eabcf4..1a6e78d918 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Cursor; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; @@ -19,10 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit private class OsuPlayfieldNoCursor : OsuPlayfield { - public OsuPlayfieldNoCursor() - { - Cursor?.Expire(); - } + protected override CursorContainer CreateCursor() => null; } } } From 9013afe41243aaee9e13e6646661e3821fc50d02 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 20 Mar 2019 19:35:40 +0900 Subject: [PATCH 159/623] Query for input manager once --- osu.Game/Screens/Play/PlayerLoader.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 92ff57b799..da51e25b4e 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; using osu.Framework.Localisation; using osu.Framework.Screens; using osu.Framework.Threading; @@ -44,6 +45,8 @@ namespace osu.Game.Screens.Play private Task loadTask; + private InputManager inputManager; + public PlayerLoader(Func createPlayer) { this.createPlayer = createPlayer; @@ -203,6 +206,12 @@ namespace osu.Game.Screens.Play } } + protected override void LoadComplete() + { + inputManager = GetContainingInputManager(); + base.LoadComplete(); + } + private void cancelLoad() { pushDebounce?.Cancel(); @@ -244,7 +253,7 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; - if (GetContainingInputManager()?.HoveredDrawables.Contains(VisualSettings) == true) + if (inputManager.HoveredDrawables.Contains(VisualSettings)) { // Acts as an "on hover" trigger for the visual settings panel. // Preview user-defined background dim and blur when hovered on the visual settings panel. From 18de9e51c51c4f5b83e8a11cada9288b6e0af09b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2019 19:38:53 +0900 Subject: [PATCH 160/623] Formatting and naming improvements --- .../Graphics/Containers/UserDimContainer.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index beb9653b71..c790d6777d 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -38,9 +38,9 @@ namespace osu.Game.Graphics.Containers /// public readonly Bindable BlurAmount = new Bindable(); - private Bindable dimLevel { get; set; } + private Bindable userDimLevel { get; set; } - private Bindable blurLevel { get; set; } + private Bindable userBlurLevel { get; set; } private Bindable showStoryboard { get; set; } @@ -51,7 +51,7 @@ namespace osu.Game.Graphics.Containers private readonly bool isStoryboard; private Vector2 blurTarget => EnableUserDim.Value - ? new Vector2(BlurAmount.Value + (float)blurLevel.Value * 25) + ? new Vector2(BlurAmount.Value + (float)userBlurLevel.Value * 25) : new Vector2(BlurAmount.Value); private Background background; @@ -77,7 +77,7 @@ namespace osu.Game.Graphics.Containers if (drawable is Background b) { background = b; - b.BlurTo(blurTarget, 0, Easing.OutQuint); + background.BlurTo(blurTarget, 0, Easing.OutQuint); } base.Add(drawable); @@ -86,12 +86,13 @@ namespace osu.Game.Graphics.Containers [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - dimLevel = config.GetBindable(OsuSetting.DimLevel); - blurLevel = config.GetBindable(OsuSetting.BlurLevel); + userDimLevel = config.GetBindable(OsuSetting.DimLevel); + userBlurLevel = config.GetBindable(OsuSetting.BlurLevel); showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); + EnableUserDim.ValueChanged += _ => updateVisuals(); - dimLevel.ValueChanged += _ => updateVisuals(); - blurLevel.ValueChanged += _ => updateVisuals(); + userDimLevel.ValueChanged += _ => updateVisuals(); + userBlurLevel.ValueChanged += _ => updateVisuals(); showStoryboard.ValueChanged += _ => updateVisuals(); StoryboardReplacesBackground.ValueChanged += _ => updateVisuals(); BlurAmount.ValueChanged += _ => updateVisuals(); @@ -107,7 +108,7 @@ namespace osu.Game.Graphics.Containers { if (isStoryboard) { - DimContainer.FadeTo(!showStoryboard.Value || dimLevel.Value == 1 ? 0 : 1, background_fade_duration, Easing.OutQuint); + DimContainer.FadeTo(!showStoryboard.Value || userDimLevel.Value == 1 ? 0 : 1, background_fade_duration, Easing.OutQuint); } else { @@ -120,7 +121,7 @@ namespace osu.Game.Graphics.Containers background?.BlurTo(blurTarget, background_fade_duration, Easing.OutQuint); } - DimContainer.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)dimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint); + DimContainer.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)userDimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint); } } } From cda5bc28c8a0104ceb845f22a47b79e28633bb88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2019 19:41:20 +0900 Subject: [PATCH 161/623] Add proper comment --- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index da51e25b4e..50816a873f 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -253,16 +253,16 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; + // We need to perform this check here rather than in OnHover as any number of children of VisualSettings + // may also be handling the hover events. if (inputManager.HoveredDrawables.Contains(VisualSettings)) { - // Acts as an "on hover" trigger for the visual settings panel. // Preview user-defined background dim and blur when hovered on the visual settings panel. Background.EnableUserDim.Value = true; Background.BlurAmount.Value = 0; } else { - // Acts as an "on hover lost" trigger for the visual settings panel. // Returns background dim and blur to the values specified by PlayerLoader. Background.EnableUserDim.Value = false; Background.BlurAmount.Value = BACKGROUND_BLUR; From 8543e8c36f5416aab16b0ad9a4b2fc008d4ecbd6 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 20 Mar 2019 19:42:39 +0900 Subject: [PATCH 162/623] Put loadcomplete next to other protected overrides --- osu.Game/Screens/Play/PlayerLoader.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index da51e25b4e..ed8adc0e5f 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -157,6 +157,12 @@ namespace osu.Game.Screens.Play logo.Delay(resuming ? 0 : 500).MoveToOffset(new Vector2(0, -0.24f), 500, Easing.InOutExpo); } + protected override void LoadComplete() + { + inputManager = GetContainingInputManager(); + base.LoadComplete(); + } + private ScheduledDelegate pushDebounce; protected VisualSettings VisualSettings; @@ -206,12 +212,6 @@ namespace osu.Game.Screens.Play } } - protected override void LoadComplete() - { - inputManager = GetContainingInputManager(); - base.LoadComplete(); - } - private void cancelLoad() { pushDebounce?.Cancel(); From 01f795b0464757dec7e2f7edd57fc8c7025bd24f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2019 19:45:04 +0900 Subject: [PATCH 163/623] Make restart public rather than reimplementing --- osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs | 7 +------ osu.Game/Screens/Play/Player.cs | 8 ++++---- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index 5f5d84af42..6f7d465cfe 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -231,12 +231,7 @@ namespace osu.Game.Tests.Visual { performFullSetup(); AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos)); - AddStep("Resume PlayerLoader", () => - { - player.ValidForResume = false; - player.RestartRequested?.Invoke(); - player.Exit(); - }); + AddStep("Resume PlayerLoader", () => player.Restart()); waitForDim(); AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 1ce8bf559d..7071841722 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -112,7 +112,7 @@ namespace osu.Game.Screens.Play PausableGameplayContainer = new PausableGameplayContainer { Retries = RestartCount, - OnRetry = restart, + OnRetry = Restart, OnQuit = performUserRequestedExit, Start = gameplayClockContainer.Start, Stop = gameplayClockContainer.Stop, @@ -154,7 +154,7 @@ namespace osu.Game.Screens.Play }, failOverlay = new FailOverlay { - OnRetry = restart, + OnRetry = Restart, OnQuit = performUserRequestedExit, }, new HotkeyRetryOverlay @@ -164,7 +164,7 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; fadeOut(true); - restart(); + Restart(); }, } }; @@ -235,7 +235,7 @@ namespace osu.Game.Screens.Play this.Exit(); } - private void restart() + public void Restart() { if (!this.IsCurrentScreen()) return; From 2ff7aa6c2e448d36800ea6584b7256b79eee8c2a Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 20 Mar 2019 19:49:37 +0900 Subject: [PATCH 164/623] Remove handle positional input --- osu.Game/Screens/Play/PlayerLoader.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 7163e95816..902764faa5 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -41,8 +41,6 @@ namespace osu.Game.Screens.Play public override bool DisallowExternalBeatmapRulesetChanges => true; - public override bool HandlePositionalInput => true; - private Task loadTask; private InputManager inputManager; From 2e9d823af47cb247c7b320729f38100afa6ca0f4 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 20 Mar 2019 19:59:54 +0900 Subject: [PATCH 165/623] Add comment regarding HandlePositionalInput --- osu.Game/Screens/Play/PlayerLoader.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 902764faa5..299fc2d299 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -164,6 +164,9 @@ namespace osu.Game.Screens.Play private ScheduledDelegate pushDebounce; protected VisualSettings VisualSettings; + // HandlePositionalInput is being set to true here because IsHovered will not update unless we do so. + public override bool HandlePositionalInput => true; + private bool readyForPush => player.LoadState == LoadState.Ready && IsHovered && GetContainingInputManager()?.DraggedDrawable == null; private void pushWhenLoaded() From 8865d539923efcb1be11d3e141593f9fd87b28cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2019 20:04:07 +0900 Subject: [PATCH 166/623] Reword comment --- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 299fc2d299..9eebce4888 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -164,7 +164,7 @@ namespace osu.Game.Screens.Play private ScheduledDelegate pushDebounce; protected VisualSettings VisualSettings; - // HandlePositionalInput is being set to true here because IsHovered will not update unless we do so. + // required for IsHovered usage in readyForPush public override bool HandlePositionalInput => true; private bool readyForPush => player.LoadState == LoadState.Ready && IsHovered && GetContainingInputManager()?.DraggedDrawable == null; From ba89bfee0c195e5368545ee85a5f2b7da4f02422 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2019 20:12:46 +0900 Subject: [PATCH 167/623] Define explicit method to add background Also cleans up some redundant/misplaced comments. --- .../Graphics/Containers/UserDimContainer.cs | 27 ++++++++++++------- .../Backgrounds/BackgroundScreenBeatmap.cs | 2 +- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index c790d6777d..18a45dd0cd 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -50,12 +51,13 @@ namespace osu.Game.Graphics.Containers private readonly bool isStoryboard; + /// + /// As an optimisation, we add the two blur portions to be applied rather than actually applying two separate blurs. + /// private Vector2 blurTarget => EnableUserDim.Value ? new Vector2(BlurAmount.Value + (float)userBlurLevel.Value * 25) : new Vector2(BlurAmount.Value); - private Background background; - /// /// Creates a new . /// @@ -71,14 +73,22 @@ namespace osu.Game.Graphics.Containers AddInternal(DimContainer = new Container { RelativeSizeAxes = Axes.Both }); } - public override void Add(Drawable drawable) + private Background background; + + public Background Background { - // Make sure we're already at the correct blur target when a background is added to the container. - if (drawable is Background b) + get => background; + set { - background = b; + background = value; background.BlurTo(blurTarget, 0, Easing.OutQuint); } + } + + public override void Add(Drawable drawable) + { + if (drawable is Background) + throw new InvalidOperationException($"Use {nameof(Background)} to set a background."); base.Add(drawable); } @@ -115,10 +125,7 @@ namespace osu.Game.Graphics.Containers // The background needs to be hidden in the case of it being replaced by the storyboard DimContainer.FadeTo(showStoryboard.Value && StoryboardReplacesBackground.Value ? 0 : 1, background_fade_duration, Easing.OutQuint); - // This only works if the background is a direct child of DimContainer. - // We can't blur the container like we did with the dim because buffered containers add considerable draw overhead. - // As a result, this blurs the background directly via the direct children of DimContainer. - background?.BlurTo(blurTarget, background_fade_duration, Easing.OutQuint); + Background?.BlurTo(blurTarget, background_fade_duration, Easing.OutQuint); } DimContainer.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)userDimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 111cc9d2c1..6df418753c 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Backgrounds } b.Depth = newDepth; - fadeContainer.Add(Background = b); + fadeContainer.Background = Background = b; StoryboardReplacesBackground.BindTo(fadeContainer.StoryboardReplacesBackground); } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 9eebce4888..e9ee5d3fa8 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -164,7 +164,7 @@ namespace osu.Game.Screens.Play private ScheduledDelegate pushDebounce; protected VisualSettings VisualSettings; - // required for IsHovered usage in readyForPush + // Hhere because IsHovered will not update unless we do so. public override bool HandlePositionalInput => true; private bool readyForPush => player.LoadState == LoadState.Ready && IsHovered && GetContainingInputManager()?.DraggedDrawable == null; From 298c98871d663d8f1d3ed3ad65c65c67b079f56b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2019 20:18:08 +0900 Subject: [PATCH 168/623] Actually add the background --- osu.Game/Graphics/Containers/UserDimContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 18a45dd0cd..b078f40420 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -80,7 +80,7 @@ namespace osu.Game.Graphics.Containers get => background; set { - background = value; + base.Add(background = value); background.BlurTo(blurTarget, 0, Easing.OutQuint); } } From 6a7ab6a0010fd9534d92b17735280750d82c57d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2019 20:47:25 +0900 Subject: [PATCH 169/623] Do less import work in [SetUp] step --- .../Visual/TestCaseBackgroundScreenBeatmap.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index 6f7d465cfe..8636e7721e 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -74,21 +74,17 @@ namespace osu.Game.Tests.Visual Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, null, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); + manager.Import(TestResources.GetTestBeatmapForImport()); + Beatmap.SetDefault(); } [SetUp] - public virtual void SetUp() + public virtual void SetUp() => Schedule(() => { - Schedule(() => - { - manager.Delete(manager.GetAllUsableBeatmapSets()); - var temp = TestResources.GetTestBeatmapForImport(); - manager.Import(temp); - Child = screenStackContainer = new ScreenStackCacheContainer { RelativeSizeAxes = Axes.Both }; - screenStackContainer.ScreenStack.Push(songSelect = new DummySongSelect()); - }); - } + Child = screenStackContainer = new ScreenStackCacheContainer { RelativeSizeAxes = Axes.Both }; + screenStackContainer.ScreenStack.Push(songSelect = new DummySongSelect()); + }); /// /// Check if properly triggers the visual settings preview when a user hovers over the visual settings panel. From c555019692ec11fb24fa7e5258b819b69716ffe4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Mar 2019 22:50:50 +0900 Subject: [PATCH 170/623] Update framework --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c02207702c..d8561770fd 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 2633da77b3..3dcb647cd2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From b17c5c0bb317e67dacfa35e74827f6c8591601a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Mar 2019 16:11:44 +0900 Subject: [PATCH 171/623] Add more tests for exitability --- osu.Game.Tests/Visual/TestCasePause.cs | 31 +++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/TestCasePause.cs b/osu.Game.Tests/Visual/TestCasePause.cs index 834b5e8ef1..8fcf6164a4 100644 --- a/osu.Game.Tests/Visual/TestCasePause.cs +++ b/osu.Game.Tests/Visual/TestCasePause.cs @@ -43,17 +43,40 @@ namespace osu.Game.Tests.Visual AddAssert("pause overlay hidden", () => !Player.PauseOverlayVisible); } + [Test] + public void TestExitTooSoon() + { + AddStep("pause", () => Player.Pause()); + AddAssert("clock stopped", () => !Player.GameplayClockContainer.GameplayClock.IsRunning); + AddStep("resume", () => Player.Resume()); + AddAssert("clock started", () => Player.GameplayClockContainer.GameplayClock.IsRunning); + AddStep("pause too soon", () => Player.Exit()); + AddAssert("clock not stopped", () => Player.GameplayClockContainer.GameplayClock.IsRunning); + AddAssert("pause overlay hidden", () => !Player.PauseOverlayVisible); + AddAssert("not exited", () => Player.IsCurrentScreen()); + } + [Test] public void TestPauseAfterFail() { AddUntilStep("wait for fail", () => Player.HasFailed); - AddAssert("fail overlay shown", () => Player.FailOverlayVisible); AddStep("try to pause", () => Player.Pause()); AddAssert("pause overlay hidden", () => !Player.PauseOverlayVisible); AddAssert("fail overlay still shown", () => Player.FailOverlayVisible); + + confirmExit(); + } + + [Test] + public void TestExitFromGameplay() + { + AddStep("exit", () => Player.Exit()); + AddUntilStep("wait for pause", () => Player.PauseOverlayVisible); + + confirmExit(); } [Test] @@ -65,6 +88,12 @@ namespace osu.Game.Tests.Visual return Player.PauseOverlayVisible; }); + confirmExit(); + } + + private void confirmExit() + { + AddUntilStep("player not exited", () => Player.IsCurrentScreen()); AddStep("exit", () => Player.Exit()); AddUntilStep("player exited", () => !Player.IsCurrentScreen()); } From aa1dfdd6631572a21aa9c20c02a3e6a064c235a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Mar 2019 16:33:34 +0900 Subject: [PATCH 172/623] Extract logic shared between tests --- osu.Game.Tests/Visual/TestCasePause.cs | 102 +++++++++++++++++-------- 1 file changed, 69 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCasePause.cs b/osu.Game.Tests/Visual/TestCasePause.cs index 8fcf6164a4..d5d2cebbab 100644 --- a/osu.Game.Tests/Visual/TestCasePause.cs +++ b/osu.Game.Tests/Visual/TestCasePause.cs @@ -23,36 +23,34 @@ namespace osu.Game.Tests.Visual [Test] public void TestPauseResume() { - AddStep("pause", () => Player.Pause()); - AddAssert("clock stopped", () => !Player.GameplayClockContainer.GameplayClock.IsRunning); - AddAssert("pause overlay shown", () => Player.PauseOverlayVisible); - - AddStep("resume", () => Player.Resume()); - AddAssert("pause overlay hidden", () => !Player.PauseOverlayVisible); + pauseAndConfirm(); + resumeAndConfirm(); } [Test] public void TestPauseTooSoon() { - AddStep("pause", () => Player.Pause()); - AddAssert("clock stopped", () => !Player.GameplayClockContainer.GameplayClock.IsRunning); - AddStep("resume", () => Player.Resume()); - AddAssert("clock started", () => Player.GameplayClockContainer.GameplayClock.IsRunning); - AddStep("pause too soon", () => Player.Pause()); - AddAssert("clock not stopped", () => Player.GameplayClockContainer.GameplayClock.IsRunning); - AddAssert("pause overlay hidden", () => !Player.PauseOverlayVisible); + pauseAndConfirm(); + resumeAndConfirm(); + + pause(); + + confirmClockRunning(true); + confirmPauseOverlayShown(false); } [Test] public void TestExitTooSoon() { - AddStep("pause", () => Player.Pause()); - AddAssert("clock stopped", () => !Player.GameplayClockContainer.GameplayClock.IsRunning); - AddStep("resume", () => Player.Resume()); - AddAssert("clock started", () => Player.GameplayClockContainer.GameplayClock.IsRunning); - AddStep("pause too soon", () => Player.Exit()); - AddAssert("clock not stopped", () => Player.GameplayClockContainer.GameplayClock.IsRunning); - AddAssert("pause overlay hidden", () => !Player.PauseOverlayVisible); + pauseAndConfirm(); + + resume(); + + AddStep("exit too soon", () => Player.Exit()); + + confirmClockRunning(true); + confirmPauseOverlayShown(false); + AddAssert("not exited", () => Player.IsCurrentScreen()); } @@ -62,42 +60,80 @@ namespace osu.Game.Tests.Visual AddUntilStep("wait for fail", () => Player.HasFailed); AddAssert("fail overlay shown", () => Player.FailOverlayVisible); - AddStep("try to pause", () => Player.Pause()); + confirmClockRunning(false); + + pause(); + + confirmClockRunning(false); + confirmPauseOverlayShown(false); - AddAssert("pause overlay hidden", () => !Player.PauseOverlayVisible); AddAssert("fail overlay still shown", () => Player.FailOverlayVisible); - confirmExit(); + exitAndConfirm(); } [Test] public void TestExitFromGameplay() { AddStep("exit", () => Player.Exit()); - AddUntilStep("wait for pause", () => Player.PauseOverlayVisible); - confirmExit(); + confirmPaused(); + + exitAndConfirm(); } [Test] public void TestExitFromPause() { - AddUntilStep("keep trying to pause", () => - { - Player.Pause(); - return Player.PauseOverlayVisible; - }); - - confirmExit(); + pauseAndConfirm(); + exitAndConfirm(); } - private void confirmExit() + private void pauseAndConfirm() + { + pause(); + confirmPaused(); + } + + private void resumeAndConfirm() + { + resume(); + confirmResumed(); + } + + private void exitAndConfirm() { AddUntilStep("player not exited", () => Player.IsCurrentScreen()); AddStep("exit", () => Player.Exit()); + confirmExited(); + } + + private void confirmPaused() + { + confirmClockRunning(false); + AddAssert("pause overlay shown", () => Player.PauseOverlayVisible); + } + + private void confirmResumed() + { + confirmClockRunning(true); + confirmPauseOverlayShown(false); + } + + private void confirmExited() + { AddUntilStep("player exited", () => !Player.IsCurrentScreen()); } + private void pause() => AddStep("pause", () => Player.Pause()); + private void resume() => AddStep("resume", () => Player.Resume()); + + private void confirmPauseOverlayShown(bool isShown) => + AddAssert("pause overlay " + (isShown ? "shown" : "hidden"), () => Player.PauseOverlayVisible == isShown); + + private void confirmClockRunning(bool isRunning) => + AddAssert("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.GameplayClock.IsRunning == isRunning); + protected override bool AllowFail => true; protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer(); From e2503888a416a7187af1cf8aceca69a3291a3596 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Mar 2019 20:51:06 +0900 Subject: [PATCH 173/623] Expose carousel's loaded flag --- osu.Game/Screens/Select/BeatmapCarousel.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index bfd1d3d236..7b9559f5b4 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -55,9 +55,9 @@ namespace osu.Game.Screens.Select public override bool HandlePositionalInput => AllowSelection; /// - /// Used to avoid firing null selections before the initial beatmaps have been loaded via . + /// Whether carousel items have completed asynchronously loaded. /// - private bool initialLoadComplete; + public bool BeatmapSetsLoaded { get; private set; } private IEnumerable beatmapSets => root.Children.OfType(); @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Select Schedule(() => { BeatmapSetsChanged?.Invoke(); - initialLoadComplete = true; + BeatmapSetsLoaded = true; }); })); } @@ -593,7 +593,7 @@ namespace osu.Game.Screens.Select currentY += DrawHeight / 2; scrollableContent.Height = currentY; - if (initialLoadComplete && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected)) + if (BeatmapSetsLoaded && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected)) { selectedBeatmapSet = null; SelectionChanged?.Invoke(null); From 96dba6a20d8d831a5d741c6d7ff4e4fd1852c945 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Mar 2019 20:51:21 +0900 Subject: [PATCH 174/623] Move nested method to bottom of class --- osu.Game/Screens/Select/SongSelect.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index a86d0beb39..3f56d38a1b 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -373,6 +373,13 @@ namespace osu.Game.Screens.Select var beatmap = beatmapNoDebounce; var ruleset = rulesetNoDebounce; + selectionChangedDebounce?.Cancel(); + + if (beatmap == null) + run(); + else + selectionChangedDebounce = Scheduler.AddDelayed(run, 200); + void run() { Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ID.ToString() ?? "null"}"); @@ -417,13 +424,6 @@ namespace osu.Game.Screens.Select if (this.IsCurrentScreen()) ensurePlayingSelected(preview); UpdateBeatmap(Beatmap.Value); } - - selectionChangedDebounce?.Cancel(); - - if (beatmap == null) - run(); - else - selectionChangedDebounce = Scheduler.AddDelayed(run, 200); } private void triggerRandom() From 4789aa81cb7f1e12fe8aadc0c2d5028a5c36769c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Mar 2019 20:52:15 +0900 Subject: [PATCH 175/623] Add an explicit flag for tracking song select's bindable binding Not required (this change does not affect logic) but improves clarity. --- osu.Game/Screens/Select/SongSelect.cs | 33 +++++++++++++++++---------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 3f56d38a1b..cc79cc5961 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -593,18 +593,7 @@ namespace osu.Game.Screens.Select private void carouselBeatmapsLoaded() { - if (rulesetNoDebounce == null) - { - // manual binding to parent ruleset to allow for delayed load in the incoming direction. - rulesetNoDebounce = decoupledRuleset.Value = Ruleset.Value; - Ruleset.ValueChanged += r => updateSelectedRuleset(r.NewValue); - - decoupledRuleset.ValueChanged += r => Ruleset.Value = r.NewValue; - decoupledRuleset.DisabledChanged += r => Ruleset.Disabled = r; - - Beatmap.BindDisabledChanged(disabled => Carousel.AllowSelection = !disabled, true); - Beatmap.BindValueChanged(workingBeatmapChanged); - } + bindBindables(); if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false && Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, false)) @@ -618,6 +607,26 @@ namespace osu.Game.Screens.Select } } + private bool boundLocalBindables; + + private void bindBindables() + { + if (boundLocalBindables) + return; + + // manual binding to parent ruleset to allow for delayed load in the incoming direction. + rulesetNoDebounce = decoupledRuleset.Value = Ruleset.Value; + Ruleset.ValueChanged += r => updateSelectedRuleset(r.NewValue); + + decoupledRuleset.ValueChanged += r => Ruleset.Value = r.NewValue; + decoupledRuleset.DisabledChanged += r => Ruleset.Disabled = r; + + Beatmap.BindDisabledChanged(disabled => Carousel.AllowSelection = !disabled, true); + Beatmap.BindValueChanged(workingBeatmapChanged); + + boundLocalBindables = true; + } + private void delete(BeatmapSetInfo beatmap) { if (beatmap == null || beatmap.ID <= 0) return; From a10e43410a994f54d1c58edcc122c6d433502050 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Mar 2019 20:52:34 +0900 Subject: [PATCH 176/623] Fix song select potentially starting play before the carousel (and bindables) have been initialised --- osu.Game/Screens/Select/SongSelect.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index cc79cc5961..8758df5151 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -300,6 +300,10 @@ namespace osu.Game.Screens.Select /// Whether to trigger . public void FinaliseSelection(BeatmapInfo beatmap = null, bool performStartAction = true) { + // This is very important as we have not yet bound to screen-level bindables before the carousel load is completed. + if (!Carousel.BeatmapSetsLoaded) + return; + // if we have a pending filter operation, we want to run it now. // it could change selection (ie. if the ruleset has been changed). Carousel.FlushPendingFilterOperations(); @@ -610,19 +614,19 @@ namespace osu.Game.Screens.Select private bool boundLocalBindables; private void bindBindables() - { + { if (boundLocalBindables) return; - // manual binding to parent ruleset to allow for delayed load in the incoming direction. - rulesetNoDebounce = decoupledRuleset.Value = Ruleset.Value; - Ruleset.ValueChanged += r => updateSelectedRuleset(r.NewValue); + // manual binding to parent ruleset to allow for delayed load in the incoming direction. + rulesetNoDebounce = decoupledRuleset.Value = Ruleset.Value; + Ruleset.ValueChanged += r => updateSelectedRuleset(r.NewValue); - decoupledRuleset.ValueChanged += r => Ruleset.Value = r.NewValue; - decoupledRuleset.DisabledChanged += r => Ruleset.Disabled = r; + decoupledRuleset.ValueChanged += r => Ruleset.Value = r.NewValue; + decoupledRuleset.DisabledChanged += r => Ruleset.Disabled = r; - Beatmap.BindDisabledChanged(disabled => Carousel.AllowSelection = !disabled, true); - Beatmap.BindValueChanged(workingBeatmapChanged); + Beatmap.BindDisabledChanged(disabled => Carousel.AllowSelection = !disabled, true); + Beatmap.BindValueChanged(workingBeatmapChanged); boundLocalBindables = true; } From 37010f97d72249080df05fc5e2fe19f395bb7bc4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Mar 2019 21:02:45 +0900 Subject: [PATCH 177/623] Fix random select crashing Closes #3383. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index bfd1d3d236..95c89e3852 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -327,6 +327,9 @@ namespace osu.Game.Screens.Select private void select(CarouselItem item) { + if (!AllowSelection) + return; + if (item == null) return; item.State.Value = CarouselItemState.Selected; From d6f17a5ecda21ea6b640561e0912d5bee01b10a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Mar 2019 02:41:21 +0900 Subject: [PATCH 178/623] Bring nuget packages up to date --- osu.Desktop/osu.Desktop.csproj | 4 ++-- .../osu.Game.Rulesets.Catch.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Mania.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Osu.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Taiko.Tests.csproj | 4 ++-- osu.Game.Tests/osu.Game.Tests.csproj | 4 ++-- osu.Game/osu.Game.csproj | 8 ++++---- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 874f73da6d..66db439c82 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -27,8 +27,8 @@ - - + + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index feab3ed81c..3f8b3bf086 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -2,9 +2,9 @@ - + - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index e26d2433f9..fd17285a38 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -2,9 +2,9 @@ - + - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 273d29c3de..8c31db9a7d 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -2,9 +2,9 @@ - + - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index fade054382..72ce6c947b 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -2,9 +2,9 @@ - + - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index b22c1aed99..938e1ae0f8 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -3,9 +3,9 @@ - + - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d8561770fd..dd69faad56 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -11,13 +11,13 @@ - - - + + + - + From d5272a83cfef5bc12ae9d52abf701d20389e4e4c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 21 Mar 2019 11:16:10 -0700 Subject: [PATCH 179/623] Rename "urgent" to "topMost" --- osu.Game/OsuGame.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index f952f92f81..b1ba2cae03 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -401,7 +401,7 @@ namespace osu.Game }, overlayContent = new Container { RelativeSizeAxes = Axes.Both }, floatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, - urgentOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, + topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, idleTracker = new GameIdleTracker(6000) }); @@ -421,7 +421,7 @@ namespace osu.Game CloseAllOverlays(false); menuScreen?.MakeCurrent(); }, - }, urgentOverlayContent.Add); + }, topMostOverlayContent.Add); loadComponentSingleFile(volume = new VolumeOverlay(), floatingOverlayContent.Add); loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add); @@ -450,11 +450,11 @@ namespace osu.Game Origin = Anchor.TopRight, }, floatingOverlayContent.Add); - loadComponentSingleFile(accountCreation = new AccountCreationOverlay(), urgentOverlayContent.Add); + loadComponentSingleFile(accountCreation = new AccountCreationOverlay(), topMostOverlayContent.Add); - loadComponentSingleFile(dialogOverlay = new DialogOverlay(), urgentOverlayContent.Add); + loadComponentSingleFile(dialogOverlay = new DialogOverlay(), topMostOverlayContent.Add); - loadComponentSingleFile(externalLinkOpener = new ExternalLinkOpener(), urgentOverlayContent.Add); + loadComponentSingleFile(externalLinkOpener = new ExternalLinkOpener(), topMostOverlayContent.Add); dependencies.CacheAs(idleTracker); dependencies.Cache(settings); @@ -700,7 +700,7 @@ namespace osu.Game private Container floatingOverlayContent; - private Container urgentOverlayContent; + private Container topMostOverlayContent; private FrameworkConfigManager frameworkConfig; private ScalingContainer screenContainer; From 454c82c49eb77c228f50c60182ae87b24ec819b2 Mon Sep 17 00:00:00 2001 From: jorolf Date: Thu, 21 Mar 2019 22:18:45 +0100 Subject: [PATCH 180/623] Don't go outside of the container bounds --- .../Graphics/UserInterface/ScreenTitle.cs | 35 +++++++++++-------- osu.Game/Screens/Multi/Header.cs | 1 + 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ScreenTitle.cs b/osu.Game/Graphics/UserInterface/ScreenTitle.cs index 14c87bb59d..dd0b06f969 100644 --- a/osu.Game/Graphics/UserInterface/ScreenTitle.cs +++ b/osu.Game/Graphics/UserInterface/ScreenTitle.cs @@ -44,30 +44,35 @@ namespace osu.Game.Graphics.UserInterface InternalChildren = new Drawable[] { - iconSprite = new SpriteIcon - { - Size = new Vector2(25), - Anchor = Anchor.TopLeft, - Origin = Anchor.TopRight, - Margin = new MarginPadding { Right = 10 }, - }, new FillFlowContainer { AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(6, 0), - Children = new[] + Spacing = new Vector2(10, 0), + Children = new Drawable[] { - titleText = new OsuSpriteText + iconSprite = new SpriteIcon { - Font = OsuFont.GetFont(size: 25), + Size = new Vector2(25), }, - pageText = new OsuSpriteText + new FillFlowContainer { - Font = OsuFont.GetFont(size: 25), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(6, 0), + Children = new[] + { + titleText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 25), + }, + pageText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 25), + } + } } } - } + }, }; } } diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 0f945d107f..3b9bd7ca4a 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -42,6 +42,7 @@ namespace osu.Game.Screens.Multi { Anchor = Anchor.CentreLeft, Origin = Anchor.BottomLeft, + X = -35, }, breadcrumbs = new HeaderBreadcrumbControl(stack) { From dc004910d702e9d0e2794bc1d3fb2b7f296c0847 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Mar 2019 11:55:35 +0900 Subject: [PATCH 181/623] Fix AccountCreationOverlay tests and better complete dummy api's behaviour --- .../Visual/TestCaseAccountCreationOverlay.cs | 30 +++++++++++-- osu.Game.Tests/Visual/TestCaseDisclaimer.cs | 2 + osu.Game/Online/API/DummyAPIAccess.cs | 45 ++++++++++++++++--- 3 files changed, 68 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseAccountCreationOverlay.cs b/osu.Game.Tests/Visual/TestCaseAccountCreationOverlay.cs index 543a43b439..24380645d1 100644 --- a/osu.Game.Tests/Visual/TestCaseAccountCreationOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseAccountCreationOverlay.cs @@ -3,9 +3,13 @@ using System; using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.AccountCreation; +using osu.Game.Users; namespace osu.Game.Tests.Visual { @@ -21,12 +25,32 @@ namespace osu.Game.Tests.Visual typeof(AccountCreationScreen), }; + [Cached(typeof(IAPIProvider))] + private DummyAPIAccess api = new DummyAPIAccess(); + public TestCaseAccountCreationOverlay() { - var accountCreation = new AccountCreationOverlay(); - Child = accountCreation; + Container userPanelArea; + AccountCreationOverlay accountCreation; - accountCreation.State = Visibility.Visible; + Children = new Drawable[] + { + api, + accountCreation = new AccountCreationOverlay(), + userPanelArea = new Container + { + Padding = new MarginPadding(10), + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + }; + + api.Logout(); + api.LocalUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true); + + AddStep("show", () => accountCreation.State = Visibility.Visible); + AddStep("logout", () => api.Logout()); } } } diff --git a/osu.Game.Tests/Visual/TestCaseDisclaimer.cs b/osu.Game.Tests/Visual/TestCaseDisclaimer.cs index f08a2a54ca..8bba16e4b4 100644 --- a/osu.Game.Tests/Visual/TestCaseDisclaimer.cs +++ b/osu.Game.Tests/Visual/TestCaseDisclaimer.cs @@ -16,6 +16,8 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { + Add(api); + AddStep("load disclaimer", () => LoadScreen(new Disclaimer())); AddStep("toggle support", () => diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 0cb49951f7..4d530a698e 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -1,12 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Threading; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Game.Users; namespace osu.Game.Online.API { - public class DummyAPIAccess : IAPIProvider + public class DummyAPIAccess : Component, IAPIProvider { public Bindable LocalUser { get; } = new Bindable(new User { @@ -20,7 +23,29 @@ namespace osu.Game.Online.API public string Endpoint => "http://localhost"; - public APIState State => LocalUser.Value.Id == 1 ? APIState.Offline : APIState.Online; + private APIState state = APIState.Online; + + private readonly List components = new List(); + + public APIState State + { + get => state; + private set + { + APIState oldState = state; + APIState newState = value; + + state = value; + + if (oldState != newState) + { + Scheduler.Add(delegate + { + components.ForEach(c => c.APIStateChanged(this, newState)); + }); + } + } + } public virtual void Queue(APIRequest request) { @@ -28,28 +53,36 @@ namespace osu.Game.Online.API public void Register(IOnlineComponent component) { - // todo: add support + Scheduler.Add(delegate { components.Add(component); }); + component.APIStateChanged(this, state); } public void Unregister(IOnlineComponent component) { - // todo: add support + Scheduler.Add(delegate { components.Remove(component); }); } public void Login(string username, string password) { LocalUser.Value = new User { - Username = @"Dummy", + Username = username, Id = 1001, }; + + State = APIState.Online; } public void Logout() { LocalUser.Value = new GuestUser(); + State = APIState.Offline; } - public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password) => null; + public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password) + { + Thread.Sleep(200); + return null; + } } } From dcae86e39a0d9b4058d6f77a2cdc9c05bb3c922e Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 22 Mar 2019 14:10:38 +0900 Subject: [PATCH 182/623] Add a new test for OsuScreenStack, fix parallax --- .../Visual/TestCaseOsuScreenStack.cs | 45 +++++++++++++++++++ osu.Game/OsuGame.cs | 2 + osu.Game/Screens/OsuScreenStack.cs | 8 +++- osu.Game/Tests/Visual/AllPlayersTestCase.cs | 10 ++--- osu.Game/Tests/Visual/PlayerTestCase.cs | 7 +-- 5 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs diff --git a/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs b/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs new file mode 100644 index 0000000000..c3a9e94448 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Graphics.Containers; +using osu.Game.Screens; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseOsuScreenStack : OsuTestCase + { + private TestScreen baseScreen; + private TestOsuScreenStack stack; + + [SetUpSteps] + public void Setup() + { + AddStep("Create new screen stack", () => { Child = stack = new TestOsuScreenStack { RelativeSizeAxes = Axes.Both }; }); + AddStep("Push new base screen", () => stack.Push(baseScreen = new TestScreen())); + } + + [Test] + public void ParallaxAssignmentTest() + { + AddStep("Push new screen to base screen", () => baseScreen.Push(new TestScreen())); + AddAssert("Parallax is correct", () => stack.IsParallaxSet); + AddStep("Exit from new screen", () => { baseScreen.MakeCurrent(); }); + AddAssert("Parallax is correct", () => stack.IsParallaxSet); + } + + private class TestScreen : ScreenWithBeatmapBackground + { + } + + private class TestOsuScreenStack : OsuScreenStack + { + public bool IsParallaxSet => ParallaxAmount == ((TestScreen)CurrentScreen).BackgroundParallaxAmount * ParallaxContainer.DEFAULT_PARALLAX_AMOUNT; + } + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ad2980d818..079a0d5bb6 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -406,6 +406,8 @@ namespace osu.Game loadComponentSingleFile(osuLogo, logo => { logoContainer.Add(logo); + + // Loader has to be created synchronously in order for DI to be successful for its background screen stack. screenStack.Push(new Loader { RelativeSizeAxes = Axes.Both diff --git a/osu.Game/Screens/OsuScreenStack.cs b/osu.Game/Screens/OsuScreenStack.cs index 2eea7bebbb..1c929470d1 100644 --- a/osu.Game/Screens/OsuScreenStack.cs +++ b/osu.Game/Screens/OsuScreenStack.cs @@ -15,6 +15,8 @@ namespace osu.Game.Screens private ParallaxContainer parallaxContainer; + protected float ParallaxAmount => parallaxContainer.ParallaxAmount; + public OsuScreenStack() { initializeStack(); @@ -35,11 +37,15 @@ namespace osu.Game.Screens }; ScreenPushed += setParallax; + ScreenExited += setParallax; } private void setParallax(IScreen prev, IScreen next) { - parallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * ((IOsuScreen)next).BackgroundParallaxAmount; + if (next != null) + { + parallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * ((IOsuScreen)next).BackgroundParallaxAmount; + } } } } diff --git a/osu.Game/Tests/Visual/AllPlayersTestCase.cs b/osu.Game/Tests/Visual/AllPlayersTestCase.cs index 507848730f..03bd7b218a 100644 --- a/osu.Game/Tests/Visual/AllPlayersTestCase.cs +++ b/osu.Game/Tests/Visual/AllPlayersTestCase.cs @@ -73,15 +73,11 @@ namespace osu.Game.Tests.Visual Player?.Exit(); Player = null; - var player = CreatePlayer(r); + Player = CreatePlayer(r); - LoadComponentAsync(player, p => - { - Player = p; - LoadScreen(p); - }); + LoadScreen(Player); - return player; + return Player; } protected virtual Player CreatePlayer(Ruleset ruleset) => new Player diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index ad01d82281..50cb839ed9 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -52,11 +52,8 @@ namespace osu.Game.Tests.Visual if (!AllowFail) Beatmap.Value.Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; - LoadComponentAsync(Player = CreatePlayer(ruleset), p => - { - Player = p; - LoadScreen(p); - }); + Player = CreatePlayer(ruleset); + LoadScreen(Player); } protected virtual Player CreatePlayer(Ruleset ruleset) => new Player From 860999ad29eaa138d8e3125740abe085cbc3cd41 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 Mar 2019 14:20:53 +0900 Subject: [PATCH 183/623] Cleanup --- osu.Game/Online/API/DummyAPIAccess.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 4d530a698e..99fde10309 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -32,18 +32,12 @@ namespace osu.Game.Online.API get => state; private set { - APIState oldState = state; - APIState newState = value; + if (state == value) + return; state = value; - if (oldState != newState) - { - Scheduler.Add(delegate - { - components.ForEach(c => c.APIStateChanged(this, newState)); - }); - } + Scheduler.Add(() => components.ForEach(c => c.APIStateChanged(this, value))); } } From 59d0996c8d90501981e3a6752604e5810c5bab52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Mar 2019 14:31:54 +0900 Subject: [PATCH 184/623] Cleanup other instance of same function --- osu.Game/Online/API/APIAccess.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 3d861e44bf..c5f6ef41c2 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -266,20 +266,18 @@ namespace osu.Game.Online.API get => state; private set { - APIState oldState = state; - APIState newState = value; + if (state == value) + return; + APIState oldState = state; state = value; - if (oldState != newState) + log.Add($@"We just went {state}!"); + Scheduler.Add(delegate { - log.Add($@"We just went {newState}!"); - Scheduler.Add(delegate - { - components.ForEach(c => c.APIStateChanged(this, newState)); - OnStateChange?.Invoke(oldState, newState); - }); - } + components.ForEach(c => c.APIStateChanged(this, state)); + OnStateChange?.Invoke(oldState, state); + }); } } From 5b8fd6822e671c6fceb08ec46f18b3c270cf9d24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Mar 2019 14:39:20 +0900 Subject: [PATCH 185/623] Move storyboard logic region down --- osu.Game/Screens/Play/Player.cs | 70 ++++++++++++++++----------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 88467aa7f5..70899d42bc 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -67,41 +67,6 @@ namespace osu.Game.Screens.Play protected HUDOverlay HUDOverlay { get; private set; } - #region Storyboard - - private DrawableStoryboard storyboard; - protected UserDimContainer StoryboardContainer { get; private set; } - - private void initializeStoryboard(bool asyncLoad) - { - if (StoryboardContainer == null || storyboard != null) - return; - - if (!showStoryboard.Value) - return; - - var beatmap = Beatmap.Value; - - storyboard = beatmap.Storyboard.CreateDrawable(); - storyboard.Masking = true; - - if (asyncLoad) - LoadComponentAsync(storyboard, StoryboardContainer.Add); - else - StoryboardContainer.Add(storyboard); - } - - #endregion - - private Bindable showStoryboard; - - protected virtual UserDimContainer CreateStoryboardContainer() => new UserDimContainer(true) - { - RelativeSizeAxes = Axes.Both, - Alpha = 1, - EnableUserDim = { Value = true } - }; - public bool LoadedBeatmapSuccessfully => DrawableRuleset?.Objects.Any() == true; protected GameplayClockContainer GameplayClockContainer { get; private set; } @@ -307,6 +272,41 @@ namespace osu.Game.Screens.Play protected virtual Results CreateResults(ScoreInfo score) => new SoloResults(score); + #region Storyboard + + private DrawableStoryboard storyboard; + protected UserDimContainer StoryboardContainer { get; private set; } + + protected virtual UserDimContainer CreateStoryboardContainer() => new UserDimContainer(true) + { + RelativeSizeAxes = Axes.Both, + Alpha = 1, + EnableUserDim = { Value = true } + }; + + private Bindable showStoryboard; + + private void initializeStoryboard(bool asyncLoad) + { + if (StoryboardContainer == null || storyboard != null) + return; + + if (!showStoryboard.Value) + return; + + var beatmap = Beatmap.Value; + + storyboard = beatmap.Storyboard.CreateDrawable(); + storyboard.Masking = true; + + if (asyncLoad) + LoadComponentAsync(storyboard, StoryboardContainer.Add); + else + StoryboardContainer.Add(storyboard); + } + + #endregion + #region Fail Logic protected FailOverlay FailOverlay { get; private set; } From 9e6cdd7bd50d0975293e9a9613b0841fda9adea3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Mar 2019 14:42:51 +0900 Subject: [PATCH 186/623] Combine conditionals and clarify comment --- osu.Game/Screens/Play/Player.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 70899d42bc..7b1cdd21a6 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -353,9 +353,8 @@ namespace osu.Game.Screens.Play && !DrawableRuleset.HasReplayLoaded.Value // cannot pause if we are already in a fail state && !HasFailed - // cannot pause if already paused (and not in the process of resuming) - && (GameplayClockContainer.IsPaused.Value == false || IsResuming) - && (!pauseCooldownActive || IsResuming); + // cannot pause if already paused (or in a cooldown state) unless we are in a resuming state. + && (IsResuming || (GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive)); private bool pauseCooldownActive => lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; @@ -454,7 +453,7 @@ namespace osu.Game.Screens.Play return true; } - if (LoadedBeatmapSuccessfully && canPause) + if (canPause) { Pause(); return true; From 9a466d97ede2d44095df61c468455861f8b20104 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 22 Mar 2019 15:08:47 +0900 Subject: [PATCH 187/623] Add texts to make test more visually confirmable, add no parallax screen. --- .../Visual/TestCaseOsuScreenStack.cs | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs b/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs index c3a9e94448..2b37eae4e4 100644 --- a/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs +++ b/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs @@ -2,32 +2,35 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Graphics.Containers; using osu.Game.Screens; using osu.Game.Screens.Play; +using osuTK.Graphics; namespace osu.Game.Tests.Visual { [TestFixture] public class TestCaseOsuScreenStack : OsuTestCase { - private TestScreen baseScreen; + private NoParallaxTestScreen baseScreen; private TestOsuScreenStack stack; [SetUpSteps] public void Setup() { AddStep("Create new screen stack", () => { Child = stack = new TestOsuScreenStack { RelativeSizeAxes = Axes.Both }; }); - AddStep("Push new base screen", () => stack.Push(baseScreen = new TestScreen())); + AddStep("Push new base screen", () => stack.Push(baseScreen = new NoParallaxTestScreen("THIS IS SCREEN 1. THIS SCREEN SHOULD HAVE NO PARALLAX."))); } [Test] public void ParallaxAssignmentTest() { - AddStep("Push new screen to base screen", () => baseScreen.Push(new TestScreen())); + AddStep("Push new screen to base screen", () => baseScreen.Push(new TestScreen("THIS IS SCREEN 2. THIS SCREEN SHOULD HAVE PARALLAX."))); AddAssert("Parallax is correct", () => stack.IsParallaxSet); AddStep("Exit from new screen", () => { baseScreen.MakeCurrent(); }); AddAssert("Parallax is correct", () => stack.IsParallaxSet); @@ -35,6 +38,32 @@ namespace osu.Game.Tests.Visual private class TestScreen : ScreenWithBeatmapBackground { + private readonly string screenText; + + public TestScreen(string screenText) + { + this.screenText = screenText; + } + + [BackgroundDependencyLoader] + private void load() + { + AddInternal(new SpriteText + { + Text = screenText, + Colour = Color4.White + }); + } + } + + private class NoParallaxTestScreen : TestScreen + { + public NoParallaxTestScreen(string screenText) + : base(screenText) + { + } + + public override float BackgroundParallaxAmount => 0.0f; } private class TestOsuScreenStack : OsuScreenStack From 8fb4de101f3ee6867ca125c3831ea842b7130c0a Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 22 Mar 2019 15:20:06 +0900 Subject: [PATCH 188/623] Wait for screen current --- osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs b/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs index 2b37eae4e4..51d6c50643 100644 --- a/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs +++ b/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs @@ -18,6 +18,7 @@ namespace osu.Game.Tests.Visual public class TestCaseOsuScreenStack : OsuTestCase { private NoParallaxTestScreen baseScreen; + private TestScreen newScreen; private TestOsuScreenStack stack; [SetUpSteps] @@ -25,14 +26,17 @@ namespace osu.Game.Tests.Visual { AddStep("Create new screen stack", () => { Child = stack = new TestOsuScreenStack { RelativeSizeAxes = Axes.Both }; }); AddStep("Push new base screen", () => stack.Push(baseScreen = new NoParallaxTestScreen("THIS IS SCREEN 1. THIS SCREEN SHOULD HAVE NO PARALLAX."))); + AddUntilStep("Wait for Screen 1 to be current", baseScreen.IsCurrentScreen); } [Test] public void ParallaxAssignmentTest() { - AddStep("Push new screen to base screen", () => baseScreen.Push(new TestScreen("THIS IS SCREEN 2. THIS SCREEN SHOULD HAVE PARALLAX."))); + AddStep("Push new screen to base screen", () => baseScreen.Push(newScreen = new TestScreen("THIS IS SCREEN 2. THIS SCREEN SHOULD HAVE PARALLAX."))); + AddUntilStep("Wait for Screen 2 to be current", newScreen.IsCurrentScreen); AddAssert("Parallax is correct", () => stack.IsParallaxSet); AddStep("Exit from new screen", () => { baseScreen.MakeCurrent(); }); + AddUntilStep("Wait for Screen 1 to be current", baseScreen.IsCurrentScreen); AddAssert("Parallax is correct", () => stack.IsParallaxSet); } From c43b1afa305d1b017c0e02437d4ed422500a118e Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 22 Mar 2019 15:23:18 +0900 Subject: [PATCH 189/623] Use is loaded instead --- osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs b/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs index 51d6c50643..bbf8dc5b01 100644 --- a/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs +++ b/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs @@ -26,17 +26,16 @@ namespace osu.Game.Tests.Visual { AddStep("Create new screen stack", () => { Child = stack = new TestOsuScreenStack { RelativeSizeAxes = Axes.Both }; }); AddStep("Push new base screen", () => stack.Push(baseScreen = new NoParallaxTestScreen("THIS IS SCREEN 1. THIS SCREEN SHOULD HAVE NO PARALLAX."))); - AddUntilStep("Wait for Screen 1 to be current", baseScreen.IsCurrentScreen); + AddUntilStep("Wait for Screen 1 to be current", () => baseScreen.IsLoaded); } [Test] public void ParallaxAssignmentTest() { AddStep("Push new screen to base screen", () => baseScreen.Push(newScreen = new TestScreen("THIS IS SCREEN 2. THIS SCREEN SHOULD HAVE PARALLAX."))); - AddUntilStep("Wait for Screen 2 to be current", newScreen.IsCurrentScreen); + AddUntilStep("Wait for Screen 2 to be current", () => newScreen.IsLoaded); AddAssert("Parallax is correct", () => stack.IsParallaxSet); AddStep("Exit from new screen", () => { baseScreen.MakeCurrent(); }); - AddUntilStep("Wait for Screen 1 to be current", baseScreen.IsCurrentScreen); AddAssert("Parallax is correct", () => stack.IsParallaxSet); } From 670c25d5e861232d25267385b80a2e5a45950233 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Mar 2019 16:03:08 +0900 Subject: [PATCH 190/623] Refactor test --- .../Visual/TestCaseOsuScreenStack.cs | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs b/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs index bbf8dc5b01..0831228681 100644 --- a/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs +++ b/osu.Game.Tests/Visual/TestCaseOsuScreenStack.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Framework.Testing; -using osu.Game.Graphics.Containers; using osu.Game.Screens; using osu.Game.Screens.Play; using osuTK.Graphics; @@ -17,26 +16,30 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCaseOsuScreenStack : OsuTestCase { - private NoParallaxTestScreen baseScreen; - private TestScreen newScreen; private TestOsuScreenStack stack; [SetUpSteps] - public void Setup() + public void SetUpSteps() { AddStep("Create new screen stack", () => { Child = stack = new TestOsuScreenStack { RelativeSizeAxes = Axes.Both }; }); - AddStep("Push new base screen", () => stack.Push(baseScreen = new NoParallaxTestScreen("THIS IS SCREEN 1. THIS SCREEN SHOULD HAVE NO PARALLAX."))); - AddUntilStep("Wait for Screen 1 to be current", () => baseScreen.IsLoaded); } [Test] public void ParallaxAssignmentTest() { - AddStep("Push new screen to base screen", () => baseScreen.Push(newScreen = new TestScreen("THIS IS SCREEN 2. THIS SCREEN SHOULD HAVE PARALLAX."))); - AddUntilStep("Wait for Screen 2 to be current", () => newScreen.IsLoaded); - AddAssert("Parallax is correct", () => stack.IsParallaxSet); - AddStep("Exit from new screen", () => { baseScreen.MakeCurrent(); }); - AddAssert("Parallax is correct", () => stack.IsParallaxSet); + NoParallaxTestScreen noParallaxScreen = null; + TestScreen parallaxScreen = null; + + AddStep("Push no parallax", () => stack.Push(noParallaxScreen = new NoParallaxTestScreen("NO PARALLAX"))); + AddUntilStep("Wait for current", () => noParallaxScreen.IsLoaded); + AddAssert("Parallax is off", () => stack.ParallaxAmount == 0); + + AddStep("Push parallax", () => noParallaxScreen.Push(parallaxScreen = new TestScreen("PARALLAX"))); + AddUntilStep("Wait for current", () => parallaxScreen.IsLoaded); + AddAssert("Parallax is on", () => stack.ParallaxAmount > 0); + + AddStep("Exit from new screen", () => { noParallaxScreen.MakeCurrent(); }); + AddAssert("Parallax is off", () => stack.ParallaxAmount == 0); } private class TestScreen : ScreenWithBeatmapBackground @@ -54,7 +57,9 @@ namespace osu.Game.Tests.Visual AddInternal(new SpriteText { Text = screenText, - Colour = Color4.White + Colour = Color4.White, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, }); } } @@ -71,7 +76,7 @@ namespace osu.Game.Tests.Visual private class TestOsuScreenStack : OsuScreenStack { - public bool IsParallaxSet => ParallaxAmount == ((TestScreen)CurrentScreen).BackgroundParallaxAmount * ParallaxContainer.DEFAULT_PARALLAX_AMOUNT; + public new float ParallaxAmount => base.ParallaxAmount; } } } From e93311fdc9e2372e262e5fb9f4cd496ec53f5798 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 22 Mar 2019 19:01:32 +0900 Subject: [PATCH 191/623] DI facade --- .../Graphics/Containers/FacadeContainer.cs | 30 +++++++++++++++++++ osu.Game/Screens/Play/PlayerLoader.cs | 14 ++++++--- 2 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Graphics/Containers/FacadeContainer.cs diff --git a/osu.Game/Graphics/Containers/FacadeContainer.cs b/osu.Game/Graphics/Containers/FacadeContainer.cs new file mode 100644 index 0000000000..7d7a4b0680 --- /dev/null +++ b/osu.Game/Graphics/Containers/FacadeContainer.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Game.Screens.Menu; +using osuTK; + +namespace osu.Game.Graphics.Containers +{ + public class FacadeContainer : Container + { + [Cached] + private Facade facade; + + public FacadeContainer() + { + facade = new Facade(); + } + + public void SetLogo(OsuLogo logo) + { + facade.Size = new Vector2(logo.SizeForFlow); + } + } + + public class Facade : Container + { + } +} diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index e9ee5d3fa8..878c2541e9 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -14,6 +14,7 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Menu; @@ -32,7 +33,7 @@ namespace osu.Game.Screens.Play private Player player; - private Container content; + private FacadeContainer content; private BeatmapMetadataDisplay info; @@ -59,7 +60,7 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { - InternalChild = content = new Container + InternalChild = content = new FacadeContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -153,6 +154,8 @@ namespace osu.Game.Screens.Play logo.FadeIn(350); logo.Delay(resuming ? 0 : 500).MoveToOffset(new Vector2(0, -0.24f), 500, Easing.InOutExpo); + + content.SetLogo(logo); } protected override void LoadComplete() @@ -302,6 +305,8 @@ namespace osu.Game.Screens.Play private LoadingAnimation loading; private Sprite backgroundSprite; private ModDisplay modDisplay; + private FillFlowContainer fillFlowContainer; + private FacadeContainer facadeContainer; public bool Loading { @@ -326,14 +331,14 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load() + private void load(Facade facade) { var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); AutoSizeAxes = Axes.Both; Children = new Drawable[] { - new FillFlowContainer + fillFlowContainer = new FillFlowContainer { AutoSizeAxes = Axes.Both, Origin = Anchor.TopCentre, @@ -341,6 +346,7 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Vertical, Children = new Drawable[] { + facade, new OsuSpriteText { Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)), From 6e98a8dd7c15ff372a39ebf9b7785b0c6e16a755 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 22 Mar 2019 20:01:58 +0900 Subject: [PATCH 192/623] Initial implementation --- osu.Game.Tests/Visual/TestCasePlayerLoader.cs | 10 +++- .../Graphics/Containers/FacadeContainer.cs | 53 ++++++++++++++++++- osu.Game/Screens/Play/PlayerLoader.cs | 13 +++-- 3 files changed, 67 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs index 2bc416f7f4..ad0965b4d6 100644 --- a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs @@ -7,7 +7,9 @@ using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Screens; +using osu.Game.Screens.Menu; using osu.Game.Screens.Play; +using osuTK; namespace osu.Game.Tests.Visual { @@ -16,6 +18,9 @@ namespace osu.Game.Tests.Visual private PlayerLoader loader; private readonly ScreenStack stack; + [Cached] + private OsuLogo logo; + [Cached] private BackgroundScreenStack backgroundStack; @@ -23,6 +28,7 @@ namespace osu.Game.Tests.Visual { InputManager.Add(backgroundStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }); InputManager.Add(stack = new ScreenStack { RelativeSizeAxes = Axes.Both }); + InputManager.Add(logo = new OsuLogo()); } [BackgroundDependencyLoader] @@ -30,6 +36,8 @@ namespace osu.Game.Tests.Visual { Beatmap.Value = new DummyWorkingBeatmap(game); + AddStep("Reset logo position", () => logo = new OsuLogo { Position = new Vector2(0, 0) }); + AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player { AllowPause = false, @@ -57,8 +65,6 @@ namespace osu.Game.Tests.Visual AllowLeadIn = false, AllowResults = false, })); - - Scheduler.AddDelayed(() => slow.Ready = true, 5000); }); AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); diff --git a/osu.Game/Graphics/Containers/FacadeContainer.cs b/osu.Game/Graphics/Containers/FacadeContainer.cs index 7d7a4b0680..e39fba64ce 100644 --- a/osu.Game/Graphics/Containers/FacadeContainer.cs +++ b/osu.Game/Graphics/Containers/FacadeContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Screens.Menu; using osuTK; @@ -13,14 +14,62 @@ namespace osu.Game.Graphics.Containers [Cached] private Facade facade; + private OsuLogo logo; + + private bool tracking; + private bool smoothTransform; + public FacadeContainer() { facade = new Facade(); } - public void SetLogo(OsuLogo logo) + private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.Centre); + + public void SetLogo(OsuLogo logo, bool resuming, double transformDelay) { - facade.Size = new Vector2(logo.SizeForFlow); + if (logo != null) + { + facade.Size = new Vector2(logo.SizeForFlow * 0.3f); + this.logo = logo; + Scheduler.AddDelayed(() => + { + tracking = true; + smoothTransform = !resuming; + }, transformDelay); + } + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + facade.Size = new Vector2(logo.SizeForFlow * 0.3f); + + if (!tracking) + return; + + logo.RelativePositionAxes = Axes.None; + + bool childrenLoaded = true; + + foreach (var d in Children) + { + if (!d.IsAlive) + childrenLoaded = false; + } + + if (smoothTransform && childrenLoaded) + { + // Our initial movement to the tracking location should be smooth. + Schedule(() => logo.MoveTo(logoTrackingPosition, 500, Easing.InOutExpo)); + smoothTransform = false; + } + else if (logo.Transforms.Count == 0) + { + // If all transforms have finished playing, the logo constantly track the position of the facade. + logo.Position = logoTrackingPosition; + } } } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 878c2541e9..97249ee6cd 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -149,13 +149,13 @@ namespace osu.Game.Screens.Play { base.LogoArriving(logo, resuming); - logo.ScaleTo(new Vector2(0.15f), 300, Easing.In); - logo.MoveTo(new Vector2(0.5f), 300, Easing.In); + const double duration = 300; + + logo.ScaleTo(new Vector2(0.15f), duration, Easing.In); + logo.MoveTo(new Vector2(0.5f), duration, Easing.In); logo.FadeIn(350); - logo.Delay(resuming ? 0 : 500).MoveToOffset(new Vector2(0, -0.24f), 500, Easing.InOutExpo); - - content.SetLogo(logo); + content.SetLogo(logo, resuming, duration); } protected override void LoadComplete() @@ -335,6 +335,9 @@ namespace osu.Game.Screens.Play { var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); + facade.Anchor = Anchor.TopCentre; + facade.Origin = Anchor.TopCentre; + AutoSizeAxes = Axes.Both; Children = new Drawable[] { From 97675f6495d2c5e9641a214226bee4dffe4f523a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Mar 2019 00:55:31 +0900 Subject: [PATCH 193/623] Update framework --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index dd69faad56..71324ea0f0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 3dcb647cd2..02099a59bb 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 9a11a08acc9f94f55f5c9b3f3ff1695bd395db4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Mar 2019 01:44:05 +0900 Subject: [PATCH 194/623] Update textbox usages --- .../Graphics/UserInterface/FocusedTextBox.cs | 6 +++--- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 8 +++----- .../Chat/Selection/ChannelSelectionOverlay.cs | 8 ++++++-- osu.Game/Overlays/Music/FilterControl.cs | 11 ++++++----- .../SearchableListFilterControl.cs | 10 +++++++--- .../Match/Components/MatchSettingsOverlay.cs | 16 ++++++++++++---- 6 files changed, 37 insertions(+), 22 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 73c9c0dd0e..f873db0dcb 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -16,9 +16,6 @@ namespace osu.Game.Graphics.UserInterface /// public class FocusedTextBox : OsuTextBox { - protected override Color4 BackgroundUnfocused => new Color4(10, 10, 10, 255); - protected override Color4 BackgroundFocused => new Color4(10, 10, 10, 255); - public Action Exit; private bool focus; @@ -47,6 +44,9 @@ namespace osu.Game.Graphics.UserInterface private void load(GameHost host) { this.host = host; + + BackgroundUnfocused = new Color4(10, 10, 10, 255); + BackgroundFocused = new Color4(10, 10, 10, 255); } // We may not be focused yet, but we need to handle keyboard input to be able to request focus diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 21cdfbf5af..ebe38db60a 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -16,10 +16,6 @@ namespace osu.Game.Graphics.UserInterface { public class OsuTextBox : TextBox, IKeyBindingHandler { - protected override Color4 BackgroundUnfocused => Color4.Black.Opacity(0.5f); - protected override Color4 BackgroundFocused => OsuColour.Gray(0.3f).Opacity(0.8f); - protected override Color4 BackgroundCommit => BorderColour; - protected override float LeftRightPadding => 10; protected override SpriteText CreatePlaceholder() => new OsuSpriteText @@ -41,7 +37,9 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colour) { - BorderColour = colour.Yellow; + BackgroundUnfocused = Color4.Black.Opacity(0.5f); + BackgroundFocused = OsuColour.Gray(0.3f).Opacity(0.8f); + BackgroundCommit = BorderColour = colour.Yellow; } protected override void OnFocus(FocusEvent e) diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs index feb47b9e8e..71e9e4bdf3 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs @@ -190,8 +190,12 @@ namespace osu.Game.Overlays.Chat.Selection private class HeaderSearchTextBox : SearchTextBox { - protected override Color4 BackgroundFocused => Color4.Black.Opacity(0.2f); - protected override Color4 BackgroundUnfocused => Color4.Black.Opacity(0.2f); + [BackgroundDependencyLoader] + private void load() + { + BackgroundFocused = Color4.Black.Opacity(0.2f); + BackgroundUnfocused = Color4.Black.Opacity(0.2f); + } } } } diff --git a/osu.Game/Overlays/Music/FilterControl.cs b/osu.Game/Overlays/Music/FilterControl.cs index 6bceade271..99017579a2 100644 --- a/osu.Game/Overlays/Music/FilterControl.cs +++ b/osu.Game/Overlays/Music/FilterControl.cs @@ -6,8 +6,8 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osuTK; -using osuTK.Graphics; using System; +using osu.Framework.Allocation; using osu.Framework.Bindables; namespace osu.Game.Overlays.Music @@ -53,15 +53,16 @@ namespace osu.Game.Overlays.Music public class FilterTextBox : SearchTextBox { - protected override Color4 BackgroundUnfocused => OsuColour.Gray(0.06f); - protected override Color4 BackgroundFocused => OsuColour.Gray(0.12f); - protected override bool AllowCommit => true; - public FilterTextBox() + [BackgroundDependencyLoader] + private void load() { Masking = true; CornerRadius = 5; + + BackgroundUnfocused = OsuColour.Gray(0.06f); + BackgroundFocused = OsuColour.Gray(0.12f); } } } diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs index 478e3d4c95..b0a8a0e77d 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs @@ -127,10 +127,14 @@ namespace osu.Game.Overlays.SearchableList private class FilterSearchTextBox : SearchTextBox { - protected override Color4 BackgroundUnfocused => OsuColour.Gray(0.06f); - protected override Color4 BackgroundFocused => OsuColour.Gray(0.12f); - protected override bool AllowCommit => true; + + [BackgroundDependencyLoader] + private void load() + { + BackgroundUnfocused = OsuColour.Gray(0.06f); + BackgroundFocused = OsuColour.Gray(0.12f); + } } } } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs index b310e62d7c..586a986111 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs @@ -316,8 +316,12 @@ namespace osu.Game.Screens.Multi.Match.Components private class SettingsTextBox : OsuTextBox { - protected override Color4 BackgroundUnfocused => Color4.Black; - protected override Color4 BackgroundFocused => Color4.Black; + [BackgroundDependencyLoader] + private void load() + { + BackgroundUnfocused = Color4.Black; + BackgroundFocused = Color4.Black; + } } private class SettingsNumberTextBox : SettingsTextBox @@ -327,8 +331,12 @@ namespace osu.Game.Screens.Multi.Match.Components private class SettingsPasswordTextBox : OsuPasswordTextBox { - protected override Color4 BackgroundUnfocused => Color4.Black; - protected override Color4 BackgroundFocused => Color4.Black; + [BackgroundDependencyLoader] + private void load() + { + BackgroundUnfocused = Color4.Black; + BackgroundFocused = Color4.Black; + } } private class SectionContainer : FillFlowContainer
From 0d721042356dbe0a1794344500187413e0e20a4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Mar 2019 12:03:06 +0900 Subject: [PATCH 195/623] Refactor for formatting sanity --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 28 ++++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index dbab8bb479..c3b9d93ef0 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -38,13 +38,15 @@ namespace osu.Game.Screens.Play /// /// Action that is invoked when is triggered. /// - protected virtual Action BackAction => () => InternalButtons.Children.Last().Click(); + protected virtual Action BackAction => () => InternalButtons.Children.LastOrDefault()?.Click(); /// /// Action that is invoked when is triggered. /// - protected Action SelectAction => () => InternalButtons.Children.FirstOrDefault(f => f.Selected)?.Click(); + protected virtual Action SelectAction => () => InternalButtons.Children.FirstOrDefault(f => f.Selected.Value)?.Click(); + public abstract string Header { get; } + public abstract string Description { get; } protected internal FillFlowContainer InternalButtons; @@ -233,22 +235,20 @@ namespace osu.Game.Screens.Play public bool OnPressed(GlobalAction action) { - if (action == GlobalAction.Back) + switch (action) { - BackAction.Invoke(); - return true; + case GlobalAction.Back: + BackAction.Invoke(); + return true; + case GlobalAction.Select: + SelectAction.Invoke(); + return true; + default: + return false; } - - if (action == GlobalAction.Select) - { - SelectAction.Invoke(); - return true; - } - - return false; } - public bool OnReleased(GlobalAction action) => action == GlobalAction.Back; + public bool OnReleased(GlobalAction action) => false; private void buttonSelectionChanged(DialogButton button, bool isSelected) { From 1e0027e4f2163be6ae3bd7b29b8b573072fb9513 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Mar 2019 12:09:18 +0900 Subject: [PATCH 196/623] Fix test --- .../Visual/TestCaseGameplayMenuOverlay.cs | 56 ++++++++++++------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs index c5ad57fec9..0a240186d2 100644 --- a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs @@ -7,8 +7,10 @@ using System.ComponentModel; using System.Linq; using osuTK.Input; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; +using osu.Game.Input.Bindings; using osu.Game.Screens.Play; using osuTK; @@ -22,21 +24,29 @@ namespace osu.Game.Tests.Visual private FailOverlay failOverlay; private PauseOverlay pauseOverlay; - [BackgroundDependencyLoader] - private void load() - { - Add(pauseOverlay = new PauseOverlay - { - OnResume = () => Logger.Log(@"Resume"), - OnRetry = () => Logger.Log(@"Retry"), - OnQuit = () => Logger.Log(@"Quit"), - }); + private GlobalActionContainer globalActionContainer; - Add(failOverlay = new FailOverlay + [BackgroundDependencyLoader] + private void load(OsuGameBase game) + { + Child = globalActionContainer = new GlobalActionContainer(game) { - OnRetry = () => Logger.Log(@"Retry"), - OnQuit = () => Logger.Log(@"Quit"), - }); + Children = new Drawable[] + { + pauseOverlay = new PauseOverlay + { + OnResume = () => Logger.Log(@"Resume"), + OnRetry = () => Logger.Log(@"Retry"), + OnQuit = () => Logger.Log(@"Quit"), + }, + failOverlay = new FailOverlay + + { + OnRetry = () => Logger.Log(@"Retry"), + OnQuit = () => Logger.Log(@"Quit"), + } + } + }; var retryCount = 0; @@ -79,12 +89,6 @@ namespace osu.Game.Tests.Visual AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected.Value)); } - private void press(Key key) - { - InputManager.PressKey(key); - InputManager.ReleaseKey(key); - } - /// /// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred. /// @@ -92,7 +96,7 @@ namespace osu.Game.Tests.Visual { AddStep("Show overlay", () => pauseOverlay.Show()); - AddStep("Press enter", () => press(Key.Enter)); + AddStep("Press select", () => press(GlobalAction.Select)); AddAssert("Overlay still open", () => pauseOverlay.State == Visibility.Visible); AddStep("Hide overlay", () => pauseOverlay.Hide()); @@ -270,5 +274,17 @@ namespace osu.Game.Tests.Visual }); AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden); } + + private void press(Key key) + { + InputManager.PressKey(key); + InputManager.ReleaseKey(key); + } + + private void press(GlobalAction action) + { + globalActionContainer.TriggerPressed(action); + globalActionContainer.TriggerReleased(action); + } } } From 11b474e1949ea71367681c3ccfa8b1c3daad88c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Mar 2019 12:19:09 +0900 Subject: [PATCH 197/623] Handle released for safety --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index c3b9d93ef0..2fac8de799 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -243,12 +243,22 @@ namespace osu.Game.Screens.Play case GlobalAction.Select: SelectAction.Invoke(); return true; - default: - return false; } + + return false; } - public bool OnReleased(GlobalAction action) => false; + public bool OnReleased(GlobalAction action) + { + switch (action) + { + case GlobalAction.Back: + case GlobalAction.Select: + return true; + } + + return false; + } private void buttonSelectionChanged(DialogButton button, bool isSelected) { From d37968d88df2ad6f6f0a6f64596212df28bfabde Mon Sep 17 00:00:00 2001 From: David Zhao Date: Sun, 24 Mar 2019 15:18:38 +0900 Subject: [PATCH 198/623] Add better test for facade containers --- .../Visual/TestCaseFacadeContainer.cs | 113 ++++++++++++++++++ .../Graphics/Containers/FacadeContainer.cs | 33 +++-- osu.Game/Screens/Play/PlayerLoader.cs | 46 +++---- 3 files changed, 152 insertions(+), 40 deletions(-) create mode 100644 osu.Game.Tests/Visual/TestCaseFacadeContainer.cs diff --git a/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs b/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs new file mode 100644 index 0000000000..3063a4ca3f --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs @@ -0,0 +1,113 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osu.Game.Configuration; +using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Play; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseFacadeContainer : ScreenTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(PlayerLoader), + typeof(Player), + typeof(Facade), + }; + + [Cached] + private OsuLogo logo; + + private readonly Bindable uiScale = new Bindable(); + + private OsuScreen baseScreen; + + public TestCaseFacadeContainer() + { + Add(logo = new OsuLogo()); + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + baseScreen = null; + config.BindWith(OsuSetting.UIScale, uiScale); + AddSliderStep("Adjust scale", 1f, 1.5f, 1f, v => uiScale.Value = v); + AddToggleStep("Toggle mods", b => { Beatmap.Value.Mods.Value = b ? Beatmap.Value.Mods.Value.Concat(new[] { new OsuModNoFail() }) : Enumerable.Empty(); }); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Null screens", () => baseScreen = null); + } + + [Test] + public void PlayerLoaderTest() + { + AddStep("Add new playerloader", () => LoadScreen(baseScreen = new TestPlayerLoader(() => new TestPlayer + { + AllowPause = false, + AllowLeadIn = false, + AllowResults = false, + }))); + } + + [Test] + public void MainMenuTest() + { + AddStep("Add new Main Menu", () => LoadScreen(baseScreen = new MainMenu())); + } + + private class TestFacadeContainer : FacadeContainer + { + protected override Facade CreateFacade() => new Facade + { + Colour = Color4.Tomato, + Alpha = 0.35f, + Child = new Box + { + Colour = Color4.Tomato, + RelativeSizeAxes = Axes.Both, + }, + }; + } + + private class TestPlayerLoader : PlayerLoader + { + public TestPlayerLoader(Func player) + : base(player) + { + } + + protected override FacadeContainer CreateFacadeContainer() => new TestFacadeContainer(); + } + + private class TestPlayer : Player + { + [BackgroundDependencyLoader] + private void load() + { + // Never finish loading + while (true) + Thread.Sleep(1); + } + } + } +} diff --git a/osu.Game/Graphics/Containers/FacadeContainer.cs b/osu.Game/Graphics/Containers/FacadeContainer.cs index e39fba64ce..d7fae8887f 100644 --- a/osu.Game/Graphics/Containers/FacadeContainer.cs +++ b/osu.Game/Graphics/Containers/FacadeContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -19,9 +20,11 @@ namespace osu.Game.Graphics.Containers private bool tracking; private bool smoothTransform; + protected virtual Facade CreateFacade() => new Facade(); + public FacadeContainer() { - facade = new Facade(); + facade = CreateFacade(); } private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.Centre); @@ -44,30 +47,26 @@ namespace osu.Game.Graphics.Containers { base.UpdateAfterChildren(); - facade.Size = new Vector2(logo.SizeForFlow * 0.3f); - - if (!tracking) + if (logo == null) return; - logo.RelativePositionAxes = Axes.None; + facade.Size = new Vector2(logo.SizeForFlow * 0.3f); - bool childrenLoaded = true; - - foreach (var d in Children) - { - if (!d.IsAlive) - childrenLoaded = false; - } - - if (smoothTransform && childrenLoaded) + if (smoothTransform && facade.IsLoaded && logo.Transforms.Count == 0) { // Our initial movement to the tracking location should be smooth. - Schedule(() => logo.MoveTo(logoTrackingPosition, 500, Easing.InOutExpo)); - smoothTransform = false; + Schedule(() => + { + facade.Size = new Vector2(logo.SizeForFlow * 0.3f); + logo.RelativePositionAxes = Axes.None; + logo.MoveTo(logoTrackingPosition, 500, Easing.InOutExpo); + smoothTransform = false; + }); } - else if (logo.Transforms.Count == 0) + else if (facade.IsLoaded && logo.Transforms.Count == 0) { // If all transforms have finished playing, the logo constantly track the position of the facade. + logo.RelativePositionAxes = Axes.None; logo.Position = logoTrackingPosition; } } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 97249ee6cd..5817e11277 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -35,6 +35,8 @@ namespace osu.Game.Screens.Play private FacadeContainer content; + protected virtual FacadeContainer CreateFacadeContainer() => new FacadeContainer(); + private BeatmapMetadataDisplay info; private bool hideOverlays; @@ -60,32 +62,30 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { - InternalChild = content = new FacadeContainer + InternalChild = content = CreateFacadeContainer(); + content.Anchor = Anchor.Centre; + content.Origin = Anchor.Centre; + content.RelativeSizeAxes = Axes.Both; + content.Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + info = new BeatmapMetadataDisplay(Beatmap.Value) { - info = new BeatmapMetadataDisplay(Beatmap.Value) + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Margin = new MarginPadding(25), + Children = new PlayerSettingsGroup[] { - Alpha = 0, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20), - Margin = new MarginPadding(25), - Children = new PlayerSettingsGroup[] - { - VisualSettings = new VisualSettings(), - new InputSettings() - } + VisualSettings = new VisualSettings(), + new InputSettings() } } }; From 5169f7a43ca627acda15cf171ae41ea2430b761e Mon Sep 17 00:00:00 2001 From: David Zhao Date: Sun, 24 Mar 2019 16:21:43 +0900 Subject: [PATCH 199/623] Change default for null screen parallax, apply reviews --- osu.Game/OsuGame.cs | 6 ++---- osu.Game/Screens/OsuScreenStack.cs | 5 +---- osu.Game/Tests/Visual/ScreenTestCase.cs | 5 +---- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 079a0d5bb6..7277990987 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -199,8 +199,6 @@ namespace osu.Game LocalConfig.BindWith(OsuSetting.VolumeInactive, inactiveVolumeAdjust); IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true); - - screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; } private ExternalLinkOpener externalLinkOpener; @@ -388,7 +386,7 @@ namespace osu.Game RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - screenStack, + screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, logoContainer = new Container { RelativeSizeAxes = Axes.Both }, } }, @@ -407,7 +405,7 @@ namespace osu.Game { logoContainer.Add(logo); - // Loader has to be created synchronously in order for DI to be successful for its background screen stack. + // Loader has to be created after the logo has finished loading as Loader performs logo transformations on entering. screenStack.Push(new Loader { RelativeSizeAxes = Axes.Both diff --git a/osu.Game/Screens/OsuScreenStack.cs b/osu.Game/Screens/OsuScreenStack.cs index 1c929470d1..02e5919cdd 100644 --- a/osu.Game/Screens/OsuScreenStack.cs +++ b/osu.Game/Screens/OsuScreenStack.cs @@ -42,10 +42,7 @@ namespace osu.Game.Screens private void setParallax(IScreen prev, IScreen next) { - if (next != null) - { - parallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * ((IOsuScreen)next).BackgroundParallaxAmount; - } + parallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * ((IOsuScreen)next)?.BackgroundParallaxAmount ?? 1.0f; } } } diff --git a/osu.Game/Tests/Visual/ScreenTestCase.cs b/osu.Game/Tests/Visual/ScreenTestCase.cs index a8286f6d7b..eec60d01c5 100644 --- a/osu.Game/Tests/Visual/ScreenTestCase.cs +++ b/osu.Game/Tests/Visual/ScreenTestCase.cs @@ -15,10 +15,7 @@ namespace osu.Game.Tests.Visual protected ScreenTestCase() { - Children = new Drawable[] - { - stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both } - }; + Child = stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; } protected void LoadScreen(OsuScreen screen) From 9bf48863b019a59e75cb983d26b773e107089d7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Mar 2019 23:40:43 +0900 Subject: [PATCH 200/623] Fix DrawableRuleset drawable creation method's name --- .../Objects/Drawable/DrawableBananaShower.cs | 4 ++-- .../Objects/Drawable/DrawableJuiceStream.cs | 4 ++-- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 4 ++-- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 4 ++-- osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 8 ++++---- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 2 +- osu.Game/Rulesets/Edit/DrawableEditRuleset.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 8 ++++---- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs index aaf723fae6..40723289ec 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { private readonly Container bananaContainer; - public DrawableBananaShower(BananaShower s, Func> getVisualRepresentation = null) + public DrawableBananaShower(BananaShower s, Func> createDrawableRepresentation = null) : base(s) { RelativeSizeAxes = Axes.X; @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both }; foreach (var b in s.NestedHitObjects.Cast()) - AddNested(getVisualRepresentation?.Invoke(b)); + AddNested(createDrawableRepresentation?.Invoke(b)); } protected override void AddNested(DrawableHitObject h) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs index 7bb12453a8..2ef0ece28f 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { private readonly Container dropletContainer; - public DrawableJuiceStream(JuiceStream s, Func> getVisualRepresentation = null) + public DrawableJuiceStream(JuiceStream s, Func> createDrawableRepresentation = null) : base(s) { RelativeSizeAxes = Axes.Both; @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable InternalChild = dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }; foreach (var o in s.NestedHitObjects.Cast()) - AddNested(getVisualRepresentation?.Invoke(o)); + AddNested(createDrawableRepresentation?.Invoke(o)); } protected override void AddNested(DrawableHitObject h) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 4dae95b53c..4ecc037e13 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.UI internal readonly CatcherArea CatcherArea; - public CatchPlayfield(BeatmapDifficulty difficulty, Func> getVisualRepresentation) + public CatchPlayfield(BeatmapDifficulty difficulty, Func> createDrawableRepresentation) { Container explodingFruitContainer; @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.UI }, CatcherArea = new CatcherArea(difficulty) { - GetVisualRepresentation = getVisualRepresentation, + CreateDrawableRepresentation = createDrawableRepresentation, ExplodingFruitTarget = explodingFruitContainer, Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index c6dd0a86a0..83f791690a 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.UI protected internal readonly Catcher MovableCatcher; - public Func> GetVisualRepresentation; + public Func> CreateDrawableRepresentation; public Container ExplodingFruitTarget { @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.UI if (result.IsHit && fruit.CanBePlated) { - var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject); + var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject); if (caughtFruit == null) return; diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 406dc10eea..fe88540e28 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -34,11 +34,11 @@ namespace osu.Game.Rulesets.Catch.UI protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); - protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, GetVisualRepresentation); + protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation); protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); - public override DrawableHitObject GetVisualRepresentation(CatchHitObject h) + public override DrawableHitObject CreateDrawableRepresentation(CatchHitObject h) { switch (h) { @@ -47,9 +47,9 @@ namespace osu.Game.Rulesets.Catch.UI case Fruit fruit: return new DrawableFruit(fruit); case JuiceStream stream: - return new DrawableJuiceStream(stream, GetVisualRepresentation); + return new DrawableJuiceStream(stream, CreateDrawableRepresentation); case BananaShower shower: - return new DrawableBananaShower(shower, GetVisualRepresentation); + return new DrawableBananaShower(shower, CreateDrawableRepresentation); case TinyDroplet tiny: return new DrawableTinyDroplet(tiny); case Droplet droplet: diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index a019401d5b..bbeac1321c 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.UI protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); - public override DrawableHitObject GetVisualRepresentation(ManiaHitObject h) + public override DrawableHitObject CreateDrawableRepresentation(ManiaHitObject h) { switch (h) { diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index b632e0fb05..c2bb01a742 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); - public override DrawableHitObject GetVisualRepresentation(OsuHitObject h) + public override DrawableHitObject CreateDrawableRepresentation(OsuHitObject h) { switch (h) { diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 899b91863e..cb0f049a9b 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo); - public override DrawableHitObject GetVisualRepresentation(TaikoHitObject h) + public override DrawableHitObject CreateDrawableRepresentation(TaikoHitObject h) { switch (h) { diff --git a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs index 76a2e7af12..68d57c559e 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Edit processor?.PostProcess(); // Add visual representation - var drawableObject = drawableRuleset.GetVisualRepresentation(tObject); + var drawableObject = drawableRuleset.CreateDrawableRepresentation(tObject); drawableRuleset.Playfield.Add(drawableObject); drawableRuleset.Playfield.PostProcess(); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 31c0afd743..32f16a5eee 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.UI private void loadObjects() { foreach (TObject h in Beatmap.HitObjects) - addRepresentation(h); + addHitObject(h); Playfield.PostProcess(); @@ -175,9 +175,9 @@ namespace osu.Game.Rulesets.UI /// Creates and adds the visual representation of a to this . ///
/// The to add the visual representation for. - private void addRepresentation(TObject hitObject) + private void addHitObject(TObject hitObject) { - var drawableObject = GetVisualRepresentation(hitObject); + var drawableObject = CreateDrawableRepresentation(hitObject); if (drawableObject == null) return; @@ -209,7 +209,7 @@ namespace osu.Game.Rulesets.UI /// /// The HitObject to make drawable. /// The DrawableHitObject. - public abstract DrawableHitObject GetVisualRepresentation(TObject h); + public abstract DrawableHitObject CreateDrawableRepresentation(TObject h); public void Attach(KeyCounterCollection keyCounter) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter); From a1c5eda05b45b7b9ae07ef2927482105e3c636bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 01:02:36 +0900 Subject: [PATCH 201/623] Add grouping of visual tests --- .../TestCaseBackgroundScreenBeatmap.cs | 2 +- .../{ => Components}/TestCaseIdleTracker.cs | 2 +- .../TestCasePollingComponent.cs | 2 +- .../TestCasePreviewTrackManager.cs | 2 +- .../TestCaseBeatDivisorControl.cs | 2 +- .../{ => Editor}/TestCaseEditorCompose.cs | 2 +- .../TestCaseEditorComposeRadioButtons.cs | 2 +- .../TestCaseEditorComposeTimeline.cs | 4 ++-- .../{ => Editor}/TestCaseEditorMenuBar.cs | 2 +- .../TestCaseEditorSeekSnapping.cs | 2 +- .../TestCaseEditorSummaryTimeline.cs | 6 ++--- .../{ => Editor}/TestCaseHitObjectComposer.cs | 4 ++-- .../{ => Editor}/TestCasePlaybackControl.cs | 2 +- .../{ => Editor}/TestCaseWaveContainer.cs | 2 +- .../Visual/{ => Editor}/TestCaseWaveform.cs | 6 ++--- .../TestCaseZoomableScrollContainer.cs | 2 +- .../Visual/{ => Gameplay}/TestCaseAutoplay.cs | 2 +- .../{ => Gameplay}/TestCaseBreakOverlay.cs | 4 ++-- .../TestCaseGameplayMenuOverlay.cs | 4 ++-- .../TestCaseHoldForMenuButton.cs | 2 +- .../{ => Gameplay}/TestCaseKeyCounter.cs | 2 +- .../{ => Gameplay}/TestCaseMedalOverlay.cs | 2 +- .../Visual/{ => Gameplay}/TestCasePause.cs | 2 +- .../{ => Gameplay}/TestCasePlayerLoader.cs | 2 +- .../TestCasePlayerReferenceLeaking.cs | 2 +- .../Visual/{ => Gameplay}/TestCaseReplay.cs | 2 +- .../TestCaseReplaySettingsOverlay.cs | 2 +- .../Visual/{ => Gameplay}/TestCaseResults.cs | 2 +- .../{ => Gameplay}/TestCaseScoreCounter.cs | 2 +- .../TestCaseScrollingHitObjects.cs | 4 ++-- .../{ => Gameplay}/TestCaseSkinReloadable.cs | 2 +- .../{ => Gameplay}/TestCaseSkipOverlay.cs | 2 +- .../{ => Gameplay}/TestCaseSongProgress.cs | 2 +- .../{ => Gameplay}/TestCaseStoryboard.cs | 2 +- .../Visual/{ => Menus}/TestCaseDisclaimer.cs | 2 +- .../{ => Menus}/TestCaseIntroSequence.cs | 4 ++-- .../{ => Menus}/TestCaseLoaderAnimation.cs | 2 +- .../Visual/{ => Menus}/TestCaseToolbar.cs | 2 +- .../TestCaseLoungeRoomsContainer.cs | 2 +- .../{ => Multiplayer}/TestCaseMatchHeader.cs | 2 +- .../TestCaseMatchHostInfo.cs | 2 +- .../{ => Multiplayer}/TestCaseMatchInfo.cs | 2 +- .../TestCaseMatchLeaderboard.cs | 2 +- .../TestCaseMatchParticipants.cs | 2 +- .../{ => Multiplayer}/TestCaseMatchResults.cs | 2 +- .../TestCaseMatchSettingsOverlay.cs | 2 +- .../{ => Multiplayer}/TestCaseMultiHeader.cs | 2 +- .../{ => Multiplayer}/TestCaseMultiScreen.cs | 7 +++--- .../{ => Multiplayer}/TestCaseRoomStatus.cs | 2 +- .../TestCaseAccountCreationOverlay.cs | 2 +- .../{ => Online}/TestCaseBadgeContainer.cs | 2 +- .../{ => Online}/TestCaseBeatmapSetOverlay.cs | 2 +- .../{ => Online}/TestCaseChannelTabControl.cs | 2 +- .../{ => Online}/TestCaseChatDisplay.cs | 2 +- .../Visual/{ => Online}/TestCaseChatLink.cs | 18 +++++++-------- .../Visual/{ => Online}/TestCaseDirect.cs | 2 +- .../{ => Online}/TestCaseDirectPanel.cs | 2 +- .../TestCaseExternalLinkButton.cs | 2 +- .../Visual/{ => Online}/TestCaseGraph.cs | 2 +- .../{ => Online}/TestCaseHistoricalSection.cs | 2 +- .../Visual/{ => Online}/TestCaseRankGraph.cs | 10 ++++----- .../Visual/{ => Online}/TestCaseSocial.cs | 2 +- .../TestCaseStandAloneChatDisplay.cs | 2 +- .../Visual/{ => Online}/TestCaseUserPanel.cs | 2 +- .../{ => Online}/TestCaseUserProfile.cs | 2 +- .../TestCaseUserProfileRecentSection.cs | 10 ++++----- .../Visual/{ => Online}/TestCaseUserRanks.cs | 8 +++---- .../TestCaseKeyConfiguration.cs | 2 +- .../Visual/{ => Settings}/TestCaseSettings.cs | 2 +- .../TestCaseBeatmapCarousel.cs | 2 +- .../TestCaseBeatmapDetailArea.cs | 2 +- .../TestCaseBeatmapDetails.cs | 2 +- .../TestCaseBeatmapInfoWedge.cs | 4 ++-- .../TestCaseBeatmapOptionsOverlay.cs | 2 +- .../TestCaseBeatmapScoresContainer.cs | 14 ++++++------ .../{ => SongSelect}/TestCaseLeaderboard.cs | 12 +++++----- .../TestCasePlaySongSelect.cs | 4 ++-- .../{ => Tournament}/TestCaseDrawings.cs | 2 +- .../TestCaseBeatSyncedContainer.cs | 6 ++--- .../TestCaseBreadcrumbs.cs | 2 +- .../TestCaseButtonSystem.cs | 2 +- .../TestCaseContextMenu.cs | 4 ++-- .../{ => UserInterface}/TestCaseCursors.cs | 2 +- .../TestCaseDialogOverlay.cs | 2 +- .../TestCaseDrawableDate.cs | 2 +- .../TestCaseHoldToConfirmOverlay.cs | 2 +- .../{ => UserInterface}/TestCaseIconButton.cs | 6 ++--- .../TestCaseLabelledTextBox.cs | 6 ++--- .../TestCaseLoadingAnimation.cs | 2 +- .../{ => UserInterface}/TestCaseMods.cs | 22 +++++++++---------- .../TestCaseMusicController.cs | 2 +- .../TestCaseNotificationOverlay.cs | 2 +- .../TestCaseOnScreenDisplay.cs | 2 +- .../TestCaseParallaxContainer.cs | 2 +- .../TestCasePopupDialog.cs | 2 +- .../TestCaseScreenBreadcrumbControl.cs | 2 +- .../{ => UserInterface}/TestCaseTabControl.cs | 2 +- .../TestCaseTextAwesome.cs | 2 +- .../TestCaseTwoLayerButton.cs | 2 +- ...stCaseUpdateableBeatmapBackgroundSprite.cs | 2 +- .../TestCaseVolumePieces.cs | 2 +- 101 files changed, 162 insertions(+), 163 deletions(-) rename osu.Game.Tests/Visual/{ => Background}/TestCaseBackgroundScreenBeatmap.cs (99%) rename osu.Game.Tests/Visual/{ => Components}/TestCaseIdleTracker.cs (99%) rename osu.Game.Tests/Visual/{ => Components}/TestCasePollingComponent.cs (99%) rename osu.Game.Tests/Visual/{ => Components}/TestCasePreviewTrackManager.cs (99%) rename osu.Game.Tests/Visual/{ => Editor}/TestCaseBeatDivisorControl.cs (95%) rename osu.Game.Tests/Visual/{ => Editor}/TestCaseEditorCompose.cs (95%) rename osu.Game.Tests/Visual/{ => Editor}/TestCaseEditorComposeRadioButtons.cs (97%) rename osu.Game.Tests/Visual/{ => Editor}/TestCaseEditorComposeTimeline.cs (99%) rename osu.Game.Tests/Visual/{ => Editor}/TestCaseEditorMenuBar.cs (99%) rename osu.Game.Tests/Visual/{ => Editor}/TestCaseEditorSeekSnapping.cs (99%) rename osu.Game.Tests/Visual/{ => Editor}/TestCaseEditorSummaryTimeline.cs (96%) rename osu.Game.Tests/Visual/{ => Editor}/TestCaseHitObjectComposer.cs (98%) rename osu.Game.Tests/Visual/{ => Editor}/TestCasePlaybackControl.cs (96%) rename osu.Game.Tests/Visual/{ => Editor}/TestCaseWaveContainer.cs (97%) rename osu.Game.Tests/Visual/{ => Editor}/TestCaseWaveform.cs (98%) rename osu.Game.Tests/Visual/{ => Editor}/TestCaseZoomableScrollContainer.cs (99%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCaseAutoplay.cs (97%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCaseBreakOverlay.cs (98%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCaseGameplayMenuOverlay.cs (99%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCaseHoldForMenuButton.cs (97%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCaseKeyCounter.cs (98%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCaseMedalOverlay.cs (95%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCasePause.cs (99%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCasePlayerLoader.cs (98%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCasePlayerReferenceLeaking.cs (97%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCaseReplay.cs (97%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCaseReplaySettingsOverlay.cs (97%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCaseResults.cs (97%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCaseScoreCounter.cs (98%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCaseScrollingHitObjects.cs (99%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCaseSkinReloadable.cs (99%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCaseSkipOverlay.cs (91%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCaseSongProgress.cs (98%) rename osu.Game.Tests/Visual/{ => Gameplay}/TestCaseStoryboard.cs (98%) rename osu.Game.Tests/Visual/{ => Menus}/TestCaseDisclaimer.cs (96%) rename osu.Game.Tests/Visual/{ => Menus}/TestCaseIntroSequence.cs (97%) rename osu.Game.Tests/Visual/{ => Menus}/TestCaseLoaderAnimation.cs (99%) rename osu.Game.Tests/Visual/{ => Menus}/TestCaseToolbar.cs (97%) rename osu.Game.Tests/Visual/{ => Multiplayer}/TestCaseLoungeRoomsContainer.cs (98%) rename osu.Game.Tests/Visual/{ => Multiplayer}/TestCaseMatchHeader.cs (97%) rename osu.Game.Tests/Visual/{ => Multiplayer}/TestCaseMatchHostInfo.cs (95%) rename osu.Game.Tests/Visual/{ => Multiplayer}/TestCaseMatchInfo.cs (98%) rename osu.Game.Tests/Visual/{ => Multiplayer}/TestCaseMatchLeaderboard.cs (97%) rename osu.Game.Tests/Visual/{ => Multiplayer}/TestCaseMatchParticipants.cs (97%) rename osu.Game.Tests/Visual/{ => Multiplayer}/TestCaseMatchResults.cs (98%) rename osu.Game.Tests/Visual/{ => Multiplayer}/TestCaseMatchSettingsOverlay.cs (99%) rename osu.Game.Tests/Visual/{ => Multiplayer}/TestCaseMultiHeader.cs (96%) rename osu.Game.Tests/Visual/{ => Multiplayer}/TestCaseMultiScreen.cs (79%) rename osu.Game.Tests/Visual/{ => Multiplayer}/TestCaseRoomStatus.cs (97%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseAccountCreationOverlay.cs (97%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseBadgeContainer.cs (98%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseBeatmapSetOverlay.cs (99%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseChannelTabControl.cs (99%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseChatDisplay.cs (97%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseChatLink.cs (99%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseDirect.cs (99%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseDirectPanel.cs (97%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseExternalLinkButton.cs (94%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseGraph.cs (97%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseHistoricalSection.cs (97%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseRankGraph.cs (98%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseSocial.cs (98%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseStandAloneChatDisplay.cs (98%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseUserPanel.cs (98%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseUserProfile.cs (99%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseUserProfileRecentSection.cs (99%) rename osu.Game.Tests/Visual/{ => Online}/TestCaseUserRanks.cs (97%) rename osu.Game.Tests/Visual/{ => Settings}/TestCaseKeyConfiguration.cs (93%) rename osu.Game.Tests/Visual/{ => Settings}/TestCaseSettings.cs (95%) rename osu.Game.Tests/Visual/{ => SongSelect}/TestCaseBeatmapCarousel.cs (99%) rename osu.Game.Tests/Visual/{ => SongSelect}/TestCaseBeatmapDetailArea.cs (99%) rename osu.Game.Tests/Visual/{ => SongSelect}/TestCaseBeatmapDetails.cs (99%) rename osu.Game.Tests/Visual/{ => SongSelect}/TestCaseBeatmapInfoWedge.cs (99%) rename osu.Game.Tests/Visual/{ => SongSelect}/TestCaseBeatmapOptionsOverlay.cs (96%) rename osu.Game.Tests/Visual/{ => SongSelect}/TestCaseBeatmapScoresContainer.cs (99%) rename osu.Game.Tests/Visual/{ => SongSelect}/TestCaseLeaderboard.cs (99%) rename osu.Game.Tests/Visual/{ => SongSelect}/TestCasePlaySongSelect.cs (99%) rename osu.Game.Tests/Visual/{ => Tournament}/TestCaseDrawings.cs (98%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseBeatSyncedContainer.cs (99%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseBreadcrumbs.cs (96%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseButtonSystem.cs (96%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseContextMenu.cs (98%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseCursors.cs (99%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseDialogOverlay.cs (98%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseDrawableDate.cs (98%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseHoldToConfirmOverlay.cs (97%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseIconButton.cs (98%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseLabelledTextBox.cs (96%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseLoadingAnimation.cs (97%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseMods.cs (99%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseMusicController.cs (95%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseNotificationOverlay.cs (99%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseOnScreenDisplay.cs (98%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseParallaxContainer.cs (96%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCasePopupDialog.cs (96%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseScreenBreadcrumbControl.cs (99%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseTabControl.cs (96%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseTextAwesome.cs (97%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseTwoLayerButton.cs (90%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseUpdateableBeatmapBackgroundSprite.cs (98%) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseVolumePieces.cs (96%) diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs rename to osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs index ba3a02a843..e05fb2b306 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs @@ -33,7 +33,7 @@ using osu.Game.Users; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Background { [TestFixture] public class TestCaseBackgroundScreenBeatmap : ManualInputManagerTestCase diff --git a/osu.Game.Tests/Visual/TestCaseIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestCaseIdleTracker.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseIdleTracker.cs rename to osu.Game.Tests/Visual/Components/TestCaseIdleTracker.cs index a7a1831ba7..bf59c116bb 100644 --- a/osu.Game.Tests/Visual/TestCaseIdleTracker.cs +++ b/osu.Game.Tests/Visual/Components/TestCaseIdleTracker.cs @@ -9,7 +9,7 @@ using osu.Game.Input; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Components { [TestFixture] public class TestCaseIdleTracker : ManualInputManagerTestCase diff --git a/osu.Game.Tests/Visual/TestCasePollingComponent.cs b/osu.Game.Tests/Visual/Components/TestCasePollingComponent.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCasePollingComponent.cs rename to osu.Game.Tests/Visual/Components/TestCasePollingComponent.cs index 63f4f88948..6582145f6e 100644 --- a/osu.Game.Tests/Visual/TestCasePollingComponent.cs +++ b/osu.Game.Tests/Visual/Components/TestCasePollingComponent.cs @@ -13,7 +13,7 @@ using osu.Game.Online; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Components { public class TestCasePollingComponent : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestCasePreviewTrackManager.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs rename to osu.Game.Tests/Visual/Components/TestCasePreviewTrackManager.cs index 87a1fb0faf..4b6ae696fe 100644 --- a/osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs +++ b/osu.Game.Tests/Visual/Components/TestCasePreviewTrackManager.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Beatmaps; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Components { public class TestCasePreviewTrackManager : OsuTestCase, IPreviewTrackOwner { diff --git a/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editor/TestCaseBeatDivisorControl.cs similarity index 95% rename from osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs rename to osu.Game.Tests/Visual/Editor/TestCaseBeatDivisorControl.cs index daf71a6447..e822e01110 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseBeatDivisorControl.cs @@ -9,7 +9,7 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { public class TestCaseBeatDivisorControl : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs b/osu.Game.Tests/Visual/Editor/TestCaseEditorCompose.cs similarity index 95% rename from osu.Game.Tests/Visual/TestCaseEditorCompose.cs rename to osu.Game.Tests/Visual/Editor/TestCaseEditorCompose.cs index a52454d684..aa7c7f5cb3 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseEditorCompose.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit.Compose; using osu.Game.Tests.Beatmaps; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCaseEditorCompose : EditorClockTestCase diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs b/osu.Game.Tests/Visual/Editor/TestCaseEditorComposeRadioButtons.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs rename to osu.Game.Tests/Visual/Editor/TestCaseEditorComposeRadioButtons.cs index 5a4ac77372..499db1b69f 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseEditorComposeRadioButtons.cs @@ -7,7 +7,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Screens.Edit.Components.RadioButtons; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCaseEditorComposeRadioButtons : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestCaseEditorComposeTimeline.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs rename to osu.Game.Tests/Visual/Editor/TestCaseEditorComposeTimeline.cs index 9ae9b55546..d7712293c3 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseEditorComposeTimeline.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -14,9 +13,10 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCaseEditorComposeTimeline : EditorClockTestCase diff --git a/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs b/osu.Game.Tests/Visual/Editor/TestCaseEditorMenuBar.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs rename to osu.Game.Tests/Visual/Editor/TestCaseEditorMenuBar.cs index 2abbf7cb80..b012d4b52d 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseEditorMenuBar.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Components.Menus; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCaseEditorMenuBar : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs b/osu.Game.Tests/Visual/Editor/TestCaseEditorSeekSnapping.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs rename to osu.Game.Tests/Visual/Editor/TestCaseEditorSeekSnapping.cs index 0ec87e6f52..9daba54b58 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseEditorSeekSnapping.cs @@ -14,7 +14,7 @@ using osu.Game.Tests.Beatmaps; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCaseEditorSeekSnapping : EditorClockTestCase diff --git a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/Editor/TestCaseEditorSummaryTimeline.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs rename to osu.Game.Tests/Visual/Editor/TestCaseEditorSummaryTimeline.cs index 305924958b..99d6385804 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseEditorSummaryTimeline.cs @@ -6,12 +6,12 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osuTK; -using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Tests.Beatmaps; +using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCaseEditorSummaryTimeline : EditorClockTestCase diff --git a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs b/osu.Game.Tests/Visual/Editor/TestCaseHitObjectComposer.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs rename to osu.Game.Tests/Visual/Editor/TestCaseHitObjectComposer.cs index 988c0459d4..be335fb71f 100644 --- a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseHitObjectComposer.cs @@ -7,7 +7,6 @@ using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Timing; -using osuTK; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -20,8 +19,9 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Beatmaps; +using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] [Cached(Type = typeof(IPlacementHandler))] diff --git a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs b/osu.Game.Tests/Visual/Editor/TestCasePlaybackControl.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCasePlaybackControl.cs rename to osu.Game.Tests/Visual/Editor/TestCasePlaybackControl.cs index abcff24c67..7d9b43251e 100644 --- a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs +++ b/osu.Game.Tests/Visual/Editor/TestCasePlaybackControl.cs @@ -10,7 +10,7 @@ using osu.Game.Screens.Edit.Components; using osu.Game.Tests.Beatmaps; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCasePlaybackControl : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseWaveContainer.cs b/osu.Game.Tests/Visual/Editor/TestCaseWaveContainer.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseWaveContainer.cs rename to osu.Game.Tests/Visual/Editor/TestCaseWaveContainer.cs index 07a282a1a7..e87304ded6 100644 --- a/osu.Game.Tests/Visual/TestCaseWaveContainer.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseWaveContainer.cs @@ -12,7 +12,7 @@ using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCaseWaveContainer : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseWaveform.cs b/osu.Game.Tests/Visual/Editor/TestCaseWaveform.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseWaveform.cs rename to osu.Game.Tests/Visual/Editor/TestCaseWaveform.cs index 9330070392..c35e8741c1 100644 --- a/osu.Game.Tests/Visual/TestCaseWaveform.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseWaveform.cs @@ -2,16 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] public class TestCaseWaveform : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editor/TestCaseZoomableScrollContainer.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs rename to osu.Game.Tests/Visual/Editor/TestCaseZoomableScrollContainer.cs index c3e8e4e05f..e2cf1ef28a 100644 --- a/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseZoomableScrollContainer.cs @@ -14,7 +14,7 @@ using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { public class TestCaseZoomableScrollContainer : ManualInputManagerTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseAutoplay.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs index 4d6a0ae7b8..2b0254f232 100644 --- a/osu.Game.Tests/Visual/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs @@ -7,7 +7,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [Description("Player instantiated with an autoplay mod.")] public class TestCaseAutoplay : AllPlayersTestCase diff --git a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseBreakOverlay.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseBreakOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseBreakOverlay.cs index f45d3c98ca..dda8005f70 100644 --- a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseBreakOverlay.cs @@ -1,12 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Beatmaps.Timing; using System.Collections.Generic; using NUnit.Framework; +using osu.Game.Beatmaps.Timing; using osu.Game.Screens.Play; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseBreakOverlay : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseGameplayMenuOverlay.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseGameplayMenuOverlay.cs index 0a240186d2..8e43bf6d3a 100644 --- a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseGameplayMenuOverlay.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using osuTK.Input; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -13,8 +12,9 @@ using osu.Framework.Logging; using osu.Game.Input.Bindings; using osu.Game.Screens.Play; using osuTK; +using osuTK.Input; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [Description("player pause/fail screens")] public class TestCaseGameplayMenuOverlay : ManualInputManagerTestCase diff --git a/osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseHoldForMenuButton.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseHoldForMenuButton.cs index a4fadbd3db..14e9c7cdb6 100644 --- a/osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseHoldForMenuButton.cs @@ -10,7 +10,7 @@ using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Input; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [Description("'Hold to Quit' UI element")] public class TestCaseHoldForMenuButton : ManualInputManagerTestCase diff --git a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseKeyCounter.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseKeyCounter.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseKeyCounter.cs index 52caffc29f..f616ffe4ed 100644 --- a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseKeyCounter.cs @@ -11,7 +11,7 @@ using osu.Framework.Timing; using osu.Game.Screens.Play; using osuTK.Input; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseKeyCounter : ManualInputManagerTestCase diff --git a/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseMedalOverlay.cs similarity index 95% rename from osu.Game.Tests/Visual/TestCaseMedalOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseMedalOverlay.cs index c7c85fc412..dd686c36e6 100644 --- a/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseMedalOverlay.cs @@ -8,7 +8,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.MedalSplash; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseMedalOverlay : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCasePause.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCasePause.cs rename to osu.Game.Tests/Visual/Gameplay/TestCasePause.cs index d5d2cebbab..1ed61c9fe1 100644 --- a/osu.Game.Tests/Visual/TestCasePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { public class TestCasePause : PlayerTestCase { diff --git a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCasePlayerLoader.cs rename to osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs index 2bc416f7f4..bcedcb10a6 100644 --- a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs @@ -9,7 +9,7 @@ using osu.Game.Beatmaps; using osu.Game.Screens; using osu.Game.Screens.Play; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { public class TestCasePlayerLoader : ManualInputManagerTestCase { diff --git a/osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerReferenceLeaking.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs rename to osu.Game.Tests/Visual/Gameplay/TestCasePlayerReferenceLeaking.cs index 3e009ae080..5937d489f2 100644 --- a/osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerReferenceLeaking.cs @@ -8,7 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Screens.Play; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { public class TestCasePlayerReferenceLeaking : AllPlayersTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseReplay.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs index 3a7e2352f8..b98ce96fbb 100644 --- a/osu.Game.Tests/Visual/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [Description("Player instantiated with a replay.")] public class TestCaseReplay : AllPlayersTestCase diff --git a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseReplaySettingsOverlay.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseReplaySettingsOverlay.cs index af71efb9e7..2fdfda0d80 100644 --- a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseReplaySettingsOverlay.cs @@ -7,7 +7,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseReplaySettingsOverlay : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseResults.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseResults.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseResults.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseResults.cs index c2880c1ea2..d9da45f39a 100644 --- a/osu.Game.Tests/Visual/TestCaseResults.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseResults.cs @@ -13,7 +13,7 @@ using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Pages; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseResults : ScreenTestCase diff --git a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseScoreCounter.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseScoreCounter.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseScoreCounter.cs index 3519ea67a6..3dd5c99e45 100644 --- a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseScoreCounter.cs @@ -9,7 +9,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.HUD; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseScoreCounter : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs index 5ebb9b270f..a1cec1e18f 100644 --- a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Extensions.IEnumerableExtensions; -using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -15,8 +14,9 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseScrollingHitObjects : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseSkinReloadable.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseSkinReloadable.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseSkinReloadable.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseSkinReloadable.cs index 94f01e9d32..a9fbf35d37 100644 --- a/osu.Game.Tests/Visual/TestCaseSkinReloadable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseSkinReloadable.cs @@ -13,7 +13,7 @@ using osu.Game.Graphics; using osu.Game.Skinning; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { public class TestCaseSkinReloadable : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseSkipOverlay.cs similarity index 91% rename from osu.Game.Tests/Visual/TestCaseSkipOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseSkipOverlay.cs index b51ba9c563..b46d79ac04 100644 --- a/osu.Game.Tests/Visual/TestCaseSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseSkipOverlay.cs @@ -4,7 +4,7 @@ using NUnit.Framework; using osu.Game.Screens.Play; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseSkipOverlay : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseSongProgress.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseSongProgress.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseSongProgress.cs index 511272a5ae..e17dcef19c 100644 --- a/osu.Game.Tests/Visual/TestCaseSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseSongProgress.cs @@ -10,7 +10,7 @@ using osu.Framework.Timing; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Play; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseSongProgress : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseStoryboard.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseStoryboard.cs rename to osu.Game.Tests/Visual/Gameplay/TestCaseStoryboard.cs index c4b41e40f4..651683a671 100644 --- a/osu.Game.Tests/Visual/TestCaseStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseStoryboard.cs @@ -13,7 +13,7 @@ using osu.Game.Overlays; using osu.Game.Storyboards.Drawables; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] public class TestCaseStoryboard : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseDisclaimer.cs b/osu.Game.Tests/Visual/Menus/TestCaseDisclaimer.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseDisclaimer.cs rename to osu.Game.Tests/Visual/Menus/TestCaseDisclaimer.cs index 8bba16e4b4..68a1ceec16 100644 --- a/osu.Game.Tests/Visual/TestCaseDisclaimer.cs +++ b/osu.Game.Tests/Visual/Menus/TestCaseDisclaimer.cs @@ -6,7 +6,7 @@ using osu.Game.Online.API; using osu.Game.Screens.Menu; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Menus { public class TestCaseDisclaimer : ScreenTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseIntroSequence.cs b/osu.Game.Tests/Visual/Menus/TestCaseIntroSequence.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseIntroSequence.cs rename to osu.Game.Tests/Visual/Menus/TestCaseIntroSequence.cs index 0e57a76167..0b924e56f5 100644 --- a/osu.Game.Tests/Visual/TestCaseIntroSequence.cs +++ b/osu.Game.Tests/Visual/Menus/TestCaseIntroSequence.cs @@ -4,14 +4,14 @@ using System; using System.Collections.Generic; using NUnit.Framework; -using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Screens.Menu; +using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Menus { [TestFixture] public class TestCaseIntroSequence : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs b/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs rename to osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs index 3803764194..899f9d431b 100644 --- a/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs +++ b/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs @@ -9,7 +9,7 @@ using osu.Game.Screens; using osu.Game.Screens.Menu; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Menus { [TestFixture] public class TestCaseLoaderAnimation : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseToolbar.cs b/osu.Game.Tests/Visual/Menus/TestCaseToolbar.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseToolbar.cs rename to osu.Game.Tests/Visual/Menus/TestCaseToolbar.cs index cb5f33911b..4da17f9944 100644 --- a/osu.Game.Tests/Visual/TestCaseToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestCaseToolbar.cs @@ -8,7 +8,7 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Game.Overlays.Toolbar; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Menus { [TestFixture] public class TestCaseToolbar : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs index 13bc5e24d9..34de61cb5b 100644 --- a/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs @@ -14,7 +14,7 @@ using osu.Game.Screens.Multi.Lounge.Components; using osu.Game.Users; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { public class TestCaseLoungeRoomsContainer : MultiplayerTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHeader.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseMatchHeader.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHeader.cs index 296e5f24ac..81cb90c7cd 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHeader.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Multi.Match.Components; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { public class TestCaseMatchHeader : MultiplayerTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseMatchHostInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHostInfo.cs similarity index 95% rename from osu.Game.Tests/Visual/TestCaseMatchHostInfo.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHostInfo.cs index 45092c5b93..d2dc417100 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchHostInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchHostInfo.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics; using osu.Game.Screens.Multi.Match.Components; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { public class TestCaseMatchHostInfo : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseMatchInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchInfo.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseMatchInfo.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMatchInfo.cs index 901c4f1644..6b04b71da4 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchInfo.cs @@ -11,7 +11,7 @@ using osu.Game.Online.Multiplayer.RoomStatuses; using osu.Game.Rulesets; using osu.Game.Screens.Multi.Match.Components; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { [TestFixture] public class TestCaseMatchInfo : MultiplayerTestCase diff --git a/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchLeaderboard.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMatchLeaderboard.cs index 484a212a38..8ec323dbc3 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchLeaderboard.cs @@ -10,7 +10,7 @@ using osu.Game.Screens.Multi.Match.Components; using osu.Game.Users; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { public class TestCaseMatchLeaderboard : MultiplayerTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseMatchParticipants.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchParticipants.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseMatchParticipants.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMatchParticipants.cs index 716523c23c..5382726516 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchParticipants.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchParticipants.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Game.Screens.Multi.Match.Components; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { [TestFixture] public class TestCaseMatchParticipants : MultiplayerTestCase diff --git a/osu.Game.Tests/Visual/TestCaseMatchResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchResults.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseMatchResults.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMatchResults.cs index 582c035e82..69606c9ba7 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchResults.cs @@ -16,7 +16,7 @@ using osu.Game.Screens.Multi.Ranking.Types; using osu.Game.Screens.Ranking; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { public class TestCaseMatchResults : MultiplayerTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchSettingsOverlay.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMatchSettingsOverlay.cs index 11c7d3ef70..51854800e3 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMatchSettingsOverlay.cs @@ -15,7 +15,7 @@ using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi; using osu.Game.Screens.Multi.Match.Components; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { public class TestCaseMatchSettingsOverlay : MultiplayerTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseMultiHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMultiHeader.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseMultiHeader.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMultiHeader.cs index f7802e2d08..b74c303726 100644 --- a/osu.Game.Tests/Visual/TestCaseMultiHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMultiHeader.cs @@ -7,7 +7,7 @@ using osu.Framework.Screens; using osu.Game.Screens; using osu.Game.Screens.Multi; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { [TestFixture] public class TestCaseMultiHeader : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseMultiScreen.cs similarity index 79% rename from osu.Game.Tests/Visual/TestCaseMultiScreen.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseMultiScreen.cs index 804e3c5b1f..ef381efd67 100644 --- a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseMultiScreen.cs @@ -4,25 +4,24 @@ using System; using System.Collections.Generic; using NUnit.Framework; -using osu.Game.Screens.Multi; using osu.Game.Screens.Multi.Lounge; using osu.Game.Screens.Multi.Lounge.Components; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { [TestFixture] public class TestCaseMultiScreen : ScreenTestCase { public override IReadOnlyList RequiredTypes => new[] { - typeof(Multiplayer), + typeof(Screens.Multi.Multiplayer), typeof(LoungeSubScreen), typeof(FilterControl) }; public TestCaseMultiScreen() { - Multiplayer multi = new Multiplayer(); + Screens.Multi.Multiplayer multi = new Screens.Multi.Multiplayer(); AddStep(@"show", () => LoadScreen(multi)); } diff --git a/osu.Game.Tests/Visual/TestCaseRoomStatus.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseRoomStatus.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseRoomStatus.cs rename to osu.Game.Tests/Visual/Multiplayer/TestCaseRoomStatus.cs index 7d175c3c49..a7c7d41ed4 100644 --- a/osu.Game.Tests/Visual/TestCaseRoomStatus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseRoomStatus.cs @@ -9,7 +9,7 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.RoomStatuses; using osu.Game.Screens.Multi.Lounge.Components; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { public class TestCaseRoomStatus : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestCaseAccountCreationOverlay.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseAccountCreationOverlay.cs rename to osu.Game.Tests/Visual/Online/TestCaseAccountCreationOverlay.cs index 24380645d1..5cdb90b61f 100644 --- a/osu.Game.Tests/Visual/TestCaseAccountCreationOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseAccountCreationOverlay.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.AccountCreation; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { public class TestCaseAccountCreationOverlay : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseBadgeContainer.cs b/osu.Game.Tests/Visual/Online/TestCaseBadgeContainer.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseBadgeContainer.cs rename to osu.Game.Tests/Visual/Online/TestCaseBadgeContainer.cs index 892c87653d..631cb190d2 100644 --- a/osu.Game.Tests/Visual/TestCaseBadgeContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseBadgeContainer.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseBadgeContainer : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs rename to osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs index b98014b866..e2985623fc 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs @@ -14,7 +14,7 @@ using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseBeatmapSetOverlay : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs b/osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseChannelTabControl.cs rename to osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs index e90b5f5372..fdc3d5394f 100644 --- a/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs @@ -15,7 +15,7 @@ using osu.Game.Overlays.Chat.Tabs; using osu.Game.Users; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { public class TestCaseChannelTabControl : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestCaseChatDisplay.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseChatDisplay.cs rename to osu.Game.Tests/Visual/Online/TestCaseChatDisplay.cs index 57e4850f84..6e20165c1b 100644 --- a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseChatDisplay.cs @@ -12,7 +12,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.Chat; using osu.Game.Overlays.Chat.Tabs; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [Description("Testing chat api and overlay")] public class TestCaseChatDisplay : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/Online/TestCaseChatLink.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseChatLink.cs rename to osu.Game.Tests/Visual/Online/TestCaseChatLink.cs index ecab64ccf3..8843f136a1 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseChatLink.cs @@ -1,24 +1,24 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Online.Chat; -using osu.Game.Overlays.Chat; -using osu.Game.Users; using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; using osu.Game.Overlays; +using osu.Game.Overlays.Chat; +using osu.Game.Users; +using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseChatLink : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseDirect.cs b/osu.Game.Tests/Visual/Online/TestCaseDirect.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseDirect.cs rename to osu.Game.Tests/Visual/Online/TestCaseDirect.cs index 24ac38c128..ff57104d8a 100644 --- a/osu.Game.Tests/Visual/TestCaseDirect.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseDirect.cs @@ -8,7 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Rulesets; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseDirect : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestCaseDirectPanel.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseDirectPanel.cs rename to osu.Game.Tests/Visual/Online/TestCaseDirectPanel.cs index beb88ac56c..fbda531792 100644 --- a/osu.Game.Tests/Visual/TestCaseDirectPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseDirectPanel.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Tests.Beatmaps; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { public class TestCaseDirectPanel : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs b/osu.Game.Tests/Visual/Online/TestCaseExternalLinkButton.cs similarity index 94% rename from osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs rename to osu.Game.Tests/Visual/Online/TestCaseExternalLinkButton.cs index 6f807e96f1..a73cbd86d0 100644 --- a/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseExternalLinkButton.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using osu.Game.Graphics.UserInterface; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { public class TestCaseExternalLinkButton : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseGraph.cs b/osu.Game.Tests/Visual/Online/TestCaseGraph.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseGraph.cs rename to osu.Game.Tests/Visual/Online/TestCaseGraph.cs index 6a5865b752..77e850fc92 100644 --- a/osu.Game.Tests/Visual/TestCaseGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseGraph.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseGraph : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs b/osu.Game.Tests/Visual/Online/TestCaseHistoricalSection.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseHistoricalSection.cs rename to osu.Game.Tests/Visual/Online/TestCaseHistoricalSection.cs index 60e6148c49..92aa9320c8 100644 --- a/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseHistoricalSection.cs @@ -12,7 +12,7 @@ using osu.Game.Overlays.Profile.Sections; using osu.Game.Overlays.Profile.Sections.Historical; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseHistoricalSection : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseRankGraph.cs b/osu.Game.Tests/Visual/Online/TestCaseRankGraph.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseRankGraph.cs rename to osu.Game.Tests/Visual/Online/TestCaseRankGraph.cs index f41033c0be..dff018bf91 100644 --- a/osu.Game.Tests/Visual/TestCaseRankGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseRankGraph.cs @@ -1,19 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osuTK; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using System.Collections.Generic; -using System; -using NUnit.Framework; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; +using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseRankGraph : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseSocial.cs b/osu.Game.Tests/Visual/Online/TestCaseSocial.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseSocial.cs rename to osu.Game.Tests/Visual/Online/TestCaseSocial.cs index d621bc600d..48325713df 100644 --- a/osu.Game.Tests/Visual/TestCaseSocial.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseSocial.cs @@ -8,7 +8,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.Social; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseSocial : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestCaseStandAloneChatDisplay.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseStandAloneChatDisplay.cs rename to osu.Game.Tests/Visual/Online/TestCaseStandAloneChatDisplay.cs index 65ae70168e..4c4b3b2612 100644 --- a/osu.Game.Tests/Visual/TestCaseStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseStandAloneChatDisplay.cs @@ -7,7 +7,7 @@ using osu.Game.Online.Chat; using osu.Game.Users; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { public class TestCaseStandAloneChatDisplay : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseUserPanel.cs b/osu.Game.Tests/Visual/Online/TestCaseUserPanel.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseUserPanel.cs rename to osu.Game.Tests/Visual/Online/TestCaseUserPanel.cs index 8caa608d5e..b2877f7bd7 100644 --- a/osu.Game.Tests/Visual/TestCaseUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseUserPanel.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Users; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseUserPanel : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseUserProfile.cs rename to osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs index aa0bd37449..5b86de28f9 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs @@ -13,7 +13,7 @@ using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseUserProfile : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs b/osu.Game.Tests/Visual/Online/TestCaseUserProfileRecentSection.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs rename to osu.Game.Tests/Visual/Online/TestCaseUserProfileRecentSection.cs index da50653831..6b29ed1e85 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseUserProfileRecentSection.cs @@ -1,20 +1,20 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Profile.Sections; using osu.Game.Overlays.Profile.Sections.Recent; -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Online.API.Requests.Responses; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseUserProfileRecentSection : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseUserRanks.cs b/osu.Game.Tests/Visual/Online/TestCaseUserRanks.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseUserRanks.cs rename to osu.Game.Tests/Visual/Online/TestCaseUserRanks.cs index 96638ef703..64257f8877 100644 --- a/osu.Game.Tests/Visual/TestCaseUserRanks.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseUserRanks.cs @@ -1,6 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -8,11 +11,8 @@ using osu.Game.Graphics; using osu.Game.Overlays.Profile.Sections; using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Users; -using System; -using System.Collections.Generic; -using NUnit.Framework; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestCaseUserRanks : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseKeyConfiguration.cs b/osu.Game.Tests/Visual/Settings/TestCaseKeyConfiguration.cs similarity index 93% rename from osu.Game.Tests/Visual/TestCaseKeyConfiguration.cs rename to osu.Game.Tests/Visual/Settings/TestCaseKeyConfiguration.cs index cd299be1e9..ce179c21ba 100644 --- a/osu.Game.Tests/Visual/TestCaseKeyConfiguration.cs +++ b/osu.Game.Tests/Visual/Settings/TestCaseKeyConfiguration.cs @@ -4,7 +4,7 @@ using NUnit.Framework; using osu.Game.Overlays; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Settings { [TestFixture] public class TestCaseKeyConfiguration : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseSettings.cs b/osu.Game.Tests/Visual/Settings/TestCaseSettings.cs similarity index 95% rename from osu.Game.Tests/Visual/TestCaseSettings.cs rename to osu.Game.Tests/Visual/Settings/TestCaseSettings.cs index 67f32a8335..e846d5c020 100644 --- a/osu.Game.Tests/Visual/TestCaseSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestCaseSettings.cs @@ -6,7 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Game.Overlays; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Settings { [TestFixture] public class TestCaseSettings : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapCarousel.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs rename to osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapCarousel.cs index 956d84618c..1500605896 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapCarousel.cs @@ -17,7 +17,7 @@ using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] public class TestCaseBeatmapCarousel : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetailArea.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs rename to osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetailArea.cs index 6cc3982f9c..722a63f2b0 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetailArea.cs @@ -10,7 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Screens.Select; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] [System.ComponentModel.Description("PlaySongSelect leaderboard/details area")] diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetails.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs rename to osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetails.cs index 84af6453f5..37987b8884 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapDetails.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Screens.Select; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [Description("PlaySongSelect beatmap details")] public class TestCaseBeatmapDetails : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapInfoWedge.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs rename to osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapInfoWedge.cs index 0d77ac666b..f3e44bd808 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapInfoWedge.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using NUnit.Framework; -using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,8 +19,9 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Taiko; using osu.Game.Screens.Select; using osu.Game.Tests.Beatmaps; +using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] public class TestCaseBeatmapInfoWedge : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapOptionsOverlay.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs rename to osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapOptionsOverlay.cs index fdab57193b..49038dc2cf 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapOptionsOverlay.cs @@ -7,7 +7,7 @@ using osu.Game.Screens.Select.Options; using osuTK.Graphics; using osuTK.Input; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [Description("bottom beatmap details")] public class TestCaseBeatmapOptionsOverlay : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs rename to osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs index bb55c0b1e8..c7970b6ebb 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs @@ -1,24 +1,24 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; +using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; -using osu.Game.Users; -using System.Collections.Generic; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets.Osu; using osu.Game.Scoring; +using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [System.ComponentModel.Description("in BeatmapOverlay")] public class TestCaseBeatmapScoresContainer : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseLeaderboard.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseLeaderboard.cs rename to osu.Game.Tests/Visual/SongSelect/TestCaseLeaderboard.cs index eb1a2c0249..13ae6f228a 100644 --- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseLeaderboard.cs @@ -4,18 +4,18 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using osu.Framework.Graphics; -using osu.Game.Screens.Select.Leaderboards; -using osu.Game.Users; -using osu.Framework.Allocation; -using osuTK; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Scoring; +using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Users; +using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [Description("PlaySongSelect leaderboard")] public class TestCaseLeaderboard : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCasePlaySongSelect.cs rename to osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs index 4a2cf24c6d..d5bc452d75 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs @@ -23,7 +23,7 @@ using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] public class TestCasePlaySongSelect : ScreenTestCase @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual public override IReadOnlyList RequiredTypes => new[] { - typeof(SongSelect), + typeof(Screens.Select.SongSelect), typeof(BeatmapCarousel), typeof(CarouselItem), diff --git a/osu.Game.Tests/Visual/TestCaseDrawings.cs b/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseDrawings.cs rename to osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs index aad135b71f..9453d0a5b2 100644 --- a/osu.Game.Tests/Visual/TestCaseDrawings.cs +++ b/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs @@ -6,7 +6,7 @@ using System.ComponentModel; using osu.Game.Screens.Tournament; using osu.Game.Screens.Tournament.Teams; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Tournament { [Description("for tournament use")] public class TestCaseDrawings : ScreenTestCase diff --git a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseBeatSyncedContainer.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseBeatSyncedContainer.cs index 2fd8d467f6..dcd194e050 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseBeatSyncedContainer.cs @@ -8,16 +8,16 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Lists; using osu.Framework.Timing; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osuTK.Graphics; -using osu.Framework.Lists; -using osu.Game.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseBeatSyncedContainer : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseBreadcrumbs.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseBreadcrumbs.cs index 98ab884ead..5e09e0a5b9 100644 --- a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseBreadcrumbs.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseBreadcrumbs : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseButtonSystem.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseButtonSystem.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseButtonSystem.cs index 8ea2ab9dde..261e87ff07 100644 --- a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseButtonSystem.cs @@ -11,7 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Screens.Menu; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseButtonSystem : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseContextMenu.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseContextMenu.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseContextMenu.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseContextMenu.cs index 5cbe97e21d..71cde787f9 100644 --- a/osu.Game.Tests/Visual/TestCaseContextMenu.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseContextMenu.cs @@ -7,12 +7,12 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osuTK; using osuTK.Graphics; -using osu.Game.Graphics.Cursor; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseContextMenu : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseCursors.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseCursors.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseCursors.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseCursors.cs index 7d38a76c7d..5f45d9ba4d 100644 --- a/osu.Game.Tests/Visual/TestCaseCursors.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseCursors.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseCursors : ManualInputManagerTestCase diff --git a/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseDialogOverlay.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseDialogOverlay.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseDialogOverlay.cs index e832793fc2..6b32f711e9 100644 --- a/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseDialogOverlay.cs @@ -6,7 +6,7 @@ using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseDialogOverlay : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseDrawableDate.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseDrawableDate.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseDrawableDate.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseDrawableDate.cs index 8d2182dd78..e8662ce965 100644 --- a/osu.Game.Tests/Visual/TestCaseDrawableDate.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseDrawableDate.cs @@ -9,7 +9,7 @@ using osu.Game.Graphics; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { public class TestCaseDrawableDate : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseHoldToConfirmOverlay.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseHoldToConfirmOverlay.cs index c9a7e9c39f..38dc4a11dc 100644 --- a/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseHoldToConfirmOverlay.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Menu; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { public class TestCaseHoldToConfirmOverlay : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseIconButton.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseIconButton.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseIconButton.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseIconButton.cs index 63e10b6ecc..2898d1a1cc 100644 --- a/osu.Game.Tests/Visual/TestCaseIconButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseIconButton.cs @@ -2,16 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osuTK; -using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseIconButton : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseLabelledTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseLabelledTextBox.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseLabelledTextBox.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseLabelledTextBox.cs index 4b424f9875..781dfbdcc1 100644 --- a/osu.Game.Tests/Visual/TestCaseLabelledTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseLabelledTextBox.cs @@ -1,15 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using System; -using System.Collections.Generic; using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseLabelledTextBox : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseLoadingAnimation.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseLoadingAnimation.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseLoadingAnimation.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseLoadingAnimation.cs index f5dc1d449a..43f6f0e4db 100644 --- a/osu.Game.Tests/Visual/TestCaseLoadingAnimation.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseLoadingAnimation.cs @@ -7,7 +7,7 @@ using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { public class TestCaseLoadingAnimation : GridTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseMods.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs index cb7e783bee..aab44f7d92 100644 --- a/osu.Game.Tests/Visual/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs @@ -2,26 +2,26 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; +using osu.Game.Overlays.Mods.Sections; using osu.Game.Rulesets; -using osu.Game.Screens.Play.HUD; -using osuTK; +using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; -using System.Linq; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Bindables; -using osu.Game.Graphics.UserInterface; -using osu.Game.Graphics.Sprites; -using osu.Game.Overlays.Mods.Sections; -using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play.HUD; +using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [Description("mod select and icon display")] public class TestCaseMods : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseMusicController.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseMusicController.cs similarity index 95% rename from osu.Game.Tests/Visual/TestCaseMusicController.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseMusicController.cs index b4a1c11b1a..644c7eb4fc 100644 --- a/osu.Game.Tests/Visual/TestCaseMusicController.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseMusicController.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Overlays; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseMusicController : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseNotificationOverlay.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseNotificationOverlay.cs index 9e70df91b6..4819597d22 100644 --- a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseNotificationOverlay.cs @@ -12,7 +12,7 @@ using osu.Framework.MathUtils; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseNotificationOverlay : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseOnScreenDisplay.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseOnScreenDisplay.cs index 8b5ae0b208..7ad42cb926 100644 --- a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseOnScreenDisplay.cs @@ -8,7 +8,7 @@ using osu.Framework.Configuration.Tracking; using osu.Framework.Graphics; using osu.Game.Overlays; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseOnScreenDisplay : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseParallaxContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseParallaxContainer.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseParallaxContainer.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseParallaxContainer.cs index 41b029d69e..5de4c3f41f 100644 --- a/osu.Game.Tests/Visual/TestCaseParallaxContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseParallaxContainer.cs @@ -6,7 +6,7 @@ using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Screens.Backgrounds; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { public class TestCaseParallaxContainer : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCasePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestCasePopupDialog.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCasePopupDialog.cs rename to osu.Game.Tests/Visual/UserInterface/TestCasePopupDialog.cs index 51b5c41e0d..490903a906 100644 --- a/osu.Game.Tests/Visual/TestCasePopupDialog.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCasePopupDialog.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Overlays.Dialog; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCasePopupDialog : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseScreenBreadcrumbControl.cs similarity index 99% rename from osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseScreenBreadcrumbControl.cs index dad684689e..b4b24ae4df 100644 --- a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseScreenBreadcrumbControl.cs @@ -13,7 +13,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Screens; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseScreenBreadcrumbControl : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseTabControl.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseTabControl.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseTabControl.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseTabControl.cs index ebf8f3bb30..480dc73dde 100644 --- a/osu.Game.Tests/Visual/TestCaseTabControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseTabControl.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Select.Filter; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [Description("SongSelect filter control")] public class TestCaseTabControl : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseTextAwesome.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseTextAwesome.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseTextAwesome.cs index 6ab9a46e8d..40179387e2 100644 --- a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseTextAwesome.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.Cursor; using osu.Game.Graphics; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] public class TestCaseTextAwesome : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseTwoLayerButton.cs similarity index 90% rename from osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseTwoLayerButton.cs index 9141aaa580..8d3cc7a0f2 100644 --- a/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseTwoLayerButton.cs @@ -4,7 +4,7 @@ using System.ComponentModel; using osu.Game.Graphics.UserInterface; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [Description("mostly back button")] public class TestCaseTwoLayerButton : OsuTestCase diff --git a/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs similarity index 98% rename from osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs index 0981b482a1..74114b2e53 100644 --- a/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs @@ -12,7 +12,7 @@ using osu.Game.Online.API.Requests; using osu.Game.Rulesets; using osu.Game.Tests.Beatmaps.IO; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { public class TestCaseUpdateableBeatmapBackgroundSprite : OsuTestCase { diff --git a/osu.Game.Tests/Visual/TestCaseVolumePieces.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseVolumePieces.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseVolumePieces.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseVolumePieces.cs index 6dee047ae6..3ad1c922e4 100644 --- a/osu.Game.Tests/Visual/TestCaseVolumePieces.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseVolumePieces.cs @@ -8,7 +8,7 @@ using osu.Game.Overlays.Volume; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { public class TestCaseVolumePieces : OsuTestCase { From 01ce8f161e0d8001c3255b7ed611f419ff357f6a Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Mon, 25 Mar 2019 05:49:57 +0800 Subject: [PATCH 202/623] make scaling container background use BackgroundScreenStack with BackgroundScreenDefault --- osu.Game/Graphics/Containers/ScalingContainer.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 51f068d920..7cbcb2d880 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -6,7 +6,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; -using osu.Game.Graphics.Backgrounds; +using osu.Game.Screens; +using osu.Game.Screens.Backgrounds; using osuTK; namespace osu.Game.Graphics.Containers @@ -32,7 +33,7 @@ namespace osu.Game.Graphics.Containers private readonly Container sizableContainer; - private Drawable backgroundLayer; + private BackgroundScreenStack backgroundLayer; /// /// Create a new instance. @@ -118,7 +119,7 @@ namespace osu.Game.Graphics.Containers if (requiresBackgroundVisible) { if (backgroundLayer == null) - LoadComponentAsync(backgroundLayer = new Background("Menu/menu-background-1") + LoadComponentAsync(backgroundLayer = new BackgroundScreenStack() { Colour = OsuColour.Gray(0.1f), Alpha = 0, @@ -126,6 +127,7 @@ namespace osu.Game.Graphics.Containers }, d => { AddInternal(d); + d.Push(new BackgroundScreenDefault()); d.FadeTo(requiresBackgroundVisible ? 1 : 0, 4000, Easing.OutQuint); }); else From fee260fa032fbe9f8b98cd6756005f09a3191ccc Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Mon, 25 Mar 2019 06:05:57 +0800 Subject: [PATCH 203/623] remove empty argument list --- osu.Game/Graphics/Containers/ScalingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 7cbcb2d880..72a7ca56c4 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -119,7 +119,7 @@ namespace osu.Game.Graphics.Containers if (requiresBackgroundVisible) { if (backgroundLayer == null) - LoadComponentAsync(backgroundLayer = new BackgroundScreenStack() + LoadComponentAsync(backgroundLayer = new BackgroundScreenStack { Colour = OsuColour.Gray(0.1f), Alpha = 0, From 371166955e14141045f1931436b86cd79e4ca9c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 12:39:40 +0900 Subject: [PATCH 204/623] Apply a few minor refactors --- osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs | 8 ++++---- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 ++ osu.Game/Screens/OsuScreenStack.cs | 6 +++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index e891e98066..146fb9ab69 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -53,8 +53,6 @@ namespace osu.Game.Tests.Visual private BeatmapManager manager; private RulesetStore rulesets; - private OsuScreenStack screenStack; - [BackgroundDependencyLoader] private void load(GameHost host) { @@ -81,8 +79,10 @@ namespace osu.Game.Tests.Visual [SetUp] public virtual void SetUp() => Schedule(() => { - Child = screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; - screenStack.Push(songSelect = new DummySongSelect()); + Child = new OsuScreenStack(songSelect = new DummySongSelect()) + { + RelativeSizeAxes = Axes.Both + }; }); /// diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index e1e76f109d..a7cfbd3300 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -120,6 +120,8 @@ namespace osu.Game.Rulesets.Objects.Drawables } } + protected override void ClearInternal(bool disposeChildren = true) => throw new InvalidOperationException($"Should never clear a {nameof(DrawableHitObject)}"); + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/OsuScreenStack.cs b/osu.Game/Screens/OsuScreenStack.cs index 02e5919cdd..0844e32d46 100644 --- a/osu.Game/Screens/OsuScreenStack.cs +++ b/osu.Game/Screens/OsuScreenStack.cs @@ -36,11 +36,11 @@ namespace osu.Game.Screens Child = backgroundScreenStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }, }; - ScreenPushed += setParallax; - ScreenExited += setParallax; + ScreenPushed += onScreenChange; + ScreenExited += onScreenChange; } - private void setParallax(IScreen prev, IScreen next) + private void onScreenChange(IScreen prev, IScreen next) { parallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * ((IOsuScreen)next)?.BackgroundParallaxAmount ?? 1.0f; } From e705eb586da8e6705dc1ea6377019da940ac99de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 12:51:54 +0900 Subject: [PATCH 205/623] Revert unintentional change --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index a7cfbd3300..e1e76f109d 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -120,8 +120,6 @@ namespace osu.Game.Rulesets.Objects.Drawables } } - protected override void ClearInternal(bool disposeChildren = true) => throw new InvalidOperationException($"Should never clear a {nameof(DrawableHitObject)}"); - protected override void LoadComplete() { base.LoadComplete(); From cd1b171df791bd4e59f585c5607ff18f85e20c65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 13:28:51 +0900 Subject: [PATCH 206/623] Refactor async load logic (not required due to stack presence) --- .../Graphics/Containers/ScalingContainer.cs | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 72a7ca56c4..2cc62d9a48 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; using osu.Game.Configuration; using osu.Game.Screens; using osu.Game.Screens.Backgrounds; @@ -33,7 +34,7 @@ namespace osu.Game.Graphics.Containers private readonly Container sizableContainer; - private BackgroundScreenStack backgroundLayer; + private BackgroundScreenStack backgroundStack; /// /// Create a new instance. @@ -113,28 +114,29 @@ namespace osu.Game.Graphics.Containers private void updateSize() { + const float fade_time = 500; + if (targetMode == ScalingMode.Everything) { // the top level scaling container manages the background to be displayed while scaling. if (requiresBackgroundVisible) { - if (backgroundLayer == null) - LoadComponentAsync(backgroundLayer = new BackgroundScreenStack + if (backgroundStack == null) + { + AddInternal(backgroundStack = new BackgroundScreenStack { Colour = OsuColour.Gray(0.1f), Alpha = 0, Depth = float.MaxValue - }, d => - { - AddInternal(d); - d.Push(new BackgroundScreenDefault()); - d.FadeTo(requiresBackgroundVisible ? 1 : 0, 4000, Easing.OutQuint); }); - else - backgroundLayer.FadeIn(500); + + backgroundStack.Push(new ScalignBackgroundScreen()); + } + + backgroundStack.FadeIn(fade_time); } else - backgroundLayer?.FadeOut(500); + backgroundStack?.FadeOut(fade_time); } bool scaling = targetMode == null || scalingMode.Value == targetMode; @@ -150,6 +152,14 @@ namespace osu.Game.Graphics.Containers sizableContainer.ResizeTo(targetSize, 500, Easing.OutQuart).OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); } + private class ScalignBackgroundScreen : BackgroundScreenDefault + { + public override void OnEntering(IScreen last) + { + this.FadeInFromZero(4000, Easing.OutQuint); + } + } + private class AlwaysInputContainer : Container { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; From c97116c91ac0a80a126993ea0e02ffe56de7d9df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 13:34:22 +0900 Subject: [PATCH 207/623] Add safety against DrawableHitObject implementations clearing children --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index e1e76f109d..a7cfbd3300 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -120,6 +120,8 @@ namespace osu.Game.Rulesets.Objects.Drawables } } + protected override void ClearInternal(bool disposeChildren = true) => throw new InvalidOperationException($"Should never clear a {nameof(DrawableHitObject)}"); + protected override void LoadComplete() { base.LoadComplete(); From 5b83e97e9dbb060706b8f78b42168be26dac8111 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 13:38:50 +0900 Subject: [PATCH 208/623] Fix typo --- osu.Game/Graphics/Containers/ScalingContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 2cc62d9a48..8f07c3a656 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -130,7 +130,7 @@ namespace osu.Game.Graphics.Containers Depth = float.MaxValue }); - backgroundStack.Push(new ScalignBackgroundScreen()); + backgroundStack.Push(new ScalingBackgroundScreen()); } backgroundStack.FadeIn(fade_time); @@ -152,7 +152,7 @@ namespace osu.Game.Graphics.Containers sizableContainer.ResizeTo(targetSize, 500, Easing.OutQuart).OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); } - private class ScalignBackgroundScreen : BackgroundScreenDefault + private class ScalingBackgroundScreen : BackgroundScreenDefault { public override void OnEntering(IScreen last) { From 39df8cce19358445e0ca7548bb8ae1623e4c25a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 13:47:28 +0900 Subject: [PATCH 209/623] Fix incorrect usage of InternalChildren Could cause overwriting of components added by base DrawableHitObjcet class (such as samples) --- .../Objects/Drawable/DrawableBananaShower.cs | 2 +- .../Objects/Drawable/DrawableDroplet.cs | 5 +---- .../Objects/Drawable/DrawableFruit.cs | 4 ++-- .../Objects/Drawable/DrawableJuiceStream.cs | 2 +- .../Objects/Drawables/DrawableHoldNote.cs | 4 ++-- .../Objects/Drawables/DrawableHoldNoteTick.cs | 4 ++-- .../Objects/Drawables/DrawableNote.cs | 2 +- .../Objects/Drawables/DrawableBarLine.cs | 17 +++++++---------- .../Objects/Drawables/DrawableTaikoHitObject.cs | 4 ++-- .../Gameplay/TestCaseScrollingHitObjects.cs | 6 +++--- 10 files changed, 22 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs index aaf723fae6..57c71d0411 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable Origin = Anchor.BottomLeft; X = 0; - InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both }; + AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both }); foreach (var b in s.NestedHitObjects.Cast()) AddNested(getVisualRepresentation?.Invoke(b)); diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs index 8fed8eb4cd..9cabdc3dd9 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs @@ -26,10 +26,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable [BackgroundDependencyLoader] private void load() { - InternalChild = pulp = new Pulp - { - Size = Size - }; + AddInternal(pulp = new Pulp { Size = Size }); } public override Color4 AccentColour diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index fac4b8098c..0dc3f73404 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable // todo: this should come from the skin. AccentColour = colourForRepresentation(HitObject.VisualRepresentation); - InternalChildren = new[] + AddRangeInternal(new[] { createPulp(HitObject.VisualRepresentation), border = new Circle @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable } } }, - }; + }); if (HitObject.HyperDash) { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs index 7bb12453a8..ae2232f8f1 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable Origin = Anchor.BottomLeft; X = 0; - InternalChild = dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }; + AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }); foreach (var o in s.NestedHitObjects.Cast()) AddNested(getVisualRepresentation?.Invoke(o)); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 4bfd940aa0..9368af987d 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { RelativeSizeAxes = Axes.X; - InternalChildren = new Drawable[] + AddRangeInternal(new Drawable[] { bodyPiece = new BodyPiece { @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre } - }; + }); foreach (var tick in tickContainer) AddNested(tick); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index 43aac7907f..f2be8d614c 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables RelativeSizeAxes = Axes.X; Size = new Vector2(1); - InternalChildren = new[] + AddRangeInternal(new[] { glowContainer = new CircularContainer { @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } } } - }; + }); } public override Color4 AccentColour diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 7ef90cdb9c..82a34224f4 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables CornerRadius = 5; Masking = true; - InternalChild = headPiece = new NotePiece(); + AddInternal(headPiece = new NotePiece()); } protected override void OnDirectionChanged(ValueChangedEvent e) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs index 5e4c6edb43..f8909fb98c 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs @@ -44,17 +44,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables RelativeSizeAxes = Axes.Y; Width = tracker_width; - InternalChildren = new[] + AddInternal(Tracker = new Box { - Tracker = new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - EdgeSmoothness = new Vector2(0.5f, 0), - Alpha = 0.75f - } - }; + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + EdgeSmoothness = new Vector2(0.5f, 0), + Alpha = 0.75f + }); } protected override void UpdateState(ArmedState state) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 5f755c7cc3..8dfe89eea7 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected DrawableTaikoHitObject(TaikoHitObject hitObject) : base(hitObject) { - InternalChildren = new[] + AddRangeInternal(new[] { nonProxiedContent = new Container { @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Child = Content = new Container { RelativeSizeAxes = Axes.Both } }, proxiedContent = new ProxiedContentContainer { RelativeSizeAxes = Axes.Both } - }; + }); } /// diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs index a1cec1e18f..c99a4bb89b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs @@ -170,12 +170,12 @@ namespace osu.Game.Tests.Visual.Gameplay { Origin = Anchor.Centre; - InternalChild = new Box + AddInternal(new Box { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both - }; + }); switch (direction) { @@ -205,7 +205,7 @@ namespace osu.Game.Tests.Visual.Gameplay Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; - InternalChild = new Box { Size = new Vector2(75) }; + AddInternal(new Box { Size = new Vector2(75) }); } protected override void UpdateState(ArmedState state) From bc16a82494582f0d6c3f294ae3dc7f89e9cd07fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Mar 2019 17:39:53 +0900 Subject: [PATCH 210/623] Move osu! cursor to its own class --- .../UI/Cursor/GameplayCursorContainer.cs | 140 ----------------- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 148 ++++++++++++++++++ 2 files changed, 148 insertions(+), 140 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs index 8c6723f5be..b64561e4f7 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs @@ -1,19 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Skinning; -using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI.Cursor { @@ -88,136 +79,5 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); } - - public class OsuCursor : SkinReloadableDrawable - { - private bool cursorExpand; - - private Bindable cursorScale; - private Bindable autoCursorScale; - private readonly IBindable beatmap = new Bindable(); - - private Container expandTarget; - private Drawable scaleTarget; - - public OsuCursor() - { - Origin = Anchor.Centre; - Size = new Vector2(28); - } - - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - cursorExpand = skin.GetValue(s => s.CursorExpand ?? true); - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config, IBindable beatmap) - { - InternalChild = expandTarget = new Container - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Child = scaleTarget = new SkinnableDrawable("cursor", _ => new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = Size.X / 6, - BorderColour = Color4.White, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Pink.Opacity(0.5f), - Radius = 5, - }, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - }, - new CircularContainer - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = Size.X / 3, - BorderColour = Color4.White.Opacity(0.5f), - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - }, - }, - }, - new CircularContainer - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.1f), - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, - }, - }, - } - }, restrictSize: false) - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - } - }; - - this.beatmap.BindTo(beatmap); - this.beatmap.ValueChanged += _ => calculateScale(); - - cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); - cursorScale.ValueChanged += _ => calculateScale(); - - autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); - autoCursorScale.ValueChanged += _ => calculateScale(); - - calculateScale(); - } - - private void calculateScale() - { - float scale = (float)cursorScale.Value; - - if (autoCursorScale.Value && beatmap.Value != null) - { - // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. - scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY); - } - - scaleTarget.Scale = new Vector2(scale); - } - - private const float pressed_scale = 1.2f; - private const float released_scale = 1f; - - public void Expand() - { - if (!cursorExpand) return; - - expandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); - } - - public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); - } } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs new file mode 100644 index 0000000000..ecdafb0fa2 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -0,0 +1,148 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI.Cursor +{ + public class OsuCursor : SkinReloadableDrawable + { + private bool cursorExpand; + + private Bindable cursorScale; + private Bindable autoCursorScale; + private readonly IBindable beatmap = new Bindable(); + + private Container expandTarget; + private Drawable scaleTarget; + + public OsuCursor() + { + Origin = Anchor.Centre; + Size = new Vector2(28); + } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + cursorExpand = skin.GetValue(s => s.CursorExpand ?? true); + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, IBindable beatmap) + { + InternalChild = expandTarget = new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Child = scaleTarget = new SkinnableDrawable("cursor", _ => new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = Size.X / 6, + BorderColour = Color4.White, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Pink.Opacity(0.5f), + Radius = 5, + }, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + new CircularContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = Size.X / 3, + BorderColour = Color4.White.Opacity(0.5f), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + }, + }, + new CircularContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.1f), + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + }, + }, + } + }, restrictSize: false) + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + } + }; + + this.beatmap.BindTo(beatmap); + this.beatmap.ValueChanged += _ => calculateScale(); + + cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); + cursorScale.ValueChanged += _ => calculateScale(); + + autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); + autoCursorScale.ValueChanged += _ => calculateScale(); + + calculateScale(); + } + + private void calculateScale() + { + float scale = (float)cursorScale.Value; + + if (autoCursorScale.Value && beatmap.Value != null) + { + // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. + scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY); + } + + scaleTarget.Scale = new Vector2(scale); + } + + private const float pressed_scale = 1.2f; + private const float released_scale = 1f; + + public void Expand() + { + if (!cursorExpand) return; + + expandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); + } + + public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); + } +} From 8ad4009c33d5c0f9984d85cfb26ed69dcf0b6eec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Mar 2019 19:04:07 +0900 Subject: [PATCH 211/623] osu! resume overlay --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 105 +++++++++++++++++++ osu.Game/Screens/Play/ResumeOverlay.cs | 74 +++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs create mode 100644 osu.Game/Screens/Play/ResumeOverlay.cs diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs new file mode 100644 index 0000000000..c2000131db --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -0,0 +1,105 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Screens.Play; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI +{ + public class OsuResumeOverlay : ResumeOverlay + { + private OsuClickToResumeCursor clickToResumeCursor; + + private GameplayCursorContainer localCursorContainer; + + public override CursorContainer LocalCursor => State == Visibility.Visible ? localCursorContainer : null; + + protected override string Message => "Click the orange cursor to resume"; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Add(clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume }); + } + + public override void Show() + { + base.Show(); + clickToResumeCursor.ShowAt(GameplayCursor.ActiveCursor.Position); + Add(localCursorContainer = new GameplayCursorContainer()); + } + + public override void Hide() + { + localCursorContainer.Expire(); + base.Hide(); + } + + protected override bool OnHover(HoverEvent e) => true; + + public class OsuClickToResumeCursor : OsuCursor, IKeyBindingHandler + { + public override bool HandlePositionalInput => true; + + public Action ResumeRequested; + + public OsuClickToResumeCursor() + { + RelativePositionAxes = Axes.Both; + } + + protected override bool OnHover(HoverEvent e) + { + updateColour(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateColour(); + base.OnHoverLost(e); + } + + public bool OnPressed(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + if (!IsHovered) return false; + + this.ScaleTo(new Vector2(2), TRANSITION_TIME, Easing.OutQuint); + + ResumeRequested?.Invoke(); + return true; + } + + return false; + } + + public bool OnReleased(OsuAction action) => false; + + public void ShowAt(Vector2 activeCursorPosition) => Schedule(() => + { + updateColour(); + this.MoveTo(activeCursorPosition); + this.ScaleTo(new Vector2(4)).Then().ScaleTo(Vector2.One, 1000, Easing.OutQuint); + }); + + private void updateColour() + { + this.FadeColour(IsHovered ? Color4.White : Color4.Orange, 400, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Play/ResumeOverlay.cs b/osu.Game/Screens/Play/ResumeOverlay.cs new file mode 100644 index 0000000000..2ef76069c2 --- /dev/null +++ b/osu.Game/Screens/Play/ResumeOverlay.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play +{ + /// + /// An overlay which can be used to require further user actions before gameplay is resumed. + /// + public abstract class ResumeOverlay : OverlayContainer + { + public CursorContainer GameplayCursor { get; set; } + + /// + /// The action to be performed to complete resuming. + /// + public Action ResumeAction { private get; set; } + + public virtual CursorContainer LocalCursor => null; + + protected const float TRANSITION_TIME = 500; + + protected override bool BlockPositionalInput => false; + + protected abstract string Message { get; } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + protected ResumeOverlay() + { + RelativeSizeAxes = Axes.Both; + } + + protected void Resume() + { + ResumeAction?.Invoke(); + Hide(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AddRange(new Drawable[] + { + new OsuSpriteText + { + RelativePositionAxes = Axes.Both, + Y = 0.4f, + Text = Message, + Font = OsuFont.GetFont(size: 30), + Spacing = new Vector2(5, 0), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Colour = colours.Yellow, + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f) + } + }); + } + + protected override void PopIn() => this.FadeIn(TRANSITION_TIME, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(TRANSITION_TIME, Easing.OutQuint); + } +} From a694626cc66780e6527c3d843b5789d197f8c0b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Mar 2019 16:57:40 +0900 Subject: [PATCH 212/623] Add proper resume request logic --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 31c0afd743..ab10c48ce0 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -169,7 +169,20 @@ namespace osu.Game.Rulesets.UI mod.ApplyToDrawableHitObjects(Playfield.HitObjectContainer.Objects); } - public override void RequestResume(Action continueResume) => continueResume(); + public override void RequestResume(Action continueResume) + { + if (ResumeOverlay != null && (Cursor == null || Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre))) + { + ResumeOverlay.ResumeAction = continueResume; + ResumeOverlay.Show(); + } + else + continueResume(); + } + + public ResumeOverlay ResumeOverlay { get; private set; } + + protected virtual ResumeOverlay CreateResumeOverlay() => null; /// /// Creates and adds the visual representation of a to this . From 57b3b7b54b8ed092b02f770aaf99f9a2c79c4a8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Mar 2019 11:55:05 +0900 Subject: [PATCH 213/623] Add back resume overlay --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index ab10c48ce0..4c0bc9867b 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -138,7 +138,8 @@ namespace osu.Game.Rulesets.UI { KeyBindingInputManager.AddRange(new Drawable[] { - Playfield + Playfield, + (ResumeOverlay = CreateResumeOverlay()) ?? new Container() }); InternalChildren = new Drawable[] From 650a5c993a176022c410d5f35cd1c60923f9d170 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 15:30:51 +0900 Subject: [PATCH 214/623] Add test --- .../TestCaseResumeOverlay.cs | 70 +++++++++++++++++++ osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 3 +- 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestCaseResumeOverlay.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseResumeOverlay.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseResumeOverlay.cs new file mode 100644 index 0000000000..5956f12146 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseResumeOverlay.cs @@ -0,0 +1,70 @@ +// Copyright (c) ppy Pty Ltd . 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseResumeOverlay : ManualInputManagerTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuResumeOverlay), + }; + + public TestCaseResumeOverlay() + { + ManualOsuInputManager osuInputManager; + CursorContainer cursor; + ResumeOverlay resume; + + bool resumeFired = false; + + Child = osuInputManager = new ManualOsuInputManager(new OsuRuleset().RulesetInfo) + { + Children = new Drawable[] + { + cursor = new CursorContainer(), + resume = new OsuResumeOverlay + { + GameplayCursor = cursor + }, + } + }; + + resume.ResumeAction = () => resumeFired = true; + + AddStep("move mouse to center", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre)); + AddStep("show", () => resume.Show()); + + AddStep("move mouse away", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.TopLeft)); + AddStep("click", () => osuInputManager.GameClick()); + AddAssert("not dismissed", () => !resumeFired && resume.State == Visibility.Visible); + + AddStep("move mouse back", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre)); + AddStep("click", () => osuInputManager.GameClick()); + AddAssert("dismissed", () => resumeFired && resume.State == Visibility.Hidden); + } + + private class ManualOsuInputManager : OsuInputManager + { + public ManualOsuInputManager(RulesetInfo ruleset) + : base(ruleset) + { + } + + public void GameClick() + { + KeyBindingContainer.TriggerPressed(OsuAction.LeftButton); + KeyBindingContainer.TriggerReleased(OsuAction.LeftButton); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index c2000131db..9829839841 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Graphics; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Screens.Play; using osuTK; @@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override string Message => "Click the orange cursor to resume"; [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { Add(clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume }); } From 38e481686f03c50c50c958a5c9b71175f4e44218 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 19:21:01 +0900 Subject: [PATCH 215/623] Make PlayfieldAdjustmentContainer universal --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 34 +-- ...s => CatchPlayfieldAdjustmentContainer.cs} | 10 +- .../UI/DrawableCatchRuleset.cs | 2 + .../UI/DrawableManiaRuleset.cs | 9 +- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 2 - .../UI/ManiaPlayfieldAdjustmentContainer.cs | 20 ++ .../Edit/OsuHitObjectComposer.cs | 2 +- .../UI/DrawableOsuRuleset.cs | 5 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 45 ++- ....cs => OsuPlayfieldAdjustmentContainer.cs} | 10 +- .../UI/DrawableTaikoRuleset.cs | 2 + osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 256 +++++++++--------- ...s => TaikoPlayfieldAdjustmentContainer.cs} | 11 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 15 +- .../UI/PlayfieldAdjustmentContainer.cs | 19 ++ 15 files changed, 236 insertions(+), 206 deletions(-) rename osu.Game.Rulesets.Catch/UI/{PlayfieldAdjustmentContainer.cs => CatchPlayfieldAdjustmentContainer.cs} (78%) create mode 100644 osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs rename osu.Game.Rulesets.Osu/UI/{PlayfieldAdjustmentContainer.cs => OsuPlayfieldAdjustmentContainer.cs} (82%) rename osu.Game.Rulesets.Taiko/UI/{PlayfieldAdjustmentContainer.cs => TaikoPlayfieldAdjustmentContainer.cs} (68%) create mode 100644 osu.Game/Rulesets/UI/PlayfieldAdjustmentContainer.cs diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 4dae95b53c..43d0dc026d 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; -using osuTK; namespace osu.Game.Rulesets.Catch.UI { @@ -24,29 +23,20 @@ namespace osu.Game.Rulesets.Catch.UI { Container explodingFruitContainer; - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; - - Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate - - InternalChild = new PlayfieldAdjustmentContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + explodingFruitContainer = new Container { - explodingFruitContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }, - CatcherArea = new CatcherArea(difficulty) - { - GetVisualRepresentation = getVisualRepresentation, - ExplodingFruitTarget = explodingFruitContainer, - Anchor = Anchor.BottomLeft, - Origin = Anchor.TopLeft, - }, - HitObjectContainer - } + RelativeSizeAxes = Axes.Both, + }, + CatcherArea = new CatcherArea(difficulty) + { + GetVisualRepresentation = getVisualRepresentation, + ExplodingFruitTarget = explodingFruitContainer, + Anchor = Anchor.BottomLeft, + Origin = Anchor.TopLeft, + }, + HitObjectContainer }; } diff --git a/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs similarity index 78% rename from osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs rename to osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs index 76daee2bbf..b8d3dc9017 100644 --- a/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs @@ -3,17 +3,23 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Catch.UI { - public class PlayfieldAdjustmentContainer : Container + public class CatchPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer { protected override Container Content => content; private readonly Container content; - public PlayfieldAdjustmentContainer() + public CatchPlayfieldAdjustmentContainer() { + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + + Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate + InternalChild = new Container { Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 406dc10eea..6981c98ec7 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -36,6 +36,8 @@ namespace osu.Game.Rulesets.Catch.UI protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, GetVisualRepresentation); + protected override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer(); + protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); public override DrawableHitObject GetVisualRepresentation(CatchHitObject h) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index a019401d5b..0dc081f3da 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.MathUtils; using osu.Game.Beatmaps; @@ -89,11 +88,9 @@ namespace osu.Game.Rulesets.Mania.UI /// The column which intersects with . public Column GetColumnByPosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition); - protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; + protected override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer(); + + protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages); public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 81888d2773..cbabfcc8b4 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -28,8 +28,6 @@ namespace osu.Game.Rulesets.Mania.UI if (stageDefinitions.Count <= 0) throw new ArgumentException("Can't have zero or fewer stages."); - Size = new Vector2(1, 0.8f); - GridContainer playfieldGrid; AddInternal(playfieldGrid = new GridContainer { diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs new file mode 100644 index 0000000000..d893a3fdde --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Mania.UI +{ + public class ManiaPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer + { + public ManiaPlayfieldAdjustmentContainer() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Size = new Vector2(1, 0.8f); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index dd3925e04f..952fe0b708 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit public override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); - protected override Container CreateLayerContainer() => new PlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both }; + protected override Container CreateLayerContainer() => new OsuPlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both }; public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) { diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index b632e0fb05..1c5032a938 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -1,7 +1,8 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; @@ -32,6 +33,8 @@ namespace osu.Game.Rulesets.Osu.UI protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); + protected override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer(); + public override DrawableHitObject GetVisualRepresentation(OsuHitObject h) { switch (h) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 51733c3c01..5e532d9b04 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -24,41 +24,28 @@ namespace osu.Game.Rulesets.Osu.UI public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); - private readonly PlayfieldAdjustmentContainer adjustmentContainer; - - protected override Container CursorTargetContainer => adjustmentContainer; - protected override CursorContainer CreateCursor() => new GameplayCursorContainer(); public OsuPlayfield() { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - Size = new Vector2(0.75f); - - InternalChild = adjustmentContainer = new PlayfieldAdjustmentContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + connectionLayer = new FollowPointRenderer { - connectionLayer = new FollowPointRenderer - { - RelativeSizeAxes = Axes.Both, - Depth = 2, - }, - judgementLayer = new JudgementContainer - { - RelativeSizeAxes = Axes.Both, - Depth = 1, - }, - HitObjectContainer, - approachCircles = new ApproachCircleProxyContainer - { - RelativeSizeAxes = Axes.Both, - Depth = -1, - }, - } + RelativeSizeAxes = Axes.Both, + Depth = 2, + }, + judgementLayer = new JudgementContainer + { + RelativeSizeAxes = Axes.Both, + Depth = 1, + }, + HitObjectContainer, + approachCircles = new ApproachCircleProxyContainer + { + RelativeSizeAxes = Axes.Both, + Depth = -1, + }, }; } diff --git a/osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs similarity index 82% rename from osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs rename to osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs index c383c47491..e28ff5f460 100644 --- a/osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs @@ -3,17 +3,23 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.UI { - public class PlayfieldAdjustmentContainer : Container + public class OsuPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer { protected override Container Content => content; private readonly Container content; - public PlayfieldAdjustmentContainer() + public OsuPlayfieldAdjustmentContainer() { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Size = new Vector2(0.75f); + InternalChild = new Container { Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 899b91863e..f595432082 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -81,6 +81,8 @@ namespace osu.Game.Rulesets.Taiko.UI public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); + protected override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer(); + protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 35b941b52b..dbff5270d2 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -55,143 +55,137 @@ namespace osu.Game.Rulesets.Taiko.UI public TaikoPlayfield(ControlPointInfo controlPoints) { - InternalChild = new PlayfieldAdjustmentContainer + InternalChildren = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + backgroundContainer = new Container { - backgroundContainer = new Container + Name = "Transparent playfield background", + RelativeSizeAxes = Axes.Both, + Masking = true, + EdgeEffect = new EdgeEffectParameters { - Name = "Transparent playfield background", - RelativeSizeAxes = Axes.Both, - Masking = true, - EdgeEffect = new EdgeEffectParameters + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.2f), + Radius = 5, + }, + Children = new Drawable[] + { + background = new Box { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.2f), - Radius = 5, + RelativeSizeAxes = Axes.Both, + Alpha = 0.6f }, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.6f - }, - } - }, - new Container - { - Name = "Right area", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = left_area_size }, - Children = new Drawable[] - { - new Container - { - Name = "Masked elements before hit objects", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Masking = true, - Children = new Drawable[] - { - hitExplosionContainer = new Container - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Blending = BlendingMode.Additive, - }, - HitTarget = new HitTarget - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit - } - } - }, - barlineContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET } - }, - new Container - { - Name = "Hit objects", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Masking = true, - Child = HitObjectContainer - }, - kiaiExplosionContainer = new Container - { - Name = "Kiai hit explosions", - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Blending = BlendingMode.Additive - }, - judgementContainer = new JudgementContainer - { - Name = "Judgements", - RelativeSizeAxes = Axes.Y, - Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Blending = BlendingMode.Additive - }, - } - }, - overlayBackgroundContainer = new Container - { - Name = "Left overlay", - RelativeSizeAxes = Axes.Y, - Size = new Vector2(left_area_size, 1), - Children = new Drawable[] - { - overlayBackground = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new InputDrum(controlPoints) - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Scale = new Vector2(0.9f), - Margin = new MarginPadding { Right = 20 } - }, - new Box - { - Anchor = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = 10, - Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), - }, - } - }, - new Container - { - Name = "Border", - RelativeSizeAxes = Axes.Both, - Masking = true, - MaskingSmoothness = 0, - BorderThickness = 2, - AlwaysPresent = true, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - } - }, - topLevelHitContainer = new Container - { - Name = "Top level hit objects", - RelativeSizeAxes = Axes.Both, } + }, + new Container + { + Name = "Right area", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = left_area_size }, + Children = new Drawable[] + { + new Container + { + Name = "Masked elements before hit objects", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Masking = true, + Children = new Drawable[] + { + hitExplosionContainer = new Container + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Blending = BlendingMode.Additive, + }, + HitTarget = new HitTarget + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit + } + } + }, + barlineContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET } + }, + new Container + { + Name = "Hit objects", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Masking = true, + Child = HitObjectContainer + }, + kiaiExplosionContainer = new Container + { + Name = "Kiai hit explosions", + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Blending = BlendingMode.Additive + }, + judgementContainer = new JudgementContainer + { + Name = "Judgements", + RelativeSizeAxes = Axes.Y, + Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Blending = BlendingMode.Additive + }, + } + }, + overlayBackgroundContainer = new Container + { + Name = "Left overlay", + RelativeSizeAxes = Axes.Y, + Size = new Vector2(left_area_size, 1), + Children = new Drawable[] + { + overlayBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new InputDrum(controlPoints) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Scale = new Vector2(0.9f), + Margin = new MarginPadding { Right = 20 } + }, + new Box + { + Anchor = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 10, + Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), + }, + } + }, + new Container + { + Name = "Border", + RelativeSizeAxes = Axes.Both, + Masking = true, + MaskingSmoothness = 0, + BorderThickness = 2, + AlwaysPresent = true, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + }, + topLevelHitContainer = new Container + { + Name = "Top level hit objects", + RelativeSizeAxes = Axes.Both, } }; } diff --git a/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs similarity index 68% rename from osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs rename to osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index 0f0ad59fd3..84464b199e 100644 --- a/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs @@ -1,16 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Taiko.UI { - public class PlayfieldAdjustmentContainer : Container + public class TaikoPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer { private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768; private const float default_aspect = 16f / 9f; + public TaikoPlayfieldAdjustmentContainer() + { + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + } + protected override void Update() { base.Update(); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 4c0bc9867b..0ced3476d0 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; @@ -133,20 +133,19 @@ namespace osu.Game.Rulesets.UI return dependencies; } + protected virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer(); + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - KeyBindingInputManager.AddRange(new Drawable[] - { - Playfield, - (ResumeOverlay = CreateResumeOverlay()) ?? new Container() - }); - InternalChildren = new Drawable[] { frameStabilityContainer = new FrameStabilityContainer { - Child = KeyBindingInputManager, + Child = KeyBindingInputManager + .WithChild(CreatePlayfieldAdjustmentContainer() + .WithChild(Playfield) + ) }, Overlays = new Container { RelativeSizeAxes = Axes.Both } }; diff --git a/osu.Game/Rulesets/UI/PlayfieldAdjustmentContainer.cs b/osu.Game/Rulesets/UI/PlayfieldAdjustmentContainer.cs new file mode 100644 index 0000000000..fff4a450e5 --- /dev/null +++ b/osu.Game/Rulesets/UI/PlayfieldAdjustmentContainer.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . 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; + +namespace osu.Game.Rulesets.UI +{ + /// + /// A container which handles sizing of the and any other components that need to match their size. + /// + public class PlayfieldAdjustmentContainer : Container + { + public PlayfieldAdjustmentContainer() + { + RelativeSizeAxes = Axes.Both; + } + } +} From c79d187a896176018dd38d06c18cf7400a33f6d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 19:21:25 +0900 Subject: [PATCH 216/623] Add final osu! resume screen implementation --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 6 ++++-- osu.Game/Rulesets/UI/DrawableRuleset.cs | 10 +++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 1c5032a938..ffb5d3b5d7 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -1,8 +1,7 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; @@ -15,6 +14,7 @@ using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Osu.UI { @@ -35,6 +35,8 @@ namespace osu.Game.Rulesets.Osu.UI protected override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer(); + protected override ResumeOverlay CreateResumeOverlay() => new OsuResumeOverlay(); + public override DrawableHitObject GetVisualRepresentation(OsuHitObject h) { switch (h) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 0ced3476d0..0f61b1cb2f 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; @@ -150,6 +150,13 @@ namespace osu.Game.Rulesets.UI Overlays = new Container { RelativeSizeAxes = Axes.Both } }; + if ((ResumeOverlay = CreateResumeOverlay()) != null) + { + AddInternal(CreateInputManager() + .WithChild(CreatePlayfieldAdjustmentContainer() + .WithChild(ResumeOverlay))); + } + applyRulesetMods(mods, config); loadObjects(); @@ -173,6 +180,7 @@ namespace osu.Game.Rulesets.UI { if (ResumeOverlay != null && (Cursor == null || Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre))) { + ResumeOverlay.GameplayCursor = Cursor; ResumeOverlay.ResumeAction = continueResume; ResumeOverlay.Show(); } From 95889440488e9639b855e85d0c33e56aec63b6eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 19:21:34 +0900 Subject: [PATCH 217/623] Fix multiple cursors appearing --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 9829839841..5312711a7d 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -35,12 +35,16 @@ namespace osu.Game.Rulesets.Osu.UI { base.Show(); clickToResumeCursor.ShowAt(GameplayCursor.ActiveCursor.Position); - Add(localCursorContainer = new GameplayCursorContainer()); + + if (localCursorContainer == null) + Add(localCursorContainer = new GameplayCursorContainer()); } public override void Hide() { - localCursorContainer.Expire(); + localCursorContainer?.Expire(); + localCursorContainer = null; + base.Hide(); } From 06d4856e1716a250d26483aa1e67d3799f33f5bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 19:21:47 +0900 Subject: [PATCH 218/623] Remove unnecessary CursorTargetContainer --- osu.Game/Rulesets/UI/Playfield.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 78d14a27e3..53bfa6af48 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.UI Cursor = CreateCursor(); if (Cursor != null) - CursorTargetContainer.Add(Cursor); + AddInternal(Cursor); } /// @@ -99,11 +99,6 @@ namespace osu.Game.Rulesets.UI /// The cursor, or null if a cursor is not rqeuired. protected virtual CursorContainer CreateCursor() => null; - /// - /// The target container to add the cursor after it is created. - /// - protected virtual Container CursorTargetContainer => null; - /// /// Registers a as a nested . /// This does not add the to the draw hierarchy. From a23dfb58ad11d7b324aa9301ddc1527c862ce56d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 20:25:16 +0900 Subject: [PATCH 219/623] Add base cursor class to retrieve true visibility state --- .../TestCaseGameplayCursor.cs | 3 ++- .../Edit/DrawableOsuEditRuleset.cs | 3 +-- .../UI/Cursor/GameplayCursorContainer.cs | 6 ++--- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 3 +-- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 3 ++- osu.Game/Rulesets/UI/DrawableRuleset.cs | 8 +++--- .../Rulesets/UI/GameplayCursorContainer.cs | 25 +++++++++++++++++++ osu.Game/Rulesets/UI/Playfield.cs | 5 ++-- 8 files changed, 41 insertions(+), 15 deletions(-) create mode 100644 osu.Game/Rulesets/UI/GameplayCursorContainer.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs index 5c1e775c01..1e2a936002 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Rulesets.UI; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests @@ -27,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Tests [BackgroundDependencyLoader] private void load() { - Add(cursorContainer = new GameplayCursorContainer { RelativeSizeAxes = Axes.Both }); + Add(cursorContainer = new OsuCursorContainer { RelativeSizeAxes = Axes.Both }); } } } diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 1a6e78d918..3ae554a5d7 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Cursor; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; @@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Edit private class OsuPlayfieldNoCursor : OsuPlayfield { - protected override CursorContainer CreateCursor() => null; + protected override GameplayCursorContainer CreateCursor() => null; } } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs index b64561e4f7..f028a5f15c 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs @@ -3,12 +3,12 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.UI.Cursor { - public class GameplayCursorContainer : CursorContainer, IKeyBindingHandler + public class OsuCursorContainer : GameplayCursorContainer, IKeyBindingHandler { protected override Drawable CreateCursor() => new OsuCursor(); @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly Container fadeContainer; - public GameplayCursorContainer() + public OsuCursorContainer() { InternalChild = fadeContainer = new Container { diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 5e532d9b04..0cbe0cca85 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Rulesets.UI; using System.Linq; -using osu.Framework.Graphics.Cursor; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.UI.Cursor; @@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.UI public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); - protected override CursorContainer CreateCursor() => new GameplayCursorContainer(); + protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); public OsuPlayfield() { diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 5312711a7d..0d4e7edb7b 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osuTK; using osuTK.Graphics; @@ -37,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.UI clickToResumeCursor.ShowAt(GameplayCursor.ActiveCursor.Position); if (localCursorContainer == null) - Add(localCursorContainer = new GameplayCursorContainer()); + Add(localCursorContainer = new OsuCursorContainer()); } public override void Hide() diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 0f61b1cb2f..2b64aec8ea 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -178,7 +178,7 @@ namespace osu.Game.Rulesets.UI public override void RequestResume(Action continueResume) { - if (ResumeOverlay != null && (Cursor == null || Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre))) + if (ResumeOverlay != null && (Cursor == null || ((GameplayCursorContainer)Cursor).LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre))) { ResumeOverlay.GameplayCursor = Cursor; ResumeOverlay.ResumeAction = continueResume; @@ -284,7 +284,9 @@ namespace osu.Game.Rulesets.UI protected override bool OnHover(HoverEvent e) => true; // required for IProvideCursor - public override CursorContainer Cursor => Playfield.Cursor; + CursorContainer IProvideCursor.Cursor => Playfield.Cursor; + + public override GameplayCursorContainer Cursor => Playfield.Cursor; public bool ProvidingUserCursor => Playfield.Cursor != null && !HasReplayLoaded.Value; @@ -354,7 +356,7 @@ namespace osu.Game.Rulesets.UI /// /// The cursor being displayed by the . May be null if no cursor is provided. /// - public abstract CursorContainer Cursor { get; } + public abstract GameplayCursorContainer Cursor { get; } /// /// Sets a replay to be used, overriding local input. diff --git a/osu.Game/Rulesets/UI/GameplayCursorContainer.cs b/osu.Game/Rulesets/UI/GameplayCursorContainer.cs new file mode 100644 index 0000000000..de73c08809 --- /dev/null +++ b/osu.Game/Rulesets/UI/GameplayCursorContainer.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; + +namespace osu.Game.Rulesets.UI +{ + public class GameplayCursorContainer : CursorContainer + { + /// + /// Because Show/Hide are executed by a parent, is updated immediately even if the cursor + /// is in a non-updating state (via limitations). + /// + /// This holds the true visibility value. + /// + public Visibility LastFrameState; + + protected override void Update() + { + base.Update(); + LastFrameState = State; + } + } +} diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 53bfa6af48..48b950c070 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -10,7 +10,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osuTK; @@ -90,14 +89,14 @@ namespace osu.Game.Rulesets.UI /// /// The cursor currently being used by this . May be null if no cursor is provided. /// - public CursorContainer Cursor { get; private set; } + public GameplayCursorContainer Cursor { get; private set; } /// /// Provide an optional cursor which is to be used for gameplay. /// If providing a cursor, must also point to a valid target container. /// /// The cursor, or null if a cursor is not rqeuired. - protected virtual CursorContainer CreateCursor() => null; + protected virtual GameplayCursorContainer CreateCursor() => null; /// /// Registers a as a nested . From 245f463e3fd554f522f666a01c4864ccfbbde7cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 20:25:47 +0900 Subject: [PATCH 220/623] Don't update gameplay loop while paused --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 5 ++++- osu.Game/Screens/Play/GameplayClock.cs | 3 +++ osu.Game/Screens/Play/GameplayClockContainer.cs | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 161e7aecb4..deec2b8eac 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -36,7 +36,10 @@ namespace osu.Game.Rulesets.UI private void load(GameplayClock clock) { if (clock != null) + { parentGameplayClock = clock; + gameplayClock.IsPaused.BindTo(clock.IsPaused); + } } protected override void LoadComplete() @@ -68,7 +71,7 @@ namespace osu.Game.Rulesets.UI public override bool UpdateSubTree() { requireMoreUpdateLoops = true; - validState = true; + validState = !gameplayClock.IsPaused.Value; int loops = 0; diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 0400bfbc27..3efcfa0f65 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Framework.Timing; namespace osu.Game.Screens.Play @@ -17,6 +18,8 @@ namespace osu.Game.Screens.Play { private readonly IFrameBasedClock underlyingClock; + public readonly BindableBool IsPaused = new BindableBool(); + public GameplayClock(IFrameBasedClock underlyingClock) { this.underlyingClock = underlyingClock; diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index deac5e02bf..546364b26d 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -79,6 +79,8 @@ namespace osu.Game.Screens.Play // the clock to be exposed via DI to children. GameplayClock = new GameplayClock(offsetClock); + + GameplayClock.IsPaused.BindTo(IsPaused); } [BackgroundDependencyLoader] From 15aea7f745ac0b9dfa4695eef7da897f84a02418 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 21:39:15 +0900 Subject: [PATCH 221/623] Update framework --- osu.Game/Screens/Multi/MultiplayerSubScreen.cs | 2 -- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs index ad72072981..65e501b114 100644 --- a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs +++ b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs @@ -14,8 +14,6 @@ namespace osu.Game.Screens.Multi { public override bool DisallowExternalBeatmapRulesetChanges => false; - public override bool RemoveWhenNotAlive => false; - public virtual string ShortTitle => Title; [Resolved(CanBeNull = true)] diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 71324ea0f0..eb5d0fd8ee 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 02099a59bb..c3792a48a1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From a642f1013167c61cd60fe4f76f69c4b391600f02 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 21:52:01 +0900 Subject: [PATCH 222/623] Remove redundant cast --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 2b64aec8ea..bdb1c9c0cc 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -178,7 +178,7 @@ namespace osu.Game.Rulesets.UI public override void RequestResume(Action continueResume) { - if (ResumeOverlay != null && (Cursor == null || ((GameplayCursorContainer)Cursor).LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre))) + if (ResumeOverlay != null && (Cursor == null || Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre))) { ResumeOverlay.GameplayCursor = Cursor; ResumeOverlay.ResumeAction = continueResume; From 82140c38fc39ca7e0124b80ebdd043fd72281584 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 22:00:33 +0900 Subject: [PATCH 223/623] Apply CI fixes --- .../{GameplayCursorContainer.cs => OsuCursorContainer.cs} | 0 osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Rulesets.Osu/UI/Cursor/{GameplayCursorContainer.cs => OsuCursorContainer.cs} (100%) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs similarity index 100% rename from osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs rename to osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index bdb1c9c0cc..905da3c33b 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -178,7 +178,7 @@ namespace osu.Game.Rulesets.UI public override void RequestResume(Action continueResume) { - if (ResumeOverlay != null && (Cursor == null || Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre))) + if (ResumeOverlay != null && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)))) { ResumeOverlay.GameplayCursor = Cursor; ResumeOverlay.ResumeAction = continueResume; From f4aeb390efb7ee2a8620a73c02e0a43bf25b1f86 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 Mar 2019 10:21:34 +0900 Subject: [PATCH 224/623] Initial re-layout of score table --- .../TestCaseBeatmapScoresContainer.cs | 12 +- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 213 +++++++++++++++++- 2 files changed, 222 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs index b626c23f25..2bc0796045 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs @@ -1,18 +1,17 @@ // Copyright (c) ppy Pty Ltd . 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.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -24,6 +23,15 @@ namespace osu.Game.Tests.Visual.SongSelect [Description("in BeatmapOverlay")] public class TestCaseBeatmapScoresContainer : OsuTestCase { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ScoresContainer), + typeof(ScoreTable), + typeof(ScoreTableRow), + typeof(ScoreTableHeader), + typeof(ScoreTableScore) + }; + private readonly Box background; public TestCaseBeatmapScoresContainer() diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index aacbc12cd8..7e838f0aae 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -6,19 +6,52 @@ using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet.Scores { public class ScoreTable : CompositeDrawable { + private const int fade_duration = 100; + private const int text_size = 14; + private readonly FillFlowContainer scoresFlow; + private readonly ScoresGrid scoresGrid; public ScoreTable() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChild = scoresFlow = new FillFlowContainer + InternalChild = scoresGrid = new ScoresGrid + { + RelativeSizeAxes = Axes.X, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 40), + new Dimension(GridSizeMode.Absolute, 70), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Distributed, minSize: 180), + new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), + new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), + new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), + new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), + new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), + new Dimension(GridSizeMode.Distributed, minSize: 40, maxSize: 70), + new Dimension(GridSizeMode.AutoSize), + } + }; + + scoresFlow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -48,6 +81,184 @@ namespace osu.Game.Overlays.BeatmapSet.Scores int index = 0; foreach (var s in value) scoresFlow.Add(new ScoreTableScore(index++, s, maxModsAmount)); + + scoresGrid.Content = value.Select((s, i) => createRowContents(s, i).ToArray()).ToArray(); + } + } + + private IEnumerable createRowContents(APIScoreInfo score, int index) + { + yield return new SpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Text = $"#{index + 1}", + Font = @"Exo2.0-Bold", + TextSize = text_size, + }; + + yield return new DrawableRank(score.Rank) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(30, 20), + FillMode = FillMode.Fit, + }; + + yield return new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 20 }, + Text = $@"{score.TotalScore:N0}", + TextSize = text_size, + Font = index == 0 ? OsuFont.GetFont(weight: FontWeight.Bold) : OsuFont.Default + }; + + yield return new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 20 }, + Text = $@"{score.Accuracy:P2}", + TextSize = text_size, + Colour = score.Accuracy == 1 ? Color4.GreenYellow : Color4.White + }; + + yield return new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new DrawableFlag(score.User.Country) + { + Size = new Vector2(20, 13), + }, + new ClickableScoreUsername + { + User = score.User, + } + } + }; + + yield return new SpriteText + { + Text = $@"{score.MaxCombo:N0}x", + TextSize = text_size, + }; + + yield return new SpriteText + { + Text = $"{score.Statistics[HitResult.Great]}", + TextSize = text_size, + Colour = score.Statistics[HitResult.Great] == 0 ? Color4.Gray : Color4.White + }; + + yield return new SpriteText + { + Text = $"{score.Statistics[HitResult.Good]}", + TextSize = text_size, + Colour = score.Statistics[HitResult.Good] == 0 ? Color4.Gray : Color4.White + }; + + yield return new SpriteText + { + Text = $"{score.Statistics[HitResult.Meh]}", + TextSize = text_size, + Colour = score.Statistics[HitResult.Meh] == 0 ? Color4.Gray : Color4.White + }; + + yield return new SpriteText + { + Text = $"{score.Statistics[HitResult.Miss]}", + TextSize = text_size, + Colour = score.Statistics[HitResult.Miss] == 0 ? Color4.Gray : Color4.White + }; + + yield return new SpriteText + { + Text = $@"{score.PP:N0}", + TextSize = text_size, + }; + + yield return new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + ChildrenEnumerable = score.Mods.Select(m => new ModIcon(m) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.3f), + }) + }; + } + + private class ScoresGrid : GridContainer + { + public ScoresGrid() + { + AutoSizeAxes = Axes.Y; + } + + public Drawable[][] Content + { + get => base.Content; + set + { + base.Content = value; + + RowDimensions = Enumerable.Repeat(new Dimension(GridSizeMode.Absolute, 25), value.Length).ToArray(); + } + } + } + + private class ClickableScoreUsername : ClickableUserContainer + { + private readonly SpriteText text; + private readonly SpriteText textBold; + + public ClickableScoreUsername() + { + Add(text = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + TextSize = text_size, + }); + + Add(textBold = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + TextSize = text_size, + Font = @"Exo2.0-Bold", + Alpha = 0, + }); + } + + protected override void OnUserChange(User user) + { + text.Text = textBold.Text = user.Username; + } + + protected override bool OnHover(HoverEvent e) + { + textBold.FadeIn(fade_duration, Easing.OutQuint); + text.FadeOut(fade_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + textBold.FadeOut(fade_duration, Easing.OutQuint); + text.FadeIn(fade_duration, Easing.OutQuint); + base.OnHoverLost(e); } } } From a0f6718145f4cadb74e86161fe635c221d30e2d9 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 26 Mar 2019 10:48:29 +0900 Subject: [PATCH 225/623] Better tests and implementation --- .../Visual/TestCaseFacadeContainer.cs | 75 ++++++++++++++++++- .../Graphics/Containers/FacadeContainer.cs | 55 ++++++++------ osu.Game/Screens/Play/PlayerLoader.cs | 5 +- 3 files changed, 109 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs b/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs index 3063a4ca3f..d3f854998c 100644 --- a/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs @@ -9,7 +9,9 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Graphics.Containers; @@ -18,6 +20,7 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; +using osuTK; using osuTK.Graphics; namespace osu.Game.Tests.Visual @@ -36,6 +39,7 @@ namespace osu.Game.Tests.Visual private readonly Bindable uiScale = new Bindable(); + private TestScreen screen1; private OsuScreen baseScreen; public TestCaseFacadeContainer() @@ -49,7 +53,6 @@ namespace osu.Game.Tests.Visual baseScreen = null; config.BindWith(OsuSetting.UIScale, uiScale); AddSliderStep("Adjust scale", 1f, 1.5f, 1f, v => uiScale.Value = v); - AddToggleStep("Toggle mods", b => { Beatmap.Value.Mods.Value = b ? Beatmap.Value.Mods.Value.Concat(new[] { new OsuModNoFail() }) : Enumerable.Empty(); }); } [SetUpSteps] @@ -58,9 +61,18 @@ namespace osu.Game.Tests.Visual AddStep("Null screens", () => baseScreen = null); } + [Test] + public void IsolatedTest() + { + bool randomPositions = false; + AddToggleStep("Toggle move continuously", b => randomPositions = b); + AddStep("Move facade to random position", () => LoadScreen(screen1 = new TestScreen(randomPositions))); + } + [Test] public void PlayerLoaderTest() { + AddToggleStep("Toggle mods", b => { Beatmap.Value.Mods.Value = b ? Beatmap.Value.Mods.Value.Concat(new[] { new OsuModNoFail() }) : Enumerable.Empty(); }); AddStep("Add new playerloader", () => LoadScreen(baseScreen = new TestPlayerLoader(() => new TestPlayer { AllowPause = false, @@ -89,6 +101,67 @@ namespace osu.Game.Tests.Visual }; } + private class TestScreen : OsuScreen + { + private TestFacadeContainer facadeContainer; + private FacadeFlowComponent facadeFlowComponent; + private OsuLogo logo; + + private readonly bool randomPositions; + + public TestScreen(bool randomPositions = false) + { + this.randomPositions = randomPositions; + } + + private SpriteText positionText; + private SpriteText sizeAxesText; + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = facadeContainer = new TestFacadeContainer + { + Child = facadeFlowComponent = new FacadeFlowComponent + { + AutoSizeAxes = Axes.Both + } + }; + } + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + logo.FadeIn(350); + logo.ScaleTo(new Vector2(0.15f), 350, Easing.In); + facadeContainer.SetLogo(logo); + moveLogoFacade(); + } + + private void moveLogoFacade() + { + Random random = new Random(); + if (facadeFlowComponent.Transforms.Count == 0) + { + facadeFlowComponent.Delay(500).MoveTo(new Vector2(random.Next(0, 800), random.Next(0, 600)), 300); + } + + if (randomPositions) + Schedule(moveLogoFacade); + } + } + + private class FacadeFlowComponent : FillFlowContainer + { + [BackgroundDependencyLoader] + private void load(Facade facade) + { + facade.Anchor = Anchor.TopCentre; + facade.Origin = Anchor.TopCentre; + Child = facade; + } + } + private class TestPlayerLoader : PlayerLoader { public TestPlayerLoader(Func player) diff --git a/osu.Game/Graphics/Containers/FacadeContainer.cs b/osu.Game/Graphics/Containers/FacadeContainer.cs index d7fae8887f..0511c87166 100644 --- a/osu.Game/Graphics/Containers/FacadeContainer.cs +++ b/osu.Game/Graphics/Containers/FacadeContainer.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.MathUtils; using osu.Game.Screens.Menu; using osuTK; @@ -18,7 +18,6 @@ namespace osu.Game.Graphics.Containers private OsuLogo logo; private bool tracking; - private bool smoothTransform; protected virtual Facade CreateFacade() => new Facade(); @@ -29,7 +28,7 @@ namespace osu.Game.Graphics.Containers private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.Centre); - public void SetLogo(OsuLogo logo, bool resuming, double transformDelay) + public void SetLogo(OsuLogo logo, double transformDelay = 0) { if (logo != null) { @@ -38,41 +37,53 @@ namespace osu.Game.Graphics.Containers Scheduler.AddDelayed(() => { tracking = true; - smoothTransform = !resuming; }, transformDelay); } } + private double startTime; + private double duration = 1000; + + private Vector2 startPosition; + private Easing easing = Easing.InOutExpo; + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - if (logo == null) + if (logo == null || !tracking) return; facade.Size = new Vector2(logo.SizeForFlow * 0.3f); - if (smoothTransform && facade.IsLoaded && logo.Transforms.Count == 0) + if (facade.IsLoaded && logo.Position != logoTrackingPosition) { - // Our initial movement to the tracking location should be smooth. - Schedule(() => + if (logo.RelativePositionAxes != Axes.None) { - facade.Size = new Vector2(logo.SizeForFlow * 0.3f); + logo.Position = logo.Parent.ToLocalSpace(logo.Position); logo.RelativePositionAxes = Axes.None; - logo.MoveTo(logoTrackingPosition, 500, Easing.InOutExpo); - smoothTransform = false; - }); - } - else if (facade.IsLoaded && logo.Transforms.Count == 0) - { - // If all transforms have finished playing, the logo constantly track the position of the facade. - logo.RelativePositionAxes = Axes.None; - logo.Position = logoTrackingPosition; + } + + if (startTime == 0) + { + startTime = Time.Current; + } + + var endTime = startTime + duration; + var remainingDuration = endTime - Time.Current; + + if (remainingDuration <= 0) + { + remainingDuration = 0; + } + + float currentTime = (float)Interpolation.ApplyEasing(easing, remainingDuration / duration); + logo.Position = Vector2.Lerp(logoTrackingPosition, startPosition, currentTime); } } } - - public class Facade : Container - { - } } + +public class Facade : Container +{ +} \ No newline at end of file diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 5817e11277..b7155f771f 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -155,7 +155,7 @@ namespace osu.Game.Screens.Play logo.MoveTo(new Vector2(0.5f), duration, Easing.In); logo.FadeIn(350); - content.SetLogo(logo, resuming, duration); + content.SetLogo(logo, duration); } protected override void LoadComplete() @@ -167,7 +167,7 @@ namespace osu.Game.Screens.Play private ScheduledDelegate pushDebounce; protected VisualSettings VisualSettings; - // Hhere because IsHovered will not update unless we do so. + // Here because IsHovered will not update unless we do so. public override bool HandlePositionalInput => true; private bool readyForPush => player.LoadState == LoadState.Ready && IsHovered && GetContainingInputManager()?.DraggedDrawable == null; @@ -306,7 +306,6 @@ namespace osu.Game.Screens.Play private Sprite backgroundSprite; private ModDisplay modDisplay; private FillFlowContainer fillFlowContainer; - private FacadeContainer facadeContainer; public bool Loading { From be9ac39f542eb6bea400086c9cf04cb7b4a15e69 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 26 Mar 2019 11:11:27 +0900 Subject: [PATCH 226/623] Cleanup --- .../Visual/TestCaseFacadeContainer.cs | 23 ++++--------------- .../Graphics/Containers/FacadeContainer.cs | 2 +- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs b/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs index d3f854998c..5591bec0b8 100644 --- a/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs @@ -11,7 +11,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Graphics.Containers; @@ -32,6 +31,7 @@ namespace osu.Game.Tests.Visual typeof(PlayerLoader), typeof(Player), typeof(Facade), + typeof(FacadeContainer) }; [Cached] @@ -39,9 +39,6 @@ namespace osu.Game.Tests.Visual private readonly Bindable uiScale = new Bindable(); - private TestScreen screen1; - private OsuScreen baseScreen; - public TestCaseFacadeContainer() { Add(logo = new OsuLogo()); @@ -50,30 +47,23 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - baseScreen = null; config.BindWith(OsuSetting.UIScale, uiScale); AddSliderStep("Adjust scale", 1f, 1.5f, 1f, v => uiScale.Value = v); } - [SetUpSteps] - public void SetUpSteps() - { - AddStep("Null screens", () => baseScreen = null); - } - [Test] public void IsolatedTest() { bool randomPositions = false; AddToggleStep("Toggle move continuously", b => randomPositions = b); - AddStep("Move facade to random position", () => LoadScreen(screen1 = new TestScreen(randomPositions))); + AddStep("Move facade to random position", () => LoadScreen(new TestScreen(randomPositions))); } [Test] public void PlayerLoaderTest() { AddToggleStep("Toggle mods", b => { Beatmap.Value.Mods.Value = b ? Beatmap.Value.Mods.Value.Concat(new[] { new OsuModNoFail() }) : Enumerable.Empty(); }); - AddStep("Add new playerloader", () => LoadScreen(baseScreen = new TestPlayerLoader(() => new TestPlayer + AddStep("Add new playerloader", () => LoadScreen(new TestPlayerLoader(() => new TestPlayer { AllowPause = false, AllowLeadIn = false, @@ -84,7 +74,7 @@ namespace osu.Game.Tests.Visual [Test] public void MainMenuTest() { - AddStep("Add new Main Menu", () => LoadScreen(baseScreen = new MainMenu())); + AddStep("Add new Main Menu", () => LoadScreen(new MainMenu())); } private class TestFacadeContainer : FacadeContainer @@ -105,8 +95,6 @@ namespace osu.Game.Tests.Visual { private TestFacadeContainer facadeContainer; private FacadeFlowComponent facadeFlowComponent; - private OsuLogo logo; - private readonly bool randomPositions; public TestScreen(bool randomPositions = false) @@ -114,9 +102,6 @@ namespace osu.Game.Tests.Visual this.randomPositions = randomPositions; } - private SpriteText positionText; - private SpriteText sizeAxesText; - [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Graphics/Containers/FacadeContainer.cs b/osu.Game/Graphics/Containers/FacadeContainer.cs index 0511c87166..611cc94958 100644 --- a/osu.Game/Graphics/Containers/FacadeContainer.cs +++ b/osu.Game/Graphics/Containers/FacadeContainer.cs @@ -60,8 +60,8 @@ namespace osu.Game.Graphics.Containers { if (logo.RelativePositionAxes != Axes.None) { - logo.Position = logo.Parent.ToLocalSpace(logo.Position); logo.RelativePositionAxes = Axes.None; + logo.Position = logo.Parent.ToLocalSpace(logo.Position); } if (startTime == 0) From b75ea295dbf97862711515506e1fad7332ca8120 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 11:28:43 +0900 Subject: [PATCH 227/623] Rename KeyCounterCollection -> KeyCounterDisplay Also fix not working --- osu.Game.Tests/Visual/TestCaseKeyCounter.cs | 4 +-- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- osu.Game/Rulesets/UI/RulesetInputManager.cs | 28 +++++++++++++------ osu.Game/Screens/Play/HUDOverlay.cs | 4 +-- ...nterCollection.cs => KeyCounterDisplay.cs} | 8 +++--- 5 files changed, 28 insertions(+), 18 deletions(-) rename osu.Game/Screens/Play/{KeyCounterCollection.cs => KeyCounterDisplay.cs} (95%) diff --git a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs index 52caffc29f..9360cfd911 100644 --- a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs +++ b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs @@ -20,13 +20,13 @@ namespace osu.Game.Tests.Visual { typeof(KeyCounterKeyboard), typeof(KeyCounterMouse), - typeof(KeyCounterCollection) + typeof(KeyCounterDisplay) }; public TestCaseKeyCounter() { KeyCounterKeyboard rewindTestKeyCounterKeyboard; - KeyCounterCollection kc = new KeyCounterCollection + KeyCounterDisplay kc = new KeyCounterDisplay { Origin = Anchor.Centre, Anchor = Anchor.Centre, diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 905da3c33b..88fdb9a135 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -232,7 +232,7 @@ namespace osu.Game.Rulesets.UI /// The DrawableHitObject. public abstract DrawableHitObject GetVisualRepresentation(TObject h); - public void Attach(KeyCounterCollection keyCounter) => + public void Attach(KeyCounterDisplay keyCounter) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter); /// diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index e303166774..150c53274f 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -34,11 +35,19 @@ namespace osu.Game.Rulesets.UI protected readonly KeyBindingContainer KeyBindingContainer; - protected override Container Content => KeyBindingContainer; + protected override Container Content => content; + + private readonly Container content; + + private class Poop : Container + { + } protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) { - InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique); + InternalChild = KeyBindingContainer = + (KeyBindingContainer)CreateKeyBindingContainer(ruleset, variant, unique) + .WithChild(content = new Container { RelativeSizeAxes = Axes.Both }); } [BackgroundDependencyLoader(true)] @@ -115,18 +124,19 @@ namespace osu.Game.Rulesets.UI #region Key Counter Attachment - public void Attach(KeyCounterCollection keyCounter) + public void Attach(KeyCounterDisplay keyCounter) { var receptor = new ActionReceptor(keyCounter); - Add(receptor); - keyCounter.SetReceptor(receptor); + KeyBindingContainer.Add(receptor); + + keyCounter.SetReceptor(receptor); keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings.Select(b => b.GetAction()).Distinct().Select(b => new KeyCounterAction(b))); } - public class ActionReceptor : KeyCounterCollection.Receptor, IKeyBindingHandler + public class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler { - public ActionReceptor(KeyCounterCollection target) + public ActionReceptor(KeyCounterDisplay target) : base(target) { } @@ -159,12 +169,12 @@ namespace osu.Game.Rulesets.UI } /// - /// Supports attaching a . + /// Supports attaching a . /// Keys will be populated automatically and a receptor will be injected inside. /// public interface ICanAttachKeyCounter { - void Attach(KeyCounterCollection keyCounter); + void Attach(KeyCounterDisplay keyCounter); } public class RulesetInputManagerInputState : InputState diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 285e6eab23..a7b7f96e7a 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Play { private const int duration = 100; - public readonly KeyCounterCollection KeyCounter; + public readonly KeyCounterDisplay KeyCounter; public readonly RollingCounter ComboCounter; public readonly ScoreCounter ScoreCounter; public readonly RollingCounter AccuracyCounter; @@ -201,7 +201,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20 } }; - protected virtual KeyCounterCollection CreateKeyCounter() => new KeyCounterCollection + protected virtual KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay { FadeTime = 50, Anchor = Anchor.BottomRight, diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs similarity index 95% rename from osu.Game/Screens/Play/KeyCounterCollection.cs rename to osu.Game/Screens/Play/KeyCounterDisplay.cs index 1b43737731..d5967f5899 100644 --- a/osu.Game/Screens/Play/KeyCounterCollection.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -14,14 +14,14 @@ using osuTK.Graphics; namespace osu.Game.Screens.Play { - public class KeyCounterCollection : FillFlowContainer + public class KeyCounterDisplay : FillFlowContainer { private const int duration = 100; public readonly Bindable Visible = new Bindable(true); private readonly Bindable configVisibility = new Bindable(); - public KeyCounterCollection() + public KeyCounterDisplay() { Direction = FillDirection.Horizontal; AutoSizeAxes = Axes.Both; @@ -138,9 +138,9 @@ namespace osu.Game.Screens.Play public class Receptor : Drawable { - protected readonly KeyCounterCollection Target; + protected readonly KeyCounterDisplay Target; - public Receptor(KeyCounterCollection target) + public Receptor(KeyCounterDisplay target) { RelativeSizeAxes = Axes.Both; Depth = float.MinValue; From c403dede20ccddd7186fd315acf4a0e5f1d09f49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 13:16:46 +0900 Subject: [PATCH 228/623] Add ManualInputManager to screen tests Also sanitises content init order (ctor for content; bdl for other) --- .../Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs | 3 ++- osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs | 4 +++- osu.Game/Tests/Visual/ManualInputManagerTestCase.cs | 3 +-- osu.Game/Tests/Visual/PlayerTestCase.cs | 5 +++++ osu.Game/Tests/Visual/ScreenTestCase.cs | 8 +++++--- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs index 34de61cb5b..497da33a05 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs @@ -27,7 +27,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [Cached(Type = typeof(IRoomManager))] private TestRoomManager roomManager = new TestRoomManager(); - public TestCaseLoungeRoomsContainer() + [BackgroundDependencyLoader] + private void load() { RoomsContainer container; diff --git a/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs b/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs index 9453d0a5b2..53fb60bcb6 100644 --- a/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs +++ b/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; +using osu.Framework.Allocation; using osu.Game.Screens.Tournament; using osu.Game.Screens.Tournament.Teams; @@ -11,7 +12,8 @@ namespace osu.Game.Tests.Visual.Tournament [Description("for tournament use")] public class TestCaseDrawings : ScreenTestCase { - public TestCaseDrawings() + [BackgroundDependencyLoader] + private void load() { LoadScreen(new Drawings { diff --git a/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs b/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs index 7c7c5938aa..f14ac833e4 100644 --- a/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs +++ b/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs @@ -14,8 +14,7 @@ namespace osu.Game.Tests.Visual protected ManualInputManagerTestCase() { - base.Content.Add(InputManager = new ManualInputManager()); - ReturnUserInput(); + base.Content.Add(InputManager = new ManualInputManager { UseParentInput = true }); } /// diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index 50cb839ed9..fb10244b12 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; @@ -23,7 +24,11 @@ namespace osu.Game.Tests.Visual protected PlayerTestCase(Ruleset ruleset) { this.ruleset = ruleset; + } + [BackgroundDependencyLoader] + private void load() + { Add(new Box { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Tests/Visual/ScreenTestCase.cs b/osu.Game/Tests/Visual/ScreenTestCase.cs index eec60d01c5..981f9acb63 100644 --- a/osu.Game/Tests/Visual/ScreenTestCase.cs +++ b/osu.Game/Tests/Visual/ScreenTestCase.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Screens; @@ -9,11 +10,12 @@ namespace osu.Game.Tests.Visual /// /// A test case which can be used to test a screen (that relies on OnEntering being called to execute startup instructions). /// - public abstract class ScreenTestCase : OsuTestCase + public abstract class ScreenTestCase : ManualInputManagerTestCase { - private readonly OsuScreenStack stack; + private OsuScreenStack stack; - protected ScreenTestCase() + [BackgroundDependencyLoader] + private void load() { Child = stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; } From 256a579de02228bb3e243c6c6b350c4907eabb63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 13:17:00 +0900 Subject: [PATCH 229/623] Allow player to not pause on focus loss --- osu.Game/Screens/Play/Player.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 7b1cdd21a6..f2fd7e765f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -47,6 +47,8 @@ namespace osu.Game.Screens.Play public bool AllowLeadIn { get; set; } = true; public bool AllowResults { get; set; } = true; + public bool PauseOnFocusLost { get; set; } = true; + private Bindable mouseWheelDisabled; private readonly Bindable storyboardReplacesBackground = new Bindable(); @@ -372,7 +374,7 @@ namespace osu.Game.Screens.Play base.Update(); // eagerly pause when we lose window focus (if we are locally playing). - if (!Game.IsActive.Value) + if (PauseOnFocusLost && !Game.IsActive.Value) Pause(); } From 85c63f14f2e630c5eadf1b66646bb658cdabdd43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 13:18:33 +0900 Subject: [PATCH 230/623] Add comprehensive player resume testing --- .../Visual/Gameplay/TestCasePause.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs index 1ed61c9fe1..a52e84ed62 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs @@ -1,13 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Cursor; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; +using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { @@ -15,14 +21,52 @@ namespace osu.Game.Tests.Visual.Gameplay { protected new PausePlayer Player => (PausePlayer)base.Player; + private readonly Container content; + + protected override Container Content => content; + public TestCasePause() : base(new OsuRuleset()) { + base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }); } [Test] public void TestPauseResume() { + AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); + pauseAndConfirm(); + resumeAndConfirm(); + } + + [Test] + public void TestResumeWithResumeOverlay() + { + AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1); + + pauseAndConfirm(); + resume(); + + confirmClockRunning(false); + confirmPauseOverlayShown(false); + + AddStep("click to resume", () => + { + InputManager.PressButton(MouseButton.Left); + InputManager.ReleaseButton(MouseButton.Left); + }); + + confirmClockRunning(true); + } + + [Test] + public void TestResumeWithResumeOverlaySkipped() + { + AddStep("move cursor to button", () => + InputManager.MoveMouseTo(Player.HUDOverlay.HoldToQuit.Children.OfType().First().ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1); + pauseAndConfirm(); resumeAndConfirm(); } @@ -30,6 +74,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestPauseTooSoon() { + AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); + pauseAndConfirm(); resumeAndConfirm(); @@ -144,9 +190,16 @@ namespace osu.Game.Tests.Visual.Gameplay public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + public new HUDOverlay HUDOverlay => base.HUDOverlay; + public bool FailOverlayVisible => FailOverlay.State == Visibility.Visible; public bool PauseOverlayVisible => PauseOverlay.State == Visibility.Visible; + + public PausePlayer() + { + PauseOnFocusLost = false; + } } } } From 868367511e5eb181e48499449d6933f87ec4964c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 13:16:46 +0900 Subject: [PATCH 231/623] Add ManualInputManager to screen tests Also sanitises content init order (ctor for content; bdl for other) --- .../Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs | 3 ++- osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs | 4 +++- osu.Game/Tests/Visual/ManualInputManagerTestCase.cs | 3 +-- osu.Game/Tests/Visual/PlayerTestCase.cs | 5 +++++ osu.Game/Tests/Visual/ScreenTestCase.cs | 8 +++++--- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs index 34de61cb5b..497da33a05 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestCaseLoungeRoomsContainer.cs @@ -27,7 +27,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [Cached(Type = typeof(IRoomManager))] private TestRoomManager roomManager = new TestRoomManager(); - public TestCaseLoungeRoomsContainer() + [BackgroundDependencyLoader] + private void load() { RoomsContainer container; diff --git a/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs b/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs index 9453d0a5b2..53fb60bcb6 100644 --- a/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs +++ b/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; +using osu.Framework.Allocation; using osu.Game.Screens.Tournament; using osu.Game.Screens.Tournament.Teams; @@ -11,7 +12,8 @@ namespace osu.Game.Tests.Visual.Tournament [Description("for tournament use")] public class TestCaseDrawings : ScreenTestCase { - public TestCaseDrawings() + [BackgroundDependencyLoader] + private void load() { LoadScreen(new Drawings { diff --git a/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs b/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs index 7c7c5938aa..f14ac833e4 100644 --- a/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs +++ b/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs @@ -14,8 +14,7 @@ namespace osu.Game.Tests.Visual protected ManualInputManagerTestCase() { - base.Content.Add(InputManager = new ManualInputManager()); - ReturnUserInput(); + base.Content.Add(InputManager = new ManualInputManager { UseParentInput = true }); } /// diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index 50cb839ed9..fb10244b12 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; @@ -23,7 +24,11 @@ namespace osu.Game.Tests.Visual protected PlayerTestCase(Ruleset ruleset) { this.ruleset = ruleset; + } + [BackgroundDependencyLoader] + private void load() + { Add(new Box { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Tests/Visual/ScreenTestCase.cs b/osu.Game/Tests/Visual/ScreenTestCase.cs index eec60d01c5..981f9acb63 100644 --- a/osu.Game/Tests/Visual/ScreenTestCase.cs +++ b/osu.Game/Tests/Visual/ScreenTestCase.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Screens; @@ -9,11 +10,12 @@ namespace osu.Game.Tests.Visual /// /// A test case which can be used to test a screen (that relies on OnEntering being called to execute startup instructions). /// - public abstract class ScreenTestCase : OsuTestCase + public abstract class ScreenTestCase : ManualInputManagerTestCase { - private readonly OsuScreenStack stack; + private OsuScreenStack stack; - protected ScreenTestCase() + [BackgroundDependencyLoader] + private void load() { Child = stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; } From 5d166a011deff56ed2f81150d48b85e93a212256 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 13:28:59 +0900 Subject: [PATCH 232/623] Update framework --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 71324ea0f0..eb5d0fd8ee 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 02099a59bb..c3792a48a1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From e512d88899e02f74941be0e91eb2cd6e49becaee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 21:39:15 +0900 Subject: [PATCH 233/623] Apply required refactor --- osu.Game/Screens/Multi/MultiplayerSubScreen.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs index ad72072981..65e501b114 100644 --- a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs +++ b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs @@ -14,8 +14,6 @@ namespace osu.Game.Screens.Multi { public override bool DisallowExternalBeatmapRulesetChanges => false; - public override bool RemoveWhenNotAlive => false; - public virtual string ShortTitle => Title; [Resolved(CanBeNull = true)] From ed3746e166d66f2231dfc6565f1392d0668f9b64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 13:31:49 +0900 Subject: [PATCH 234/623] Make PlayfieldAdjustmentContainer universal --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 34 +-- ...s => CatchPlayfieldAdjustmentContainer.cs} | 10 +- .../UI/DrawableCatchRuleset.cs | 2 + .../UI/DrawableManiaRuleset.cs | 9 +- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 2 - .../UI/ManiaPlayfieldAdjustmentContainer.cs | 20 ++ .../Edit/OsuHitObjectComposer.cs | 2 +- .../UI/DrawableOsuRuleset.cs | 4 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 45 ++- ....cs => OsuPlayfieldAdjustmentContainer.cs} | 10 +- .../UI/DrawableTaikoRuleset.cs | 2 + osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 256 +++++++++--------- ...s => TaikoPlayfieldAdjustmentContainer.cs} | 11 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 14 +- .../UI/PlayfieldAdjustmentContainer.cs | 19 ++ 15 files changed, 235 insertions(+), 205 deletions(-) rename osu.Game.Rulesets.Catch/UI/{PlayfieldAdjustmentContainer.cs => CatchPlayfieldAdjustmentContainer.cs} (78%) create mode 100644 osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs rename osu.Game.Rulesets.Osu/UI/{PlayfieldAdjustmentContainer.cs => OsuPlayfieldAdjustmentContainer.cs} (82%) rename osu.Game.Rulesets.Taiko/UI/{PlayfieldAdjustmentContainer.cs => TaikoPlayfieldAdjustmentContainer.cs} (68%) create mode 100644 osu.Game/Rulesets/UI/PlayfieldAdjustmentContainer.cs diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 4dae95b53c..43d0dc026d 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; -using osuTK; namespace osu.Game.Rulesets.Catch.UI { @@ -24,29 +23,20 @@ namespace osu.Game.Rulesets.Catch.UI { Container explodingFruitContainer; - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; - - Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate - - InternalChild = new PlayfieldAdjustmentContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + explodingFruitContainer = new Container { - explodingFruitContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }, - CatcherArea = new CatcherArea(difficulty) - { - GetVisualRepresentation = getVisualRepresentation, - ExplodingFruitTarget = explodingFruitContainer, - Anchor = Anchor.BottomLeft, - Origin = Anchor.TopLeft, - }, - HitObjectContainer - } + RelativeSizeAxes = Axes.Both, + }, + CatcherArea = new CatcherArea(difficulty) + { + GetVisualRepresentation = getVisualRepresentation, + ExplodingFruitTarget = explodingFruitContainer, + Anchor = Anchor.BottomLeft, + Origin = Anchor.TopLeft, + }, + HitObjectContainer }; } diff --git a/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs similarity index 78% rename from osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs rename to osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs index 76daee2bbf..b8d3dc9017 100644 --- a/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs @@ -3,17 +3,23 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Catch.UI { - public class PlayfieldAdjustmentContainer : Container + public class CatchPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer { protected override Container Content => content; private readonly Container content; - public PlayfieldAdjustmentContainer() + public CatchPlayfieldAdjustmentContainer() { + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + + Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate + InternalChild = new Container { Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 406dc10eea..6981c98ec7 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -36,6 +36,8 @@ namespace osu.Game.Rulesets.Catch.UI protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, GetVisualRepresentation); + protected override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer(); + protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); public override DrawableHitObject GetVisualRepresentation(CatchHitObject h) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index a019401d5b..0dc081f3da 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.MathUtils; using osu.Game.Beatmaps; @@ -89,11 +88,9 @@ namespace osu.Game.Rulesets.Mania.UI /// The column which intersects with . public Column GetColumnByPosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition); - protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; + protected override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer(); + + protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages); public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 81888d2773..cbabfcc8b4 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -28,8 +28,6 @@ namespace osu.Game.Rulesets.Mania.UI if (stageDefinitions.Count <= 0) throw new ArgumentException("Can't have zero or fewer stages."); - Size = new Vector2(1, 0.8f); - GridContainer playfieldGrid; AddInternal(playfieldGrid = new GridContainer { diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs new file mode 100644 index 0000000000..d893a3fdde --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Mania.UI +{ + public class ManiaPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer + { + public ManiaPlayfieldAdjustmentContainer() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Size = new Vector2(1, 0.8f); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index dd3925e04f..952fe0b708 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit public override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); - protected override Container CreateLayerContainer() => new PlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both }; + protected override Container CreateLayerContainer() => new OsuPlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both }; public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) { diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index b632e0fb05..ad8341af09 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Linq; @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.UI protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); + protected override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer(); + public override DrawableHitObject GetVisualRepresentation(OsuHitObject h) { switch (h) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 51733c3c01..5e532d9b04 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -24,41 +24,28 @@ namespace osu.Game.Rulesets.Osu.UI public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); - private readonly PlayfieldAdjustmentContainer adjustmentContainer; - - protected override Container CursorTargetContainer => adjustmentContainer; - protected override CursorContainer CreateCursor() => new GameplayCursorContainer(); public OsuPlayfield() { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - Size = new Vector2(0.75f); - - InternalChild = adjustmentContainer = new PlayfieldAdjustmentContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + connectionLayer = new FollowPointRenderer { - connectionLayer = new FollowPointRenderer - { - RelativeSizeAxes = Axes.Both, - Depth = 2, - }, - judgementLayer = new JudgementContainer - { - RelativeSizeAxes = Axes.Both, - Depth = 1, - }, - HitObjectContainer, - approachCircles = new ApproachCircleProxyContainer - { - RelativeSizeAxes = Axes.Both, - Depth = -1, - }, - } + RelativeSizeAxes = Axes.Both, + Depth = 2, + }, + judgementLayer = new JudgementContainer + { + RelativeSizeAxes = Axes.Both, + Depth = 1, + }, + HitObjectContainer, + approachCircles = new ApproachCircleProxyContainer + { + RelativeSizeAxes = Axes.Both, + Depth = -1, + }, }; } diff --git a/osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs similarity index 82% rename from osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs rename to osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs index c383c47491..e28ff5f460 100644 --- a/osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs @@ -3,17 +3,23 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.UI { - public class PlayfieldAdjustmentContainer : Container + public class OsuPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer { protected override Container Content => content; private readonly Container content; - public PlayfieldAdjustmentContainer() + public OsuPlayfieldAdjustmentContainer() { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Size = new Vector2(0.75f); + InternalChild = new Container { Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 899b91863e..f595432082 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -81,6 +81,8 @@ namespace osu.Game.Rulesets.Taiko.UI public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); + protected override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer(); + protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 35b941b52b..dbff5270d2 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -55,143 +55,137 @@ namespace osu.Game.Rulesets.Taiko.UI public TaikoPlayfield(ControlPointInfo controlPoints) { - InternalChild = new PlayfieldAdjustmentContainer + InternalChildren = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + backgroundContainer = new Container { - backgroundContainer = new Container + Name = "Transparent playfield background", + RelativeSizeAxes = Axes.Both, + Masking = true, + EdgeEffect = new EdgeEffectParameters { - Name = "Transparent playfield background", - RelativeSizeAxes = Axes.Both, - Masking = true, - EdgeEffect = new EdgeEffectParameters + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.2f), + Radius = 5, + }, + Children = new Drawable[] + { + background = new Box { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.2f), - Radius = 5, + RelativeSizeAxes = Axes.Both, + Alpha = 0.6f }, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.6f - }, - } - }, - new Container - { - Name = "Right area", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = left_area_size }, - Children = new Drawable[] - { - new Container - { - Name = "Masked elements before hit objects", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Masking = true, - Children = new Drawable[] - { - hitExplosionContainer = new Container - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Blending = BlendingMode.Additive, - }, - HitTarget = new HitTarget - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit - } - } - }, - barlineContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET } - }, - new Container - { - Name = "Hit objects", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Masking = true, - Child = HitObjectContainer - }, - kiaiExplosionContainer = new Container - { - Name = "Kiai hit explosions", - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Blending = BlendingMode.Additive - }, - judgementContainer = new JudgementContainer - { - Name = "Judgements", - RelativeSizeAxes = Axes.Y, - Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Blending = BlendingMode.Additive - }, - } - }, - overlayBackgroundContainer = new Container - { - Name = "Left overlay", - RelativeSizeAxes = Axes.Y, - Size = new Vector2(left_area_size, 1), - Children = new Drawable[] - { - overlayBackground = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new InputDrum(controlPoints) - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Scale = new Vector2(0.9f), - Margin = new MarginPadding { Right = 20 } - }, - new Box - { - Anchor = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = 10, - Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), - }, - } - }, - new Container - { - Name = "Border", - RelativeSizeAxes = Axes.Both, - Masking = true, - MaskingSmoothness = 0, - BorderThickness = 2, - AlwaysPresent = true, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - } - }, - topLevelHitContainer = new Container - { - Name = "Top level hit objects", - RelativeSizeAxes = Axes.Both, } + }, + new Container + { + Name = "Right area", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = left_area_size }, + Children = new Drawable[] + { + new Container + { + Name = "Masked elements before hit objects", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Masking = true, + Children = new Drawable[] + { + hitExplosionContainer = new Container + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Blending = BlendingMode.Additive, + }, + HitTarget = new HitTarget + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit + } + } + }, + barlineContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET } + }, + new Container + { + Name = "Hit objects", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Masking = true, + Child = HitObjectContainer + }, + kiaiExplosionContainer = new Container + { + Name = "Kiai hit explosions", + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Blending = BlendingMode.Additive + }, + judgementContainer = new JudgementContainer + { + Name = "Judgements", + RelativeSizeAxes = Axes.Y, + Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Blending = BlendingMode.Additive + }, + } + }, + overlayBackgroundContainer = new Container + { + Name = "Left overlay", + RelativeSizeAxes = Axes.Y, + Size = new Vector2(left_area_size, 1), + Children = new Drawable[] + { + overlayBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new InputDrum(controlPoints) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Scale = new Vector2(0.9f), + Margin = new MarginPadding { Right = 20 } + }, + new Box + { + Anchor = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 10, + Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), + }, + } + }, + new Container + { + Name = "Border", + RelativeSizeAxes = Axes.Both, + Masking = true, + MaskingSmoothness = 0, + BorderThickness = 2, + AlwaysPresent = true, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + }, + topLevelHitContainer = new Container + { + Name = "Top level hit objects", + RelativeSizeAxes = Axes.Both, } }; } diff --git a/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs similarity index 68% rename from osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs rename to osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index 0f0ad59fd3..84464b199e 100644 --- a/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs @@ -1,16 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Taiko.UI { - public class PlayfieldAdjustmentContainer : Container + public class TaikoPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer { private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768; private const float default_aspect = 16f / 9f; + public TaikoPlayfieldAdjustmentContainer() + { + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + } + protected override void Update() { base.Update(); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 31c0afd743..bd843bbfcd 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; @@ -133,19 +133,19 @@ namespace osu.Game.Rulesets.UI return dependencies; } + protected virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer(); + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - KeyBindingInputManager.AddRange(new Drawable[] - { - Playfield - }); - InternalChildren = new Drawable[] { frameStabilityContainer = new FrameStabilityContainer { - Child = KeyBindingInputManager, + Child = KeyBindingInputManager + .WithChild(CreatePlayfieldAdjustmentContainer() + .WithChild(Playfield) + ) }, Overlays = new Container { RelativeSizeAxes = Axes.Both } }; diff --git a/osu.Game/Rulesets/UI/PlayfieldAdjustmentContainer.cs b/osu.Game/Rulesets/UI/PlayfieldAdjustmentContainer.cs new file mode 100644 index 0000000000..fff4a450e5 --- /dev/null +++ b/osu.Game/Rulesets/UI/PlayfieldAdjustmentContainer.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . 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; + +namespace osu.Game.Rulesets.UI +{ + /// + /// A container which handles sizing of the and any other components that need to match their size. + /// + public class PlayfieldAdjustmentContainer : Container + { + public PlayfieldAdjustmentContainer() + { + RelativeSizeAxes = Axes.Both; + } + } +} From b4d785c76cea8b297e98ff7a0685241e38a2fac3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Mar 2019 20:25:47 +0900 Subject: [PATCH 235/623] Don't update gameplay loop while paused --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 5 ++++- osu.Game/Screens/Play/GameplayClock.cs | 3 +++ osu.Game/Screens/Play/GameplayClockContainer.cs | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 161e7aecb4..deec2b8eac 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -36,7 +36,10 @@ namespace osu.Game.Rulesets.UI private void load(GameplayClock clock) { if (clock != null) + { parentGameplayClock = clock; + gameplayClock.IsPaused.BindTo(clock.IsPaused); + } } protected override void LoadComplete() @@ -68,7 +71,7 @@ namespace osu.Game.Rulesets.UI public override bool UpdateSubTree() { requireMoreUpdateLoops = true; - validState = true; + validState = !gameplayClock.IsPaused.Value; int loops = 0; diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 0400bfbc27..3efcfa0f65 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Framework.Timing; namespace osu.Game.Screens.Play @@ -17,6 +18,8 @@ namespace osu.Game.Screens.Play { private readonly IFrameBasedClock underlyingClock; + public readonly BindableBool IsPaused = new BindableBool(); + public GameplayClock(IFrameBasedClock underlyingClock) { this.underlyingClock = underlyingClock; diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index deac5e02bf..546364b26d 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -79,6 +79,8 @@ namespace osu.Game.Screens.Play // the clock to be exposed via DI to children. GameplayClock = new GameplayClock(offsetClock); + + GameplayClock.IsPaused.BindTo(IsPaused); } [BackgroundDependencyLoader] From fbc97edc5567a6b55f4a71286563f3b0d76b95b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 13:39:19 +0900 Subject: [PATCH 236/623] Add base cursor class to retrieve true visibility state --- .../TestCaseGameplayCursor.cs | 3 ++- .../Edit/DrawableOsuEditRuleset.cs | 3 +-- .../UI/Cursor/GameplayCursorContainer.cs | 6 ++--- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 9 ++----- osu.Game/Rulesets/UI/DrawableRuleset.cs | 6 +++-- .../Rulesets/UI/GameplayCursorContainer.cs | 25 +++++++++++++++++++ osu.Game/Rulesets/UI/Playfield.cs | 5 ++-- 7 files changed, 39 insertions(+), 18 deletions(-) create mode 100644 osu.Game/Rulesets/UI/GameplayCursorContainer.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs index 5c1e775c01..1e2a936002 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Rulesets.UI; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests @@ -27,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Tests [BackgroundDependencyLoader] private void load() { - Add(cursorContainer = new GameplayCursorContainer { RelativeSizeAxes = Axes.Both }); + Add(cursorContainer = new OsuCursorContainer { RelativeSizeAxes = Axes.Both }); } } } diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 1a6e78d918..3ae554a5d7 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Cursor; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; @@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Edit private class OsuPlayfieldNoCursor : OsuPlayfield { - protected override CursorContainer CreateCursor() => null; + protected override GameplayCursorContainer CreateCursor() => null; } } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs index 8c6723f5be..ba6b27f743 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; @@ -14,10 +13,11 @@ using osu.Game.Configuration; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.UI.Cursor { - public class GameplayCursorContainer : CursorContainer, IKeyBindingHandler + public class OsuCursorContainer : GameplayCursorContainer, IKeyBindingHandler { protected override Drawable CreateCursor() => new OsuCursor(); @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly Container fadeContainer; - public GameplayCursorContainer() + public OsuCursorContainer() { InternalChild = fadeContainer = new Container { diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 51733c3c01..f68aee7e0c 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Rulesets.UI; using System.Linq; -using osu.Framework.Graphics.Cursor; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.UI.Cursor; @@ -24,11 +23,7 @@ namespace osu.Game.Rulesets.Osu.UI public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); - private readonly PlayfieldAdjustmentContainer adjustmentContainer; - - protected override Container CursorTargetContainer => adjustmentContainer; - - protected override CursorContainer CreateCursor() => new GameplayCursorContainer(); + protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); public OsuPlayfield() { @@ -37,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.UI Size = new Vector2(0.75f); - InternalChild = adjustmentContainer = new PlayfieldAdjustmentContainer + InternalChild = new PlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 31c0afd743..d5b150d2fa 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -263,7 +263,9 @@ namespace osu.Game.Rulesets.UI protected override bool OnHover(HoverEvent e) => true; // required for IProvideCursor - public override CursorContainer Cursor => Playfield.Cursor; + CursorContainer IProvideCursor.Cursor => Playfield.Cursor; + + public override GameplayCursorContainer Cursor => Playfield.Cursor; public bool ProvidingUserCursor => Playfield.Cursor != null && !HasReplayLoaded.Value; @@ -333,7 +335,7 @@ namespace osu.Game.Rulesets.UI /// /// The cursor being displayed by the . May be null if no cursor is provided. /// - public abstract CursorContainer Cursor { get; } + public abstract GameplayCursorContainer Cursor { get; } /// /// Sets a replay to be used, overriding local input. diff --git a/osu.Game/Rulesets/UI/GameplayCursorContainer.cs b/osu.Game/Rulesets/UI/GameplayCursorContainer.cs new file mode 100644 index 0000000000..de73c08809 --- /dev/null +++ b/osu.Game/Rulesets/UI/GameplayCursorContainer.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; + +namespace osu.Game.Rulesets.UI +{ + public class GameplayCursorContainer : CursorContainer + { + /// + /// Because Show/Hide are executed by a parent, is updated immediately even if the cursor + /// is in a non-updating state (via limitations). + /// + /// This holds the true visibility value. + /// + public Visibility LastFrameState; + + protected override void Update() + { + base.Update(); + LastFrameState = State; + } + } +} diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 78d14a27e3..1c4d765da6 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -10,7 +10,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osuTK; @@ -90,14 +89,14 @@ namespace osu.Game.Rulesets.UI /// /// The cursor currently being used by this . May be null if no cursor is provided. /// - public CursorContainer Cursor { get; private set; } + public GameplayCursorContainer Cursor { get; private set; } /// /// Provide an optional cursor which is to be used for gameplay. /// If providing a cursor, must also point to a valid target container. /// /// The cursor, or null if a cursor is not rqeuired. - protected virtual CursorContainer CreateCursor() => null; + protected virtual GameplayCursorContainer CreateCursor() => null; /// /// The target container to add the cursor after it is created. From 27cb4ce0d1a761b1e9df48f92cdd74802c46d061 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 13:48:35 +0900 Subject: [PATCH 237/623] Remove poop --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 150c53274f..656170f339 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -39,10 +39,6 @@ namespace osu.Game.Rulesets.UI private readonly Container content; - private class Poop : Container - { - } - protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) { InternalChild = KeyBindingContainer = From 2951d3e0e43ebeb260b3778d1a0ca14005814bb0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 13:50:30 +0900 Subject: [PATCH 238/623] Apply CI fixes --- .../Cursor/{GameplayCursorContainer.cs => OsuCursorContainer.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Rulesets.Osu/UI/Cursor/{GameplayCursorContainer.cs => OsuCursorContainer.cs} (100%) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs similarity index 100% rename from osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs rename to osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs From 8658de5108c32d3f1de317d3f88cf64aa9fa424b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 11:28:43 +0900 Subject: [PATCH 239/623] Rename KeyCounterCollection -> KeyCounterDisplay Also fix not working --- .../Visual/Gameplay/TestCaseKeyCounter.cs | 4 ++-- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- osu.Game/Rulesets/UI/RulesetInputManager.cs | 23 +++++++++++-------- osu.Game/Screens/Play/HUDOverlay.cs | 4 ++-- ...nterCollection.cs => KeyCounterDisplay.cs} | 8 +++---- 5 files changed, 23 insertions(+), 18 deletions(-) rename osu.Game/Screens/Play/{KeyCounterCollection.cs => KeyCounterDisplay.cs} (95%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseKeyCounter.cs index f616ffe4ed..4b55879224 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseKeyCounter.cs @@ -20,13 +20,13 @@ namespace osu.Game.Tests.Visual.Gameplay { typeof(KeyCounterKeyboard), typeof(KeyCounterMouse), - typeof(KeyCounterCollection) + typeof(KeyCounterDisplay) }; public TestCaseKeyCounter() { KeyCounterKeyboard rewindTestKeyCounterKeyboard; - KeyCounterCollection kc = new KeyCounterCollection + KeyCounterDisplay kc = new KeyCounterDisplay { Origin = Anchor.Centre, Anchor = Anchor.Centre, diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 31c0afd743..e3bdafb3b9 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -211,7 +211,7 @@ namespace osu.Game.Rulesets.UI /// The DrawableHitObject. public abstract DrawableHitObject GetVisualRepresentation(TObject h); - public void Attach(KeyCounterCollection keyCounter) => + public void Attach(KeyCounterDisplay keyCounter) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter); /// diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index e303166774..3ce8f92458 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -34,11 +34,15 @@ namespace osu.Game.Rulesets.UI protected readonly KeyBindingContainer KeyBindingContainer; - protected override Container Content => KeyBindingContainer; + protected override Container Content => content; + + private readonly Container content; protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) { - InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique); + InternalChild = KeyBindingContainer = + (KeyBindingContainer)CreateKeyBindingContainer(ruleset, variant, unique) + .WithChild(content = new Container { RelativeSizeAxes = Axes.Both }); } [BackgroundDependencyLoader(true)] @@ -115,18 +119,19 @@ namespace osu.Game.Rulesets.UI #region Key Counter Attachment - public void Attach(KeyCounterCollection keyCounter) + public void Attach(KeyCounterDisplay keyCounter) { var receptor = new ActionReceptor(keyCounter); - Add(receptor); - keyCounter.SetReceptor(receptor); + KeyBindingContainer.Add(receptor); + + keyCounter.SetReceptor(receptor); keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings.Select(b => b.GetAction()).Distinct().Select(b => new KeyCounterAction(b))); } - public class ActionReceptor : KeyCounterCollection.Receptor, IKeyBindingHandler + public class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler { - public ActionReceptor(KeyCounterCollection target) + public ActionReceptor(KeyCounterDisplay target) : base(target) { } @@ -159,12 +164,12 @@ namespace osu.Game.Rulesets.UI } /// - /// Supports attaching a . + /// Supports attaching a . /// Keys will be populated automatically and a receptor will be injected inside. /// public interface ICanAttachKeyCounter { - void Attach(KeyCounterCollection keyCounter); + void Attach(KeyCounterDisplay keyCounter); } public class RulesetInputManagerInputState : InputState diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 285e6eab23..a7b7f96e7a 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Play { private const int duration = 100; - public readonly KeyCounterCollection KeyCounter; + public readonly KeyCounterDisplay KeyCounter; public readonly RollingCounter ComboCounter; public readonly ScoreCounter ScoreCounter; public readonly RollingCounter AccuracyCounter; @@ -201,7 +201,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20 } }; - protected virtual KeyCounterCollection CreateKeyCounter() => new KeyCounterCollection + protected virtual KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay { FadeTime = 50, Anchor = Anchor.BottomRight, diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs similarity index 95% rename from osu.Game/Screens/Play/KeyCounterCollection.cs rename to osu.Game/Screens/Play/KeyCounterDisplay.cs index 1b43737731..d5967f5899 100644 --- a/osu.Game/Screens/Play/KeyCounterCollection.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -14,14 +14,14 @@ using osuTK.Graphics; namespace osu.Game.Screens.Play { - public class KeyCounterCollection : FillFlowContainer + public class KeyCounterDisplay : FillFlowContainer { private const int duration = 100; public readonly Bindable Visible = new Bindable(true); private readonly Bindable configVisibility = new Bindable(); - public KeyCounterCollection() + public KeyCounterDisplay() { Direction = FillDirection.Horizontal; AutoSizeAxes = Axes.Both; @@ -138,9 +138,9 @@ namespace osu.Game.Screens.Play public class Receptor : Drawable { - protected readonly KeyCounterCollection Target; + protected readonly KeyCounterDisplay Target; - public Receptor(KeyCounterCollection target) + public Receptor(KeyCounterDisplay target) { RelativeSizeAxes = Axes.Both; Depth = float.MinValue; From fb302e7ad8e464ac6dd6950c0972819e213147b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 13:58:07 +0900 Subject: [PATCH 240/623] Remove using --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 656170f339..3ce8f92458 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; From 83863d35c3713f29b271bcf571c10789c4daf3b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 16:34:26 +0900 Subject: [PATCH 241/623] Remove AllowLeadIn flag --- osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs | 1 - osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs | 1 - osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs | 2 -- osu.Game/Screens/Play/GameplayClockContainer.cs | 6 ++---- osu.Game/Screens/Play/Player.cs | 3 +-- osu.Game/Tests/Visual/AllPlayersTestCase.cs | 1 - osu.Game/Tests/Visual/PlayerTestCase.cs | 1 - 7 files changed, 3 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs index 2f33982d41..9614ece40e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs @@ -333,7 +333,6 @@ namespace osu.Game.Rulesets.Osu.Tests var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }) { AllowPause = false, - AllowLeadIn = false, AllowResults = false }; diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs index 2b0254f232..efb6dac158 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs @@ -18,7 +18,6 @@ namespace osu.Game.Tests.Visual.Gameplay return new ScoreAccessiblePlayer { AllowPause = false, - AllowLeadIn = false, AllowResults = false, }; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs index be2a21d23d..f571e85fe8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs @@ -29,7 +29,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player { AllowPause = false, - AllowLeadIn = false, AllowResults = false, }))); @@ -50,7 +49,6 @@ namespace osu.Game.Tests.Visual.Gameplay stack.Push(loader = new PlayerLoader(() => slow = new SlowLoadPlayer { AllowPause = false, - AllowLeadIn = false, AllowResults = false, })); diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index deac5e02bf..6ceef1dd18 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Play private readonly FramedOffsetClock offsetClock; - public GameplayClockContainer(WorkingBeatmap beatmap, bool allowLeadIn, double gameplayStartTime) + public GameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime) { this.beatmap = beatmap; @@ -64,9 +64,7 @@ namespace osu.Game.Screens.Play adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - adjustableClock.Seek(allowLeadIn - ? Math.Min(0, gameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn) - : gameplayStartTime); + adjustableClock.Seek(Math.Min(0, gameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn)); adjustableClock.ProcessFrame(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 7b1cdd21a6..3ef8525b65 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -44,7 +44,6 @@ namespace osu.Game.Screens.Play public bool HasFailed { get; private set; } public bool AllowPause { get; set; } = true; - public bool AllowLeadIn { get; set; } = true; public bool AllowResults { get; set; } = true; private Bindable mouseWheelDisabled; @@ -90,7 +89,7 @@ namespace osu.Game.Screens.Play if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - InternalChild = GameplayClockContainer = new GameplayClockContainer(working, AllowLeadIn, DrawableRuleset.GameplayStartTime); + InternalChild = GameplayClockContainer = new GameplayClockContainer(working, DrawableRuleset.GameplayStartTime); GameplayClockContainer.Children = new[] { diff --git a/osu.Game/Tests/Visual/AllPlayersTestCase.cs b/osu.Game/Tests/Visual/AllPlayersTestCase.cs index 03bd7b218a..25c397857d 100644 --- a/osu.Game/Tests/Visual/AllPlayersTestCase.cs +++ b/osu.Game/Tests/Visual/AllPlayersTestCase.cs @@ -83,7 +83,6 @@ namespace osu.Game.Tests.Visual protected virtual Player CreatePlayer(Ruleset ruleset) => new Player { AllowPause = false, - AllowLeadIn = false, AllowResults = false, }; } diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index 50cb839ed9..3be887910c 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -59,7 +59,6 @@ namespace osu.Game.Tests.Visual protected virtual Player CreatePlayer(Ruleset ruleset) => new Player { AllowPause = false, - AllowLeadIn = false, AllowResults = false, }; } From 83076e32c7f79d1e8f4d6337cb881a7d208f5aeb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 16:17:37 +0900 Subject: [PATCH 242/623] Fix TestWorkingBeatmap not running for times below zero --- osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index 90b5178169..78f9103a74 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osuTK; namespace osu.Game.Tests.Beatmaps { @@ -68,7 +67,7 @@ namespace osu.Game.Tests.Beatmaps public override bool Seek(double seek) { - offset = MathHelper.Clamp(seek, 0, Length); + offset = Math.Min(seek, Length); lastReferenceTime = null; return true; } From 92184adef53d0852d038d1666816902cc6b6997d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 16:16:28 +0900 Subject: [PATCH 243/623] Add stable sorting of storyboard elements --- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 5 +++++ .../Drawables/DrawableStoryboardSample.cs | 12 ++++++------ osu.Game/Storyboards/IStoryboardElement.cs | 2 ++ osu.Game/Storyboards/StoryboardLayer.cs | 5 ++--- osu.Game/Storyboards/StoryboardSample.cs | 5 +++-- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 9584b10ef5..757125c5b4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics; @@ -38,6 +39,10 @@ namespace osu.Game.Beatmaps.Formats { this.storyboard = storyboard; base.ParseStreamInto(stream, storyboard); + + // OrderBy is used to guarantee that the parsing order of hitobjects with equal start times is maintained (stably-sorted) + foreach (StoryboardLayer layer in storyboard.Layers) + layer.Elements = layer.Elements.OrderBy(h => h.StartTime).ToList(); } protected override void ParseLine(Storyboard storyboard, Section section, string line) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 9fa481b8b6..ffd238d4e1 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -25,7 +25,7 @@ namespace osu.Game.Storyboards.Drawables public DrawableStoryboardSample(StoryboardSample sample) { this.sample = sample; - LifetimeStart = sample.Time; + LifetimeStart = sample.StartTime; } [BackgroundDependencyLoader] @@ -43,27 +43,27 @@ namespace osu.Game.Storyboards.Drawables base.Update(); // TODO: this logic will need to be consolidated with other game samples like hit sounds. - if (Time.Current < sample.Time) + if (Time.Current < sample.StartTime) { // We've rewound before the start time of the sample channel?.Stop(); // In the case that the user fast-forwards to a point far beyond the start time of the sample, // we want to be able to fall into the if-conditional below (therefore we must not have a life time end) - LifetimeStart = sample.Time; + LifetimeStart = sample.StartTime; LifetimeEnd = double.MaxValue; } - else if (Time.Current - Time.Elapsed < sample.Time) + else if (Time.Current - Time.Elapsed < sample.StartTime) { // We've passed the start time of the sample. We only play the sample if we're within an allowable range // from the sample's start, to reduce layering if we've been fast-forwarded far into the future - if (Time.Current - sample.Time < allowable_late_start) + if (Time.Current - sample.StartTime < allowable_late_start) channel?.Play(); // In the case that the user rewinds to a point far behind the start time of the sample, // we want to be able to fall into the if-conditional above (therefore we must not have a life time start) LifetimeStart = double.MinValue; - LifetimeEnd = sample.Time; + LifetimeEnd = sample.StartTime; } } } diff --git a/osu.Game/Storyboards/IStoryboardElement.cs b/osu.Game/Storyboards/IStoryboardElement.cs index 454db2afc2..c4c150a8a4 100644 --- a/osu.Game/Storyboards/IStoryboardElement.cs +++ b/osu.Game/Storyboards/IStoryboardElement.cs @@ -10,6 +10,8 @@ namespace osu.Game.Storyboards string Path { get; } bool IsDrawable { get; } + double StartTime { get; } + Drawable CreateDrawable(); } } diff --git a/osu.Game/Storyboards/StoryboardLayer.cs b/osu.Game/Storyboards/StoryboardLayer.cs index daf03b00b4..d15f771534 100644 --- a/osu.Game/Storyboards/StoryboardLayer.cs +++ b/osu.Game/Storyboards/StoryboardLayer.cs @@ -13,8 +13,7 @@ namespace osu.Game.Storyboards public bool EnabledWhenPassing = true; public bool EnabledWhenFailing = true; - private readonly List elements = new List(); - public IEnumerable Elements => elements; + public List Elements = new List(); public StoryboardLayer(string name, int depth) { @@ -24,7 +23,7 @@ namespace osu.Game.Storyboards public void Add(IStoryboardElement element) { - elements.Add(element); + Elements.Add(element); } public DrawableStoryboardLayer CreateDrawable() diff --git a/osu.Game/Storyboards/StoryboardSample.cs b/osu.Game/Storyboards/StoryboardSample.cs index 1bdf774e74..24231cdca6 100644 --- a/osu.Game/Storyboards/StoryboardSample.cs +++ b/osu.Game/Storyboards/StoryboardSample.cs @@ -11,13 +11,14 @@ namespace osu.Game.Storyboards public string Path { get; set; } public bool IsDrawable => true; - public double Time; + public double StartTime { get; } + public float Volume; public StoryboardSample(string path, double time, float volume) { Path = path; - Time = time; + StartTime = time; Volume = volume; } From 4c77899738c8fc03a6c0745336c6e68a68ddc28c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 16:14:20 +0900 Subject: [PATCH 244/623] Add storyboard ordering test --- .../Formats/LegacyStoryboardDecoderTest.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 136d1de930..2288d04493 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -29,28 +29,28 @@ namespace osu.Game.Tests.Beatmaps.Formats StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3); Assert.IsNotNull(background); - Assert.AreEqual(16, background.Elements.Count()); + Assert.AreEqual(16, background.Elements.Count); Assert.IsTrue(background.EnabledWhenFailing); Assert.IsTrue(background.EnabledWhenPassing); Assert.AreEqual("Background", background.Name); StoryboardLayer fail = storyboard.Layers.FirstOrDefault(l => l.Depth == 2); Assert.IsNotNull(fail); - Assert.AreEqual(0, fail.Elements.Count()); + Assert.AreEqual(0, fail.Elements.Count); Assert.IsTrue(fail.EnabledWhenFailing); Assert.IsFalse(fail.EnabledWhenPassing); Assert.AreEqual("Fail", fail.Name); StoryboardLayer pass = storyboard.Layers.FirstOrDefault(l => l.Depth == 1); Assert.IsNotNull(pass); - Assert.AreEqual(0, pass.Elements.Count()); + Assert.AreEqual(0, pass.Elements.Count); Assert.IsFalse(pass.EnabledWhenFailing); Assert.IsTrue(pass.EnabledWhenPassing); Assert.AreEqual("Pass", pass.Name); StoryboardLayer foreground = storyboard.Layers.FirstOrDefault(l => l.Depth == 0); Assert.IsNotNull(foreground); - Assert.AreEqual(151, foreground.Elements.Count()); + Assert.AreEqual(151, foreground.Elements.Count); Assert.IsTrue(foreground.EnabledWhenFailing); Assert.IsTrue(foreground.EnabledWhenPassing); Assert.AreEqual("Foreground", foreground.Name); @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(15, spriteCount); Assert.AreEqual(1, animationCount); Assert.AreEqual(0, sampleCount); - Assert.AreEqual(background.Elements.Count(), spriteCount + animationCount + sampleCount); + Assert.AreEqual(background.Elements.Count, spriteCount + animationCount + sampleCount); var sprite = background.Elements.ElementAt(0) as StoryboardSprite; Assert.NotNull(sprite); @@ -70,9 +70,9 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition); Assert.IsTrue(sprite.IsDrawable); Assert.AreEqual(Anchor.Centre, sprite.Origin); - Assert.AreEqual("SB/lyric/ja-21.png", sprite.Path); + Assert.AreEqual("SB/black.jpg", sprite.Path); - var animation = background.Elements.ElementAt(12) as StoryboardAnimation; + var animation = background.Elements.OfType().First(); Assert.NotNull(animation); Assert.AreEqual(141175, animation.EndTime); Assert.AreEqual(10, animation.FrameCount); From a88f23e55530d569b1ff953f4af9ab40cc67035a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 16:53:44 +0900 Subject: [PATCH 245/623] Convert remaining two flags to ctor parameters --- .../TestCaseSliderInput.cs | 8 ++----- .../TestCaseBackgroundScreenBeatmap.cs | 8 +++++-- .../Visual/Gameplay/TestCaseAutoplay.cs | 11 +++++----- .../Visual/Gameplay/TestCasePlayerLoader.cs | 17 +++++++-------- osu.Game/Screens/Play/Player.cs | 21 ++++++++++++++----- osu.Game/Screens/Play/ReplayPlayer.cs | 3 ++- osu.Game/Tests/Visual/AllPlayersTestCase.cs | 6 +----- osu.Game/Tests/Visual/PlayerTestCase.cs | 6 +----- 8 files changed, 41 insertions(+), 39 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs index 9614ece40e..a14af59b38 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs @@ -330,11 +330,7 @@ namespace osu.Game.Rulesets.Osu.Tests }, }, Clock); - var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }) - { - AllowPause = false, - AllowResults = false - }; + var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); p.OnLoadComplete += _ => { @@ -363,7 +359,7 @@ namespace osu.Game.Rulesets.Osu.Tests public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public ScoreAccessibleReplayPlayer(Score score) - : base(score) + : base(score, false, false) { } } diff --git a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs index e56156752b..9abd1bdf23 100644 --- a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs @@ -257,9 +257,8 @@ namespace osu.Game.Tests.Visual.Background AddStep("Start player loader", () => { - songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer + songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer(allowPause) { - AllowPause = allowPause, Ready = true, })); }); @@ -357,6 +356,11 @@ namespace osu.Game.Tests.Visual.Background public readonly Bindable ReplacesBackground = new Bindable(); public readonly Bindable IsPaused = new Bindable(); + public TestPlayer(bool allowPause = true) + : base(allowPause) + { + } + public bool IsStoryboardVisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha == 1; public bool IsStoryboardInvisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha <= 1; diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs index efb6dac158..a2d92b7861 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs @@ -15,11 +15,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Player CreatePlayer(Ruleset ruleset) { Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); - return new ScoreAccessiblePlayer - { - AllowPause = false, - AllowResults = false, - }; + return new ScoreAccessiblePlayer(); } protected override void AddCheckSteps() @@ -32,6 +28,11 @@ namespace osu.Game.Tests.Visual.Gameplay { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new HUDOverlay HUDOverlay => base.HUDOverlay; + + public ScoreAccessiblePlayer() + : base(false, false) + { + } } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs index f571e85fe8..41d484e21f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs @@ -26,11 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay { Beatmap.Value = new DummyWorkingBeatmap(game); - AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player - { - AllowPause = false, - AllowResults = false, - }))); + AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player(false, false)))); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); @@ -46,11 +42,7 @@ namespace osu.Game.Tests.Visual.Gameplay { SlowLoadPlayer slow = null; - stack.Push(loader = new PlayerLoader(() => slow = new SlowLoadPlayer - { - AllowPause = false, - AllowResults = false, - })); + stack.Push(loader = new PlayerLoader(() => slow = new SlowLoadPlayer(false, false))); Scheduler.AddDelayed(() => slow.Ready = true, 5000); }); @@ -62,6 +54,11 @@ namespace osu.Game.Tests.Visual.Gameplay { public bool Ready; + public SlowLoadPlayer(bool allowPause = true, bool showResults = true) + : base(allowPause, showResults) + { + } + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3ef8525b65..6847dcafd3 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -43,9 +43,6 @@ namespace osu.Game.Screens.Play public bool HasFailed { get; private set; } - public bool AllowPause { get; set; } = true; - public bool AllowResults { get; set; } = true; - private Bindable mouseWheelDisabled; private readonly Bindable storyboardReplacesBackground = new Bindable(); @@ -70,6 +67,20 @@ namespace osu.Game.Screens.Play protected GameplayClockContainer GameplayClockContainer { get; private set; } + private readonly bool allowPause; + private readonly bool showResults; + + /// + /// Create a new player instance. + /// + /// Whether pausing should be allowed. If not allowed, attempting to pause will quit. + /// Whether results screen should be pushed on completion. + public Player(bool allowPause = true, bool showResults = true) + { + this.allowPause = allowPause; + this.showResults = showResults; + } + [BackgroundDependencyLoader] private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) { @@ -233,7 +244,7 @@ namespace osu.Game.Screens.Play ValidForResume = false; - if (!AllowResults) return; + if (!showResults) return; using (BeginDelayedSequence(1000)) { @@ -347,7 +358,7 @@ namespace osu.Game.Screens.Play private bool canPause => // must pass basic screen conditions (beatmap loaded, instance allows pause) - LoadedBeatmapSuccessfully && AllowPause && ValidForResume + LoadedBeatmapSuccessfully && allowPause && ValidForResume // replays cannot be paused and exit immediately && !DrawableRuleset.HasReplayLoaded.Value // cannot pause if we are already in a fail state diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 949b08d98d..a9c0ee3a15 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -9,7 +9,8 @@ namespace osu.Game.Screens.Play { private readonly Score score; - public ReplayPlayer(Score score) + public ReplayPlayer(Score score, bool allowPause = true, bool showResults = true) + : base(allowPause, showResults) { this.score = score; } diff --git a/osu.Game/Tests/Visual/AllPlayersTestCase.cs b/osu.Game/Tests/Visual/AllPlayersTestCase.cs index 25c397857d..4ef9b346b0 100644 --- a/osu.Game/Tests/Visual/AllPlayersTestCase.cs +++ b/osu.Game/Tests/Visual/AllPlayersTestCase.cs @@ -80,10 +80,6 @@ namespace osu.Game.Tests.Visual return Player; } - protected virtual Player CreatePlayer(Ruleset ruleset) => new Player - { - AllowPause = false, - AllowResults = false, - }; + protected virtual Player CreatePlayer(Ruleset ruleset) => new Player(false, false); } } diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index 3be887910c..c655b7ed47 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -56,10 +56,6 @@ namespace osu.Game.Tests.Visual LoadScreen(Player); } - protected virtual Player CreatePlayer(Ruleset ruleset) => new Player - { - AllowPause = false, - AllowResults = false, - }; + protected virtual Player CreatePlayer(Ruleset ruleset) => new Player(false, false); } } From 15821c751105cff18db3190ed252e548473c663e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 17:04:14 +0900 Subject: [PATCH 246/623] Fix cursor adding --- osu.Game/Rulesets/UI/Playfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 78d14a27e3..ecad478caf 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.UI Cursor = CreateCursor(); if (Cursor != null) - CursorTargetContainer.Add(Cursor); + AddInternal(Cursor); } /// From 7cfc2f7dcbc7c1da9a9f8dce0ac0b42224d8e937 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 17:11:14 +0900 Subject: [PATCH 247/623] Add back adjustment container --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index f68aee7e0c..001069f9d8 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -23,6 +23,10 @@ namespace osu.Game.Rulesets.Osu.UI public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); + private readonly PlayfieldAdjustmentContainer adjustmentContainer; + + protected override Container CursorTargetContainer => adjustmentContainer; + protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); public OsuPlayfield() @@ -32,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.UI Size = new Vector2(0.75f); - InternalChild = new PlayfieldAdjustmentContainer + InternalChild = adjustmentContainer = new PlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] From 3fe52be77febe7ce3e41cc1762717cdd71b02db6 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 26 Mar 2019 17:18:35 +0900 Subject: [PATCH 248/623] Better tests, add documentation --- .../Visual/Gameplay/TestCasePlayerLoader.cs | 7 +- .../Visual/TestCaseFacadeContainer.cs | 30 ++++--- .../Graphics/Containers/FacadeContainer.cs | 78 ++++++++++++------- osu.Game/Screens/Menu/ButtonSystem.cs | 63 +++++++-------- osu.Game/Screens/Play/PlayerLoader.cs | 37 +++++---- 5 files changed, 121 insertions(+), 94 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs index 67dba71d45..be2a21d23d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs @@ -7,16 +7,13 @@ using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Screens; -using osu.Game.Screens.Menu; using osu.Game.Screens.Play; -using osuTK; namespace osu.Game.Tests.Visual.Gameplay { public class TestCasePlayerLoader : ManualInputManagerTestCase { private PlayerLoader loader; - private readonly OsuScreenStack stack; public TestCasePlayerLoader() @@ -29,8 +26,6 @@ namespace osu.Game.Tests.Visual.Gameplay { Beatmap.Value = new DummyWorkingBeatmap(game); - AddStep("Reset logo position", () => logo = new OsuLogo { Position = new Vector2(0, 0) }); - AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player { AllowPause = false, @@ -58,6 +53,8 @@ namespace osu.Game.Tests.Visual.Gameplay AllowLeadIn = false, AllowResults = false, })); + + Scheduler.AddDelayed(() => slow.Ready = true, 5000); }); AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); diff --git a/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs b/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs index 5591bec0b8..0d4caff97e 100644 --- a/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs @@ -11,7 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Testing; +using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Mods; @@ -31,7 +31,11 @@ namespace osu.Game.Tests.Visual typeof(PlayerLoader), typeof(Player), typeof(Facade), - typeof(FacadeContainer) + typeof(FacadeContainer), + typeof(ButtonSystem), + typeof(ButtonSystemState), + typeof(Menu), + typeof(MainMenu) }; [Cached] @@ -68,15 +72,10 @@ namespace osu.Game.Tests.Visual AllowPause = false, AllowLeadIn = false, AllowResults = false, + Ready = false }))); } - [Test] - public void MainMenuTest() - { - AddStep("Add new Main Menu", () => LoadScreen(new MainMenu())); - } - private class TestFacadeContainer : FacadeContainer { protected override Facade CreateFacade() => new Facade @@ -119,16 +118,23 @@ namespace osu.Game.Tests.Visual base.LogoArriving(logo, resuming); logo.FadeIn(350); logo.ScaleTo(new Vector2(0.15f), 350, Easing.In); - facadeContainer.SetLogo(logo); + facadeContainer.SetLogo(logo, 0.3f, 1000, Easing.InOutQuint); + facadeContainer.Tracking = true; moveLogoFacade(); } + protected override void LogoExiting(OsuLogo logo) + { + base.LogoExiting(logo); + facadeContainer.Tracking = false; + } + private void moveLogoFacade() { Random random = new Random(); if (facadeFlowComponent.Transforms.Count == 0) { - facadeFlowComponent.Delay(500).MoveTo(new Vector2(random.Next(0, 800), random.Next(0, 600)), 300); + facadeFlowComponent.Delay(500).MoveTo(new Vector2(random.Next(0, (int)DrawWidth), random.Next(0, (int)DrawHeight)), 300); } if (randomPositions) @@ -159,11 +165,13 @@ namespace osu.Game.Tests.Visual private class TestPlayer : Player { + public bool Ready; + [BackgroundDependencyLoader] private void load() { // Never finish loading - while (true) + while (!Ready) Thread.Sleep(1); } } diff --git a/osu.Game/Graphics/Containers/FacadeContainer.cs b/osu.Game/Graphics/Containers/FacadeContainer.cs index 611cc94958..47ba738f1c 100644 --- a/osu.Game/Graphics/Containers/FacadeContainer.cs +++ b/osu.Game/Graphics/Containers/FacadeContainer.cs @@ -10,80 +10,98 @@ using osuTK; namespace osu.Game.Graphics.Containers { + /// + /// A container that creates a to be used by its children. + /// This container also updates the position and size of the Facade, and contains logic for tracking an on the Facade's position. + /// public class FacadeContainer : Container { + protected virtual Facade CreateFacade() => new Facade(); + + public Facade Facade => facade; + + /// + /// Whether or not the logo assigned to this FacadeContainer should be tracking the position its facade. + /// + public bool Tracking; + [Cached] private Facade facade; private OsuLogo logo; + private float facadeScale; - private bool tracking; - - protected virtual Facade CreateFacade() => new Facade(); + private Vector2 startPosition; + private Easing easing; + private double startTime; + private double duration; public FacadeContainer() { facade = CreateFacade(); } - private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.Centre); - - public void SetLogo(OsuLogo logo, double transformDelay = 0) + /// + /// Set the logo that should track the Facade's position, as well as how it should transform to its initial position. + /// + /// The instance of the logo to be used for tracking. + /// The scale of the facade. + /// The duration of the initial transform. Default is instant. + /// The easing type of the initial transform. + public void SetLogo(OsuLogo logo, float facadeScale, double duration = 0, Easing easing = Easing.None) { if (logo != null) { - facade.Size = new Vector2(logo.SizeForFlow * 0.3f); this.logo = logo; - Scheduler.AddDelayed(() => - { - tracking = true; - }, transformDelay); } + + this.facadeScale = facadeScale; + this.duration = duration; + this.easing = easing; } - private double startTime; - private double duration = 1000; - - private Vector2 startPosition; - private Easing easing = Easing.InOutExpo; + private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.Centre); protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - if (logo == null || !tracking) + if (logo == null || !Tracking) return; - facade.Size = new Vector2(logo.SizeForFlow * 0.3f); + facade.Size = new Vector2(logo.SizeForFlow * facadeScale); if (facade.IsLoaded && logo.Position != logoTrackingPosition) { - if (logo.RelativePositionAxes != Axes.None) - { - logo.RelativePositionAxes = Axes.None; - logo.Position = logo.Parent.ToLocalSpace(logo.Position); - } + // Required for the correct position of the logo to be set with respect to logoTrackingPosition + logo.RelativePositionAxes = Axes.None; + // If this is our first update since tracking has started, initialize our starting values for interpolation if (startTime == 0) { startTime = Time.Current; + startPosition = logo.Position; } var endTime = startTime + duration; var remainingDuration = endTime - Time.Current; - if (remainingDuration <= 0) - { - remainingDuration = 0; - } + // If our transform should be instant, our position should already be at logoTrackingPosition, thus set the blend to 0. + // If we are already past when the transform should be finished playing, set the blend to 0 so that the logo is always at the position of the facade. + var blend = duration > 0 && remainingDuration > 0 + ? (float)Interpolation.ApplyEasing(easing, remainingDuration / duration) + : 0; - float currentTime = (float)Interpolation.ApplyEasing(easing, remainingDuration / duration); - logo.Position = Vector2.Lerp(logoTrackingPosition, startPosition, currentTime); + // Interpolate the position of the logo, where blend 0 is the position of the Facade, and blend 1 is where the logo was when it first began interpolating. + logo.Position = Vector2.Lerp(logoTrackingPosition, startPosition, blend); } } } } +/// +/// A placeholder container that serves as a dummy object to denote another object's location and size. +/// public class Facade : Container { -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 3df4ef9059..4c0bcd399a 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; @@ -16,6 +17,7 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Online.API; @@ -53,15 +55,19 @@ namespace osu.Game.Screens.Menu if (this.logo != null) { this.logo.Action = onOsuLogo; + facadeContainer.SetLogo(logo, 0.5f); // osuLogo.SizeForFlow relies on loading to be complete. buttonArea.Flow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + this.logo.SizeForFlow / 4), 0); updateLogoState(); } + else + { + facadeContainer.Tracking = false; + } } - private readonly Drawable iconFacade; private readonly ButtonArea buttonArea; private readonly Button backButton; @@ -71,26 +77,29 @@ namespace osu.Game.Screens.Menu private SampleChannel sampleBack; + private readonly FacadeContainer facadeContainer; + public ButtonSystem() { RelativeSizeAxes = Axes.Both; - Child = buttonArea = new ButtonArea(); + Child = facadeContainer = new FacadeContainer + { + RelativeSizeAxes = Axes.Both, + Child = buttonArea = new ButtonArea() + }; - buttonArea.AddRange(new[] + buttonArea.AddRange(new Container[] { new Button(@"settings", string.Empty, FontAwesome.fa_gear, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), backButton = new Button(@"back", @"button-back-select", FontAwesome.fa_osu_left_o, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) { VisibleState = ButtonSystemState.Play, }, - iconFacade = new Container //need a container to make the osu! icon flow properly. - { - Size = new Vector2(0, ButtonArea.BUTTON_AREA_HEIGHT) - } + facadeContainer.Facade }); - buttonArea.Flow.CentreTarget = iconFacade; + buttonArea.Flow.CentreTarget = facadeContainer.Facade; } [Resolved(CanBeNull = true)] @@ -120,6 +129,15 @@ namespace osu.Game.Screens.Menu buttonArea.AddRange(buttonsPlay); buttonArea.AddRange(buttonsTopLevel); + buttonArea.ForEach(b => + { + if (b is Button) + { + b.Origin = Anchor.CentreLeft; + b.Anchor = Anchor.CentreLeft; + } + }); + isIdle.ValueChanged += idle => updateIdleState(idle.NewValue); if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle); @@ -247,7 +265,7 @@ namespace osu.Game.Screens.Menu logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => { - logoTracking = false; + facadeContainer.Tracking = false; game?.Toolbar.Hide(); @@ -266,19 +284,16 @@ namespace osu.Game.Screens.Menu break; case ButtonSystemState.Initial: logo.ClearTransforms(targetMember: nameof(Position)); - logo.RelativePositionAxes = Axes.None; bool impact = logo.Scale.X > 0.6f; if (lastState == ButtonSystemState.Initial) logo.ScaleTo(0.5f, 200, Easing.In); - logo.MoveTo(logoTrackingPosition, lastState == ButtonSystemState.EnteringMode ? 0 : 200, Easing.In); - logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => { - logoTracking = true; + facadeContainer.Tracking = true; if (impact) logo.Impact(); @@ -288,35 +303,17 @@ namespace osu.Game.Screens.Menu break; default: logo.ClearTransforms(targetMember: nameof(Position)); - logo.RelativePositionAxes = Axes.None; - logoTracking = true; + facadeContainer.Tracking = true; logo.ScaleTo(0.5f, 200, Easing.OutQuint); break; } break; case ButtonSystemState.EnteringMode: - logoTracking = true; + facadeContainer.Tracking = true; break; } } - - private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(iconFacade.ScreenSpaceDrawQuad.Centre); - - private bool logoTracking; - - protected override void Update() - { - base.Update(); - - if (logo != null) - { - if (logoTracking && logo.RelativePositionAxes == Axes.None && iconFacade.IsLoaded) - logo.Position = logoTrackingPosition; - - iconFacade.Width = logo.SizeForFlow * 0.5f; - } - } } public enum ButtonSystemState diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index b7155f771f..f35eb6979d 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play private Player player; - private FacadeContainer content; + private FacadeContainer facadeContainer; protected virtual FacadeContainer CreateFacadeContainer() => new FacadeContainer(); @@ -62,11 +62,11 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { - InternalChild = content = CreateFacadeContainer(); - content.Anchor = Anchor.Centre; - content.Origin = Anchor.Centre; - content.RelativeSizeAxes = Axes.Both; - content.Children = new Drawable[] + InternalChild = facadeContainer = CreateFacadeContainer(); + facadeContainer.Anchor = Anchor.Centre; + facadeContainer.Origin = Anchor.Centre; + facadeContainer.RelativeSizeAxes = Axes.Both; + facadeContainer.Children = new Drawable[] { info = new BeatmapMetadataDisplay(Beatmap.Value) { @@ -122,21 +122,21 @@ namespace osu.Game.Screens.Play private void contentIn() { - content.ScaleTo(1, 650, Easing.OutQuint); - content.FadeInFromZero(400); + facadeContainer.ScaleTo(1, 650, Easing.OutQuint); + facadeContainer.FadeInFromZero(400); } private void contentOut() { - content.ScaleTo(0.7f, 300, Easing.InQuint); - content.FadeOut(250); + facadeContainer.ScaleTo(0.7f, 300, Easing.InQuint); + facadeContainer.FadeOut(250); } public override void OnEntering(IScreen last) { base.OnEntering(last); - content.ScaleTo(0.7f); + facadeContainer.ScaleTo(0.7f); Background?.FadeColour(Color4.White, 800, Easing.OutQuint); contentIn(); @@ -155,7 +155,15 @@ namespace osu.Game.Screens.Play logo.MoveTo(new Vector2(0.5f), duration, Easing.In); logo.FadeIn(350); - content.SetLogo(logo, duration); + facadeContainer.SetLogo(logo, 0.3f, 500, Easing.InOutQuint); + + Scheduler.AddDelayed(() => facadeContainer.Tracking = true, duration); + } + + protected override void LogoExiting(OsuLogo logo) + { + base.LogoExiting(logo); + facadeContainer.Tracking = false; } protected override void LoadComplete() @@ -230,7 +238,7 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { - content.ScaleTo(0.7f, 150, Easing.InQuint); + facadeContainer.ScaleTo(0.7f, 150, Easing.InQuint); this.FadeOut(150); cancelLoad(); @@ -305,7 +313,6 @@ namespace osu.Game.Screens.Play private LoadingAnimation loading; private Sprite backgroundSprite; private ModDisplay modDisplay; - private FillFlowContainer fillFlowContainer; public bool Loading { @@ -340,7 +347,7 @@ namespace osu.Game.Screens.Play AutoSizeAxes = Axes.Both; Children = new Drawable[] { - fillFlowContainer = new FillFlowContainer + new FillFlowContainer { AutoSizeAxes = Axes.Both, Origin = Anchor.TopCentre, From adab31fd582dfe6509e863f3f221b012271142b1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 Mar 2019 17:38:56 +0900 Subject: [PATCH 249/623] Cleanup + fix up score table layout --- .../Online/TestCaseBeatmapSetOverlay.cs | 6 +- .../TestCaseBeatmapScoresContainer.cs | 5 +- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 237 +++-------------- .../BeatmapSet/Scores/ScoreTableHeader.cs | 70 ----- .../BeatmapSet/Scores/ScoreTableHeaderRow.cs | 46 ++++ .../BeatmapSet/Scores/ScoreTableRow.cs | 246 ++++++------------ .../Scores/ScoreTableRowBackground.cs | 64 +++++ .../BeatmapSet/Scores/ScoreTableScore.cs | 220 ---------------- .../BeatmapSet/Scores/ScoreTableScoreRow.cs | 168 ++++++++++++ 9 files changed, 401 insertions(+), 661 deletions(-) delete mode 100644 osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeader.cs create mode 100644 osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeaderRow.cs create mode 100644 osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs delete mode 100644 osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScore.cs create mode 100644 osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs diff --git a/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs index df24c42b00..19d295cf12 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs @@ -25,7 +25,11 @@ namespace osu.Game.Tests.Visual.Online { typeof(Header), typeof(ClickableUserContainer), - typeof(ScoreTableScore), + typeof(ScoreTable), + typeof(ScoreTableRow), + typeof(ScoreTableHeaderRow), + typeof(ScoreTableScoreRow), + typeof(ScoreTableRowBackground), typeof(DrawableTopScore), typeof(ScoresContainer), typeof(AuthorInfo), diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs index 2bc0796045..63dacef8fa 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs @@ -28,8 +28,9 @@ namespace osu.Game.Tests.Visual.SongSelect typeof(ScoresContainer), typeof(ScoreTable), typeof(ScoreTableRow), - typeof(ScoreTableHeader), - typeof(ScoreTableScore) + typeof(ScoreTableHeaderRow), + typeof(ScoreTableScoreRow), + typeof(ScoreTableRowBackground), }; private readonly Box background; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 7e838f0aae..e2acf778a3 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -6,203 +6,74 @@ using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Online.Leaderboards; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Users; -using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet.Scores { public class ScoreTable : CompositeDrawable { - private const int fade_duration = 100; - private const int text_size = 14; - - private readonly FillFlowContainer scoresFlow; private readonly ScoresGrid scoresGrid; + private readonly FillFlowContainer backgroundFlow; public ScoreTable() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChild = scoresGrid = new ScoresGrid + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.X, - ColumnDimensions = new[] + backgroundFlow = new FillFlowContainer { - new Dimension(GridSizeMode.Absolute, 40), - new Dimension(GridSizeMode.Absolute, 70), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Distributed, minSize: 180), - new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), - new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), - new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), - new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), - new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), - new Dimension(GridSizeMode.Distributed, minSize: 40, maxSize: 70), - new Dimension(GridSizeMode.AutoSize), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 25 } + }, + scoresGrid = new ScoresGrid + { + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 40), + new Dimension(GridSizeMode.Absolute, 70), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Distributed, minSize: 150), + new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 90), + new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), + new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), + new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), + new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), + new Dimension(GridSizeMode.Distributed, minSize: 40, maxSize: 70), + new Dimension(GridSizeMode.AutoSize), + } } }; - - scoresFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical - }; } public IEnumerable Scores { set { - scoresFlow.Clear(); - if (value == null || !value.Any()) return; - int maxModsAmount = 0; - foreach (var s in value) - { - var scoreModsAmount = s.Mods.Length; - if (scoreModsAmount > maxModsAmount) - maxModsAmount = scoreModsAmount; - } - - scoresFlow.Add(new ScoreTableHeader(maxModsAmount)); + var content = new List { new ScoreTableHeaderRow().CreateDrawables().ToArray() }; int index = 0; foreach (var s in value) - scoresFlow.Add(new ScoreTableScore(index++, s, maxModsAmount)); + content.Add(new ScoreTableScoreRow(index++, s).CreateDrawables().ToArray()); - scoresGrid.Content = value.Select((s, i) => createRowContents(s, i).ToArray()).ToArray(); + scoresGrid.Content = content.ToArray(); + + backgroundFlow.Clear(); + for (int i = 0; i < index; i++) + backgroundFlow.Add(new ScoreTableRowBackground(i)); } } - private IEnumerable createRowContents(APIScoreInfo score, int index) - { - yield return new SpriteText - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Text = $"#{index + 1}", - Font = @"Exo2.0-Bold", - TextSize = text_size, - }; - - yield return new DrawableRank(score.Rank) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(30, 20), - FillMode = FillMode.Fit, - }; - - yield return new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 20 }, - Text = $@"{score.TotalScore:N0}", - TextSize = text_size, - Font = index == 0 ? OsuFont.GetFont(weight: FontWeight.Bold) : OsuFont.Default - }; - - yield return new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 20 }, - Text = $@"{score.Accuracy:P2}", - TextSize = text_size, - Colour = score.Accuracy == 1 ? Color4.GreenYellow : Color4.White - }; - - yield return new FillFlowContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), - Children = new Drawable[] - { - new DrawableFlag(score.User.Country) - { - Size = new Vector2(20, 13), - }, - new ClickableScoreUsername - { - User = score.User, - } - } - }; - - yield return new SpriteText - { - Text = $@"{score.MaxCombo:N0}x", - TextSize = text_size, - }; - - yield return new SpriteText - { - Text = $"{score.Statistics[HitResult.Great]}", - TextSize = text_size, - Colour = score.Statistics[HitResult.Great] == 0 ? Color4.Gray : Color4.White - }; - - yield return new SpriteText - { - Text = $"{score.Statistics[HitResult.Good]}", - TextSize = text_size, - Colour = score.Statistics[HitResult.Good] == 0 ? Color4.Gray : Color4.White - }; - - yield return new SpriteText - { - Text = $"{score.Statistics[HitResult.Meh]}", - TextSize = text_size, - Colour = score.Statistics[HitResult.Meh] == 0 ? Color4.Gray : Color4.White - }; - - yield return new SpriteText - { - Text = $"{score.Statistics[HitResult.Miss]}", - TextSize = text_size, - Colour = score.Statistics[HitResult.Miss] == 0 ? Color4.Gray : Color4.White - }; - - yield return new SpriteText - { - Text = $@"{score.PP:N0}", - TextSize = text_size, - }; - - yield return new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - ChildrenEnumerable = score.Mods.Select(m => new ModIcon(m) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Scale = new Vector2(0.3f), - }) - }; - } - private class ScoresGrid : GridContainer { public ScoresGrid() { + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; } @@ -217,49 +88,5 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } } - - private class ClickableScoreUsername : ClickableUserContainer - { - private readonly SpriteText text; - private readonly SpriteText textBold; - - public ClickableScoreUsername() - { - Add(text = new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - TextSize = text_size, - }); - - Add(textBold = new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - TextSize = text_size, - Font = @"Exo2.0-Bold", - Alpha = 0, - }); - } - - protected override void OnUserChange(User user) - { - text.Text = textBold.Text = user.Username; - } - - protected override bool OnHover(HoverEvent e) - { - textBold.FadeIn(fade_duration, Easing.OutQuint); - text.FadeOut(fade_duration, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - textBold.FadeOut(fade_duration, Easing.OutQuint); - text.FadeIn(fade_duration, Easing.OutQuint); - base.OnHoverLost(e); - } - } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeader.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeader.cs deleted file mode 100644 index 544bf34ec5..0000000000 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeader.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics.Sprites; - -namespace osu.Game.Overlays.BeatmapSet.Scores -{ - public class ScoreTableHeader : ScoreTableRow - { - public ScoreTableHeader(int maxModsAmount) - : base(maxModsAmount) - { - RankContainer.Add(new ScoreText - { - Text = @"rank".ToUpper(), - }); - ScoreContainer.Add(new ScoreText - { - Text = @"score".ToUpper(), - }); - AccuracyContainer.Add(new ScoreText - { - Text = @"accuracy".ToUpper(), - }); - PlayerContainer.Add(new ScoreText - { - Text = @"player".ToUpper(), - }); - MaxComboContainer.Add(new ScoreText - { - Text = @"max combo".ToUpper(), - }); - HitGreatContainer.Add(new ScoreText - { - Text = "300".ToUpper(), - }); - HitGoodContainer.Add(new ScoreText - { - Text = "100".ToUpper(), - }); - HitMehContainer.Add(new ScoreText - { - Text = "50".ToUpper(), - }); - HitMissContainer.Add(new ScoreText - { - Text = @"misses".ToUpper(), - }); - PPContainer.Add(new ScoreText - { - Text = @"pp".ToUpper(), - }); - ModsContainer.Add(new ScoreText - { - Text = @"mods".ToUpper(), - }); - } - - private class ScoreText : SpriteText - { - private const float text_size = 12; - - public ScoreText() - { - TextSize = text_size; - Font = @"Exo2.0-Black"; - } - } - } -} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeaderRow.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeaderRow.cs new file mode 100644 index 0000000000..a48716b71a --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeaderRow.cs @@ -0,0 +1,46 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class ScoreTableHeaderRow : ScoreTableRow + { + protected override Drawable CreateIndexCell() => new CellText("rank"); + + protected override Drawable CreateRankCell() => new Container(); + + protected override Drawable CreateScoreCell() => new CellText("score"); + + protected override Drawable CreateAccuracyCell() => new CellText("accuracy"); + + protected override Drawable CreatePlayerCell() => new CellText("player"); + + protected override IEnumerable CreateStatisticsCells() => new[] + { + new CellText("max combo"), + new CellText("300"), + new CellText("100"), + new CellText("50"), + new CellText("miss"), + }; + + protected override Drawable CreatePpCell() => new CellText("pp"); + + protected override Drawable CreateModsCell() => new CellText("mods"); + + private class CellText : OsuSpriteText + { + public CellText(string text) + { + Text = text.ToUpper(); + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black); + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs index 3a48a0cca1..4abfb92957 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs @@ -1,183 +1,103 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; namespace osu.Game.Overlays.BeatmapSet.Scores { - public class ScoreTableRow : GridContainer + public abstract class ScoreTableRow { - private const float rank_position = 30; - private const float drawable_rank_position = 45; - private const float score_position = 90; - private const float accuracy_position = 170; - private const float flag_position = 220; - private const float player_position = 250; + protected const int TEXT_SIZE = 14; - private const float max_combo_position = 0.1f; - private const float hit_great_position = 0.3f; - private const float hit_good_position = 0.45f; - private const float hit_meh_position = 0.6f; - private const float hit_miss_position = 0.75f; - private const float pp_position = 0.9f; - - protected readonly Container RankContainer; - protected readonly Container DrawableRankContainer; - protected readonly Container ScoreContainer; - protected readonly Container AccuracyContainer; - protected readonly Container FlagContainer; - protected readonly Container PlayerContainer; - protected readonly Container MaxComboContainer; - protected readonly Container HitGreatContainer; - protected readonly Container HitGoodContainer; - protected readonly Container HitMehContainer; - protected readonly Container HitMissContainer; - protected readonly Container PPContainer; - protected readonly Container ModsContainer; - - public ScoreTableRow(int maxModsAmount) + public IEnumerable CreateDrawables() { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - RowDimensions = new[] + yield return new Container { - new Dimension(GridSizeMode.Absolute, 25), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Child = CreateIndexCell() }; - ColumnDimensions = new[] + + yield return new Container { - new Dimension(GridSizeMode.Absolute, 300), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Child = CreateRankCell() }; - Content = new[] + + yield return new Container { - new Drawable[] + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Right = 20 }, + Child = CreateScoreCell() + }; + + yield return new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Right = 20 }, + Child = CreateAccuracyCell() + }; + + yield return new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Right = 20 }, + Child = CreatePlayerCell() + }; + + foreach (var cell in CreateStatisticsCells()) + { + yield return new Container { - new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - RankContainer = new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, - AutoSizeAxes = Axes.Both, - X = rank_position, - }, - DrawableRankContainer = new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - X = drawable_rank_position, - }, - ScoreContainer = new Container - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - X = score_position, - }, - AccuracyContainer = new Container - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - X = accuracy_position, - }, - FlagContainer = new Container - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - X = flag_position, - }, - PlayerContainer = new Container - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - X = player_position, - } - } - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] - { - MaxComboContainer = new Container - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = max_combo_position, - }, - HitGreatContainer = new Container - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = hit_great_position, - }, - HitGoodContainer = new Container - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = hit_good_position, - }, - HitMehContainer = new Container - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = hit_meh_position, - }, - HitMissContainer = new Container - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = hit_miss_position, - }, - PPContainer = new Container - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = pp_position, - } - } - }, - new Container - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Child = ModsContainer = new Container - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - X = -30 * ((maxModsAmount == 0) ? 1 : maxModsAmount), - } - } - } + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Child = cell + }; + } + + yield return new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Child = CreatePpCell() + }; + + yield return new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Child = CreateModsCell() }; } + + protected abstract Drawable CreateIndexCell(); + + protected abstract Drawable CreateRankCell(); + + protected abstract Drawable CreateScoreCell(); + + protected abstract Drawable CreateAccuracyCell(); + + protected abstract Drawable CreatePlayerCell(); + + protected abstract IEnumerable CreateStatisticsCells(); + + protected abstract Drawable CreatePpCell(); + + protected abstract Drawable CreateModsCell(); } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs new file mode 100644 index 0000000000..d820f4d89d --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class ScoreTableRowBackground : CompositeDrawable + { + private const int fade_duration = 100; + + private readonly Box hoveredBackground; + private readonly Box background; + + public ScoreTableRowBackground(int index) + { + RelativeSizeAxes = Axes.X; + Height = 25; + + CornerRadius = 3; + Masking = true; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + hoveredBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + }; + + if (index % 2 != 0) + background.Alpha = 0; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoveredBackground.Colour = colours.Gray4; + background.Colour = colours.Gray3; + } + + protected override bool OnHover(HoverEvent e) + { + hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScore.cs deleted file mode 100644 index a54770fb39..0000000000 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScore.cs +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Leaderboards; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Users; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Overlays.BeatmapSet.Scores -{ - public class ScoreTableScore : Container - { - private const int fade_duration = 100; - private const int text_size = 14; - - private readonly Box hoveredBackground; - private readonly Box background; - - public ScoreTableScore(int index, APIScoreInfo score, int maxModsAmount) - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - CornerRadius = 3; - Masking = true; - - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - }, - hoveredBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - new ScoreRow(index, score, maxModsAmount), - }; - - if (index % 2 != 0) - background.Alpha = 0; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - hoveredBackground.Colour = colours.Gray4; - background.Colour = colours.Gray3; - } - - protected override bool OnHover(HoverEvent e) - { - hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); - base.OnHoverLost(e); - } - - protected override bool OnClick(ClickEvent e) => true; - - private class ClickableScoreUsername : ClickableUserContainer - { - private readonly SpriteText text; - private readonly SpriteText textBold; - - public ClickableScoreUsername() - { - Add(text = new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - TextSize = text_size, - }); - - Add(textBold = new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - TextSize = text_size, - Font = @"Exo2.0-Bold", - Alpha = 0, - }); - } - - protected override void OnUserChange(User user) - { - text.Text = textBold.Text = user.Username; - } - - protected override bool OnHover(HoverEvent e) - { - textBold.FadeIn(fade_duration, Easing.OutQuint); - text.FadeOut(fade_duration, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - textBold.FadeOut(fade_duration, Easing.OutQuint); - text.FadeIn(fade_duration, Easing.OutQuint); - base.OnHoverLost(e); - } - } - - private class ScoreRow : ScoreTableRow - { - public ScoreRow(int index, APIScoreInfo score, int maxModsAmount) - : base(maxModsAmount) - { - SpriteText scoreText; - SpriteText accuracy; - SpriteText hitGreat; - SpriteText hitGood; - SpriteText hitMeh; - SpriteText hitMiss; - - FillFlowContainer modsContainer; - - RankContainer.Add(new SpriteText - { - Text = $"#{index + 1}", - Font = @"Exo2.0-Bold", - TextSize = text_size, - }); - DrawableRankContainer.Add(new DrawableRank(score.Rank) - { - Size = new Vector2(30, 20), - FillMode = FillMode.Fit, - }); - ScoreContainer.Add(scoreText = new SpriteText - { - Text = $@"{score.TotalScore:N0}", - TextSize = text_size, - }); - AccuracyContainer.Add(accuracy = new SpriteText - { - Text = $@"{score.Accuracy:P2}", - TextSize = text_size, - }); - FlagContainer.Add(new DrawableFlag(score.User.Country) - { - Size = new Vector2(20, 13), - }); - PlayerContainer.Add(new ClickableScoreUsername - { - User = score.User, - }); - MaxComboContainer.Add(new SpriteText - { - Text = $@"{score.MaxCombo:N0}x", - TextSize = text_size, - }); - HitGreatContainer.Add(hitGreat = new SpriteText - { - Text = $"{score.Statistics[HitResult.Great]}", - TextSize = text_size, - }); - HitGoodContainer.Add(hitGood = new SpriteText - { - Text = $"{score.Statistics[HitResult.Good]}", - TextSize = text_size, - }); - HitMehContainer.Add(hitMeh = new SpriteText - { - Text = $"{score.Statistics[HitResult.Meh]}", - TextSize = text_size, - }); - HitMissContainer.Add(hitMiss = new SpriteText - { - Text = $"{score.Statistics[HitResult.Miss]}", - TextSize = text_size, - }); - PPContainer.Add(new SpriteText - { - Text = $@"{score.PP:N0}", - TextSize = text_size, - }); - ModsContainer.Add(modsContainer = new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - }); - - if (index == 0) - scoreText.Font = @"Exo2.0-Bold"; - - accuracy.Colour = (score.Accuracy == 1) ? Color4.GreenYellow : Color4.White; - hitGreat.Colour = (score.Statistics[HitResult.Great] == 0) ? Color4.Gray : Color4.White; - hitGood.Colour = (score.Statistics[HitResult.Good] == 0) ? Color4.Gray : Color4.White; - hitMeh.Colour = (score.Statistics[HitResult.Meh] == 0) ? Color4.Gray : Color4.White; - hitMiss.Colour = (score.Statistics[HitResult.Miss] == 0) ? Color4.Gray : Color4.White; - - foreach (Mod mod in score.Mods) - modsContainer.Add(new ModIcon(mod) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Scale = new Vector2(0.3f), - }); - } - } - } -} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs new file mode 100644 index 0000000000..cd1ade934a --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs @@ -0,0 +1,168 @@ +// Copyright (c) ppy Pty Ltd . 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class ScoreTableScoreRow : ScoreTableRow + { + private readonly int index; + private readonly ScoreInfo score; + + public ScoreTableScoreRow(int index, ScoreInfo score) + { + this.index = index; + this.score = score; + } + + protected override Drawable CreateIndexCell() => new OsuSpriteText + { + Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold) + }; + + protected override Drawable CreateRankCell() => new DrawableRank(score.Rank) + { + Size = new Vector2(30, 20), + }; + + protected override Drawable CreateScoreCell() => new OsuSpriteText + { + Text = $@"{score.TotalScore:N0}", + Font = OsuFont.GetFont(size: TEXT_SIZE, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) + }; + + protected override Drawable CreateAccuracyCell() => new OsuSpriteText + { + Text = $@"{score.Accuracy:P2}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + Colour = score.Accuracy == 1 ? Color4.GreenYellow : Color4.White + }; + + protected override Drawable CreatePlayerCell() => new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new DrawableFlag(score.User.Country) { Size = new Vector2(20, 13) }, + new ClickableScoreUsername { User = score.User } + } + }; + + protected override IEnumerable CreateStatisticsCells() + { + yield return new OsuSpriteText + { + Text = $@"{score.MaxCombo:N0}x", + Font = OsuFont.GetFont(size: TEXT_SIZE) + }; + + yield return new OsuSpriteText + { + Text = $"{score.Statistics[HitResult.Great]}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + Colour = score.Statistics[HitResult.Great] == 0 ? Color4.Gray : Color4.White + }; + + yield return new OsuSpriteText + { + Text = $"{score.Statistics[HitResult.Good]}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + Colour = score.Statistics[HitResult.Good] == 0 ? Color4.Gray : Color4.White + }; + + yield return new OsuSpriteText + { + Text = $"{score.Statistics[HitResult.Meh]}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + Colour = score.Statistics[HitResult.Meh] == 0 ? Color4.Gray : Color4.White + }; + + yield return new OsuSpriteText + { + Text = $"{score.Statistics[HitResult.Miss]}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + Colour = score.Statistics[HitResult.Miss] == 0 ? Color4.Gray : Color4.White + }; + } + + protected override Drawable CreatePpCell() => new OsuSpriteText + { + Text = $@"{score.PP:N0}", + Font = OsuFont.GetFont(size: TEXT_SIZE) + }; + + protected override Drawable CreateModsCell() => new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + ChildrenEnumerable = score.Mods.Select(m => new ModIcon(m) + { + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.3f) + }) + }; + + private class ClickableScoreUsername : ClickableUserContainer + { + private const int fade_duration = 100; + + private readonly SpriteText text; + private readonly SpriteText textBold; + + public ClickableScoreUsername() + { + Add(text = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: TEXT_SIZE) + }); + + Add(textBold = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold), + Alpha = 0, + }); + } + + protected override void OnUserChange(User user) + { + text.Text = textBold.Text = user.Username; + } + + protected override bool OnHover(HoverEvent e) + { + textBold.FadeIn(fade_duration, Easing.OutQuint); + text.FadeOut(fade_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + textBold.FadeOut(fade_duration, Easing.OutQuint); + text.FadeIn(fade_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + } + } +} From 6ac49fed757409b58df76cede6b6559fa7e54dda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Mar 2019 17:40:22 +0900 Subject: [PATCH 250/623] Remove broken frame --- osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs index a14af59b38..76bd9ef758 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs @@ -297,11 +297,6 @@ namespace osu.Game.Rulesets.Osu.Tests private void performTest(List frames) { - // Empty frame to be added as a workaround for first frame behavior. - // If an input exists on the first frame, the input would apply to the entire intro lead-in - // Likely requires some discussion regarding how first frame inputs should be handled. - frames.Insert(0, new OsuReplayFrame()); - AddStep("load player", () => { Beatmap.Value = new TestWorkingBeatmap(new Beatmap From 3fcbc2eab3397a79092f4252e7af49ee9ef0e86b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Mar 2019 10:31:21 +0900 Subject: [PATCH 251/623] Fix key counter getting overridden --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index e303166774..f88c8e024e 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -34,11 +34,14 @@ namespace osu.Game.Rulesets.UI protected readonly KeyBindingContainer KeyBindingContainer; - protected override Container Content => KeyBindingContainer; + protected override Container Content => content; + + private readonly Container content; protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) { - InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique); + InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique) + .WithChild(content = new Container { RelativeSizeAxes = Axes.Both }); } [BackgroundDependencyLoader(true)] From 384eee33957a30c88ec2b8ff2baffa30ede3bb98 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 27 Mar 2019 11:32:26 +0900 Subject: [PATCH 252/623] Remove DI requirement for the Facade in PlayerLoader --- .../Visual/TestCaseFacadeContainer.cs | 11 +++-------- osu.Game/Graphics/Containers/FacadeContainer.cs | 17 ++++++----------- osu.Game/Screens/Play/PlayerLoader.cs | 8 +++++--- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs b/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs index 0d4caff97e..6291b026f3 100644 --- a/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Visual private class TestScreen : OsuScreen { private TestFacadeContainer facadeContainer; - private FacadeFlowComponent facadeFlowComponent; + private Facade facadeFlowComponent; private readonly bool randomPositions; public TestScreen(bool randomPositions = false) @@ -104,13 +104,8 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { - InternalChild = facadeContainer = new TestFacadeContainer - { - Child = facadeFlowComponent = new FacadeFlowComponent - { - AutoSizeAxes = Axes.Both - } - }; + InternalChild = facadeContainer = new TestFacadeContainer(); + facadeContainer.Child = facadeFlowComponent = facadeContainer.Facade; } protected override void LogoArriving(OsuLogo logo, bool resuming) diff --git a/osu.Game/Graphics/Containers/FacadeContainer.cs b/osu.Game/Graphics/Containers/FacadeContainer.cs index 47ba738f1c..11c256725d 100644 --- a/osu.Game/Graphics/Containers/FacadeContainer.cs +++ b/osu.Game/Graphics/Containers/FacadeContainer.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; @@ -18,19 +17,15 @@ namespace osu.Game.Graphics.Containers { protected virtual Facade CreateFacade() => new Facade(); - public Facade Facade => facade; + public readonly Facade Facade; /// /// Whether or not the logo assigned to this FacadeContainer should be tracking the position its facade. /// public bool Tracking; - [Cached] - private Facade facade; - private OsuLogo logo; private float facadeScale; - private Vector2 startPosition; private Easing easing; private double startTime; @@ -38,11 +33,11 @@ namespace osu.Game.Graphics.Containers public FacadeContainer() { - facade = CreateFacade(); + Facade = CreateFacade(); } /// - /// Set the logo that should track the Facade's position, as well as how it should transform to its initial position. + /// Assign the logo that should track the Facade's position, as well as how it should transform to its initial position. /// /// The instance of the logo to be used for tracking. /// The scale of the facade. @@ -60,7 +55,7 @@ namespace osu.Game.Graphics.Containers this.easing = easing; } - private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.Centre); + private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(Facade.ScreenSpaceDrawQuad.Centre); protected override void UpdateAfterChildren() { @@ -69,9 +64,9 @@ namespace osu.Game.Graphics.Containers if (logo == null || !Tracking) return; - facade.Size = new Vector2(logo.SizeForFlow * facadeScale); + Facade.Size = new Vector2(logo.SizeForFlow * facadeScale); - if (facade.IsLoaded && logo.Position != logoTrackingPosition) + if (Facade.IsLoaded && logo.Position != logoTrackingPosition) { // Required for the correct position of the logo to be set with respect to logoTrackingPosition logo.RelativePositionAxes = Axes.None; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index f35eb6979d..4601cf71e0 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Play facadeContainer.RelativeSizeAxes = Axes.Both; facadeContainer.Children = new Drawable[] { - info = new BeatmapMetadataDisplay(Beatmap.Value) + info = new BeatmapMetadataDisplay(Beatmap.Value, facadeContainer.Facade) { Alpha = 0, Anchor = Anchor.Centre, @@ -310,6 +310,7 @@ namespace osu.Game.Screens.Play } private readonly WorkingBeatmap beatmap; + private readonly Facade facade; private LoadingAnimation loading; private Sprite backgroundSprite; private ModDisplay modDisplay; @@ -331,13 +332,14 @@ namespace osu.Game.Screens.Play } } - public BeatmapMetadataDisplay(WorkingBeatmap beatmap) + public BeatmapMetadataDisplay(WorkingBeatmap beatmap, Facade facade) { this.beatmap = beatmap; + this.facade = facade; } [BackgroundDependencyLoader] - private void load(Facade facade) + private void load() { var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); From efeed715170c5e9e45501cabda7da80d59f947fa Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 27 Mar 2019 11:37:16 +0900 Subject: [PATCH 253/623] Add comment --- osu.Game/Screens/Menu/ButtonSystem.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 4c0bcd399a..7b458809b1 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -64,6 +64,8 @@ namespace osu.Game.Screens.Menu } else { + // If logo is null, we are suspending from the screen that uses this ButtonSystem. + // We should stop tracking as the facade is now out of scope. facadeContainer.Tracking = false; } } From 43c6a8d2e59a3fbdba14ce3014abaa289d32bef8 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 27 Mar 2019 11:44:50 +0900 Subject: [PATCH 254/623] use a property instead --- osu.Game/Graphics/Containers/FacadeContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/FacadeContainer.cs b/osu.Game/Graphics/Containers/FacadeContainer.cs index 11c256725d..0ec5e77c72 100644 --- a/osu.Game/Graphics/Containers/FacadeContainer.cs +++ b/osu.Game/Graphics/Containers/FacadeContainer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Graphics.Containers { protected virtual Facade CreateFacade() => new Facade(); - public readonly Facade Facade; + public Facade Facade { get; } /// /// Whether or not the logo assigned to this FacadeContainer should be tracking the position its facade. From 7239ebf5deb6b0809e453fa8e9fcd1c1b61056a5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Mar 2019 13:57:26 +0900 Subject: [PATCH 255/623] Add margin for mods --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs index 4abfb92957..8efda73270 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs @@ -80,6 +80,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Right = 20 }, Child = CreateModsCell() }; } From 4b1e564df2fe8d7882725ad71b744dd6ffc70f42 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 27 Mar 2019 14:31:07 +0900 Subject: [PATCH 256/623] Fix test cases potentially getting stuck after 4th run --- osu.Game.Tests/Visual/TestCaseFacadeContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs b/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs index 6291b026f3..55842af867 100644 --- a/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs @@ -163,10 +163,10 @@ namespace osu.Game.Tests.Visual public bool Ready; [BackgroundDependencyLoader] - private void load() + private void load(CancellationToken token) { // Never finish loading - while (!Ready) + while (!Ready && !token.IsCancellationRequested) Thread.Sleep(1); } } From be62cd9d72c30b2db6ac8252ccd2940b4bbaa2a4 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 27 Mar 2019 14:33:50 +0900 Subject: [PATCH 257/623] Fix TestCaseBackgroundsScreenBeatmap --- .../TestCaseBackgroundScreenBeatmap.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs index e56156752b..94c659424c 100644 --- a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.Background public void PlayerLoaderSettingsHoverTest() { setupUserSettings(); - AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer()))); + AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer { Ready = false }))); AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false); AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent()); AddStep("Trigger background preview", () => @@ -255,14 +255,7 @@ namespace osu.Game.Tests.Visual.Background { setupUserSettings(); - AddStep("Start player loader", () => - { - songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer - { - AllowPause = allowPause, - Ready = true, - })); - }); + AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer { AllowPause = allowPause, }))); AddUntilStep("Wait for Player Loader to load", () => playerLoader.IsLoaded); AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); AddUntilStep("Wait for player to load", () => player.IsLoaded); @@ -351,7 +344,7 @@ namespace osu.Game.Tests.Visual.Background public UserDimContainer CurrentStoryboardContainer => StoryboardContainer; // Whether or not the player should be allowed to load. - public bool Ready; + public bool Ready = true; public Bindable StoryboardEnabled; public readonly Bindable ReplacesBackground = new Bindable(); @@ -362,10 +355,11 @@ namespace osu.Game.Tests.Visual.Background public bool IsStoryboardInvisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha <= 1; [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, CancellationToken token) { - while (!Ready) + while (!Ready && !token.IsCancellationRequested) Thread.Sleep(1); + StoryboardEnabled = config.GetBindable(OsuSetting.ShowStoryboard); ReplacesBackground.BindTo(Background.StoryboardReplacesBackground); DrawableRuleset.IsPaused.BindTo(IsPaused); From a5d5f469eba3420de0a06314a7617e84b3cee16a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Mar 2019 14:52:56 +0900 Subject: [PATCH 258/623] Populate more hit results for catch --- osu.Game/Scoring/Legacy/LegacyScoreParser.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs index ace8892330..ebe7bf8219 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -71,7 +71,10 @@ namespace osu.Game.Scoring.Legacy score.ScoreInfo.Statistics[HitResult.Miss] = countMiss; break; case 2: - score.ScoreInfo.Statistics[HitResult.Perfect] = count300; + score.ScoreInfo.Statistics[HitResult.Great] = count300; + score.ScoreInfo.Statistics[HitResult.Good] = count100; + score.ScoreInfo.Statistics[HitResult.Ok] = countKatu; + score.ScoreInfo.Statistics[HitResult.Meh] = count50; score.ScoreInfo.Statistics[HitResult.Miss] = countMiss; break; case 3: From 1a6c2022ea1e6ad6d012a36505d2261a405a9dbc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Mar 2019 14:53:29 +0900 Subject: [PATCH 259/623] Fix up/adjust counts --- .../Difficulty/CatchPerformanceCalculator.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index b5262d02f5..029f1bed9b 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -20,17 +20,21 @@ namespace osu.Game.Rulesets.Catch.Difficulty private Mod[] mods; private int countGreat; private int countGood; + private int countKatu; private int countMeh; private int countMiss; public CatchPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) - : base(ruleset, beatmap, score) { } + : base(ruleset, beatmap, score) + { + } public override double Calculate(Dictionary categoryDifficulty = null) { mods = Score.Mods; countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]); countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]); + countKatu = Convert.ToInt32(Score.Statistics[HitResult.Ok]); countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]); countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]); @@ -88,8 +92,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty } private float accuracy() => totalHits() == 0 ? 0 : MathHelper.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f); - private int totalHits() => countMeh + countGood + countGreat + countMiss; + private int totalHits() => countMeh + countGood + countGreat + countMiss + countKatu; private int totalSuccessfulHits() => countMeh + countGood + countGreat; - private int totalComboHits() => countMeh + countGood + countGreat; + private int totalComboHits() => countMiss + countGood + countGreat; } } From 8fcb75809d7f8b747a104bc72f70da22b39cedd8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Mar 2019 16:55:46 +0900 Subject: [PATCH 260/623] Add LegacyScoreInfo for statistics preservation/conversion --- .../Difficulty/CatchPerformanceCalculator.cs | 33 ++--- osu.Game/Scoring/Legacy/LegacyScoreInfo.cs | 118 ++++++++++++++++++ osu.Game/Scoring/Legacy/LegacyScoreParser.cs | 62 +++------ 3 files changed, 154 insertions(+), 59 deletions(-) create mode 100644 osu.Game/Scoring/Legacy/LegacyScoreInfo.cs diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 029f1bed9b..ebf5c265d5 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Scoring.Legacy; using osuTK; namespace osu.Game.Rulesets.Catch.Difficulty @@ -18,11 +19,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty protected new CatchDifficultyAttributes Attributes => (CatchDifficultyAttributes)base.Attributes; private Mod[] mods; - private int countGreat; - private int countGood; - private int countKatu; - private int countMeh; - private int countMiss; + + private int fruitsHit; + private int ticksHit; + private int tinyTicksHit; + private int tinyTicksMissed; + private int misses; public CatchPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) : base(ruleset, beatmap, score) @@ -32,11 +34,14 @@ namespace osu.Game.Rulesets.Catch.Difficulty public override double Calculate(Dictionary categoryDifficulty = null) { mods = Score.Mods; - countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]); - countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]); - countKatu = Convert.ToInt32(Score.Statistics[HitResult.Ok]); - countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]); - countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]); + + var legacyScore = Score as LegacyScoreInfo; + + fruitsHit = legacyScore?.Count300 ?? Score.Statistics[HitResult.Perfect]; + ticksHit = legacyScore?.Count100 ?? 0; + tinyTicksHit = legacyScore?.Count50 ?? 0; + tinyTicksMissed = legacyScore?.CountKatu ?? 0; + misses = Score.Statistics[HitResult.Miss]; // Don't count scores made with supposedly unranked mods if (mods.Any(m => !m.Ranked)) @@ -57,7 +62,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty value *= lengthBonus; // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available - value *= Math.Pow(0.97f, countMiss); + value *= Math.Pow(0.97f, misses); // Combo scaling float beatmapMaxCombo = Attributes.MaxCombo; @@ -92,8 +97,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty } private float accuracy() => totalHits() == 0 ? 0 : MathHelper.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f); - private int totalHits() => countMeh + countGood + countGreat + countMiss + countKatu; - private int totalSuccessfulHits() => countMeh + countGood + countGreat; - private int totalComboHits() => countMiss + countGood + countGreat; + private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed; + private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit; + private int totalComboHits() => misses + ticksHit + fruitsHit; } } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs b/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs new file mode 100644 index 0000000000..470dc9598e --- /dev/null +++ b/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs @@ -0,0 +1,118 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Scoring.Legacy +{ + public class LegacyScoreInfo : ScoreInfo + { + private int countGeki; + + public int CountGeki + { + get => countGeki; + set + { + countGeki = value; + + switch (Ruleset?.ID ?? RulesetID) + { + case 3: + Statistics[HitResult.Perfect] = value; + break; + } + } + } + + private int count300; + + public int Count300 + { + get => count300; + set + { + count300 = value; + + switch (Ruleset?.ID ?? RulesetID) + { + case 0: + case 1: + case 3: + Statistics[HitResult.Great] = value; + break; + case 2: + Statistics[HitResult.Perfect] = value; + break; + } + } + } + + private int countKatu; + + public int CountKatu + { + get => countKatu; + set + { + countKatu = value; + + switch (Ruleset?.ID ?? RulesetID) + { + case 3: + Statistics[HitResult.Good] = value; + break; + } + } + } + + private int count100; + + public int Count100 + { + get => count100; + set + { + count100 = value; + + switch (Ruleset?.ID ?? RulesetID) + { + case 0: + case 1: + case 2: + Statistics[HitResult.Good] = value; + break; + case 3: + Statistics[HitResult.Ok] = value; + break; + } + } + } + + private int count50; + + public int Count50 + { + get => count50; + set + { + count50 = value; + + switch (Ruleset?.ID ?? RulesetID) + { + case 0: + case 2: + case 3: + Statistics[HitResult.Meh] = value; + break; + } + } + } + + public int CountMiss + { + get => Statistics[HitResult.Miss]; + set => Statistics[HitResult.Miss] = value; + } + } +} diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs index ebe7bf8219..c1ca4ee17f 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -34,7 +34,9 @@ namespace osu.Game.Scoring.Legacy using (SerializationReader sr = new SerializationReader(stream)) { currentRuleset = GetRuleset(sr.ReadByte()); - score.ScoreInfo = new ScoreInfo { Ruleset = currentRuleset.RulesetInfo }; + var scoreInfo = new LegacyScoreInfo { Ruleset = currentRuleset.RulesetInfo }; + + scoreInfo = scoreInfo; var version = sr.ReadInt32(); @@ -43,69 +45,39 @@ namespace osu.Game.Scoring.Legacy throw new BeatmapNotFoundException(); currentBeatmap = workingBeatmap.Beatmap; - score.ScoreInfo.Beatmap = currentBeatmap.BeatmapInfo; + scoreInfo.Beatmap = currentBeatmap.BeatmapInfo; - score.ScoreInfo.User = new User { Username = sr.ReadString() }; + scoreInfo.User = new User { Username = sr.ReadString() }; // MD5Hash sr.ReadString(); - var count300 = (int)sr.ReadUInt16(); - var count100 = (int)sr.ReadUInt16(); - var count50 = (int)sr.ReadUInt16(); - var countGeki = (int)sr.ReadUInt16(); - var countKatu = (int)sr.ReadUInt16(); - var countMiss = (int)sr.ReadUInt16(); + scoreInfo.Count300 = sr.ReadUInt16(); + scoreInfo.Count100 = sr.ReadUInt16(); + scoreInfo.Count50 = sr.ReadUInt16(); + scoreInfo.CountGeki = sr.ReadUInt16(); + scoreInfo.CountKatu = sr.ReadUInt16(); + scoreInfo.CountMiss = sr.ReadUInt16(); - switch (currentRuleset.LegacyID) - { - case 0: - score.ScoreInfo.Statistics[HitResult.Great] = count300; - score.ScoreInfo.Statistics[HitResult.Good] = count100; - score.ScoreInfo.Statistics[HitResult.Meh] = count50; - score.ScoreInfo.Statistics[HitResult.Miss] = countMiss; - break; - case 1: - score.ScoreInfo.Statistics[HitResult.Great] = count300; - score.ScoreInfo.Statistics[HitResult.Good] = count100; - score.ScoreInfo.Statistics[HitResult.Miss] = countMiss; - break; - case 2: - score.ScoreInfo.Statistics[HitResult.Great] = count300; - score.ScoreInfo.Statistics[HitResult.Good] = count100; - score.ScoreInfo.Statistics[HitResult.Ok] = countKatu; - score.ScoreInfo.Statistics[HitResult.Meh] = count50; - score.ScoreInfo.Statistics[HitResult.Miss] = countMiss; - break; - case 3: - score.ScoreInfo.Statistics[HitResult.Perfect] = countGeki; - score.ScoreInfo.Statistics[HitResult.Great] = count300; - score.ScoreInfo.Statistics[HitResult.Good] = countKatu; - score.ScoreInfo.Statistics[HitResult.Ok] = count100; - score.ScoreInfo.Statistics[HitResult.Meh] = count50; - score.ScoreInfo.Statistics[HitResult.Miss] = countMiss; - break; - } - - score.ScoreInfo.TotalScore = sr.ReadInt32(); - score.ScoreInfo.MaxCombo = sr.ReadUInt16(); + scoreInfo.TotalScore = sr.ReadInt32(); + scoreInfo.MaxCombo = sr.ReadUInt16(); /* score.Perfect = */ sr.ReadBoolean(); - score.ScoreInfo.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); + scoreInfo.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); /* score.HpGraphString = */ sr.ReadString(); - score.ScoreInfo.Date = sr.ReadDateTime(); + scoreInfo.Date = sr.ReadDateTime(); var compressedReplay = sr.ReadByteArray(); if (version >= 20140721) - score.ScoreInfo.OnlineScoreID = sr.ReadInt64(); + scoreInfo.OnlineScoreID = sr.ReadInt64(); else if (version >= 20121008) - score.ScoreInfo.OnlineScoreID = sr.ReadInt32(); + scoreInfo.OnlineScoreID = sr.ReadInt32(); if (compressedReplay?.Length > 0) { From 5c8e8a16974c717d3b4f452cc14c7c34cd12db55 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Mar 2019 16:56:15 +0900 Subject: [PATCH 261/623] Fix license header --- .../Difficulty/CatchPerformanceCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index ebf5c265d5..5a640f6d1a 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; From 977122d05f4e894be1773b912f5ff71d2872bcc3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Mar 2019 16:59:29 +0900 Subject: [PATCH 262/623] Fix ScoreInfo not getting set --- osu.Game/Scoring/Legacy/LegacyScoreParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs index c1ca4ee17f..51810945db 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -36,7 +36,7 @@ namespace osu.Game.Scoring.Legacy currentRuleset = GetRuleset(sr.ReadByte()); var scoreInfo = new LegacyScoreInfo { Ruleset = currentRuleset.RulesetInfo }; - scoreInfo = scoreInfo; + score.ScoreInfo = scoreInfo; var version = sr.ReadInt32(); From eceecde0f8e70f24f54c2de2a42b974a79609f18 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Mar 2019 17:07:18 +0900 Subject: [PATCH 263/623] Make APIScoreInfo derive LegacyScoreInfo --- osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index ded4ca71ee..0085fff7b9 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -8,12 +8,12 @@ using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; +using osu.Game.Scoring.Legacy; using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses { - public class APIScoreInfo : ScoreInfo + public class APIScoreInfo : LegacyScoreInfo { [JsonProperty(@"score")] private int totalScore @@ -96,7 +96,11 @@ namespace osu.Game.Online.API.Requests.Responses } [JsonProperty(@"mode_int")] - public int OnlineRulesetID { get; set; } + public int OnlineRulesetID + { + get => RulesetID; + set => RulesetID = value; + } [JsonProperty(@"mods")] private string[] modStrings { get; set; } From d2af2cf21d88abcf44f613a010df93fd4b55f4b0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Mar 2019 17:08:01 +0900 Subject: [PATCH 264/623] Set legacy api score statistics directly --- .../API/Requests/Responses/APIScoreInfo.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index 0085fff7b9..ef893dfcb7 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -74,23 +74,27 @@ namespace osu.Game.Online.API.Requests.Responses HitResult newKey; switch (kvp.Key) { + case @"count_geki": + CountGeki = kvp.Value; + break; case @"count_300": - newKey = HitResult.Great; + Count300 = kvp.Value; + break; + case @"count_katu": + CountKatu = kvp.Value; break; case @"count_100": - newKey = HitResult.Good; + Count100 = kvp.Value; break; case @"count_50": - newKey = HitResult.Meh; + Count50 = kvp.Value; break; case @"count_miss": - newKey = HitResult.Miss; + CountMiss = kvp.Value; break; default: continue; } - - Statistics.Add(newKey, kvp.Value); } } } From 012dcc25c923254b5d8895314529663621ebfc40 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Mar 2019 17:08:40 +0900 Subject: [PATCH 265/623] Rename APIScoreInfo -> APILegacyScoreInfo --- .../TestCaseBeatmapScoresContainer.cs | 27 +++++++++---------- .../Online/API/Requests/GetScoresRequest.cs | 6 ++--- .../API/Requests/GetUserScoresRequest.cs | 2 +- ...{APIScoreInfo.cs => APILegacyScoreInfo.cs} | 2 +- .../{APIScores.cs => APILegacyScores.cs} | 4 +-- .../BeatmapSet/Scores/DrawableScore.cs | 4 +-- .../BeatmapSet/Scores/DrawableTopScore.cs | 5 ++-- .../BeatmapSet/Scores/ScoresContainer.cs | 6 ++--- 8 files changed, 27 insertions(+), 29 deletions(-) rename osu.Game/Online/API/Requests/Responses/{APIScoreInfo.cs => APILegacyScoreInfo.cs} (98%) rename osu.Game/Online/API/Requests/Responses/{APIScores.cs => APILegacyScores.cs} (77%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs index c7970b6ebb..8de6cc2a88 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -44,9 +43,9 @@ namespace osu.Game.Tests.Visual.SongSelect } }; - IEnumerable scores = new[] + IEnumerable scores = new[] { - new APIScoreInfo + new ScoreInfo { User = new User { @@ -69,7 +68,7 @@ namespace osu.Game.Tests.Visual.SongSelect TotalScore = 1234567890, Accuracy = 1, }, - new APIScoreInfo + new ScoreInfo { User = new User { @@ -91,7 +90,7 @@ namespace osu.Game.Tests.Visual.SongSelect TotalScore = 1234789, Accuracy = 0.9997, }, - new APIScoreInfo + new ScoreInfo { User = new User { @@ -112,7 +111,7 @@ namespace osu.Game.Tests.Visual.SongSelect TotalScore = 12345678, Accuracy = 0.9854, }, - new APIScoreInfo + new ScoreInfo { User = new User { @@ -132,7 +131,7 @@ namespace osu.Game.Tests.Visual.SongSelect TotalScore = 1234567, Accuracy = 0.8765, }, - new APIScoreInfo + new ScoreInfo { User = new User { @@ -157,9 +156,9 @@ namespace osu.Game.Tests.Visual.SongSelect s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); } - IEnumerable anotherScores = new[] + IEnumerable anotherScores = new[] { - new APIScoreInfo + new ScoreInfo { User = new User { @@ -181,7 +180,7 @@ namespace osu.Game.Tests.Visual.SongSelect TotalScore = 1234789, Accuracy = 0.9997, }, - new APIScoreInfo + new ScoreInfo { User = new User { @@ -204,7 +203,7 @@ namespace osu.Game.Tests.Visual.SongSelect TotalScore = 1234567890, Accuracy = 1, }, - new APIScoreInfo + new ScoreInfo { User = new User { @@ -220,7 +219,7 @@ namespace osu.Game.Tests.Visual.SongSelect TotalScore = 123456, Accuracy = 0.6543, }, - new APIScoreInfo + new ScoreInfo { User = new User { @@ -241,7 +240,7 @@ namespace osu.Game.Tests.Visual.SongSelect TotalScore = 12345678, Accuracy = 0.9854, }, - new APIScoreInfo + new ScoreInfo { User = new User { @@ -269,7 +268,7 @@ namespace osu.Game.Tests.Visual.SongSelect s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); } - var topScoreInfo = new APIScoreInfo + var topScoreInfo = new ScoreInfo { User = new User { diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index f6be849b56..0b6f65a0e0 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -10,7 +10,7 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetScoresRequest : APIRequest + public class GetScoresRequest : APIRequest { private readonly BeatmapInfo beatmap; private readonly BeatmapLeaderboardScope scope; @@ -31,9 +31,9 @@ namespace osu.Game.Online.API.Requests Success += onSuccess; } - private void onSuccess(APIScores r) + private void onSuccess(APILegacyScores r) { - foreach (APIScoreInfo score in r.Scores) + foreach (APILegacyScoreInfo score in r.Scores) score.Beatmap = beatmap; } diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index c5b436f99c..48a43bbbad 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -6,7 +6,7 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetUserScoresRequest : APIRequest> + public class GetUserScoresRequest : APIRequest> { private readonly long userId; private readonly ScoreType type; diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs similarity index 98% rename from osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs rename to osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index ef893dfcb7..8ee71ce9ac 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -13,7 +13,7 @@ using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses { - public class APIScoreInfo : LegacyScoreInfo + public class APILegacyScoreInfo : LegacyScoreInfo { [JsonProperty(@"score")] private int totalScore diff --git a/osu.Game/Online/API/Requests/Responses/APIScores.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs similarity index 77% rename from osu.Game/Online/API/Requests/Responses/APIScores.cs rename to osu.Game/Online/API/Requests/Responses/APILegacyScores.cs index a867d86d9b..15ec5d3b13 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScores.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs @@ -6,9 +6,9 @@ using Newtonsoft.Json; namespace osu.Game.Online.API.Requests.Responses { - public class APIScores + public class APILegacyScores { [JsonProperty(@"scores")] - public IEnumerable Scores; + public IEnumerable Scores; } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index c6c8315aeb..e3fb1bc961 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -9,12 +9,12 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Overlays.BeatmapSet.Scores @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly Box background; - public DrawableScore(int index, APIScoreInfo score) + public DrawableScore(int index, ScoreInfo score) { ScoreModsContainer modsContainer; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 78e560cdbe..ac4485a410 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Mods; @@ -43,9 +42,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly InfoColumn statistics; private readonly ScoreModsContainer modsContainer; - private APIScoreInfo score; + private ScoreInfo score; - public APIScoreInfo Score + public ScoreInfo Score { get => score; set diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 3dd03fcea6..ef3129441b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -11,7 +11,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; +using osu.Game.Scoring; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -29,10 +29,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores set => loadingAnimation.FadeTo(value ? 1 : 0, fade_duration); } - private IEnumerable scores; + private IEnumerable scores; private BeatmapInfo beatmap; - public IEnumerable Scores + public IEnumerable Scores { get => scores; set From ca7a20585dbb58b09b7426949e0ad4b28543efd5 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 27 Mar 2019 17:28:53 +0900 Subject: [PATCH 266/623] Apply reviews, delete playerloader test --- ...iner.cs => TestCaseLogoFacadeContainer.cs} | 72 +++---------- .../Graphics/Containers/FacadeContainer.cs | 102 ------------------ .../Containers/LogoFacadeContainer.cs | 100 +++++++++++++++++ osu.Game/Screens/Menu/ButtonSystem.cs | 25 +++-- osu.Game/Screens/Play/PlayerLoader.cs | 70 ++++++------ 5 files changed, 161 insertions(+), 208 deletions(-) rename osu.Game.Tests/Visual/{TestCaseFacadeContainer.cs => TestCaseLogoFacadeContainer.cs} (60%) delete mode 100644 osu.Game/Graphics/Containers/FacadeContainer.cs create mode 100644 osu.Game/Graphics/Containers/LogoFacadeContainer.cs diff --git a/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs similarity index 60% rename from osu.Game.Tests/Visual/TestCaseFacadeContainer.cs rename to osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs index 55842af867..0e7f4d4bc0 100644 --- a/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs @@ -9,7 +9,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; @@ -24,14 +23,14 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual { - public class TestCaseFacadeContainer : ScreenTestCase + public class TestCaseLogoFacadeContainer : ScreenTestCase { public override IReadOnlyList RequiredTypes => new[] { typeof(PlayerLoader), typeof(Player), - typeof(Facade), - typeof(FacadeContainer), + typeof(LogoFacadeContainer.Facade), + typeof(LogoFacadeContainer), typeof(ButtonSystem), typeof(ButtonSystemState), typeof(Menu), @@ -43,7 +42,7 @@ namespace osu.Game.Tests.Visual private readonly Bindable uiScale = new Bindable(); - public TestCaseFacadeContainer() + public TestCaseLogoFacadeContainer() { Add(logo = new OsuLogo()); } @@ -63,20 +62,7 @@ namespace osu.Game.Tests.Visual AddStep("Move facade to random position", () => LoadScreen(new TestScreen(randomPositions))); } - [Test] - public void PlayerLoaderTest() - { - AddToggleStep("Toggle mods", b => { Beatmap.Value.Mods.Value = b ? Beatmap.Value.Mods.Value.Concat(new[] { new OsuModNoFail() }) : Enumerable.Empty(); }); - AddStep("Add new playerloader", () => LoadScreen(new TestPlayerLoader(() => new TestPlayer - { - AllowPause = false, - AllowLeadIn = false, - AllowResults = false, - Ready = false - }))); - } - - private class TestFacadeContainer : FacadeContainer + private class TestLogoFacadeContainer : LogoFacadeContainer { protected override Facade CreateFacade() => new Facade { @@ -92,8 +78,8 @@ namespace osu.Game.Tests.Visual private class TestScreen : OsuScreen { - private TestFacadeContainer facadeContainer; - private Facade facadeFlowComponent; + private TestLogoFacadeContainer logoFacadeContainer; + private LogoFacadeContainer.Facade facadeFlowComponent; private readonly bool randomPositions; public TestScreen(bool randomPositions = false) @@ -104,8 +90,8 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { - InternalChild = facadeContainer = new TestFacadeContainer(); - facadeContainer.Child = facadeFlowComponent = facadeContainer.Facade; + InternalChild = logoFacadeContainer = new TestLogoFacadeContainer(); + logoFacadeContainer.Child = facadeFlowComponent = logoFacadeContainer.LogoFacade; } protected override void LogoArriving(OsuLogo logo, bool resuming) @@ -113,15 +99,15 @@ namespace osu.Game.Tests.Visual base.LogoArriving(logo, resuming); logo.FadeIn(350); logo.ScaleTo(new Vector2(0.15f), 350, Easing.In); - facadeContainer.SetLogo(logo, 0.3f, 1000, Easing.InOutQuint); - facadeContainer.Tracking = true; + logoFacadeContainer.SetLogo(logo, 0.3f, 1000, Easing.InOutQuint); + logoFacadeContainer.Tracking = true; moveLogoFacade(); } protected override void LogoExiting(OsuLogo logo) { base.LogoExiting(logo); - facadeContainer.Tracking = false; + logoFacadeContainer.Tracking = false; } private void moveLogoFacade() @@ -136,39 +122,5 @@ namespace osu.Game.Tests.Visual Schedule(moveLogoFacade); } } - - private class FacadeFlowComponent : FillFlowContainer - { - [BackgroundDependencyLoader] - private void load(Facade facade) - { - facade.Anchor = Anchor.TopCentre; - facade.Origin = Anchor.TopCentre; - Child = facade; - } - } - - private class TestPlayerLoader : PlayerLoader - { - public TestPlayerLoader(Func player) - : base(player) - { - } - - protected override FacadeContainer CreateFacadeContainer() => new TestFacadeContainer(); - } - - private class TestPlayer : Player - { - public bool Ready; - - [BackgroundDependencyLoader] - private void load(CancellationToken token) - { - // Never finish loading - while (!Ready && !token.IsCancellationRequested) - Thread.Sleep(1); - } - } } } diff --git a/osu.Game/Graphics/Containers/FacadeContainer.cs b/osu.Game/Graphics/Containers/FacadeContainer.cs deleted file mode 100644 index 0ec5e77c72..0000000000 --- a/osu.Game/Graphics/Containers/FacadeContainer.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) ppy Pty Ltd . 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.Framework.MathUtils; -using osu.Game.Screens.Menu; -using osuTK; - -namespace osu.Game.Graphics.Containers -{ - /// - /// A container that creates a to be used by its children. - /// This container also updates the position and size of the Facade, and contains logic for tracking an on the Facade's position. - /// - public class FacadeContainer : Container - { - protected virtual Facade CreateFacade() => new Facade(); - - public Facade Facade { get; } - - /// - /// Whether or not the logo assigned to this FacadeContainer should be tracking the position its facade. - /// - public bool Tracking; - - private OsuLogo logo; - private float facadeScale; - private Vector2 startPosition; - private Easing easing; - private double startTime; - private double duration; - - public FacadeContainer() - { - Facade = CreateFacade(); - } - - /// - /// Assign the logo that should track the Facade's position, as well as how it should transform to its initial position. - /// - /// The instance of the logo to be used for tracking. - /// The scale of the facade. - /// The duration of the initial transform. Default is instant. - /// The easing type of the initial transform. - public void SetLogo(OsuLogo logo, float facadeScale, double duration = 0, Easing easing = Easing.None) - { - if (logo != null) - { - this.logo = logo; - } - - this.facadeScale = facadeScale; - this.duration = duration; - this.easing = easing; - } - - private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(Facade.ScreenSpaceDrawQuad.Centre); - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - if (logo == null || !Tracking) - return; - - Facade.Size = new Vector2(logo.SizeForFlow * facadeScale); - - if (Facade.IsLoaded && logo.Position != logoTrackingPosition) - { - // Required for the correct position of the logo to be set with respect to logoTrackingPosition - logo.RelativePositionAxes = Axes.None; - - // If this is our first update since tracking has started, initialize our starting values for interpolation - if (startTime == 0) - { - startTime = Time.Current; - startPosition = logo.Position; - } - - var endTime = startTime + duration; - var remainingDuration = endTime - Time.Current; - - // If our transform should be instant, our position should already be at logoTrackingPosition, thus set the blend to 0. - // If we are already past when the transform should be finished playing, set the blend to 0 so that the logo is always at the position of the facade. - var blend = duration > 0 && remainingDuration > 0 - ? (float)Interpolation.ApplyEasing(easing, remainingDuration / duration) - : 0; - - // Interpolate the position of the logo, where blend 0 is the position of the Facade, and blend 1 is where the logo was when it first began interpolating. - logo.Position = Vector2.Lerp(logoTrackingPosition, startPosition, blend); - } - } - } -} - -/// -/// A placeholder container that serves as a dummy object to denote another object's location and size. -/// -public class Facade : Container -{ -} diff --git a/osu.Game/Graphics/Containers/LogoFacadeContainer.cs b/osu.Game/Graphics/Containers/LogoFacadeContainer.cs new file mode 100644 index 0000000000..a556fa697c --- /dev/null +++ b/osu.Game/Graphics/Containers/LogoFacadeContainer.cs @@ -0,0 +1,100 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.MathUtils; +using osu.Game.Screens.Menu; +using osuTK; + +namespace osu.Game.Graphics.Containers +{ + /// + /// A container that creates a to be used to update and track the position of an . + /// + public class LogoFacadeContainer : Container + { + protected virtual Facade CreateFacade() => new Facade(); + + public Facade LogoFacade { get; } + + /// + /// Whether or not the logo assigned to this FacadeContainer should be tracking the position its facade. + /// + public bool Tracking = false; + + private OsuLogo logo; + private float facadeScale; + private Vector2 startPosition; + private Easing easing; + private double? startTime; + private double duration; + + public LogoFacadeContainer() + { + LogoFacade = CreateFacade(); + } + + /// + /// Assign the logo that should track the Facade's position, as well as how it should transform to its initial position. + /// + /// The instance of the logo to be used for tracking. + /// The scale of the facade. Does not actually affect the logo itself. + /// The duration of the initial transform. Default is instant. + /// The easing type of the initial transform. + public void SetLogo(OsuLogo logo, float facadeScale, double duration = 0, Easing easing = Easing.None) + { + this.logo = logo ?? throw new ArgumentNullException(nameof(logo)); + this.facadeScale = facadeScale; + this.duration = duration; + this.easing = easing; + } + + private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre); + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + if (logo == null || !Tracking) + return; + + LogoFacade.Size = new Vector2(logo.SizeForFlow * facadeScale); + + if (LogoFacade.Parent != null && logo.Position != logoTrackingPosition) + { + // Required for the correct position of the logo to be set with respect to logoTrackingPosition + logo.RelativePositionAxes = Axes.None; + + // If this is our first update since tracking has started, initialize our starting values for interpolation + if (startTime == null) + { + startTime = Time.Current; + startPosition = logo.Position; + } + + if (duration != 0) + { + double elapsedDuration = Time.Current - startTime ?? 0; + + var mount = (float)Interpolation.ApplyEasing(easing, Math.Min(elapsedDuration / duration, 1)); + + // Interpolate the position of the logo, where mount 0 is where the logo was when it first began interpolating, and mount 1 is the target location. + logo.Position = Vector2.Lerp(startPosition, logoTrackingPosition, mount); + } + else + { + logo.Position = logoTrackingPosition; + } + } + } + + /// + /// A placeholder container that serves as a dummy object to denote another object's location and size. + /// + public class Facade : Container + { + } + } +} diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 7b458809b1..3fad36cddb 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -48,6 +48,10 @@ namespace osu.Game.Screens.Menu private OsuLogo logo; + /// + /// Assign the that this ButtonSystem should manage the position of. + /// + /// The instance of the logo to be assigned. If null, we are suspending from the screen that uses this ButtonSystem. public void SetOsuLogo(OsuLogo logo) { this.logo = logo; @@ -55,7 +59,7 @@ namespace osu.Game.Screens.Menu if (this.logo != null) { this.logo.Action = onOsuLogo; - facadeContainer.SetLogo(logo, 0.5f); + logoFacadeContainer.SetLogo(logo, 0.5f); // osuLogo.SizeForFlow relies on loading to be complete. buttonArea.Flow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + this.logo.SizeForFlow / 4), 0); @@ -64,9 +68,8 @@ namespace osu.Game.Screens.Menu } else { - // If logo is null, we are suspending from the screen that uses this ButtonSystem. // We should stop tracking as the facade is now out of scope. - facadeContainer.Tracking = false; + logoFacadeContainer.Tracking = false; } } @@ -79,13 +82,13 @@ namespace osu.Game.Screens.Menu private SampleChannel sampleBack; - private readonly FacadeContainer facadeContainer; + private readonly LogoFacadeContainer logoFacadeContainer; public ButtonSystem() { RelativeSizeAxes = Axes.Both; - Child = facadeContainer = new FacadeContainer + Child = logoFacadeContainer = new LogoFacadeContainer { RelativeSizeAxes = Axes.Both, Child = buttonArea = new ButtonArea() @@ -98,10 +101,10 @@ namespace osu.Game.Screens.Menu { VisibleState = ButtonSystemState.Play, }, - facadeContainer.Facade + logoFacadeContainer.LogoFacade }); - buttonArea.Flow.CentreTarget = facadeContainer.Facade; + buttonArea.Flow.CentreTarget = logoFacadeContainer.LogoFacade; } [Resolved(CanBeNull = true)] @@ -267,7 +270,7 @@ namespace osu.Game.Screens.Menu logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => { - facadeContainer.Tracking = false; + logoFacadeContainer.Tracking = false; game?.Toolbar.Hide(); @@ -295,7 +298,7 @@ namespace osu.Game.Screens.Menu logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => { - facadeContainer.Tracking = true; + logoFacadeContainer.Tracking = true; if (impact) logo.Impact(); @@ -305,14 +308,14 @@ namespace osu.Game.Screens.Menu break; default: logo.ClearTransforms(targetMember: nameof(Position)); - facadeContainer.Tracking = true; + logoFacadeContainer.Tracking = true; logo.ScaleTo(0.5f, 200, Easing.OutQuint); break; } break; case ButtonSystemState.EnteringMode: - facadeContainer.Tracking = true; + logoFacadeContainer.Tracking = true; break; } } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 4601cf71e0..6ac2c8220f 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -33,9 +33,7 @@ namespace osu.Game.Screens.Play private Player player; - private FacadeContainer facadeContainer; - - protected virtual FacadeContainer CreateFacadeContainer() => new FacadeContainer(); + private LogoFacadeContainer content; private BeatmapMetadataDisplay info; @@ -62,30 +60,32 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { - InternalChild = facadeContainer = CreateFacadeContainer(); - facadeContainer.Anchor = Anchor.Centre; - facadeContainer.Origin = Anchor.Centre; - facadeContainer.RelativeSizeAxes = Axes.Both; - facadeContainer.Children = new Drawable[] + InternalChild = content = new LogoFacadeContainer { - info = new BeatmapMetadataDisplay(Beatmap.Value, facadeContainer.Facade) + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Alpha = 0, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20), - Margin = new MarginPadding(25), - Children = new PlayerSettingsGroup[] + info = new BeatmapMetadataDisplay(Beatmap.Value, content.LogoFacade) { - VisualSettings = new VisualSettings(), - new InputSettings() + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Margin = new MarginPadding(25), + Children = new PlayerSettingsGroup[] + { + VisualSettings = new VisualSettings(), + new InputSettings() + } } } }; @@ -122,21 +122,21 @@ namespace osu.Game.Screens.Play private void contentIn() { - facadeContainer.ScaleTo(1, 650, Easing.OutQuint); - facadeContainer.FadeInFromZero(400); + content.ScaleTo(1, 650, Easing.OutQuint); + content.FadeInFromZero(400); } private void contentOut() { - facadeContainer.ScaleTo(0.7f, 300, Easing.InQuint); - facadeContainer.FadeOut(250); + content.ScaleTo(0.7f, 300, Easing.InQuint); + content.FadeOut(250); } public override void OnEntering(IScreen last) { base.OnEntering(last); - facadeContainer.ScaleTo(0.7f); + content.ScaleTo(0.7f); Background?.FadeColour(Color4.White, 800, Easing.OutQuint); contentIn(); @@ -155,15 +155,15 @@ namespace osu.Game.Screens.Play logo.MoveTo(new Vector2(0.5f), duration, Easing.In); logo.FadeIn(350); - facadeContainer.SetLogo(logo, 0.3f, 500, Easing.InOutQuint); + content.SetLogo(logo, 0.3f, 500, Easing.InOutExpo); - Scheduler.AddDelayed(() => facadeContainer.Tracking = true, duration); + Scheduler.AddDelayed(() => content.Tracking = true, resuming ? 0 : 500); } protected override void LogoExiting(OsuLogo logo) { base.LogoExiting(logo); - facadeContainer.Tracking = false; + content.Tracking = false; } protected override void LoadComplete() @@ -238,7 +238,7 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { - facadeContainer.ScaleTo(0.7f, 150, Easing.InQuint); + content.ScaleTo(0.7f, 150, Easing.InQuint); this.FadeOut(150); cancelLoad(); @@ -310,7 +310,7 @@ namespace osu.Game.Screens.Play } private readonly WorkingBeatmap beatmap; - private readonly Facade facade; + private readonly LogoFacadeContainer.Facade facade; private LoadingAnimation loading; private Sprite backgroundSprite; private ModDisplay modDisplay; @@ -332,7 +332,7 @@ namespace osu.Game.Screens.Play } } - public BeatmapMetadataDisplay(WorkingBeatmap beatmap, Facade facade) + public BeatmapMetadataDisplay(WorkingBeatmap beatmap, LogoFacadeContainer.Facade facade) { this.beatmap = beatmap; this.facade = facade; From 34a33b335d1e5046cd1cc5f9712f5994753d8fc9 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 27 Mar 2019 17:29:38 +0900 Subject: [PATCH 267/623] oops --- osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs index 0e7f4d4bc0..152b9b1706 100644 --- a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Threading; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -13,8 +11,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; using osu.Game.Graphics.Containers; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; From 2c059efbab66a0641f93f7e46f931002ab438ec6 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 27 Mar 2019 17:34:58 +0900 Subject: [PATCH 268/623] Rename to BlockLoad --- .../Visual/Background/TestCaseBackgroundScreenBeatmap.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs index 94c659424c..420a52c6b7 100644 --- a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.Background public void PlayerLoaderSettingsHoverTest() { setupUserSettings(); - AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer { Ready = false }))); + AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer { BlockLoad = true }))); AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false); AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent()); AddStep("Trigger background preview", () => @@ -344,7 +344,7 @@ namespace osu.Game.Tests.Visual.Background public UserDimContainer CurrentStoryboardContainer => StoryboardContainer; // Whether or not the player should be allowed to load. - public bool Ready = true; + public bool BlockLoad; public Bindable StoryboardEnabled; public readonly Bindable ReplacesBackground = new Bindable(); @@ -357,7 +357,7 @@ namespace osu.Game.Tests.Visual.Background [BackgroundDependencyLoader] private void load(OsuConfigManager config, CancellationToken token) { - while (!Ready && !token.IsCancellationRequested) + while (BlockLoad && !token.IsCancellationRequested) Thread.Sleep(1); StoryboardEnabled = config.GetBindable(OsuSetting.ShowStoryboard); From 2e3791be1ca9e67676e4b5b42be184cd7c001060 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 27 Mar 2019 18:11:12 +0900 Subject: [PATCH 269/623] Fix incorrect usage of LogoFacade --- osu.Game/Screens/Play/PlayerLoader.cs | 39 ++++++++++++++------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 6ac2c8220f..3f547755c1 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -65,27 +65,28 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + }; + + content.Children = new Drawable[] + { + info = new BeatmapMetadataDisplay(Beatmap.Value, content.LogoFacade) { - info = new BeatmapMetadataDisplay(Beatmap.Value, content.LogoFacade) + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Margin = new MarginPadding(25), + Children = new PlayerSettingsGroup[] { - Alpha = 0, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20), - Margin = new MarginPadding(25), - Children = new PlayerSettingsGroup[] - { - VisualSettings = new VisualSettings(), - new InputSettings() - } + VisualSettings = new VisualSettings(), + new InputSettings() } } }; From fea6adbf547d8bba76d9d4a9b4cc7b02c4291486 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 27 Mar 2019 10:12:04 +0100 Subject: [PATCH 270/623] fix wedge bleeding into other elements (and being misaligned) --- osu.Game/Screens/Select/SongSelect.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 8758df5151..2b9c635547 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -89,13 +89,12 @@ namespace osu.Game.Screens.Select protected SongSelect() { const float carousel_width = 640; - const float filter_height = 100; AddRangeInternal(new Drawable[] { new ParallaxContainer { - Padding = new MarginPadding { Top = filter_height }, + Masking = true, ParallaxAmount = 0.005f, RelativeSizeAxes = Axes.Both, Children = new[] @@ -154,7 +153,7 @@ namespace osu.Game.Screens.Select FilterControl = new FilterControl { RelativeSizeAxes = Axes.X, - Height = filter_height, + Height = 100, FilterChanged = c => Carousel.Filter(c), Background = { Width = 2 }, Exit = () => From de80fc0eac4be82a717953c6a864128f13010b64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Mar 2019 19:29:27 +0900 Subject: [PATCH 271/623] Update icon usage to match framework changes --- osu.Desktop/Overlays/VersionManager.cs | 2 +- osu.Desktop/Updater/SimpleUpdateManager.cs | 4 +- osu.Desktop/Updater/SquirrelUpdateManager.cs | 3 +- .../Beatmaps/CatchBeatmap.cs | 8 +- osu.Game.Rulesets.Catch/CatchRuleset.cs | 3 +- .../Beatmaps/ManiaBeatmap.cs | 6 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 3 +- .../Mods/ManiaModFadeIn.cs | 3 +- .../Mods/ManiaModRandom.cs | 3 +- osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs | 8 +- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 3 +- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 3 +- osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs | 4 +- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 3 +- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 3 +- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 4 +- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 4 +- .../Objects/Drawables/DrawableRepeatPoint.cs | 4 +- .../Objects/Drawables/DrawableSpinner.cs | 3 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 +- .../Beatmaps/TaikoBeatmap.cs | 8 +- .../Drawables/Pieces/SwellSymbolPiece.cs | 4 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 3 +- .../TestCaseBeatmapOptionsOverlay.cs | 10 +- .../UserInterface/TestCaseDialogOverlay.cs | 6 +- .../UserInterface/TestCaseIconButton.cs | 2 +- .../UserInterface/TestCasePopupDialog.cs | 4 +- .../UserInterface/TestCaseTextAwesome.cs | 6 +- osu.Game/Beatmaps/BeatmapStatistic.cs | 4 +- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 4 +- .../Graphics/Containers/LinkFlowContainer.cs | 2 +- .../Containers/OsuTextFlowContainer.cs | 2 +- .../Graphics/Containers/UserDimContainer.cs | 1 + osu.Game/Graphics/OsuFont.cs | 2 +- osu.Game/Graphics/OsuIcon.cs | 94 ++ osu.Game/Graphics/SpriteIcon.cs | 1007 ----------------- osu.Game/Graphics/UserInterface/BackButton.cs | 2 +- .../UserInterface/BreadcrumbControl.cs | 3 +- .../UserInterface/ExternalLinkButton.cs | 3 +- osu.Game/Graphics/UserInterface/IconButton.cs | 3 +- .../UserInterface/LoadingAnimation.cs | 5 +- .../Graphics/UserInterface/OsuDropdown.cs | 4 +- .../UserInterface/OsuPasswordTextBox.cs | 3 +- .../Graphics/UserInterface/OsuTabControl.cs | 2 +- .../UserInterface/OsuTabControlCheckbox.cs | 6 +- .../Graphics/UserInterface/SearchTextBox.cs | 3 +- .../Graphics/UserInterface/StarCounter.cs | 3 +- .../Graphics/UserInterface/TwoLayerButton.cs | 4 +- .../Online/Leaderboards/LeaderboardScore.cs | 10 +- .../Online/Leaderboards/MessagePlaceholder.cs | 4 +- .../RetrievalFailurePlaceholder.cs | 4 +- .../Multiplayer/GameTypes/GameTypeTag.cs | 3 +- .../Multiplayer/GameTypes/GameTypeTagTeam.cs | 5 +- .../GameTypes/GameTypeTimeshift.cs | 3 +- osu.Game/OsuGame.cs | 5 +- osu.Game/OsuGameBase.cs | 3 - osu.Game/Overlays/BeatmapSet/BasicStats.cs | 13 +- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 7 +- .../BeatmapSet/Buttons/DownloadButton.cs | 3 +- .../BeatmapSet/Buttons/FavouriteButton.cs | 7 +- osu.Game/Overlays/Chat/ExternalLinkDialog.cs | 4 +- .../Chat/Selection/ChannelListItem.cs | 5 +- .../Overlays/Chat/Tabs/ChannelTabControl.cs | 4 +- osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs | 2 +- .../Chat/Tabs/PrivateChannelTabItem.cs | 3 +- osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs | 4 +- osu.Game/Overlays/Dialog/PopupDialog.cs | 5 +- osu.Game/Overlays/Direct/DirectGridPanel.cs | 9 +- osu.Game/Overlays/Direct/DirectListPanel.cs | 9 +- osu.Game/Overlays/Direct/DirectPanel.cs | 2 +- osu.Game/Overlays/Direct/DownloadButton.cs | 5 +- osu.Game/Overlays/Direct/Header.cs | 3 +- osu.Game/Overlays/Direct/IconPill.cs | 4 +- osu.Game/Overlays/Direct/PlayButton.cs | 5 +- .../KeyBinding/GlobalKeyBindingsSection.cs | 4 +- .../KeyBinding/RulesetBindingsSection.cs | 3 +- osu.Game/Overlays/KeyBindingOverlay.cs | 3 +- osu.Game/Overlays/Music/PlaylistItem.cs | 2 +- osu.Game/Overlays/MusicController.cs | 12 +- .../Overlays/Notifications/Notification.cs | 3 +- .../ProgressCompletionNotification.cs | 3 +- .../Notifications/SimpleNotification.cs | 5 +- .../Overlays/Profile/Header/SupporterIcon.cs | 3 +- osu.Game/Overlays/Profile/ProfileHeader.cs | 18 +- .../SearchableList/DisplayStyleControl.cs | 8 +- .../SearchableList/SearchableListHeader.cs | 3 +- .../Settings/Sections/AudioSection.cs | 4 +- .../Settings/Sections/DebugSection.cs | 4 +- .../Settings/Sections/GameplaySection.cs | 4 +- .../Sections/General/LoginSettings.cs | 3 +- .../Settings/Sections/GeneralSection.cs | 4 +- .../Settings/Sections/GraphicsSection.cs | 4 +- .../Settings/Sections/InputSection.cs | 4 +- .../Maintenance/DeleteAllBeatmapsDialog.cs | 4 +- .../Settings/Sections/MaintenanceSection.cs | 4 +- .../Settings/Sections/OnlineSection.cs | 4 +- .../Overlays/Settings/Sections/SkinSection.cs | 4 +- osu.Game/Overlays/Settings/SettingsSection.cs | 3 +- osu.Game/Overlays/Social/Header.cs | 3 +- osu.Game/Overlays/Toolbar/Toolbar.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 4 +- .../Overlays/Toolbar/ToolbarChatButton.cs | 4 +- .../Overlays/Toolbar/ToolbarDirectButton.cs | 2 +- .../Overlays/Toolbar/ToolbarHomeButton.cs | 4 +- .../Overlays/Toolbar/ToolbarMusicButton.cs | 4 +- .../Toolbar/ToolbarNotificationButton.cs | 3 +- .../Overlays/Toolbar/ToolbarSettingsButton.cs | 4 +- .../Overlays/Toolbar/ToolbarSocialButton.cs | 4 +- osu.Game/Overlays/Volume/MuteButton.cs | 3 +- osu.Game/Rulesets/Mods/Mod.cs | 4 +- osu.Game/Rulesets/Mods/ModAutoplay.cs | 3 +- osu.Game/Rulesets/Mods/ModCinema.cs | 3 +- osu.Game/Rulesets/Mods/ModDaycore.cs | 4 +- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 3 +- osu.Game/Rulesets/Mods/ModEasy.cs | 3 +- osu.Game/Rulesets/Mods/ModFlashlight.cs | 3 +- osu.Game/Rulesets/Mods/ModHalfTime.cs | 3 +- osu.Game/Rulesets/Mods/ModHardRock.cs | 3 +- osu.Game/Rulesets/Mods/ModHidden.cs | 3 +- osu.Game/Rulesets/Mods/ModNightcore.cs | 3 +- osu.Game/Rulesets/Mods/ModNoFail.cs | 3 +- osu.Game/Rulesets/Mods/ModPerfect.cs | 3 +- osu.Game/Rulesets/Mods/ModRelax.cs | 3 +- osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 3 +- osu.Game/Rulesets/Mods/ModWindDown.cs | 4 +- osu.Game/Rulesets/Mods/ModWindUp.cs | 4 +- osu.Game/Rulesets/Ruleset.cs | 4 +- osu.Game/Rulesets/UI/ModIcon.cs | 5 +- .../Edit/Components/PlaybackControl.cs | 5 +- .../Compose/Components/BeatDivisorControl.cs | 4 +- .../Components/Timeline/TimelineArea.cs | 5 +- .../Components/Timeline/TimelineButton.cs | 3 +- osu.Game/Screens/Menu/Button.cs | 4 +- osu.Game/Screens/Menu/ButtonSystem.cs | 21 +- osu.Game/Screens/Menu/Disclaimer.cs | 4 +- osu.Game/Screens/Multi/Header.cs | 3 +- .../Match/Components/MatchLeaderboardScore.cs | 8 +- .../Screens/Multi/MultiplayerSubScreen.cs | 2 - .../Ranking/Types/RoomLeaderboardPageInfo.cs | 4 +- osu.Game/Screens/Play/Break/BlurredIcon.cs | 3 +- osu.Game/Screens/Play/Break/BreakArrows.cs | 9 +- osu.Game/Screens/Play/Break/GlowIcon.cs | 3 +- .../Screens/Play/HUD/HoldForMenuButton.cs | 3 +- .../PlayerSettings/PlayerSettingsGroup.cs | 3 +- osu.Game/Screens/Play/SkipOverlay.cs | 7 +- osu.Game/Screens/Ranking/IResultPageInfo.cs | 4 +- osu.Game/Screens/Ranking/ResultModeButton.cs | 3 +- .../Ranking/Types/LocalLeaderboardPageInfo.cs | 4 +- .../Ranking/Types/ScoreOverviewPageInfo.cs | 4 +- osu.Game/Screens/ScreenWhiteBox.cs | 5 +- .../Select/BeatmapClearScoresDialog.cs | 4 +- .../Screens/Select/BeatmapDeleteDialog.cs | 4 +- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 7 +- .../Screens/Select/ImportFromStablePopup.cs | 4 +- .../Select/Options/BeatmapOptionsButton.cs | 5 +- .../Select/Options/BeatmapOptionsOverlay.cs | 4 +- osu.Game/Screens/Select/PlaySongSelect.cs | 3 +- osu.Game/Screens/Select/SongSelect.cs | 7 +- osu.Game/Users/UserPanel.cs | 3 +- 159 files changed, 467 insertions(+), 1307 deletions(-) create mode 100644 osu.Game/Graphics/OsuIcon.cs delete mode 100644 osu.Game/Graphics/SpriteIcon.cs diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index b8a0e337b6..2998e08715 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -110,7 +110,7 @@ namespace osu.Desktop.Overlays public UpdateCompleteNotification(string version, Action openUrl = null) { Text = $"You are now running osu!lazer {version}.\nClick to see what's new!"; - Icon = FontAwesome.fa_check_square; + Icon = FontAwesome.CheckSquare; Activated = delegate { openUrl?.Invoke($"https://osu.ppy.sh/home/changelog/lazer/{version}"); diff --git a/osu.Desktop/Updater/SimpleUpdateManager.cs b/osu.Desktop/Updater/SimpleUpdateManager.cs index ab05888408..1cb47d6b58 100644 --- a/osu.Desktop/Updater/SimpleUpdateManager.cs +++ b/osu.Desktop/Updater/SimpleUpdateManager.cs @@ -7,10 +7,10 @@ using Newtonsoft.Json; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.IO.Network; using osu.Framework.Platform; using osu.Game; -using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -54,7 +54,7 @@ namespace osu.Desktop.Updater { Text = $"A newer release of osu! has been found ({version} → {latest.TagName}).\n\n" + "Click here to download the new version, which can be installed over the top of your existing installation", - Icon = FontAwesome.fa_upload, + Icon = FontAwesome.Upload, Activated = () => { host.OpenUrlExternally(getBestUrl(latest)); diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 6400fd776d..6ebadeb4e9 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Logging; using osu.Game; using osu.Game.Graphics; @@ -158,7 +159,7 @@ namespace osu.Desktop.Updater { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_upload, + Icon = FontAwesome.Upload, Colour = Color4.White, Size = new Vector2(20), } diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs index c5ced26e42..d55f3ff159 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Catch.Objects; namespace osu.Game.Rulesets.Catch.Beatmaps @@ -23,19 +23,19 @@ namespace osu.Game.Rulesets.Catch.Beatmaps { Name = @"Fruit Count", Content = fruits.ToString(), - Icon = FontAwesome.fa_circle_o + Icon = FontAwesome.CircleOutline }, new BeatmapStatistic { Name = @"Juice Stream Count", Content = juiceStreams.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Circle }, new BeatmapStatistic { Name = @"Banana Shower Count", Content = bananaShowers.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Circle } }; } diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 5140135f80..440aa8a7fc 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Replays; @@ -114,7 +115,7 @@ namespace osu.Game.Rulesets.Catch public override string ShortName => "fruits"; - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch }; public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs index 77a10131a6..184cbf339d 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; @@ -42,13 +42,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps { Name = @"Note Count", Content = notes.ToString(), - Icon = FontAwesome.fa_circle_o + Icon = FontAwesome.CircleOutline }, new BeatmapStatistic { Name = @"Hold Note Count", Content = holdnotes.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Circle }, }; } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index a4a10f1742..0ff79d2836 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.UI; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Objects; @@ -160,7 +161,7 @@ namespace osu.Game.Rulesets.Mania public override string ShortName => "mania"; - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetMania }; public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs index 269e318a73..39185e6a57 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; @@ -12,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override string Name => "Fade In"; public override string Acronym => "FI"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; + public override IconUsage Icon => OsuIcon.ModHidden; public override ModType Type => ModType.DifficultyIncrease; public override string Description => @"Keys appear out of nowhere!"; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index 5d5023abae..ba16140644 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs @@ -3,6 +3,7 @@ using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Random"; public override string Acronym => "RD"; public override ModType Type => ModType.Conversion; - public override FontAwesome Icon => FontAwesome.fa_osu_dice; + public override IconUsage Icon => OsuIcon.Dice; public override string Description => @"Shuffle around the keys!"; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs index 1a352aa2a1..7099758e3d 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Beatmaps @@ -23,19 +23,19 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { Name = @"Circle Count", Content = circles.ToString(), - Icon = FontAwesome.fa_circle_o + Icon = FontAwesome.CircleOutline }, new BeatmapStatistic { Name = @"Slider Count", Content = sliders.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Circle }, new BeatmapStatistic { Name = @"Spinner Count", Content = spinners.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Circle } }; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 21b4dbfeda..401bd28d7c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Name => "Autopilot"; public override string Acronym => "AP"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_autopilot; + public override IconUsage Icon => OsuIcon.ModAutopilot; public override ModType Type => ModType.Automation; public override string Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index a1f4dfe1da..7f94b68cc0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "Play with blinds on your screen."; public override string Acronym => "BL"; - public override FontAwesome Icon => FontAwesome.fa_adjust; + public override IconUsage Icon => FontAwesome.Adjust; public override ModType Type => ModType.DifficultyIncrease; public override bool Ranked => false; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs index 65e9eb7a1d..2e93815ef0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Acronym => "GR"; - public override FontAwesome Icon => FontAwesome.fa_arrows_v; + public override IconUsage Icon => FontAwesome.ArrowsV; public override ModType Type => ModType.Fun; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 0c8436d096..1cdcddbd33 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Name => "Spun Out"; public override string Acronym => "SO"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_spunout; + public override IconUsage Icon => OsuIcon.ModSpunout; public override ModType Type => ModType.DifficultyReduction; public override string Description => @"Spinners will be automatically completed."; public override double ScoreMultiplier => 0.9; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 36fa5f3098..8360e2692e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Name => "Target"; public override string Acronym => "TP"; public override ModType Type => ModType.Conversion; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_target; + public override IconUsage Icon => OsuIcon.ModTarget; public override string Description => @"Practice keeping up with the beat of the song."; public override double ScoreMultiplier => 1; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 9a769ec39c..31195b7878 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Name => "Transform"; public override string Acronym => "TR"; - public override FontAwesome Icon => FontAwesome.fa_arrows; + public override IconUsage Icon => FontAwesome.Arrows; public override ModType Type => ModType.Fun; public override string Description => "Everything rotates. EVERYTHING."; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index af9f0ec8f3..bdc2873d8d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Name => "Wiggle"; public override string Acronym => "WG"; - public override FontAwesome Icon => FontAwesome.fa_certificate; + public override IconUsage Icon => FontAwesome.Certificate; public override ModType Type => ModType.Fun; public override string Description => "They just won't stay still..."; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 22d2034fe0..a6714690b1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -4,10 +4,10 @@ using System; using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; using osu.Game.Rulesets.Objects.Drawables; using osuTK; -using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables new SkinnableDrawable("Play/osu/reversearrow", _ => new SpriteIcon { RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_chevron_right + Icon = FontAwesome.ChevronRight }, restrictSize: false) }; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 789af4f49b..3a6ff3fcf8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; using osu.Game.Screens.Ranking; using osu.Game.Rulesets.Scoring; @@ -76,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(48), - Icon = FontAwesome.fa_asterisk, + Icon = FontAwesome.Asterisk, Shadow = false, }, } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 7d3aff7801..44bce5bed8 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Osu.Edit; @@ -137,7 +138,7 @@ namespace osu.Game.Rulesets.Osu } } - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetOsu }; public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap); diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs index 2bc39a1c58..4149da67c7 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Beatmaps @@ -23,19 +23,19 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { Name = @"Hit Count", Content = hits.ToString(), - Icon = FontAwesome.fa_circle_o + Icon = FontAwesome.CircleOutline }, new BeatmapStatistic { Name = @"Drumroll Count", Content = drumrolls.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Circle }, new BeatmapStatistic { Name = @"Swell Count", Content = swells.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Circle } }; } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs index f8dcaa0b45..569ac96c15 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs @@ -4,7 +4,7 @@ using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces new SpriteIcon { RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_asterisk, + Icon = FontAwesome.Asterisk, Shadow = false } }; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 3e94775eb6..448b1b42bb 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.UI; using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Taiko.Objects; @@ -114,7 +115,7 @@ namespace osu.Game.Rulesets.Taiko public override string ShortName => "taiko"; - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetTaiko }; public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap); diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapOptionsOverlay.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapOptionsOverlay.cs index 49038dc2cf..3cb480bab8 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapOptionsOverlay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapOptionsOverlay.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Screens.Select.Options; using osuTK.Graphics; using osuTK.Input; @@ -16,10 +16,10 @@ namespace osu.Game.Tests.Visual.SongSelect { var overlay = new BeatmapOptionsOverlay(); - overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, Color4.Purple, null, Key.Number1); - overlay.AddButton(@"Clear", @"local scores", FontAwesome.fa_eraser, Color4.Purple, null, Key.Number2); - overlay.AddButton(@"Edit", @"Beatmap", FontAwesome.fa_pencil, Color4.Yellow, null, Key.Number3); - overlay.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, Color4.Pink, null, Key.Number4, float.MaxValue); + overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.TimesCircleOutline, Color4.Purple, null, Key.Number1); + overlay.AddButton(@"Clear", @"local scores", FontAwesome.Eraser, Color4.Purple, null, Key.Number2); + overlay.AddButton(@"Edit", @"Beatmap", FontAwesome.Pencil, Color4.Yellow, null, Key.Number3); + overlay.AddButton(@"Delete", @"Beatmap", FontAwesome.Trash, Color4.Pink, null, Key.Number4, float.MaxValue); Add(overlay); diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseDialogOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseDialogOverlay.cs index 6b32f711e9..98d6f3a149 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseDialogOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseDialogOverlay.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("dialog #1", () => overlay.Push(new PopupDialog { - Icon = FontAwesome.fa_trash_o, + Icon = FontAwesome.TrashOutline, HeaderText = @"Confirm deletion of", BodyText = @"Ayase Rie - Yuima-ru*World TVver.", Buttons = new PopupDialogButton[] @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("dialog #2", () => overlay.Push(new PopupDialog { - Icon = FontAwesome.fa_gear, + Icon = FontAwesome.Gear, HeaderText = @"What do you want to do with", BodyText = "Camellia as \"Bang Riot\" - Blastix Riotz", Buttons = new PopupDialogButton[] diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseIconButton.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseIconButton.cs index 2898d1a1cc..6bb1347608 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseIconButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseIconButton.cs @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.UserInterface button.Anchor = Anchor.Centre; button.Origin = Anchor.Centre; - button.Icon = FontAwesome.fa_osu_osu_o; + button.Icon = OsuIcon.RulesetOsu; } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestCasePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestCasePopupDialog.cs index 490903a906..bcba7e6811 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCasePopupDialog.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCasePopupDialog.cs @@ -3,7 +3,7 @@ using NUnit.Framework; using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; namespace osu.Game.Tests.Visual.UserInterface @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.UserInterface { RelativeSizeAxes = Axes.Both, State = Framework.Graphics.Containers.Visibility.Visible, - Icon = FontAwesome.fa_assistive_listening_systems, + Icon = FontAwesome.AssistiveListeningSystems, HeaderText = @"This is a test popup", BodyText = "I can say lots of stuff and even wrap my words!", Buttons = new PopupDialogButton[] diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseTextAwesome.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseTextAwesome.cs index 40179387e2..9df97f6bee 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseTextAwesome.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseTextAwesome.cs @@ -6,7 +6,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osuTK; namespace osu.Game.Tests.Visual.UserInterface @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.UserInterface }, }); - foreach (FontAwesome fa in Enum.GetValues(typeof(FontAwesome))) + foreach (IconUsage fa in Enum.GetValues(typeof(FontAwesome))) flow.Add(new Icon(fa)); } @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.UserInterface { public string TooltipText { get; } - public Icon(FontAwesome fa) + public Icon(IconUsage fa) { TooltipText = fa.ToString(); diff --git a/osu.Game/Beatmaps/BeatmapStatistic.cs b/osu.Game/Beatmaps/BeatmapStatistic.cs index 8c0c7c09ae..0745ec5222 100644 --- a/osu.Game/Beatmaps/BeatmapStatistic.cs +++ b/osu.Game/Beatmaps/BeatmapStatistic.cs @@ -1,13 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Beatmaps { public class BeatmapStatistic { - public FontAwesome Icon; + public IconUsage Icon; public string Content; public string Name; } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index dd8cdb862e..f0f58b9b5d 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -7,7 +7,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; using osu.Game.Rulesets; using osuTK; @@ -60,7 +60,7 @@ namespace osu.Game.Beatmaps.Drawables Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) - Icon = ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.fa_question_circle_o } + Icon = ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.QuestionCircleOutline } } }; } diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 37afefb7f8..204c83aac9 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Graphics.Containers showNotImplementedError = () => notifications?.Post(new SimpleNotification { Text = @"This link type is not yet supported!", - Icon = FontAwesome.fa_life_saver, + Icon = FontAwesome.LifeSaver, }); } diff --git a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs index 56e5f411b8..6a87a4b8b9 100644 --- a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs @@ -21,6 +21,6 @@ namespace osu.Game.Graphics.Containers public void AddArbitraryDrawable(Drawable drawable) => AddInternal(drawable); - public IEnumerable AddIcon(FontAwesome icon, Action creationParameters = null) => AddText(((char)icon).ToString(), creationParameters); + public IEnumerable AddIcon(IconUsage icon, Action creationParameters = null) => AddText(icon.Icon.ToString(), creationParameters); } } diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index b078f40420..fe9eb7baf4 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Graphics.Backgrounds; +using osu.Game.Screens.Play; using osuTK; using osuTK.Graphics; diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index dc660fd159..26112430f6 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -5,7 +5,7 @@ using osu.Framework.Graphics.Sprites; namespace osu.Game.Graphics { - public struct OsuFont + public static class OsuFont { /// /// The default font size. diff --git a/osu.Game/Graphics/OsuIcon.cs b/osu.Game/Graphics/OsuIcon.cs new file mode 100644 index 0000000000..52fb31553d --- /dev/null +++ b/osu.Game/Graphics/OsuIcon.cs @@ -0,0 +1,94 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Graphics +{ + public static class OsuIcon + { + public static IconUsage Get(int icon) => new IconUsage((char)icon, "OsuFont"); + + // ruleset icons in circles + public static IconUsage RulesetOsu => Get(0xe000); + public static IconUsage RulesetMania => Get(0xe001); + public static IconUsage RulesetCatch => Get(0xe002); + public static IconUsage RulesetTaiko => Get(0xe003); + + // ruleset icons without circles + public static IconUsage FilledCircle => Get(0xe004); + public static IconUsage CrossCircle => Get(0xe005); + public static IconUsage Logo => Get(0xe006); + public static IconUsage ChevronDownCircle => Get(0xe007); + public static IconUsage EditCircle => Get(0xe033); + public static IconUsage LeftCircle => Get(0xe034); + public static IconUsage RightCircle => Get(0xe035); + public static IconUsage Charts => Get(0xe036); + public static IconUsage Solo => Get(0xe037); + public static IconUsage Multi => Get(0xe038); + public static IconUsage Gear => Get(0xe039); + + // misc icons + public static IconUsage Bat => Get(0xe008); + public static IconUsage Bubble => Get(0xe009); + public static IconUsage BubblePop => Get(0xe02e); + public static IconUsage Dice => Get(0xe011); + public static IconUsage Heart => Get(0xe02f); + public static IconUsage HeartBreak => Get(0xe030); + public static IconUsage Hot => Get(0xe031); + public static IconUsage ListSearch => Get(0xe032); + + //osu! playstyles + public static IconUsage PlaystyleTablet => Get(0xe02a); + public static IconUsage PlaystyleMouse => Get(0xe029); + public static IconUsage PlaystyleKeyboard => Get(0xe02b); + public static IconUsage PlaystyleTouch => Get(0xe02c); + + // osu! difficulties + public static IconUsage EasyOsu => Get(0xe015); + public static IconUsage NormalOsu => Get(0xe016); + public static IconUsage HardOsu => Get(0xe017); + public static IconUsage InsaneOsu => Get(0xe018); + public static IconUsage ExpertOsu => Get(0xe019); + + // taiko difficulties + public static IconUsage EasyTaiko => Get(0xe01a); + public static IconUsage NormalTaiko => Get(0xe01b); + public static IconUsage HardTaiko => Get(0xe01c); + public static IconUsage InsaneTaiko => Get(0xe01d); + public static IconUsage ExpertTaiko => Get(0xe01e); + + // fruits difficulties + public static IconUsage EasyFruits => Get(0xe01f); + public static IconUsage NormalFruits => Get(0xe020); + public static IconUsage HardFruits => Get(0xe021); + public static IconUsage InsaneFruits => Get(0xe022); + public static IconUsage ExpertFruits => Get(0xe023); + + // mania difficulties + public static IconUsage EasyMania => Get(0xe024); + public static IconUsage NormalMania => Get(0xe025); + public static IconUsage HardMania => Get(0xe026); + public static IconUsage InsaneMania => Get(0xe027); + public static IconUsage ExpertMania => Get(0xe028); + + // mod icons + public static IconUsage ModPerfect => Get(0xe049); + public static IconUsage ModAutopilot => Get(0xe03a); + public static IconUsage ModAuto => Get(0xe03b); + public static IconUsage ModCinema => Get(0xe03c); + public static IconUsage ModDoubletime => Get(0xe03d); + public static IconUsage ModEasy => Get(0xe03e); + public static IconUsage ModFlashlight => Get(0xe03f); + public static IconUsage ModHalftime => Get(0xe040); + public static IconUsage ModHardrock => Get(0xe041); + public static IconUsage ModHidden => Get(0xe042); + public static IconUsage ModNightcore => Get(0xe043); + public static IconUsage ModNofail => Get(0xe044); + public static IconUsage ModRelax => Get(0xe045); + public static IconUsage ModSpunout => Get(0xe046); + public static IconUsage ModSuddendeath => Get(0xe047); + public static IconUsage ModTarget => Get(0xe048); + public static IconUsage ModBg => Get(0xe04a); + } +} diff --git a/osu.Game/Graphics/SpriteIcon.cs b/osu.Game/Graphics/SpriteIcon.cs deleted file mode 100644 index f7d7d21435..0000000000 --- a/osu.Game/Graphics/SpriteIcon.cs +++ /dev/null @@ -1,1007 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics; -using osu.Framework.IO.Stores; -using osuTK; -using osuTK.Graphics; -using osu.Framework.Caching; - -namespace osu.Game.Graphics -{ - public class SpriteIcon : CompositeDrawable - { - private Sprite spriteShadow; - private Sprite spriteMain; - - private Cached layout = new Cached(); - private Container shadowVisibility; - - private FontStore store; - - [BackgroundDependencyLoader] - private void load(FontStore store) - { - this.store = store; - - InternalChildren = new Drawable[] - { - shadowVisibility = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Child = spriteShadow = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Y = 2, - Colour = new Color4(0f, 0f, 0f, 0.2f), - }, - Alpha = shadow ? 1 : 0, - }, - spriteMain = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit - }, - }; - - updateTexture(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - updateTexture(); - } - - private FontAwesome loadedIcon; - - private void updateTexture() - { - var loadableIcon = icon; - - if (loadableIcon == loadedIcon) return; - - var texture = store.Get(((char)loadableIcon).ToString()); - - spriteMain.Texture = texture; - spriteShadow.Texture = texture; - - if (Size == Vector2.Zero) - Size = new Vector2(texture?.DisplayWidth ?? 0, texture?.DisplayHeight ?? 0); - - loadedIcon = loadableIcon; - } - - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.Colour) > 0 && Shadow) - layout.Invalidate(); - return base.Invalidate(invalidation, source, shallPropagate); - } - - protected override void Update() - { - if (!layout.IsValid) - { - //adjust shadow alpha based on highest component intensity to avoid muddy display of darker text. - //squared result for quadratic fall-off seems to give the best result. - var avgColour = (Color4)DrawColourInfo.Colour.AverageColour; - - spriteShadow.Alpha = (float)Math.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2); - - layout.Validate(); - } - } - - private bool shadow; - - public bool Shadow - { - get => shadow; - set - { - shadow = value; - if (shadowVisibility != null) - shadowVisibility.Alpha = value ? 1 : 0; - } - } - - private FontAwesome icon; - - public FontAwesome Icon - { - get => icon; - set - { - if (icon == value) return; - - icon = value; - if (LoadState == LoadState.Loaded) - updateTexture(); - } - } - } - - public enum FontAwesome - { - fa_500px = 0xf26e, - fa_address_book = 0xf2b9, - fa_address_book_o = 0xf2ba, - fa_address_card = 0xf2bb, - fa_address_card_o = 0xf2bc, - fa_adjust = 0xf042, - fa_adn = 0xf170, - fa_align_center = 0xf037, - fa_align_justify = 0xf039, - fa_align_left = 0xf036, - fa_align_right = 0xf038, - fa_amazon = 0xf270, - fa_ambulance = 0xf0f9, - fa_american_sign_language_interpreting = 0xf2a3, - fa_anchor = 0xf13d, - fa_android = 0xf17b, - fa_angellist = 0xf209, - fa_angle_double_down = 0xf103, - fa_angle_double_left = 0xf100, - fa_angle_double_right = 0xf101, - fa_angle_double_up = 0xf102, - fa_angle_down = 0xf107, - fa_angle_left = 0xf104, - fa_angle_right = 0xf105, - fa_angle_up = 0xf106, - fa_apple = 0xf179, - fa_archive = 0xf187, - fa_area_chart = 0xf1fe, - fa_arrow_circle_down = 0xf0ab, - fa_arrow_circle_left = 0xf0a8, - fa_arrow_circle_o_down = 0xf01a, - fa_arrow_circle_o_left = 0xf190, - fa_arrow_circle_o_right = 0xf18e, - fa_arrow_circle_o_up = 0xf01b, - fa_arrow_circle_right = 0xf0a9, - fa_arrow_circle_up = 0xf0aa, - fa_arrow_down = 0xf063, - fa_arrow_left = 0xf060, - fa_arrow_right = 0xf061, - fa_arrow_up = 0xf062, - fa_arrows = 0xf047, - fa_arrows_alt = 0xf0b2, - fa_arrows_h = 0xf07e, - fa_arrows_v = 0xf07d, - fa_asl_interpreting = 0xf2a3, - fa_assistive_listening_systems = 0xf2a2, - fa_asterisk = 0xf069, - fa_at = 0xf1fa, - fa_audio_description = 0xf29e, - fa_automobile = 0xf1b9, - fa_backward = 0xf04a, - fa_balance_scale = 0xf24e, - fa_ban = 0xf05e, - fa_bandcamp = 0xf2d5, - fa_bank = 0xf19c, - fa_bar_chart = 0xf080, - fa_bar_chart_o = 0xf080, - fa_barcode = 0xf02a, - fa_bars = 0xf0c9, - fa_bath = 0xf2cd, - fa_bathtub = 0xf2cd, - fa_battery = 0xf240, - fa_battery_0 = 0xf244, - fa_battery_1 = 0xf243, - fa_battery_2 = 0xf242, - fa_battery_3 = 0xf241, - fa_battery_4 = 0xf240, - fa_battery_empty = 0xf244, - fa_battery_full = 0xf240, - fa_battery_half = 0xf242, - fa_battery_quarter = 0xf243, - fa_battery_three_quarters = 0xf241, - fa_bed = 0xf236, - fa_beer = 0xf0fc, - fa_behance = 0xf1b4, - fa_behance_square = 0xf1b5, - fa_bell = 0xf0f3, - fa_bell_o = 0xf0a2, - fa_bell_slash = 0xf1f6, - fa_bell_slash_o = 0xf1f7, - fa_bicycle = 0xf206, - fa_binoculars = 0xf1e5, - fa_birthday_cake = 0xf1fd, - fa_bitbucket = 0xf171, - fa_bitbucket_square = 0xf172, - fa_bitcoin = 0xf15a, - fa_black_tie = 0xf27e, - fa_blind = 0xf29d, - fa_bluetooth = 0xf293, - fa_bluetooth_b = 0xf294, - fa_bold = 0xf032, - fa_bolt = 0xf0e7, - fa_bomb = 0xf1e2, - fa_book = 0xf02d, - fa_bookmark = 0xf02e, - fa_bookmark_o = 0xf097, - fa_braille = 0xf2a1, - fa_briefcase = 0xf0b1, - fa_btc = 0xf15a, - fa_bug = 0xf188, - fa_building = 0xf1ad, - fa_building_o = 0xf0f7, - fa_bullhorn = 0xf0a1, - fa_bullseye = 0xf140, - fa_bus = 0xf207, - fa_buysellads = 0xf20d, - fa_cab = 0xf1ba, - fa_calculator = 0xf1ec, - fa_calendar = 0xf073, - fa_calendar_check_o = 0xf274, - fa_calendar_minus_o = 0xf272, - fa_calendar_o = 0xf133, - fa_calendar_plus_o = 0xf271, - fa_calendar_times_o = 0xf273, - fa_camera = 0xf030, - fa_camera_retro = 0xf083, - fa_car = 0xf1b9, - fa_caret_down = 0xf0d7, - fa_caret_left = 0xf0d9, - fa_caret_right = 0xf0da, - fa_caret_square_o_down = 0xf150, - fa_caret_square_o_left = 0xf191, - fa_caret_square_o_right = 0xf152, - fa_caret_square_o_up = 0xf151, - fa_caret_up = 0xf0d8, - fa_cart_arrow_down = 0xf218, - fa_cart_plus = 0xf217, - fa_cc = 0xf20a, - fa_cc_amex = 0xf1f3, - fa_cc_diners_club = 0xf24c, - fa_cc_discover = 0xf1f2, - fa_cc_jcb = 0xf24b, - fa_cc_mastercard = 0xf1f1, - fa_cc_paypal = 0xf1f4, - fa_cc_stripe = 0xf1f5, - fa_cc_visa = 0xf1f0, - fa_certificate = 0xf0a3, - fa_chain = 0xf0c1, - fa_chain_broken = 0xf127, - fa_check = 0xf00c, - fa_check_circle = 0xf058, - fa_check_circle_o = 0xf05d, - fa_check_square = 0xf14a, - fa_check_square_o = 0xf046, - fa_chevron_circle_down = 0xf13a, - fa_chevron_circle_left = 0xf137, - fa_chevron_circle_right = 0xf138, - fa_chevron_circle_up = 0xf139, - fa_chevron_down = 0xf078, - fa_chevron_left = 0xf053, - fa_chevron_right = 0xf054, - fa_chevron_up = 0xf077, - fa_child = 0xf1ae, - fa_chrome = 0xf268, - fa_circle = 0xf111, - fa_circle_o = 0xf10c, - fa_circle_o_notch = 0xf1ce, - fa_circle_thin = 0xf1db, - fa_clipboard = 0xf0ea, - fa_clock_o = 0xf017, - fa_clone = 0xf24d, - fa_close = 0xf00d, - fa_cloud = 0xf0c2, - fa_cloud_download = 0xf0ed, - fa_cloud_upload = 0xf0ee, - fa_cny = 0xf157, - fa_code = 0xf121, - fa_code_fork = 0xf126, - fa_codepen = 0xf1cb, - fa_codiepie = 0xf284, - fa_coffee = 0xf0f4, - fa_cog = 0xf013, - fa_cogs = 0xf085, - fa_columns = 0xf0db, - fa_comment = 0xf075, - fa_comment_o = 0xf0e5, - fa_commenting = 0xf27a, - fa_commenting_o = 0xf27b, - fa_comments = 0xf086, - fa_comments_o = 0xf0e6, - fa_compass = 0xf14e, - fa_compress = 0xf066, - fa_connectdevelop = 0xf20e, - fa_contao = 0xf26d, - fa_copy = 0xf0c5, - fa_copyright = 0xf1f9, - fa_creative_commons = 0xf25e, - fa_credit_card = 0xf09d, - fa_credit_card_alt = 0xf283, - fa_crop = 0xf125, - fa_crosshairs = 0xf05b, - fa_css3 = 0xf13c, - fa_cube = 0xf1b2, - fa_cubes = 0xf1b3, - fa_cut = 0xf0c4, - fa_cutlery = 0xf0f5, - fa_dashboard = 0xf0e4, - fa_dashcube = 0xf210, - fa_database = 0xf1c0, - fa_deaf = 0xf2a4, - fa_deafness = 0xf2a4, - fa_dedent = 0xf03b, - fa_delicious = 0xf1a5, - fa_desktop = 0xf108, - fa_deviantart = 0xf1bd, - fa_diamond = 0xf219, - fa_digg = 0xf1a6, - fa_dollar = 0xf155, - fa_dot_circle_o = 0xf192, - fa_download = 0xf019, - fa_dribbble = 0xf17d, - fa_drivers_license = 0xf2c2, - fa_drivers_license_o = 0xf2c3, - fa_dropbox = 0xf16b, - fa_drupal = 0xf1a9, - fa_edge = 0xf282, - fa_edit = 0xf044, - fa_eercast = 0xf2da, - fa_eject = 0xf052, - fa_ellipsis_h = 0xf141, - fa_ellipsis_v = 0xf142, - fa_empire = 0xf1d1, - fa_envelope = 0xf0e0, - fa_envelope_o = 0xf003, - fa_envelope_open = 0xf2b6, - fa_envelope_open_o = 0xf2b7, - fa_envelope_square = 0xf199, - fa_envira = 0xf299, - fa_eraser = 0xf12d, - fa_etsy = 0xf2d7, - fa_eur = 0xf153, - fa_euro = 0xf153, - fa_exchange = 0xf0ec, - fa_exclamation = 0xf12a, - fa_exclamation_circle = 0xf06a, - fa_exclamation_triangle = 0xf071, - fa_expand = 0xf065, - fa_expeditedssl = 0xf23e, - fa_external_link = 0xf08e, - fa_external_link_square = 0xf14c, - fa_eye = 0xf06e, - fa_eye_slash = 0xf070, - fa_eyedropper = 0xf1fb, - fa_fa = 0xf2b4, - fa_facebook = 0xf09a, - fa_facebook_f = 0xf09a, - fa_facebook_official = 0xf230, - fa_facebook_square = 0xf082, - fa_fast_backward = 0xf049, - fa_fast_forward = 0xf050, - fa_fax = 0xf1ac, - fa_feed = 0xf09e, - fa_female = 0xf182, - fa_fighter_jet = 0xf0fb, - fa_file = 0xf15b, - fa_file_archive_o = 0xf1c6, - fa_file_audio_o = 0xf1c7, - fa_file_code_o = 0xf1c9, - fa_file_excel_o = 0xf1c3, - fa_file_image_o = 0xf1c5, - fa_file_movie_o = 0xf1c8, - fa_file_o = 0xf016, - fa_file_pdf_o = 0xf1c1, - fa_file_photo_o = 0xf1c5, - fa_file_picture_o = 0xf1c5, - fa_file_powerpoint_o = 0xf1c4, - fa_file_sound_o = 0xf1c7, - fa_file_text = 0xf15c, - fa_file_text_o = 0xf0f6, - fa_file_video_o = 0xf1c8, - fa_file_word_o = 0xf1c2, - fa_file_zip_o = 0xf1c6, - fa_files_o = 0xf0c5, - fa_film = 0xf008, - fa_filter = 0xf0b0, - fa_fire = 0xf06d, - fa_fire_extinguisher = 0xf134, - fa_firefox = 0xf269, - fa_first_order = 0xf2b0, - fa_flag = 0xf024, - fa_flag_checkered = 0xf11e, - fa_flag_o = 0xf11d, - fa_flash = 0xf0e7, - fa_flask = 0xf0c3, - fa_flickr = 0xf16e, - fa_floppy_o = 0xf0c7, - fa_folder = 0xf07b, - fa_folder_o = 0xf114, - fa_folder_open = 0xf07c, - fa_folder_open_o = 0xf115, - fa_font = 0xf031, - fa_font_awesome = 0xf2b4, - fa_fonticons = 0xf280, - fa_fort_awesome = 0xf286, - fa_forumbee = 0xf211, - fa_forward = 0xf04e, - fa_foursquare = 0xf180, - fa_free_code_camp = 0xf2c5, - fa_frown_o = 0xf119, - fa_futbol_o = 0xf1e3, - fa_gamepad = 0xf11b, - fa_gavel = 0xf0e3, - fa_gbp = 0xf154, - fa_ge = 0xf1d1, - fa_gear = 0xf013, - fa_gears = 0xf085, - fa_genderless = 0xf22d, - fa_get_pocket = 0xf265, - fa_gg = 0xf260, - fa_gg_circle = 0xf261, - fa_gift = 0xf06b, - fa_git = 0xf1d3, - fa_git_square = 0xf1d2, - fa_github = 0xf09b, - fa_github_alt = 0xf113, - fa_github_square = 0xf092, - fa_gitlab = 0xf296, - fa_gittip = 0xf184, - fa_glass = 0xf000, - fa_glide = 0xf2a5, - fa_glide_g = 0xf2a6, - fa_globe = 0xf0ac, - fa_google = 0xf1a0, - fa_google_plus = 0xf0d5, - fa_google_plus_circle = 0xf2b3, - fa_google_plus_official = 0xf2b3, - fa_google_plus_square = 0xf0d4, - fa_google_wallet = 0xf1ee, - fa_graduation_cap = 0xf19d, - fa_gratipay = 0xf184, - fa_grav = 0xf2d6, - fa_group = 0xf0c0, - fa_h_square = 0xf0fd, - fa_hacker_news = 0xf1d4, - fa_hand_grab_o = 0xf255, - fa_hand_lizard_o = 0xf258, - fa_hand_o_down = 0xf0a7, - fa_hand_o_left = 0xf0a5, - fa_hand_o_right = 0xf0a4, - fa_hand_o_up = 0xf0a6, - fa_hand_paper_o = 0xf256, - fa_hand_peace_o = 0xf25b, - fa_hand_pointer_o = 0xf25a, - fa_hand_rock_o = 0xf255, - fa_hand_scissors_o = 0xf257, - fa_hand_spock_o = 0xf259, - fa_hand_stop_o = 0xf256, - fa_handshake_o = 0xf2b5, - fa_hard_of_hearing = 0xf2a4, - fa_hashtag = 0xf292, - fa_hdd_o = 0xf0a0, - fa_header = 0xf1dc, - fa_headphones = 0xf025, - fa_heart = 0xf004, - fa_heart_o = 0xf08a, - fa_heartbeat = 0xf21e, - fa_history = 0xf1da, - fa_home = 0xf015, - fa_hospital_o = 0xf0f8, - fa_hotel = 0xf236, - fa_hourglass = 0xf254, - fa_hourglass_1 = 0xf251, - fa_hourglass_2 = 0xf252, - fa_hourglass_3 = 0xf253, - fa_hourglass_end = 0xf253, - fa_hourglass_half = 0xf252, - fa_hourglass_o = 0xf250, - fa_hourglass_start = 0xf251, - fa_houzz = 0xf27c, - fa_html5 = 0xf13b, - fa_i_cursor = 0xf246, - fa_id_badge = 0xf2c1, - fa_id_card = 0xf2c2, - fa_id_card_o = 0xf2c3, - fa_ils = 0xf20b, - fa_image = 0xf03e, - fa_imdb = 0xf2d8, - fa_inbox = 0xf01c, - fa_indent = 0xf03c, - fa_industry = 0xf275, - fa_info = 0xf129, - fa_info_circle = 0xf05a, - fa_inr = 0xf156, - fa_instagram = 0xf16d, - fa_institution = 0xf19c, - fa_internet_explorer = 0xf26b, - fa_intersex = 0xf224, - fa_ioxhost = 0xf208, - fa_italic = 0xf033, - fa_joomla = 0xf1aa, - fa_jpy = 0xf157, - fa_jsfiddle = 0xf1cc, - fa_key = 0xf084, - fa_keyboard_o = 0xf11c, - fa_krw = 0xf159, - fa_language = 0xf1ab, - fa_laptop = 0xf109, - fa_lastfm = 0xf202, - fa_lastfm_square = 0xf203, - fa_leaf = 0xf06c, - fa_leanpub = 0xf212, - fa_legal = 0xf0e3, - fa_lemon_o = 0xf094, - fa_level_down = 0xf149, - fa_level_up = 0xf148, - fa_life_bouy = 0xf1cd, - fa_life_buoy = 0xf1cd, - fa_life_ring = 0xf1cd, - fa_life_saver = 0xf1cd, - fa_lightbulb_o = 0xf0eb, - fa_line_chart = 0xf201, - fa_link = 0xf0c1, - fa_linkedin = 0xf0e1, - fa_linkedin_square = 0xf08c, - fa_linode = 0xf2b8, - fa_linux = 0xf17c, - fa_list = 0xf03a, - fa_list_alt = 0xf022, - fa_list_ol = 0xf0cb, - fa_list_ul = 0xf0ca, - fa_location_arrow = 0xf124, - fa_lock = 0xf023, - fa_long_arrow_down = 0xf175, - fa_long_arrow_left = 0xf177, - fa_long_arrow_right = 0xf178, - fa_long_arrow_up = 0xf176, - fa_low_vision = 0xf2a8, - fa_magic = 0xf0d0, - fa_magnet = 0xf076, - fa_mail_forward = 0xf064, - fa_mail_reply = 0xf112, - fa_mail_reply_all = 0xf122, - fa_male = 0xf183, - fa_map = 0xf279, - fa_map_marker = 0xf041, - fa_map_o = 0xf278, - fa_map_pin = 0xf276, - fa_map_signs = 0xf277, - fa_mars = 0xf222, - fa_mars_double = 0xf227, - fa_mars_stroke = 0xf229, - fa_mars_stroke_h = 0xf22b, - fa_mars_stroke_v = 0xf22a, - fa_maxcdn = 0xf136, - fa_meanpath = 0xf20c, - fa_medium = 0xf23a, - fa_medkit = 0xf0fa, - fa_meetup = 0xf2e0, - fa_meh_o = 0xf11a, - fa_mercury = 0xf223, - fa_microchip = 0xf2db, - fa_microphone = 0xf130, - fa_microphone_slash = 0xf131, - fa_minus = 0xf068, - fa_minus_circle = 0xf056, - fa_minus_square = 0xf146, - fa_minus_square_o = 0xf147, - fa_mixcloud = 0xf289, - fa_mobile = 0xf10b, - fa_mobile_phone = 0xf10b, - fa_modx = 0xf285, - fa_money = 0xf0d6, - fa_moon_o = 0xf186, - fa_mortar_board = 0xf19d, - fa_motorcycle = 0xf21c, - fa_mouse_pointer = 0xf245, - fa_music = 0xf001, - fa_navicon = 0xf0c9, - fa_neuter = 0xf22c, - fa_newspaper_o = 0xf1ea, - fa_object_group = 0xf247, - fa_object_ungroup = 0xf248, - fa_odnoklassniki = 0xf263, - fa_odnoklassniki_square = 0xf264, - fa_opencart = 0xf23d, - fa_openid = 0xf19b, - fa_opera = 0xf26a, - fa_optin_monster = 0xf23c, - fa_outdent = 0xf03b, - fa_pagelines = 0xf18c, - fa_paint_brush = 0xf1fc, - fa_paper_plane = 0xf1d8, - fa_paper_plane_o = 0xf1d9, - fa_paperclip = 0xf0c6, - fa_paragraph = 0xf1dd, - fa_paste = 0xf0ea, - fa_pause = 0xf04c, - fa_pause_circle = 0xf28b, - fa_pause_circle_o = 0xf28c, - fa_paw = 0xf1b0, - fa_paypal = 0xf1ed, - fa_pencil = 0xf040, - fa_pencil_square = 0xf14b, - fa_pencil_square_o = 0xf044, - fa_percent = 0xf295, - fa_phone = 0xf095, - fa_phone_square = 0xf098, - fa_photo = 0xf03e, - fa_picture_o = 0xf03e, - fa_pie_chart = 0xf200, - fa_pied_piper = 0xf2ae, - fa_pied_piper_alt = 0xf1a8, - fa_pied_piper_pp = 0xf1a7, - fa_pinterest = 0xf0d2, - fa_pinterest_p = 0xf231, - fa_pinterest_square = 0xf0d3, - fa_plane = 0xf072, - fa_play = 0xf04b, - fa_play_circle = 0xf144, - fa_play_circle_o = 0xf01d, - fa_plug = 0xf1e6, - fa_plus = 0xf067, - fa_plus_circle = 0xf055, - fa_plus_square = 0xf0fe, - fa_plus_square_o = 0xf196, - fa_podcast = 0xf2ce, - fa_power_off = 0xf011, - fa_print = 0xf02f, - fa_product_hunt = 0xf288, - fa_puzzle_piece = 0xf12e, - fa_qq = 0xf1d6, - fa_qrcode = 0xf029, - fa_question = 0xf128, - fa_question_circle = 0xf059, - fa_question_circle_o = 0xf29c, - fa_quora = 0xf2c4, - fa_quote_left = 0xf10d, - fa_quote_right = 0xf10e, - fa_ra = 0xf1d0, - fa_random = 0xf074, - fa_ravelry = 0xf2d9, - fa_rebel = 0xf1d0, - fa_recycle = 0xf1b8, - fa_reddit = 0xf1a1, - fa_reddit_alien = 0xf281, - fa_reddit_square = 0xf1a2, - fa_refresh = 0xf021, - fa_registered = 0xf25d, - fa_remove = 0xf00d, - fa_renren = 0xf18b, - fa_reorder = 0xf0c9, - fa_repeat = 0xf01e, - fa_reply = 0xf112, - fa_reply_all = 0xf122, - fa_resistance = 0xf1d0, - fa_retweet = 0xf079, - fa_rmb = 0xf157, - fa_road = 0xf018, - fa_rocket = 0xf135, - fa_rotate_left = 0xf0e2, - fa_rotate_right = 0xf01e, - fa_rouble = 0xf158, - fa_rss = 0xf09e, - fa_rss_square = 0xf143, - fa_rub = 0xf158, - fa_ruble = 0xf158, - fa_rupee = 0xf156, - fa_s15 = 0xf2cd, - fa_safari = 0xf267, - fa_save = 0xf0c7, - fa_scissors = 0xf0c4, - fa_scribd = 0xf28a, - fa_search = 0xf002, - fa_search_minus = 0xf010, - fa_search_plus = 0xf00e, - fa_sellsy = 0xf213, - fa_send = 0xf1d8, - fa_send_o = 0xf1d9, - fa_server = 0xf233, - fa_share = 0xf064, - fa_share_alt = 0xf1e0, - fa_share_alt_square = 0xf1e1, - fa_share_square = 0xf14d, - fa_share_square_o = 0xf045, - fa_shekel = 0xf20b, - fa_sheqel = 0xf20b, - fa_shield = 0xf132, - fa_ship = 0xf21a, - fa_shirtsinbulk = 0xf214, - fa_shopping_bag = 0xf290, - fa_shopping_basket = 0xf291, - fa_shopping_cart = 0xf07a, - fa_shower = 0xf2cc, - fa_sign_in = 0xf090, - fa_sign_language = 0xf2a7, - fa_sign_out = 0xf08b, - fa_signal = 0xf012, - fa_signing = 0xf2a7, - fa_simplybuilt = 0xf215, - fa_sitemap = 0xf0e8, - fa_skyatlas = 0xf216, - fa_skype = 0xf17e, - fa_slack = 0xf198, - fa_sliders = 0xf1de, - fa_slideshare = 0xf1e7, - fa_smile_o = 0xf118, - fa_snapchat = 0xf2ab, - fa_snapchat_ghost = 0xf2ac, - fa_snapchat_square = 0xf2ad, - fa_snowflake_o = 0xf2dc, - fa_soccer_ball_o = 0xf1e3, - fa_sort = 0xf0dc, - fa_sort_alpha_asc = 0xf15d, - fa_sort_alpha_desc = 0xf15e, - fa_sort_amount_asc = 0xf160, - fa_sort_amount_desc = 0xf161, - fa_sort_asc = 0xf0de, - fa_sort_desc = 0xf0dd, - fa_sort_down = 0xf0dd, - fa_sort_numeric_asc = 0xf162, - fa_sort_numeric_desc = 0xf163, - fa_sort_up = 0xf0de, - fa_soundcloud = 0xf1be, - fa_space_shuttle = 0xf197, - fa_spinner = 0xf110, - fa_spoon = 0xf1b1, - fa_spotify = 0xf1bc, - fa_square = 0xf0c8, - fa_square_o = 0xf096, - fa_stack_exchange = 0xf18d, - fa_stack_overflow = 0xf16c, - fa_star = 0xf005, - fa_star_half = 0xf089, - fa_star_half_empty = 0xf123, - fa_star_half_full = 0xf123, - fa_star_half_o = 0xf123, - fa_star_o = 0xf006, - fa_steam = 0xf1b6, - fa_steam_square = 0xf1b7, - fa_step_backward = 0xf048, - fa_step_forward = 0xf051, - fa_stethoscope = 0xf0f1, - fa_sticky_note = 0xf249, - fa_sticky_note_o = 0xf24a, - fa_stop = 0xf04d, - fa_stop_circle = 0xf28d, - fa_stop_circle_o = 0xf28e, - fa_street_view = 0xf21d, - fa_strikethrough = 0xf0cc, - fa_stumbleupon = 0xf1a4, - fa_stumbleupon_circle = 0xf1a3, - fa_subscript = 0xf12c, - fa_subway = 0xf239, - fa_suitcase = 0xf0f2, - fa_sun_o = 0xf185, - fa_superpowers = 0xf2dd, - fa_superscript = 0xf12b, - fa_support = 0xf1cd, - fa_table = 0xf0ce, - fa_tablet = 0xf10a, - fa_tachometer = 0xf0e4, - fa_tag = 0xf02b, - fa_tags = 0xf02c, - fa_tasks = 0xf0ae, - fa_taxi = 0xf1ba, - fa_telegram = 0xf2c6, - fa_television = 0xf26c, - fa_tencent_weibo = 0xf1d5, - fa_terminal = 0xf120, - fa_text_height = 0xf034, - fa_text_width = 0xf035, - fa_th = 0xf00a, - fa_th_large = 0xf009, - fa_th_list = 0xf00b, - fa_themeisle = 0xf2b2, - fa_thermometer = 0xf2c7, - fa_thermometer_0 = 0xf2cb, - fa_thermometer_1 = 0xf2ca, - fa_thermometer_2 = 0xf2c9, - fa_thermometer_3 = 0xf2c8, - fa_thermometer_4 = 0xf2c7, - fa_thermometer_empty = 0xf2cb, - fa_thermometer_full = 0xf2c7, - fa_thermometer_half = 0xf2c9, - fa_thermometer_quarter = 0xf2ca, - fa_thermometer_three_quarters = 0xf2c8, - fa_thumb_tack = 0xf08d, - fa_thumbs_down = 0xf165, - fa_thumbs_o_down = 0xf088, - fa_thumbs_o_up = 0xf087, - fa_thumbs_up = 0xf164, - fa_ticket = 0xf145, - fa_times = 0xf00d, - fa_times_circle = 0xf057, - fa_times_circle_o = 0xf05c, - fa_times_rectangle = 0xf2d3, - fa_times_rectangle_o = 0xf2d4, - fa_tint = 0xf043, - fa_toggle_down = 0xf150, - fa_toggle_left = 0xf191, - fa_toggle_off = 0xf204, - fa_toggle_on = 0xf205, - fa_toggle_right = 0xf152, - fa_toggle_up = 0xf151, - fa_trademark = 0xf25c, - fa_train = 0xf238, - fa_transgender = 0xf224, - fa_transgender_alt = 0xf225, - fa_trash = 0xf1f8, - fa_trash_o = 0xf014, - fa_tree = 0xf1bb, - fa_trello = 0xf181, - fa_tripadvisor = 0xf262, - fa_trophy = 0xf091, - fa_truck = 0xf0d1, - fa_try = 0xf195, - fa_tty = 0xf1e4, - fa_tumblr = 0xf173, - fa_tumblr_square = 0xf174, - fa_turkish_lira = 0xf195, - fa_tv = 0xf26c, - fa_twitch = 0xf1e8, - fa_twitter = 0xf099, - fa_twitter_square = 0xf081, - fa_umbrella = 0xf0e9, - fa_underline = 0xf0cd, - fa_undo = 0xf0e2, - fa_universal_access = 0xf29a, - fa_university = 0xf19c, - fa_unlink = 0xf127, - fa_unlock = 0xf09c, - fa_unlock_alt = 0xf13e, - fa_unsorted = 0xf0dc, - fa_upload = 0xf093, - fa_usb = 0xf287, - fa_usd = 0xf155, - fa_user = 0xf007, - fa_user_circle = 0xf2bd, - fa_user_circle_o = 0xf2be, - fa_user_md = 0xf0f0, - fa_user_o = 0xf2c0, - fa_user_plus = 0xf234, - fa_user_secret = 0xf21b, - fa_user_times = 0xf235, - fa_users = 0xf0c0, - fa_vcard = 0xf2bb, - fa_vcard_o = 0xf2bc, - fa_venus = 0xf221, - fa_venus_double = 0xf226, - fa_venus_mars = 0xf228, - fa_viacoin = 0xf237, - fa_viadeo = 0xf2a9, - fa_viadeo_square = 0xf2aa, - fa_video_camera = 0xf03d, - fa_vimeo = 0xf27d, - fa_vimeo_square = 0xf194, - fa_vine = 0xf1ca, - fa_vk = 0xf189, - fa_volume_control_phone = 0xf2a0, - fa_volume_down = 0xf027, - fa_volume_off = 0xf026, - fa_volume_up = 0xf028, - fa_warning = 0xf071, - fa_wechat = 0xf1d7, - fa_weibo = 0xf18a, - fa_weixin = 0xf1d7, - fa_whatsapp = 0xf232, - fa_wheelchair = 0xf193, - fa_wheelchair_alt = 0xf29b, - fa_wifi = 0xf1eb, - fa_wikipedia_w = 0xf266, - fa_window_close = 0xf2d3, - fa_window_close_o = 0xf2d4, - fa_window_maximize = 0xf2d0, - fa_window_minimize = 0xf2d1, - fa_window_restore = 0xf2d2, - fa_windows = 0xf17a, - fa_won = 0xf159, - fa_wordpress = 0xf19a, - fa_wpbeginner = 0xf297, - fa_wpexplorer = 0xf2de, - fa_wpforms = 0xf298, - fa_wrench = 0xf0ad, - fa_xing = 0xf168, - fa_xing_square = 0xf169, - fa_y_combinator = 0xf23b, - fa_y_combinator_square = 0xf1d4, - fa_yahoo = 0xf19e, - fa_yc = 0xf23b, - fa_yc_square = 0xf1d4, - fa_yelp = 0xf1e9, - fa_yen = 0xf157, - fa_yoast = 0xf2b1, - fa_youtube = 0xf167, - fa_youtube_play = 0xf16a, - fa_youtube_square = 0xf166, - - // ruleset icons in circles - fa_osu_osu_o = 0xe000, - fa_osu_mania_o = 0xe001, - fa_osu_fruits_o = 0xe002, - fa_osu_taiko_o = 0xe003, - - // ruleset icons without circles - fa_osu_filled_circle = 0xe004, - fa_osu_cross_o = 0xe005, - fa_osu_logo = 0xe006, - fa_osu_chevron_down_o = 0xe007, - fa_osu_edit_o = 0xe033, - fa_osu_left_o = 0xe034, - fa_osu_right_o = 0xe035, - fa_osu_charts = 0xe036, - fa_osu_solo = 0xe037, - fa_osu_multi = 0xe038, - fa_osu_gear = 0xe039, - - // misc icons - fa_osu_bat = 0xe008, - fa_osu_bubble = 0xe009, - fa_osu_bubble_pop = 0xe02e, - fa_osu_dice = 0xe011, - fa_osu_heart1 = 0xe02f, - fa_osu_heart1_break = 0xe030, - fa_osu_hot = 0xe031, - fa_osu_list_search = 0xe032, - - //osu! playstyles - fa_osu_playstyle_tablet = 0xe02a, - fa_osu_playstyle_mouse = 0xe029, - fa_osu_playstyle_keyboard = 0xe02b, - fa_osu_playstyle_touch = 0xe02c, - - // osu! difficulties - fa_osu_easy_osu = 0xe015, - fa_osu_normal_osu = 0xe016, - fa_osu_hard_osu = 0xe017, - fa_osu_insane_osu = 0xe018, - fa_osu_expert_osu = 0xe019, - - // taiko difficulties - fa_osu_easy_taiko = 0xe01a, - fa_osu_normal_taiko = 0xe01b, - fa_osu_hard_taiko = 0xe01c, - fa_osu_insane_taiko = 0xe01d, - fa_osu_expert_taiko = 0xe01e, - - // fruits difficulties - fa_osu_easy_fruits = 0xe01f, - fa_osu_normal_fruits = 0xe020, - fa_osu_hard_fruits = 0xe021, - fa_osu_insane_fruits = 0xe022, - fa_osu_expert_fruits = 0xe023, - - // mania difficulties - fa_osu_easy_mania = 0xe024, - fa_osu_normal_mania = 0xe025, - fa_osu_hard_mania = 0xe026, - fa_osu_insane_mania = 0xe027, - fa_osu_expert_mania = 0xe028, - - // mod icons - fa_osu_mod_perfect = 0xe049, - fa_osu_mod_autopilot = 0xe03a, - fa_osu_mod_auto = 0xe03b, - fa_osu_mod_cinema = 0xe03c, - fa_osu_mod_doubletime = 0xe03d, - fa_osu_mod_easy = 0xe03e, - fa_osu_mod_flashlight = 0xe03f, - fa_osu_mod_halftime = 0xe040, - fa_osu_mod_hardrock = 0xe041, - fa_osu_mod_hidden = 0xe042, - fa_osu_mod_nightcore = 0xe043, - fa_osu_mod_nofail = 0xe044, - fa_osu_mod_relax = 0xe045, - fa_osu_mod_spunout = 0xe046, - fa_osu_mod_suddendeath = 0xe047, - fa_osu_mod_target = 0xe048, - fa_osu_mod_bg = 0xe04a, - } -} diff --git a/osu.Game/Graphics/UserInterface/BackButton.cs b/osu.Game/Graphics/UserInterface/BackButton.cs index 16a9f367e6..10e8227f16 100644 --- a/osu.Game/Graphics/UserInterface/BackButton.cs +++ b/osu.Game/Graphics/UserInterface/BackButton.cs @@ -11,7 +11,7 @@ namespace osu.Game.Graphics.UserInterface public BackButton() { Text = @"back"; - Icon = FontAwesome.fa_osu_left_o; + Icon = OsuIcon.LeftCircle; Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; } diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs index 40bc98a654..8eb9b99f29 100644 --- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using System.Linq; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface { @@ -92,7 +93,7 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.CentreRight, Origin = Anchor.CentreLeft, Size = new Vector2(item_chevron_size), - Icon = FontAwesome.fa_chevron_right, + Icon = FontAwesome.ChevronRight, Margin = new MarginPadding { Left = padding }, Alpha = 0f, }); diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 2ed37799f6..14328930ce 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Platform; using osuTK; @@ -25,7 +26,7 @@ namespace osu.Game.Graphics.UserInterface Size = new Vector2(12); InternalChild = new SpriteIcon { - Icon = FontAwesome.fa_external_link, + Icon = FontAwesome.ExternalLink, RelativeSizeAxes = Axes.Both }; } diff --git a/osu.Game/Graphics/UserInterface/IconButton.cs b/osu.Game/Graphics/UserInterface/IconButton.cs index 025aa30986..6414e488e8 100644 --- a/osu.Game/Graphics/UserInterface/IconButton.cs +++ b/osu.Game/Graphics/UserInterface/IconButton.cs @@ -4,6 +4,7 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; namespace osu.Game.Graphics.UserInterface @@ -41,7 +42,7 @@ namespace osu.Game.Graphics.UserInterface /// /// The icon. /// - public FontAwesome Icon + public IconUsage Icon { get => icon.Icon; set => icon.Icon = value; diff --git a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs index c7c6d0462c..bb92d8a2a9 100644 --- a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs +++ b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -36,14 +37,14 @@ namespace osu.Game.Graphics.UserInterface Position = new Vector2(1, 1), Colour = Color4.Black, Alpha = 0.4f, - Icon = FontAwesome.fa_circle_o_notch + Icon = FontAwesome.CircleONotch }, spinner = new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_circle_o_notch + Icon = FontAwesome.CircleONotch } }; } diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index db38067a50..902fd310c5 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -179,7 +179,7 @@ namespace osu.Game.Graphics.UserInterface Chevron = new SpriteIcon { AlwaysPresent = true, - Icon = FontAwesome.fa_chevron_right, + Icon = FontAwesome.ChevronRight, Colour = Color4.Black, Alpha = 0.5f, Size = new Vector2(8), @@ -244,7 +244,7 @@ namespace osu.Game.Graphics.UserInterface }, Icon = new SpriteIcon { - Icon = FontAwesome.fa_chevron_down, + Icon = FontAwesome.ChevronDown, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Margin = new MarginPadding { Right = 4 }, diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs index aeb974681d..37a13f5274 100644 --- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Platform; @@ -107,7 +108,7 @@ namespace osu.Game.Graphics.UserInterface public CapsWarning() { - Icon = FontAwesome.fa_warning; + Icon = FontAwesome.Warning; } [BackgroundDependencyLoader] diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index e2a4955011..0ddc88b29e 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -254,7 +254,7 @@ namespace osu.Game.Graphics.UserInterface { new SpriteIcon { - Icon = FontAwesome.fa_ellipsis_h, + Icon = FontAwesome.EllipsisH, Size = new Vector2(14), Origin = Anchor.Centre, Anchor = Anchor.Centre, diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index 918473ac53..557a337941 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -99,7 +99,7 @@ namespace osu.Game.Graphics.UserInterface icon = new SpriteIcon { Size = new Vector2(14), - Icon = FontAwesome.fa_circle_o, + Icon = FontAwesome.CircleOutline, Shadow = true, }, }, @@ -120,12 +120,12 @@ namespace osu.Game.Graphics.UserInterface if (selected.NewValue) { fadeIn(); - icon.Icon = FontAwesome.fa_check_circle_o; + icon.Icon = FontAwesome.CheckCircleOutline; } else { fadeOut(); - icon.Icon = FontAwesome.fa_circle_o; + icon.Icon = FontAwesome.CircleOutline; } }; } diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index 54bb968d21..341f49732e 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osuTK; using osuTK.Input; @@ -21,7 +22,7 @@ namespace osu.Game.Graphics.UserInterface { new SpriteIcon { - Icon = FontAwesome.fa_search, + Icon = FontAwesome.Search, Origin = Anchor.CentreRight, Anchor = Anchor.CentreRight, Margin = new MarginPadding { Right = 10 }, diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index 7dc05d174f..ac6e393435 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; using System; using System.Linq; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface { @@ -142,7 +143,7 @@ namespace osu.Game.Graphics.UserInterface Child = Icon = new SpriteIcon { Size = new Vector2(star_size), - Icon = FontAwesome.fa_star, + Icon = FontAwesome.Star, Anchor = Anchor.Centre, Origin = Anchor.Centre, }; diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index 1d8298904b..9911a7c368 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -149,7 +149,7 @@ namespace osu.Game.Graphics.UserInterface }; } - public FontAwesome Icon + public IconUsage Icon { set => bouncingIcon.Icon = value; } @@ -207,7 +207,7 @@ namespace osu.Game.Graphics.UserInterface private readonly SpriteIcon icon; - public FontAwesome Icon + public IconUsage Icon { set => icon.Icon = value; } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index c5602fc4ad..da5cc76060 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -258,8 +258,8 @@ namespace osu.Game.Online.Leaderboards protected virtual IEnumerable GetStatistics(ScoreInfo model) => new[] { - new LeaderboardScoreStatistic(FontAwesome.fa_link, "Max Combo", model.MaxCombo.ToString()), - new LeaderboardScoreStatistic(FontAwesome.fa_crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)) + new LeaderboardScoreStatistic(FontAwesome.Link, "Max Combo", model.MaxCombo.ToString()), + new LeaderboardScoreStatistic(FontAwesome.Crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)) }; protected override bool OnHover(HoverEvent e) @@ -353,7 +353,7 @@ namespace osu.Game.Online.Leaderboards Size = new Vector2(icon_size), Rotation = 45, Colour = OsuColour.FromHex(@"3087ac"), - Icon = FontAwesome.fa_square, + Icon = FontAwesome.Square, Shadow = true, }, new SpriteIcon @@ -378,11 +378,11 @@ namespace osu.Game.Online.Leaderboards public class LeaderboardScoreStatistic { - public FontAwesome Icon; + public IconUsage Icon; public string Value; public string Name; - public LeaderboardScoreStatistic(FontAwesome icon, string name, string value) + public LeaderboardScoreStatistic(IconUsage icon, string name, string value) { Icon = icon; Name = name; diff --git a/osu.Game/Online/Leaderboards/MessagePlaceholder.cs b/osu.Game/Online/Leaderboards/MessagePlaceholder.cs index d4256e4a9d..b4980444d1 100644 --- a/osu.Game/Online/Leaderboards/MessagePlaceholder.cs +++ b/osu.Game/Online/Leaderboards/MessagePlaceholder.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Online.Leaderboards { @@ -12,7 +12,7 @@ namespace osu.Game.Online.Leaderboards public MessagePlaceholder(string message) { - AddIcon(FontAwesome.fa_exclamation_circle, cp => + AddIcon(FontAwesome.ExclamationCircle, cp => { cp.Font = cp.Font.With(size: TEXT_SIZE); cp.Padding = new MarginPadding { Right = 10 }; diff --git a/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs b/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs index 9edd578967..9a35dbc476 100644 --- a/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs +++ b/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs @@ -3,8 +3,8 @@ using System; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; @@ -41,7 +41,7 @@ namespace osu.Game.Online.Leaderboards Action = () => Action?.Invoke(), Child = icon = new SpriteIcon { - Icon = FontAwesome.fa_refresh, + Icon = FontAwesome.Refresh, Size = new Vector2(TEXT_SIZE), Shadow = true, }, diff --git a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTag.cs b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTag.cs index 4d6a792377..d51c5eb9bb 100644 --- a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTag.cs +++ b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTag.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osuTK; @@ -17,7 +18,7 @@ namespace osu.Game.Online.Multiplayer.GameTypes { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_refresh, + Icon = FontAwesome.Refresh, Size = new Vector2(size), Colour = colours.Blue, Shadow = false, diff --git a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTagTeam.cs b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTagTeam.cs index 350e609b83..266f4a77b2 100644 --- a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTagTeam.cs +++ b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTagTeam.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osuTK; @@ -25,14 +26,14 @@ namespace osu.Game.Online.Multiplayer.GameTypes { new SpriteIcon { - Icon = FontAwesome.fa_refresh, + Icon = FontAwesome.Refresh, Size = new Vector2(size * 0.75f), Colour = colours.Blue, Shadow = false, }, new SpriteIcon { - Icon = FontAwesome.fa_refresh, + Icon = FontAwesome.Refresh, Size = new Vector2(size * 0.75f), Colour = colours.Pink, Shadow = false, diff --git a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTimeshift.cs b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTimeshift.cs index 8971368db1..1271556db4 100644 --- a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTimeshift.cs +++ b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTimeshift.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osuTK; @@ -15,7 +16,7 @@ namespace osu.Game.Online.Multiplayer.GameTypes { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_clock_o, + Icon = FontAwesome.ClockOutline, Size = new Vector2(size), Colour = colours.Blue, Shadow = false diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7277990987..8b78873dcb 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -22,6 +22,7 @@ using System.Threading.Tasks; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Platform; @@ -587,7 +588,7 @@ namespace osu.Game { Schedule(() => notifications.Post(new SimpleNotification { - Icon = entry.Level == LogLevel.Important ? FontAwesome.fa_exclamation_circle : FontAwesome.fa_bomb, + Icon = entry.Level == LogLevel.Important ? FontAwesome.ExclamationCircle : FontAwesome.Bomb, Text = entry.Message + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty), })); } @@ -595,7 +596,7 @@ namespace osu.Game { Schedule(() => notifications.Post(new SimpleNotification { - Icon = FontAwesome.fa_ellipsis_h, + Icon = FontAwesome.EllipsisH, Text = "Subsequent messages have been logged. Click to view log files.", Activated = () => { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7d55d19e50..44776bb2a8 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -119,9 +119,6 @@ namespace osu.Game dependencies.CacheAs(this); dependencies.Cache(LocalConfig); - //this completely overrides the framework default. will need to change once we make a proper FontStore. - dependencies.Cache(Fonts = new FontStore(new GlyphStore(Resources, @"Fonts/FontAwesome"))); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/osuFont")); Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Medium")); Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-MediumItalic")); diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index 9ed9875be9..e817b28589 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -74,10 +75,10 @@ namespace osu.Game.Overlays.BeatmapSet Direction = FillDirection.Horizontal, Children = new[] { - length = new Statistic(FontAwesome.fa_clock_o, "Length") { Width = 0.25f }, - bpm = new Statistic(FontAwesome.fa_circle, "BPM") { Width = 0.25f }, - circleCount = new Statistic(FontAwesome.fa_circle_o, "Circle Count") { Width = 0.25f }, - sliderCount = new Statistic(FontAwesome.fa_circle, "Slider Count") { Width = 0.25f }, + length = new Statistic(FontAwesome.ClockOutline, "Length") { Width = 0.25f }, + bpm = new Statistic(FontAwesome.Circle, "BPM") { Width = 0.25f }, + circleCount = new Statistic(FontAwesome.CircleOutline, "Circle Count") { Width = 0.25f }, + sliderCount = new Statistic(FontAwesome.Circle, "Slider Count") { Width = 0.25f }, }, }; } @@ -101,7 +102,7 @@ namespace osu.Game.Overlays.BeatmapSet set => this.value.Text = value; } - public Statistic(FontAwesome icon, string name) + public Statistic(IconUsage icon, string name) { this.name = name; RelativeSizeAxes = Axes.X; @@ -120,7 +121,7 @@ namespace osu.Game.Overlays.BeatmapSet { Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, - Icon = FontAwesome.fa_square, + Icon = FontAwesome.Square, Size = new Vector2(13), Rotation = 45, Colour = OsuColour.FromHex(@"441288"), diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 55dee904b4..1d4f181256 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -10,6 +10,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -130,8 +131,8 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Top = 5 }, Children = new[] { - plays = new Statistic(FontAwesome.fa_play_circle), - favourites = new Statistic(FontAwesome.fa_heart), + plays = new Statistic(FontAwesome.PlayCircle), + favourites = new Statistic(FontAwesome.Heart), }, }, }, @@ -292,7 +293,7 @@ namespace osu.Game.Overlays.BeatmapSet } } - public Statistic(FontAwesome icon) + public Statistic(IconUsage icon) { AutoSizeAxes = Axes.Both; Direction = FillDirection.Horizontal; diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs index bbbcff0558..667869e310 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -77,7 +78,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons Depth = -1, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Icon = FontAwesome.fa_download, + Icon = FontAwesome.Download, Size = new Vector2(16), Margin = new MarginPadding { Right = 5 }, }, diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 7824a78a14..43c14e2a58 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osuTK; @@ -47,7 +48,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_heart_o, + Icon = FontAwesome.HeartOutline, Size = new Vector2(18), Shadow = false, }, @@ -58,12 +59,12 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons if (favourited.NewValue) { pink.FadeIn(200); - icon.Icon = FontAwesome.fa_heart; + icon.Icon = FontAwesome.Heart; } else { pink.FadeOut(200); - icon.Icon = FontAwesome.fa_heart_o; + icon.Icon = FontAwesome.HeartOutline; } }; diff --git a/osu.Game/Overlays/Chat/ExternalLinkDialog.cs b/osu.Game/Overlays/Chat/ExternalLinkDialog.cs index 6c3fb4e6f6..bcf63672ac 100644 --- a/osu.Game/Overlays/Chat/ExternalLinkDialog.cs +++ b/osu.Game/Overlays/Chat/ExternalLinkDialog.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; namespace osu.Game.Overlays.Chat @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Chat HeaderText = "Just checking..."; BodyText = $"You are about to leave osu! and open the following link in a web browser:\n\n{url}"; - Icon = FontAwesome.fa_warning; + Icon = FontAwesome.Warning; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs index a36abc4f99..a4953d6334 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -71,7 +72,7 @@ namespace osu.Game.Overlays.Chat.Selection { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Icon = FontAwesome.fa_check_circle, + Icon = FontAwesome.CheckCircle, Size = new Vector2(text_size), Shadow = false, Margin = new MarginPadding { Right = 10f }, @@ -118,7 +119,7 @@ namespace osu.Game.Overlays.Chat.Selection { new SpriteIcon { - Icon = FontAwesome.fa_user, + Icon = FontAwesome.User, Size = new Vector2(text_size - 2), Shadow = false, Margin = new MarginPadding { Top = 1 }, diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index 7a43ca4b8c..f8a8038878 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -3,13 +3,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Chat; using osuTK; using System; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Chat.Tabs { @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Chat.Tabs AddInternal(new SpriteIcon { - Icon = FontAwesome.fa_comments, + Icon = FontAwesome.Comments, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Size = new Vector2(20), diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs index 95c5fbf8fa..e1f29a40e4 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -117,7 +117,7 @@ namespace osu.Game.Overlays.Chat.Tabs }; } - protected virtual FontAwesome DisplayIcon => FontAwesome.fa_hashtag; + protected virtual IconUsage DisplayIcon => FontAwesome.Hashtag; protected virtual bool ShowCloseOnHover => true; diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs index 8111ac7394..f8add20674 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; @@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Chat.Tabs private readonly OsuSpriteText username; private readonly Avatar avatarContainer; - protected override FontAwesome DisplayIcon => FontAwesome.fa_at; + protected override IconUsage DisplayIcon => FontAwesome.At; public PrivateChannelTabItem(Channel value) : base(value) diff --git a/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs b/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs index e8217fa9f6..b15f568c94 100644 --- a/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs +++ b/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; using osuTK.Graphics; @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Chat.Tabs Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(0.75f), - Icon = FontAwesome.fa_close, + Icon = FontAwesome.Close, RelativeSizeAxes = Axes.Both, }; } diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 72e3cc4f6a..ede2f34574 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; @@ -38,7 +39,7 @@ namespace osu.Game.Overlays.Dialog private bool actionInvoked; - public FontAwesome Icon + public IconUsage Icon { get => icon.Icon; set => icon.Icon = value; @@ -165,7 +166,7 @@ namespace osu.Game.Overlays.Dialog { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Icon = FontAwesome.fa_close, + Icon = FontAwesome.Close, Size = new Vector2(50), }, }, diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index b35dbde639..b8168f692a 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; @@ -185,8 +186,8 @@ namespace osu.Game.Overlays.Direct Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding }, Children = new[] { - new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0), - new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), + new Statistic(FontAwesome.PlayCircle, SetInfo.OnlineInfo?.PlayCount ?? 0), + new Statistic(FontAwesome.Heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), }, }, statusContainer = new FillFlowContainer @@ -205,12 +206,12 @@ namespace osu.Game.Overlays.Direct if (SetInfo.OnlineInfo?.HasVideo ?? false) { - statusContainer.Add(new IconPill(FontAwesome.fa_film)); + statusContainer.Add(new IconPill(FontAwesome.Film)); } if (SetInfo.OnlineInfo?.HasStoryboard ?? false) { - statusContainer.Add(new IconPill(FontAwesome.fa_image)); + statusContainer.Add(new IconPill(FontAwesome.Image)); } statusContainer.Add(new BeatmapSetOnlineStatusPill diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index d857a0f042..518f6e498a 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -160,8 +161,8 @@ namespace osu.Game.Overlays.Direct Direction = FillDirection.Vertical, Children = new Drawable[] { - new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0), - new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), + new Statistic(FontAwesome.PlayCircle, SetInfo.OnlineInfo?.PlayCount ?? 0), + new Statistic(FontAwesome.Heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), new FillFlowContainer { Anchor = Anchor.TopRight, @@ -210,12 +211,12 @@ namespace osu.Game.Overlays.Direct if (SetInfo.OnlineInfo?.HasVideo ?? false) { - statusContainer.Add(new IconPill(FontAwesome.fa_film) { IconSize = new Vector2(20) }); + statusContainer.Add(new IconPill(FontAwesome.Film) { IconSize = new Vector2(20) }); } if (SetInfo.OnlineInfo?.HasStoryboard ?? false) { - statusContainer.Add(new IconPill(FontAwesome.fa_image) { IconSize = new Vector2(20) }); + statusContainer.Add(new IconPill(FontAwesome.Image) { IconSize = new Vector2(20) }); } statusContainer.Add(new BeatmapSetOnlineStatusPill diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 3867886f6d..2b509f370e 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -166,7 +166,7 @@ namespace osu.Game.Overlays.Direct } } - public Statistic(FontAwesome icon, int value = 0) + public Statistic(IconUsage icon, int value = 0) { Anchor = Anchor.TopRight; Origin = Anchor.TopRight; diff --git a/osu.Game/Overlays/Direct/DownloadButton.cs b/osu.Game/Overlays/Direct/DownloadButton.cs index f15413d522..7fc145d635 100644 --- a/osu.Game/Overlays/Direct/DownloadButton.cs +++ b/osu.Game/Overlays/Direct/DownloadButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -48,7 +49,7 @@ namespace osu.Game.Overlays.Direct Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(13), - Icon = FontAwesome.fa_download, + Icon = FontAwesome.Download, }, checkmark = new SpriteIcon { @@ -56,7 +57,7 @@ namespace osu.Game.Overlays.Direct Origin = Anchor.Centre, X = 8, Size = Vector2.Zero, - Icon = FontAwesome.fa_check, + Icon = FontAwesome.Check, } } } diff --git a/osu.Game/Overlays/Direct/Header.cs b/osu.Game/Overlays/Direct/Header.cs index e85cb3b4ac..80870dcb68 100644 --- a/osu.Game/Overlays/Direct/Header.cs +++ b/osu.Game/Overlays/Direct/Header.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using osuTK.Graphics; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.SearchableList; @@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Direct protected override DirectTab DefaultTab => DirectTab.Search; protected override Drawable CreateHeaderText() => new OsuSpriteText { Text = @"osu!direct", Font = OsuFont.GetFont(size: 25) }; - protected override FontAwesome Icon => FontAwesome.fa_osu_chevron_down_o; + protected override IconUsage Icon => OsuIcon.ChevronDownCircle; public Header() { diff --git a/osu.Game/Overlays/Direct/IconPill.cs b/osu.Game/Overlays/Direct/IconPill.cs index e7f516f449..d63bb2a292 100644 --- a/osu.Game/Overlays/Direct/IconPill.cs +++ b/osu.Game/Overlays/Direct/IconPill.cs @@ -4,7 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Direct private readonly Container iconContainer; - public IconPill(FontAwesome icon) + public IconPill(IconUsage icon) { AutoSizeAxes = Axes.Both; Masking = true; diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs index 3c5508ba00..05ef5c8496 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -73,7 +74,7 @@ namespace osu.Game.Overlays.Direct Origin = Anchor.Centre, FillMode = FillMode.Fit, RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_play, + Icon = FontAwesome.Play, }, loadingAnimation = new LoadingAnimation { @@ -115,7 +116,7 @@ namespace osu.Game.Overlays.Direct private void playingStateChanged(ValueChangedEvent e) { - icon.Icon = e.NewValue ? FontAwesome.fa_stop : FontAwesome.fa_play; + icon.Icon = e.NewValue ? FontAwesome.Stop : FontAwesome.Play; icon.FadeColour(e.NewValue || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint); if (e.NewValue) diff --git a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs index 82e24f550b..fb524e32c3 100644 --- a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Input.Bindings; using osu.Game.Overlays.Settings; @@ -9,7 +9,7 @@ namespace osu.Game.Overlays.KeyBinding { public class GlobalKeyBindingsSection : SettingsSection { - public override FontAwesome Icon => FontAwesome.fa_globe; + public override IconUsage Icon => FontAwesome.Globe; public override string Header => "Global"; public GlobalKeyBindingsSection(GlobalActionContainer manager) diff --git a/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs b/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs index 7b3bef90c0..1f4042c57c 100644 --- a/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Overlays.Settings; using osu.Game.Rulesets; @@ -9,7 +10,7 @@ namespace osu.Game.Overlays.KeyBinding { public class RulesetBindingsSection : SettingsSection { - public override FontAwesome Icon => (ruleset.CreateInstance().CreateIcon() as SpriteIcon)?.Icon ?? FontAwesome.fa_osu_hot; + public override IconUsage Icon => (ruleset.CreateInstance().CreateIcon() as SpriteIcon)?.Icon ?? OsuIcon.Hot; public override string Header => ruleset.Name; private readonly RulesetInfo ruleset; diff --git a/osu.Game/Overlays/KeyBindingOverlay.cs b/osu.Game/Overlays/KeyBindingOverlay.cs index 300563dc59..6259f39c66 100644 --- a/osu.Game/Overlays/KeyBindingOverlay.cs +++ b/osu.Game/Overlays/KeyBindingOverlay.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; @@ -66,7 +67,7 @@ namespace osu.Game.Overlays Y = -15, Size = new Vector2(15), Shadow = true, - Icon = FontAwesome.fa_chevron_left + Icon = FontAwesome.ChevronLeft }, new OsuSpriteText { diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 886a202c2a..fa0c2ace46 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -162,7 +162,7 @@ namespace osu.Game.Overlays.Music Anchor = Anchor.TopLeft; Origin = Anchor.TopLeft; Size = new Vector2(12); - Icon = FontAwesome.fa_bars; + Icon = FontAwesome.Bars; Alpha = 0f; Margin = new MarginPadding { Left = 5, Top = 2 }; } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index c1b742e4e5..de5204ad43 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -143,7 +143,7 @@ namespace osu.Game.Overlays Anchor = Anchor.Centre, Origin = Anchor.Centre, Action = prev, - Icon = FontAwesome.fa_step_backward, + Icon = FontAwesome.StepBackward, }, playButton = new MusicIconButton { @@ -152,14 +152,14 @@ namespace osu.Game.Overlays Scale = new Vector2(1.4f), IconScale = new Vector2(1.4f), Action = play, - Icon = FontAwesome.fa_play_circle_o, + Icon = FontAwesome.PlayCircleOutline, }, nextButton = new MusicIconButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, Action = () => next(), - Icon = FontAwesome.fa_step_forward, + Icon = FontAwesome.StepForward, }, } }, @@ -168,7 +168,7 @@ namespace osu.Game.Overlays Origin = Anchor.Centre, Anchor = Anchor.CentreRight, Position = new Vector2(-bottom_black_area_height / 2, 0), - Icon = FontAwesome.fa_bars, + Icon = FontAwesome.Bars, Action = () => playlist.ToggleVisibility(), }, } @@ -257,13 +257,13 @@ namespace osu.Game.Overlays progressBar.EndTime = track.Length; progressBar.CurrentTime = track.CurrentTime; - playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; + playButton.Icon = track.IsRunning ? FontAwesome.PauseCircleOutline : FontAwesome.PlayCircleOutline; } else { progressBar.CurrentTime = 0; progressBar.EndTime = 1; - playButton.Icon = FontAwesome.fa_play_circle_o; + playButton.Icon = FontAwesome.PlayCircleOutline; } } diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index ea6e250556..7abff9252f 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; @@ -174,7 +175,7 @@ namespace osu.Game.Overlays.Notifications { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_times_circle, + Icon = FontAwesome.TimesCircle, Size = new Vector2(20), } }; diff --git a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs index f4807b00a8..d5993e1f79 100644 --- a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Game.Graphics; using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Notifications { @@ -11,7 +12,7 @@ namespace osu.Game.Overlays.Notifications { public ProgressCompletionNotification() { - Icon = FontAwesome.fa_check; + Icon = FontAwesome.Check; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index aee056b63d..26852242d2 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; @@ -26,9 +27,9 @@ namespace osu.Game.Overlays.Notifications } } - private FontAwesome icon = FontAwesome.fa_info_circle; + private IconUsage icon = FontAwesome.InfoCircle; - public FontAwesome Icon + public IconUsage Icon { get => icon; set diff --git a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs index 722c9c9af2..7b07617e2e 100644 --- a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osuTK; @@ -49,7 +50,7 @@ namespace osu.Game.Overlays.Profile.Header Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_heart, + Icon = FontAwesome.Heart, Scale = new Vector2(0.45f), } }; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index c41d977701..28877c21f0 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -415,16 +415,16 @@ namespace osu.Game.Overlays.Profile websiteWithoutProtcol = websiteWithoutProtcol.Substring(protocolIndex + 2); } - tryAddInfoRightLine(FontAwesome.fa_map_marker, user.Location); - tryAddInfoRightLine(FontAwesome.fa_heart_o, user.Interests); - tryAddInfoRightLine(FontAwesome.fa_suitcase, user.Occupation); + tryAddInfoRightLine(FontAwesome.MapMarker, user.Location); + tryAddInfoRightLine(FontAwesome.HeartOutline, user.Interests); + tryAddInfoRightLine(FontAwesome.Suitcase, user.Occupation); infoTextRight.NewParagraph(); if (!string.IsNullOrEmpty(user.Twitter)) - tryAddInfoRightLine(FontAwesome.fa_twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); - tryAddInfoRightLine(FontAwesome.fa_gamepad, user.Discord); - tryAddInfoRightLine(FontAwesome.fa_skype, user.Skype, @"skype:" + user.Skype + @"?chat"); - tryAddInfoRightLine(FontAwesome.fa_lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); - tryAddInfoRightLine(FontAwesome.fa_globe, websiteWithoutProtcol, user.Website); + tryAddInfoRightLine(FontAwesome.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); + tryAddInfoRightLine(FontAwesome.Gamepad, user.Discord); + tryAddInfoRightLine(FontAwesome.Skype, user.Skype, @"skype:" + user.Skype + @"?chat"); + tryAddInfoRightLine(FontAwesome.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); + tryAddInfoRightLine(FontAwesome.Globe, websiteWithoutProtcol, user.Website); if (user.Statistics != null) { @@ -463,7 +463,7 @@ namespace osu.Game.Overlays.Profile badgeContainer.ShowBadges(user.Badges); } - private void tryAddInfoRightLine(FontAwesome icon, string str, string url = null) + private void tryAddInfoRightLine(IconUsage icon, string str, string url = null) { if (string.IsNullOrEmpty(str)) return; diff --git a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs index f55e5f8c59..48be91ea23 100644 --- a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs +++ b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs @@ -5,7 +5,7 @@ using osu.Framework.Bindables; using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.SearchableList @@ -37,8 +37,8 @@ namespace osu.Game.Overlays.SearchableList Direction = FillDirection.Horizontal, Children = new[] { - new DisplayStyleToggleButton(FontAwesome.fa_th_large, PanelDisplayStyle.Grid, DisplayStyle), - new DisplayStyleToggleButton(FontAwesome.fa_list_ul, PanelDisplayStyle.List, DisplayStyle), + new DisplayStyleToggleButton(FontAwesome.ThLarge, PanelDisplayStyle.Grid, DisplayStyle), + new DisplayStyleToggleButton(FontAwesome.ListUl, PanelDisplayStyle.List, DisplayStyle), }, }, Dropdown = new SlimEnumDropdown @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.SearchableList private readonly PanelDisplayStyle style; private readonly Bindable bindable; - public DisplayStyleToggleButton(FontAwesome icon, PanelDisplayStyle style, Bindable bindable) + public DisplayStyleToggleButton(IconUsage icon, PanelDisplayStyle style, Bindable bindable) { this.bindable = bindable; this.style = style; diff --git a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs index afdbe33adb..73dca956d1 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.SearchableList { @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.SearchableList protected abstract Color4 BackgroundColour { get; } protected abstract T DefaultTab { get; } protected abstract Drawable CreateHeaderText(); - protected abstract FontAwesome Icon { get; } + protected abstract IconUsage Icon { get; } protected SearchableListHeader() { diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs index dfb24a08ae..ea7011ea01 100644 --- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs +++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Audio; namespace osu.Game.Overlays.Settings.Sections @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class AudioSection : SettingsSection { public override string Header => "Audio"; - public override FontAwesome Icon => FontAwesome.fa_volume_up; + public override IconUsage Icon => FontAwesome.VolumeUp; public AudioSection() { diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs index 441ee12f0d..d90bb9be10 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSection.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Debug; namespace osu.Game.Overlays.Settings.Sections @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class DebugSection : SettingsSection { public override string Header => "Debug"; - public override FontAwesome Icon => FontAwesome.fa_bug; + public override IconUsage Icon => FontAwesome.Bug; public DebugSection() { diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index bf4034d641..e69a19b447 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs @@ -3,17 +3,17 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Graphics; using osu.Game.Overlays.Settings.Sections.Gameplay; using osu.Game.Rulesets; using System.Linq; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Settings.Sections { public class GameplaySection : SettingsSection { public override string Header => "Gameplay"; - public override FontAwesome Icon => FontAwesome.fa_circle_o; + public override IconUsage Icon => FontAwesome.CircleOutline; public GameplaySection() { diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index d6738250f9..078c01ce92 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -16,6 +16,7 @@ using System.ComponentModel; using osu.Game.Graphics; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; @@ -362,7 +363,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Icon = FontAwesome.fa_circle_o, + Icon = FontAwesome.CircleOutline, Size = new Vector2(14), }); diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs index ad1e714096..f571d5ff7c 100644 --- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.General; namespace osu.Game.Overlays.Settings.Sections @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class GeneralSection : SettingsSection { public override string Header => "General"; - public override FontAwesome Icon => FontAwesome.fa_gear; + public override IconUsage Icon => FontAwesome.Gear; public GeneralSection() { diff --git a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs index d37acf8700..92746d5117 100644 --- a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Graphics; namespace osu.Game.Overlays.Settings.Sections @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class GraphicsSection : SettingsSection { public override string Header => "Graphics"; - public override FontAwesome Icon => FontAwesome.fa_laptop; + public override IconUsage Icon => FontAwesome.Laptop; public GraphicsSection() { diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index d37a2a6d65..d193277a6b 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Input; namespace osu.Game.Overlays.Settings.Sections @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class InputSection : SettingsSection { public override string Header => "Input"; - public override FontAwesome Icon => FontAwesome.fa_keyboard_o; + public override IconUsage Icon => FontAwesome.KeyboardOutline; public InputSection(KeyBindingOverlay keyConfig) { diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs index 9b09a41c92..7ab3629e12 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; namespace osu.Game.Overlays.Settings.Sections.Maintenance @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { BodyText = "Everything?"; - Icon = FontAwesome.fa_trash_o; + Icon = FontAwesome.TrashOutline; HeaderText = @"Confirm deletion of"; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs index 98ed8ebdaa..41530e20ca 100644 --- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Maintenance; using osuTK; @@ -11,7 +11,7 @@ namespace osu.Game.Overlays.Settings.Sections public class MaintenanceSection : SettingsSection { public override string Header => "Maintenance"; - public override FontAwesome Icon => FontAwesome.fa_wrench; + public override IconUsage Icon => FontAwesome.Wrench; public MaintenanceSection() { diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs index 7c959525f7..f9f5d99c84 100644 --- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs +++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Online; namespace osu.Game.Overlays.Settings.Sections @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class OnlineSection : SettingsSection { public override string Header => "Online"; - public override FontAwesome Icon => FontAwesome.fa_globe; + public override IconUsage Icon => FontAwesome.Globe; public OnlineSection() { diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 4b0147eb5d..79b9076a52 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -5,8 +5,8 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Skinning; using osuTK; @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections public override string Header => "Skin"; - public override FontAwesome Icon => FontAwesome.fa_paint_brush; + public override IconUsage Icon => FontAwesome.PaintBrush; private readonly Bindable dropdownBindable = new Bindable { Default = SkinInfo.Default }; private readonly Bindable configBindable = new Bindable(); diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index 38a8b58a68..92e74d96a1 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Settings { @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.Settings protected FillFlowContainer FlowContent; protected override Container Content => FlowContent; - public abstract FontAwesome Icon { get; } + public abstract IconUsage Icon { get; } public abstract string Header { get; } public IEnumerable FilterableChildren => Children.OfType(); diff --git a/osu.Game/Overlays/Social/Header.cs b/osu.Game/Overlays/Social/Header.cs index cf8053ac6e..bf07c343e6 100644 --- a/osu.Game/Overlays/Social/Header.cs +++ b/osu.Game/Overlays/Social/Header.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Framework.Allocation; using System.ComponentModel; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Social { @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.Social protected override Color4 BackgroundColour => OsuColour.FromHex(@"38202e"); protected override SocialTab DefaultTab => SocialTab.AllPlayers; - protected override FontAwesome Icon => FontAwesome.fa_users; + protected override IconUsage Icon => FontAwesome.Users; protected override Drawable CreateHeaderText() { diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index dca0226499..241c3dfd51 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Toolbar new ToolbarMusicButton(), //new ToolbarButton //{ - // Icon = FontAwesome.fa_search + // Icon = FontAwesome.search //}, userArea = new ToolbarUserArea(), new ToolbarNotificationButton(), diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 855c7ad823..71374d5180 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -27,13 +27,13 @@ namespace osu.Game.Overlays.Toolbar IconContainer.Show(); } - public void SetIcon(FontAwesome icon) => SetIcon(new SpriteIcon + public void SetIcon(IconUsage icon) => SetIcon(new SpriteIcon { Size = new Vector2(20), Icon = icon }); - public FontAwesome Icon + public IconUsage Icon { set => SetIcon(value); } diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs index 226960564d..8ea21e88b5 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarChatButton() { - SetIcon(FontAwesome.fa_comments); + SetIcon(FontAwesome.Comments); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs b/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs index 38ce4c7ccf..1d07a3ae70 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarDirectButton() { - SetIcon(FontAwesome.fa_osu_chevron_down_o); + SetIcon(OsuIcon.ChevronDownCircle); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs index 3675c4578e..18a116127c 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { @@ -9,7 +9,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarHomeButton() { - Icon = FontAwesome.fa_home; + Icon = FontAwesome.Home; TooltipMain = "Home"; TooltipSub = "Return to the main menu"; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index 40ffc71d87..7f4c9d455e 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarMusicButton() { - Icon = FontAwesome.fa_music; + Icon = FontAwesome.Music; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs index 751045f61c..b3bd82ae38 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; @@ -23,7 +24,7 @@ namespace osu.Game.Overlays.Toolbar public ToolbarNotificationButton() { - Icon = FontAwesome.fa_bars; + Icon = FontAwesome.Bars; TooltipMain = "Notifications"; TooltipSub = "Waiting for 'ya"; diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs index 14f652f6fe..4e48ffd034 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarSettingsButton() { - Icon = FontAwesome.fa_gear; + Icon = FontAwesome.Gear; TooltipMain = "Settings"; TooltipSub = "Change your settings"; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs index d0e664ecae..769fa520cb 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarSocialButton() { - Icon = FontAwesome.fa_users; + Icon = FontAwesome.Users; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index 6061ead2da..090e443a0c 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -9,6 +9,7 @@ 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.Framework.Input.Events; using osu.Game.Graphics; @@ -71,7 +72,7 @@ namespace osu.Game.Overlays.Volume Current.ValueChanged += muted => { - icon.Icon = muted.NewValue ? FontAwesome.fa_volume_off : FontAwesome.fa_volume_up; + icon.Icon = muted.NewValue ? FontAwesome.VolumeOff : FontAwesome.VolumeUp; icon.Margin = new MarginPadding { Left = muted.NewValue ? width / 2 - 15 : width / 2 - 10 }; //Magic numbers to line up both icons because they're different widths }; Current.TriggerChange(); diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 1f9907caa7..be2ff33730 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -1,9 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Graphics; using System; using Newtonsoft.Json; +using osu.Framework.Graphics.Sprites; using osu.Game.IO.Serialization; namespace osu.Game.Rulesets.Mods @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mods /// The icon of this mod. /// [JsonIgnore] - public virtual FontAwesome Icon => FontAwesome.fa_question; + public virtual IconUsage Icon => FontAwesome.Question; /// /// The type of this mod. diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 1c76abbc4b..e70d58acea 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Replays; @@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Autoplay"; public override string Acronym => "AT"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto; + public override IconUsage Icon => OsuIcon.ModAuto; public override ModType Type => ModType.Automation; public override string Description => "Watch a perfect automated play through the song."; public override double ScoreMultiplier => 1; diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 6f8eed4a0a..3c6a3a54aa 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -10,7 +11,7 @@ namespace osu.Game.Rulesets.Mods public override string Name => "Cinema"; public override string Acronym => "CN"; public override bool HasImplementation => false; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_cinema; + public override IconUsage Icon => OsuIcon.ModCinema; public override string Description => "Watch the video without visual distractions."; } } diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index dded688e80..0dd5d7b815 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; +using osu.Framework.Graphics.Sprites; using osu.Framework.Timing; -using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Daycore"; public override string Acronym => "DC"; - public override FontAwesome Icon => FontAwesome.fa_question; + public override IconUsage Icon => FontAwesome.Question; public override string Description => "Whoaaaaa..."; public override void ApplyToClock(IAdjustableClock clock) diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 9ea9eb76bc..a5e76e32b1 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Double Time"; public override string Acronym => "DT"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_doubletime; + public override IconUsage Icon => OsuIcon.ModDoubletime; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Zoooooooooom..."; public override bool Ranked => true; diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index ef4de0e300..56ec0bec06 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Easy"; public override string Acronym => "EZ"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_easy; + public override IconUsage Icon => OsuIcon.ModEasy; public override ModType Type => ModType.DifficultyReduction; public override double ScoreMultiplier => 0.5; public override bool Ranked => true; diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 23e928d991..0ad99d13ff 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; @@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Flashlight"; public override string Acronym => "FL"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_flashlight; + public override IconUsage Icon => OsuIcon.ModFlashlight; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Restricted view area."; public override bool Ranked => true; diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index fe26c96214..27369f4c30 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Half Time"; public override string Acronym => "HT"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_halftime; + public override IconUsage Icon => OsuIcon.ModHalftime; public override ModType Type => ModType.DifficultyReduction; public override string Description => "Less zoom..."; public override bool Ranked => true; diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index 4b8792098e..2044cbeae2 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Hard Rock"; public override string Acronym => "HR"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_hardrock; + public override IconUsage Icon => OsuIcon.ModHardrock; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Everything just got a bit harder..."; public override Type[] IncompatibleMods => new[] { typeof(ModEasy) }; diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index e526125947..c7e3f0a78f 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -7,6 +7,7 @@ using osu.Game.Rulesets.Objects.Drawables; using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Rulesets.Mods { @@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Hidden"; public override string Acronym => "HD"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; + public override IconUsage Icon => OsuIcon.ModHidden; public override ModType Type => ModType.DifficultyIncrease; public override bool Ranked => true; diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index a689292ed7..dc0fc33088 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; +using osu.Framework.Graphics.Sprites; using osu.Framework.Timing; using osu.Game.Graphics; @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Nightcore"; public override string Acronym => "NC"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_nightcore; + public override IconUsage Icon => OsuIcon.ModNightcore; public override string Description => "Uguuuuuuuu..."; public override void ApplyToClock(IAdjustableClock clock) diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 5bcba289c6..1ee1f92d8c 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -10,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "No Fail"; public override string Acronym => "NF"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_nofail; + public override IconUsage Icon => OsuIcon.ModNofail; public override ModType Type => ModType.DifficultyReduction; public override string Description => "You can't fail, no matter what."; public override double ScoreMultiplier => 0.5; diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 5145f85124..e984fb8574 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; @@ -10,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Perfect"; public override string Acronym => "PF"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_perfect; + public override IconUsage Icon => OsuIcon.ModPerfect; public override string Description => "SS or quit."; protected override bool FailCondition(ScoreProcessor scoreProcessor) => scoreProcessor.Accuracy.Value != 1; diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs index ee59810a94..4feb89186c 100644 --- a/osu.Game/Rulesets/Mods/ModRelax.cs +++ b/osu.Game/Rulesets/Mods/ModRelax.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -10,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Relax"; public override string Acronym => "RX"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_relax; + public override IconUsage Icon => OsuIcon.ModRelax; public override ModType Type => ModType.Automation; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModSuddenDeath) }; diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 26223b24d1..6a82050d26 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; @@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Sudden Death"; public override string Acronym => "SD"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_suddendeath; + public override IconUsage Icon => OsuIcon.ModSuddendeath; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Miss and fail."; public override double ScoreMultiplier => 1; diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index 174070eb85..eccd848c48 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -3,7 +3,7 @@ using System; using System.Linq; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mods @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods public override string Name => "Wind Down"; public override string Acronym => "WD"; public override string Description => "Sloooow doooown..."; - public override FontAwesome Icon => FontAwesome.fa_chevron_circle_down; + public override IconUsage Icon => FontAwesome.ChevronCircleDown; public override double ScoreMultiplier => 1.0; protected override double FinalRateAdjustment => -0.25; diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index bf9af8a51d..d430c291cb 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -3,7 +3,7 @@ using System; using System.Linq; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mods @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods public override string Name => "Wind Up"; public override string Acronym => "WU"; public override string Description => "Can you keep up?"; - public override FontAwesome Icon => FontAwesome.fa_chevron_circle_up; + public override IconUsage Icon => FontAwesome.ChevronCircleUp; public override double ScoreMultiplier => 1.0; protected override double FinalRateAdjustment => 0.5; diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index feac49ca2c..013fffb7cb 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -5,9 +5,9 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mods; @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets public virtual HitObjectComposer CreateHitObjectComposer() => null; - public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_question_circle }; + public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.QuestionCircle }; public abstract string Description { get; } diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 9f80dea9f7..f9f6b5cc2f 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osuTK; @@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.UI private const float size = 80; - public FontAwesome Icon + public IconUsage Icon { get => modIcon.Icon; set => modIcon.Icon = value; @@ -47,7 +48,7 @@ namespace osu.Game.Rulesets.UI Origin = Anchor.Centre, Anchor = Anchor.Centre, Size = new Vector2(size), - Icon = FontAwesome.fa_osu_mod_bg, + Icon = OsuIcon.ModBg, Y = -6.5f, Shadow = true, }, diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 227ad29000..6d590780b0 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Timing; @@ -38,7 +39,7 @@ namespace osu.Game.Screens.Edit.Components Origin = Anchor.Centre, Scale = new Vector2(1.4f), IconScale = new Vector2(1.4f), - Icon = FontAwesome.fa_play_circle_o, + Icon = FontAwesome.PlayCircleOutline, Action = togglePause, Padding = new MarginPadding { Left = 20 } }, @@ -88,7 +89,7 @@ namespace osu.Game.Screens.Edit.Components { base.Update(); - playButton.Icon = adjustableClock.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; + playButton.Icon = adjustableClock.IsRunning ? FontAwesome.PauseCircleOutline : FontAwesome.PlayCircleOutline; } private class PlaybackTabControl : OsuTabControl diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 3f7672ae08..1f13797497 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -94,13 +94,13 @@ namespace osu.Game.Screens.Edit.Compose.Components { new DivisorButton { - Icon = FontAwesome.fa_chevron_left, + Icon = FontAwesome.ChevronLeft, Action = beatDivisor.Previous }, new DivisorText(beatDivisor), new DivisorButton { - Icon = FontAwesome.fa_chevron_right, + Icon = FontAwesome.ChevronRight, Action = beatDivisor.Next } }, diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 3b24925f2c..2bed231da7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osuTK; @@ -90,7 +91,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { RelativeSizeAxes = Axes.Y, Height = 0.5f, - Icon = FontAwesome.fa_search_plus, + Icon = FontAwesome.SearchPlus, Action = () => changeZoom(1) }, new TimelineButton @@ -99,7 +100,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.Y, Height = 0.5f, - Icon = FontAwesome.fa_search_minus, + Icon = FontAwesome.SearchMinus, Action = () => changeZoom(-1) }, } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs index 5ded97393b..49e97e698b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osuTK; @@ -17,7 +18,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public Action Action; public readonly BindableBool Enabled = new BindableBool(true); - public FontAwesome Icon + public IconUsage Icon { get => button.Icon; set => button.Icon = value; diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index a02c2a37fa..794fc093d3 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -9,7 +9,6 @@ using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -17,6 +16,7 @@ using osuTK.Input; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Menu public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); - public Button(string text, string sampleName, FontAwesome symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown) + public Button(string text, string sampleName, IconUsage symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown) { this.sampleName = sampleName; this.clickAction = clickAction; diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 3df4ef9059..bcd24fd83e 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -11,6 +11,7 @@ using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Logging; using osu.Framework.Platform; @@ -79,8 +80,8 @@ namespace osu.Game.Screens.Menu buttonArea.AddRange(new[] { - new Button(@"settings", string.Empty, FontAwesome.fa_gear, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), - backButton = new Button(@"back", @"button-back-select", FontAwesome.fa_osu_left_o, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) + new Button(@"settings", string.Empty, FontAwesome.Gear, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), + backButton = new Button(@"back", @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) { VisibleState = ButtonSystemState.Play, }, @@ -105,17 +106,17 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader(true)] private void load(AudioManager audio, IdleTracker idleTracker, GameHost host) { - buttonsPlay.Add(new Button(@"solo", @"button-solo-select", FontAwesome.fa_user, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); - buttonsPlay.Add(new Button(@"multi", @"button-generic-select", FontAwesome.fa_users, new Color4(94, 63, 186, 255), onMulti, 0, Key.M)); - buttonsPlay.Add(new Button(@"chart", @"button-generic-select", FontAwesome.fa_osu_charts, new Color4(80, 53, 160, 255), () => OnChart?.Invoke())); + buttonsPlay.Add(new Button(@"solo", @"button-solo-select", FontAwesome.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); + buttonsPlay.Add(new Button(@"multi", @"button-generic-select", FontAwesome.Users, new Color4(94, 63, 186, 255), onMulti, 0, Key.M)); + buttonsPlay.Add(new Button(@"chart", @"button-generic-select", OsuIcon.Charts, new Color4(80, 53, 160, 255), () => OnChart?.Invoke())); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); - buttonsTopLevel.Add(new Button(@"play", @"button-play-select", FontAwesome.fa_osu_logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); - buttonsTopLevel.Add(new Button(@"osu!editor", @"button-generic-select", FontAwesome.fa_osu_edit_o, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); - buttonsTopLevel.Add(new Button(@"osu!direct", @"button-direct-select", FontAwesome.fa_osu_chevron_down_o, new Color4(165, 204, 0, 255), () => OnDirect?.Invoke(), 0, Key.D)); + buttonsTopLevel.Add(new Button(@"play", @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); + buttonsTopLevel.Add(new Button(@"osu!editor", @"button-generic-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); + buttonsTopLevel.Add(new Button(@"osu!direct", @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnDirect?.Invoke(), 0, Key.D)); if (host.CanExit) - buttonsTopLevel.Add(new Button(@"exit", string.Empty, FontAwesome.fa_osu_cross_o, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); + buttonsTopLevel.Add(new Button(@"exit", string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); buttonArea.AddRange(buttonsPlay); buttonArea.AddRange(buttonsTopLevel); @@ -134,7 +135,7 @@ namespace osu.Game.Screens.Menu notifications?.Post(new SimpleNotification { Text = "You gotta be logged in to multi 'yo!", - Icon = FontAwesome.fa_globe + Icon = FontAwesome.Globe }); return; diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index e6a90f76c0..170209207b 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Menu { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_warning, + Icon = FontAwesome.Warning, Size = new Vector2(icon_size), Y = icon_y, }, @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Menu supportFlow.AddText(" to help support the game", format); } - heart = supportFlow.AddIcon(FontAwesome.fa_heart, t => + heart = supportFlow.AddIcon(FontAwesome.Heart, t => { t.Padding = new MarginPadding { Left = 5 }; t.Font = t.Font.With(size: 12); diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 0e958bf523..dbfdc86571 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -53,7 +54,7 @@ namespace osu.Game.Screens.Multi new SpriteIcon { Size = new Vector2(25), - Icon = FontAwesome.fa_osu_multi, + Icon = OsuIcon.Multi, }, new FillFlowContainer { diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs index ed09203f96..2734c55ce7 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Scoring; @@ -25,9 +25,9 @@ namespace osu.Game.Screens.Multi.Match.Components protected override IEnumerable GetStatistics(ScoreInfo model) => new[] { - new LeaderboardScoreStatistic(FontAwesome.fa_crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)), - new LeaderboardScoreStatistic(FontAwesome.fa_refresh, "Total Attempts", ((APIRoomScoreInfo)model).TotalAttempts.ToString()), - new LeaderboardScoreStatistic(FontAwesome.fa_check, "Completed Beatmaps", ((APIRoomScoreInfo)model).CompletedBeatmaps.ToString()), + new LeaderboardScoreStatistic(FontAwesome.Crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)), + new LeaderboardScoreStatistic(FontAwesome.Refresh, "Total Attempts", ((APIRoomScoreInfo)model).TotalAttempts.ToString()), + new LeaderboardScoreStatistic(FontAwesome.Check, "Completed Beatmaps", ((APIRoomScoreInfo)model).CompletedBeatmaps.ToString()), }; } } diff --git a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs index ad72072981..65e501b114 100644 --- a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs +++ b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs @@ -14,8 +14,6 @@ namespace osu.Game.Screens.Multi { public override bool DisallowExternalBeatmapRulesetChanges => false; - public override bool RemoveWhenNotAlive => false; - public virtual string ShortTitle => Title; [Resolved(CanBeNull = true)] diff --git a/osu.Game/Screens/Multi/Ranking/Types/RoomLeaderboardPageInfo.cs b/osu.Game/Screens/Multi/Ranking/Types/RoomLeaderboardPageInfo.cs index 6cc13f88a5..b03fafbd13 100644 --- a/osu.Game/Screens/Multi/Ranking/Types/RoomLeaderboardPageInfo.cs +++ b/osu.Game/Screens/Multi/Ranking/Types/RoomLeaderboardPageInfo.cs @@ -1,8 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Scoring; using osu.Game.Screens.Multi.Ranking.Pages; using osu.Game.Screens.Ranking; @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Multi.Ranking.Types this.beatmap = beatmap; } - public FontAwesome Icon => FontAwesome.fa_users; + public IconUsage Icon => FontAwesome.Users; public string Name => "Room Leaderboard"; diff --git a/osu.Game/Screens/Play/Break/BlurredIcon.cs b/osu.Game/Screens/Play/Break/BlurredIcon.cs index 53b968959c..a88112a0db 100644 --- a/osu.Game/Screens/Play/Break/BlurredIcon.cs +++ b/osu.Game/Screens/Play/Break/BlurredIcon.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osuTK; @@ -13,7 +14,7 @@ namespace osu.Game.Screens.Play.Break { private readonly SpriteIcon icon; - public FontAwesome Icon + public IconUsage Icon { set => icon.Icon = value; get => icon.Icon; diff --git a/osu.Game/Screens/Play/Break/BreakArrows.cs b/osu.Game/Screens/Play/Break/BreakArrows.cs index 9d9f0ab898..e0238f6814 100644 --- a/osu.Game/Screens/Play/Break/BreakArrows.cs +++ b/osu.Game/Screens/Play/Break/BreakArrows.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; using osuTK; @@ -41,7 +42,7 @@ namespace osu.Game.Screens.Play.Break Anchor = Anchor.Centre, Origin = Anchor.CentreRight, X = -glow_icon_offscreen_offset, - Icon = Graphics.FontAwesome.fa_chevron_right, + Icon = FontAwesome.ChevronRight, BlurSigma = new Vector2(glow_icon_blur_sigma), Size = new Vector2(glow_icon_size), }, @@ -50,7 +51,7 @@ namespace osu.Game.Screens.Play.Break Anchor = Anchor.Centre, Origin = Anchor.CentreLeft, X = glow_icon_offscreen_offset, - Icon = Graphics.FontAwesome.fa_chevron_left, + Icon = FontAwesome.ChevronLeft, BlurSigma = new Vector2(glow_icon_blur_sigma), Size = new Vector2(glow_icon_size), }, @@ -67,7 +68,7 @@ namespace osu.Game.Screens.Play.Break Origin = Anchor.CentreRight, Alpha = 0.7f, X = -blurred_icon_offscreen_offset, - Icon = Graphics.FontAwesome.fa_chevron_right, + Icon = FontAwesome.ChevronRight, BlurSigma = new Vector2(blurred_icon_blur_sigma), Size = new Vector2(blurred_icon_size), }, @@ -77,7 +78,7 @@ namespace osu.Game.Screens.Play.Break Origin = Anchor.CentreLeft, Alpha = 0.7f, X = blurred_icon_offscreen_offset, - Icon = Graphics.FontAwesome.fa_chevron_left, + Icon = FontAwesome.ChevronLeft, BlurSigma = new Vector2(blurred_icon_blur_sigma), Size = new Vector2(blurred_icon_size), }, diff --git a/osu.Game/Screens/Play/Break/GlowIcon.cs b/osu.Game/Screens/Play/Break/GlowIcon.cs index 8d918cd225..2810389619 100644 --- a/osu.Game/Screens/Play/Break/GlowIcon.cs +++ b/osu.Game/Screens/Play/Break/GlowIcon.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osuTK; @@ -30,7 +31,7 @@ namespace osu.Game.Screens.Play.Break set => blurredIcon.BlurSigma = value; } - public FontAwesome Icon + public IconUsage Icon { get => spriteIcon.Icon; set => spriteIcon.Icon = blurredIcon.Icon = value; diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 50bc34726a..03843eeb90 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -128,7 +129,7 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(15), - Icon = FontAwesome.fa_close + Icon = FontAwesome.Close }, } }; diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs index efaeeea79f..d243ff24a3 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -103,7 +104,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Origin = Anchor.Centre, Anchor = Anchor.CentreRight, Position = new Vector2(-15, 0), - Icon = FontAwesome.fa_bars, + Icon = FontAwesome.Bars, Scale = new Vector2(0.75f), Action = () => Expanded = !Expanded, }, diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index a6e6009b95..78ed742bfa 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -15,6 +15,7 @@ using osu.Game.Screens.Ranking; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -258,9 +259,9 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Horizontal, Children = new[] { - new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_right }, - new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_right }, - new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_right }, + new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.ChevronRight }, + new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.ChevronRight }, + new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.ChevronRight }, } }, new OsuSpriteText diff --git a/osu.Game/Screens/Ranking/IResultPageInfo.cs b/osu.Game/Screens/Ranking/IResultPageInfo.cs index 5e0bec21f3..cc86e7441a 100644 --- a/osu.Game/Screens/Ranking/IResultPageInfo.cs +++ b/osu.Game/Screens/Ranking/IResultPageInfo.cs @@ -1,13 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Screens.Ranking { public interface IResultPageInfo { - FontAwesome Icon { get; } + IconUsage Icon { get; } string Name { get; } diff --git a/osu.Game/Screens/Ranking/ResultModeButton.cs b/osu.Game/Screens/Ranking/ResultModeButton.cs index b1fd8f9fde..109d0195db 100644 --- a/osu.Game/Screens/Ranking/ResultModeButton.cs +++ b/osu.Game/Screens/Ranking/ResultModeButton.cs @@ -11,12 +11,13 @@ using osu.Game.Graphics; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Screens.Ranking { public class ResultModeButton : TabItem, IHasTooltip { - private readonly FontAwesome icon; + private readonly IconUsage icon; private Color4 activeColour; private Color4 inactiveColour; private CircularContainer colouredPart; diff --git a/osu.Game/Screens/Ranking/Types/LocalLeaderboardPageInfo.cs b/osu.Game/Screens/Ranking/Types/LocalLeaderboardPageInfo.cs index e8a11ab1a4..e563eb8116 100644 --- a/osu.Game/Screens/Ranking/Types/LocalLeaderboardPageInfo.cs +++ b/osu.Game/Screens/Ranking/Types/LocalLeaderboardPageInfo.cs @@ -1,8 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Pages; @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Ranking.Types this.beatmap = beatmap; } - public FontAwesome Icon => FontAwesome.fa_user; + public IconUsage Icon => FontAwesome.User; public string Name => @"Local Leaderboard"; diff --git a/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs b/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs index d8e5e9b135..2d9b3b9ef9 100644 --- a/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs +++ b/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs @@ -1,8 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Pages; @@ -10,7 +10,7 @@ namespace osu.Game.Screens.Ranking.Types { public class ScoreOverviewPageInfo : IResultPageInfo { - public FontAwesome Icon => FontAwesome.fa_asterisk; + public IconUsage Icon => FontAwesome.Asterisk; public string Name => "Overview"; private readonly ScoreInfo score; diff --git a/osu.Game/Screens/ScreenWhiteBox.cs b/osu.Game/Screens/ScreenWhiteBox.cs index b222b91221..f471cab063 100644 --- a/osu.Game/Screens/ScreenWhiteBox.cs +++ b/osu.Game/Screens/ScreenWhiteBox.cs @@ -14,6 +14,7 @@ using osuTK.Graphics; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Screens { @@ -112,7 +113,7 @@ namespace osu.Game.Screens { new SpriteIcon { - Icon = FontAwesome.fa_universal_access, + Icon = FontAwesome.UniversalAccess, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Size = new Vector2(50), @@ -188,7 +189,7 @@ namespace osu.Game.Screens { public ChildModeButton() { - Icon = FontAwesome.fa_osu_right_o; + Icon = OsuIcon.RightCircle; Anchor = Anchor.BottomRight; Origin = Anchor.BottomRight; } diff --git a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs index a37327f2c3..aa579ac665 100644 --- a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs +++ b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs @@ -3,12 +3,12 @@ using osu.Framework.Allocation; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Overlays.Dialog; using osu.Game.Scoring; using System; using System.Linq; using System.Threading.Tasks; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Screens.Select { @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Select public BeatmapClearScoresDialog(BeatmapInfo beatmap, Action onCompletion) { BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}"; - Icon = FontAwesome.fa_eraser; + Icon = FontAwesome.Eraser; HeaderText = @"Clearing all local scores. Are you sure?"; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs index f2c1940ed8..a1adaff1d8 100644 --- a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs +++ b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Overlays.Dialog; namespace osu.Game.Screens.Select @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Select { BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}"; - Icon = FontAwesome.fa_trash_o; + Icon = FontAwesome.TrashOutline; HeaderText = @"Confirm deletion of"; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 32e7215e69..b2e08aeefd 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -22,6 +22,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Rulesets; using osu.Game.Rulesets.UI; @@ -292,14 +293,14 @@ namespace osu.Game.Screens.Select labels.Add(new InfoLabel(new BeatmapStatistic { Name = "Length", - Icon = FontAwesome.fa_clock_o, + Icon = FontAwesome.ClockOutline, Content = TimeSpan.FromMilliseconds(endTime - b.HitObjects.First().StartTime).ToString(@"m\:ss"), })); labels.Add(new InfoLabel(new BeatmapStatistic { Name = "BPM", - Icon = FontAwesome.fa_circle, + Icon = FontAwesome.Circle, Content = getBPMRange(b), })); @@ -377,7 +378,7 @@ namespace osu.Game.Screens.Select Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Colour = OsuColour.FromHex(@"441288"), - Icon = FontAwesome.fa_square, + Icon = FontAwesome.Square, Rotation = 45, }, new SpriteIcon diff --git a/osu.Game/Screens/Select/ImportFromStablePopup.cs b/osu.Game/Screens/Select/ImportFromStablePopup.cs index 537736a4ec..f1cc3d632c 100644 --- a/osu.Game/Screens/Select/ImportFromStablePopup.cs +++ b/osu.Game/Screens/Select/ImportFromStablePopup.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; namespace osu.Game.Screens.Select @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Select HeaderText = @"You have no beatmaps!"; BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps (and skins)?"; - Icon = FontAwesome.fa_plane; + Icon = FontAwesome.Plane; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs index 758e1c24c3..0f1f49bd85 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -32,7 +33,7 @@ namespace osu.Game.Screens.Select.Options set => background.Colour = value; } - public FontAwesome Icon + public IconUsage Icon { get => iconText.Icon; set => iconText.Icon = value; @@ -140,7 +141,7 @@ namespace osu.Game.Screens.Select.Options Anchor = Anchor.TopCentre, Size = new Vector2(30), Shadow = true, - Icon = FontAwesome.fa_close, + Icon = FontAwesome.Close, Margin = new MarginPadding { Bottom = 5, diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs index 5fedb2f8cc..669264cef0 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs @@ -6,7 +6,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -93,7 +93,7 @@ namespace osu.Game.Screens.Select.Options /// Lower depth to be put on the left, and higher to be put on the right. /// Notice this is different to ! /// - public void AddButton(string firstLine, string secondLine, FontAwesome icon, Color4 colour, Action action, Key? hotkey = null, float depth = 0) + public void AddButton(string firstLine, string secondLine, IconUsage icon, Color4 colour, Action action, Key? hotkey = null, float depth = 0) { var button = new BeatmapOptionsButton { diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index d06436c92e..6a10e86198 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -3,6 +3,7 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Screens.Play; @@ -20,7 +21,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader] private void load(OsuColour colours) { - BeatmapOptions.AddButton(@"Edit", @"beatmap", FontAwesome.fa_pencil, colours.Yellow, () => + BeatmapOptions.AddButton(@"Edit", @"beatmap", FontAwesome.Pencil, colours.Yellow, () => { ValidForResume = false; Edit(); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 8758df5151..0f8375b0d1 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -32,6 +32,7 @@ using osuTK.Input; using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Screens.Select { @@ -228,9 +229,9 @@ namespace osu.Game.Screens.Select Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2); Footer.AddButton(@"options", colours.Blue, BeatmapOptions, Key.F3); - BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.fa_trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); - BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, colours.Purple, null, Key.Number1); - BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.fa_eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo), Key.Number2); + BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); + BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.TimesCircleOutline, colours.Purple, null, Key.Number1); + BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo), Key.Number2); } if (this.beatmaps == null) diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 65062dc58e..1f62111a4e 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -16,6 +16,7 @@ using osu.Game.Overlays; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; using osu.Game.Overlays.Profile.Header; @@ -165,7 +166,7 @@ namespace osu.Game.Users { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Icon = FontAwesome.fa_circle_o, + Icon = FontAwesome.CircleOutline, Shadow = true, Size = new Vector2(14), }, From 061527a260241e78b5ce479006f6c91e2b91e271 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 27 Mar 2019 20:04:01 +0900 Subject: [PATCH 272/623] Add new automated tests for logofacade, reset interpolation --- .../Visual/TestCaseLogoFacadeContainer.cs | 115 ++++++++++++++---- .../Containers/LogoFacadeContainer.cs | 11 +- osu.Game/Screens/Play/PlayerLoader.cs | 4 +- 3 files changed, 103 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs index 152b9b1706..8be0d25a86 100644 --- a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs @@ -7,8 +7,10 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Screens; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Screens; @@ -25,7 +27,6 @@ namespace osu.Game.Tests.Visual { typeof(PlayerLoader), typeof(Player), - typeof(LogoFacadeContainer.Facade), typeof(LogoFacadeContainer), typeof(ButtonSystem), typeof(ButtonSystemState), @@ -47,36 +48,65 @@ namespace osu.Game.Tests.Visual private void load(OsuConfigManager config) { config.BindWith(OsuSetting.UIScale, uiScale); - AddSliderStep("Adjust scale", 1f, 1.5f, 1f, v => uiScale.Value = v); + AddSliderStep("Adjust scale", 0.8f, 1.5f, 1f, v => uiScale.Value = v); } + /// + /// Move the facade to 0,0, then move it to a random new location while the logo is still transforming to it. + /// Check if the logo is still tracking the facade. + /// [Test] - public void IsolatedTest() + public void MoveFacadeTest() { + TestScreen screen = null; bool randomPositions = false; AddToggleStep("Toggle move continuously", b => randomPositions = b); - AddStep("Move facade to random position", () => LoadScreen(new TestScreen(randomPositions))); + AddStep("Move facade to random position", () => LoadScreen(screen = new TestScreen(randomPositions))); + AddUntilStep("Screen is current", () => screen.IsCurrentScreen()); + waitForMove(); + AddAssert("Logo is tracking", () => screen.IsLogoTracking); } - private class TestLogoFacadeContainer : LogoFacadeContainer + /// + /// Check if the facade is removed from the container, the logo stops tracking. + /// + [Test] + public void RemoveFacadeTest() { - protected override Facade CreateFacade() => new Facade - { - Colour = Color4.Tomato, - Alpha = 0.35f, - Child = new Box - { - Colour = Color4.Tomato, - RelativeSizeAxes = Axes.Both, - }, - }; + TestScreen screen = null; + AddStep("Move facade to random position", () => LoadScreen(screen = new TestScreen())); + AddUntilStep("Screen is current", () => screen.IsCurrentScreen()); + AddStep("Remove facade from FacadeContainer", () => screen.RemoveFacade()); + waitForMove(); + AddAssert("Logo is not tracking", () => !screen.IsLogoTracking); } + /// + /// Check if the facade gets added to a new container, tracking starts on the new facade. + /// + [Test] + public void TransferFacadeTest() + { + TestScreen screen = null; + AddStep("Move facade to random position", () => LoadScreen(screen = new TestScreen())); + AddUntilStep("Screen is current", () => screen.IsCurrentScreen()); + AddStep("Remove facade from FacadeContainer", () => screen.RemoveFacade()); + AddStep("Transfer facade to a new container", () => screen.TransferFacade()); + waitForMove(); + AddAssert("Logo is tracking", () => screen.IsLogoTracking); + } + + private void waitForMove() => AddWaitStep("Wait for transforms to finish", 5); + private class TestScreen : OsuScreen { - private TestLogoFacadeContainer logoFacadeContainer; - private LogoFacadeContainer.Facade facadeFlowComponent; + private LogoFacadeContainer logoFacadeContainer; + private Container transferContainer; + private Container logoFacade; private readonly bool randomPositions; + private OsuLogo logo; + private Box visualBox; + private Box transferContainerBox; public TestScreen(bool randomPositions = false) { @@ -86,13 +116,55 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { - InternalChild = logoFacadeContainer = new TestLogoFacadeContainer(); - logoFacadeContainer.Child = facadeFlowComponent = logoFacadeContainer.LogoFacade; + InternalChildren = new Drawable[] + { + logoFacadeContainer = new LogoFacadeContainer + { + Alpha = 0.35f, + RelativeSizeAxes = Axes.None, + Size = new Vector2(107), + Child = visualBox = new Box + { + Colour = Color4.Tomato, + RelativeSizeAxes = Axes.Both, + } + }, + transferContainer = new Container + { + Alpha = 0.35f, + RelativeSizeAxes = Axes.None, + Size = new Vector2(107), + Child = transferContainerBox = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + } + }, + }; + + logoFacadeContainer.Add(logoFacade = logoFacadeContainer.LogoFacade); + } + + public bool IsLogoTracking => logo.Position == logo.Parent.ToLocalSpace(logoFacadeContainer.LogoFacade.ScreenSpaceDrawQuad.Centre); + + public void RemoveFacade() + { + logoFacadeContainer.Remove(logoFacade); + visualBox.Colour = Color4.White; + moveLogoFacade(); + } + + public void TransferFacade() + { + transferContainer.Add(logoFacade); + transferContainerBox.Colour = Color4.Tomato; + moveLogoFacade(); } protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); + this.logo = logo; logo.FadeIn(350); logo.ScaleTo(new Vector2(0.15f), 350, Easing.In); logoFacadeContainer.SetLogo(logo, 0.3f, 1000, Easing.InOutQuint); @@ -109,9 +181,10 @@ namespace osu.Game.Tests.Visual private void moveLogoFacade() { Random random = new Random(); - if (facadeFlowComponent.Transforms.Count == 0) + if (logoFacade.Transforms.Count == 0 && transferContainer.Transforms.Count == 0) { - facadeFlowComponent.Delay(500).MoveTo(new Vector2(random.Next(0, (int)DrawWidth), random.Next(0, (int)DrawHeight)), 300); + logoFacadeContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)DrawWidth), random.Next(0, (int)DrawHeight)), 300); + transferContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)DrawWidth), random.Next(0, (int)DrawHeight)), 300); } if (randomPositions) diff --git a/osu.Game/Graphics/Containers/LogoFacadeContainer.cs b/osu.Game/Graphics/Containers/LogoFacadeContainer.cs index a556fa697c..f4599e0039 100644 --- a/osu.Game/Graphics/Containers/LogoFacadeContainer.cs +++ b/osu.Game/Graphics/Containers/LogoFacadeContainer.cs @@ -26,8 +26,8 @@ namespace osu.Game.Graphics.Containers private OsuLogo logo; private float facadeScale; - private Vector2 startPosition; private Easing easing; + private Vector2? startPosition; private double? startTime; private double duration; @@ -49,6 +49,9 @@ namespace osu.Game.Graphics.Containers this.facadeScale = facadeScale; this.duration = duration; this.easing = easing; + + startTime = null; + startPosition = null; } private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre); @@ -68,7 +71,7 @@ namespace osu.Game.Graphics.Containers logo.RelativePositionAxes = Axes.None; // If this is our first update since tracking has started, initialize our starting values for interpolation - if (startTime == null) + if (startTime == null || startPosition == null) { startTime = Time.Current; startPosition = logo.Position; @@ -76,12 +79,12 @@ namespace osu.Game.Graphics.Containers if (duration != 0) { - double elapsedDuration = Time.Current - startTime ?? 0; + double elapsedDuration = Time.Current - startTime ?? throw new ArgumentNullException(nameof(startTime)); var mount = (float)Interpolation.ApplyEasing(easing, Math.Min(elapsedDuration / duration, 1)); // Interpolate the position of the logo, where mount 0 is where the logo was when it first began interpolating, and mount 1 is the target location. - logo.Position = Vector2.Lerp(startPosition, logoTrackingPosition, mount); + logo.Position = Vector2.Lerp(startPosition ?? throw new ArgumentNullException(nameof(startPosition)), logoTrackingPosition, mount); } else { diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 3f547755c1..72fbb378a4 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -311,7 +311,7 @@ namespace osu.Game.Screens.Play } private readonly WorkingBeatmap beatmap; - private readonly LogoFacadeContainer.Facade facade; + private readonly Container facade; private LoadingAnimation loading; private Sprite backgroundSprite; private ModDisplay modDisplay; @@ -333,7 +333,7 @@ namespace osu.Game.Screens.Play } } - public BeatmapMetadataDisplay(WorkingBeatmap beatmap, LogoFacadeContainer.Facade facade) + public BeatmapMetadataDisplay(WorkingBeatmap beatmap, Container facade) { this.beatmap = beatmap; this.facade = facade; From 1a0d1b238eaee2f44f4a73192894280f001e3377 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Mar 2019 20:58:07 +0900 Subject: [PATCH 273/623] Fix storage regression --- osu.Game/Tests/Visual/OsuTestCase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs index 2fbf6f7e46..495c5dfbad 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestCase.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual protected OsuTestCase() { - localStorage = new Lazy(() => new DesktopStorage($"{GetType().Name}-{Guid.NewGuid()}", null)); + localStorage = new Lazy(() => new NativeStorage($"{GetType().Name}-{Guid.NewGuid()}")); } [BackgroundDependencyLoader] From a14701619ed748a0c1e6a4e3077fb7cbfc78cccc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Mar 2019 21:44:13 +0900 Subject: [PATCH 274/623] Update icon test case --- .../Visual/UserInterface/TestCaseOsuIcon.cs | 73 +++++++++++++++++++ .../UserInterface/TestCaseTextAwesome.cs | 55 -------------- 2 files changed, 73 insertions(+), 55 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestCaseOsuIcon.cs delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestCaseTextAwesome.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseOsuIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseOsuIcon.cs new file mode 100644 index 0000000000..a57e11cb0c --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseOsuIcon.cs @@ -0,0 +1,73 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Reflection; +using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Game.Graphics; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestCaseOsuIcon : TestCase + { + public TestCaseOsuIcon() + { + FillFlowContainer flow; + + AddRange(new Drawable[] + { + new Box + { + Colour = Color4.Teal, + RelativeSizeAxes = Axes.Both, + }, + new ScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = flow = new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + }, + } + }); + + foreach (var p in typeof(OsuIcon).GetProperties(BindingFlags.Public | BindingFlags.Static)) + flow.Add(new Icon($"{nameof(OsuIcon)}.{p.Name}", (IconUsage)p.GetValue(null))); + + AddStep("toggle shadows", () => flow.Children.ForEach(i => i.SpriteIcon.Shadow = !i.SpriteIcon.Shadow)); + AddStep("change icons", () => flow.Children.ForEach(i => i.SpriteIcon.Icon = new IconUsage((char)(i.SpriteIcon.Icon.Icon + 1)))); + } + + private class Icon : Container, IHasTooltip + { + public string TooltipText { get; } + + public SpriteIcon SpriteIcon { get; } + + public Icon(string name, IconUsage icon) + { + TooltipText = name; + + AutoSizeAxes = Axes.Both; + Child = SpriteIcon = new SpriteIcon + { + Icon = icon, + Size = new Vector2(60), + }; + } + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseTextAwesome.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseTextAwesome.cs deleted file mode 100644 index 9df97f6bee..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseTextAwesome.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Sprites; -using osuTK; - -namespace osu.Game.Tests.Visual.UserInterface -{ - [TestFixture] - public class TestCaseTextAwesome : OsuTestCase - { - public TestCaseTextAwesome() - { - FillFlowContainer flow; - - Add(new ScrollContainer - { - RelativeSizeAxes = Axes.Both, - Child = flow = new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Full, - }, - }); - - foreach (IconUsage fa in Enum.GetValues(typeof(FontAwesome))) - flow.Add(new Icon(fa)); - } - - private class Icon : Container, IHasTooltip - { - public string TooltipText { get; } - - public Icon(IconUsage fa) - { - TooltipText = fa.ToString(); - - AutoSizeAxes = Axes.Both; - Child = new SpriteIcon - { - Icon = fa, - Size = new Vector2(60), - }; - } - } - } -} From 5c4f2cefea142b22aadfa704f2219038dcfba37e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Mar 2019 22:27:53 +0900 Subject: [PATCH 275/623] Fix Loader and TestCaseLoaderAnimation --- .../Visual/Menus/TestCaseLoaderAnimation.cs | 20 ++++++++++++------- osu.Game/Screens/Loader.cs | 1 + 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs b/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs index 899f9d431b..1686436924 100644 --- a/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs +++ b/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; @@ -12,19 +13,24 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Menus { [TestFixture] - public class TestCaseLoaderAnimation : OsuTestCase + public class TestCaseLoaderAnimation : ScreenTestCase { private TestLoader loader; + [Cached] + private OsuLogo logo; + + public TestCaseLoaderAnimation() + { + Add(logo = new OsuLogo { Depth = float.MinValue }); + } + protected override void LoadComplete() { base.LoadComplete(); - // required to preload the logo in a headless run (so it doesn't delay the loading itself). - Add(new OsuLogo()); - bool logoVisible = false; - AddStep("almost instant display", () => Child = loader = new TestLoader(250)); + AddStep("almost instant display", () => LoadScreen(loader = new TestLoader(250))); AddUntilStep("loaded", () => { logoVisible = loader.Logo?.Alpha > 0; @@ -32,7 +38,7 @@ namespace osu.Game.Tests.Visual.Menus }); AddAssert("logo not visible", () => !logoVisible); - AddStep("short load", () => Child = loader = new TestLoader(800)); + AddStep("short load", () => LoadScreen(loader = new TestLoader(800))); AddUntilStep("loaded", () => { logoVisible = loader.Logo?.Alpha > 0; @@ -41,7 +47,7 @@ namespace osu.Game.Tests.Visual.Menus AddAssert("logo visible", () => logoVisible); AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0); - AddStep("longer load", () => Child = loader = new TestLoader(1400)); + AddStep("longer load", () => LoadScreen(loader = new TestLoader(1400))); AddUntilStep("loaded", () => { logoVisible = loader.Logo?.Alpha > 0; diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index d858cb076a..fbe4b6311e 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -36,6 +36,7 @@ namespace osu.Game.Screens logo.BeatMatching = false; logo.Triangles = false; + logo.RelativePositionAxes = Axes.None; logo.Origin = Anchor.BottomRight; logo.Anchor = Anchor.BottomRight; logo.Position = new Vector2(-40); From 61b8fb03665b925193801877a73fdfe09c179bc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Mar 2019 22:28:32 +0900 Subject: [PATCH 276/623] Allow ScreenTestCase to support content --- osu.Game/Tests/Visual/ScreenTestCase.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Tests/Visual/ScreenTestCase.cs b/osu.Game/Tests/Visual/ScreenTestCase.cs index eec60d01c5..eb0623bbfc 100644 --- a/osu.Game/Tests/Visual/ScreenTestCase.cs +++ b/osu.Game/Tests/Visual/ScreenTestCase.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Screens; @@ -11,11 +12,12 @@ namespace osu.Game.Tests.Visual /// public abstract class ScreenTestCase : OsuTestCase { - private readonly OsuScreenStack stack; + private OsuScreenStack stack; - protected ScreenTestCase() + [BackgroundDependencyLoader] + private void load() { - Child = stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; + Add(stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }); } protected void LoadScreen(OsuScreen screen) From cd65ea48658154a4ed0419b9e58808752ccebbc2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Mar 2019 01:24:48 +0900 Subject: [PATCH 277/623] Fix TestCaseDrawings regression --- osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs b/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs index 9453d0a5b2..53fb60bcb6 100644 --- a/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs +++ b/osu.Game.Tests/Visual/Tournament/TestCaseDrawings.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; +using osu.Framework.Allocation; using osu.Game.Screens.Tournament; using osu.Game.Screens.Tournament.Teams; @@ -11,7 +12,8 @@ namespace osu.Game.Tests.Visual.Tournament [Description("for tournament use")] public class TestCaseDrawings : ScreenTestCase { - public TestCaseDrawings() + [BackgroundDependencyLoader] + private void load() { LoadScreen(new Drawings { From dfb7d789037aa45fea13513f3a6d30a3cfae8a59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Mar 2019 01:29:06 +0900 Subject: [PATCH 278/623] Fix remaining game host regressions --- osu.Game/Tests/CleanRunHeadlessGameHost.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs index 746dd936de..bfbf7bb9da 100644 --- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs +++ b/osu.Game/Tests/CleanRunHeadlessGameHost.cs @@ -13,6 +13,11 @@ namespace osu.Game.Tests public CleanRunHeadlessGameHost(string gameName = @"", bool bindIPC = false, bool realtime = true) : base(gameName, bindIPC, realtime) { + } + + protected override void SetupForRun() + { + base.SetupForRun(); Storage.DeleteDirectory(string.Empty); } } From 9b047d9b90c112013369959d23fb1703775ac52d Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 28 Mar 2019 12:00:50 +0900 Subject: [PATCH 279/623] Add back menu logo transform --- osu.Game/Screens/Menu/ButtonSystem.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 3fad36cddb..21305c6489 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -295,11 +295,12 @@ namespace osu.Game.Screens.Menu if (lastState == ButtonSystemState.Initial) logo.ScaleTo(0.5f, 200, Easing.In); + logoFacadeContainer.SetLogo(logo, 0.5f, lastState == ButtonSystemState.EnteringMode ? 0 : 200, Easing.In); + logoFacadeContainer.Tracking = true; + logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => { - logoFacadeContainer.Tracking = true; - if (impact) logo.Impact(); From bdb39a79a95caed02c4fbc389944d5a07fe6dd70 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 27 Mar 2019 20:27:26 -0700 Subject: [PATCH 280/623] Fix offset of music controller when toolbar is hidden --- osu.Game/OsuGame.cs | 2 +- osu.Game/Overlays/MusicController.cs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7ab4494a69..bb9a4ddae7 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -441,7 +441,7 @@ namespace osu.Game loadComponentSingleFile(musicController = new MusicController { - Position = new Vector2(0, Toolbar.HEIGHT), + GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, floatingOverlayContent.Add); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index c1b742e4e5..70858f93a8 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -56,6 +56,11 @@ namespace osu.Game.Overlays private readonly Bindable beatmap = new Bindable(); + /// + /// Provide a source for the toolbar height. + /// + public Func GetToolbarHeight; + public MusicController() { Width = 400; @@ -244,6 +249,8 @@ namespace osu.Game.Overlays { base.UpdateAfterChildren(); Height = dragContainer.Height; + + dragContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; } protected override void Update() From 8f5e76942584d0ee0bd8be1815c128fa61cac035 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Mar 2019 12:36:42 +0900 Subject: [PATCH 281/623] Fix slider ball not always receiving position in time --- .../Objects/Drawables/Pieces/SliderBall.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index e41c568403..7d1d77ae96 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets.Objects.Types; using osuTK.Graphics; @@ -14,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { - public class SliderBall : CircularContainer, ISliderProgress + public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition { private const float width = 128; @@ -107,18 +108,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private Vector2? lastScreenSpaceMousePosition; - protected override bool OnMouseDown(MouseDownEvent e) - { - lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition; - return base.OnMouseDown(e); - } - - protected override bool OnMouseUp(MouseUpEvent e) - { - lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition; - return base.OnMouseUp(e); - } - protected override bool OnMouseMove(MouseMoveEvent e) { lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition; From 148e26a6d49d50860d4f8fb1b3dbe47c4eee3c56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Mar 2019 12:40:40 +0900 Subject: [PATCH 282/623] Fix FramedReplayInputHandler starting at frame 0 when it shouldn't --- .../Replays/OsuFramedReplayInputHandler.cs | 10 ++++--- .../Replays/FramedReplayInputHandler.cs | 27 +++---------------- 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs index d1ac77857d..1765cde3a3 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs @@ -18,16 +18,18 @@ namespace osu.Game.Rulesets.Osu.Replays { } - protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any(); + protected override bool IsImportant(OsuReplayFrame frame) => frame?.Actions.Any() ?? false; protected Vector2? Position { get { - if (!HasFrames) + var frame = CurrentFrame; + + if (frame == null) return null; - return Interpolation.ValueAt(CurrentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time); + return Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time); } } @@ -41,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Replays }, new ReplayState { - PressedActions = CurrentFrame.Actions + PressedActions = CurrentFrame?.Actions ?? new List() } }; } diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index c89ac59e10..133fe99ca8 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -7,7 +7,6 @@ using osu.Framework.Input.StateChanges; using osu.Game.Input.Handlers; using osu.Game.Replays; using osuTK; -using osuTK.Input; namespace osu.Game.Rulesets.Replays { @@ -22,12 +21,12 @@ namespace osu.Game.Rulesets.Replays protected List Frames => replay.Frames; - public TFrame CurrentFrame => !HasFrames ? null : (TFrame)Frames[currentFrameIndex]; + public TFrame CurrentFrame => !HasFrames || !currentFrameIndex.HasValue ? null : (TFrame)Frames[currentFrameIndex.Value]; public TFrame NextFrame => !HasFrames ? null : (TFrame)Frames[nextFrameIndex]; - private int currentFrameIndex; + private int? currentFrameIndex; - private int nextFrameIndex => MathHelper.Clamp(currentFrameIndex + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1); + private int nextFrameIndex => currentFrameIndex.HasValue ? MathHelper.Clamp(currentFrameIndex.Value + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1) : 0; protected FramedReplayInputHandler(Replay replay) { @@ -47,9 +46,6 @@ namespace osu.Game.Rulesets.Replays public override List GetPendingInputs() => new List(); - public bool AtLastFrame => currentFrameIndex == Frames.Count - 1; - public bool AtFirstFrame => currentFrameIndex == 0; - private const double sixty_frame_time = 1000.0 / 60; protected double CurrentTime { get; private set; } @@ -106,22 +102,5 @@ namespace osu.Game.Rulesets.Replays return CurrentTime = time; } - - protected class ReplayMouseState : osu.Framework.Input.States.MouseState - { - public ReplayMouseState(Vector2 position) - { - Position = position; - } - } - - protected class ReplayKeyboardState : osu.Framework.Input.States.KeyboardState - { - public ReplayKeyboardState(List keys) - { - foreach (var key in keys) - Keys.Add(key); - } - } } } From 0066459968586de8883d6ae630ee7ff73cbab032 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 Mar 2019 12:55:41 +0900 Subject: [PATCH 283/623] Don't convert 100s/50s for catch --- osu.Game/Scoring/Legacy/LegacyScoreInfo.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs b/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs index 470dc9598e..df80f848e3 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs @@ -79,7 +79,6 @@ namespace osu.Game.Scoring.Legacy { case 0: case 1: - case 2: Statistics[HitResult.Good] = value; break; case 3: @@ -101,7 +100,6 @@ namespace osu.Game.Scoring.Legacy switch (Ruleset?.ID ?? RulesetID) { case 0: - case 2: case 3: Statistics[HitResult.Meh] = value; break; From 53f3dacdfbae40f4cb5e17c6bc64ede440ae8d05 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 27 Mar 2019 22:01:06 -0700 Subject: [PATCH 284/623] Fix login overlay behavior --- osu.Game/OsuGame.cs | 11 +++++ osu.Game/Overlays/LoginOverlay.cs | 14 +++++++ osu.Game/Overlays/Toolbar/Toolbar.cs | 6 +-- osu.Game/Overlays/Toolbar/ToolbarUserArea.cs | 42 ------------------- .../Overlays/Toolbar/ToolbarUserButton.cs | 8 ++-- 5 files changed, 33 insertions(+), 48 deletions(-) delete mode 100644 osu.Game/Overlays/Toolbar/ToolbarUserArea.cs diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7ab4494a69..7ea300347b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -62,6 +62,8 @@ namespace osu.Game private NotificationOverlay notifications; + public LoginOverlay loginOverlay; + private DialogOverlay dialogOverlay; private AccountCreationOverlay accountCreation; @@ -446,6 +448,14 @@ namespace osu.Game Origin = Anchor.TopRight, }, floatingOverlayContent.Add); + loadComponentSingleFile(loginOverlay = new LoginOverlay + { + GetToolbarHeight = () => ToolbarOffset, + Position = new Vector2(-ToolbarButton.WIDTH, 0), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, floatingOverlayContent.Add); + loadComponentSingleFile(accountCreation = new AccountCreationOverlay(), topMostOverlayContent.Add); loadComponentSingleFile(dialogOverlay = new DialogOverlay(), topMostOverlayContent.Add); @@ -463,6 +473,7 @@ namespace osu.Game dependencies.Cache(musicController); dependencies.Cache(beatmapSetOverlay); dependencies.Cache(notifications); + dependencies.Cache(loginOverlay); dependencies.Cache(dialogOverlay); dependencies.Cache(accountCreation); diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index e7caaa3aca..5890c89f89 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -10,6 +10,7 @@ using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; +using System; namespace osu.Game.Overlays { @@ -19,6 +20,11 @@ namespace osu.Game.Overlays private const float transition_time = 400; + /// + /// Provide a source for the toolbar height. + /// + public Func GetToolbarHeight; + public LoginOverlay() { AutoSizeAxes = Axes.Both; @@ -88,5 +94,13 @@ namespace osu.Game.Overlays settingsSection.Bounding = false; this.FadeOut(transition_time); } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; + } + } } diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index dca0226499..2f435a778d 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Toolbar public Action OnHome; - private ToolbarUserArea userArea; + private ToolbarUserButton userButton; protected override bool BlockPositionalInput => false; @@ -77,7 +77,7 @@ namespace osu.Game.Overlays.Toolbar //{ // Icon = FontAwesome.fa_search //}, - userArea = new ToolbarUserArea(), + userButton = new ToolbarUserButton(), new ToolbarNotificationButton(), } } @@ -143,7 +143,7 @@ namespace osu.Game.Overlays.Toolbar protected override void PopOut() { - userArea?.LoginOverlay.Hide(); + userButton?.StateContainer.Hide(); this.MoveToY(-DrawSize.Y, transition_time, Easing.OutQuint); this.FadeOut(transition_time); diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs b/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs deleted file mode 100644 index f9cf5d4350..0000000000 --- a/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osuTK; -using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarUserArea : Container - { - public LoginOverlay LoginOverlay; - private ToolbarUserButton button; - - public override RectangleF BoundingBox => button.BoundingBox; - - [BackgroundDependencyLoader] - private void load() - { - RelativeSizeAxes = Axes.Y; - AutoSizeAxes = Axes.X; - - Children = new Drawable[] - { - button = new ToolbarUserButton - { - Action = () => LoginOverlay.ToggleVisibility(), - }, - LoginOverlay = new LoginOverlay - { - BypassAutoSizeAxes = Axes.Both, - Position = new Vector2(0, 1), - RelativePositionAxes = Axes.Y, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - } - }; - } - } -} diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 8d1910fc19..356ffa5180 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -13,7 +13,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Toolbar { - public class ToolbarUserButton : ToolbarButton, IOnlineComponent + public class ToolbarUserButton : ToolbarOverlayToggleButton, IOnlineComponent { private readonly UpdateableAvatar avatar; @@ -42,10 +42,12 @@ namespace osu.Game.Overlays.Toolbar }); } - [BackgroundDependencyLoader] - private void load(IAPIProvider api) + [BackgroundDependencyLoader(true)] + private void load(IAPIProvider api, LoginOverlay login) { api.Register(this); + + StateContainer = login; } public void APIStateChanged(IAPIProvider api, APIState state) From 2254c572c40a2d99aa68de1662109bdccad3f6f7 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 27 Mar 2019 22:21:28 -0700 Subject: [PATCH 285/623] Remove unnecessary newline --- osu.Game/Overlays/LoginOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index 5890c89f89..d0411ba9e7 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -101,6 +101,5 @@ namespace osu.Game.Overlays Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; } - } } From 9a3528ea9d527edfe239706dc7e0478abd395b7c Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 27 Mar 2019 22:31:40 -0700 Subject: [PATCH 286/623] Fix field modifier --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7ea300347b..db796a4ca1 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -62,7 +62,7 @@ namespace osu.Game private NotificationOverlay notifications; - public LoginOverlay loginOverlay; + private LoginOverlay loginOverlay; private DialogOverlay dialogOverlay; From 09a7950a3b01372c233894901a087f55d5339d6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Mar 2019 15:09:06 +0900 Subject: [PATCH 287/623] Fix handlers for other rulesets --- .../Replays/CatchFramedReplayInputHandler.cs | 6 ++++-- .../Replays/ManiaFramedReplayInputHandler.cs | 2 +- .../Replays/TaikoFramedReplayInputHandler.cs | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index dd0223314d..91f5c52440 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -22,10 +22,12 @@ namespace osu.Game.Rulesets.Catch.Replays { get { - if (!HasFrames) + var frame = CurrentFrame; + + if (frame == null) return null; - return Interpolation.ValueAt(CurrentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time); + return Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time); } } diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs index 197b105437..899718b77e 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs @@ -18,6 +18,6 @@ namespace osu.Game.Rulesets.Mania.Replays protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any(); - public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame.Actions } }; + public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() } }; } } diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs index d97d7626ef..97337acc45 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs @@ -18,6 +18,6 @@ namespace osu.Game.Rulesets.Taiko.Replays protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any(); - public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame.Actions } }; + public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() } }; } } From bfe44eb33d7df500b5031171b928c9320b347473 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 28 Mar 2019 15:40:58 +0900 Subject: [PATCH 288/623] Remove SizeForFlow magic number --- osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs | 7 ++++++- osu.Game/Graphics/Containers/LogoFacadeContainer.cs | 3 ++- osu.Game/Screens/Menu/OsuLogo.cs | 2 +- osu.Game/Screens/Play/PlayerLoader.cs | 3 ++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs index 8be0d25a86..f81e70e7b0 100644 --- a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs @@ -145,7 +145,12 @@ namespace osu.Game.Tests.Visual logoFacadeContainer.Add(logoFacade = logoFacadeContainer.LogoFacade); } - public bool IsLogoTracking => logo.Position == logo.Parent.ToLocalSpace(logoFacadeContainer.LogoFacade.ScreenSpaceDrawQuad.Centre); + private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(logoFacade.ScreenSpaceDrawQuad.Centre); + + /// + /// Check that the logo is tracking the position of the facade, with an acceptable precision lenience. + /// + public bool IsLogoTracking => Math.Abs(logo.Position.X - logoTrackingPosition.X) < 0.001f && Math.Abs(logo.Position.Y - logoTrackingPosition.Y) < 0.001f; public void RemoveFacade() { diff --git a/osu.Game/Graphics/Containers/LogoFacadeContainer.cs b/osu.Game/Graphics/Containers/LogoFacadeContainer.cs index f4599e0039..547af87522 100644 --- a/osu.Game/Graphics/Containers/LogoFacadeContainer.cs +++ b/osu.Game/Graphics/Containers/LogoFacadeContainer.cs @@ -63,7 +63,8 @@ namespace osu.Game.Graphics.Containers if (logo == null || !Tracking) return; - LogoFacade.Size = new Vector2(logo.SizeForFlow * facadeScale); + // Account for the scale of the actual logo container, as SizeForFlow only accounts for the sprite scale. + LogoFacade.Size = new Vector2(logo.SizeForFlow * logo.Scale.X * facadeScale); if (LogoFacade.Parent != null && logo.Position != logoTrackingPosition) { diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index af697d37bd..c54ccd21b5 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Menu /// public Func Action; - public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X * 0.74f; + public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X; private readonly Sprite ripple; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 72fbb378a4..5a8c3846fa 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -156,7 +156,7 @@ namespace osu.Game.Screens.Play logo.MoveTo(new Vector2(0.5f), duration, Easing.In); logo.FadeIn(350); - content.SetLogo(logo, 0.3f, 500, Easing.InOutExpo); + content.SetLogo(logo, 1.0f, 500, Easing.InOutExpo); Scheduler.AddDelayed(() => content.Tracking = true, resuming ? 0 : 500); } @@ -365,6 +365,7 @@ namespace osu.Game.Screens.Play Font = OsuFont.GetFont(size: 36, italics: true), Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, + Margin = new MarginPadding { Top = 15 }, }, new OsuSpriteText { From 039e451ab1b4eee334c937a9bcaf3320118e1f48 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 28 Mar 2019 16:09:42 +0900 Subject: [PATCH 289/623] ensure logo is where it already needs to be on resume --- osu.Game/Screens/Play/PlayerLoader.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 5a8c3846fa..da5abbae95 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -152,8 +152,12 @@ namespace osu.Game.Screens.Play const double duration = 300; + if (!resuming) + { + logo.MoveTo(new Vector2(0.5f), duration, Easing.In); + } + logo.ScaleTo(new Vector2(0.15f), duration, Easing.In); - logo.MoveTo(new Vector2(0.5f), duration, Easing.In); logo.FadeIn(350); content.SetLogo(logo, 1.0f, 500, Easing.InOutExpo); From 9d66a5e4b20841ed8d6222a71e5767a4fe7c6eaa Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 28 Mar 2019 16:29:35 +0900 Subject: [PATCH 290/623] Ensure logo stops tracking before suspend animation --- osu.Game/Screens/Play/PlayerLoader.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index da5abbae95..53a349f595 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -129,6 +129,9 @@ namespace osu.Game.Screens.Play private void contentOut() { + // Ensure the logo is no longer tracking before we scale the content. + content.Tracking = false; + content.ScaleTo(0.7f, 300, Easing.InQuint); content.FadeOut(250); } From f066bd11384b56ea55afb7a0439b9a6fc8de12fd Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 28 Mar 2019 16:35:15 +0900 Subject: [PATCH 291/623] Adjust facade scale now that the size is different --- osu.Game/Screens/Menu/ButtonSystem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 21305c6489..19b460250f 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Menu if (this.logo != null) { this.logo.Action = onOsuLogo; - logoFacadeContainer.SetLogo(logo, 0.5f); + logoFacadeContainer.SetLogo(logo, 0.74f); // osuLogo.SizeForFlow relies on loading to be complete. buttonArea.Flow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + this.logo.SizeForFlow / 4), 0); @@ -295,7 +295,7 @@ namespace osu.Game.Screens.Menu if (lastState == ButtonSystemState.Initial) logo.ScaleTo(0.5f, 200, Easing.In); - logoFacadeContainer.SetLogo(logo, 0.5f, lastState == ButtonSystemState.EnteringMode ? 0 : 200, Easing.In); + logoFacadeContainer.SetLogo(logo, 0.74f, lastState == ButtonSystemState.EnteringMode ? 0 : 200, Easing.In); logoFacadeContainer.Tracking = true; logoDelayedAction?.Cancel(); From 70f99400ad066d380fd80c73d11ef37aee5a9d14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Mar 2019 19:28:13 +0900 Subject: [PATCH 292/623] Fix many behavioural issues and add tests --- .../Replays/CatchFramedReplayInputHandler.cs | 5 +- .../Replays/OsuFramedReplayInputHandler.cs | 5 +- .../NonVisual/FramedReplayinputHandlerTest.cs | 211 ++++++++++++++++++ .../Replays/FramedReplayInputHandler.cs | 42 ++-- 4 files changed, 245 insertions(+), 18 deletions(-) create mode 100644 osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index 91f5c52440..c6c6ccd1f3 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using osu.Framework.Input.StateChanges; using osu.Framework.MathUtils; using osu.Game.Replays; @@ -27,7 +28,9 @@ namespace osu.Game.Rulesets.Catch.Replays if (frame == null) return null; - return Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time); + Debug.Assert(CurrentTime != null); + + return Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time); } } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs index 1765cde3a3..b96a477e42 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Input.StateChanges; using osu.Framework.MathUtils; @@ -29,7 +30,9 @@ namespace osu.Game.Rulesets.Osu.Replays if (frame == null) return null; - return Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time); + Debug.Assert(CurrentTime != null); + + return Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time); } } diff --git a/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs new file mode 100644 index 0000000000..05bcef7d16 --- /dev/null +++ b/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs @@ -0,0 +1,211 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Replays; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class FramedReplayinputHandlerTest + { + private Replay replay; + private TestInputHandler handler; + + [SetUp] + public void SetUp() + { + handler = new TestInputHandler(replay = new Replay + { + Frames = new List + { + new TestReplayFrame(0), + new TestReplayFrame(1000), + new TestReplayFrame(2000), + new TestReplayFrame(3000, true), + new TestReplayFrame(4000, true), + new TestReplayFrame(5000, true), + new TestReplayFrame(7000, true), + new TestReplayFrame(8000), + } + }); + } + + [Test] + public void TestNormalPlayback() + { + Assert.IsNull(handler.CurrentFrame); + + confirmCurrentFrame(null); + confirmNextFrame(0); + + setTime(0, 0); + confirmCurrentFrame(0); + confirmNextFrame(1); + + //if we hit the first frame perfectly, time should progress to it. + setTime(1000, 1000); + confirmCurrentFrame(1); + confirmNextFrame(2); + + //in between non-important frames should progress based on input. + setTime(1200, 1200); + confirmCurrentFrame(1); + + setTime(1400, 1400); + confirmCurrentFrame(1); + + // progressing beyond the next frame should force time to that frame once. + setTime(2200, 2000); + confirmCurrentFrame(2); + + // second attempt should progress to input time + setTime(2200, 2200); + confirmCurrentFrame(2); + + // entering important section + setTime(3000, 3000); + confirmCurrentFrame(3); + + // cannot progress within + setTime(3500, null); + confirmCurrentFrame(3); + + setTime(4000, 4000); + confirmCurrentFrame(4); + + // still cannot progress + setTime(4500, null); + confirmCurrentFrame(4); + + setTime(5200, 5000); + confirmCurrentFrame(5); + + // important section AllowedImportantTimeSpan allowance + setTime(5200, 5200); + confirmCurrentFrame(5); + + setTime(7200, 7000); + confirmCurrentFrame(6); + + setTime(7200, null); + confirmCurrentFrame(6); + + // exited important section + setTime(8200, 8000); + confirmCurrentFrame(7); + confirmNextFrame(null); + + setTime(8200, 8200); + confirmCurrentFrame(7); + confirmNextFrame(null); + } + + [Test] + public void TestIntroTime() + { + setTime(-1000, -1000); + confirmCurrentFrame(null); + confirmNextFrame(0); + + setTime(-500, -500); + confirmCurrentFrame(null); + confirmNextFrame(0); + + setTime(0, 0); + confirmCurrentFrame(0); + confirmNextFrame(1); + } + + [Test] + public void TestBasicRewind() + { + setTime(3000, 0); + setTime(3000, 1000); + setTime(3000, 2000); + setTime(3000, 3000); + confirmCurrentFrame(3); + confirmNextFrame(4); + + setTime(1980, 2000); + confirmCurrentFrame(2); + confirmNextFrame(1); + + setTime(1980, 1980); + confirmCurrentFrame(2); + confirmNextFrame(1); + + setTime(1200, 1200); + confirmCurrentFrame(2); + confirmNextFrame(1); + + setTime(-500, 1000); + confirmCurrentFrame(1); + confirmNextFrame(0); + + setTime(-500, 0); + confirmCurrentFrame(0); + confirmNextFrame(null); + + setTime(-500, -500); + confirmCurrentFrame(0); + confirmNextFrame(null); + } + + private void setTime(double set, double? expect) + { + Assert.AreEqual(expect, handler.SetFrameFromTime(set)); + } + + private void confirmCurrentFrame(int? frame) + { + if (frame.HasValue) + { + Assert.IsNotNull(handler.CurrentFrame); + Assert.AreEqual(replay.Frames[frame.Value].Time, handler.CurrentFrame.Time); + } + else + { + Assert.IsNull(handler.CurrentFrame); + } + } + + private void confirmNextFrame(int? frame) + { + if (frame.HasValue) + { + Assert.IsNotNull(handler.NextFrame); + Assert.AreEqual(replay.Frames[frame.Value].Time, handler.NextFrame.Time); + } + else + { + Assert.IsNull(handler.NextFrame); + } + } + + private class TestReplayFrame : ReplayFrame + { + public readonly bool IsImportant; + + public TestReplayFrame(double time, bool isImportant = false) + : base(time) + { + IsImportant = isImportant; + } + } + + private class TestInputHandler : FramedReplayInputHandler + { + public TestInputHandler(Replay replay) + : base(replay) + { + } + + protected override double AllowedImportantTimeSpan => 1000; + + protected override bool IsImportant(TestReplayFrame frame) => frame?.IsImportant ?? false; + } + } +} diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 133fe99ca8..acd19721f5 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Replays protected List Frames => replay.Frames; public TFrame CurrentFrame => !HasFrames || !currentFrameIndex.HasValue ? null : (TFrame)Frames[currentFrameIndex.Value]; - public TFrame NextFrame => !HasFrames ? null : (TFrame)Frames[nextFrameIndex]; + public TFrame NextFrame => !HasFrames || ((currentDirection > 0 && currentFrameIndex == Frames.Count - 1) || (currentDirection < 0 && currentFrameIndex == 0)) ? null : (TFrame)Frames[nextFrameIndex]; private int? currentFrameIndex; @@ -48,7 +48,10 @@ namespace osu.Game.Rulesets.Replays private const double sixty_frame_time = 1000.0 / 60; - protected double CurrentTime { get; private set; } + protected virtual double AllowedImportantTimeSpan => sixty_frame_time * 1.2; + + protected double? CurrentTime { get; private set; } + private int currentDirection; /// @@ -64,7 +67,7 @@ namespace osu.Game.Rulesets.Replays //a button is in a pressed state IsImportant(currentDirection > 0 ? CurrentFrame : NextFrame) && //the next frame is within an allowable time span - Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= sixty_frame_time * 1.2; + Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= AllowedImportantTimeSpan; protected virtual bool IsImportant(TFrame frame) => false; @@ -77,27 +80,34 @@ namespace osu.Game.Rulesets.Replays /// The usable time value. If null, we should not advance time as we do not have enough data. public override double? SetFrameFromTime(double time) { - currentDirection = time.CompareTo(CurrentTime); - if (currentDirection == 0) currentDirection = 1; + if (!CurrentTime.HasValue) + { + CurrentTime = time; + currentDirection = 1; + } + else + { + currentDirection = time.CompareTo(CurrentTime); + if (currentDirection == 0) currentDirection = 1; + } if (HasFrames) { - // check if the next frame is in the "future" for the current playback direction - if (currentDirection != time.CompareTo(NextFrame.Time)) + // check if the next frame is valid for the current playback direction. + // validity is if the next frame is equal or "earlier" + var compare = time.CompareTo(NextFrame?.Time); + + if (compare == 0 || compare == currentDirection) + { + if (advanceFrame()) + return CurrentTime = CurrentFrame.Time; + } + else { // if we didn't change frames, we need to ensure we are allowed to run frames in between, else return null. if (inImportantSection) return null; } - else if (advanceFrame()) - { - // If going backwards, we need to execute once _before_ the frame time to reverse any judgements - // that would occur as a result of this frame in forward playback - if (currentDirection == -1) - return CurrentTime = CurrentFrame.Time - 1; - - return CurrentTime = CurrentFrame.Time; - } } return CurrentTime = time; From bca871490552e71256d04112a707337c84b723d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Mar 2019 23:25:31 +0900 Subject: [PATCH 293/623] Update framework --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 71324ea0f0..f958d00860 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 02099a59bb..b9832c1cf3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 2f5668f4e77ee9acfcfe1329399fb6adcc7a8fba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2019 00:29:07 +0900 Subject: [PATCH 294/623] Fix remaining framework changes --- osu.Game/Graphics/UserInterface/TriangleButton.cs | 2 ++ osu.Game/Overlays/Chat/Selection/ChannelListItem.cs | 2 ++ osu.Game/Overlays/Chat/Selection/ChannelSection.cs | 2 ++ osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 2 ++ osu.Game/Overlays/Music/PlaylistItem.cs | 2 ++ osu.Game/Overlays/Music/PlaylistList.cs | 5 +++++ osu.Game/Overlays/Settings/SettingsItem.cs | 2 ++ osu.Game/Overlays/Settings/SettingsSection.cs | 2 ++ osu.Game/Overlays/Settings/SettingsSubsection.cs | 2 ++ osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs | 2 ++ 10 files changed, 23 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/TriangleButton.cs b/osu.Game/Graphics/UserInterface/TriangleButton.cs index 685d230a4b..5baf794227 100644 --- a/osu.Game/Graphics/UserInterface/TriangleButton.cs +++ b/osu.Game/Graphics/UserInterface/TriangleButton.cs @@ -33,5 +33,7 @@ namespace osu.Game.Graphics.UserInterface { set => this.FadeTo(value ? 1 : 0); } + + public bool FilteringActive { get; set; } } } diff --git a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs index a4953d6334..85a10510ef 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs @@ -43,6 +43,8 @@ namespace osu.Game.Overlays.Chat.Selection set => this.FadeTo(value ? 1f : 0f, 100); } + public bool FilteringActive { get; set; } + public Action OnRequestJoin; public Action OnRequestLeave; diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs index 3f979b6309..eac48ca5cb 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs @@ -27,6 +27,8 @@ namespace osu.Game.Overlays.Chat.Selection set => this.FadeTo(value ? 1f : 0f, 100); } + public bool FilteringActive { get; set; } + public string Header { get => header.Text; diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index ef16c81dfc..8313dac50a 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -43,6 +43,8 @@ namespace osu.Game.Overlays.KeyBinding } } + public bool FilteringActive { get; set; } + private OsuSpriteText text; private OsuTextFlowContainer pressAKey; diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index fa0c2ace46..96e9cc9ca7 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -155,6 +155,8 @@ namespace osu.Game.Overlays.Music } } + public bool FilteringActive { get; set; } + private class PlaylistItemHandle : SpriteIcon { public PlaylistItemHandle() diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 7846e31725..310c6c919f 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -231,6 +231,11 @@ namespace osu.Game.Overlays.Music } } + public bool FilteringActive + { + set { } + } + public IEnumerable FilterableChildren => Children; public ItemSearchContainer() diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index f6517bafd6..02e9d48f40 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -79,6 +79,8 @@ namespace osu.Game.Overlays.Settings set => this.FadeTo(value ? 1 : 0); } + public bool FilteringActive { get; set; } + protected SettingsItem() { RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index 92e74d96a1..e92f28d5d2 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -35,6 +35,8 @@ namespace osu.Game.Overlays.Settings set => this.FadeTo(value ? 1 : 0); } + public bool FilteringActive { get; set; } + protected SettingsSection() { Margin = new MarginPadding { Top = 20 }; diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index 2215e95fec..a1b4d8b131 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -28,6 +28,8 @@ namespace osu.Game.Overlays.Settings set => this.FadeTo(value ? 1 : 0); } + public bool FilteringActive { get; set; } + protected SettingsSubsection() { RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs index 4bab68058f..dce597b276 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs @@ -77,6 +77,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components } } + public bool FilteringActive { get; set; } + public DrawableRoom(Room room) { Room = room; From d770dac3bc3cc957d60ffa540d611e6f3c5bedef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2019 00:35:26 +0900 Subject: [PATCH 295/623] Fix interpolation nullrefs --- .../Replays/CatchFramedReplayInputHandler.cs | 2 +- osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index c6c6ccd1f3..103aa6c3f1 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Replays Debug.Assert(CurrentTime != null); - return Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time); + return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; } } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs index b96a477e42..614edba400 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Replays Debug.Assert(CurrentTime != null); - return Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time); + return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; } } From 759c5b3db94ff9e2457aac2e6bb6f9f8a857a173 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2019 10:52:26 +0900 Subject: [PATCH 296/623] Restore previous resume behaviour for the time being --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 5cf1697113..9eb6f2b9b9 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -491,7 +491,7 @@ namespace osu.Game.Screens.Select if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) { UpdateBeatmap(Beatmap.Value); - ensurePlayingSelected(true); + ensurePlayingSelected(); } base.OnResuming(last); From bab7d781301208a6ba837fb8ecd0fafd149dcee6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com> Date: Fri, 29 Mar 2019 11:36:40 +0900 Subject: [PATCH 297/623] Remove redundant cast Co-Authored-By: peppy --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 3ce8f92458..b4271085f5 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.UI protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) { InternalChild = KeyBindingContainer = - (KeyBindingContainer)CreateKeyBindingContainer(ruleset, variant, unique) + CreateKeyBindingContainer(ruleset, variant, unique) .WithChild(content = new Container { RelativeSizeAxes = Axes.Both }); } From 82b9dfdeb1a73f3f29a7e20dfe54f756e51ef26d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2019 11:34:39 +0900 Subject: [PATCH 298/623] Add important section rewind tests --- .../NonVisual/FramedReplayinputHandlerTest.cs | 87 +++++++++++++++++-- 1 file changed, 80 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs index 05bcef7d16..976bb3e177 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs @@ -122,17 +122,19 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestBasicRewind() { - setTime(3000, 0); - setTime(3000, 1000); - setTime(3000, 2000); - setTime(3000, 3000); - confirmCurrentFrame(3); - confirmNextFrame(4); + setTime(2800, 0); + setTime(2800, 1000); + setTime(2800, 2000); + setTime(2800, 2800); + confirmCurrentFrame(2); + confirmNextFrame(3); - setTime(1980, 2000); + // pivot without crossing a frame boundary + setTime(2700, 2700); confirmCurrentFrame(2); confirmNextFrame(1); + // cross current frame boundary; should not yet update frame setTime(1980, 1980); confirmCurrentFrame(2); confirmNextFrame(1); @@ -141,6 +143,7 @@ namespace osu.Game.Tests.NonVisual confirmCurrentFrame(2); confirmNextFrame(1); + //ensure each frame plays out until start setTime(-500, 1000); confirmCurrentFrame(1); confirmNextFrame(0); @@ -154,6 +157,76 @@ namespace osu.Game.Tests.NonVisual confirmNextFrame(null); } + [Test] + public void TestRewindInsideImportantSection() + { + // fast forward to important section + while (handler.SetFrameFromTime(3000) != null) + { + } + + setTime(4000, 4000); + confirmCurrentFrame(4); + confirmNextFrame(5); + + setTime(3500, null); + confirmCurrentFrame(4); + confirmNextFrame(3); + + setTime(3000, 3000); + confirmCurrentFrame(3); + confirmNextFrame(2); + + setTime(3500, null); + confirmCurrentFrame(3); + confirmNextFrame(4); + + setTime(4000, 4000); + confirmCurrentFrame(4); + confirmNextFrame(5); + + setTime(4500, null); + confirmCurrentFrame(4); + confirmNextFrame(5); + + setTime(4000, null); + confirmCurrentFrame(4); + confirmNextFrame(5); + + setTime(3500, null); + confirmCurrentFrame(4); + confirmNextFrame(3); + + setTime(3000, 3000); + confirmCurrentFrame(3); + confirmNextFrame(2); + } + + [Test] + public void TestRewindOutOfImportantSection() + { + // fast forward to important section + while (handler.SetFrameFromTime(3500) != null) + { + } + + confirmCurrentFrame(3); + confirmNextFrame(4); + + setTime(3200, null); + // next frame doesn't change even though direction reversed, because of important section. + confirmCurrentFrame(3); + confirmNextFrame(4); + + setTime(3000, null); + confirmCurrentFrame(3); + confirmNextFrame(4); + + setTime(2800, 2800); + confirmCurrentFrame(3); + confirmNextFrame(2); + } + private void setTime(double set, double? expect) { Assert.AreEqual(expect, handler.SetFrameFromTime(set)); From 1e369628a5c1181cd1e681478a7baff218d15ab4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2019 11:38:45 +0900 Subject: [PATCH 299/623] Fix incorrect type specification --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index d2a76f9d49..0cbe0cca85 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.UI public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); - protected override CursorContainer CreateCursor() => new OsuCursorContainer(); + protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); public OsuPlayfield() { From 5b0aa7bf8d52f29f52378313ad9a182678eaeb71 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2019 12:38:40 +0900 Subject: [PATCH 300/623] Split out current/next frame conditionals for readability --- .../Replays/FramedReplayInputHandler.cs | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index acd19721f5..361cae910f 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -21,8 +21,33 @@ namespace osu.Game.Rulesets.Replays protected List Frames => replay.Frames; - public TFrame CurrentFrame => !HasFrames || !currentFrameIndex.HasValue ? null : (TFrame)Frames[currentFrameIndex.Value]; - public TFrame NextFrame => !HasFrames || ((currentDirection > 0 && currentFrameIndex == Frames.Count - 1) || (currentDirection < 0 && currentFrameIndex == 0)) ? null : (TFrame)Frames[nextFrameIndex]; + public TFrame CurrentFrame + { + get + { + if (!HasFrames || !currentFrameIndex.HasValue) + return null; + + return (TFrame)Frames[currentFrameIndex.Value]; + } + } + + public TFrame NextFrame + { + get + { + if (!HasFrames) + return null; + + if (!currentFrameIndex.HasValue) + return (TFrame)Frames[0]; + + if (currentDirection > 0) + return currentFrameIndex == Frames.Count - 1 ? null : (TFrame)Frames[currentFrameIndex.Value + 1]; + else + return currentFrameIndex == 0 ? null : (TFrame)Frames[nextFrameIndex]; + } + } private int? currentFrameIndex; From e7b38cdc755151068417a00c3726f77cd2b7d64d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2019 12:38:47 +0900 Subject: [PATCH 301/623] Remove unnecessary set --- osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 361cae910f..178e1c2364 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -107,7 +107,6 @@ namespace osu.Game.Rulesets.Replays { if (!CurrentTime.HasValue) { - CurrentTime = time; currentDirection = 1; } else From c4096fb6285d912ef4131af9491c8292b1fc0cf0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2019 13:56:56 +0900 Subject: [PATCH 302/623] Remove unused using --- osu.Game/OsuGame.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index bb9a4ddae7..e18b7065f5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -15,7 +15,6 @@ using osu.Framework.Allocation; using osu.Game.Overlays.Toolbar; using osu.Game.Screens; using osu.Game.Screens.Menu; -using osuTK; using System.Linq; using System.Threading; using System.Threading.Tasks; From e45c08ad238de6d41841f2fdc72d837580ba8023 Mon Sep 17 00:00:00 2001 From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com> Date: Fri, 29 Mar 2019 14:02:19 +0900 Subject: [PATCH 303/623] Adjust comment --- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 757125c5b4..0f83edf034 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -40,7 +40,7 @@ namespace osu.Game.Beatmaps.Formats this.storyboard = storyboard; base.ParseStreamInto(stream, storyboard); - // OrderBy is used to guarantee that the parsing order of hitobjects with equal start times is maintained (stably-sorted) + // OrderBy is used to guarantee that the parsing order of elements with equal start times is maintained (stably-sorted) foreach (StoryboardLayer layer in storyboard.Layers) layer.Elements = layer.Elements.OrderBy(h => h.StartTime).ToList(); } From 3e28c4ae0acb1777ad0d36c483456fe3c8b4bcdf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2019 14:03:00 +0900 Subject: [PATCH 304/623] Fix remaining IconUsage changes --- osu.Game/Graphics/UserInterface/ScreenTitle.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/ScreenTitle.cs b/osu.Game/Graphics/UserInterface/ScreenTitle.cs index dd0b06f969..1574023068 100644 --- a/osu.Game/Graphics/UserInterface/ScreenTitle.cs +++ b/osu.Game/Graphics/UserInterface/ScreenTitle.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -14,7 +15,7 @@ namespace osu.Game.Graphics.UserInterface private readonly SpriteIcon iconSprite; private readonly OsuSpriteText titleText, pageText; - protected FontAwesome Icon + protected IconUsage Icon { get => iconSprite.Icon; set => iconSprite.Icon = value; From cabec85544de138ac493dd00bd447e67956cdb59 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 29 Mar 2019 14:15:57 +0900 Subject: [PATCH 305/623] Rework TestCaseLoaderAnimation to avoid timing issues --- .../Visual/Menus/TestCaseLoaderAnimation.cs | 87 +++++++++++-------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs b/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs index 1686436924..df12e14891 100644 --- a/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs +++ b/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -22,55 +23,76 @@ namespace osu.Game.Tests.Visual.Menus public TestCaseLoaderAnimation() { - Add(logo = new OsuLogo { Depth = float.MinValue }); + Child = logo = new OsuLogo { Depth = float.MinValue }; } - protected override void LoadComplete() + [Test] + public void TestInstantLoad() { - base.LoadComplete(); - bool logoVisible = false; - AddStep("almost instant display", () => LoadScreen(loader = new TestLoader(250))); - AddUntilStep("loaded", () => - { - logoVisible = loader.Logo?.Alpha > 0; - return loader.Logo != null && loader.ScreenLoaded; - }); - AddAssert("logo not visible", () => !logoVisible); - AddStep("short load", () => LoadScreen(loader = new TestLoader(800))); - AddUntilStep("loaded", () => + AddStep("begin loading", () => + { + loader = new TestLoader(); + loader.AllowLoad.Set(); + + LoadScreen(loader); + }); + + AddAssert("loaded", () => { logoVisible = loader.Logo?.Alpha > 0; return loader.Logo != null && loader.ScreenLoaded; }); - AddAssert("logo visible", () => logoVisible); + + AddAssert("logo was not visible", () => !logoVisible); + } + + [Test] + public void TestShortLoad() + { + bool logoVisible = false; + + AddStep("begin loading", () => LoadScreen(loader = new TestLoader())); + AddWaitStep("wait", 2); + AddStep("finish loading", () => + { + logoVisible = loader.Logo?.Alpha > 0; + loader.AllowLoad.Set(); + }); + + AddAssert("loaded", () => loader.Logo != null && loader.ScreenLoaded); + AddAssert("logo was visible", () => logoVisible); AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0); + } - AddStep("longer load", () => LoadScreen(loader = new TestLoader(1400))); - AddUntilStep("loaded", () => + [Test] + public void TestLongLoad() + { + bool logoVisible = false; + + AddStep("begin loading", () => LoadScreen(loader = new TestLoader())); + AddWaitStep("wait", 10); + AddStep("finish loading", () => { logoVisible = loader.Logo?.Alpha > 0; - return loader.Logo != null && loader.ScreenLoaded; + loader.AllowLoad.Set(); }); - AddAssert("logo visible", () => logoVisible); + + AddAssert("loaded", () => loader.Logo != null && loader.ScreenLoaded); + AddAssert("logo was visible", () => logoVisible); AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0); } private class TestLoader : Loader { - private readonly double delay; + public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(); public OsuLogo Logo; private TestScreen screen; public bool ScreenLoaded => screen.IsCurrentScreen(); - public TestLoader(double delay) - { - this.delay = delay; - } - protected override void LogoArriving(OsuLogo logo, bool resuming) { Logo = logo; @@ -78,25 +100,18 @@ namespace osu.Game.Tests.Visual.Menus } protected override OsuScreen CreateLoadableScreen() => screen = new TestScreen(); - protected override ShaderPrecompiler CreateShaderPrecompiler() => new TestShaderPrecompiler(delay); + protected override ShaderPrecompiler CreateShaderPrecompiler() => new TestShaderPrecompiler(AllowLoad); private class TestShaderPrecompiler : ShaderPrecompiler { - private readonly double delay; - private double startTime; + private readonly ManualResetEventSlim allowLoad; - public TestShaderPrecompiler(double delay) + public TestShaderPrecompiler(ManualResetEventSlim allowLoad) { - this.delay = delay; + this.allowLoad = allowLoad; } - protected override void LoadComplete() - { - base.LoadComplete(); - startTime = Time.Current; - } - - protected override bool AllLoaded => Time.Current > startTime + delay; + protected override bool AllLoaded => allowLoad.IsSet; } private class TestScreen : OsuScreen From 5495a0a70fd1c2320c801a0e9ba6c72e6f64f7a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2019 14:34:58 +0900 Subject: [PATCH 306/623] Add content to ScreenTestCase as protection against overwriting --- osu.Game/Tests/Visual/ScreenTestCase.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Tests/Visual/ScreenTestCase.cs b/osu.Game/Tests/Visual/ScreenTestCase.cs index d10779349b..4fd4c7c207 100644 --- a/osu.Game/Tests/Visual/ScreenTestCase.cs +++ b/osu.Game/Tests/Visual/ScreenTestCase.cs @@ -1,8 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Screens; namespace osu.Game.Tests.Visual @@ -12,12 +12,19 @@ namespace osu.Game.Tests.Visual /// public abstract class ScreenTestCase : ManualInputManagerTestCase { - private OsuScreenStack stack; + private readonly OsuScreenStack stack; - [BackgroundDependencyLoader] - private void load() + private readonly Container content; + + protected override Container Content => content; + + protected ScreenTestCase() { - Add(stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }); + base.Content.AddRange(new Drawable[] + { + stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, + content = new Container { RelativeSizeAxes = Axes.Both } + }); } protected void LoadScreen(OsuScreen screen) From 90a4cb8e0441b37ee7458ff535ce82e46835aac3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2019 14:49:15 +0900 Subject: [PATCH 307/623] Fix unnecessary positioning --- osu.Game/OsuGame.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index bd92ad951d..f099f0946a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -451,7 +451,6 @@ namespace osu.Game loadComponentSingleFile(loginOverlay = new LoginOverlay { GetToolbarHeight = () => ToolbarOffset, - Position = new Vector2(-ToolbarButton.WIDTH, 0), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, floatingOverlayContent.Add); From ccc0853f75a4212297b724f3a7a120353467bf1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Mar 2019 14:53:40 +0900 Subject: [PATCH 308/623] Change login overlay's depth and load order --- osu.Game/OsuGame.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index f099f0946a..e470d554c9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -424,6 +424,13 @@ namespace osu.Game loadComponentSingleFile(volume = new VolumeOverlay(), floatingOverlayContent.Add); loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add); + loadComponentSingleFile(loginOverlay = new LoginOverlay + { + GetToolbarHeight = () => ToolbarOffset, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, floatingOverlayContent.Add); + loadComponentSingleFile(screenshotManager, Add); //overlay elements @@ -434,6 +441,7 @@ namespace osu.Game loadComponentSingleFile(settings = new MainSettings { GetToolbarHeight = () => ToolbarOffset }, floatingOverlayContent.Add); loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add); loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add); + loadComponentSingleFile(notifications = new NotificationOverlay { GetToolbarHeight = () => ToolbarOffset, @@ -448,13 +456,6 @@ namespace osu.Game Origin = Anchor.TopRight, }, floatingOverlayContent.Add); - loadComponentSingleFile(loginOverlay = new LoginOverlay - { - GetToolbarHeight = () => ToolbarOffset, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }, floatingOverlayContent.Add); - loadComponentSingleFile(accountCreation = new AccountCreationOverlay(), topMostOverlayContent.Add); loadComponentSingleFile(dialogOverlay = new DialogOverlay(), topMostOverlayContent.Add); From eefc55f89bbf9ab64e94291a01c792473955645b Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 29 Mar 2019 00:20:16 -0700 Subject: [PATCH 309/623] Fix volume overlay being blocked by other floating overlays - excluding settings --- osu.Game/OsuGame.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e470d554c9..2172f5870d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -421,7 +421,6 @@ namespace osu.Game }, }, topMostOverlayContent.Add); - loadComponentSingleFile(volume = new VolumeOverlay(), floatingOverlayContent.Add); loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add); loadComponentSingleFile(loginOverlay = new LoginOverlay @@ -438,7 +437,6 @@ namespace osu.Game loadComponentSingleFile(social = new SocialOverlay(), overlayContent.Add); loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal); loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add); - loadComponentSingleFile(settings = new MainSettings { GetToolbarHeight = () => ToolbarOffset }, floatingOverlayContent.Add); loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add); loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add); @@ -456,6 +454,9 @@ namespace osu.Game Origin = Anchor.TopRight, }, floatingOverlayContent.Add); + loadComponentSingleFile(volume = new VolumeOverlay(), floatingOverlayContent.Add); + loadComponentSingleFile(settings = new MainSettings { GetToolbarHeight = () => ToolbarOffset }, floatingOverlayContent.Add); + loadComponentSingleFile(accountCreation = new AccountCreationOverlay(), topMostOverlayContent.Add); loadComponentSingleFile(dialogOverlay = new DialogOverlay(), topMostOverlayContent.Add); From 1ba608f01fcc3edb0f854ce4f2d6bc1e9bcd3777 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 29 Mar 2019 00:26:17 -0700 Subject: [PATCH 310/623] Remove line spacing on similar code --- osu.Game/OsuGame.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2172f5870d..9f47d545e2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -458,9 +458,7 @@ namespace osu.Game loadComponentSingleFile(settings = new MainSettings { GetToolbarHeight = () => ToolbarOffset }, floatingOverlayContent.Add); loadComponentSingleFile(accountCreation = new AccountCreationOverlay(), topMostOverlayContent.Add); - loadComponentSingleFile(dialogOverlay = new DialogOverlay(), topMostOverlayContent.Add); - loadComponentSingleFile(externalLinkOpener = new ExternalLinkOpener(), topMostOverlayContent.Add); dependencies.CacheAs(idleTracker); From 352b4b20d917d34d2d8f9ec4b34e9b9323f41c46 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 29 Mar 2019 16:28:25 +0900 Subject: [PATCH 311/623] Correct the sizes of TestCaseLogoFacadeContainer --- osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs index f81e70e7b0..adbf22302b 100644 --- a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual { Alpha = 0.35f, RelativeSizeAxes = Axes.None, - Size = new Vector2(107), + Size = new Vector2(72), Child = visualBox = new Box { Colour = Color4.Tomato, @@ -133,7 +133,7 @@ namespace osu.Game.Tests.Visual { Alpha = 0.35f, RelativeSizeAxes = Axes.None, - Size = new Vector2(107), + Size = new Vector2(72), Child = transferContainerBox = new Box { Colour = Color4.White, @@ -172,7 +172,7 @@ namespace osu.Game.Tests.Visual this.logo = logo; logo.FadeIn(350); logo.ScaleTo(new Vector2(0.15f), 350, Easing.In); - logoFacadeContainer.SetLogo(logo, 0.3f, 1000, Easing.InOutQuint); + logoFacadeContainer.SetLogo(logo, 1.0f, 1000, Easing.InOutQuint); logoFacadeContainer.Tracking = true; moveLogoFacade(); } From 952a12bb19b118fa129b8aa73551906e8baa8388 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 29 Mar 2019 16:54:34 +0900 Subject: [PATCH 312/623] Return logo relativepositionaxes on content out --- osu.Game/Screens/Play/PlayerLoader.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 53a349f595..9e6f7cf24e 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -37,6 +37,8 @@ namespace osu.Game.Screens.Play private BeatmapMetadataDisplay info; + private OsuLogo logo; + private bool hideOverlays; public override bool HideOverlaysOnEnter => hideOverlays; @@ -129,9 +131,12 @@ namespace osu.Game.Screens.Play private void contentOut() { - // Ensure the logo is no longer tracking before we scale the content. + // Ensure the logo is no longer tracking before we scale the content, and that its RelativePositionAxes have been returned. content.Tracking = false; + if (logo != null) + logo.RelativePositionAxes = Axes.Both; + content.ScaleTo(0.7f, 300, Easing.InQuint); content.FadeOut(250); } @@ -153,6 +158,8 @@ namespace osu.Game.Screens.Play { base.LogoArriving(logo, resuming); + this.logo = logo; + const double duration = 300; if (!resuming) From e4ef542710687f1d4253c0884e58ae91a943854e Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 29 Mar 2019 01:03:42 -0700 Subject: [PATCH 313/623] Remove unnecessary comments on issue templates --- .github/ISSUE_TEMPLATE/bug-issues.md | 9 +++------ .github/ISSUE_TEMPLATE/crash-issues.md | 11 ++++------- .github/ISSUE_TEMPLATE/feature-request-issues.md | 7 ++----- .github/ISSUE_TEMPLATE/missing-for-live-issues.md | 7 ++----- 4 files changed, 11 insertions(+), 23 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-issues.md b/.github/ISSUE_TEMPLATE/bug-issues.md index 8d85c92fec..ff958b706a 100644 --- a/.github/ISSUE_TEMPLATE/bug-issues.md +++ b/.github/ISSUE_TEMPLATE/bug-issues.md @@ -2,13 +2,10 @@ name: Bug Report about: For issues regarding encountered game bugs --- - - - -**Describe your problem:** +**Describe your problem:** **Screenshots or videos showing encountered issue:** -**osu!lazer version:** +**osu!lazer version:** -**Logs:** \ No newline at end of file +**Logs:** diff --git a/.github/ISSUE_TEMPLATE/crash-issues.md b/.github/ISSUE_TEMPLATE/crash-issues.md index 849f042c1f..4f10e41c00 100644 --- a/.github/ISSUE_TEMPLATE/crash-issues.md +++ b/.github/ISSUE_TEMPLATE/crash-issues.md @@ -2,15 +2,12 @@ name: Crash Report about: For issues regarding game crashes or permanent freezes --- - - - -**Describe your problem:** +**Describe your problem:** **Screenshots or videos showing encountered issue:** -**osu!lazer version:** +**osu!lazer version:** -**Logs:** +**Logs:** -**Computer Specifications:** \ No newline at end of file +**Computer Specifications:** diff --git a/.github/ISSUE_TEMPLATE/feature-request-issues.md b/.github/ISSUE_TEMPLATE/feature-request-issues.md index 73c4f37a3e..dcf961a93f 100644 --- a/.github/ISSUE_TEMPLATE/feature-request-issues.md +++ b/.github/ISSUE_TEMPLATE/feature-request-issues.md @@ -2,9 +2,6 @@ name: Feature Request about: Let us know what you would like to see in the game! --- +**Describe the feature:** - - -**Describe the feature:** - -**Proposal designs of the feature:** +**Proposal designs of the feature:** diff --git a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md b/.github/ISSUE_TEMPLATE/missing-for-live-issues.md index ae3cf20a8c..09d22cca82 100644 --- a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md +++ b/.github/ISSUE_TEMPLATE/missing-for-live-issues.md @@ -2,9 +2,6 @@ name: Missing for Live about: Let us know the features you need which are available in osu-stable but not lazer --- +**Describe the feature:** - - -**Describe the feature:** - -**Designs:** +**Designs:** From 669397b14b4c5075b8b4382690dbe55eeb34f57d Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 29 Mar 2019 01:12:42 -0700 Subject: [PATCH 314/623] Specify labels by the template's purpose --- .github/ISSUE_TEMPLATE/bug-issues.md | 2 +- .github/ISSUE_TEMPLATE/crash-issues.md | 2 +- .github/ISSUE_TEMPLATE/feature-request-issues.md | 2 +- .github/ISSUE_TEMPLATE/missing-for-live-issues.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-issues.md b/.github/ISSUE_TEMPLATE/bug-issues.md index ff958b706a..b24abd624b 100644 --- a/.github/ISSUE_TEMPLATE/bug-issues.md +++ b/.github/ISSUE_TEMPLATE/bug-issues.md @@ -2,7 +2,7 @@ name: Bug Report about: For issues regarding encountered game bugs --- -**Describe your problem:** +**Describe the bug:** **Screenshots or videos showing encountered issue:** diff --git a/.github/ISSUE_TEMPLATE/crash-issues.md b/.github/ISSUE_TEMPLATE/crash-issues.md index 4f10e41c00..f16011a4a6 100644 --- a/.github/ISSUE_TEMPLATE/crash-issues.md +++ b/.github/ISSUE_TEMPLATE/crash-issues.md @@ -2,7 +2,7 @@ name: Crash Report about: For issues regarding game crashes or permanent freezes --- -**Describe your problem:** +**Describe the crash:** **Screenshots or videos showing encountered issue:** diff --git a/.github/ISSUE_TEMPLATE/feature-request-issues.md b/.github/ISSUE_TEMPLATE/feature-request-issues.md index dcf961a93f..6d5fe9cbe1 100644 --- a/.github/ISSUE_TEMPLATE/feature-request-issues.md +++ b/.github/ISSUE_TEMPLATE/feature-request-issues.md @@ -2,6 +2,6 @@ name: Feature Request about: Let us know what you would like to see in the game! --- -**Describe the feature:** +**Describe the new feature:** **Proposal designs of the feature:** diff --git a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md b/.github/ISSUE_TEMPLATE/missing-for-live-issues.md index 09d22cca82..870bf7fabd 100644 --- a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md +++ b/.github/ISSUE_TEMPLATE/missing-for-live-issues.md @@ -2,6 +2,6 @@ name: Missing for Live about: Let us know the features you need which are available in osu-stable but not lazer --- -**Describe the feature:** +**Describe the missing feature:** **Designs:** From 85abcb3de06cba91dcc9111f4edd8a67e8fb3b5c Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 29 Mar 2019 01:15:04 -0700 Subject: [PATCH 315/623] Remove regular issue template --- .github/ISSUE_TEMPLATE.md | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 2bff304fba..0000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,11 +0,0 @@ -osu!lazer is currently still under heavy development! - -Please ensure that you are making an issue for one of the following: - -- A bug with currently implemented features (not features that don't exist) -- A feature you are considering adding, so we can collaborate on feedback and design. -- Discussions about technical design decisions - -If your issue qualifies, replace this text with a detailed description of your issue with as much relevant information as you can provide. - -Screenshots and log files are highly welcomed. \ No newline at end of file From 47711eef42f5bb9118d9ddb49abc921121502c83 Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Fri, 29 Mar 2019 07:51:28 -0700 Subject: [PATCH 316/623] Reword issue descriptions Co-Authored-By: Joehuu --- .github/ISSUE_TEMPLATE/bug-issues.md | 2 +- .github/ISSUE_TEMPLATE/crash-issues.md | 2 +- .github/ISSUE_TEMPLATE/feature-request-issues.md | 2 +- .github/ISSUE_TEMPLATE/missing-for-live-issues.md | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-issues.md b/.github/ISSUE_TEMPLATE/bug-issues.md index b24abd624b..c8c41e5a78 100644 --- a/.github/ISSUE_TEMPLATE/bug-issues.md +++ b/.github/ISSUE_TEMPLATE/bug-issues.md @@ -1,6 +1,6 @@ --- name: Bug Report -about: For issues regarding encountered game bugs +about: Issues regarding encountered bugs. --- **Describe the bug:** diff --git a/.github/ISSUE_TEMPLATE/crash-issues.md b/.github/ISSUE_TEMPLATE/crash-issues.md index f16011a4a6..8ad27e9e31 100644 --- a/.github/ISSUE_TEMPLATE/crash-issues.md +++ b/.github/ISSUE_TEMPLATE/crash-issues.md @@ -1,6 +1,6 @@ --- name: Crash Report -about: For issues regarding game crashes or permanent freezes +about: Issues regarding crashes or permanent freezes. --- **Describe the crash:** diff --git a/.github/ISSUE_TEMPLATE/feature-request-issues.md b/.github/ISSUE_TEMPLATE/feature-request-issues.md index 6d5fe9cbe1..54c4ff94e5 100644 --- a/.github/ISSUE_TEMPLATE/feature-request-issues.md +++ b/.github/ISSUE_TEMPLATE/feature-request-issues.md @@ -1,6 +1,6 @@ --- name: Feature Request -about: Let us know what you would like to see in the game! +about: Features you would like to see in the game! --- **Describe the new feature:** diff --git a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md b/.github/ISSUE_TEMPLATE/missing-for-live-issues.md index 870bf7fabd..5822da9c65 100644 --- a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md +++ b/.github/ISSUE_TEMPLATE/missing-for-live-issues.md @@ -1,7 +1,7 @@ --- name: Missing for Live -about: Let us know the features you need which are available in osu-stable but not lazer +about: Features which are available in osu!stable but not yet in osu!lazer. --- **Describe the missing feature:** -**Designs:** +**Proposal designs of the feature:** From 22519ddacca0b7d7b1fdf035284c31138af0d7a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 30 Mar 2019 12:45:09 +0900 Subject: [PATCH 317/623] Update framework --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f958d00860..52c53503ee 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index b9832c1cf3..9ecc7d4632 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 41d776b0900e9f202a1a34bfc33afaf379dfac08 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 30 Mar 2019 23:56:38 +0900 Subject: [PATCH 318/623] Fix stable import failing Regressed due to null GameHost. Fixes the new reports in #4043. --- osu.Desktop/OsuGameDesktop.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 7e5b003f03..e7e0af7eea 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -14,6 +14,7 @@ using osuTK.Input; using Microsoft.Win32; using osu.Desktop.Updater; using osu.Framework; +using osu.Framework.Logging; using osu.Framework.Platform.Windows; using osu.Framework.Screens; using osu.Game.Screens.Menu; @@ -35,12 +36,15 @@ namespace osu.Desktop { try { - return new StableStorage(); + if (Host is DesktopGameHost desktopHost) + return new StableStorage(desktopHost); } - catch + catch (Exception e) { - return null; + Logger.Error(e, "Error while searching for stable install"); } + + return null; } protected override void LoadComplete() @@ -139,8 +143,8 @@ namespace osu.Desktop return null; } - public StableStorage() - : base(string.Empty, null) + public StableStorage(DesktopGameHost host) + : base(string.Empty, host) { } } From 42eaabe24c06d785f8503ad35ff3792c709cb142 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 Mar 2019 01:29:37 +0900 Subject: [PATCH 319/623] Fix editor blueprints being misplaced Regressed with PlayfieldAdjustmentContainer changes. --- osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 2 +- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs | 4 +++- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 -- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 2 +- osu.Game/Rulesets/Edit/DrawableEditRuleset.cs | 4 ++++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- 9 files changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index a8ae5c7337..ba0f5b90ba 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation); - protected override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer(); + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer(); protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index e9aa69e4f3..1c1ec604f6 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Mania.UI /// The column which intersects with . public Column GetColumnByPosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition); - protected override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer(); + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer(); protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages); diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 3ae554a5d7..d9cb203bdf 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -15,7 +15,9 @@ namespace osu.Game.Rulesets.Osu.Edit { } - protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor { Size = Vector2.One }; + protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor(); + + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { Size = Vector2.One }; private class OsuPlayfieldNoCursor : OsuPlayfield { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 952fe0b708..1c1e51a457 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -38,8 +38,6 @@ namespace osu.Game.Rulesets.Osu.Edit public override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); - protected override Container CreateLayerContainer() => new OsuPlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both }; - public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) { switch (hitObject) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 162e6e596d..828b3720d3 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); - protected override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer(); + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer(); public override DrawableHitObject CreateDrawableRepresentation(OsuHitObject h) { diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index c71141b4c7..f4b9c46dfc 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Taiko.UI public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); - protected override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer(); + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer(); protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); diff --git a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs index 68d57c559e..2200caeb20 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Edit /// public abstract Playfield Playfield { get; } + public abstract PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer(); + internal DrawableEditRuleset() { RelativeSizeAxes = Axes.Both; @@ -43,6 +45,8 @@ namespace osu.Game.Rulesets.Edit { public override Playfield Playfield => drawableRuleset.Playfield; + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => drawableRuleset.CreatePlayfieldAdjustmentContainer(); + private Ruleset ruleset => drawableRuleset.Ruleset; private Beatmap beatmap => drawableRuleset.Beatmap; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 45bf9b8be7..3f735b2826 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -178,7 +178,7 @@ namespace osu.Game.Rulesets.Edit /// /// Creates a which provides a layer above or below the . /// - protected virtual Container CreateLayerContainer() => new Container { RelativeSizeAxes = Axes.Both }; + protected virtual Container CreateLayerContainer() => DrawableRuleset.CreatePlayfieldAdjustmentContainer(); } public abstract class HitObjectComposer : HitObjectComposer diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 8345b0c5cf..bbb587cb3f 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.UI return dependencies; } - protected virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer(); + public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer(); [BackgroundDependencyLoader] private void load(OsuConfigManager config) From 5d91c3bcfc2383c909c95edd376a6fd43f4c20a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 Mar 2019 01:33:56 +0900 Subject: [PATCH 320/623] Fix replay handler nullref crashes --- .../Replays/OsuFramedReplayInputHandler.cs | 2 +- .../Replays/FramedReplayInputHandler.cs | 25 +++++++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs index 614edba400..c6ac1dd346 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Replays { } - protected override bool IsImportant(OsuReplayFrame frame) => frame?.Actions.Any() ?? false; + protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any(); protected Vector2? Position { diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 178e1c2364..e8c5472ba6 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Input.StateChanges; using osu.Game.Input.Handlers; using osu.Game.Replays; @@ -87,14 +88,24 @@ namespace osu.Game.Rulesets.Replays protected bool HasFrames => Frames.Count > 0; - private bool inImportantSection => - HasFrames && FrameAccuratePlayback && - //a button is in a pressed state - IsImportant(currentDirection > 0 ? CurrentFrame : NextFrame) && - //the next frame is within an allowable time span - Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= AllowedImportantTimeSpan; + private bool inImportantSection + { + get + { + if (!HasFrames || !FrameAccuratePlayback) + return false; - protected virtual bool IsImportant(TFrame frame) => false; + var checkFrame = currentDirection > 0 ? CurrentFrame : NextFrame; + + if (checkFrame == null) + return false; + + return IsImportant(currentDirection > 0 ? CurrentFrame : NextFrame) && //a button is in a pressed state + Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= AllowedImportantTimeSpan; //the next frame is within an allowable time span + } + } + + protected virtual bool IsImportant([NotNull] TFrame frame) => false; /// /// Update the current frame based on an incoming time value. From d53e6f7e0eec1fce3549b08b22db1d25def77dea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 Mar 2019 01:34:50 +0900 Subject: [PATCH 321/623] Remove unused references --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 1c1e51a457..039ec5585e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; @@ -13,7 +11,6 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; From 73de146fb465ad0b0beb0c8474b36513cf01669c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 Mar 2019 01:42:38 +0900 Subject: [PATCH 322/623] Update test null check to match --- osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs index 976bb3e177..73387fa5ab 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs @@ -278,7 +278,7 @@ namespace osu.Game.Tests.NonVisual protected override double AllowedImportantTimeSpan => 1000; - protected override bool IsImportant(TestReplayFrame frame) => frame?.IsImportant ?? false; + protected override bool IsImportant(TestReplayFrame frame) => frame.IsImportant; } } } From 92673c6d1ea78fa3d9384e70cf7640250a8ee035 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 30 Mar 2019 19:24:16 -0700 Subject: [PATCH 323/623] Add ability to sort by favourites on osu!direct --- osu.Game/Overlays/Direct/FilterControl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs index d7e0760fc6..268e011350 100644 --- a/osu.Game/Overlays/Direct/FilterControl.cs +++ b/osu.Game/Overlays/Direct/FilterControl.cs @@ -116,5 +116,6 @@ namespace osu.Game.Overlays.Direct Ranked, Rating, Plays, + Favourites, } } From 698e38c4e059085a36ea2c4cfab137c05a2acab4 Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Sun, 31 Mar 2019 20:10:44 +0800 Subject: [PATCH 324/623] make menu flashes and visualisation colourable by skin --- osu.Game/Screens/Menu/LogoVisualisation.cs | 24 +++++++++++- osu.Game/Screens/Menu/MenuSideFlashes.cs | 44 ++++++++++++++++++---- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index a41a12927b..8501040ca7 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -12,6 +12,9 @@ using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Skinning; +using osu.Game.Online.API; +using osu.Game.Users; using System; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -66,18 +69,27 @@ namespace osu.Game.Screens.Menu private IShader shader; private readonly Texture texture; + private Bindable user; + private Bindable skin; + public LogoVisualisation() { texture = Texture.WhitePixel; - AccentColour = new Color4(1, 1, 1, 0.2f); Blending = BlendingMode.Additive; } [BackgroundDependencyLoader] - private void load(ShaderManager shaders, IBindable beatmap) + private void load(ShaderManager shaders, IBindable beatmap, IAPIProvider api, SkinManager skinManager) { this.beatmap.BindTo(beatmap); shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); + user = api.LocalUser.GetBoundCopy(); + skin = skinManager.CurrentSkin.GetBoundCopy(); + + user.ValueChanged += _ => changeColour(); + skin.ValueChanged += _ => changeColour(); + + changeColour(); } private void updateAmplitudes() @@ -107,6 +119,14 @@ namespace osu.Game.Screens.Menu Scheduler.AddDelayed(updateAmplitudes, time_between_updates); } + private void changeColour() + { + if (user.Value?.IsSupporter ?? false) + AccentColour = skin.Value.GetValue(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? new Color4(1, 1, 1, 0.2f); + else + AccentColour = new Color4(1, 1, 1, 0.2f); + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index ce0a38ba8d..af4fa40b14 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -12,6 +12,9 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Skinning; +using osu.Game.Online.API; +using osu.Game.Users; using System; using osu.Framework.Bindables; @@ -32,6 +35,12 @@ namespace osu.Game.Screens.Menu private const double box_fade_in_time = 65; private const int box_width = 200; + private Bindable user; + private Bindable skin; + + [Resolved] + private OsuColour colours { get; set; } + public MenuSideFlashes() { EarlyActivationMilliseconds = box_fade_in_time; @@ -42,13 +51,15 @@ namespace osu.Game.Screens.Menu } [BackgroundDependencyLoader] - private void load(IBindable beatmap, OsuColour colours) + private void load(IBindable beatmap, IAPIProvider api, SkinManager skinManager) { this.beatmap.BindTo(beatmap); - // linear colour looks better in this case, so let's use it for now. - Color4 gradientDark = colours.Blue.Opacity(0).ToLinear(); - Color4 gradientLight = colours.Blue.Opacity(0.6f).ToLinear(); + user = api.LocalUser.GetBoundCopy(); + skin = skinManager.CurrentSkin.GetBoundCopy(); + + user.ValueChanged += _ => changeColour(); + skin.ValueChanged += _ => changeColour(); Children = new Drawable[] { @@ -62,8 +73,7 @@ namespace osu.Game.Screens.Menu // align off-screen to make sure our edges don't become visible during parallax. X = -box_width, Alpha = 0, - Blending = BlendingMode.Additive, - Colour = ColourInfo.GradientHorizontal(gradientLight, gradientDark) + Blending = BlendingMode.Additive }, rightBox = new Box { @@ -74,10 +84,11 @@ namespace osu.Game.Screens.Menu Height = 1.5f, X = box_width, Alpha = 0, - Blending = BlendingMode.Additive, - Colour = ColourInfo.GradientHorizontal(gradientDark, gradientLight) + Blending = BlendingMode.Additive } }; + + changeColour(); } protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) @@ -97,5 +108,22 @@ namespace osu.Game.Screens.Menu .Then() .FadeOut(beatLength, Easing.In); } + + private void changeColour() + { + Color4 baseColour; + + if (user.Value?.IsSupporter ?? false) + baseColour = skin.Value.GetValue(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? colours.Blue; + else + baseColour = colours.Blue; + + // linear colour looks better in this case, so let's use it for now. + Color4 gradientDark = baseColour.Opacity(0).ToLinear(); + Color4 gradientLight = baseColour.Opacity(0.6f).ToLinear(); + + leftBox.Colour = ColourInfo.GradientHorizontal(gradientLight, gradientDark); + rightBox.Colour = ColourInfo.GradientHorizontal(gradientDark, gradientLight); + } } } From 8088e27fa84c099cc88ae846a080a127202a989b Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Sun, 31 Mar 2019 21:10:35 +0800 Subject: [PATCH 325/623] Mimic stable minimum cursor size --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 2d8cfa12ee..795f0b43f7 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -55,7 +55,7 @@ namespace osu.Game.Configuration // Input Set(OsuSetting.MenuCursorSize, 1.0, 0.5f, 2, 0.01); - Set(OsuSetting.GameplayCursorSize, 1.0, 0.5f, 2, 0.01); + Set(OsuSetting.GameplayCursorSize, 1.0, 0.1f, 2, 0.01); Set(OsuSetting.AutoCursorSize, false); Set(OsuSetting.MouseDisableButtons, false); From f45367583802f595e148947c9335cc8975c09c9a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Apr 2019 10:31:20 +0900 Subject: [PATCH 326/623] Fix replays being parsed with incorrect cultures --- osu.Game/Scoring/Legacy/LegacyScoreParser.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs index 51810945db..969a5bba5a 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using System.IO; using System.Linq; using osu.Game.Beatmaps; @@ -232,7 +233,10 @@ namespace osu.Game.Scoring.Legacy if (diff < 0) continue; - replay.Frames.Add(convertFrame(new LegacyReplayFrame(lastTime, float.Parse(split[1]), float.Parse(split[2]), (ReplayButtonState)int.Parse(split[3])))); + replay.Frames.Add(convertFrame(new LegacyReplayFrame(lastTime, + float.Parse(split[1], CultureInfo.InvariantCulture), + float.Parse(split[2], CultureInfo.InvariantCulture), + (ReplayButtonState)int.Parse(split[3])))); } } From d1a175675df1cf11c466f641eaefd9a2657f17cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Apr 2019 10:37:02 +0900 Subject: [PATCH 327/623] Use variable --- osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index e8c5472ba6..3830fa5cbe 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -95,12 +95,12 @@ namespace osu.Game.Rulesets.Replays if (!HasFrames || !FrameAccuratePlayback) return false; - var checkFrame = currentDirection > 0 ? CurrentFrame : NextFrame; + var frame = currentDirection > 0 ? CurrentFrame : NextFrame; - if (checkFrame == null) + if (frame == null) return false; - return IsImportant(currentDirection > 0 ? CurrentFrame : NextFrame) && //a button is in a pressed state + return IsImportant(frame) && //a button is in a pressed state Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= AllowedImportantTimeSpan; //the next frame is within an allowable time span } } From 6896ec773154a8caebb929dae95007bbfe48f581 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Apr 2019 10:39:55 +0900 Subject: [PATCH 328/623] Remove unnecessary method --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 3f735b2826..41de0c36fc 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -63,10 +63,10 @@ namespace osu.Game.Rulesets.Edit return; } - var layerBelowRuleset = CreateLayerContainer(); + var layerBelowRuleset = DrawableRuleset.CreatePlayfieldAdjustmentContainer(); layerBelowRuleset.Child = new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both }; - var layerAboveRuleset = CreateLayerContainer(); + var layerAboveRuleset = DrawableRuleset.CreatePlayfieldAdjustmentContainer(); layerAboveRuleset.Child = blueprintContainer = new BlueprintContainer(); layerContainers.Add(layerBelowRuleset); @@ -174,11 +174,6 @@ namespace osu.Game.Rulesets.Edit /// Creates a which outlines s and handles movement of selections. /// public virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); - - /// - /// Creates a which provides a layer above or below the . - /// - protected virtual Container CreateLayerContainer() => DrawableRuleset.CreatePlayfieldAdjustmentContainer(); } public abstract class HitObjectComposer : HitObjectComposer From fb0bba9b37df499eb03c7953faf47cccd8ab7394 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Apr 2019 11:23:07 +0900 Subject: [PATCH 329/623] Use Parsing helpers --- osu.Game/Scoring/Legacy/LegacyScoreParser.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs index 969a5bba5a..d1649a6098 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -2,10 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Globalization; using System.IO; using System.Linq; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; using osu.Game.IO.Legacy; using osu.Game.Replays; @@ -225,7 +225,7 @@ namespace osu.Game.Scoring.Legacy continue; } - var diff = float.Parse(split[0]); + var diff = Parsing.ParseFloat(split[0]); lastTime += diff; // Todo: At some point we probably want to rewind and play back the negative-time frames @@ -234,9 +234,9 @@ namespace osu.Game.Scoring.Legacy continue; replay.Frames.Add(convertFrame(new LegacyReplayFrame(lastTime, - float.Parse(split[1], CultureInfo.InvariantCulture), - float.Parse(split[2], CultureInfo.InvariantCulture), - (ReplayButtonState)int.Parse(split[3])))); + Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE), + Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE), + (ReplayButtonState)Parsing.ParseInt(split[3])))); } } From 7d6a08d6dac94a942174ae52aa4a5f8ab9357809 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Apr 2019 11:39:02 +0900 Subject: [PATCH 330/623] Fix a few new inspections in latest Rider EAP --- .../Replays/OsuAutoGenerator.cs | 6 ++---- osu.Game.Tests/Visual/TestCaseCharLookup.cs | 16 ++++++++++++++++ osu.Game/Scoring/Legacy/LegacyScoreParser.cs | 4 ++++ osu.Game/Skinning/LegacySkinDecoder.cs | 11 +++-------- osu.sln.DotSettings | 1 + 5 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 osu.Game.Tests/Visual/TestCaseCharLookup.cs diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index c1aaa7767e..690263c6a0 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -199,6 +199,7 @@ namespace osu.Game.Rulesets.Osu.Replays // Wait until Auto could "see and react" to the next note. double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime); + if (waitTime > lastFrame.Time) { lastFrame = new OsuReplayFrame(waitTime, lastFrame.Position) { Actions = lastFrame.Actions }; @@ -292,7 +293,6 @@ namespace osu.Game.Rulesets.Osu.Replays { // We add intermediate frames for spinning / following a slider here. case Spinner spinner: - { Vector2 difference = startPosition - SPINNER_CENTRE; float radius = difference.Length; @@ -315,9 +315,8 @@ namespace osu.Game.Rulesets.Osu.Replays endFrame.Position = endPosition; break; - } + case Slider slider: - { for (double j = FrameDelay; j < slider.Duration; j += FrameDelay) { Vector2 pos = slider.StackedPositionAt(j / slider.Duration); @@ -326,7 +325,6 @@ namespace osu.Game.Rulesets.Osu.Replays AddFrameToReplay(new OsuReplayFrame(slider.EndTime, new Vector2(slider.StackedEndPosition.X, slider.StackedEndPosition.Y), action)); break; - } } // We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed! diff --git a/osu.Game.Tests/Visual/TestCaseCharLookup.cs b/osu.Game.Tests/Visual/TestCaseCharLookup.cs new file mode 100644 index 0000000000..0b9413f332 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseCharLookup.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseCharLookup : OsuTestCase + { + public TestCaseCharLookup() + { + AddStep("null", () => { }); + AddStep("display acharacter", () => Add(new OsuSpriteText { Text = "振込申請" })); + } + } +} diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs index 51810945db..bc54882f28 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -88,6 +88,7 @@ namespace osu.Game.Scoring.Legacy throw new IOException("input .lzma is too short"); long outSize = 0; + for (int i = 0; i < 8; i++) { int v = replayInStream.ReadByte(); @@ -144,6 +145,7 @@ namespace osu.Game.Scoring.Legacy score.Rank = ScoreRank.D; break; } + case 1: { int totalHits = count50 + count100 + count300 + countMiss; @@ -166,6 +168,7 @@ namespace osu.Game.Scoring.Legacy score.Rank = ScoreRank.D; break; } + case 2: { int totalHits = count50 + count100 + count300 + countMiss + countKatu; @@ -185,6 +188,7 @@ namespace osu.Game.Scoring.Legacy score.Rank = ScoreRank.D; break; } + case 3: { int totalHits = count50 + count100 + count300 + countMiss + countGeki + countKatu; diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 96a9116c51..461dfed589 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -16,12 +16,11 @@ namespace osu.Game.Skinning { line = StripComments(line); + var pair = SplitKeyVal(line); + switch (section) { case Section.General: - { - var pair = SplitKeyVal(line); - switch (pair.Key) { case @"Name": @@ -36,11 +35,8 @@ namespace osu.Game.Skinning } break; - } - case Section.Fonts: - { - var pair = SplitKeyVal(line); + case Section.Fonts: switch (pair.Key) { case "HitCirclePrefix": @@ -52,7 +48,6 @@ namespace osu.Game.Skinning } break; - } } base.ParseLine(skin, section, line); diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 71cbd83e3e..cfffed663c 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -230,6 +230,7 @@ True True NEXT_LINE + 1 NEXT_LINE 1 1 From 612db31c38737e3be893fb65f7a598730c7a1960 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Apr 2019 12:16:05 +0900 Subject: [PATCH 331/623] Apply newline additions --- osu.Desktop/OsuGameDesktop.cs | 2 + osu.Desktop/Overlays/VersionManager.cs | 1 + osu.Desktop/Program.cs | 1 + .../Beatmaps/CatchBeatmapProcessor.cs | 3 ++ .../Difficulty/CatchDifficultyCalculator.cs | 1 + .../Objects/Drawable/DrawableFruit.cs | 11 +++++ osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 5 +++ .../TestCaseNotes.cs | 2 + .../Beatmaps/ManiaBeatmapConverter.cs | 1 + .../Legacy/DistanceObjectPatternGenerator.cs | 7 ++++ .../Legacy/HitObjectPatternGenerator.cs | 9 ++++ .../Patterns/Legacy/PatternGenerator.cs | 1 + osu.Game.Rulesets.Mania/ManiaRuleset.cs | 7 ++++ .../Objects/Drawables/Pieces/BodyPiece.cs | 1 + .../Replays/ManiaAutoGenerator.cs | 3 ++ .../Replays/ManiaReplayFrame.cs | 1 + .../UI/DrawableManiaRuleset.cs | 3 ++ osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 2 + .../Beatmaps/OsuBeatmapProcessor.cs | 4 ++ .../Difficulty/OsuPerformanceCalculator.cs | 1 + .../Preprocessing/OsuDifficultyHitObject.cs | 1 + .../Difficulty/Skills/Speed.cs | 2 + .../Sliders/SliderPlacementBlueprint.cs | 2 + osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs | 2 + .../Connections/FollowPointRenderer.cs | 1 + .../Objects/Drawables/DrawableHitCircle.cs | 3 ++ .../UI/Cursor/CursorTrail.cs | 1 + .../Beatmaps/TaikoBeatmapConverter.cs | 1 + .../Objects/Drawables/DrawableHit.cs | 4 ++ .../Drawables/DrawableTaikoHitObject.cs | 1 + osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 1 + .../Replays/TaikoAutoGenerator.cs | 4 ++ .../SongSelect/TestCaseBeatmapCarousel.cs | 1 + .../TestCaseBeatmapScoresContainer.cs | 1 + osu.Game/Beatmaps/BeatmapManager.cs | 2 + .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 + osu.Game/Beatmaps/Formats/Decoder.cs | 1 + .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 42 +++++++++++++++++++ osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 1 + .../Formats/LegacyStoryboardDecoder.cs | 35 ++++++++++++++-- .../Configuration/DatabasedConfigManager.cs | 1 + osu.Game/Database/ArchiveModelManager.cs | 2 + osu.Game/Database/OsuDbContext.cs | 3 ++ .../Containers/ConstrainedIconContainer.cs | 1 + .../Graphics/Containers/LinkFlowContainer.cs | 7 ++++ .../Graphics/Containers/SectionsContainer.cs | 2 + osu.Game/Graphics/Cursor/MenuCursor.cs | 2 + .../Graphics/Cursor/OsuTooltipContainer.cs | 1 + osu.Game/Graphics/DrawableDate.cs | 2 + osu.Game/Graphics/UserInterface/BarGraph.cs | 2 + osu.Game/Graphics/UserInterface/LineGraph.cs | 1 + .../Graphics/UserInterface/OsuSliderBar.cs | 1 + .../Graphics/UserInterface/StarCounter.cs | 2 + osu.Game/IO/Legacy/SerializationReader.cs | 21 ++++++++++ osu.Game/IO/Legacy/SerializationWriter.cs | 1 + .../Converters/TypedListConverter.cs | 2 + osu.Game/Online/API/APIAccess.cs | 2 + .../Requests/Responses/APILegacyScoreInfo.cs | 7 ++++ osu.Game/Online/Chat/MessageFormatter.cs | 13 ++++++ osu.Game/Online/Leaderboards/Leaderboard.cs | 6 +++ osu.Game/OsuGame.cs | 9 ++++ .../Overlays/AccountCreation/ScreenEntry.cs | 1 + osu.Game/Overlays/Chat/DrawableChannel.cs | 1 + osu.Game/Overlays/ChatOverlay.cs | 2 + osu.Game/Overlays/Dialog/PopupDialog.cs | 2 + osu.Game/Overlays/DirectOverlay.cs | 2 + osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 1 + osu.Game/Overlays/Mods/ModButton.cs | 4 ++ osu.Game/Overlays/Mods/ModSection.cs | 1 + osu.Game/Overlays/Music/PlaylistList.cs | 1 + osu.Game/Overlays/Music/PlaylistOverlay.cs | 1 + osu.Game/Overlays/MusicController.cs | 3 ++ .../Overlays/Profile/Header/BadgeContainer.cs | 1 + osu.Game/Overlays/Profile/ProfileHeader.cs | 6 +-- osu.Game/Overlays/Settings/SettingsItem.cs | 1 + osu.Game/Overlays/Settings/SidebarButton.cs | 1 + osu.Game/Overlays/SocialOverlay.cs | 4 ++ .../Toolbar/ToolbarOverlayToggleButton.cs | 2 + .../Toolbar/ToolbarRulesetSelector.cs | 1 + osu.Game/Overlays/UserProfileOverlay.cs | 1 + osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 2 + .../Objects/Drawables/DrawableHitObject.cs | 3 ++ .../Objects/Legacy/ConvertHitObjectParser.cs | 6 +++ osu.Game/Rulesets/Objects/SliderPath.cs | 3 ++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 1 + .../Backgrounds/BackgroundScreenBeatmap.cs | 1 + .../Screens/Edit/Compose/ComposeScreen.cs | 2 + osu.Game/Screens/Edit/Editor.cs | 5 +++ osu.Game/Screens/Edit/EditorClock.cs | 1 + osu.Game/Screens/Menu/ButtonSystem.cs | 11 +++++ osu.Game/Screens/Menu/LogoVisualisation.cs | 1 + .../Multi/Lounge/Components/RoomInspector.cs | 1 + .../Screens/Multi/Match/MatchSubScreen.cs | 1 + osu.Game/Screens/Multi/Multiplayer.cs | 1 + osu.Game/Screens/OsuScreenDependencies.cs | 2 + osu.Game/Screens/Play/HUD/ModDisplay.cs | 1 + osu.Game/Screens/Play/KeyCounter.cs | 1 + osu.Game/Screens/Play/SquareGraph.cs | 1 + .../Screens/Ranking/Pages/ScoreResultsPage.cs | 2 + osu.Game/Screens/Select/BeatmapCarousel.cs | 8 ++++ osu.Game/Screens/Select/PlaySongSelect.cs | 1 + osu.Game/Screens/Tournament/Drawings.cs | 1 + osu.Game/Skinning/LegacySkin.cs | 6 +++ osu.Game/Skinning/SkinManager.cs | 1 + .../Drawables/DrawableStoryboardAnimation.cs | 1 + osu.Game/Storyboards/StoryboardSprite.cs | 1 + .../Tests/Beatmaps/BeatmapConversionTest.cs | 2 + osu.sln.DotSettings | 1 + 108 files changed, 359 insertions(+), 7 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index e7e0af7eea..00cabbadf7 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -77,6 +77,7 @@ namespace osu.Desktop if (versionManager != null) versionManager.State = Visibility.Visible; break; + default: if (versionManager != null) versionManager.State = Visibility.Hidden; @@ -87,6 +88,7 @@ namespace osu.Desktop public override void SetHost(GameHost host) { base.SetHost(host); + if (host.Window is DesktopGameWindow desktopWindow) { desktopWindow.CursorState |= CursorState.Hidden; diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index 2998e08715..2fbbe6f685 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -95,6 +95,7 @@ namespace osu.Desktop.Overlays var version = game.Version; var lastVersion = config.Get(OsuSetting.Version); + if (game.IsDeployedBuild && version != lastVersion) { config.Set(OsuSetting.Version, version); diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index ff9972ac48..29554df64c 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -31,6 +31,7 @@ namespace osu.Desktop var importer = new ArchiveImportIPCChannel(host); // Restore the cwd so relative paths given at the command line work correctly Directory.SetCurrentDirectory(cwd); + foreach (var file in args) { Console.WriteLine(@"Importing {0}", file); diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 78b5a510b2..645cb5701a 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps initialiseHyperDash((List)Beatmap.HitObjects); int index = 0; + foreach (var obj in Beatmap.HitObjects.OfType()) { obj.IndexInBeatmap = index++; @@ -58,6 +59,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps } break; + case JuiceStream juiceStream: foreach (var nested in juiceStream.NestedHitObjects) { @@ -103,6 +105,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth); float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext); + if (distanceToHyper < 0) { currentObject.HyperDashTarget = nextObject; diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index b4998347f4..bd647fd667 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -73,6 +73,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty lastObject = hitObject; break; + case JuiceStream _: foreach (var nested in hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet))) { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 0dc3f73404..8368e2f276 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -105,6 +105,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { default: return new Container(); + case FruitVisualRepresentation.Raspberry: return new Container { @@ -143,6 +144,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable }, } }; + case FruitVisualRepresentation.Pineapple: return new Container { @@ -181,6 +183,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable }, } }; + case FruitVisualRepresentation.Pear: return new Container { @@ -213,6 +216,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable }, } }; + case FruitVisualRepresentation.Grape: return new Container { @@ -245,6 +249,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable }, } }; + case FruitVisualRepresentation.Banana: return new Container { @@ -282,19 +287,25 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable default: case FruitVisualRepresentation.Pear: return new Color4(17, 136, 170, 255); + case FruitVisualRepresentation.Grape: return new Color4(204, 102, 0, 255); + case FruitVisualRepresentation.Raspberry: return new Color4(121, 9, 13, 255); + case FruitVisualRepresentation.Pineapple: return new Color4(102, 136, 0, 255); + case FruitVisualRepresentation.Banana: switch (RNG.Next(0, 3)) { default: return new Color4(255, 240, 0, 255); + case 1: return new Color4(255, 192, 0, 255); + case 2: return new Color4(214, 221, 28, 255); } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 83f791690a..e7c7fd77df 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -292,6 +292,7 @@ namespace osu.Game.Rulesets.Catch.UI const float hyper_dash_transition_length = 180; bool previouslyHyperDashing = HyperDashing; + if (modifier <= 1 || X == targetPosition) { hyperDashModifier = 1; @@ -325,9 +326,11 @@ namespace osu.Game.Rulesets.Catch.UI case CatchAction.MoveLeft: currentDirection--; return true; + case CatchAction.MoveRight: currentDirection++; return true; + case CatchAction.Dash: Dashing = true; return true; @@ -343,9 +346,11 @@ namespace osu.Game.Rulesets.Catch.UI case CatchAction.MoveLeft: currentDirection++; return true; + case CatchAction.MoveRight: currentDirection--; return true; + case CatchAction.Dash: Dashing = false; return true; diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs index 0bc2c3ea28..2220873d89 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs @@ -168,11 +168,13 @@ namespace osu.Game.Rulesets.Mania.Tests foreach (var nested in obj.NestedHitObjects) { double finalPosition = (nested.HitObject.StartTime - obj.HitObject.StartTime) / endTime.Duration; + switch (direction) { case ScrollingDirection.Up: nested.Y = (float)(finalPosition * content.DrawHeight); break; + case ScrollingDirection.Down: nested.Y = (float)(-finalPosition * content.DrawHeight); break; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 71df68c087..704deba78b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -48,6 +48,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps if (IsForCurrentRuleset) { TargetColumns = (int)Math.Max(1, roundedCircleSize); + if (TargetColumns >= 10) { TargetColumns = TargetColumns / 2; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index ed52bdd23f..1b6ff16388 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -179,6 +179,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int usableColumns = TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects; int nextColumn = GetRandomColumn(); + for (int i = 0; i < Math.Min(usableColumns, noteCount); i++) { // Find available column @@ -217,6 +218,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy nextColumn = FindAvailableColumn(nextColumn, PreviousPattern); int lastColumn = nextColumn; + for (int i = 0; i < noteCount; i++) { addToPattern(pattern, nextColumn, startTime, startTime); @@ -299,6 +301,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int interval = Random.Next(1, TotalColumns - (legacy ? 1 : 0)); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + for (int i = 0; i <= spanCount; i++) { addToPattern(pattern, nextColumn, startTime, startTime); @@ -341,16 +344,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy p3 = 0; p4 = 0; break; + case 3: p2 = Math.Min(p2, 0.1); p3 = 0; p4 = 0; break; + case 4: p2 = Math.Min(p2, 0.3); p3 = Math.Min(p3, 0.04); p4 = 0; break; + case 5: p2 = Math.Min(p2, 0.34); p3 = Math.Min(p3, 0.1); @@ -440,6 +446,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == SampleInfo.HIT_WHISTLE || s.Name == SampleInfo.HIT_FINISH || s.Name == SampleInfo.HIT_CLAP); var rowPattern = new Pattern(); + for (int i = 0; i <= spanCount; i++) { if (!(ignoreHead && startTime == HitObject.StartTime)) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index 34f5f5c415..d13b21183b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -233,6 +233,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + for (int i = 0; i < noteCount; i++) { nextColumn = allowStacking @@ -303,6 +304,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int columnLimit = (TotalColumns % 2 == 0 ? TotalColumns : TotalColumns - 1) / 2; int nextColumn = GetRandomColumn(upperBound: columnLimit); + for (int i = 0; i < noteCount; i++) { nextColumn = FindAvailableColumn(nextColumn, upperBound: columnLimit, patterns: pattern); @@ -340,18 +342,21 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy p4 = 0; p5 = 0; break; + case 3: p2 = Math.Min(p2, 0.1); p3 = 0; p4 = 0; p5 = 0; break; + case 4: p2 = Math.Min(p2, 0.23); p3 = Math.Min(p3, 0.04); p4 = 0; p5 = 0; break; + case 5: p3 = Math.Min(p3, 0.15); p4 = Math.Min(p4, 0.03); @@ -384,20 +389,24 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy p2 = 0; p3 = 0; break; + case 3: centreProbability = Math.Min(centreProbability, 0.03); p2 = 0; p3 = 0; break; + case 4: centreProbability = 0; p2 = Math.Min(p2 * 2, 0.2); p3 = 0; break; + case 5: centreProbability = Math.Min(centreProbability, 0.03); p3 = 0; break; + case 6: centreProbability = 0; p2 = Math.Min(p2 * 2, 0.5); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index b702291c5d..fba52dfc32 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -158,6 +158,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy // Ensure that we have at least one free column, so that an endless loop is avoided bool hasValidColumns = false; + for (int i = lowerBound.Value; i < upperBound.Value; i++) { hasValidColumns = isValid(i); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 0ff79d2836..0ef53f0f37 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -117,6 +117,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModNoFail(), new MultiMod(new ManiaModHalfTime(), new ManiaModDaycore()), }; + case ModType.DifficultyIncrease: return new Mod[] { @@ -126,6 +127,7 @@ namespace osu.Game.Rulesets.Mania new MultiMod(new ManiaModFadeIn(), new ManiaModHidden()), new ManiaModFlashlight(), }; + case ModType.Conversion: return new Mod[] { @@ -142,16 +144,19 @@ namespace osu.Game.Rulesets.Mania new ManiaModDualStages(), new ManiaModMirror(), }; + case ModType.Automation: return new Mod[] { new MultiMod(new ManiaModAutoplay(), new ModCinema()), }; + case ModType.Fun: return new Mod[] { new MultiMod(new ModWindUp(), new ModWindDown()) }; + default: return new Mod[] { }; } @@ -214,6 +219,7 @@ namespace osu.Game.Rulesets.Mania SpecialAction = ManiaAction.Special1, NormalActionStart = ManiaAction.Key1, }.GenerateKeyBindingsFor(variant, out _); + case PlayfieldType.Dual: int keys = getDualStageKeyCount(variant); @@ -271,6 +277,7 @@ namespace osu.Game.Rulesets.Mania { default: return $"{variant}K"; + case PlayfieldType.Dual: { var keys = getDualStageKeyCount(variant); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs index 2baf1ad520..2bed1d42db 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs @@ -144,6 +144,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces const float animation_length = 50; Foreground.ClearTransforms(false, nameof(Foreground.Colour)); + if (hitting) { // wait for the next sync point diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 65b7d54cd2..e5669816fa 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Mania.Replays var normalAction = ManiaAction.Key1; var specialAction = ManiaAction.Special1; int totalCounter = 0; + foreach (var stage in Beatmap.Stages) { for (int i = 0; i < stage.Columns; i++) @@ -51,6 +52,7 @@ namespace osu.Game.Rulesets.Mania.Replays var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time); var actions = new List(); + foreach (var group in pointGroups) { foreach (var point in group) @@ -60,6 +62,7 @@ namespace osu.Game.Rulesets.Mania.Replays case HitPoint _: actions.Add(columnActions[point.Column]); break; + case ReleasePoint _: actions.Remove(columnActions[point.Column]); break; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index 81a76c93e6..f7277d3669 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Mania.Replays int activeColumns = (int)(legacyFrame.MouseX ?? 0); int counter = 0; + while (activeColumns > 0) { var isSpecial = stage.IsSpecialColumn(counter); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 1c1ec604f6..989bbdbfde 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -56,6 +56,7 @@ namespace osu.Game.Rulesets.Mania.UI double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - point.BeatLength : lastObjectTime + point.BeatLength * (int)point.TimeSignature; int index = 0; + for (double t = timingPoints[i].Time; Precision.DefinitelyBigger(endTime, t); t += point.BeatLength, index++) { barLines.Add(new BarLine @@ -104,8 +105,10 @@ namespace osu.Game.Rulesets.Mania.UI { case HoldNote holdNote: return new DrawableHoldNote(holdNote); + case Note note: return new DrawableNote(note); + default: return null; } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index cbabfcc8b4..5ab07416a6 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Mania.UI var normalColumnAction = ManiaAction.Key1; var specialColumnAction = ManiaAction.Special1; int firstColumnIndex = 0; + for (int i = 0; i < stageDefinitions.Count; i++) { var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); @@ -92,6 +93,7 @@ namespace osu.Game.Rulesets.Mania.UI private ManiaStage getStageByColumn(int column) { int sum = 0; + foreach (var stage in stages) { sum = sum + stage.Columns.Count; diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index 59a5f90fd0..b2beda18f4 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -44,12 +44,14 @@ namespace osu.Game.Rulesets.Osu.Beatmaps if (endIndex < 0) throw new ArgumentOutOfRangeException(nameof(endIndex), $"{nameof(endIndex)} cannot be less than 0."); int extendedEndIndex = endIndex; + if (endIndex < beatmap.HitObjects.Count - 1) { // Extend the end index to include objects they are stacked on for (int i = endIndex; i >= startIndex; i--) { int stackBaseIndex = i; + for (int n = stackBaseIndex + 1; n < beatmap.HitObjects.Count; n++) { OsuHitObject stackBaseObject = beatmap.HitObjects[stackBaseIndex]; @@ -87,6 +89,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps //Reverse pass for stack calculation. int extendedStartIndex = startIndex; + for (int i = extendedEndIndex; i > startIndex; i--) { int n = i; @@ -138,6 +141,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps if (objectN is Slider && Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance) { int offset = objectI.StackHeight - objectN.StackHeight + 1; + for (int j = n + 1; j <= i; j++) { //For each object which was declared under this slider, we will offset it to appear *below* the slider end (rather than above). diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 0dce5208dd..093081b6a1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -109,6 +109,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); double approachRateFactor = 1.0f; + if (Attributes.ApproachRate > 10.33f) approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f); else if (Attributes.ApproachRate < 8.0f) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 37276a3432..eacac7ae6a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -56,6 +56,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing { // We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps. float scalingFactor = normalized_radius / (float)BaseObject.Radius; + if (BaseObject.Radius < 30) { float smallCircleBonus = Math.Min(30 - (float)BaseObject.Radius, 5) / 50; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 46a81a9480..01f2fb8dc8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -42,9 +42,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2); double angleBonus = 1.0; + if (osuCurrent.Angle != null && osuCurrent.Angle.Value < angle_bonus_begin) { angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - osuCurrent.Angle.Value)), 2) / 3.57; + if (osuCurrent.Angle.Value < pi_over_2) { angleBonus = 1.28; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 989a53db1f..55de626d7d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -62,6 +62,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case PlacementState.Initial: HitObject.Position = e.MousePosition; return true; + case PlacementState.Body: cursor = e.MousePosition - HitObject.Position; return true; @@ -77,6 +78,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case PlacementState.Initial: beginCurve(); break; + case PlacementState.Body: switch (e.Button) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs index 2e93815ef0..3e53cd7087 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Osu.Mods { case DrawableSpinner _: continue; + default: drawable.ApplyCustomUpdateState += ApplyCustomState; break; @@ -51,6 +52,7 @@ namespace osu.Game.Rulesets.Osu.Mods case DrawableSliderTail _: // special cases we should *not* be scaling. break; + case DrawableSlider _: case DrawableHitCircle _: { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 8f9d487d49..7569626230 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -67,6 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections return; OsuHitObject prevHitObject = null; + foreach (var currHitObject in hitObjects) { if (prevHitObject != null && !currHitObject.NewCombo && !(prevHitObject is Spinner) && !(currHitObject is Spinner)) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index decd0ce073..fef0bfdc2c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -124,6 +124,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } var result = HitObject.HitWindows.ResultFor(timeOffset); + if (result == HitResult.None) { Shake(Math.Abs(timeOffset) - HitObject.HitWindows.HalfWindowFor(HitResult.Miss)); @@ -158,11 +159,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early. LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.HalfWindowFor(HitResult.Miss); break; + case ArmedState.Miss: ApproachCircle.FadeOut(50); this.FadeOut(100); Expire(); break; + case ArmedState.Hit: ApproachCircle.FadeOut(50); diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 03dbf7ac63..1a3e244fa6 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -191,6 +191,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Shader.GetUniform("g_FadeClock").UpdateValue(ref Time); int updateStart = -1, updateEnd = 0; + for (int i = 0; i < Parts.Length; ++i) { if (Parts[i].WasUpdated) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 3e0e2624bf..f8672037cd 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -120,6 +120,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps List> allSamples = curveData != null ? curveData.NodeSamples : new List>(new[] { samples }); int i = 0; + for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) { List currentSamples = allSamples[i]; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index d3837946c9..4c8d5d5204 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -98,6 +98,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables circlePiece?.FlashBox.FinishTransforms(); var offset = !AllJudged ? 0 : Time.Current - HitObject.StartTime; + using (BeginDelayedSequence(HitObject.StartTime - Time.Current + offset, true)) { switch (State.Value) @@ -108,15 +109,18 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables UnproxyContent(); this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire(); break; + case ArmedState.Miss: this.FadeOut(100) .Expire(); break; + case ArmedState.Hit: // If we're far enough away from the left stage, we should bring outselves in front of it ProxyContent(); var flash = circlePiece?.FlashBox; + if (flash != null) { flash.FadeTo(0.9f); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 8dfe89eea7..119940536e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -111,6 +111,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece.KiaiMode = HitObject.Kiai; var strongObject = HitObject.NestedHitObjects.OfType().FirstOrDefault(); + if (strongObject != null) { var strongHit = CreateStrongHit(strongObject); diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 6f3bdca6fb..1d25735fe3 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -70,6 +70,7 @@ namespace osu.Game.Rulesets.Taiko.Objects return; bool first = true; + for (double t = StartTime; t < EndTime + tickSpacing / 2; t += tickSpacing) { AddNested(new DrumRollTick diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 01ba53e07b..422ba748e3 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -52,6 +52,7 @@ namespace osu.Game.Rulesets.Taiko.Replays int count = 0; int req = swell.RequiredHits; double hitRate = Math.Min(swell_hit_speed, swell.Duration / req); + for (double j = h.StartTime; j < endTime; j += hitRate) { TaikoAction action; @@ -62,12 +63,15 @@ namespace osu.Game.Rulesets.Taiko.Replays case 0: action = TaikoAction.LeftCentre; break; + case 1: action = TaikoAction.LeftRim; break; + case 2: action = TaikoAction.RightCentre; break; + case 3: action = TaikoAction.RightRim; break; diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapCarousel.cs index 1500605896..1ffc164026 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapCarousel.cs @@ -508,6 +508,7 @@ namespace osu.Game.Tests.Visual.SongSelect }, Beatmaps = new List(), }; + for (int b = 1; b < 101; b++) { toReturn.Beatmaps.Add(new BeatmapInfo diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs index 8de6cc2a88..5bddf4222a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs @@ -261,6 +261,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.8765, }, }; + foreach (var s in anotherScores) { s.Statistics.Add(HitResult.Great, RNG.Next(2000)); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 9caa64ec96..95ec7d55c8 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -117,6 +117,7 @@ namespace osu.Game.Beatmaps if (beatmapSet.OnlineBeatmapSetID != null) { var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == beatmapSet.OnlineBeatmapSetID); + if (existingOnlineId != null) { Delete(existingOnlineId); @@ -325,6 +326,7 @@ namespace osu.Game.Beatmaps { // let's make sure there are actually .osu files to import. string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); + if (string.IsNullOrEmpty(mapName)) { Logger.Log($"No beatmap files found in the beatmap archive ({reader.Name}).", LoggingTarget.Database); diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index a2e43e5a97..0bdab22dd2 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -101,6 +101,7 @@ namespace osu.Game.Beatmaps protected override Storyboard GetStoryboard() { Storyboard storyboard; + try { using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) @@ -131,6 +132,7 @@ namespace osu.Game.Beatmaps protected override Skin GetSkin() { Skin skin; + try { skin = new LegacyBeatmapSkin(BeatmapInfo, store, audioManager); diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index a895ba3d63..953e50eadc 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -49,6 +49,7 @@ namespace osu.Game.Beatmaps.Formats throw new IOException(@"Unknown decoder type"); string line; + do { line = stream.ReadLine()?.Trim(); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index a27126ad9c..b489b5e6d9 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -70,21 +70,27 @@ namespace osu.Game.Beatmaps.Formats case Section.General: handleGeneral(strippedLine); return; + case Section.Editor: handleEditor(strippedLine); return; + case Section.Metadata: handleMetadata(line); return; + case Section.Difficulty: handleDifficulty(strippedLine); return; + case Section.Events: handleEvent(strippedLine); return; + case Section.TimingPoints: handleTimingPoint(strippedLine); return; + case Section.HitObjects: handleHitObject(strippedLine); return; @@ -98,29 +104,37 @@ namespace osu.Game.Beatmaps.Formats var pair = SplitKeyVal(line); var metadata = beatmap.BeatmapInfo.Metadata; + switch (pair.Key) { case @"AudioFilename": metadata.AudioFile = FileSafety.PathStandardise(pair.Value); break; + case @"AudioLeadIn": beatmap.BeatmapInfo.AudioLeadIn = Parsing.ParseInt(pair.Value); break; + case @"PreviewTime": metadata.PreviewTime = getOffsetTime(Parsing.ParseInt(pair.Value)); break; + case @"Countdown": beatmap.BeatmapInfo.Countdown = Parsing.ParseInt(pair.Value) == 1; break; + case @"SampleSet": defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value); break; + case @"SampleVolume": defaultSampleVolume = Parsing.ParseInt(pair.Value); break; + case @"StackLeniency": beatmap.BeatmapInfo.StackLeniency = Parsing.ParseFloat(pair.Value); break; + case @"Mode": beatmap.BeatmapInfo.RulesetID = Parsing.ParseInt(pair.Value); @@ -129,24 +143,30 @@ namespace osu.Game.Beatmaps.Formats case 0: parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; + case 1: parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; + case 2: parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; + case 3: parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; } break; + case @"LetterboxInBreaks": beatmap.BeatmapInfo.LetterboxInBreaks = Parsing.ParseInt(pair.Value) == 1; break; + case @"SpecialStyle": beatmap.BeatmapInfo.SpecialStyle = Parsing.ParseInt(pair.Value) == 1; break; + case @"WidescreenStoryboard": beatmap.BeatmapInfo.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1; break; @@ -162,15 +182,19 @@ namespace osu.Game.Beatmaps.Formats case @"Bookmarks": beatmap.BeatmapInfo.StoredBookmarks = pair.Value; break; + case @"DistanceSpacing": beatmap.BeatmapInfo.DistanceSpacing = Math.Max(0, Parsing.ParseDouble(pair.Value)); break; + case @"BeatDivisor": beatmap.BeatmapInfo.BeatDivisor = Parsing.ParseInt(pair.Value); break; + case @"GridSize": beatmap.BeatmapInfo.GridSize = Parsing.ParseInt(pair.Value); break; + case @"TimelineZoom": beatmap.BeatmapInfo.TimelineZoom = Math.Max(0, Parsing.ParseDouble(pair.Value)); break; @@ -182,35 +206,45 @@ namespace osu.Game.Beatmaps.Formats var pair = SplitKeyVal(line); var metadata = beatmap.BeatmapInfo.Metadata; + switch (pair.Key) { case @"Title": metadata.Title = pair.Value; break; + case @"TitleUnicode": metadata.TitleUnicode = pair.Value; break; + case @"Artist": metadata.Artist = pair.Value; break; + case @"ArtistUnicode": metadata.ArtistUnicode = pair.Value; break; + case @"Creator": metadata.AuthorString = pair.Value; break; + case @"Version": beatmap.BeatmapInfo.Version = pair.Value; break; + case @"Source": beatmap.BeatmapInfo.Metadata.Source = pair.Value; break; + case @"Tags": beatmap.BeatmapInfo.Metadata.Tags = pair.Value; break; + case @"BeatmapID": beatmap.BeatmapInfo.OnlineBeatmapID = Parsing.ParseInt(pair.Value); break; + case @"BeatmapSetID": beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = Parsing.ParseInt(pair.Value) }; break; @@ -222,23 +256,29 @@ namespace osu.Game.Beatmaps.Formats var pair = SplitKeyVal(line); var difficulty = beatmap.BeatmapInfo.BaseDifficulty; + switch (pair.Key) { case @"HPDrainRate": difficulty.DrainRate = Parsing.ParseFloat(pair.Value); break; + case @"CircleSize": difficulty.CircleSize = Parsing.ParseFloat(pair.Value); break; + case @"OverallDifficulty": difficulty.OverallDifficulty = Parsing.ParseFloat(pair.Value); break; + case @"ApproachRate": difficulty.ApproachRate = Parsing.ParseFloat(pair.Value); break; + case @"SliderMultiplier": difficulty.SliderMultiplier = Parsing.ParseDouble(pair.Value); break; + case @"SliderTickRate": difficulty.SliderTickRate = Parsing.ParseDouble(pair.Value); break; @@ -259,6 +299,7 @@ namespace osu.Game.Beatmaps.Formats string filename = split[2].Trim('"'); beatmap.BeatmapInfo.Metadata.BackgroundFile = FileSafety.PathStandardise(filename); break; + case EventType.Break: double start = getOffsetTime(Parsing.ParseDouble(split[1])); @@ -308,6 +349,7 @@ namespace osu.Game.Beatmaps.Formats bool kiaiMode = false; bool omitFirstBarSignature = false; + if (split.Length >= 8) { EffectFlags effectFlags = (EffectFlags)Parsing.ParseInt(split[7]); diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 040f582e3b..cd1d8a6218 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -26,6 +26,7 @@ namespace osu.Game.Beatmaps.Formats Section section = Section.None; string line; + while ((line = stream.ReadLine()) != null) { if (ShouldSkipLine(line)) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 0f83edf034..f6e2bf6966 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -54,6 +54,7 @@ namespace osu.Game.Beatmaps.Formats case Section.Events: handleEvents(line); return; + case Section.Variables: handleVariables(line); return; @@ -65,6 +66,7 @@ namespace osu.Game.Beatmaps.Formats private void handleEvents(string line) { var depth = 0; + while (line.StartsWith(" ", StringComparison.Ordinal) || line.StartsWith("_", StringComparison.Ordinal)) { ++depth; @@ -94,8 +96,9 @@ namespace osu.Game.Beatmaps.Formats var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y)); storyboard.GetLayer(layer).Add(storyboardSprite); - } break; + } + case EventType.Animation: { var layer = parseLayer(split[1]); @@ -108,8 +111,9 @@ namespace osu.Game.Beatmaps.Formats var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever; storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType); storyboard.GetLayer(layer).Add(storyboardSprite); - } break; + } + case EventType.Sample: { var time = double.Parse(split[1], CultureInfo.InvariantCulture); @@ -117,8 +121,8 @@ namespace osu.Game.Beatmaps.Formats var path = cleanFilename(split[3]); var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100; storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume)); - } break; + } } } else @@ -127,6 +131,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup = storyboardSprite?.TimelineGroup; var commandType = split[0]; + switch (commandType) { case "T": @@ -138,6 +143,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber); } break; + case "L": { var startTime = double.Parse(split[1], CultureInfo.InvariantCulture); @@ -145,6 +151,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount); } break; + default: { if (string.IsNullOrEmpty(split[3])) @@ -163,6 +170,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue); } break; + case "S": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -170,6 +178,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue)); } break; + case "V": { var startX = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -179,6 +188,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY)); } break; + case "R": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -186,6 +196,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue)); } break; + case "M": { var startX = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -196,6 +207,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY); } break; + case "MX": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -203,6 +215,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue); } break; + case "MY": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -210,6 +223,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue); } break; + case "C": { var startRed = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -223,23 +237,28 @@ namespace osu.Game.Beatmaps.Formats new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1)); } break; + case "P": { var type = split[4]; + switch (type) { case "A": timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit); break; + case "H": timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime); break; + case "V": timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime); break; } } break; + default: throw new InvalidDataException($@"Unknown command type: {commandType}"); } @@ -254,26 +273,36 @@ namespace osu.Game.Beatmaps.Formats private Anchor parseOrigin(string value) { var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value); + switch (origin) { case LegacyOrigins.TopLeft: return Anchor.TopLeft; + case LegacyOrigins.TopCentre: return Anchor.TopCentre; + case LegacyOrigins.TopRight: return Anchor.TopRight; + case LegacyOrigins.CentreLeft: return Anchor.CentreLeft; + case LegacyOrigins.Centre: return Anchor.Centre; + case LegacyOrigins.CentreRight: return Anchor.CentreRight; + case LegacyOrigins.BottomLeft: return Anchor.BottomLeft; + case LegacyOrigins.BottomCentre: return Anchor.BottomCentre; + case LegacyOrigins.BottomRight: return Anchor.BottomRight; + default: return Anchor.TopLeft; } diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs index f547a7d3e1..8f1780cab5 100644 --- a/osu.Game/Configuration/DatabasedConfigManager.cs +++ b/osu.Game/Configuration/DatabasedConfigManager.cs @@ -44,6 +44,7 @@ namespace osu.Game.Configuration base.AddBindable(lookup, bindable); var setting = databasedSettings.Find(s => (int)s.Key == (int)(object)lookup); + if (setting != null) { bindable.Parse(setting.Value); diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 3805921ac2..1eb199327e 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -84,6 +84,7 @@ namespace osu.Game.Database private void flushEvents(bool perform) { Action[] events; + lock (queuedEvents) { events = queuedEvents.ToArray(); @@ -147,6 +148,7 @@ namespace osu.Game.Database List imported = new List(); int current = 0; + foreach (string path in paths) { if (notification.State == ProgressNotificationState.Cancelled) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 17efe2c839..38f2a53586 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -60,6 +60,7 @@ namespace osu.Game.Database this.connectionString = connectionString; var connection = Database.GetDbConnection(); + try { connection.Open(); @@ -170,9 +171,11 @@ namespace osu.Game.Database default: frameworkLogLevel = Framework.Logging.LogLevel.Debug; break; + case LogLevel.Warning: frameworkLogLevel = Framework.Logging.LogLevel.Important; break; + case LogLevel.Error: case LogLevel.Critical: frameworkLogLevel = Framework.Logging.LogLevel.Error; diff --git a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs index c1811f37d5..3bb9e1f091 100644 --- a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs +++ b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs @@ -34,6 +34,7 @@ namespace osu.Game.Graphics.Containers protected override void Update() { base.Update(); + if (InternalChildren.Count > 0 && InternalChild.DrawSize.X > 0) { // We're modifying scale here for a few reasons diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 204c83aac9..23e2a66107 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -51,6 +51,7 @@ namespace osu.Game.Graphics.Containers } int previousLinkEnd = 0; + foreach (var link in links) { AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd)); @@ -90,10 +91,12 @@ namespace osu.Game.Graphics.Containers if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId)) game?.ShowBeatmap(beatmapId); break; + case LinkAction.OpenBeatmapSet: if (int.TryParse(linkArgument, out int setId)) game?.ShowBeatmapSet(setId); break; + case LinkAction.OpenChannel: try { @@ -105,18 +108,22 @@ namespace osu.Game.Graphics.Containers } break; + case LinkAction.OpenEditorTimestamp: case LinkAction.JoinMultiplayerMatch: case LinkAction.Spectate: showNotImplementedError?.Invoke(); break; + case LinkAction.External: game?.OpenUrlExternally(url); break; + case LinkAction.OpenUserProfile: if (long.TryParse(linkArgument, out long userId)) game?.ShowUser(userId); break; + default: throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); } diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index 6bbab4766d..1f2ee53fe9 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -150,6 +150,7 @@ namespace osu.Game.Graphics.Containers float headerH = (ExpandableHeader?.LayoutSize.Y ?? 0) + (FixedHeader?.LayoutSize.Y ?? 0); float footerH = Footer?.LayoutSize.Y ?? 0; + if (headerH != headerHeight || footerH != footerHeight) { headerHeight = headerH; @@ -181,6 +182,7 @@ namespace osu.Game.Graphics.Containers foreach (var section in Children) { float diff = Math.Abs(scrollContainer.GetChildPosInContent(section) - currentScroll - scrollOffset); + if (diff < minDiff) { minDiff = diff; diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 059beeca4d..092a23e787 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -45,10 +45,12 @@ namespace osu.Game.Graphics.Cursor { var position = e.MousePosition; var distance = Vector2Extensions.Distance(position, positionMouseDown); + // don't start rotating until we're moved a minimum distance away from the mouse down location, // else it can have an annoying effect. if (dragRotationState == DragRotationState.DragStarted && distance > 30) dragRotationState = DragRotationState.Rotating; + // don't rotate when distance is zero to avoid NaN if (dragRotationState == DragRotationState.Rotating && distance > 0) { diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index 4e0ce4a3e1..fa79331274 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -37,6 +37,7 @@ namespace osu.Game.Graphics.Cursor if (value == text.Text) return; text.Text = value; + if (IsPresent) { AutoSizeDuration = 250; diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index 3ae1033f5d..125c994c92 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -54,9 +54,11 @@ namespace osu.Game.Graphics var diffToNow = DateTimeOffset.Now.Subtract(Date); double timeUntilNextUpdate = 1000; + if (Math.Abs(diffToNow.TotalSeconds) > 120) { timeUntilNextUpdate *= 60; + if (Math.Abs(diffToNow.TotalMinutes) > 120) { timeUntilNextUpdate *= 60; diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 58058c9d4c..953f3985f9 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -25,6 +25,7 @@ namespace osu.Game.Graphics.UserInterface { direction = value; base.Direction = direction.HasFlag(BarDirection.Horizontal) ? FillDirection.Vertical : FillDirection.Horizontal; + foreach (var bar in Children) { bar.Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1); @@ -41,6 +42,7 @@ namespace osu.Game.Graphics.UserInterface set { List bars = Children.ToList(); + foreach (var bar in value.Select((length, index) => new { Value = length, Bar = bars.Count > index ? bars[index] : null })) { float length = MaxValue ?? value.Max(); diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index 74025b71ff..3882e7c1e5 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -86,6 +86,7 @@ namespace osu.Game.Graphics.UserInterface protected override void Update() { base.Update(); + if (!pathCached.IsValid) { applyPath(); diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index c558fd7c7b..c3c447ef83 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -203,6 +203,7 @@ namespace osu.Game.Graphics.UserInterface private int findPrecision(decimal d) { int precision = 0; + while (d != Math.Round(d)) { d *= 10; diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index ac6e393435..f7f282c1aa 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -100,6 +100,7 @@ namespace osu.Game.Graphics.UserInterface public void StopAnimation() { int i = 0; + foreach (var star in stars.Children) { star.ClearTransforms(true); @@ -120,6 +121,7 @@ namespace osu.Game.Graphics.UserInterface private void transformCount(float newValue) { int i = 0; + foreach (var star in stars.Children) { star.ClearTransforms(true); diff --git a/osu.Game/IO/Legacy/SerializationReader.cs b/osu.Game/IO/Legacy/SerializationReader.cs index 95ee5aea6b..7a84c11930 100644 --- a/osu.Game/IO/Legacy/SerializationReader.cs +++ b/osu.Game/IO/Legacy/SerializationReader.cs @@ -85,6 +85,7 @@ namespace osu.Game.IO.Legacy for (int i = 0; i < count; i++) { T obj = new T(); + try { obj.ReadFromStream(sr); @@ -129,44 +130,63 @@ namespace osu.Game.IO.Legacy public object ReadObject() { ObjType t = (ObjType)ReadByte(); + switch (t) { case ObjType.boolType: return ReadBoolean(); + case ObjType.byteType: return ReadByte(); + case ObjType.uint16Type: return ReadUInt16(); + case ObjType.uint32Type: return ReadUInt32(); + case ObjType.uint64Type: return ReadUInt64(); + case ObjType.sbyteType: return ReadSByte(); + case ObjType.int16Type: return ReadInt16(); + case ObjType.int32Type: return ReadInt32(); + case ObjType.int64Type: return ReadInt64(); + case ObjType.charType: return ReadChar(); + case ObjType.stringType: return base.ReadString(); + case ObjType.singleType: return ReadSingle(); + case ObjType.doubleType: return ReadDouble(); + case ObjType.decimalType: return ReadDecimal(); + case ObjType.dateTimeType: return ReadDateTime(); + case ObjType.byteArrayType: return ReadByteArray(); + case ObjType.charArrayType: return ReadCharArray(); + case ObjType.otherType: return DynamicDeserializer.Deserialize(BaseStream); + default: return null; } @@ -241,6 +261,7 @@ namespace osu.Game.IO.Legacy string toAssemblyName = assemblyName.Split(',')[0]; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (Assembly a in assemblies) { if (a.FullName.Split(',')[0] == toAssemblyName) diff --git a/osu.Game/IO/Legacy/SerializationWriter.cs b/osu.Game/IO/Legacy/SerializationWriter.cs index 695767c822..f30e4492af 100644 --- a/osu.Game/IO/Legacy/SerializationWriter.cs +++ b/osu.Game/IO/Legacy/SerializationWriter.cs @@ -111,6 +111,7 @@ namespace osu.Game.IO.Legacy else { Write(d.Count); + foreach (KeyValuePair kvp in d) { WriteObject(kvp.Key); diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index 13be4be0c6..6d244bff60 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -65,6 +65,7 @@ namespace osu.Game.IO.Serialization.Converters var lookupTable = new List(); var objects = new List(); + foreach (var item in list) { var type = item.GetType(); @@ -75,6 +76,7 @@ namespace osu.Game.IO.Serialization.Converters typeString += $", {assemblyName.Version}"; int typeId = lookupTable.IndexOf(typeString); + if (typeId == -1) { lookupTable.Add(typeString); diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index c5f6ef41c2..6d855765b1 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -113,6 +113,7 @@ namespace osu.Game.Online.API } break; + case APIState.Offline: case APIState.Connecting: //work to restore a connection... @@ -300,6 +301,7 @@ namespace osu.Game.Online.API case HttpStatusCode.Unauthorized: Logout(); return true; + case HttpStatusCode.RequestTimeout: failureCount++; log.Add($@"API failure count is now {failureCount}"); diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index 8ee71ce9ac..b2bb48b3de 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -72,26 +72,33 @@ namespace osu.Game.Online.API.Requests.Responses foreach (var kvp in value) { HitResult newKey; + switch (kvp.Key) { case @"count_geki": CountGeki = kvp.Value; break; + case @"count_300": Count300 = kvp.Value; break; + case @"count_katu": CountKatu = kvp.Value; break; + case @"count_100": Count100 = kvp.Value; break; + case @"count_50": Count50 = kvp.Value; break; + case @"count_miss": CountMiss = kvp.Value; break; + default: continue; } diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index d35dc07368..e1fc65da6c 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -51,6 +51,7 @@ namespace osu.Game.Online.Chat private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null) { int captureOffset = 0; + foreach (Match m in regex.Matches(result.Text, startIndex)) { var index = m.Index - captureOffset; @@ -114,51 +115,63 @@ namespace osu.Game.Online.Chat case "b": case "beatmaps": return new LinkDetails(LinkAction.OpenBeatmap, args[3]); + case "s": case "beatmapsets": case "d": return new LinkDetails(LinkAction.OpenBeatmapSet, args[3]); + case "u": return new LinkDetails(LinkAction.OpenUserProfile, args[3]); } } return new LinkDetails(LinkAction.External, null); + case "osu": // every internal link also needs some kind of argument if (args.Length < 3) return new LinkDetails(LinkAction.External, null); LinkAction linkType; + switch (args[1]) { case "chan": linkType = LinkAction.OpenChannel; break; + case "edit": linkType = LinkAction.OpenEditorTimestamp; break; + case "b": linkType = LinkAction.OpenBeatmap; break; + case "s": case "dl": linkType = LinkAction.OpenBeatmapSet; break; + case "spectate": linkType = LinkAction.Spectate; break; + case "u": linkType = LinkAction.OpenUserProfile; break; + default: linkType = LinkAction.External; break; } return new LinkDetails(linkType, args[2]); + case "osump": return new LinkDetails(LinkAction.JoinMultiplayerMatch, args[1]); + default: return new LinkDetails(LinkAction.External, null); } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index ac1666f8ed..3ce71cccba 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -74,6 +74,7 @@ namespace osu.Game.Online.Leaderboards scrollContainer.Add(scrollFlow); int i = 0; + foreach (var s in scrollFlow.Children) { using (s.BeginDelayedSequence(i++ * 50, true)) @@ -138,18 +139,23 @@ namespace osu.Game.Online.Leaderboards OnRetry = UpdateScores, }); break; + case PlaceholderState.Unavailable: replacePlaceholder(new MessagePlaceholder(@"Leaderboards are not available for this beatmap!")); break; + case PlaceholderState.NoScores: replacePlaceholder(new MessagePlaceholder(@"No records yet!")); break; + case PlaceholderState.NotLoggedIn: replacePlaceholder(new MessagePlaceholder(@"Please sign in to view online leaderboards!")); break; + case PlaceholderState.NotSupporter: replacePlaceholder(new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!")); break; + default: replacePlaceholder(null); break; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e470d554c9..5ac5842b22 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -271,6 +271,7 @@ namespace osu.Game { var databasedScore = ScoreManager.GetScore(score); var databasedScoreInfo = databasedScore.ScoreInfo; + if (databasedScore.Replay == null) { Logger.Log("The loaded score has no replay data.", LoggingTarget.Information); @@ -278,6 +279,7 @@ namespace osu.Game } var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.ID == databasedScoreInfo.Beatmap.ID); + if (databasedBeatmap == null) { Logger.Log("Tried to load a score for a beatmap we don't have!", LoggingTarget.Information); @@ -661,9 +663,11 @@ namespace osu.Game case GlobalAction.ToggleChat: chatOverlay.ToggleVisibility(); return true; + case GlobalAction.ToggleSocial: social.ToggleVisibility(); return true; + case GlobalAction.ResetInputSettings: var sensitivity = frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity); @@ -674,15 +678,19 @@ namespace osu.Game frameworkConfig.Set(FrameworkSetting.IgnoredInputHandlers, string.Empty); frameworkConfig.GetBindable(FrameworkSetting.ConfineMouseMode).SetDefault(); return true; + case GlobalAction.ToggleToolbar: Toolbar.ToggleVisibility(); return true; + case GlobalAction.ToggleSettings: settings.ToggleVisibility(); return true; + case GlobalAction.ToggleDirect: direct.ToggleVisibility(); return true; + case GlobalAction.ToggleGameplayMouseButtons: LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get(OsuSetting.MouseDisableButtons)); return true; @@ -758,6 +766,7 @@ namespace osu.Game case Intro intro: introScreen = intro; break; + case MainMenu menu: menuScreen = menu; break; diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index 13d8df098f..e136fc1403 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -209,6 +209,7 @@ namespace osu.Game.Overlays.AccountCreation private bool focusNextTextbox() { var nextTextbox = nextUnfilledTextbox(); + if (nextTextbox != null) { Schedule(() => GetContainingInputManager().ChangeFocus(nextTextbox)); diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index e3df81e455..aec78b962f 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -99,6 +99,7 @@ namespace osu.Game.Overlays.Chat private void pendingMessageResolved(Message existing, Message updated) { var found = ChatLineFlow.Children.LastOrDefault(c => c.Message == existing); + if (found != null) { Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 77f88ab4e7..221fd35576 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -205,6 +205,7 @@ namespace osu.Game.Overlays Scheduler.Add(() => channelTabControl.Current.Value = e.NewValue); var loaded = loadedChannels.Find(d => d.Channel == e.NewValue); + if (loaded == null) { currentChannelContainer.FadeOut(500, Easing.OutQuint); @@ -288,6 +289,7 @@ namespace osu.Game.Overlays case Key.Number9: selectTab((int)e.Key - (int)Key.Number1); return true; + case Key.Number0: selectTab(9); return true; diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index ede2f34574..1ab5d76555 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -72,6 +72,7 @@ namespace osu.Game.Overlays.Dialog set { buttonsContainer.ChildrenEnumerable = value; + foreach (PopupDialogButton b in value) { var action = b.Action; @@ -222,6 +223,7 @@ namespace osu.Game.Overlays.Dialog // press button at number if 1-9 on number row or keypad are pressed var k = e.Key; + if (k >= Key.Number1 && k <= Key.Number9) { pressButtonAtIndex(k - Key.Number1); diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 34edbbcc8b..40c4c90fca 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -58,6 +58,7 @@ namespace osu.Game.Overlays var artists = new List(); var songs = new List(); var tags = new List(); + foreach (var s in beatmapSets) { artists.Add(s.Metadata.Artist); @@ -210,6 +211,7 @@ namespace osu.Game.Overlays Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }; + default: return new DirectListPanel(b); } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 8313dac50a..ee7a65042b 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -117,6 +117,7 @@ namespace osu.Game.Overlays.KeyBinding public void RestoreDefaults() { int i = 0; + foreach (var d in Defaults) { var button = buttons[i++]; diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 23b75caedc..fa1ee500a8 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -81,6 +81,7 @@ namespace osu.Game.Overlays.Mods backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing); backgroundIcon.Icon = modAfter.Icon; + using (BeginDelayedSequence(mod_switch_duration, true)) { foregroundIcon @@ -139,6 +140,7 @@ namespace osu.Game.Overlays.Mods } createIcons(); + if (Mods.Length > 0) { displayMod(Mods[0]); @@ -168,6 +170,7 @@ namespace osu.Game.Overlays.Mods case MouseButton.Left: SelectNext(1); break; + case MouseButton.Right: SelectNext(-1); break; @@ -219,6 +222,7 @@ namespace osu.Game.Overlays.Mods private void createIcons() { iconsContainer.Clear(); + if (Mods.Length > 1) { iconsContainer.AddRange(new[] diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index a118357f21..50400e254f 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -77,6 +77,7 @@ namespace osu.Game.Overlays.Mods public void DeselectTypes(IEnumerable modTypes, bool immediate = false) { int delay = 0; + foreach (var button in buttons) { Mod selected = button.SelectedMod; diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 310c6c919f..89d166b788 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -190,6 +190,7 @@ namespace osu.Game.Overlays.Music // the item positions as they are being transformed float heightAccumulator = 0; int dstIndex = 0; + for (; dstIndex < items.Count; dstIndex++) { // Using BoundingBox here takes care of scale, paddings, etc... diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 8cbea63fe3..c66d0694ae 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -81,6 +81,7 @@ namespace osu.Game.Overlays.Music filter.Search.OnCommit = (sender, newText) => { BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); + if (toSelect != null) { beatmap.Value = beatmaps.GetWorkingBeatmap(toSelect); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index ce2137346f..75073a14f1 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -296,6 +296,7 @@ namespace osu.Game.Overlays queuedDirection = TransformDirection.Prev; var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault(); + if (playable != null) { beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); @@ -309,6 +310,7 @@ namespace osu.Game.Overlays queuedDirection = TransformDirection.Next; var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault(); + if (playable != null) { beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); @@ -399,6 +401,7 @@ namespace osu.Game.Overlays newBackground.MoveToX(0, 500, Easing.OutCubic); background.MoveToX(-400, 500, Easing.OutCubic); break; + case TransformDirection.Prev: newBackground.Position = new Vector2(-400, 0); newBackground.MoveToX(0, 500, Easing.OutCubic); diff --git a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs index ff4d7a10dc..769eff53c2 100644 --- a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs @@ -106,6 +106,7 @@ namespace osu.Game.Overlays.Profile.Header visibleBadge = 0; badgeFlowContainer.Clear(); + for (var index = 0; index < badges.Length; index++) { int displayIndex = index; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 28877c21f0..b8a07dfad2 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -408,6 +408,7 @@ namespace osu.Game.Overlays.Profile infoTextLeft.AddLink("forum post".ToQuantity(user.PostCount), url: $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: boldItalic); string websiteWithoutProtcol = user.Website; + if (!string.IsNullOrEmpty(websiteWithoutProtcol)) { int protocolIndex = websiteWithoutProtcol.IndexOf("//", StringComparison.Ordinal); @@ -468,14 +469,11 @@ namespace osu.Game.Overlays.Profile if (string.IsNullOrEmpty(str)) return; infoTextRight.AddIcon(icon); + if (url != null) - { infoTextRight.AddLink(" " + str, url); - } else - { infoTextRight.AddText(" " + str); - } infoTextRight.NewLine(); } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 02e9d48f40..ec35040a42 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -64,6 +64,7 @@ namespace osu.Game.Overlays.Settings { bindable = value; controlWithCurrent?.Current.BindTo(bindable); + if (ShowsDefaultIndicator) { restoreDefaultButton.Bindable = bindable.GetBoundCopy(); diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index c7736d6047..a94f76e7af 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -46,6 +46,7 @@ namespace osu.Game.Overlays.Settings set { selected = value; + if (selected) { selectionIndicator.FadeIn(50); diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index daf3d1c576..e6d0c2fe40 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -111,6 +111,7 @@ namespace osu.Game.Overlays ChildrenEnumerable = Users.Select(u => { SocialPanel panel; + switch (displayStyle) { case PanelDisplayStyle.Grid: @@ -120,6 +121,7 @@ namespace osu.Game.Overlays Origin = Anchor.TopCentre }; break; + default: panel = new SocialListPanel(u); break; @@ -167,6 +169,7 @@ namespace osu.Game.Overlays friendRequest.Success += updateUsers; api.Queue(getUsersRequest = friendRequest); break; + default: var userRequest = new GetUsersRequest(); // TODO filter arguments! userRequest.Success += response => updateUsers(response.Select(r => r.User)); @@ -200,6 +203,7 @@ namespace osu.Game.Overlays case APIState.Online: Scheduler.AddOnce(updateSearch); break; + default: Users = null; clearPanels(); diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index ca86ce7aa7..b2ae273e31 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -21,6 +21,7 @@ namespace osu.Game.Overlays.Toolbar set { stateContainer = value; + if (stateContainer != null) { Action = stateContainer.ToggleVisibility; @@ -55,6 +56,7 @@ namespace osu.Game.Overlays.Toolbar case Visibility.Hidden: stateBackground.FadeOut(200); break; + case Visibility.Visible: stateBackground.FadeIn(200); break; diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index d01eab4dab..bbe1b34a48 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -71,6 +71,7 @@ namespace osu.Game.Overlays.Toolbar private void load(RulesetStore rulesets, Bindable parentRuleset) { this.rulesets = rulesets; + foreach (var r in rulesets.AvailableRulesets) { modeButtons.Add(new ToolbarRulesetButton diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 48ce055975..f856592f2e 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -176,6 +176,7 @@ namespace osu.Game.Overlays foreach (string id in user.ProfileOrder) { var sec = sections.FirstOrDefault(s => s.Identifier == id); + if (sec != null) { sec.User.Value = user; diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 85b6c91a07..8f892f2be1 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -71,12 +71,14 @@ namespace osu.Game.Rulesets.Edit if (state == value) return; state = value; + switch (state) { case SelectionState.Selected: Show(); Selected?.Invoke(this); break; + case SelectionState.NotSelected: Hide(); Deselected?.Invoke(this); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index a7cfbd3300..edbf9079af 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -97,6 +97,7 @@ namespace osu.Game.Rulesets.Objects.Drawables private void load() { var judgement = HitObject.CreateJudgement(); + if (judgement != null) { Result = CreateResult(judgement); @@ -211,9 +212,11 @@ namespace osu.Game.Rulesets.Objects.Drawables { case HitResult.None: break; + case HitResult.Miss: State.Value = ArmedState.Miss; break; + default: State.Value = ArmedState.Hit; break; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 8d6bb8bd3f..c14f3b6a42 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -84,6 +84,7 @@ namespace osu.Game.Rulesets.Objects.Legacy var points = new Vector2[pointCount]; int pointIndex = 1; + foreach (string t in pointSplit) { if (t.Length == 1) @@ -93,12 +94,15 @@ namespace osu.Game.Rulesets.Objects.Legacy case @"C": pathType = PathType.Catmull; break; + case @"B": pathType = PathType.Bezier; break; + case @"L": pathType = PathType.Linear; break; + case @"P": pathType = PathType.PerfectCurve; break; @@ -143,6 +147,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (split.Length > 9 && split[9].Length > 0) { string[] sets = split[9].Split('|'); + for (int i = 0; i < nodes; i++) { if (i >= sets.Length) @@ -162,6 +167,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (split.Length > 8 && split[8].Length > 0) { string[] adds = split[8].Split('|'); + for (int i = 0; i < nodes; i++) { if (i >= adds.Length) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 1e9767a54f..5515d4a41a 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -95,6 +95,7 @@ namespace osu.Game.Rulesets.Objects path.Clear(); int i = 0; + for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i) { } @@ -142,6 +143,7 @@ namespace osu.Game.Rulesets.Objects { case PathType.Linear: return PathApproximator.ApproximateLinear(subControlPoints); + case PathType.PerfectCurve: //we can only use CircularArc iff we have exactly three control points and no dissection. if (ControlPoints.Length != 3 || subControlPoints.Length != 3) @@ -155,6 +157,7 @@ namespace osu.Game.Rulesets.Objects break; return subpath; + case PathType.Catmull: return PathApproximator.ApproximateCatmull(subControlPoints); } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index bbb587cb3f..0e74caf8ba 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -124,6 +124,7 @@ namespace osu.Game.Rulesets.UI onScreenDisplay = dependencies.Get(); Config = dependencies.Get().GetConfigFor(Ruleset); + if (Config != null) { dependencies.Cache(Config); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 6df418753c..b6c2d016d2 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -76,6 +76,7 @@ namespace osu.Game.Screens.Backgrounds private void switchBackground(BeatmapBackground b) { float newDepth = 0; + if (Background != null) { newDepth = Background.Depth + 1; diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 9ccf974244..5699ef0a84 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -100,6 +100,7 @@ namespace osu.Game.Screens.Edit.Compose }; var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); + if (ruleset == null) { Logger.Log("Beatmap doesn't have a ruleset assigned."); @@ -108,6 +109,7 @@ namespace osu.Game.Screens.Edit.Compose } composer = ruleset.CreateHitObjectComposer(); + if (composer == null) { Logger.Log($"Ruleset {ruleset.Description} doesn't support hitobject composition."); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0ba1e74aca..3ae26c4ea4 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -70,6 +70,7 @@ namespace osu.Game.Screens.Edit PlaybackControl playback; var fileMenuItems = new List(); + if (RuntimeInfo.IsDesktop) { fileMenuItems.Add(new EditorMenuItem("Export", MenuItemType.Standard, exportBeatmap)); @@ -173,6 +174,7 @@ namespace osu.Game.Screens.Edit case Key.Left: seek(e, -1); return true; + case Key.Right: seek(e, 1); return true; @@ -218,6 +220,7 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { Background.FadeColour(Color4.White, 500); + if (Beatmap.Value.Track != null) { Beatmap.Value.Track.Tempo.Value = 1; @@ -238,9 +241,11 @@ namespace osu.Game.Screens.Edit case EditorScreenMode.Compose: currentScreen = new ComposeScreen(); break; + case EditorScreenMode.Design: currentScreen = new DesignScreen(); break; + default: currentScreen = new EditorScreen(); break; diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 8f65366650..24fb561f04 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -83,6 +83,7 @@ namespace osu.Game.Screens.Edit if (amount <= 0) throw new ArgumentException("Value should be greater than zero", nameof(amount)); var timingPoint = ControlPointInfo.TimingPointAt(CurrentTime); + if (direction < 0 && timingPoint.Time == CurrentTime) { // When going backwards and we're at the boundary of two timing points, we compute the seek distance with the timing point which we are seeking into diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index bcd24fd83e..61b93c9486 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -156,9 +156,11 @@ namespace osu.Game.Screens.Menu { case GlobalAction.Back: return goBack(); + case GlobalAction.Select: logo?.Click(); return true; + default: return false; } @@ -174,9 +176,11 @@ namespace osu.Game.Screens.Menu State = ButtonSystemState.Initial; sampleBack?.Play(); return true; + case ButtonSystemState.Play: backButton.Click(); return true; + default: return false; } @@ -188,12 +192,15 @@ namespace osu.Game.Screens.Menu { default: return true; + case ButtonSystemState.Initial: State = ButtonSystemState.TopLevel; return true; + case ButtonSystemState.TopLevel: buttonsTopLevel.First().Click(); return false; + case ButtonSystemState.Play: buttonsPlay.First().Click(); return false; @@ -259,12 +266,14 @@ namespace osu.Game.Screens.Menu logo.ScaleTo(1, 800, Easing.OutExpo); }, buttonArea.Alpha * 150); break; + case ButtonSystemState.TopLevel: case ButtonSystemState.Play: switch (lastState) { case ButtonSystemState.TopLevel: // coming from toplevel to play break; + case ButtonSystemState.Initial: logo.ClearTransforms(targetMember: nameof(Position)); logo.RelativePositionAxes = Axes.None; @@ -287,6 +296,7 @@ namespace osu.Game.Screens.Menu game?.Toolbar.Show(); }, 200); break; + default: logo.ClearTransforms(targetMember: nameof(Position)); logo.RelativePositionAxes = Axes.None; @@ -296,6 +306,7 @@ namespace osu.Game.Screens.Menu } break; + case ButtonSystemState.EnteringMode: logoTracking = true; break; diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index a41a12927b..a6ca483c12 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -118,6 +118,7 @@ namespace osu.Game.Screens.Menu base.Update(); float decayFactor = (float)Time.Elapsed * decay_per_milisecond; + for (int i = 0; i < bars_per_visualiser; i++) { //3% of extra bar length to make it a little faster when bar is almost at it's minimum diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs index 5798fce457..485de87d31 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs @@ -258,6 +258,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components // nice little progressive fade int time = 500; + foreach (var c in fill.Children) { c.Delay(500 - time).FadeOut(time, Easing.Out); diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index a71106872e..eac9871a57 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -149,6 +149,7 @@ namespace osu.Game.Screens.Multi.Match header.Tabs.Current.BindValueChanged(tab => { const float fade_duration = 500; + if (tab.NewValue is SettingsMatchPage) { settings.Show(); diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 5e019a7b3a..a726523ee5 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -248,6 +248,7 @@ namespace osu.Game.Screens.Multi if (screenStack.CurrentScreen is MatchSubScreen) { var track = Beatmap.Value.Track; + if (track != null) { track.Looping = true; diff --git a/osu.Game/Screens/OsuScreenDependencies.cs b/osu.Game/Screens/OsuScreenDependencies.cs index 84e5de76de..8c759ec6f8 100644 --- a/osu.Game/Screens/OsuScreenDependencies.cs +++ b/osu.Game/Screens/OsuScreenDependencies.cs @@ -20,12 +20,14 @@ namespace osu.Game.Screens if (requireLease) { Beatmap = parent.Get>()?.GetBoundCopy(); + if (Beatmap == null) { Cache(Beatmap = parent.Get>().BeginLease(false)); } Ruleset = parent.Get>()?.GetBoundCopy(); + if (Ruleset == null) { Cache(Ruleset = parent.Get>().BeginLease(true)); diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 2c1293833f..2df5ce101c 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -68,6 +68,7 @@ namespace osu.Game.Screens.Play.HUD Current.ValueChanged += mods => { iconsContainer.Clear(); + foreach (Mod mod in mods.NewValue) { iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) }); diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index 0626c40334..88a62ac8d4 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -52,6 +52,7 @@ namespace osu.Game.Screens.Play { isLit = value; updateGlowSprite(value); + if (value && IsCounting) { CountPresses++; diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index d10034d552..5b7a9574b6 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -169,6 +169,7 @@ namespace osu.Game.Screens.Play var max = values.Max(); float step = values.Length / (float)ColumnCount; + for (float i = 0; i < values.Length; i += step) { newValues.Add((float)values[(int)i] / max); diff --git a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs index 043bf55d2b..fab227c7f4 100644 --- a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs +++ b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs @@ -180,6 +180,7 @@ namespace osu.Game.Screens.Ranking.Pages scoreCounter.Increment(Score.TotalScore); int delay = 0; + foreach (var s in statisticsContainer.Children) { s.FadeOut() @@ -336,6 +337,7 @@ namespace osu.Game.Screens.Ranking.Pages versionMapper.Colour = colours.Gray8; var creator = beatmap.Metadata.Author?.Username; + if (!string.IsNullOrEmpty(creator)) { versionMapper.Text = $"mapped by {creator}"; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index d7240a40ad..63ad3b6ab2 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -257,6 +257,7 @@ namespace osu.Game.Screens.Select select(beatmap); return; + case CarouselBeatmapSet set: if (skipDifficulties) select(set); @@ -292,6 +293,7 @@ namespace osu.Game.Screens.Select if (RandomAlgorithm.Value == RandomSelectAlgorithm.RandomPermutation) { var notYetVisitedSets = visibleSets.Except(previouslyVisitedRandomSets).ToList(); + if (!notYetVisitedSets.Any()) { previouslyVisitedRandomSets.RemoveAll(s => visibleSets.Contains(s)); @@ -394,13 +396,16 @@ namespace osu.Game.Screens.Select case Key.Up: direction = -1; break; + case Key.Down: direction = 1; break; + case Key.Left: direction = -1; skipDifficulties = true; break; + case Key.Right: direction = 1; skipDifficulties = true; @@ -465,8 +470,10 @@ namespace osu.Game.Screens.Select case LoadState.NotLoaded: LoadComponentAsync(item); break; + case LoadState.Loading: break; + default: scrollableContent.Add(item); break; @@ -557,6 +564,7 @@ namespace osu.Game.Screens.Select set.MoveToX(set.Item.State.Value == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); set.MoveToY(currentY, 750, Easing.OutExpo); break; + case DrawableCarouselBeatmap beatmap: if (beatmap.Item.State.Value == CarouselItemState.Selected) scrollTarget = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2; diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 6a10e86198..7c7d9e3928 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -53,6 +53,7 @@ namespace osu.Game.Screens.Select var autoType = auto.GetType(); var mods = SelectedMods.Value; + if (mods.All(m => m.GetType() != autoType)) { SelectedMods.Value = mods.Append(auto); diff --git a/osu.Game/Screens/Tournament/Drawings.cs b/osu.Game/Screens/Tournament/Drawings.cs index f8445a4a7d..8499b56847 100644 --- a/osu.Game/Screens/Tournament/Drawings.cs +++ b/osu.Game/Screens/Tournament/Drawings.cs @@ -318,6 +318,7 @@ namespace osu.Game.Screens.Tournament using (StreamReader sr = new StreamReader(stream)) { string line; + while ((line = sr.ReadLine()?.Trim()) != null) { if (string.IsNullOrEmpty(line)) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 358b2b222b..ea4a777b47 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -49,15 +49,19 @@ namespace osu.Game.Skinning case "Play/Miss": componentName = "hit0"; break; + case "Play/Meh": componentName = "hit50"; break; + case "Play/Good": componentName = "hit100"; break; + case "Play/Great": componentName = "hit300"; break; + case "Play/osu/number-text": return !hasFont(Configuration.HitCircleFont) ? null @@ -82,6 +86,7 @@ namespace osu.Game.Skinning float ratio = 2; var texture = Textures.Get($"{componentName}@2x"); + if (texture == null) { ratio = 1; @@ -184,6 +189,7 @@ namespace osu.Game.Skinning float ratio = 36; var texture = textures.Get($"{textureName}@2x"); + if (texture == null) { ratio = 18; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index f6bbbc8355..3a4d44f608 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -76,6 +76,7 @@ namespace osu.Game.Skinning base.Populate(model, archive); Skin reference = getSkin(model); + if (!string.IsNullOrEmpty(reference.Configuration.SkinInfo.Name)) { model.Name = reference.Configuration.SkinInfo.Name; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 0b9ebaf3a7..d01fba7d39 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -67,6 +67,7 @@ namespace osu.Game.Storyboards.Drawables private void load(IBindable beatmap, TextureStore textureStore) { var basePath = Animation.Path.ToLowerInvariant(); + for (var frame = 0; frame < Animation.FrameCount; frame++) { var framePath = basePath.Replace(".", frame + "."); diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index b91b05bd04..8f8ec22aae 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -82,6 +82,7 @@ namespace osu.Game.Storyboards where T : struct { var initialized = false; + foreach (var command in commands.OrderBy(l => l)) { if (!initialized) diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 44ac38044d..6a5e17eb38 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -35,6 +35,7 @@ namespace osu.Game.Tests.Beatmaps Assert.Multiple(() => { int mappingCounter = 0; + while (true) { if (mappingCounter >= ourResult.Mappings.Count && mappingCounter >= expectedResult.Mappings.Count) @@ -61,6 +62,7 @@ namespace osu.Game.Tests.Beatmaps Assert.Multiple(() => { int objectCounter = 0; + while (true) { if (objectCounter >= ourMapping.Objects.Count && objectCounter >= expectedMapping.Objects.Count) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index cfffed663c..e6f8044b60 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -231,6 +231,7 @@ True NEXT_LINE 1 + 1 NEXT_LINE 1 1 From c39c37a18dce16f25fbf9a23be5f27c93c485320 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Apr 2019 12:44:46 +0900 Subject: [PATCH 332/623] Apply more missed cases --- osu.Desktop/Updater/SimpleUpdateManager.cs | 1 + .../CatchBeatmapConversionTest.cs | 2 ++ osu.Game.Rulesets.Catch/CatchRuleset.cs | 4 ++++ .../Judgements/CatchBananaJudgement.cs | 2 ++ .../Judgements/CatchDropletJudgement.cs | 2 ++ .../Judgements/CatchJudgement.cs | 2 ++ .../Judgements/CatchTinyDropletJudgement.cs | 2 ++ .../Objects/Drawable/DrawableCatchHitObject.cs | 1 + osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 1 + .../UI/DrawableCatchRuleset.cs | 5 +++++ .../Legacy/EndTimeObjectPatternGenerator.cs | 2 ++ .../Edit/ManiaHitObjectComposer.cs | 1 + .../Judgements/ManiaJudgement.cs | 4 ++++ .../Objects/Drawables/DrawableManiaHitObject.cs | 1 + .../Scoring/ManiaScoreProcessor.cs | 5 +++++ .../OsuBeatmapConversionTest.cs | 1 + .../Sliders/Components/SliderCirclePiece.cs | 1 + .../Edit/OsuHitObjectComposer.cs | 2 ++ osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs | 3 +++ osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 3 +++ .../Objects/Drawables/DrawableRepeatPoint.cs | 2 ++ .../Objects/Drawables/DrawableSliderTick.cs | 2 ++ .../Objects/Drawables/DrawableSpinner.cs | 2 ++ osu.Game.Rulesets.Osu/Objects/Slider.cs | 3 +++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 5 +++++ osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 2 ++ .../TestCaseTaikoPlayfield.cs | 5 +++++ .../Judgements/TaikoDrumRollJudgement.cs | 1 + .../Judgements/TaikoDrumRollTickJudgement.cs | 2 ++ .../Judgements/TaikoJudgement.cs | 5 +++++ .../Judgements/TaikoSwellJudgement.cs | 1 + .../Objects/Drawables/DrawableSwell.cs | 1 + .../Objects/TaikoHitWindows.cs | 1 + osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 4 ++++ .../UI/DrawableTaikoJudgement.cs | 1 + .../UI/DrawableTaikoRuleset.cs | 5 +++++ osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 ++ .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 15 +++++++++++++++ .../Formats/LegacyStoryboardDecoderTest.cs | 2 ++ .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 1 + .../Beatmaps/IO/OszArchiveReaderTest.cs | 1 + osu.Game.Tests/Skins/LegacySkinDecoderTest.cs | 1 + .../Gameplay/TestCaseScrollingHitObjects.cs | 4 ++++ .../Visual/SongSelect/TestCaseBeatmapInfoWedge.cs | 4 ++++ .../UserInterface/TestCaseNotificationOverlay.cs | 3 +++ osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs | 2 ++ .../Drawables/DifficultyColouredContainer.cs | 5 +++++ .../WorkingBeatmap_VirtualBeatmapTrack.cs | 2 ++ .../Containers/OsuFocusedOverlayContainer.cs | 2 ++ osu.Game/Graphics/OsuColour.cs | 2 ++ osu.Game/Graphics/OsuFont.cs | 2 ++ osu.Game/Graphics/ScreenshotManager.cs | 2 ++ osu.Game/Graphics/UserInterface/OsuMenu.cs | 2 ++ osu.Game/Input/IdleTracker.cs | 1 + osu.Game/Online/API/Requests/GetRoomsRequest.cs | 3 +++ osu.Game/Overlays/AccountCreationOverlay.cs | 2 ++ .../Overlays/BeatmapSet/Buttons/DownloadButton.cs | 3 +++ osu.Game/Overlays/BeatmapSet/Header.cs | 2 ++ osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs | 1 + osu.Game/Overlays/Direct/DownloadButton.cs | 4 ++++ osu.Game/Overlays/Direct/DownloadProgressBar.cs | 3 +++ osu.Game/Overlays/MainSettings.cs | 1 + osu.Game/Overlays/MedalSplash/DrawableMedal.cs | 3 +++ .../Notifications/ProgressNotification.cs | 3 +++ osu.Game/Overlays/OnScreenDisplay.cs | 1 + .../Sections/Ranks/PaginatedScoreContainer.cs | 1 + .../Settings/Sections/General/LoginSettings.cs | 5 +++++ osu.Game/Overlays/Settings/Sidebar.cs | 1 + osu.Game/Overlays/Toolbar/ToolbarUserButton.cs | 1 + osu.Game/Overlays/VolumeOverlay.cs | 2 ++ .../Rulesets/Difficulty/DifficultyCalculator.cs | 2 ++ osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 2 ++ osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 6 ++++++ osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 ++ osu.Game/Rulesets/Objects/HitWindows.cs | 7 +++++++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 3 +++ osu.Game/Rulesets/UI/ModIcon.cs | 4 ++++ osu.Game/Rulesets/UI/RulesetInputManager.cs | 1 + .../UI/Scrolling/DrawableScrollingRuleset.cs | 3 +++ .../UI/Scrolling/ScrollingHitObjectContainer.cs | 5 +++++ osu.Game/Scoring/Legacy/LegacyScoreInfo.cs | 2 ++ .../Edit/Compose/Components/BeatDivisorControl.cs | 9 +++++++++ osu.Game/Screens/Menu/Button.cs | 5 +++++ osu.Game/Screens/Menu/ButtonArea.cs | 3 +++ osu.Game/Screens/Menu/MainMenu.cs | 1 + osu.Game/Screens/Play/GameplayMenuOverlay.cs | 2 ++ osu.Game/Screens/Play/KeyCounterMouse.cs | 2 ++ osu.Game/Screens/Play/SkipOverlay.cs | 1 + .../Screens/Select/Carousel/CarouselBeatmapSet.cs | 3 +++ osu.Game/Screens/Select/Carousel/CarouselGroup.cs | 1 + .../Select/Carousel/CarouselGroupEagerSelect.cs | 1 + .../Select/Carousel/DrawableCarouselItem.cs | 1 + .../Screens/Tournament/ScrollingTeamContainer.cs | 3 +++ osu.Game/Skinning/LegacySkinDecoder.cs | 3 +++ osu.Game/Tests/Visual/ScrollingTestContainer.cs | 2 ++ 95 files changed, 250 insertions(+) diff --git a/osu.Desktop/Updater/SimpleUpdateManager.cs b/osu.Desktop/Updater/SimpleUpdateManager.cs index 1cb47d6b58..e07ecc9433 100644 --- a/osu.Desktop/Updater/SimpleUpdateManager.cs +++ b/osu.Desktop/Updater/SimpleUpdateManager.cs @@ -78,6 +78,7 @@ namespace osu.Desktop.Updater case RuntimeInfo.Platform.Windows: bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".exe")); break; + case RuntimeInfo.Platform.MacOsx: bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".app.zip")); break; diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index 7f85d4ccce..e45ed8c6f4 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -36,11 +36,13 @@ namespace osu.Game.Rulesets.Catch.Tests yield return new ConvertValue((CatchHitObject)nested); break; + case BananaShower shower: foreach (var nested in shower.NestedHitObjects) yield return new ConvertValue((CatchHitObject)nested); break; + default: yield return new ConvertValue((CatchHitObject)hitObject); diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index aa00e182a9..0c46c1f9e5 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -87,6 +87,7 @@ namespace osu.Game.Rulesets.Catch new CatchModNoFail(), new MultiMod(new CatchModHalfTime(), new CatchModDaycore()) }; + case ModType.DifficultyIncrease: return new Mod[] { @@ -96,17 +97,20 @@ namespace osu.Game.Rulesets.Catch new CatchModHidden(), new CatchModFlashlight(), }; + case ModType.Automation: return new Mod[] { new MultiMod(new CatchModAutoplay(), new ModCinema()), new CatchModRelax(), }; + case ModType.Fun: return new Mod[] { new MultiMod(new ModWindUp(), new ModWindDown()) }; + default: return new Mod[] { }; } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs index 4e64753a65..1da4cf4a5e 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 1100; } @@ -27,6 +28,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 8; } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs index 2598dee156..4272d8471e 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs @@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 30; } @@ -24,6 +25,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 7; } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs index 5d7ef04dd2..6acef7190c 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 300; } @@ -28,6 +29,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 10.2; } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs index b8c51b7b60..d71ff3f640 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 10; } @@ -26,6 +27,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 4; } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index 294fd97d59..2f8ccec48b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -84,6 +84,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable case ArmedState.Miss: this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out).Expire(); break; + case ArmedState.Hit: this.FadeOut().Expire(); break; diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 2adc156efd..a9fd34455a 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -95,6 +95,7 @@ namespace osu.Game.Rulesets.Catch.Objects X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH, }); break; + case SliderEventType.Head: case SliderEventType.Tail: case SliderEventType.Repeat: diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index ba0f5b90ba..555c1adc4d 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -46,14 +46,19 @@ namespace osu.Game.Rulesets.Catch.UI { case Banana banana: return new DrawableBanana(banana); + case Fruit fruit: return new DrawableFruit(fruit); + case JuiceStream stream: return new DrawableJuiceStream(stream, CreateDrawableRepresentation); + case BananaShower shower: return new DrawableBananaShower(shower, CreateDrawableRepresentation); + case TinyDroplet tiny: return new DrawableTinyDroplet(tiny); + case Droplet droplet: return new DrawableDroplet(droplet); } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index 0bf6c055ac..9e95be35fa 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -38,9 +38,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy case 8 when HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000: addToPattern(pattern, 0, generateHold); break; + case 8: addToPattern(pattern, FindAvailableColumn(GetRandomColumn(), PreviousPattern), generateHold); break; + default: if (TotalColumns > 0) addToPattern(pattern, GetRandomColumn(), generateHold); diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 56c9471462..eec3e1b33d 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -65,6 +65,7 @@ namespace osu.Game.Rulesets.Mania.Edit { case DrawableNote note: return new NoteSelectionBlueprint(note); + case DrawableHoldNote holdNote: return new HoldNoteSelectionBlueprint(holdNote); } diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs index b6fb37f054..c2f8fb8678 100644 --- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs @@ -14,12 +14,16 @@ namespace osu.Game.Rulesets.Mania.Judgements { default: return 0; + case HitResult.Meh: return 50; + case HitResult.Ok: return 100; + case HitResult.Good: return 200; + case HitResult.Great: case HitResult.Perfect: return 300; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index a78524011f..0873f753be 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -65,6 +65,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables case ArmedState.Miss: this.FadeOut(150, Easing.In).Expire(); break; + case ArmedState.Hit: this.FadeOut(150, Easing.OutQuint).Expire(); break; diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 5c914d8eac..ba38d11225 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -140,18 +140,23 @@ namespace osu.Game.Rulesets.Mania.Scoring case HitResult.Miss: Health.Value += hpMissMultiplier * hp_increase_miss; break; + case HitResult.Meh: Health.Value += hpMultiplier * hp_increase_bad; break; + case HitResult.Ok: Health.Value += hpMultiplier * hp_increase_ok; break; + case HitResult.Good: Health.Value += hpMultiplier * hp_increase_good; break; + case HitResult.Great: Health.Value += hpMultiplier * hp_increase_great; break; + case HitResult.Perfect: Health.Value += hpMultiplier * hp_increase_perfect; break; diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index f7d1ff4db1..f98d63e6c7 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -35,6 +35,7 @@ namespace osu.Game.Rulesets.Osu.Tests yield return createConvertValue(nested); break; + default: yield return createConvertValue(hitObject); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs index 9d164ebe0b..2ecfea2e3e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs @@ -37,6 +37,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components case SliderPosition.Start: Position = slider.StackedPosition + slider.Path.PositionAt(0); break; + case SliderPosition.End: Position = slider.StackedPosition + slider.Path.PositionAt(1); break; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 039ec5585e..8d007ad88e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -41,8 +41,10 @@ namespace osu.Game.Rulesets.Osu.Edit { case DrawableHitCircle circle: return new HitCircleSelectionBlueprint(circle); + case DrawableSlider slider: return new SliderSelectionBlueprint(slider); + case DrawableSpinner spinner: return new SpinnerSelectionBlueprint(spinner); } diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs index 81fedf9f4a..bf30fbc351 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs @@ -16,10 +16,13 @@ namespace osu.Game.Rulesets.Osu.Judgements { default: return 0; + case HitResult.Meh: return 50; + case HitResult.Good: return 100; + case HitResult.Great: return 300; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 1a30b2c944..ddf708d0f1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -59,11 +59,13 @@ namespace osu.Game.Rulesets.Osu.Mods circle.FadeOut(fadeOutDuration); break; + case DrawableSlider slider: using (slider.BeginAbsoluteSequence(fadeOutStartTime, true)) slider.Body.FadeOut(longFadeDuration, Easing.Out); break; + case DrawableSliderTick sliderTick: // slider ticks fade out over up to one second var tickFadeOutDuration = Math.Min(sliderTick.HitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000); @@ -72,6 +74,7 @@ namespace osu.Game.Rulesets.Osu.Mods sliderTick.FadeOut(tickFadeOutDuration); break; + case DrawableSpinner spinner: // hide elements we don't care about. spinner.Disc.Hide(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index a6714690b1..c278c0c7ec 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -64,9 +64,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Idle: this.Delay(HitObject.TimePreempt).FadeOut(); break; + case ArmedState.Miss: this.FadeOut(animDuration); break; + case ArmedState.Hit: this.FadeOut(animDuration, Easing.OutQuint) .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index b5ce36f889..72b648bfd0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -67,10 +67,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Idle: this.Delay(HitObject.TimePreempt).FadeOut(); break; + case ArmedState.Miss: this.FadeOut(ANIM_DURATION); this.FadeColour(Color4.Red, ANIM_DURATION / 2); break; + case ArmedState.Hit: this.FadeOut(ANIM_DURATION, Easing.OutQuint); this.ScaleTo(Scale * 1.5f, ANIM_DURATION, Easing.Out); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 3a6ff3fcf8..ca219b0094 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -222,9 +222,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Idle: Expire(true); break; + case ArmedState.Hit: sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out); break; + case ArmedState.Miss: sequence.ScaleTo(Scale * 0.8f, 320, Easing.In); break; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 1afbacc01e..a8aec005d1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -183,6 +183,7 @@ namespace osu.Game.Rulesets.Osu.Objects Samples = sampleList }); break; + case SliderEventType.Head: AddNested(HeadCircle = new SliderCircle { @@ -194,6 +195,7 @@ namespace osu.Game.Rulesets.Osu.Objects ComboIndex = ComboIndex, }); break; + case SliderEventType.LegacyLastTick: // we need to use the LegacyLastTick here for compatibility reasons (difficulty). // it is *okay* to use this because the TailCircle is not used for any meaningful purpose in gameplay. @@ -206,6 +208,7 @@ namespace osu.Game.Rulesets.Osu.Objects ComboIndex = ComboIndex, }); break; + case SliderEventType.Repeat: AddNested(new RepeatPoint { diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 44bce5bed8..3481b7751b 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -104,6 +104,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModHalfTime(), new OsuModDaycore()), new OsuModSpunOut(), }; + case ModType.DifficultyIncrease: return new Mod[] { @@ -113,11 +114,13 @@ namespace osu.Game.Rulesets.Osu new OsuModHidden(), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), }; + case ModType.Conversion: return new Mod[] { new OsuModTarget(), }; + case ModType.Automation: return new Mod[] { @@ -125,6 +128,7 @@ namespace osu.Game.Rulesets.Osu new OsuModRelax(), new OsuModAutopilot(), }; + case ModType.Fun: return new Mod[] { @@ -133,6 +137,7 @@ namespace osu.Game.Rulesets.Osu new OsuModGrow(), new MultiMod(new ModWindUp(), new ModWindDown()), }; + default: return new Mod[] { }; } diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 828b3720d3..3adf788665 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -40,8 +40,10 @@ namespace osu.Game.Rulesets.Osu.UI { case HitCircle circle: return new DrawableHitCircle(circle); + case Slider slider: return new DrawableSlider(slider); + case Spinner spinner: return new DrawableSpinner(spinner); } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs index 369cdd49d2..69eb48b7ce 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs @@ -100,15 +100,19 @@ namespace osu.Game.Rulesets.Taiko.Tests case 1: addCentreHit(false); break; + case 2: addCentreHit(true); break; + case 3: addDrumRoll(false); break; + case 4: addDrumRoll(true); break; + case 5: addSwell(); delay = scroll_time - 100; @@ -121,6 +125,7 @@ namespace osu.Game.Rulesets.Taiko.Tests default: playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, rng.Next(25, 400)), 500); break; + case 6: playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500); break; diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs index e5ebd5c647..604daa929f 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Miss: return 0; + default: return base.HealthIncreaseFor(result); } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index 32d4b77ca4..a617028f1c 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Great: return 200; + default: return 0; } @@ -26,6 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Great: return 0.15; + default: return 0; } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs index 427d38aaa7..eb5f443365 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs @@ -16,8 +16,10 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Good: return 100; + case HitResult.Great: return 300; + default: return 0; } @@ -29,10 +31,13 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Miss: return -1.0; + case HitResult.Good: return 1.1; + case HitResult.Great: return 3.0; + default: return 0; } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs index f0f621d12b..29be5e0eac 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { case HitResult.Miss: return -0.65; + default: return 0; } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 9211eccc40..5ec9dc61e2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -192,6 +192,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables using (BeginAbsoluteSequence(HitObject.StartTime - preempt, true)) targetRing.ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint); break; + case ArmedState.Miss: case ArmedState.Hit: this.FadeOut(out_transition_time, Easing.Out); diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs index ce841fff80..f232919cbf 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs @@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Objects case HitResult.Good: case HitResult.Miss: return true; + default: return false; } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 448b1b42bb..1fec4ae173 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -86,6 +86,7 @@ namespace osu.Game.Rulesets.Taiko new TaikoModNoFail(), new MultiMod(new TaikoModHalfTime(), new TaikoModDaycore()), }; + case ModType.DifficultyIncrease: return new Mod[] { @@ -95,17 +96,20 @@ namespace osu.Game.Rulesets.Taiko new TaikoModHidden(), new TaikoModFlashlight(), }; + case ModType.Automation: return new Mod[] { new MultiMod(new TaikoModAutoplay(), new ModCinema()), new TaikoModRelax(), }; + case ModType.Fun: return new Mod[] { new MultiMod(new ModWindUp(), new ModWindDown()) }; + default: return new Mod[] { }; } diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs index 943adaed4b..f91bbb14e8 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Taiko.UI case HitResult.Good: JudgementBody.Colour = colours.GreenLight; break; + case HitResult.Great: JudgementBody.Colour = colours.BlueLight; break; diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index f4b9c46dfc..d3b2f4e987 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -52,9 +52,11 @@ namespace osu.Game.Rulesets.Taiko.UI int currentIndex = 0; int currentBeat = 0; double time = timingPoints[currentIndex].Time; + while (time <= lastHitTime) { int nextIndex = currentIndex + 1; + if (nextIndex < timingPoints.Count && time > timingPoints[nextIndex].Time) { currentIndex = nextIndex; @@ -93,10 +95,13 @@ namespace osu.Game.Rulesets.Taiko.UI { case CentreHit centreHit: return new DrawableCentreHit(centreHit); + case RimHit rimHit: return new DrawableRimHit(rimHit); + case DrumRoll drumRoll: return new DrawableDrumRoll(drumRoll); + case Swell swell: return new DrawableSwell(swell); } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index dbff5270d2..260c5b0727 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -211,6 +211,7 @@ namespace osu.Game.Rulesets.Taiko.UI case DrawableBarLine barline: barlineContainer.Add(barline.CreateProxy()); break; + case DrawableTaikoHitObject taikoObject: topLevelHitContainer.Add(taikoObject.CreateProxiedContent()); break; @@ -231,6 +232,7 @@ namespace osu.Game.Rulesets.Taiko.UI if (result.IsHit) hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == ((DrawableStrongNestedHit)judgedObject).MainObject)?.VisualiseSecondHit(); break; + default: judgementContainer.Add(new DrawableTaikoJudgement(result, judgedObject) { diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 02dff6993d..6df4e7d6c8 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -47,6 +47,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapGeneral() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -70,6 +71,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapEditor() { var decoder = new LegacyBeatmapDecoder(); + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -95,6 +97,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapMetadata() { var decoder = new LegacyBeatmapDecoder(); + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -119,6 +122,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapDifficulty() { var decoder = new LegacyBeatmapDecoder(); + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -137,6 +141,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapEvents() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -155,6 +160,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapTimingPoints() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -190,6 +196,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapColours() { var decoder = new LegacySkinDecoder(); + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -215,6 +222,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapComboOffsetsOsu() { var decoder = new LegacyBeatmapDecoder(); + using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu")) using (var stream = new StreamReader(resStream)) { @@ -237,6 +245,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapComboOffsetsCatch() { var decoder = new LegacyBeatmapDecoder(); + using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu")) using (var stream = new StreamReader(resStream)) { @@ -259,6 +268,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapHitObjects() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -286,6 +296,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeControlPointCustomSampleBank() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("controlpoint-custom-samplebank.osu")) using (var stream = new StreamReader(resStream)) { @@ -307,6 +318,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeHitObjectCustomSampleBank() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("hitobject-custom-samplebank.osu")) using (var stream = new StreamReader(resStream)) { @@ -324,6 +336,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeHitObjectFileSamples() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("hitobject-file-samples.osu")) using (var stream = new StreamReader(resStream)) { @@ -343,6 +356,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeSliderSamples() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("slider-samples.osu")) using (var stream = new StreamReader(resStream)) { @@ -386,6 +400,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeHitObjectNullAdditionBank() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = TestResources.OpenResource("hitobject-no-addition-bank.osu")) using (var stream = new StreamReader(resStream)) { diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 2288d04493..971518909d 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -19,6 +19,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeStoryboardEvents() { var decoder = new LegacyStoryboardDecoder(); + using (var resStream = TestResources.OpenResource("Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu")) using (var stream = new StreamReader(resStream)) { @@ -91,6 +92,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeVariableWithSuffix() { var decoder = new LegacyStoryboardDecoder(); + using (var resStream = TestResources.OpenResource("variable-with-suffix.osb")) using (var stream = new StreamReader(resStream)) { diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index a867ddebae..39b7735a55 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -151,6 +151,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var sr = new StreamReader(stream)) { var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr); + using (var ms = new MemoryStream()) using (var sw = new StreamWriter(ms)) using (var sr2 = new StreamReader(ms)) diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 17197aff27..37e0565df0 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -75,6 +75,7 @@ namespace osu.Game.Tests.Beatmaps.IO using (var osz = TestResources.GetTestBeatmapStream()) { var reader = new ZipArchiveReader(osz); + using (var stream = new StreamReader( reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) { diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index 2a97519e21..24ef9e4535 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -18,6 +18,7 @@ namespace osu.Game.Tests.Skins public void TestDecodeSkinColours(bool hasColours) { var decoder = new LegacySkinDecoder(); + using (var resStream = TestResources.OpenResource(hasColours ? "skin.ini" : "skin-empty.ini")) using (var stream = new StreamReader(resStream)) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs index c99a4bb89b..b5a7240839 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs @@ -124,12 +124,15 @@ namespace osu.Game.Tests.Visual.Gameplay case ScrollingDirection.Up: obj.Anchor = Anchor.TopCentre; break; + case ScrollingDirection.Down: obj.Anchor = Anchor.BottomCentre; break; + case ScrollingDirection.Left: obj.Anchor = Anchor.CentreLeft; break; + case ScrollingDirection.Right: obj.Anchor = Anchor.CentreRight; break; @@ -184,6 +187,7 @@ namespace osu.Game.Tests.Visual.Gameplay RelativeSizeAxes = Axes.X; Height = 2; break; + case ScrollingDirection.Left: case ScrollingDirection.Right: RelativeSizeAxes = Axes.Y; diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapInfoWedge.cs index f3e44bd808..36a7eba37f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapInfoWedge.cs @@ -83,15 +83,19 @@ namespace osu.Game.Tests.Visual.SongSelect case OsuRuleset _: testInfoLabels(5); break; + case TaikoRuleset _: testInfoLabels(5); break; + case CatchRuleset _: testInfoLabels(5); break; + case ManiaRuleset _: testInfoLabels(4); break; + default: testInfoLabels(2); break; diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseNotificationOverlay.cs index 4819597d22..faf80d22ff 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseNotificationOverlay.cs @@ -86,12 +86,15 @@ namespace osu.Game.Tests.Visual.UserInterface case 0: sendHelloNotification(); break; + case 1: sendAmazingNotification(); break; + case 2: sendUploadProgress(); break; + case 3: sendDownloadProgress(); break; diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs index 3adfcb85ea..d0db7765c2 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs @@ -32,9 +32,11 @@ namespace osu.Game.Beatmaps.Drawables case BeatmapSetCoverType.Cover: resource = set.OnlineInfo.Covers.Cover; break; + case BeatmapSetCoverType.Card: resource = set.OnlineInfo.Covers.Card; break; + case BeatmapSetCoverType.List: resource = set.OnlineInfo.Covers.List; break; diff --git a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs index f1607ad749..26ffcca1ec 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs @@ -63,15 +63,20 @@ namespace osu.Game.Beatmaps.Drawables { case DifficultyRating.Easy: return palette.Green; + default: case DifficultyRating.Normal: return palette.Blue; + case DifficultyRating.Hard: return palette.Yellow; + case DifficultyRating.Insane: return palette.Pink; + case DifficultyRating.Expert: return palette.Purple; + case DifficultyRating.ExpertPlus: return palette.Gray0; } diff --git a/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs b/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs index cb1be82c69..1e237a2b53 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs @@ -26,9 +26,11 @@ namespace osu.Game.Beatmaps case null: Length = excess_length; break; + case IHasEndTime endTime: Length = endTime.EndTime + excess_length; break; + default: Length = lastObject.StartTime + excess_length; break; diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index c6ee91f961..3f84f77081 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -84,6 +84,7 @@ namespace osu.Game.Graphics.Containers case GlobalAction.Back: State = Visibility.Hidden; return true; + case GlobalAction.Select: return true; } @@ -107,6 +108,7 @@ namespace osu.Game.Graphics.Containers State = Visibility.Hidden; break; + case Visibility.Hidden: if (PlaySamplesOnStateChange) samplePopOut?.Play(); if (BlockScreenWideMouse) osuGame?.RemoveBlockingOverlay(this); diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 712dc4c444..192cb917d5 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -20,12 +20,14 @@ namespace osu.Game.Graphics { default: throw new ArgumentException(@"Invalid hex string length!"); + case 3: return new Color4( (byte)(Convert.ToByte(hex.Substring(0, 1), 16) * 17), (byte)(Convert.ToByte(hex.Substring(1, 1), 16) * 17), (byte)(Convert.ToByte(hex.Substring(2, 1), 16) * 17), 255); + case 6: return new Color4( Convert.ToByte(hex.Substring(0, 2), 16), diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 26112430f6..841e53ffbb 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -42,8 +42,10 @@ namespace osu.Game.Graphics { case Typeface.Exo: return "Exo2.0"; + case Typeface.FontAwesome: return "FontAwesome"; + case Typeface.Venera: return "Venera"; } diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index a2ac71de93..24a98e6dc9 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -104,9 +104,11 @@ namespace osu.Game.Graphics case ScreenshotFormat.Png: image.SaveAsPng(stream); break; + case ScreenshotFormat.Jpg: image.SaveAsJpeg(stream); break; + default: throw new ArgumentOutOfRangeException(nameof(screenshotFormat)); } diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index 9b5755ea8b..32994be78a 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -88,9 +88,11 @@ namespace osu.Game.Graphics.UserInterface case MenuItemType.Standard: text.Colour = Color4.White; break; + case MenuItemType.Destructive: text.Colour = Color4.Red; break; + case MenuItemType.Highlighted: text.Colour = OsuColour.FromHex(@"ffcc22"); break; diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs index 91e2456ef7..cbc446a126 100644 --- a/osu.Game/Input/IdleTracker.cs +++ b/osu.Game/Input/IdleTracker.cs @@ -62,6 +62,7 @@ namespace osu.Game.Input case MouseUpEvent _: case MouseMoveEvent _: return updateLastInteractionTime(); + default: return base.Handle(e); } diff --git a/osu.Game/Online/API/Requests/GetRoomsRequest.cs b/osu.Game/Online/API/Requests/GetRoomsRequest.cs index d7c66707e4..8f1497ef33 100644 --- a/osu.Game/Online/API/Requests/GetRoomsRequest.cs +++ b/osu.Game/Online/API/Requests/GetRoomsRequest.cs @@ -26,12 +26,15 @@ namespace osu.Game.Online.API.Requests { case PrimaryFilter.Open: break; + case PrimaryFilter.Owned: target += "/owned"; break; + case PrimaryFilter.Participated: target += "/participated"; break; + case PrimaryFilter.RecentlyEnded: target += "/ended"; break; diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index e8e44c206e..0169401d2d 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -103,8 +103,10 @@ namespace osu.Game.Overlays case APIState.Offline: case APIState.Failing: break; + case APIState.Connecting: break; + case APIState.Online: State = Visibility.Hidden; break; diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs index 667869e310..e6fca84731 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs @@ -123,6 +123,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons }, }; break; + case DownloadState.Downloaded: textSprites.Children = new Drawable[] { @@ -133,9 +134,11 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons }, }; break; + case DownloadState.LocallyAvailable: this.FadeOut(200); break; + case DownloadState.NotDownloaded: textSprites.Children = new Drawable[] { diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 95cf9e9d04..7ea0748585 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -225,11 +225,13 @@ namespace osu.Game.Overlays.BeatmapSet RelativeSizeAxes = Axes.Y }; break; + case DownloadState.Downloading: case DownloadState.Downloaded: // temporary to avoid showing two buttons for maps with novideo. will be fixed in new beatmap overlay design. downloadButtonsContainer.Child = new DownloadButton(BeatmapSet.Value); break; + default: downloadButtonsContainer.Child = new DownloadButton(BeatmapSet.Value); if (BeatmapSet.Value.OnlineInfo.HasVideo) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index f8a8038878..a47a494a42 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -58,6 +58,7 @@ namespace osu.Game.Overlays.Chat.Tabs { default: return new ChannelTabItem(value) { OnRequestClose = tabCloseRequested }; + case ChannelType.PM: return new PrivateChannelTabItem(value) { OnRequestClose = tabCloseRequested }; } diff --git a/osu.Game/Overlays/Direct/DownloadButton.cs b/osu.Game/Overlays/Direct/DownloadButton.cs index 7fc145d635..26e2bb1ae4 100644 --- a/osu.Game/Overlays/Direct/DownloadButton.cs +++ b/osu.Game/Overlays/Direct/DownloadButton.cs @@ -85,9 +85,11 @@ namespace osu.Game.Overlays.Direct case DownloadState.Downloaded: shakeContainer.Shake(); break; + case DownloadState.LocallyAvailable: game.PresentBeatmap(BeatmapSet.Value); break; + default: beatmaps.Download(BeatmapSet.Value, noVideo); break; @@ -110,9 +112,11 @@ namespace osu.Game.Overlays.Direct icon.MoveToX(0, 500, Easing.InOutExpo); checkmark.ScaleTo(Vector2.Zero, 500, Easing.InOutExpo); break; + case DownloadState.Downloaded: background.FadeColour(colours.Yellow, 500, Easing.InOutExpo); break; + case DownloadState.LocallyAvailable: background.FadeColour(colours.Green, 500, Easing.InOutExpo); icon.MoveToX(-8, 500, Easing.InOutExpo); diff --git a/osu.Game/Overlays/Direct/DownloadProgressBar.cs b/osu.Game/Overlays/Direct/DownloadProgressBar.cs index 9c2b1e5b63..57500b3531 100644 --- a/osu.Game/Overlays/Direct/DownloadProgressBar.cs +++ b/osu.Game/Overlays/Direct/DownloadProgressBar.cs @@ -43,10 +43,12 @@ namespace osu.Game.Overlays.Direct progressBar.Current.Value = 0; progressBar.FadeOut(500); break; + case DownloadState.Downloading: progressBar.FadeIn(400, Easing.OutQuint); progressBar.ResizeHeightTo(4, 400, Easing.OutQuint); break; + case DownloadState.Downloaded: progressBar.FadeIn(400, Easing.OutQuint); progressBar.ResizeHeightTo(4, 400, Easing.OutQuint); @@ -54,6 +56,7 @@ namespace osu.Game.Overlays.Direct progressBar.Current.Value = 1; progressBar.FillColour = colours.Yellow; break; + case DownloadState.LocallyAvailable: progressBar.FadeOut(500); break; diff --git a/osu.Game/Overlays/MainSettings.cs b/osu.Game/Overlays/MainSettings.cs index 61dd51d16f..39bfdfd4d6 100644 --- a/osu.Game/Overlays/MainSettings.cs +++ b/osu.Game/Overlays/MainSettings.cs @@ -55,6 +55,7 @@ namespace osu.Game.Overlays SectionsContainer.FadeOut(300, Easing.OutQuint); ContentContainer.MoveToX(-WIDTH, 500, Easing.OutQuint); break; + case Visibility.Hidden: Background.FadeTo(0.6f, 500, Easing.OutQuint); Sidebar?.FadeColour(Color4.White, 300, Easing.OutQuint); diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 431ae98c2c..f1ae5d64f5 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -156,11 +156,13 @@ namespace osu.Game.Overlays.MedalSplash case DisplayState.None: medalContainer.ScaleTo(0); break; + case DisplayState.Icon: medalContainer .FadeIn(duration) .ScaleTo(1, duration, Easing.OutElastic); break; + case DisplayState.MedalUnlocked: medalContainer .FadeTo(1) @@ -170,6 +172,7 @@ namespace osu.Game.Overlays.MedalSplash this.MoveToY(MedalOverlay.DISC_SIZE / 2 - 30, duration, Easing.OutExpo); unlocked.FadeInFromZero(duration); break; + case DisplayState.Full: medalContainer .FadeTo(1) diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 75e70b18ea..857a0bda9e 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -54,11 +54,13 @@ namespace osu.Game.Overlays.Notifications Light.Pulsate = false; progressBar.Active = false; break; + case ProgressNotificationState.Active: Light.Colour = colourActive; Light.Pulsate = true; progressBar.Active = true; break; + case ProgressNotificationState.Cancelled: Light.Colour = colourCancelled; Light.Pulsate = false; @@ -145,6 +147,7 @@ namespace osu.Game.Overlays.Notifications case ProgressNotificationState.Cancelled: base.Close(); break; + case ProgressNotificationState.Active: case ProgressNotificationState.Queued: if (CancelRequested?.Invoke() != false) diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index 5e45fbf081..5af7d5aea5 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -188,6 +188,7 @@ namespace osu.Game.Overlays optionCount = 1; if (val) selectedOption = 0; break; + case Enum _: var values = Enum.GetValues(description.RawValue.GetType()); optionCount = values.Length; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 95a18ccfa9..470bed2854 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -54,6 +54,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks default: drawableScores = scores.Select(score => new DrawablePerformanceScore(score, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null)); break; + case ScoreType.Recent: drawableScores = scores.Select(score => new DrawableTotalScore(score)); break; diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 078c01ce92..8ac13060b0 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -87,6 +87,7 @@ namespace osu.Game.Overlays.Settings.Sections.General } }; break; + case APIState.Failing: case APIState.Connecting: LinkFlowContainer linkFlow; @@ -112,6 +113,7 @@ namespace osu.Game.Overlays.Settings.Sections.General linkFlow.AddLink("cancel", api.Logout, string.Empty); break; + case APIState.Online: Children = new Drawable[] { @@ -160,14 +162,17 @@ namespace osu.Game.Overlays.Settings.Sections.General api.LocalUser.Value.Status.Value = new UserStatusOnline(); dropdown.StatusColour = colours.Green; break; + case UserAction.DoNotDisturb: api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb(); dropdown.StatusColour = colours.Red; break; + case UserAction.AppearOffline: api.LocalUser.Value.Status.Value = new UserStatusOffline(); dropdown.StatusColour = colours.Gray7; break; + case UserAction.SignOut: api.Logout(); break; diff --git a/osu.Game/Overlays/Settings/Sidebar.cs b/osu.Game/Overlays/Settings/Sidebar.cs index 969686e36d..3c18627f23 100644 --- a/osu.Game/Overlays/Settings/Sidebar.cs +++ b/osu.Game/Overlays/Settings/Sidebar.cs @@ -102,6 +102,7 @@ namespace osu.Game.Overlays.Settings default: this.ResizeTo(new Vector2(DEFAULT_WIDTH, Height), 500, Easing.OutQuint); break; + case ExpandedState.Expanded: this.ResizeTo(new Vector2(EXPANDED_WIDTH, Height), 500, Easing.OutQuint); break; diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 356ffa5180..77def1adcf 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -58,6 +58,7 @@ namespace osu.Game.Overlays.Toolbar Text = @"Guest"; avatar.User = new User(); break; + case APIState.Online: Text = api.LocalUser.Value.Username; avatar.User = api.LocalUser.Value; diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index e2e480ef53..34b15d958d 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -105,12 +105,14 @@ namespace osu.Game.Overlays else volumeMeterMaster.Decrease(amount, isPrecise); return true; + case GlobalAction.IncreaseVolume: if (State == Visibility.Hidden) Show(); else volumeMeterMaster.Increase(amount, isPrecise); return true; + case GlobalAction.ToggleMute: Show(); muteButton.Current.Value = !muteButton.Current.Value; diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index aad55f8a38..4e71615195 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -117,10 +117,12 @@ namespace osu.Game.Rulesets.Difficulty yield return new ModNoMod(); break; + case 1: yield return currentSet.Single(); break; + default: yield return new MultiMod(currentSet.ToArray()); diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 74aa9ace2d..2dd45660c7 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -122,8 +122,10 @@ namespace osu.Game.Rulesets.Edit { case ScrollEvent _: return false; + case MouseEvent _: return true; + default: return false; } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 89db954c36..2150726a42 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -89,6 +89,7 @@ namespace osu.Game.Rulesets.Judgements { case HitResult.None: break; + case HitResult.Miss: JudgementBody.ScaleTo(1.6f); JudgementBody.ScaleTo(1, 100, Easing.In); @@ -98,6 +99,7 @@ namespace osu.Game.Rulesets.Judgements this.Delay(600).FadeOut(200); break; + default: ApplyHitAnimations(); break; @@ -113,13 +115,17 @@ namespace osu.Game.Rulesets.Judgements case HitResult.Perfect: case HitResult.Great: return colours.Blue; + case HitResult.Ok: case HitResult.Good: return colours.Green; + case HitResult.Meh: return colours.Yellow; + case HitResult.Miss: return colours.Red; + default: return Color4.White; } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 62407907c1..a5f96087c0 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -73,10 +73,12 @@ namespace osu.Game.Rulesets.Mods pitch.PitchAdjust /= lastAdjust; pitch.PitchAdjust *= adjust; break; + case IHasTempoAdjust tempo: tempo.TempoAdjust /= lastAdjust; tempo.TempoAdjust *= adjust; break; + default: clock.Rate /= lastAdjust; clock.Rate *= adjust; diff --git a/osu.Game/Rulesets/Objects/HitWindows.cs b/osu.Game/Rulesets/Objects/HitWindows.cs index c5b7686da6..6fb8425a33 100644 --- a/osu.Game/Rulesets/Objects/HitWindows.cs +++ b/osu.Game/Rulesets/Objects/HitWindows.cs @@ -77,6 +77,7 @@ namespace osu.Game.Rulesets.Objects case HitResult.Perfect: case HitResult.Ok: return false; + default: return true; } @@ -126,16 +127,22 @@ namespace osu.Game.Rulesets.Objects { case HitResult.Perfect: return Perfect / 2; + case HitResult.Great: return Great / 2; + case HitResult.Good: return Good / 2; + case HitResult.Ok: return Ok / 2; + case HitResult.Meh: return Meh / 2; + case HitResult.Miss: return Miss / 2; + default: throw new ArgumentException(nameof(result)); } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 0fddb19a6c..034ebbeb3e 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -313,9 +313,11 @@ namespace osu.Game.Rulesets.Scoring { case HitResult.None: break; + case HitResult.Miss: Combo.Value = 0; break; + default: Combo.Value++; break; @@ -373,6 +375,7 @@ namespace osu.Game.Rulesets.Scoring default: case ScoringMode.Standardised: return max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo.Value / maxHighestCombo) + bonusScore; + case ScoringMode.Classic: // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) return bonusScore + baseScore * (1 + Math.Max(0, HighestCombo.Value - 1) / 25); diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index f9f6b5cc2f..86feea09a8 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -76,18 +76,22 @@ namespace osu.Game.Rulesets.UI backgroundColour = colours.Yellow; highlightedColour = colours.YellowLight; break; + case ModType.DifficultyReduction: backgroundColour = colours.Green; highlightedColour = colours.GreenLight; break; + case ModType.Automation: backgroundColour = colours.Blue; highlightedColour = colours.BlueLight; break; + case ModType.Conversion: backgroundColour = colours.Purple; highlightedColour = colours.PurpleLight; break; + case ModType.Fun: backgroundColour = colours.Pink; highlightedColour = colours.PinkLight; diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index b4271085f5..e25c3bd0e7 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -105,6 +105,7 @@ namespace osu.Game.Rulesets.UI return false; break; + case MouseUpEvent mouseUp: if (!CurrentState.Mouse.IsPressed(mouseUp.Button)) return false; diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 3b368652f2..a0bfb356bc 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -92,9 +92,11 @@ namespace osu.Game.Rulesets.UI.Scrolling case ScrollVisualisationMethod.Sequential: scrollingInfo.Algorithm = new SequentialScrollAlgorithm(controlPoints); break; + case ScrollVisualisationMethod.Overlapping: scrollingInfo.Algorithm = new OverlappingScrollAlgorithm(controlPoints); break; + case ScrollVisualisationMethod.Constant: scrollingInfo.Algorithm = new ConstantScrollAlgorithm(); break; @@ -159,6 +161,7 @@ namespace osu.Game.Rulesets.UI.Scrolling case GlobalAction.IncreaseScrollSpeed: this.TransformBindableTo(TimeRange, TimeRange.Value - time_span_step, 200, Easing.OutQuint); return true; + case GlobalAction.DecreaseScrollSpeed: this.TransformBindableTo(TimeRange, TimeRange.Value + time_span_step, 200, Easing.OutQuint); return true; diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index ed3534fb36..069e2d1a0b 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -72,6 +72,7 @@ namespace osu.Game.Rulesets.UI.Scrolling case ScrollingDirection.Down: scrollLength = DrawSize.Y; break; + default: scrollLength = DrawSize.X; break; @@ -97,6 +98,7 @@ namespace osu.Game.Rulesets.UI.Scrolling case ScrollingDirection.Down: hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, timeRange.Value, scrollLength); break; + case ScrollingDirection.Left: case ScrollingDirection.Right: hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, timeRange.Value, scrollLength); @@ -129,12 +131,15 @@ namespace osu.Game.Rulesets.UI.Scrolling case ScrollingDirection.Up: hitObject.Y = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; + case ScrollingDirection.Down: hitObject.Y = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; + case ScrollingDirection.Left: hitObject.X = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; + case ScrollingDirection.Right: hitObject.X = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs b/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs index df80f848e3..e66f93ec8d 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs @@ -41,6 +41,7 @@ namespace osu.Game.Scoring.Legacy case 3: Statistics[HitResult.Great] = value; break; + case 2: Statistics[HitResult.Perfect] = value; break; @@ -81,6 +82,7 @@ namespace osu.Game.Scoring.Legacy case 1: Statistics[HitResult.Good] = value; break; + case 3: Statistics[HitResult.Ok] = value; break; diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 1f13797497..74b1e3c6cb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -236,10 +236,12 @@ namespace osu.Game.Screens.Edit.Compose.Components beatDivisor.Next(); OnUserChange(Current.Value); return true; + case Key.Left: beatDivisor.Previous(); OnUserChange(Current.Value); return true; + default: return false; } @@ -307,18 +309,25 @@ namespace osu.Game.Screens.Edit.Compose.Components { case 2: return colours.BlueLight; + case 4: return colours.Blue; + case 8: return colours.BlueDarker; + case 16: return colours.PurpleDark; + case 3: return colours.YellowLight; + case 6: return colours.Yellow; + case 12: return colours.YellowDarker; + default: return Color4.White; } diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index 794fc093d3..cc2a0c6c46 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -260,6 +260,7 @@ namespace osu.Game.Screens.Menu box.ScaleTo(new Vector2(0, 1), 500, Easing.OutExpo); this.FadeOut(500); break; + case 1: box.ScaleTo(new Vector2(0, 1), 400, Easing.InSine); this.FadeOut(800); @@ -267,11 +268,13 @@ namespace osu.Game.Screens.Menu } break; + case ButtonState.Expanded: const int expand_duration = 500; box.ScaleTo(new Vector2(1, 1), expand_duration, Easing.OutExpo); this.FadeIn(expand_duration / 6f); break; + case ButtonState.Exploded: const int explode_duration = 200; box.ScaleTo(new Vector2(2, 1), explode_duration, Easing.OutExpo); @@ -294,10 +297,12 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.Initial: State = ButtonState.Contracted; break; + case ButtonSystemState.EnteringMode: ContractStyle = 1; State = ButtonState.Contracted; break; + default: if (value == VisibleState) State = ButtonState.Expanded; diff --git a/osu.Game/Screens/Menu/ButtonArea.cs b/osu.Game/Screens/Menu/ButtonArea.cs index d6e1aef63c..b25efe53e1 100644 --- a/osu.Game/Screens/Menu/ButtonArea.cs +++ b/osu.Game/Screens/Menu/ButtonArea.cs @@ -57,6 +57,7 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.EnteringMode: State = Visibility.Hidden; break; + case ButtonSystemState.TopLevel: case ButtonSystemState.Play: State = Visibility.Visible; @@ -109,6 +110,7 @@ namespace osu.Game.Screens.Menu case ButtonAreaBackgroundState.Flat: this.ScaleTo(new Vector2(2, 0), 300, Easing.InSine); break; + case ButtonAreaBackgroundState.Normal: this.ScaleTo(Vector2.One, 400, Easing.OutQuint); break; @@ -127,6 +129,7 @@ namespace osu.Game.Screens.Menu default: State = ButtonAreaBackgroundState.Normal; break; + case ButtonSystemState.Initial: case ButtonSystemState.Exit: case ButtonSystemState.EnteringMode: diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 5403f7c702..21fc53be6e 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -79,6 +79,7 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.Exit: Background.FadeColour(Color4.White, 500, Easing.OutSine); break; + default: Background.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine); break; diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 2fac8de799..c4425fd5fe 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -221,6 +221,7 @@ namespace osu.Game.Screens.Play else selectionIndex--; return true; + case Key.Down: if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) selectionIndex = 0; @@ -240,6 +241,7 @@ namespace osu.Game.Screens.Play case GlobalAction.Back: BackAction.Invoke(); return true; + case GlobalAction.Select: SelectAction.Invoke(); return true; diff --git a/osu.Game/Screens/Play/KeyCounterMouse.cs b/osu.Game/Screens/Play/KeyCounterMouse.cs index 13dbe40a8b..95fa58e5c0 100644 --- a/osu.Game/Screens/Play/KeyCounterMouse.cs +++ b/osu.Game/Screens/Play/KeyCounterMouse.cs @@ -25,8 +25,10 @@ namespace osu.Game.Screens.Play { default: return button.ToString(); + case MouseButton.Left: return @"M1"; + case MouseButton.Right: return @"M2"; } diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 78ed742bfa..d2d56f8c40 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -174,6 +174,7 @@ namespace osu.Game.Screens.Play using (BeginDelayedSequence(1000)) scheduledHide = Schedule(() => State = Visibility.Hidden); break; + case Visibility.Hidden: this.FadeOut(1000, Easing.OutExpo); break; diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 8e7ea8f964..5c334b126c 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -38,10 +38,13 @@ namespace osu.Game.Screens.Select.Carousel default: case SortMode.Artist: return string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.InvariantCultureIgnoreCase); + case SortMode.Title: return string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.InvariantCultureIgnoreCase); + case SortMode.Author: return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.InvariantCultureIgnoreCase); + case SortMode.Difficulty: return BeatmapSet.MaxStarDifficulty.CompareTo(otherSet.BeatmapSet.MaxStarDifficulty); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 5d8f4f0ec6..6ebd2d41cc 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -66,6 +66,7 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.NotSelected: InternalChildren.ForEach(c => c.State.Value = CarouselItemState.Collapsed); break; + case CarouselItemState.Selected: InternalChildren.ForEach(c => { diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 67e8282b76..045c682dc3 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -69,6 +69,7 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.Selected: updateSelected(item); break; + case CarouselItemState.NotSelected: case CarouselItemState.Collapsed: attemptSelection(); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 3c1b7cc831..9a07852e02 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -106,6 +106,7 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.NotSelected: Deselected(); break; + case CarouselItemState.Selected: Selected(); break; diff --git a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs b/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs index 0bcf1b1816..02f7f73399 100644 --- a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs +++ b/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs @@ -108,12 +108,14 @@ namespace osu.Game.Screens.Tournament speedTo(1000f, 200); tracker.FadeOut(100); break; + case ScrollState.Stopping: speedTo(0f, 2000); tracker.FadeIn(200); delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Stopped, 2300); break; + case ScrollState.Stopped: // Find closest to center if (!Children.Any()) @@ -155,6 +157,7 @@ namespace osu.Game.Screens.Tournament delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Idle, 10000); break; + case ScrollState.Idle: resetSelected(); diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 461dfed589..09f7e09961 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -26,9 +26,11 @@ namespace osu.Game.Skinning case @"Name": skin.SkinInfo.Name = pair.Value; break; + case @"Author": skin.SkinInfo.Creator = pair.Value; break; + case @"CursorExpand": skin.CursorExpand = pair.Value != "0"; break; @@ -42,6 +44,7 @@ namespace osu.Game.Skinning case "HitCirclePrefix": skin.HitCircleFont = pair.Value; break; + case "HitCircleOverlap": skin.HitCircleOverlap = int.Parse(pair.Value); break; diff --git a/osu.Game/Tests/Visual/ScrollingTestContainer.cs b/osu.Game/Tests/Visual/ScrollingTestContainer.cs index f2e03208fd..bdad3d278c 100644 --- a/osu.Game/Tests/Visual/ScrollingTestContainer.cs +++ b/osu.Game/Tests/Visual/ScrollingTestContainer.cs @@ -74,9 +74,11 @@ namespace osu.Game.Tests.Visual case ScrollVisualisationMethod.Constant: implementation = new ConstantScrollAlgorithm(); break; + case ScrollVisualisationMethod.Overlapping: implementation = new OverlappingScrollAlgorithm(ControlPoints); break; + case ScrollVisualisationMethod.Sequential: implementation = new SequentialScrollAlgorithm(ControlPoints); break; From 2b3c70b2d25ce80b7876e384c8a948ddfcade5de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Apr 2019 13:13:56 +0900 Subject: [PATCH 333/623] Refactor with constants and better method names --- osu.Game/Screens/Menu/LogoVisualisation.cs | 15 ++++++++------- osu.Game/Screens/Menu/MenuSideFlashes.cs | 14 +++++--------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 8501040ca7..8283bf7ea2 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -18,6 +18,7 @@ using osu.Game.Users; using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Screens.Menu { @@ -86,10 +87,8 @@ namespace osu.Game.Screens.Menu user = api.LocalUser.GetBoundCopy(); skin = skinManager.CurrentSkin.GetBoundCopy(); - user.ValueChanged += _ => changeColour(); - skin.ValueChanged += _ => changeColour(); - - changeColour(); + user.ValueChanged += _ => updateColour(); + skin.BindValueChanged(_ => updateColour(), true); } private void updateAmplitudes() @@ -119,12 +118,14 @@ namespace osu.Game.Screens.Menu Scheduler.AddDelayed(updateAmplitudes, time_between_updates); } - private void changeColour() + private void updateColour() { + Color4 defaultColour = Color4.White.Opacity(0.2f); + if (user.Value?.IsSupporter ?? false) - AccentColour = skin.Value.GetValue(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? new Color4(1, 1, 1, 0.2f); + AccentColour = skin.Value.GetValue(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? defaultColour; else - AccentColour = new Color4(1, 1, 1, 0.2f); + AccentColour = defaultColour; } protected override void LoadComplete() diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index af4fa40b14..95d0bf04b4 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -58,9 +58,6 @@ namespace osu.Game.Screens.Menu user = api.LocalUser.GetBoundCopy(); skin = skinManager.CurrentSkin.GetBoundCopy(); - user.ValueChanged += _ => changeColour(); - skin.ValueChanged += _ => changeColour(); - Children = new Drawable[] { leftBox = new Box @@ -88,7 +85,8 @@ namespace osu.Game.Screens.Menu } }; - changeColour(); + user.ValueChanged += _ => updateColour(); + skin.BindValueChanged(_ => updateColour(), true); } protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) @@ -109,14 +107,12 @@ namespace osu.Game.Screens.Menu .FadeOut(beatLength, Easing.In); } - private void changeColour() + private void updateColour() { - Color4 baseColour; + Color4 baseColour = colours.Blue; if (user.Value?.IsSupporter ?? false) - baseColour = skin.Value.GetValue(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? colours.Blue; - else - baseColour = colours.Blue; + baseColour = skin.Value.GetValue(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? baseColour; // linear colour looks better in this case, so let's use it for now. Color4 gradientDark = baseColour.Opacity(0).ToLinear(); From 2060bad3bc6212efca44429bcace0e23deac635d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Apr 2019 13:28:14 +0900 Subject: [PATCH 334/623] Try applying minimal inspection fixes for latest Rider EAP --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 4 ---- osu.Game/Scoring/Legacy/LegacyScoreParser.cs | 3 +++ osu.Game/Skinning/LegacySkinDecoder.cs | 10 ++-------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index c1aaa7767e..41bb740e46 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -292,7 +292,6 @@ namespace osu.Game.Rulesets.Osu.Replays { // We add intermediate frames for spinning / following a slider here. case Spinner spinner: - { Vector2 difference = startPosition - SPINNER_CENTRE; float radius = difference.Length; @@ -315,9 +314,7 @@ namespace osu.Game.Rulesets.Osu.Replays endFrame.Position = endPosition; break; - } case Slider slider: - { for (double j = FrameDelay; j < slider.Duration; j += FrameDelay) { Vector2 pos = slider.StackedPositionAt(j / slider.Duration); @@ -326,7 +323,6 @@ namespace osu.Game.Rulesets.Osu.Replays AddFrameToReplay(new OsuReplayFrame(slider.EndTime, new Vector2(slider.StackedEndPosition.X, slider.StackedEndPosition.Y), action)); break; - } } // We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed! diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs index d1649a6098..3491a5779a 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -145,6 +145,7 @@ namespace osu.Game.Scoring.Legacy score.Rank = ScoreRank.D; break; } + case 1: { int totalHits = count50 + count100 + count300 + countMiss; @@ -167,6 +168,7 @@ namespace osu.Game.Scoring.Legacy score.Rank = ScoreRank.D; break; } + case 2: { int totalHits = count50 + count100 + count300 + countMiss + countKatu; @@ -186,6 +188,7 @@ namespace osu.Game.Scoring.Legacy score.Rank = ScoreRank.D; break; } + case 3: { int totalHits = count50 + count100 + count300 + countMiss + countGeki + countKatu; diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 96a9116c51..a655c884be 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -16,12 +16,10 @@ namespace osu.Game.Skinning { line = StripComments(line); + var pair = SplitKeyVal(line); switch (section) { case Section.General: - { - var pair = SplitKeyVal(line); - switch (pair.Key) { case @"Name": @@ -36,11 +34,8 @@ namespace osu.Game.Skinning } break; - } - case Section.Fonts: - { - var pair = SplitKeyVal(line); + case Section.Fonts: switch (pair.Key) { case "HitCirclePrefix": @@ -52,7 +47,6 @@ namespace osu.Game.Skinning } break; - } } base.ParseLine(skin, section, line); From d8af5e1c5a01df26271c5d9fa50f080ac1660514 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Apr 2019 11:56:22 +0900 Subject: [PATCH 335/623] Update in-line with drawnode changes --- .../UI/Cursor/CursorTrail.cs | 76 ++++++++++--------- osu.Game/Graphics/Backgrounds/Triangles.cs | 62 ++++++++------- osu.Game/Rulesets/Mods/ModFlashlight.cs | 61 ++++++++------- osu.Game/Screens/Menu/LogoVisualisation.cs | 61 ++++++++------- 4 files changed, 139 insertions(+), 121 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 03dbf7ac63..4aec7c634e 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -43,22 +43,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly InputResampler resampler = new InputResampler(); - protected override DrawNode CreateDrawNode() => new TrailDrawNode(); - - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - TrailDrawNode tNode = (TrailDrawNode)node; - tNode.Shader = shader; - tNode.Texture = texture; - tNode.Size = size; - tNode.Time = time; - - for (int i = 0; i < parts.Length; ++i) - if (parts[i].InvalidationID > tNode.Parts[i].InvalidationID) - tNode.Parts[i] = parts[i]; - } + protected override DrawNode CreateDrawNode() => new TrailDrawNode(this); public CursorTrail() { @@ -167,33 +152,52 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private class TrailDrawNode : DrawNode { - public IShader Shader; - public Texture Texture; + protected new CursorTrail Source => (CursorTrail)base.Source; - public float Time; + private IShader shader; + private Texture texture; - public readonly TrailPart[] Parts = new TrailPart[max_sprites]; - public Vector2 Size; + private float time; + + private readonly TrailPart[] parts = new TrailPart[max_sprites]; + private Vector2 size; private readonly VertexBuffer vertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw); - public TrailDrawNode() + public TrailDrawNode(CursorTrail source) + : base(source) { for (int i = 0; i < max_sprites; i++) { - Parts[i].InvalidationID = 0; - Parts[i].WasUpdated = false; + parts[i].InvalidationID = 0; + parts[i].WasUpdated = false; + } + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + texture = Source.texture; + size = Source.size; + time = Source.time; + + for (int i = 0; i < Source.parts.Length; ++i) + { + if (Source.parts[i].InvalidationID > parts[i].InvalidationID) + parts[i] = Source.parts[i]; } } public override void Draw(Action vertexAction) { - Shader.GetUniform("g_FadeClock").UpdateValue(ref Time); + shader.GetUniform("g_FadeClock").UpdateValue(ref time); int updateStart = -1, updateEnd = 0; - for (int i = 0; i < Parts.Length; ++i) + for (int i = 0; i < parts.Length; ++i) { - if (Parts[i].WasUpdated) + if (parts[i].WasUpdated) { if (updateStart == -1) updateStart = i; @@ -202,22 +206,22 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor int start = i * 4; int end = start; - Vector2 pos = Parts[i].Position; - float time = Parts[i].Time; + Vector2 pos = parts[i].Position; + float localTime = parts[i].Time; - Texture.DrawQuad( - new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y), + texture.DrawQuad( + new Quad(pos.X - size.X / 2, pos.Y - size.Y / 2, size.X, size.Y), DrawColourInfo.Colour, null, v => vertexBuffer.Vertices[end++] = new TexturedTrailVertex { Position = v.Position, TexturePosition = v.TexturePosition, - Time = time + 1, + Time = localTime + 1, Colour = v.Colour, }); - Parts[i].WasUpdated = false; + parts[i].WasUpdated = false; } else if (updateStart != -1) { @@ -232,12 +236,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor base.Draw(vertexAction); - Shader.Bind(); + shader.Bind(); - Texture.TextureGL.Bind(); + texture.TextureGL.Bind(); vertexBuffer.Draw(); - Shader.Unbind(); + shader.Unbind(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index c67d779c37..e2c7693700 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -178,64 +178,68 @@ namespace osu.Game.Graphics.Backgrounds /// The colour. protected virtual Color4 CreateTriangleShade() => Interpolation.ValueAt(RNG.NextSingle(), ColourDark, ColourLight, 0, 1); - protected override DrawNode CreateDrawNode() => new TrianglesDrawNode(); - - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - var trianglesNode = (TrianglesDrawNode)node; - - trianglesNode.Shader = shader; - trianglesNode.Texture = texture; - trianglesNode.Size = DrawSize; - - trianglesNode.Parts.Clear(); - trianglesNode.Parts.AddRange(parts); - } + protected override DrawNode CreateDrawNode() => new TrianglesDrawNode(this); private class TrianglesDrawNode : DrawNode { - public IShader Shader; - public Texture Texture; + protected new Triangles Source => (Triangles)base.Source; - public readonly List Parts = new List(); - public Vector2 Size; + private IShader shader; + private Texture texture; + + private readonly List parts = new List(); + private Vector2 size; private readonly LinearBatch vertexBatch = new LinearBatch(100 * 3, 10, PrimitiveType.Triangles); + public TrianglesDrawNode(Triangles source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + texture = Source.texture; + size = Source.DrawSize; + + parts.Clear(); + parts.AddRange(Source.parts); + } + public override void Draw(Action vertexAction) { base.Draw(vertexAction); - Shader.Bind(); - Texture.TextureGL.Bind(); + shader.Bind(); + texture.TextureGL.Bind(); Vector2 localInflationAmount = edge_smoothness * DrawInfo.MatrixInverse.ExtractScale().Xy; - foreach (TriangleParticle particle in Parts) + foreach (TriangleParticle particle in parts) { var offset = triangle_size * new Vector2(particle.Scale * 0.5f, particle.Scale * 0.866f); - var size = new Vector2(2 * offset.X, offset.Y); var triangle = new Triangle( - Vector2Extensions.Transform(particle.Position * Size, DrawInfo.Matrix), - Vector2Extensions.Transform(particle.Position * Size + offset, DrawInfo.Matrix), - Vector2Extensions.Transform(particle.Position * Size + new Vector2(-offset.X, offset.Y), DrawInfo.Matrix) + Vector2Extensions.Transform(particle.Position * size, DrawInfo.Matrix), + Vector2Extensions.Transform(particle.Position * size + offset, DrawInfo.Matrix), + Vector2Extensions.Transform(particle.Position * size + new Vector2(-offset.X, offset.Y), DrawInfo.Matrix) ); ColourInfo colourInfo = DrawColourInfo.Colour; colourInfo.ApplyChild(particle.Colour); - Texture.DrawTriangle( + texture.DrawTriangle( triangle, colourInfo, null, vertexBatch.AddAction, - Vector2.Divide(localInflationAmount, size)); + Vector2.Divide(localInflationAmount, new Vector2(2 * offset.X, offset.Y))); } - Shader.Unbind(); + shader.Unbind(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 0ad99d13ff..31d3720cee 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -64,24 +64,12 @@ namespace osu.Game.Rulesets.Mods internal BindableInt Combo; private IShader shader; - protected override DrawNode CreateDrawNode() => new FlashlightDrawNode(); + protected override DrawNode CreateDrawNode() => new FlashlightDrawNode(this); public override bool RemoveCompletedTransforms => false; public List Breaks; - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - var flashNode = (FlashlightDrawNode)node; - - flashNode.Shader = shader; - flashNode.ScreenSpaceDrawQuad = ScreenSpaceDrawQuad; - flashNode.FlashlightPosition = Vector2Extensions.Transform(FlashlightPosition, DrawInfo.Matrix); - flashNode.FlashlightSize = Vector2Extensions.Transform(FlashlightSize, DrawInfo.Matrix); - } - [BackgroundDependencyLoader] private void load(ShaderManager shaderManager) { @@ -136,27 +124,44 @@ namespace osu.Game.Rulesets.Mods Invalidate(Invalidation.DrawNode); } } - } - private class FlashlightDrawNode : DrawNode - { - public IShader Shader; - public Quad ScreenSpaceDrawQuad; - public Vector2 FlashlightPosition; - public Vector2 FlashlightSize; - - public override void Draw(Action vertexAction) + private class FlashlightDrawNode : DrawNode { - base.Draw(vertexAction); + protected new Flashlight Source => (Flashlight)base.Source; - Shader.Bind(); + private IShader shader; + private Quad screenSpaceDrawQuad; + private Vector2 flashlightPosition; + private Vector2 flashlightSize; - Shader.GetUniform("flashlightPos").UpdateValue(ref FlashlightPosition); - Shader.GetUniform("flashlightSize").UpdateValue(ref FlashlightSize); + public FlashlightDrawNode(Flashlight source) + : base(source) + { + } - Texture.WhitePixel.DrawQuad(ScreenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); + public override void ApplyState() + { + base.ApplyState(); - Shader.Unbind(); + shader = Source.shader; + screenSpaceDrawQuad = Source.ScreenSpaceDrawQuad; + flashlightPosition = Vector2Extensions.Transform(Source.FlashlightPosition, DrawInfo.Matrix); + flashlightSize = Vector2Extensions.Transform(Source.FlashlightSize, DrawInfo.Matrix); + } + + public override void Draw(Action vertexAction) + { + base.Draw(vertexAction); + + shader.Bind(); + + shader.GetUniform("flashlightPos").UpdateValue(ref flashlightPosition); + shader.GetUniform("flashlightSize").UpdateValue(ref flashlightSize); + + Texture.WhitePixel.DrawQuad(screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); + + shader.Unbind(); + } } } } diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 8283bf7ea2..9eab588a57 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -150,62 +150,67 @@ namespace osu.Game.Screens.Menu Invalidate(Invalidation.DrawNode, shallPropagate: false); } - protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(); - - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - var visNode = (VisualisationDrawNode)node; - - visNode.Shader = shader; - visNode.Texture = texture; - visNode.Size = DrawSize.X; - visNode.Colour = AccentColour; - visNode.AudioData = frequencyAmplitudes; - } + protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(this); private class VisualisationDrawNode : DrawNode { - public IShader Shader; - public Texture Texture; + protected new LogoVisualisation Source => (LogoVisualisation)base.Source; + + private IShader shader; + private Texture texture; //Asuming the logo is a circle, we don't need a second dimension. - public float Size; + private float size; - public Color4 Colour; - public float[] AudioData; + private Color4 colour; + private float[] audioData; private readonly QuadBatch vertexBatch = new QuadBatch(100, 10); + public VisualisationDrawNode(LogoVisualisation source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + texture = Source.texture; + size = Source.DrawSize.X; + colour = Source.AccentColour; + audioData = Source.frequencyAmplitudes; + } + public override void Draw(Action vertexAction) { base.Draw(vertexAction); - Shader.Bind(); - Texture.TextureGL.Bind(); + shader.Bind(); + texture.TextureGL.Bind(); Vector2 inflation = DrawInfo.MatrixInverse.ExtractScale().Xy; ColourInfo colourInfo = DrawColourInfo.Colour; - colourInfo.ApplyChild(Colour); + colourInfo.ApplyChild(colour); - if (AudioData != null) + if (audioData != null) { for (int j = 0; j < visualiser_rounds; j++) { for (int i = 0; i < bars_per_visualiser; i++) { - if (AudioData[i] < amplitude_dead_zone) + if (audioData[i] < amplitude_dead_zone) continue; float rotation = MathHelper.DegreesToRadians(i / (float)bars_per_visualiser * 360 + j * 360 / visualiser_rounds); float rotationCos = (float)Math.Cos(rotation); float rotationSin = (float)Math.Sin(rotation); //taking the cos and sin to the 0..1 range - var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * Size; + var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * size; - var barSize = new Vector2(Size * (float)Math.Sqrt(2 * (1 - Math.Cos(MathHelper.DegreesToRadians(360f / bars_per_visualiser)))) / 2f, bar_length * AudioData[i]); + var barSize = new Vector2(size * (float)Math.Sqrt(2 * (1 - Math.Cos(MathHelper.DegreesToRadians(360f / bars_per_visualiser)))) / 2f, bar_length * audioData[i]); //The distance between the position and the sides of the bar. var bottomOffset = new Vector2(-rotationSin * barSize.X / 2, rotationCos * barSize.X / 2); //The distance between the bottom side of the bar and the top side. @@ -218,7 +223,7 @@ namespace osu.Game.Screens.Menu Vector2Extensions.Transform(barPosition + bottomOffset + amplitudeOffset, DrawInfo.Matrix) ); - Texture.DrawQuad( + texture.DrawQuad( rectangle, colourInfo, null, @@ -229,7 +234,7 @@ namespace osu.Game.Screens.Menu } } - Shader.Unbind(); + shader.Unbind(); } protected override void Dispose(bool isDisposing) From a56e29347fdcdad1618c139b6fe47f1bed276f32 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Apr 2019 14:51:28 +0900 Subject: [PATCH 336/623] Adjust namespaces --- osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs | 1 + osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs | 2 +- .../Objects/Drawables/DrawableHoldNoteTick.cs | 1 + osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs | 2 +- osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs | 1 + osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs | 1 + osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs | 1 + osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs | 1 + osu.Game.Rulesets.Mania/UI/HitExplosion.cs | 1 + .../Objects/Drawables/Connections/FollowPoint.cs | 1 + osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs | 1 + .../Objects/Drawables/Pieces/SpinnerBackground.cs | 1 + osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs | 1 + osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs | 1 + osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs | 1 + osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs | 1 + osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 1 + osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 1 + osu.Game/Graphics/Containers/ConstrainedIconContainer.cs | 1 + osu.Game/Graphics/Containers/WaveContainer.cs | 1 + osu.Game/Graphics/Cursor/OsuTooltipContainer.cs | 2 +- osu.Game/Graphics/UserInterface/DialogButton.cs | 1 + osu.Game/Graphics/UserInterface/Nub.cs | 1 + osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs | 1 + osu.Game/Graphics/UserInterface/OsuContextMenu.cs | 2 +- osu.Game/Graphics/UserInterface/TwoLayerButton.cs | 1 + osu.Game/Online/Leaderboards/LeaderboardScore.cs | 1 + osu.Game/Overlays/AccountCreationOverlay.cs | 1 + osu.Game/Overlays/BeatmapSet/AuthorInfo.cs | 1 + osu.Game/Overlays/BeatmapSet/Header.cs | 1 + osu.Game/Overlays/BeatmapSet/Info.cs | 1 + osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs | 1 + osu.Game/Overlays/BeatmapSetOverlay.cs | 1 + osu.Game/Overlays/Chat/ChatLine.cs | 1 + osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs | 1 + osu.Game/Overlays/Dialog/PopupDialog.cs | 1 + osu.Game/Overlays/Direct/DirectPanel.cs | 1 + osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 1 + osu.Game/Overlays/MedalOverlay.cs | 1 + osu.Game/Overlays/Music/CollectionsDropdown.cs | 2 +- osu.Game/Overlays/Music/PlaylistOverlay.cs | 1 + osu.Game/Overlays/MusicController.cs | 1 + osu.Game/Overlays/Notifications/Notification.cs | 1 + osu.Game/Overlays/OnScreenDisplay.cs | 1 + osu.Game/Overlays/Profile/ProfileHeader.cs | 1 + osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs | 1 + osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs | 1 + osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs | 1 + osu.Game/Overlays/Settings/SettingsItem.cs | 1 + osu.Game/Overlays/Social/SocialPanel.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 1 + osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs | 1 + osu.Game/Overlays/Toolbar/ToolbarUserButton.cs | 2 +- osu.Game/Overlays/UserProfileOverlay.cs | 2 +- .../Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs | 1 + osu.Game/Screens/Menu/Button.cs | 1 + osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs | 1 + osu.Game/Screens/Play/GameplayMenuOverlay.cs | 1 + osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs | 1 + osu.Game/Screens/Ranking/ResultModeButton.cs | 1 + osu.Game/Screens/Ranking/Results.cs | 1 + osu.Game/Screens/Ranking/ResultsPage.cs | 1 + osu.Game/Screens/Select/BeatmapInfoWedge.cs | 1 + osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 1 + osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs | 1 + osu.Game/Users/UserPanel.cs | 1 + 67 files changed, 67 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 0dc3f73404..625e857156 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; 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.MathUtils; using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs index 2e18c5f2ad..b9b6d5b924 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs @@ -3,7 +3,7 @@ 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.Game.Graphics; using osuTK.Graphics; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index f2be8d614c..9a29273282 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -7,6 +7,7 @@ using osuTK.Graphics; 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.Game.Rulesets.Scoring; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 82a34224f4..afd7777861 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -5,7 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osuTK.Graphics; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Scoring; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs index 2baf1ad520..b515abcc86 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs @@ -7,6 +7,7 @@ using osuTK.Graphics; 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.Game.Graphics; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs index b146a33fd3..1d25a0c966 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs @@ -4,6 +4,7 @@ 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.Game.Graphics; using osuTK.Graphics; diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index 89e8cd9b5a..a0d713067d 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.UI; diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs index 03b55cbead..85880222d7 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Game.Graphics; diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs index f5a9978f77..0ec1fc38d2 100644 --- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs @@ -4,6 +4,7 @@ using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; using osu.Game.Rulesets.Mania.Objects.Drawables; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 3c64fe57d4..aacf3ee08d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -6,6 +6,7 @@ using osuTK.Graphics; 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.Game.Skinning; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs index 93ac8748dd..84034d3ee9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs @@ -4,6 +4,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Graphics.Sprites; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs index c982f53c2b..77228e28af 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs @@ -4,6 +4,7 @@ using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs index f47617bcf6..9219fab830 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index ba6b27f743..0cc7858f5a 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs index 53dbe5d08e..b7db819717 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs @@ -9,6 +9,7 @@ using osu.Game.Graphics.Backgrounds; using osuTK.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Effects; namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { diff --git a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs index bed2c554ec..e80b463481 100644 --- a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs @@ -5,6 +5,7 @@ using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index dbff5270d2..88d7f9a751 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; 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.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index f0f58b9b5d..5bb2767438 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; 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.Graphics.Sprites; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs index c1811f37d5..f5ef291c8f 100644 --- a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs +++ b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osuTK; namespace osu.Game.Graphics.Containers diff --git a/osu.Game/Graphics/Containers/WaveContainer.cs b/osu.Game/Graphics/Containers/WaveContainer.cs index 48131d7e86..464682a0ad 100644 --- a/osu.Game/Graphics/Containers/WaveContainer.cs +++ b/osu.Game/Graphics/Containers/WaveContainer.cs @@ -5,6 +5,7 @@ using System; 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 osuTK.Graphics; diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index 4e0ce4a3e1..7bb6396041 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -6,8 +6,8 @@ using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index dbbe5b4258..b50bf14bab 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Effects; using osu.Game.Graphics.Containers; using osu.Framework.Input.Events; diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index 1f5195eaf1..82b09e0821 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -8,6 +8,7 @@ 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.Graphics.UserInterface; diff --git a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs index d64068f74c..a8041c79fc 100644 --- a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; 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.Graphics.Containers; diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs index c72d11b57e..cea8427296 100644 --- a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs @@ -5,7 +5,7 @@ using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; namespace osu.Game.Graphics.UserInterface { diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index 9911a7c368..36a9aca412 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Beatmaps.ControlPoints; using osu.Framework.Audio.Track; using System; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index da5cc76060..e4458181f6 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index e8e44c206e..0d376257e0 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; 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.Screens; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index 18de87e7b4..abe954aa80 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -11,6 +11,7 @@ using osuTK; using osuTK.Graphics; using osu.Game.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 95cf9e9d04..3659769752 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -6,6 +6,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 4d974a0b63..44827f0a0c 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index ac4485a410..44b0d9e4f6 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; 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.Graphics; diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index c49268bc16..82bac71f5e 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Beatmaps; diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 908ec5f026..66a6672ab1 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs index e1f29a40e4..5e506f1e4b 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; 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.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index ede2f34574..a5f5ea37eb 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -6,6 +6,7 @@ using System.Linq; 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.Graphics.Sprites; using osu.Framework.Input.Events; diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 2b509f370e..f413dc3771 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 8313dac50a..58d6cd10d2 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -8,6 +8,7 @@ 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.Bindings; using osu.Framework.Input.Events; diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index a5703eba92..6d82db5603 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -19,6 +19,7 @@ using osu.Framework.Graphics.Textures; using osuTK.Input; using osu.Framework.Graphics.Shapes; using System; +using osu.Framework.Graphics.Effects; using osu.Framework.Input.Events; using osu.Framework.MathUtils; diff --git a/osu.Game/Overlays/Music/CollectionsDropdown.cs b/osu.Game/Overlays/Music/CollectionsDropdown.cs index aa93e349e8..4f59b053b6 100644 --- a/osu.Game/Overlays/Music/CollectionsDropdown.cs +++ b/osu.Game/Overlays/Music/CollectionsDropdown.cs @@ -6,7 +6,7 @@ using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 8cbea63fe3..949090e8b8 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index ce2137346f..307fac11df 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 7abff9252f..711b3a1eef 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Graphics; using osuTK; using osuTK.Graphics; diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index 5e45fbf081..9198455bf7 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics; using osuTK; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Transforms; using osu.Framework.Threading; using osu.Game.Configuration; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 28877c21f0..46b0726123 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -20,6 +20,7 @@ using osu.Game.Overlays.Profile.Components; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; using Humanizer; +using osu.Framework.Graphics.Effects; namespace osu.Game.Overlays.Profile { diff --git a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs index a93fefdd75..23fe6e9cd5 100644 --- a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs +++ b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; 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.Graphics; diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index 3c69082e9d..aeea5118a7 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -7,6 +7,7 @@ using osuTK.Graphics; 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.Graphics; diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 078c01ce92..0dbc96998a 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -16,6 +16,7 @@ using System.ComponentModel; using osu.Game.Graphics; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 02e9d48f40..e970ff6211 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; diff --git a/osu.Game/Overlays/Social/SocialPanel.cs b/osu.Game/Overlays/Social/SocialPanel.cs index 738f940484..555527670a 100644 --- a/osu.Game/Overlays/Social/SocialPanel.cs +++ b/osu.Game/Overlays/Social/SocialPanel.cs @@ -5,7 +5,7 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Input.Events; using osu.Game.Users; diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 71374d5180..2b2b19b73a 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs index f729810fbc..87b18ba9f4 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Rulesets; using osuTK.Graphics; diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index d01eab4dab..ebfa6706d4 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osuTK; using osuTK.Input; using osuTK.Graphics; diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 356ffa5180..c9e49a09f4 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -4,7 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Graphics; using osu.Game.Online.API; using osu.Game.Users; diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 48ce055975..0d33a8b9ef 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -5,7 +5,7 @@ using System.Linq; using osu.Framework.Allocation; 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.Graphics.UserInterface; using osu.Game.Graphics; diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs index 1ad69afe91..70c0cf623e 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; 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.Graphics.Sprites; using osu.Framework.Input.Events; diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index 794fc093d3..611dd66ab3 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -16,6 +16,7 @@ using osuTK.Input; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; diff --git a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs index dce597b276..6ec8f2bfe5 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 2fac8de799..ae50e0898a 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -19,6 +19,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using Humanizer; +using osu.Framework.Graphics.Effects; namespace osu.Game.Screens.Play { diff --git a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs index 8f09c2b2bf..315bc27a79 100644 --- a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osuTK; diff --git a/osu.Game/Screens/Ranking/ResultModeButton.cs b/osu.Game/Screens/Ranking/ResultModeButton.cs index 109d0195db..1383511241 100644 --- a/osu.Game/Screens/Ranking/ResultModeButton.cs +++ b/osu.Game/Screens/Ranking/ResultModeButton.cs @@ -6,6 +6,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osuTK; diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index dafb4c0aad..bebeaee00a 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Screens/Ranking/ResultsPage.cs b/osu.Game/Screens/Ranking/ResultsPage.cs index 1b17dda563..8776c599dd 100644 --- a/osu.Game/Screens/Ranking/ResultsPage.cs +++ b/osu.Game/Screens/Ranking/ResultsPage.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; 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.Game.Beatmaps; using osu.Game.Graphics; diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index b2e08aeefd..670b5ca62c 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -22,6 +22,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Rulesets; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 3c1b7cc831..4402b25c9e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -7,6 +7,7 @@ using osu.Framework.Audio.Sample; 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.Framework.MathUtils; diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs index 0f1f49bd85..410102b607 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs @@ -4,6 +4,7 @@ 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.Graphics.Sprites; using osu.Framework.Input.Events; diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 1f62111a4e..fc7a544433 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -16,6 +16,7 @@ using osu.Game.Overlays; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; using osu.Game.Overlays.Profile.Header; From f1952c08162ff088194ab0d19a365c57871f6553 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Apr 2019 19:55:24 +0900 Subject: [PATCH 337/623] Update font awesome usage --- osu.Desktop/Overlays/VersionManager.cs | 2 +- osu.Desktop/Updater/SimpleUpdateManager.cs | 2 +- osu.Desktop/Updater/SquirrelUpdateManager.cs | 2 +- osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs | 6 +++--- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs | 4 ++-- osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs | 6 +++--- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- .../Objects/Drawables/DrawableRepeatPoint.cs | 2 +- .../Objects/Drawables/DrawableSpinner.cs | 2 +- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs | 6 +++--- .../Objects/Drawables/Pieces/SwellSymbolPiece.cs | 2 +- .../SongSelect/TestCaseBeatmapOptionsOverlay.cs | 8 ++++---- .../UserInterface/TestCaseDialogOverlay.cs | 4 ++-- .../Visual/UserInterface/TestCasePopupDialog.cs | 2 +- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 2 +- .../Graphics/Containers/LinkFlowContainer.cs | 2 +- .../Graphics/UserInterface/BreadcrumbControl.cs | 2 +- .../Graphics/UserInterface/ExternalLinkButton.cs | 2 +- .../Graphics/UserInterface/LoadingAnimation.cs | 4 ++-- osu.Game/Graphics/UserInterface/OsuDropdown.cs | 4 ++-- .../Graphics/UserInterface/OsuPasswordTextBox.cs | 2 +- osu.Game/Graphics/UserInterface/OsuTabControl.cs | 2 +- .../UserInterface/OsuTabControlCheckbox.cs | 6 +++--- osu.Game/Graphics/UserInterface/SearchTextBox.cs | 2 +- osu.Game/Graphics/UserInterface/StarCounter.cs | 2 +- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 6 +++--- .../Online/Leaderboards/MessagePlaceholder.cs | 2 +- .../Leaderboards/RetrievalFailurePlaceholder.cs | 2 +- .../Online/Multiplayer/GameTypes/GameTypeTag.cs | 2 +- .../Multiplayer/GameTypes/GameTypeTagTeam.cs | 4 ++-- .../Multiplayer/GameTypes/GameTypeTimeshift.cs | 2 +- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Overlays/BeatmapSet/BasicStats.cs | 10 +++++----- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 4 ++-- .../BeatmapSet/Buttons/DownloadButton.cs | 2 +- .../BeatmapSet/Buttons/FavouriteButton.cs | 6 +++--- osu.Game/Overlays/Chat/ExternalLinkDialog.cs | 2 +- .../Overlays/Chat/Selection/ChannelListItem.cs | 4 ++-- osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs | 2 +- osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs | 2 +- .../Overlays/Chat/Tabs/PrivateChannelTabItem.cs | 2 +- osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs | 2 +- osu.Game/Overlays/Dialog/PopupDialog.cs | 2 +- osu.Game/Overlays/Direct/DirectGridPanel.cs | 8 ++++---- osu.Game/Overlays/Direct/DirectListPanel.cs | 8 ++++---- osu.Game/Overlays/Direct/DownloadButton.cs | 4 ++-- osu.Game/Overlays/Direct/PlayButton.cs | 4 ++-- .../KeyBinding/GlobalKeyBindingsSection.cs | 2 +- osu.Game/Overlays/KeyBindingOverlay.cs | 2 +- osu.Game/Overlays/Music/PlaylistItem.cs | 2 +- osu.Game/Overlays/MusicController.cs | 12 ++++++------ osu.Game/Overlays/Notifications/Notification.cs | 2 +- .../ProgressCompletionNotification.cs | 2 +- .../Overlays/Notifications/SimpleNotification.cs | 2 +- .../Overlays/Profile/Header/SupporterIcon.cs | 2 +- osu.Game/Overlays/Profile/ProfileHeader.cs | 16 ++++++++-------- .../SearchableList/DisplayStyleControl.cs | 4 ++-- .../Overlays/Settings/Sections/AudioSection.cs | 2 +- .../Overlays/Settings/Sections/DebugSection.cs | 2 +- .../Settings/Sections/GameplaySection.cs | 2 +- .../Settings/Sections/General/LoginSettings.cs | 2 +- .../Overlays/Settings/Sections/GeneralSection.cs | 2 +- .../Settings/Sections/GraphicsSection.cs | 2 +- .../Overlays/Settings/Sections/InputSection.cs | 2 +- .../Maintenance/DeleteAllBeatmapsDialog.cs | 2 +- .../Settings/Sections/MaintenanceSection.cs | 2 +- .../Overlays/Settings/Sections/OnlineSection.cs | 2 +- .../Overlays/Settings/Sections/SkinSection.cs | 2 +- osu.Game/Overlays/Social/Header.cs | 2 +- osu.Game/Overlays/Toolbar/Toolbar.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarChatButton.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs | 2 +- .../Toolbar/ToolbarNotificationButton.cs | 2 +- .../Overlays/Toolbar/ToolbarSettingsButton.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs | 2 +- osu.Game/Overlays/Volume/MuteButton.cs | 2 +- osu.Game/Rulesets/Mods/Mod.cs | 2 +- osu.Game/Rulesets/Mods/ModDaycore.cs | 2 +- osu.Game/Rulesets/Mods/ModWindDown.cs | 2 +- osu.Game/Rulesets/Mods/ModWindUp.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 2 +- .../Screens/Edit/Components/PlaybackControl.cs | 4 ++-- .../Compose/Components/BeatDivisorControl.cs | 4 ++-- .../Compose/Components/Timeline/TimelineArea.cs | 4 ++-- osu.Game/Screens/Menu/ButtonSystem.cs | 8 ++++---- osu.Game/Screens/Menu/Disclaimer.cs | 4 ++-- .../Match/Components/MatchLeaderboardScore.cs | 6 +++--- .../Ranking/Types/RoomLeaderboardPageInfo.cs | 2 +- osu.Game/Screens/Play/Break/BreakArrows.cs | 8 ++++---- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 2 +- .../Play/PlayerSettings/PlayerSettingsGroup.cs | 2 +- osu.Game/Screens/Play/SkipOverlay.cs | 6 +++--- .../Ranking/Types/LocalLeaderboardPageInfo.cs | 2 +- .../Ranking/Types/ScoreOverviewPageInfo.cs | 2 +- osu.Game/Screens/ScreenWhiteBox.cs | 2 +- .../Screens/Select/BeatmapClearScoresDialog.cs | 2 +- osu.Game/Screens/Select/BeatmapDeleteDialog.cs | 2 +- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 6 +++--- osu.Game/Screens/Select/ImportFromStablePopup.cs | 2 +- .../Select/Options/BeatmapOptionsButton.cs | 2 +- osu.Game/Screens/Select/PlaySongSelect.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 6 +++--- osu.Game/Users/UserPanel.cs | 2 +- 107 files changed, 173 insertions(+), 173 deletions(-) diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index 2998e08715..711ffa7d9e 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -110,7 +110,7 @@ namespace osu.Desktop.Overlays public UpdateCompleteNotification(string version, Action openUrl = null) { Text = $"You are now running osu!lazer {version}.\nClick to see what's new!"; - Icon = FontAwesome.CheckSquare; + Icon = FontAwesome.Solid.CheckSquare; Activated = delegate { openUrl?.Invoke($"https://osu.ppy.sh/home/changelog/lazer/{version}"); diff --git a/osu.Desktop/Updater/SimpleUpdateManager.cs b/osu.Desktop/Updater/SimpleUpdateManager.cs index 1cb47d6b58..0600804339 100644 --- a/osu.Desktop/Updater/SimpleUpdateManager.cs +++ b/osu.Desktop/Updater/SimpleUpdateManager.cs @@ -54,7 +54,7 @@ namespace osu.Desktop.Updater { Text = $"A newer release of osu! has been found ({version} → {latest.TagName}).\n\n" + "Click here to download the new version, which can be installed over the top of your existing installation", - Icon = FontAwesome.Upload, + Icon = FontAwesome.Solid.Upload, Activated = () => { host.OpenUrlExternally(getBestUrl(latest)); diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 6ebadeb4e9..5fed2a63e1 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -159,7 +159,7 @@ namespace osu.Desktop.Updater { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.Upload, + Icon = FontAwesome.Solid.Upload, Colour = Color4.White, Size = new Vector2(20), } diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs index d55f3ff159..18cc300ff9 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs @@ -23,19 +23,19 @@ namespace osu.Game.Rulesets.Catch.Beatmaps { Name = @"Fruit Count", Content = fruits.ToString(), - Icon = FontAwesome.CircleOutline + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Juice Stream Count", Content = juiceStreams.ToString(), - Icon = FontAwesome.Circle + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Banana Shower Count", Content = bananaShowers.ToString(), - Icon = FontAwesome.Circle + Icon = FontAwesome.Regular.Circle } }; } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs index 184cbf339d..dc24a344e9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs @@ -42,13 +42,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps { Name = @"Note Count", Content = notes.ToString(), - Icon = FontAwesome.CircleOutline + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Hold Note Count", Content = holdnotes.ToString(), - Icon = FontAwesome.Circle + Icon = FontAwesome.Regular.Circle }, }; } diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs index 7099758e3d..491d82b89e 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs @@ -23,19 +23,19 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { Name = @"Circle Count", Content = circles.ToString(), - Icon = FontAwesome.CircleOutline + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Slider Count", Content = sliders.ToString(), - Icon = FontAwesome.Circle + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Spinner Count", Content = spinners.ToString(), - Icon = FontAwesome.Circle + Icon = FontAwesome.Regular.Circle } }; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 7f94b68cc0..f3c7939a94 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "Play with blinds on your screen."; public override string Acronym => "BL"; - public override IconUsage Icon => FontAwesome.Adjust; + public override IconUsage Icon => FontAwesome.Solid.Adjust; public override ModType Type => ModType.DifficultyIncrease; public override bool Ranked => false; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs index 2e93815ef0..35a5992e25 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Acronym => "GR"; - public override IconUsage Icon => FontAwesome.ArrowsV; + public override IconUsage Icon => FontAwesome.Solid.ArrowsAltV; public override ModType Type => ModType.Fun; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 31195b7878..9b079895fa 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Name => "Transform"; public override string Acronym => "TR"; - public override IconUsage Icon => FontAwesome.Arrows; + public override IconUsage Icon => FontAwesome.Solid.ArrowsAlt; public override ModType Type => ModType.Fun; public override string Description => "Everything rotates. EVERYTHING."; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index bdc2873d8d..17fcd03dd5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Name => "Wiggle"; public override string Acronym => "WG"; - public override IconUsage Icon => FontAwesome.Certificate; + public override IconUsage Icon => FontAwesome.Solid.Certificate; public override ModType Type => ModType.Fun; public override string Description => "They just won't stay still..."; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index a6714690b1..edf2d90c08 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables new SkinnableDrawable("Play/osu/reversearrow", _ => new SpriteIcon { RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.ChevronRight + Icon = FontAwesome.Solid.ChevronRight }, restrictSize: false) }; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 3a6ff3fcf8..ab4935e350 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(48), - Icon = FontAwesome.Asterisk, + Icon = FontAwesome.Solid.Asterisk, Shadow = false, }, } diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs index 4149da67c7..b595f43fbb 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs @@ -23,19 +23,19 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { Name = @"Hit Count", Content = hits.ToString(), - Icon = FontAwesome.CircleOutline + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Drumroll Count", Content = drumrolls.ToString(), - Icon = FontAwesome.Circle + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Swell Count", Content = swells.ToString(), - Icon = FontAwesome.Circle + Icon = FontAwesome.Regular.Circle } }; } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs index 569ac96c15..0ed9923924 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces new SpriteIcon { RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.Asterisk, + Icon = FontAwesome.Solid.Asterisk, Shadow = false } }; diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapOptionsOverlay.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapOptionsOverlay.cs index 3cb480bab8..7d09debbd6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapOptionsOverlay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapOptionsOverlay.cs @@ -16,10 +16,10 @@ namespace osu.Game.Tests.Visual.SongSelect { var overlay = new BeatmapOptionsOverlay(); - overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.TimesCircleOutline, Color4.Purple, null, Key.Number1); - overlay.AddButton(@"Clear", @"local scores", FontAwesome.Eraser, Color4.Purple, null, Key.Number2); - overlay.AddButton(@"Edit", @"Beatmap", FontAwesome.Pencil, Color4.Yellow, null, Key.Number3); - overlay.AddButton(@"Delete", @"Beatmap", FontAwesome.Trash, Color4.Pink, null, Key.Number4, float.MaxValue); + overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, Color4.Purple, null, Key.Number1); + overlay.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, Color4.Purple, null, Key.Number2); + overlay.AddButton(@"Edit", @"Beatmap", FontAwesome.Solid.PencilAlt, Color4.Yellow, null, Key.Number3); + overlay.AddButton(@"Delete", @"Beatmap", FontAwesome.Solid.Trash, Color4.Pink, null, Key.Number4, float.MaxValue); Add(overlay); diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseDialogOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseDialogOverlay.cs index 98d6f3a149..8964d20564 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseDialogOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseDialogOverlay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("dialog #1", () => overlay.Push(new PopupDialog { - Icon = FontAwesome.TrashOutline, + Icon = FontAwesome.Regular.TrashAlt, HeaderText = @"Confirm deletion of", BodyText = @"Ayase Rie - Yuima-ru*World TVver.", Buttons = new PopupDialogButton[] @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("dialog #2", () => overlay.Push(new PopupDialog { - Icon = FontAwesome.Gear, + Icon = FontAwesome.Solid.Cog, HeaderText = @"What do you want to do with", BodyText = "Camellia as \"Bang Riot\" - Blastix Riotz", Buttons = new PopupDialogButton[] diff --git a/osu.Game.Tests/Visual/UserInterface/TestCasePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestCasePopupDialog.cs index bcba7e6811..2f01f593c7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCasePopupDialog.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCasePopupDialog.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.UserInterface { RelativeSizeAxes = Axes.Both, State = Framework.Graphics.Containers.Visibility.Visible, - Icon = FontAwesome.AssistiveListeningSystems, + Icon = FontAwesome.Solid.AssistiveListeningSystems, HeaderText = @"This is a test popup", BodyText = "I can say lots of stuff and even wrap my words!", Buttons = new PopupDialogButton[] diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index f0f58b9b5d..1be7411bec 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -60,7 +60,7 @@ namespace osu.Game.Beatmaps.Drawables Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) - Icon = ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.QuestionCircleOutline } + Icon = ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } } }; } diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 204c83aac9..dace873b92 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Graphics.Containers showNotImplementedError = () => notifications?.Post(new SimpleNotification { Text = @"This link type is not yet supported!", - Icon = FontAwesome.LifeSaver, + Icon = FontAwesome.Solid.LifeRing, }); } diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs index 8eb9b99f29..f5e57e5f27 100644 --- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs @@ -93,7 +93,7 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.CentreRight, Origin = Anchor.CentreLeft, Size = new Vector2(item_chevron_size), - Icon = FontAwesome.ChevronRight, + Icon = FontAwesome.Solid.ChevronRight, Margin = new MarginPadding { Left = padding }, Alpha = 0f, }); diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 14328930ce..8c00cae08a 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -26,7 +26,7 @@ namespace osu.Game.Graphics.UserInterface Size = new Vector2(12); InternalChild = new SpriteIcon { - Icon = FontAwesome.ExternalLink, + Icon = FontAwesome.Solid.ExternalLinkAlt, RelativeSizeAxes = Axes.Both }; } diff --git a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs index bb92d8a2a9..5a8a0da135 100644 --- a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs +++ b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs @@ -37,14 +37,14 @@ namespace osu.Game.Graphics.UserInterface Position = new Vector2(1, 1), Colour = Color4.Black, Alpha = 0.4f, - Icon = FontAwesome.CircleONotch + Icon = FontAwesome.Solid.CircleNotch }, spinner = new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.CircleONotch + Icon = FontAwesome.Solid.CircleNotch } }; } diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 902fd310c5..8245de9f70 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -179,7 +179,7 @@ namespace osu.Game.Graphics.UserInterface Chevron = new SpriteIcon { AlwaysPresent = true, - Icon = FontAwesome.ChevronRight, + Icon = FontAwesome.Solid.ChevronRight, Colour = Color4.Black, Alpha = 0.5f, Size = new Vector2(8), @@ -244,7 +244,7 @@ namespace osu.Game.Graphics.UserInterface }, Icon = new SpriteIcon { - Icon = FontAwesome.ChevronDown, + Icon = FontAwesome.Solid.ChevronDown, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Margin = new MarginPadding { Right = 4 }, diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs index 37a13f5274..418ad038f7 100644 --- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs @@ -108,7 +108,7 @@ namespace osu.Game.Graphics.UserInterface public CapsWarning() { - Icon = FontAwesome.Warning; + Icon = FontAwesome.Solid.ExclamationTriangle; } [BackgroundDependencyLoader] diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 0ddc88b29e..fadc905541 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -254,7 +254,7 @@ namespace osu.Game.Graphics.UserInterface { new SpriteIcon { - Icon = FontAwesome.EllipsisH, + Icon = FontAwesome.Solid.EllipsisH, Size = new Vector2(14), Origin = Anchor.Centre, Anchor = Anchor.Centre, diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index 557a337941..869005d05c 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -99,7 +99,7 @@ namespace osu.Game.Graphics.UserInterface icon = new SpriteIcon { Size = new Vector2(14), - Icon = FontAwesome.CircleOutline, + Icon = FontAwesome.Regular.Circle, Shadow = true, }, }, @@ -120,12 +120,12 @@ namespace osu.Game.Graphics.UserInterface if (selected.NewValue) { fadeIn(); - icon.Icon = FontAwesome.CheckCircleOutline; + icon.Icon = FontAwesome.Regular.CheckCircle; } else { fadeOut(); - icon.Icon = FontAwesome.CircleOutline; + icon.Icon = FontAwesome.Regular.Circle; } }; } diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index 341f49732e..7023711aaa 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -22,7 +22,7 @@ namespace osu.Game.Graphics.UserInterface { new SpriteIcon { - Icon = FontAwesome.Search, + Icon = FontAwesome.Solid.Search, Origin = Anchor.CentreRight, Anchor = Anchor.CentreRight, Margin = new MarginPadding { Right = 10 }, diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index ac6e393435..8ccf3001e3 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -143,7 +143,7 @@ namespace osu.Game.Graphics.UserInterface Child = Icon = new SpriteIcon { Size = new Vector2(star_size), - Icon = FontAwesome.Star, + Icon = FontAwesome.Solid.Star, Anchor = Anchor.Centre, Origin = Anchor.Centre, }; diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index da5cc76060..70edcc3fc8 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -258,8 +258,8 @@ namespace osu.Game.Online.Leaderboards protected virtual IEnumerable GetStatistics(ScoreInfo model) => new[] { - new LeaderboardScoreStatistic(FontAwesome.Link, "Max Combo", model.MaxCombo.ToString()), - new LeaderboardScoreStatistic(FontAwesome.Crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)) + new LeaderboardScoreStatistic(FontAwesome.Solid.Link, "Max Combo", model.MaxCombo.ToString()), + new LeaderboardScoreStatistic(FontAwesome.Solid.Crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)) }; protected override bool OnHover(HoverEvent e) @@ -353,7 +353,7 @@ namespace osu.Game.Online.Leaderboards Size = new Vector2(icon_size), Rotation = 45, Colour = OsuColour.FromHex(@"3087ac"), - Icon = FontAwesome.Square, + Icon = FontAwesome.Solid.Square, Shadow = true, }, new SpriteIcon diff --git a/osu.Game/Online/Leaderboards/MessagePlaceholder.cs b/osu.Game/Online/Leaderboards/MessagePlaceholder.cs index b4980444d1..ef425dacd8 100644 --- a/osu.Game/Online/Leaderboards/MessagePlaceholder.cs +++ b/osu.Game/Online/Leaderboards/MessagePlaceholder.cs @@ -12,7 +12,7 @@ namespace osu.Game.Online.Leaderboards public MessagePlaceholder(string message) { - AddIcon(FontAwesome.ExclamationCircle, cp => + AddIcon(FontAwesome.Solid.ExclamationCircle, cp => { cp.Font = cp.Font.With(size: TEXT_SIZE); cp.Padding = new MarginPadding { Right = 10 }; diff --git a/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs b/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs index 9a35dbc476..801f3f8ff0 100644 --- a/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs +++ b/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs @@ -41,7 +41,7 @@ namespace osu.Game.Online.Leaderboards Action = () => Action?.Invoke(), Child = icon = new SpriteIcon { - Icon = FontAwesome.Refresh, + Icon = FontAwesome.Solid.Sync, Size = new Vector2(TEXT_SIZE), Shadow = true, }, diff --git a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTag.cs b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTag.cs index d51c5eb9bb..5ba5f1a415 100644 --- a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTag.cs +++ b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTag.cs @@ -18,7 +18,7 @@ namespace osu.Game.Online.Multiplayer.GameTypes { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.Refresh, + Icon = FontAwesome.Solid.Sync, Size = new Vector2(size), Colour = colours.Blue, Shadow = false, diff --git a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTagTeam.cs b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTagTeam.cs index 266f4a77b2..ef0a00a9f0 100644 --- a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTagTeam.cs +++ b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTagTeam.cs @@ -26,14 +26,14 @@ namespace osu.Game.Online.Multiplayer.GameTypes { new SpriteIcon { - Icon = FontAwesome.Refresh, + Icon = FontAwesome.Solid.Sync, Size = new Vector2(size * 0.75f), Colour = colours.Blue, Shadow = false, }, new SpriteIcon { - Icon = FontAwesome.Refresh, + Icon = FontAwesome.Solid.Sync, Size = new Vector2(size * 0.75f), Colour = colours.Pink, Shadow = false, diff --git a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTimeshift.cs b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTimeshift.cs index 1271556db4..1a3d2837ce 100644 --- a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTimeshift.cs +++ b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTimeshift.cs @@ -16,7 +16,7 @@ namespace osu.Game.Online.Multiplayer.GameTypes { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.ClockOutline, + Icon = FontAwesome.Regular.Clock, Size = new Vector2(size), Colour = colours.Blue, Shadow = false diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e470d554c9..f8ca1bc65f 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -580,7 +580,7 @@ namespace osu.Game { Schedule(() => notifications.Post(new SimpleNotification { - Icon = entry.Level == LogLevel.Important ? FontAwesome.ExclamationCircle : FontAwesome.Bomb, + Icon = entry.Level == LogLevel.Important ? FontAwesome.Solid.ExclamationCircle : FontAwesome.Solid.Bomb, Text = entry.Message + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty), })); } @@ -588,7 +588,7 @@ namespace osu.Game { Schedule(() => notifications.Post(new SimpleNotification { - Icon = FontAwesome.EllipsisH, + Icon = FontAwesome.Solid.EllipsisH, Text = "Subsequent messages have been logged. Click to view log files.", Activated = () => { diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index e817b28589..8ed52dade5 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -75,10 +75,10 @@ namespace osu.Game.Overlays.BeatmapSet Direction = FillDirection.Horizontal, Children = new[] { - length = new Statistic(FontAwesome.ClockOutline, "Length") { Width = 0.25f }, - bpm = new Statistic(FontAwesome.Circle, "BPM") { Width = 0.25f }, - circleCount = new Statistic(FontAwesome.CircleOutline, "Circle Count") { Width = 0.25f }, - sliderCount = new Statistic(FontAwesome.Circle, "Slider Count") { Width = 0.25f }, + length = new Statistic(FontAwesome.Regular.Clock, "Length") { Width = 0.25f }, + bpm = new Statistic(FontAwesome.Regular.Circle, "BPM") { Width = 0.25f }, + circleCount = new Statistic(FontAwesome.Regular.Circle, "Circle Count") { Width = 0.25f }, + sliderCount = new Statistic(FontAwesome.Regular.Circle, "Slider Count") { Width = 0.25f }, }, }; } @@ -121,7 +121,7 @@ namespace osu.Game.Overlays.BeatmapSet { Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, - Icon = FontAwesome.Square, + Icon = FontAwesome.Solid.Square, Size = new Vector2(13), Rotation = 45, Colour = OsuColour.FromHex(@"441288"), diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 1d4f181256..baf702eebc 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -131,8 +131,8 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Top = 5 }, Children = new[] { - plays = new Statistic(FontAwesome.PlayCircle), - favourites = new Statistic(FontAwesome.Heart), + plays = new Statistic(FontAwesome.Solid.PlayCircle), + favourites = new Statistic(FontAwesome.Solid.Heart), }, }, }, diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs index 667869e310..4a60b69a5a 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs @@ -78,7 +78,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons Depth = -1, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Icon = FontAwesome.Download, + Icon = FontAwesome.Solid.Download, Size = new Vector2(16), Margin = new MarginPadding { Right = 5 }, }, diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 43c14e2a58..7207739646 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.HeartOutline, + Icon = FontAwesome.Regular.Heart, Size = new Vector2(18), Shadow = false, }, @@ -59,12 +59,12 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons if (favourited.NewValue) { pink.FadeIn(200); - icon.Icon = FontAwesome.Heart; + icon.Icon = FontAwesome.Solid.Heart; } else { pink.FadeOut(200); - icon.Icon = FontAwesome.HeartOutline; + icon.Icon = FontAwesome.Regular.Heart; } }; diff --git a/osu.Game/Overlays/Chat/ExternalLinkDialog.cs b/osu.Game/Overlays/Chat/ExternalLinkDialog.cs index bcf63672ac..dbae091fb0 100644 --- a/osu.Game/Overlays/Chat/ExternalLinkDialog.cs +++ b/osu.Game/Overlays/Chat/ExternalLinkDialog.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Chat HeaderText = "Just checking..."; BodyText = $"You are about to leave osu! and open the following link in a web browser:\n\n{url}"; - Icon = FontAwesome.Warning; + Icon = FontAwesome.Solid.ExclamationTriangle; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs index 85a10510ef..4d77e5f93d 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs @@ -74,7 +74,7 @@ namespace osu.Game.Overlays.Chat.Selection { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Icon = FontAwesome.CheckCircle, + Icon = FontAwesome.Solid.CheckCircle, Size = new Vector2(text_size), Shadow = false, Margin = new MarginPadding { Right = 10f }, @@ -121,7 +121,7 @@ namespace osu.Game.Overlays.Chat.Selection { new SpriteIcon { - Icon = FontAwesome.User, + Icon = FontAwesome.Solid.User, Size = new Vector2(text_size - 2), Shadow = false, Margin = new MarginPadding { Top = 1 }, diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index f8a8038878..2e7f2d5908 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Chat.Tabs AddInternal(new SpriteIcon { - Icon = FontAwesome.Comments, + Icon = FontAwesome.Solid.Comments, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Size = new Vector2(20), diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs index e1f29a40e4..a4aefa4c4f 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -117,7 +117,7 @@ namespace osu.Game.Overlays.Chat.Tabs }; } - protected virtual IconUsage DisplayIcon => FontAwesome.Hashtag; + protected virtual IconUsage DisplayIcon => FontAwesome.Solid.Hashtag; protected virtual bool ShowCloseOnHover => true; diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs index f8add20674..8aa6d6fecd 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Chat.Tabs private readonly OsuSpriteText username; private readonly Avatar avatarContainer; - protected override IconUsage DisplayIcon => FontAwesome.At; + protected override IconUsage DisplayIcon => FontAwesome.Solid.At; public PrivateChannelTabItem(Channel value) : base(value) diff --git a/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs b/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs index b15f568c94..bde930d4fb 100644 --- a/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs +++ b/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Chat.Tabs Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(0.75f), - Icon = FontAwesome.Close, + Icon = FontAwesome.Solid.TimesCircle, RelativeSizeAxes = Axes.Both, }; } diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index ede2f34574..91f42a491a 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -166,7 +166,7 @@ namespace osu.Game.Overlays.Dialog { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Icon = FontAwesome.Close, + Icon = FontAwesome.Solid.TimesCircle, Size = new Vector2(50), }, }, diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index b8168f692a..eb73a50f99 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -186,8 +186,8 @@ namespace osu.Game.Overlays.Direct Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding }, Children = new[] { - new Statistic(FontAwesome.PlayCircle, SetInfo.OnlineInfo?.PlayCount ?? 0), - new Statistic(FontAwesome.Heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), + new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.OnlineInfo?.PlayCount ?? 0), + new Statistic(FontAwesome.Solid.Heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), }, }, statusContainer = new FillFlowContainer @@ -206,12 +206,12 @@ namespace osu.Game.Overlays.Direct if (SetInfo.OnlineInfo?.HasVideo ?? false) { - statusContainer.Add(new IconPill(FontAwesome.Film)); + statusContainer.Add(new IconPill(FontAwesome.Solid.Film)); } if (SetInfo.OnlineInfo?.HasStoryboard ?? false) { - statusContainer.Add(new IconPill(FontAwesome.Image)); + statusContainer.Add(new IconPill(FontAwesome.Solid.Image)); } statusContainer.Add(new BeatmapSetOnlineStatusPill diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index 518f6e498a..d645fd3bda 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -161,8 +161,8 @@ namespace osu.Game.Overlays.Direct Direction = FillDirection.Vertical, Children = new Drawable[] { - new Statistic(FontAwesome.PlayCircle, SetInfo.OnlineInfo?.PlayCount ?? 0), - new Statistic(FontAwesome.Heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), + new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.OnlineInfo?.PlayCount ?? 0), + new Statistic(FontAwesome.Solid.Heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), new FillFlowContainer { Anchor = Anchor.TopRight, @@ -211,12 +211,12 @@ namespace osu.Game.Overlays.Direct if (SetInfo.OnlineInfo?.HasVideo ?? false) { - statusContainer.Add(new IconPill(FontAwesome.Film) { IconSize = new Vector2(20) }); + statusContainer.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); } if (SetInfo.OnlineInfo?.HasStoryboard ?? false) { - statusContainer.Add(new IconPill(FontAwesome.Image) { IconSize = new Vector2(20) }); + statusContainer.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) }); } statusContainer.Add(new BeatmapSetOnlineStatusPill diff --git a/osu.Game/Overlays/Direct/DownloadButton.cs b/osu.Game/Overlays/Direct/DownloadButton.cs index 7fc145d635..6107dc3af3 100644 --- a/osu.Game/Overlays/Direct/DownloadButton.cs +++ b/osu.Game/Overlays/Direct/DownloadButton.cs @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Direct Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(13), - Icon = FontAwesome.Download, + Icon = FontAwesome.Solid.Download, }, checkmark = new SpriteIcon { @@ -57,7 +57,7 @@ namespace osu.Game.Overlays.Direct Origin = Anchor.Centre, X = 8, Size = Vector2.Zero, - Icon = FontAwesome.Check, + Icon = FontAwesome.Solid.Check, } } } diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs index 05ef5c8496..6daebb3c15 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -74,7 +74,7 @@ namespace osu.Game.Overlays.Direct Origin = Anchor.Centre, FillMode = FillMode.Fit, RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.Play, + Icon = FontAwesome.Solid.Play, }, loadingAnimation = new LoadingAnimation { @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Direct private void playingStateChanged(ValueChangedEvent e) { - icon.Icon = e.NewValue ? FontAwesome.Stop : FontAwesome.Play; + icon.Icon = e.NewValue ? FontAwesome.Solid.Stop : FontAwesome.Solid.Play; icon.FadeColour(e.NewValue || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint); if (e.NewValue) diff --git a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs index fb524e32c3..7e33d7ba27 100644 --- a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs @@ -9,7 +9,7 @@ namespace osu.Game.Overlays.KeyBinding { public class GlobalKeyBindingsSection : SettingsSection { - public override IconUsage Icon => FontAwesome.Globe; + public override IconUsage Icon => FontAwesome.Solid.Globe; public override string Header => "Global"; public GlobalKeyBindingsSection(GlobalActionContainer manager) diff --git a/osu.Game/Overlays/KeyBindingOverlay.cs b/osu.Game/Overlays/KeyBindingOverlay.cs index 6259f39c66..b223d4701d 100644 --- a/osu.Game/Overlays/KeyBindingOverlay.cs +++ b/osu.Game/Overlays/KeyBindingOverlay.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays Y = -15, Size = new Vector2(15), Shadow = true, - Icon = FontAwesome.ChevronLeft + Icon = FontAwesome.Solid.ChevronLeft }, new OsuSpriteText { diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 96e9cc9ca7..df37a1b2c7 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -164,7 +164,7 @@ namespace osu.Game.Overlays.Music Anchor = Anchor.TopLeft; Origin = Anchor.TopLeft; Size = new Vector2(12); - Icon = FontAwesome.Bars; + Icon = FontAwesome.Solid.Bars; Alpha = 0f; Margin = new MarginPadding { Left = 5, Top = 2 }; } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index ce2137346f..b24c6c3508 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -148,7 +148,7 @@ namespace osu.Game.Overlays Anchor = Anchor.Centre, Origin = Anchor.Centre, Action = prev, - Icon = FontAwesome.StepBackward, + Icon = FontAwesome.Solid.StepBackward, }, playButton = new MusicIconButton { @@ -157,14 +157,14 @@ namespace osu.Game.Overlays Scale = new Vector2(1.4f), IconScale = new Vector2(1.4f), Action = play, - Icon = FontAwesome.PlayCircleOutline, + Icon = FontAwesome.Regular.PlayCircle, }, nextButton = new MusicIconButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, Action = () => next(), - Icon = FontAwesome.StepForward, + Icon = FontAwesome.Solid.StepForward, }, } }, @@ -173,7 +173,7 @@ namespace osu.Game.Overlays Origin = Anchor.Centre, Anchor = Anchor.CentreRight, Position = new Vector2(-bottom_black_area_height / 2, 0), - Icon = FontAwesome.Bars, + Icon = FontAwesome.Solid.Bars, Action = () => playlist.ToggleVisibility(), }, } @@ -264,13 +264,13 @@ namespace osu.Game.Overlays progressBar.EndTime = track.Length; progressBar.CurrentTime = track.CurrentTime; - playButton.Icon = track.IsRunning ? FontAwesome.PauseCircleOutline : FontAwesome.PlayCircleOutline; + playButton.Icon = track.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; } else { progressBar.CurrentTime = 0; progressBar.EndTime = 1; - playButton.Icon = FontAwesome.PlayCircleOutline; + playButton.Icon = FontAwesome.Regular.PlayCircle; } } diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 7abff9252f..522e039cdb 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -175,7 +175,7 @@ namespace osu.Game.Overlays.Notifications { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.TimesCircle, + Icon = FontAwesome.Solid.TimesCircle, Size = new Vector2(20), } }; diff --git a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs index d5993e1f79..feffb4fa66 100644 --- a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Notifications { public ProgressCompletionNotification() { - Icon = FontAwesome.Check; + Icon = FontAwesome.Solid.Check; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index 26852242d2..3a3136b1ea 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Notifications } } - private IconUsage icon = FontAwesome.InfoCircle; + private IconUsage icon = FontAwesome.Solid.InfoCircle; public IconUsage Icon { diff --git a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs index 7b07617e2e..5c9126dbe0 100644 --- a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs @@ -50,7 +50,7 @@ namespace osu.Game.Overlays.Profile.Header Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.Heart, + Icon = FontAwesome.Solid.Heart, Scale = new Vector2(0.45f), } }; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 28877c21f0..138e522cd7 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -415,16 +415,16 @@ namespace osu.Game.Overlays.Profile websiteWithoutProtcol = websiteWithoutProtcol.Substring(protocolIndex + 2); } - tryAddInfoRightLine(FontAwesome.MapMarker, user.Location); - tryAddInfoRightLine(FontAwesome.HeartOutline, user.Interests); - tryAddInfoRightLine(FontAwesome.Suitcase, user.Occupation); + tryAddInfoRightLine(FontAwesome.Solid.MapMarker, user.Location); + tryAddInfoRightLine(FontAwesome.Regular.Heart, user.Interests); + tryAddInfoRightLine(FontAwesome.Solid.Suitcase, user.Occupation); infoTextRight.NewParagraph(); if (!string.IsNullOrEmpty(user.Twitter)) - tryAddInfoRightLine(FontAwesome.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); - tryAddInfoRightLine(FontAwesome.Gamepad, user.Discord); - tryAddInfoRightLine(FontAwesome.Skype, user.Skype, @"skype:" + user.Skype + @"?chat"); - tryAddInfoRightLine(FontAwesome.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); - tryAddInfoRightLine(FontAwesome.Globe, websiteWithoutProtcol, user.Website); + tryAddInfoRightLine(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); + tryAddInfoRightLine(FontAwesome.Solid.Gamepad, user.Discord); + tryAddInfoRightLine(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat"); + tryAddInfoRightLine(FontAwesome.Brands.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); + tryAddInfoRightLine(FontAwesome.Solid.Globe, websiteWithoutProtcol, user.Website); if (user.Statistics != null) { diff --git a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs index 48be91ea23..0808cc8fcc 100644 --- a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs +++ b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs @@ -37,8 +37,8 @@ namespace osu.Game.Overlays.SearchableList Direction = FillDirection.Horizontal, Children = new[] { - new DisplayStyleToggleButton(FontAwesome.ThLarge, PanelDisplayStyle.Grid, DisplayStyle), - new DisplayStyleToggleButton(FontAwesome.ListUl, PanelDisplayStyle.List, DisplayStyle), + new DisplayStyleToggleButton(FontAwesome.Solid.ThLarge, PanelDisplayStyle.Grid, DisplayStyle), + new DisplayStyleToggleButton(FontAwesome.Solid.ListUl, PanelDisplayStyle.List, DisplayStyle), }, }, Dropdown = new SlimEnumDropdown diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs index ea7011ea01..772f5c2039 100644 --- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs +++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class AudioSection : SettingsSection { public override string Header => "Audio"; - public override IconUsage Icon => FontAwesome.VolumeUp; + public override IconUsage Icon => FontAwesome.Solid.VolumeUp; public AudioSection() { diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs index d90bb9be10..0149cab802 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSection.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class DebugSection : SettingsSection { public override string Header => "Debug"; - public override IconUsage Icon => FontAwesome.Bug; + public override IconUsage Icon => FontAwesome.Solid.Bug; public DebugSection() { diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index e69a19b447..97d9d3c697 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections public class GameplaySection : SettingsSection { public override string Header => "Gameplay"; - public override IconUsage Icon => FontAwesome.CircleOutline; + public override IconUsage Icon => FontAwesome.Regular.Circle; public GameplaySection() { diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 078c01ce92..e4ddc53e17 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -363,7 +363,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Icon = FontAwesome.CircleOutline, + Icon = FontAwesome.Regular.Circle, Size = new Vector2(14), }); diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs index f571d5ff7c..d9947f16cc 100644 --- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class GeneralSection : SettingsSection { public override string Header => "General"; - public override IconUsage Icon => FontAwesome.Gear; + public override IconUsage Icon => FontAwesome.Solid.Cog; public GeneralSection() { diff --git a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs index 92746d5117..3d6086d3ea 100644 --- a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class GraphicsSection : SettingsSection { public override string Header => "Graphics"; - public override IconUsage Icon => FontAwesome.Laptop; + public override IconUsage Icon => FontAwesome.Solid.Laptop; public GraphicsSection() { diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index d193277a6b..6a3f8783b0 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class InputSection : SettingsSection { public override string Header => "Input"; - public override IconUsage Icon => FontAwesome.KeyboardOutline; + public override IconUsage Icon => FontAwesome.Regular.Keyboard; public InputSection(KeyBindingOverlay keyConfig) { diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs index 7ab3629e12..a124501454 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { BodyText = "Everything?"; - Icon = FontAwesome.TrashOutline; + Icon = FontAwesome.Regular.TrashAlt; HeaderText = @"Confirm deletion of"; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs index 41530e20ca..0f3acd5b7f 100644 --- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs @@ -11,7 +11,7 @@ namespace osu.Game.Overlays.Settings.Sections public class MaintenanceSection : SettingsSection { public override string Header => "Maintenance"; - public override IconUsage Icon => FontAwesome.Wrench; + public override IconUsage Icon => FontAwesome.Solid.Wrench; public MaintenanceSection() { diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs index f9f5d99c84..80295690c0 100644 --- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs +++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class OnlineSection : SettingsSection { public override string Header => "Online"; - public override IconUsage Icon => FontAwesome.Globe; + public override IconUsage Icon => FontAwesome.Solid.GlobeAsia; public OnlineSection() { diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 79b9076a52..100022bd13 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections public override string Header => "Skin"; - public override IconUsage Icon => FontAwesome.PaintBrush; + public override IconUsage Icon => FontAwesome.Solid.PaintBrush; private readonly Bindable dropdownBindable = new Bindable { Default = SkinInfo.Default }; private readonly Bindable configBindable = new Bindable(); diff --git a/osu.Game/Overlays/Social/Header.cs b/osu.Game/Overlays/Social/Header.cs index bf07c343e6..22bca9b421 100644 --- a/osu.Game/Overlays/Social/Header.cs +++ b/osu.Game/Overlays/Social/Header.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Social protected override Color4 BackgroundColour => OsuColour.FromHex(@"38202e"); protected override SocialTab DefaultTab => SocialTab.AllPlayers; - protected override IconUsage Icon => FontAwesome.Users; + protected override IconUsage Icon => FontAwesome.Solid.Users; protected override Drawable CreateHeaderText() { diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 59d7a18a34..a7f2a0e8d0 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Toolbar new ToolbarMusicButton(), //new ToolbarButton //{ - // Icon = FontAwesome.search + // Icon = FontAwesome.Solid.search //}, userButton = new ToolbarUserButton(), new ToolbarNotificationButton(), diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs index 8ea21e88b5..ad0e5be551 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarChatButton() { - SetIcon(FontAwesome.Comments); + SetIcon(FontAwesome.Solid.Comments); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs index 18a116127c..6f5e703a66 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs @@ -9,7 +9,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarHomeButton() { - Icon = FontAwesome.Home; + Icon = FontAwesome.Solid.Home; TooltipMain = "Home"; TooltipSub = "Return to the main menu"; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index 7f4c9d455e..f03df2ed93 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarMusicButton() { - Icon = FontAwesome.Music; + Icon = FontAwesome.Solid.Music; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs index b3bd82ae38..dbd6c557d3 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Toolbar public ToolbarNotificationButton() { - Icon = FontAwesome.Bars; + Icon = FontAwesome.Solid.Bars; TooltipMain = "Notifications"; TooltipSub = "Waiting for 'ya"; diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs index 4e48ffd034..08f8f867fd 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarSettingsButton() { - Icon = FontAwesome.Gear; + Icon = FontAwesome.Solid.Cog; TooltipMain = "Settings"; TooltipSub = "Change your settings"; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs index 769fa520cb..5e353d3319 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarSocialButton() { - Icon = FontAwesome.Users; + Icon = FontAwesome.Solid.Users; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index 090e443a0c..2b1f78243b 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -72,7 +72,7 @@ namespace osu.Game.Overlays.Volume Current.ValueChanged += muted => { - icon.Icon = muted.NewValue ? FontAwesome.VolumeOff : FontAwesome.VolumeUp; + icon.Icon = muted.NewValue ? FontAwesome.Solid.VolumeOff : FontAwesome.Solid.VolumeUp; icon.Margin = new MarginPadding { Left = muted.NewValue ? width / 2 - 15 : width / 2 - 10 }; //Magic numbers to line up both icons because they're different widths }; Current.TriggerChange(); diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index be2ff33730..d2d0a5bb26 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mods /// The icon of this mod. /// [JsonIgnore] - public virtual IconUsage Icon => FontAwesome.Question; + public virtual IconUsage Icon => FontAwesome.Solid.Question; /// /// The type of this mod. diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 0dd5d7b815..7e6d959119 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Daycore"; public override string Acronym => "DC"; - public override IconUsage Icon => FontAwesome.Question; + public override IconUsage Icon => FontAwesome.Solid.Question; public override string Description => "Whoaaaaa..."; public override void ApplyToClock(IAdjustableClock clock) diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index eccd848c48..5d71c8950b 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods public override string Name => "Wind Down"; public override string Acronym => "WD"; public override string Description => "Sloooow doooown..."; - public override IconUsage Icon => FontAwesome.ChevronCircleDown; + public override IconUsage Icon => FontAwesome.Solid.ChevronCircleDown; public override double ScoreMultiplier => 1.0; protected override double FinalRateAdjustment => -0.25; diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index d430c291cb..aae85cec19 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods public override string Name => "Wind Up"; public override string Acronym => "WU"; public override string Description => "Can you keep up?"; - public override IconUsage Icon => FontAwesome.ChevronCircleUp; + public override IconUsage Icon => FontAwesome.Solid.ChevronCircleUp; public override double ScoreMultiplier => 1.0; protected override double FinalRateAdjustment => 0.5; diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 013fffb7cb..cdfe02b60b 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets public virtual HitObjectComposer CreateHitObjectComposer() => null; - public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.QuestionCircle }; + public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle }; public abstract string Description { get; } diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 6d590780b0..f5c9f74f62 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Edit.Components Origin = Anchor.Centre, Scale = new Vector2(1.4f), IconScale = new Vector2(1.4f), - Icon = FontAwesome.PlayCircleOutline, + Icon = FontAwesome.Regular.PlayCircle, Action = togglePause, Padding = new MarginPadding { Left = 20 } }, @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Edit.Components { base.Update(); - playButton.Icon = adjustableClock.IsRunning ? FontAwesome.PauseCircleOutline : FontAwesome.PlayCircleOutline; + playButton.Icon = adjustableClock.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; } private class PlaybackTabControl : OsuTabControl diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 1f13797497..9a7ac8dfd0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -94,13 +94,13 @@ namespace osu.Game.Screens.Edit.Compose.Components { new DivisorButton { - Icon = FontAwesome.ChevronLeft, + Icon = FontAwesome.Solid.ChevronLeft, Action = beatDivisor.Previous }, new DivisorText(beatDivisor), new DivisorButton { - Icon = FontAwesome.ChevronRight, + Icon = FontAwesome.Solid.ChevronRight, Action = beatDivisor.Next } }, diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 2bed231da7..863a120fc3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { RelativeSizeAxes = Axes.Y, Height = 0.5f, - Icon = FontAwesome.SearchPlus, + Icon = FontAwesome.Solid.SearchPlus, Action = () => changeZoom(1) }, new TimelineButton @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.Y, Height = 0.5f, - Icon = FontAwesome.SearchMinus, + Icon = FontAwesome.Solid.SearchMinus, Action = () => changeZoom(-1) }, } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index bcd24fd83e..d3cf23dab8 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Menu buttonArea.AddRange(new[] { - new Button(@"settings", string.Empty, FontAwesome.Gear, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), + new Button(@"settings", string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), backButton = new Button(@"back", @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) { VisibleState = ButtonSystemState.Play, @@ -106,8 +106,8 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader(true)] private void load(AudioManager audio, IdleTracker idleTracker, GameHost host) { - buttonsPlay.Add(new Button(@"solo", @"button-solo-select", FontAwesome.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); - buttonsPlay.Add(new Button(@"multi", @"button-generic-select", FontAwesome.Users, new Color4(94, 63, 186, 255), onMulti, 0, Key.M)); + buttonsPlay.Add(new Button(@"solo", @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); + buttonsPlay.Add(new Button(@"multi", @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMulti, 0, Key.M)); buttonsPlay.Add(new Button(@"chart", @"button-generic-select", OsuIcon.Charts, new Color4(80, 53, 160, 255), () => OnChart?.Invoke())); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); @@ -135,7 +135,7 @@ namespace osu.Game.Screens.Menu notifications?.Post(new SimpleNotification { Text = "You gotta be logged in to multi 'yo!", - Icon = FontAwesome.Globe + Icon = FontAwesome.Solid.Globe }); return; diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 170209207b..0130a5143b 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Menu { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.Warning, + Icon = FontAwesome.Solid.ExclamationTriangle, Size = new Vector2(icon_size), Y = icon_y, }, @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Menu supportFlow.AddText(" to help support the game", format); } - heart = supportFlow.AddIcon(FontAwesome.Heart, t => + heart = supportFlow.AddIcon(FontAwesome.Solid.Heart, t => { t.Padding = new MarginPadding { Left = 5 }; t.Font = t.Font.With(size: 12); diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs index 2734c55ce7..92074abe4b 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs @@ -25,9 +25,9 @@ namespace osu.Game.Screens.Multi.Match.Components protected override IEnumerable GetStatistics(ScoreInfo model) => new[] { - new LeaderboardScoreStatistic(FontAwesome.Crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)), - new LeaderboardScoreStatistic(FontAwesome.Refresh, "Total Attempts", ((APIRoomScoreInfo)model).TotalAttempts.ToString()), - new LeaderboardScoreStatistic(FontAwesome.Check, "Completed Beatmaps", ((APIRoomScoreInfo)model).CompletedBeatmaps.ToString()), + new LeaderboardScoreStatistic(FontAwesome.Solid.Crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)), + new LeaderboardScoreStatistic(FontAwesome.Solid.Sync, "Total Attempts", ((APIRoomScoreInfo)model).TotalAttempts.ToString()), + new LeaderboardScoreStatistic(FontAwesome.Solid.Check, "Completed Beatmaps", ((APIRoomScoreInfo)model).CompletedBeatmaps.ToString()), }; } } diff --git a/osu.Game/Screens/Multi/Ranking/Types/RoomLeaderboardPageInfo.cs b/osu.Game/Screens/Multi/Ranking/Types/RoomLeaderboardPageInfo.cs index b03fafbd13..dcfad8458f 100644 --- a/osu.Game/Screens/Multi/Ranking/Types/RoomLeaderboardPageInfo.cs +++ b/osu.Game/Screens/Multi/Ranking/Types/RoomLeaderboardPageInfo.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Multi.Ranking.Types this.beatmap = beatmap; } - public IconUsage Icon => FontAwesome.Users; + public IconUsage Icon => FontAwesome.Solid.Users; public string Name => "Room Leaderboard"; diff --git a/osu.Game/Screens/Play/Break/BreakArrows.cs b/osu.Game/Screens/Play/Break/BreakArrows.cs index e0238f6814..4b96fa666a 100644 --- a/osu.Game/Screens/Play/Break/BreakArrows.cs +++ b/osu.Game/Screens/Play/Break/BreakArrows.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Play.Break Anchor = Anchor.Centre, Origin = Anchor.CentreRight, X = -glow_icon_offscreen_offset, - Icon = FontAwesome.ChevronRight, + Icon = FontAwesome.Solid.ChevronRight, BlurSigma = new Vector2(glow_icon_blur_sigma), Size = new Vector2(glow_icon_size), }, @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Play.Break Anchor = Anchor.Centre, Origin = Anchor.CentreLeft, X = glow_icon_offscreen_offset, - Icon = FontAwesome.ChevronLeft, + Icon = FontAwesome.Solid.ChevronLeft, BlurSigma = new Vector2(glow_icon_blur_sigma), Size = new Vector2(glow_icon_size), }, @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Play.Break Origin = Anchor.CentreRight, Alpha = 0.7f, X = -blurred_icon_offscreen_offset, - Icon = FontAwesome.ChevronRight, + Icon = FontAwesome.Solid.ChevronRight, BlurSigma = new Vector2(blurred_icon_blur_sigma), Size = new Vector2(blurred_icon_size), }, @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Play.Break Origin = Anchor.CentreLeft, Alpha = 0.7f, X = blurred_icon_offscreen_offset, - Icon = FontAwesome.ChevronLeft, + Icon = FontAwesome.Solid.ChevronLeft, BlurSigma = new Vector2(blurred_icon_blur_sigma), Size = new Vector2(blurred_icon_size), }, diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 03843eeb90..6883f246e4 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(15), - Icon = FontAwesome.Close + Icon = FontAwesome.Solid.TimesCircle }, } }; diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs index d243ff24a3..90424ec007 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Origin = Anchor.Centre, Anchor = Anchor.CentreRight, Position = new Vector2(-15, 0), - Icon = FontAwesome.Bars, + Icon = FontAwesome.Solid.Bars, Scale = new Vector2(0.75f), Action = () => Expanded = !Expanded, }, diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 78ed742bfa..738877232d 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -259,9 +259,9 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Horizontal, Children = new[] { - new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.ChevronRight }, - new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.ChevronRight }, - new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.ChevronRight }, + new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.Solid.ChevronRight }, + new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.Solid.ChevronRight }, + new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.Solid.ChevronRight }, } }, new OsuSpriteText diff --git a/osu.Game/Screens/Ranking/Types/LocalLeaderboardPageInfo.cs b/osu.Game/Screens/Ranking/Types/LocalLeaderboardPageInfo.cs index e563eb8116..fe183c5f89 100644 --- a/osu.Game/Screens/Ranking/Types/LocalLeaderboardPageInfo.cs +++ b/osu.Game/Screens/Ranking/Types/LocalLeaderboardPageInfo.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Ranking.Types this.beatmap = beatmap; } - public IconUsage Icon => FontAwesome.User; + public IconUsage Icon => FontAwesome.Solid.User; public string Name => @"Local Leaderboard"; diff --git a/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs b/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs index 2d9b3b9ef9..424dbff6f6 100644 --- a/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs +++ b/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs @@ -10,7 +10,7 @@ namespace osu.Game.Screens.Ranking.Types { public class ScoreOverviewPageInfo : IResultPageInfo { - public IconUsage Icon => FontAwesome.Asterisk; + public IconUsage Icon => FontAwesome.Solid.Asterisk; public string Name => "Overview"; private readonly ScoreInfo score; diff --git a/osu.Game/Screens/ScreenWhiteBox.cs b/osu.Game/Screens/ScreenWhiteBox.cs index f471cab063..d6766c2b49 100644 --- a/osu.Game/Screens/ScreenWhiteBox.cs +++ b/osu.Game/Screens/ScreenWhiteBox.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens { new SpriteIcon { - Icon = FontAwesome.UniversalAccess, + Icon = FontAwesome.Solid.UniversalAccess, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Size = new Vector2(50), diff --git a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs index aa579ac665..c9b6ca7bb3 100644 --- a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs +++ b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Select public BeatmapClearScoresDialog(BeatmapInfo beatmap, Action onCompletion) { BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}"; - Icon = FontAwesome.Eraser; + Icon = FontAwesome.Solid.Eraser; HeaderText = @"Clearing all local scores. Are you sure?"; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs index a1adaff1d8..5fb72e4151 100644 --- a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs +++ b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Select { BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}"; - Icon = FontAwesome.TrashOutline; + Icon = FontAwesome.Regular.TrashAlt; HeaderText = @"Confirm deletion of"; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index b2e08aeefd..d32387c1d3 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -293,14 +293,14 @@ namespace osu.Game.Screens.Select labels.Add(new InfoLabel(new BeatmapStatistic { Name = "Length", - Icon = FontAwesome.ClockOutline, + Icon = FontAwesome.Regular.Clock, Content = TimeSpan.FromMilliseconds(endTime - b.HitObjects.First().StartTime).ToString(@"m\:ss"), })); labels.Add(new InfoLabel(new BeatmapStatistic { Name = "BPM", - Icon = FontAwesome.Circle, + Icon = FontAwesome.Regular.Circle, Content = getBPMRange(b), })); @@ -378,7 +378,7 @@ namespace osu.Game.Screens.Select Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Colour = OsuColour.FromHex(@"441288"), - Icon = FontAwesome.Square, + Icon = FontAwesome.Solid.Square, Rotation = 45, }, new SpriteIcon diff --git a/osu.Game/Screens/Select/ImportFromStablePopup.cs b/osu.Game/Screens/Select/ImportFromStablePopup.cs index f1cc3d632c..54e4c096f6 100644 --- a/osu.Game/Screens/Select/ImportFromStablePopup.cs +++ b/osu.Game/Screens/Select/ImportFromStablePopup.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Select HeaderText = @"You have no beatmaps!"; BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps (and skins)?"; - Icon = FontAwesome.Plane; + Icon = FontAwesome.Solid.Plane; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs index 0f1f49bd85..a9616ee535 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs @@ -141,7 +141,7 @@ namespace osu.Game.Screens.Select.Options Anchor = Anchor.TopCentre, Size = new Vector2(30), Shadow = true, - Icon = FontAwesome.Close, + Icon = FontAwesome.Solid.TimesCircle, Margin = new MarginPadding { Bottom = 5, diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 6a10e86198..340ceb6864 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader] private void load(OsuColour colours) { - BeatmapOptions.AddButton(@"Edit", @"beatmap", FontAwesome.Pencil, colours.Yellow, () => + BeatmapOptions.AddButton(@"Edit", @"beatmap", FontAwesome.Solid.PencilAlt, colours.Yellow, () => { ValidForResume = false; Edit(); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 9ac8e26ec0..b60e693cbf 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -228,9 +228,9 @@ namespace osu.Game.Screens.Select Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2); Footer.AddButton(@"options", colours.Blue, BeatmapOptions, Key.F3); - BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); - BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.TimesCircleOutline, colours.Purple, null, Key.Number1); - BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo), Key.Number2); + BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); + BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null, Key.Number1); + BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo), Key.Number2); } if (this.beatmaps == null) diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 1f62111a4e..e745aa54c8 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -166,7 +166,7 @@ namespace osu.Game.Users { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Icon = FontAwesome.CircleOutline, + Icon = FontAwesome.Regular.Circle, Shadow = true, Size = new Vector2(14), }, From bc1077ed73b2f237e4a52bf6aa2e13af0b310578 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Apr 2019 19:55:34 +0900 Subject: [PATCH 338/623] Remove remaining FontAwesome reference --- osu.Game/Graphics/OsuFont.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 26112430f6..c8a736f49a 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -42,8 +42,6 @@ namespace osu.Game.Graphics { case Typeface.Exo: return "Exo2.0"; - case Typeface.FontAwesome: - return "FontAwesome"; case Typeface.Venera: return "Venera"; } @@ -101,7 +99,6 @@ namespace osu.Game.Graphics public enum Typeface { Exo, - FontAwesome, Venera, } From bcd51afea1686f874b120cc043906d3d32ecf021 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Apr 2019 19:55:46 +0900 Subject: [PATCH 339/623] Fix osu! icon font name mismatch --- osu.Game/Graphics/OsuIcon.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/OsuIcon.cs b/osu.Game/Graphics/OsuIcon.cs index 52fb31553d..982e9dacab 100644 --- a/osu.Game/Graphics/OsuIcon.cs +++ b/osu.Game/Graphics/OsuIcon.cs @@ -7,7 +7,7 @@ namespace osu.Game.Graphics { public static class OsuIcon { - public static IconUsage Get(int icon) => new IconUsage((char)icon, "OsuFont"); + public static IconUsage Get(int icon) => new IconUsage((char)icon, "osuFont"); // ruleset icons in circles public static IconUsage RulesetOsu => Get(0xe000); From 072954e4c05fd274c276dd43d361b101d510cf95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Apr 2019 21:00:05 +0900 Subject: [PATCH 340/623] Update framework --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 52c53503ee..f3c648cf02 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9ecc7d4632..7ce7329246 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From e9269dc83bb5df8db8e9bf470cf9f263ff42ee27 Mon Sep 17 00:00:00 2001 From: Samuel Van Allen Date: Tue, 2 Apr 2019 23:57:31 +0800 Subject: [PATCH 341/623] Prevent unnecessary query in OsuGame::PresentBeatmap This resolves issue #4575 --- osu.Game/OsuGame.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index f8ca1bc65f..9ab09d1122 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -254,6 +254,12 @@ namespace osu.Game if (menuScreen.IsCurrentScreen()) menuScreen.LoadToSolo(); + // we might even already be at the song + if (Beatmap.Value.BeatmapSetInfo.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID) + { + return; + } + // Use first beatmap available for current ruleset, else switch ruleset. var first = databasedSet.Beatmaps.Find(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First(); From ab4be3b75f1ae455a18d21fc3609c96e9dbab00c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Apr 2019 15:20:38 +0900 Subject: [PATCH 342/623] General refactoring --- .../TestCaseBeatmapScoresContainer.cs | 1 + .../Graphics/Containers/OsuHoverContainer.cs | 4 +- .../Scores/ClickableUserContainer.cs | 29 ++++---- .../BeatmapSet/Scores/DrawableTopScore.cs | 66 ++++++++----------- .../BeatmapSet/Scores/ScoreTableScoreRow.cs | 2 +- 5 files changed, 48 insertions(+), 54 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs index c72d12c09c..4a38f98810 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs @@ -24,6 +24,7 @@ namespace osu.Game.Tests.Visual.SongSelect { public override IReadOnlyList RequiredTypes => new[] { + typeof(DrawableTopScore), typeof(ScoresContainer), typeof(ScoreTable), typeof(ScoreTableRow), diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs index 091499b7cd..880807c8b4 100644 --- a/osu.Game/Graphics/Containers/OsuHoverContainer.cs +++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs @@ -12,12 +12,12 @@ namespace osu.Game.Graphics.Containers { public class OsuHoverContainer : OsuClickableContainer { + protected const float FADE_DURATION = 500; + protected Color4 HoverColour; protected Color4 IdleColour = Color4.White; - protected const float FADE_DURATION = 500; - protected virtual IEnumerable EffectTargets => new[] { Content }; protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs index cf1c3d7fcf..2448ae17f8 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs @@ -13,6 +13,17 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { private UserProfileOverlay profile; + protected ClickableUserContainer() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader(true)] + private void load(UserProfileOverlay profile) + { + this.profile = profile; + } + private User user; public User User @@ -20,26 +31,16 @@ namespace osu.Game.Overlays.BeatmapSet.Scores get => user; set { - if (user == value) return; + if (user == value) + return; user = value; - OnUserChange(user); + OnUserChanged(user); } } - protected ClickableUserContainer() - { - AutoSizeAxes = Axes.Both; - } - - protected abstract void OnUserChange(User user); - - [BackgroundDependencyLoader(true)] - private void load(UserProfileOverlay profile) - { - this.profile = profile; - } + protected abstract void OnUserChanged(User user); protected override bool OnClick(ClickEvent e) { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 82905d065e..e3141624b5 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -19,6 +19,7 @@ using osu.Game.Users; using osuTK; using osuTK.Graphics; using System.Collections.Generic; +using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -164,7 +165,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - TextSize = 20, }, date = new SpriteText { @@ -263,67 +263,59 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { private const float username_fade_duration = 500; - private readonly Box underscore; - private readonly Container underscoreContainer; - private readonly SpriteText text; + private readonly FillFlowContainer hoverContainer; - private Color4 hoverColour; - - public float TextSize - { - set - { - if (text.TextSize == value) - return; - - text.TextSize = value; - } - get => text.TextSize; - } + private readonly SpriteText normalText; + private readonly SpriteText hoveredText; public ClickableTopScoreUsername() { - Add(underscoreContainer = new Container + var font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold, italics: true); + + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = 1, - Child = underscore = new Box + normalText = new OsuSpriteText { Font = font }, + hoverContainer = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Alpha = 0, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 1), + Children = new Drawable[] + { + hoveredText = new OsuSpriteText { Font = font }, + new Box + { + BypassAutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + Height = 1 + } + } } - }); - Add(text = new SpriteText - { - Font = @"Exo2.0-BoldItalic", - }); + }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - hoverColour = underscore.Colour = colours.Blue; - underscoreContainer.Position = new Vector2(0, TextSize / 2 - 1); + hoverContainer.Colour = colours.Blue; } - protected override void OnUserChange(User user) + protected override void OnUserChanged(User user) { - text.Text = user.Username; + normalText.Text = hoveredText.Text = user.Username; } protected override bool OnHover(HoverEvent e) { - text.FadeColour(hoverColour, username_fade_duration, Easing.OutQuint); - underscore.FadeIn(username_fade_duration, Easing.OutQuint); + hoverContainer.FadeIn(username_fade_duration, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - text.FadeColour(Color4.White, username_fade_duration, Easing.OutQuint); - underscore.FadeOut(username_fade_duration, Easing.OutQuint); + hoverContainer.FadeOut(username_fade_duration, Easing.OutQuint); base.OnHoverLost(e); } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs index cd1ade934a..a0c6db9a56 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs @@ -145,7 +145,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }); } - protected override void OnUserChange(User user) + protected override void OnUserChanged(User user) { text.Text = textBold.Text = user.Username; } From 1dad1523632352769cfab479de52af7609c0e1b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Apr 2019 15:37:22 +0900 Subject: [PATCH 343/623] Correctly handle nvika/inspectcode return codes --- build/build.cake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build/build.cake b/build/build.cake index 81deeb3bc7..3269bbf1c0 100644 --- a/build/build.cake +++ b/build/build.cake @@ -46,7 +46,9 @@ Task("InspectCode") OutputFile = "inspectcodereport.xml", }); - StartProcess(nVikaToolPath, @"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors"); + int returnCode = StartProcess(nVikaToolPath, $@"parsereport ""{inspectcodereport}"" --treatwarningsaserrors"); + if (returnCode != 0) + throw new Exception($"inspectcode failed with return code {returnCode}"); }); Task("CodeFileSanity") From 7f425059aecbc83a0f8b12873fd95f789c8c5858 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Apr 2019 15:41:22 +0900 Subject: [PATCH 344/623] Adjust transforms --- .../Overlays/BeatmapSet/Scores/DrawableTopScore.cs | 2 +- .../BeatmapSet/Scores/ScoreTableScoreRow.cs | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index e3141624b5..3bcb44ab94 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -261,7 +261,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private class ClickableTopScoreUsername : ClickableUserContainer { - private const float username_fade_duration = 500; + private const float username_fade_duration = 150; private readonly FillFlowContainer hoverContainer; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs index a0c6db9a56..83901cc662 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs @@ -145,22 +145,19 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }); } - protected override void OnUserChanged(User user) - { - text.Text = textBold.Text = user.Username; - } + protected override void OnUserChanged(User user) => text.Text = textBold.Text = user.Username; protected override bool OnHover(HoverEvent e) { - textBold.FadeIn(fade_duration, Easing.OutQuint); - text.FadeOut(fade_duration, Easing.OutQuint); + textBold.Show(); + text.Hide(); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - textBold.FadeOut(fade_duration, Easing.OutQuint); - text.FadeIn(fade_duration, Easing.OutQuint); + textBold.Hide(); + text.Show(); base.OnHoverLost(e); } } From dff58ab4edb52c2faec7eb241b6b50602cd1d432 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Apr 2019 15:41:33 +0900 Subject: [PATCH 345/623] Initial cleanup pass of DrawableTopScore --- .../BeatmapSet/Scores/DrawableTopScore.cs | 201 +++++++----------- 1 file changed, 72 insertions(+), 129 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 3bcb44ab94..66ef2a30a3 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -43,15 +43,14 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly SpriteText date; private readonly DrawableRank rank; - private readonly AutoSizedInfoColumn totalScore; - private readonly AutoSizedInfoColumn accuracy; - private readonly MediumInfoColumn maxCombo; - - private readonly SmallInfoColumn hitGreat; - private readonly SmallInfoColumn hitGood; - private readonly SmallInfoColumn hitMeh; - private readonly SmallInfoColumn hitMiss; - private readonly SmallInfoColumn pp; + private readonly SpriteText totalScoreText; + private readonly SpriteText accuracyText; + private readonly SpriteText maxComboText; + private readonly SpriteText hitGreatText; + private readonly SpriteText hitGoodText; + private readonly SpriteText hitMehText; + private readonly SpriteText hitMissText; + private readonly SpriteText ppText; private readonly ModsInfoColumn modsInfo; @@ -72,17 +71,16 @@ namespace osu.Game.Overlays.BeatmapSet.Scores date.Text = $@"achieved {score.Date.Humanize()}"; rank.UpdateRank(score.Rank); - totalScore.Value = $@"{score.TotalScore:N0}"; - accuracy.Value = $@"{score.Accuracy:P2}"; - maxCombo.Value = $@"{score.MaxCombo:N0}x"; + totalScoreText.Text = $@"{score.TotalScore:N0}"; + accuracyText.Text = $@"{score.Accuracy:P2}"; + maxComboText.Text = $@"{score.MaxCombo:N0}x"; - hitGreat.Value = $"{score.Statistics[HitResult.Great]}"; - hitGood.Value = $"{score.Statistics[HitResult.Good]}"; - hitMeh.Value = $"{score.Statistics[HitResult.Meh]}"; - hitMiss.Value = $"{score.Statistics[HitResult.Miss]}"; - pp.Value = $@"{score.PP:N0}"; + hitGreatText.Text = $"{score.Statistics[HitResult.Great]}"; + hitGoodText.Text = $"{score.Statistics[HitResult.Good]}"; + hitMehText.Text = $"{score.Statistics[HitResult.Meh]}"; + hitMissText.Text = $"{score.Statistics[HitResult.Miss]}"; + ppText.Text = $@"{score.PP:N0}"; - modsInfo.ClearMods(); modsInfo.Mods = score.Mods; } } @@ -91,8 +89,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - CornerRadius = 10; + Masking = true; + CornerRadius = 10; EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, @@ -100,6 +99,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Radius = 1, Offset = new Vector2(0, 1), }; + + var smallFont = OsuFont.GetFont(size: 20); + var largeFont = OsuFont.GetFont(size: 25); + Children = new Drawable[] { background = new Box @@ -208,12 +211,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Spacing = new Vector2(margin, 0), Children = new Drawable[] { - hitGreat = new SmallInfoColumn("300", 20), - hitGood = new SmallInfoColumn("100", 20), - hitMeh = new SmallInfoColumn("50", 20), - hitMiss = new SmallInfoColumn("misses", 20), - pp = new SmallInfoColumn("pp", 20), - modsInfo = new ModsInfoColumn("mods"), + new InfoColumn("300", hitGreatText = new OsuSpriteText { Font = smallFont }), + new InfoColumn("100", hitGoodText = new OsuSpriteText { Font = smallFont }), + new InfoColumn("50", hitMehText = new OsuSpriteText { Font = smallFont }), + new InfoColumn("misses", hitMissText = new OsuSpriteText { Font = smallFont }), + new InfoColumn("pp", ppText = new OsuSpriteText { Font = smallFont }), + modsInfo = new ModsInfoColumn(), } }, new FillFlowContainer @@ -225,9 +228,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Spacing = new Vector2(margin, 0), Children = new Drawable[] { - totalScore = new AutoSizedInfoColumn("Total Score"), - accuracy = new AutoSizedInfoColumn("Accuracy"), - maxCombo = new MediumInfoColumn("Max Combo"), + new InfoColumn("total score", totalScoreText = new OsuSpriteText { Font = largeFont }), + new InfoColumn("accuracy", accuracyText = new OsuSpriteText { Font = largeFont }), + new InfoColumn("max combo", maxComboText = new OsuSpriteText { Font = largeFont }) } }, } @@ -302,10 +305,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores hoverContainer.Colour = colours.Blue; } - protected override void OnUserChanged(User user) - { - normalText.Text = hoveredText.Text = user.Username; - } + protected override void OnUserChanged(User user) => normalText.Text = hoveredText.Text = user.Username; protected override bool OnHover(HoverEvent e) { @@ -320,38 +320,32 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - private class DrawableInfoColumn : FillFlowContainer + private class InfoColumn : CompositeDrawable { - private const float header_text_size = 12; + private readonly Box separator; - private readonly Box line; - - protected DrawableInfoColumn(string header) + public InfoColumn(string title, Drawable content) { - AutoSizeAxes = Axes.Y; - Direction = FillDirection.Vertical; - Spacing = new Vector2(0, 2); - Children = new Drawable[] + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer { - new Container + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 2), + Children = new[] { - AutoSizeAxes = Axes.X, - Height = header_text_size, - Child = new SpriteText + new OsuSpriteText { - TextSize = 12, - Text = header.ToUpper(), - Font = @"Exo2.0-Black", - } - }, - new Container - { - RelativeSizeAxes = Axes.X, - Height = 2, - Child = line = new Box + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black), + Text = title.ToUpper() + }, + separator = new Box { - RelativeSizeAxes = Axes.Both, - } + RelativeSizeAxes = Axes.X, + Height = 2 + }, + content } }; } @@ -359,96 +353,45 @@ namespace osu.Game.Overlays.BeatmapSet.Scores [BackgroundDependencyLoader] private void load(OsuColour colours) { - line.Colour = colours.Gray5; + separator.Colour = colours.Gray5; } } - private class ModsInfoColumn : DrawableInfoColumn + private class ModsInfoColumn : InfoColumn { private readonly FillFlowContainer modsContainer; + public ModsInfoColumn() + : this(new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal + }) + { + } + + private ModsInfoColumn(FillFlowContainer modsContainer) + : base("mods", modsContainer) + { + this.modsContainer = modsContainer; + } + public IEnumerable Mods { set { + modsContainer.Clear(); + foreach (Mod mod in value) + { modsContainer.Add(new ModIcon(mod) { AutoSizeAxes = Axes.Both, Scale = new Vector2(0.3f), }); + } } } - - public ModsInfoColumn(string header) - : base(header) - { - AutoSizeAxes = Axes.Both; - Add(modsContainer = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - }); - } - - public void ClearMods() => modsContainer.Clear(); - } - - private class TextInfoColumn : DrawableInfoColumn - { - private readonly SpriteText valueText; - - public string Value - { - set - { - if (valueText.Text == value) - return; - - valueText.Text = value; - } - get => valueText.Text; - } - - protected TextInfoColumn(string header, float valueTextSize = 25) - : base(header) - { - Add(valueText = new SpriteText - { - TextSize = valueTextSize, - }); - } - } - - private class AutoSizedInfoColumn : TextInfoColumn - { - public AutoSizedInfoColumn(string header, float valueTextSize = 25) - : base(header, valueTextSize) - { - AutoSizeAxes = Axes.Both; - } - } - - private class MediumInfoColumn : TextInfoColumn - { - private const float width = 70; - - public MediumInfoColumn(string header, float valueTextSize = 25) - : base(header, valueTextSize) - { - Width = width; - } - } - - private class SmallInfoColumn : TextInfoColumn - { - private const float width = 40; - - public SmallInfoColumn(string header, float valueTextSize = 25) - : base(header, valueTextSize) - { - Width = width; - } } } } From f8d53a1e30b2f3471ac7712983dc8af703a79272 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Apr 2019 15:43:36 +0900 Subject: [PATCH 346/623] Fix variable mismatch --- build/build.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.cake b/build/build.cake index 3269bbf1c0..f1b7fa4036 100644 --- a/build/build.cake +++ b/build/build.cake @@ -46,7 +46,7 @@ Task("InspectCode") OutputFile = "inspectcodereport.xml", }); - int returnCode = StartProcess(nVikaToolPath, $@"parsereport ""{inspectcodereport}"" --treatwarningsaserrors"); + int returnCode = StartProcess(nVikaToolPath, $@"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors"); if (returnCode != 0) throw new Exception($"inspectcode failed with return code {returnCode}"); }); From 542a46b0a7675cc9b4676cdceeb1326257217261 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Apr 2019 15:43:48 +0900 Subject: [PATCH 347/623] Update r# version --- build/build.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.cake b/build/build.cake index f1b7fa4036..de94eb7ab3 100644 --- a/build/build.cake +++ b/build/build.cake @@ -1,5 +1,5 @@ #addin "nuget:?package=CodeFileSanity&version=0.0.21" -#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.2" +#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.3.4" #tool "nuget:?package=NVika.MSBuild&version=1.0.1" var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); From 0cf27e244d4948d9425cdfe18941800a96d5226e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Apr 2019 15:44:14 +0900 Subject: [PATCH 348/623] Fix usages of SpriteText and obsolete members --- osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 66ef2a30a3..6e5104b137 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -125,13 +125,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Spacing = new Vector2(margin, 0), Children = new Drawable[] { - rankText = new SpriteText + rankText = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "#1", - TextSize = 30, - Font = @"Exo2.0-BoldItalic", + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold, italics: true) }, rank = new DrawableRank(ScoreRank.F) { @@ -173,8 +172,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - TextSize = 15, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold) }, flag = new DrawableFlag { From d6b15f040a7b7cab448905b4b9d562105c8ff3c1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Apr 2019 15:57:36 +0900 Subject: [PATCH 349/623] Improve statistics display --- .../BeatmapSet/Scores/DrawableTopScore.cs | 78 ++++++++++++------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 6e5104b137..052eaf60e5 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -12,13 +12,15 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Users; using osuTK; using osuTK.Graphics; using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions; +using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.BeatmapSet.Scores @@ -26,7 +28,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public class DrawableTopScore : Container { private const float fade_duration = 100; - private const float height = 100; private const float avatar_size = 80; private const float margin = 10; @@ -35,6 +36,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private Color4 backgroundIdleColour => colours.Gray3; private Color4 backgroundHoveredColour => colours.Gray4; + private readonly FontUsage smallStatisticsFont = OsuFont.GetFont(size: 20); + private readonly FontUsage largeStatisticsFont = OsuFont.GetFont(size: 25); + private readonly Box background; private readonly UpdateableAvatar avatar; private readonly DrawableFlag flag; @@ -43,14 +47,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly SpriteText date; private readonly DrawableRank rank; - private readonly SpriteText totalScoreText; - private readonly SpriteText accuracyText; - private readonly SpriteText maxComboText; - private readonly SpriteText hitGreatText; - private readonly SpriteText hitGoodText; - private readonly SpriteText hitMehText; - private readonly SpriteText hitMissText; - private readonly SpriteText ppText; + private readonly FillFlowContainer statisticsContainer; + + private readonly TextColumn totalScoreColumn; + private readonly TextColumn accuracyColumn; + private readonly TextColumn maxComboColumn; + private readonly TextColumn ppColumn; private readonly ModsInfoColumn modsInfo; @@ -71,15 +73,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores date.Text = $@"achieved {score.Date.Humanize()}"; rank.UpdateRank(score.Rank); - totalScoreText.Text = $@"{score.TotalScore:N0}"; - accuracyText.Text = $@"{score.Accuracy:P2}"; - maxComboText.Text = $@"{score.MaxCombo:N0}x"; + totalScoreColumn.Text = $@"{score.TotalScore:N0}"; + accuracyColumn.Text = $@"{score.Accuracy:P2}"; + maxComboColumn.Text = $@"{score.MaxCombo:N0}x"; + ppColumn.Text = $@"{score.PP:N0}"; - hitGreatText.Text = $"{score.Statistics[HitResult.Great]}"; - hitGoodText.Text = $"{score.Statistics[HitResult.Good]}"; - hitMehText.Text = $"{score.Statistics[HitResult.Meh]}"; - hitMissText.Text = $"{score.Statistics[HitResult.Miss]}"; - ppText.Text = $@"{score.PP:N0}"; + statisticsContainer.ChildrenEnumerable = score.Statistics.Select(kvp => new TextColumn(kvp.Key.GetDescription(), smallStatisticsFont) { Text = kvp.Value.ToString() }); modsInfo.Mods = score.Mods; } @@ -100,9 +99,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Offset = new Vector2(0, 1), }; - var smallFont = OsuFont.GetFont(size: 20); - var largeFont = OsuFont.GetFont(size: 25); - Children = new Drawable[] { background = new Box @@ -209,11 +205,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Spacing = new Vector2(margin, 0), Children = new Drawable[] { - new InfoColumn("300", hitGreatText = new OsuSpriteText { Font = smallFont }), - new InfoColumn("100", hitGoodText = new OsuSpriteText { Font = smallFont }), - new InfoColumn("50", hitMehText = new OsuSpriteText { Font = smallFont }), - new InfoColumn("misses", hitMissText = new OsuSpriteText { Font = smallFont }), - new InfoColumn("pp", ppText = new OsuSpriteText { Font = smallFont }), + statisticsContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(margin, 0), + }, + ppColumn = new TextColumn("pp", smallStatisticsFont), modsInfo = new ModsInfoColumn(), } }, @@ -226,9 +224,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Spacing = new Vector2(margin, 0), Children = new Drawable[] { - new InfoColumn("total score", totalScoreText = new OsuSpriteText { Font = largeFont }), - new InfoColumn("accuracy", accuracyText = new OsuSpriteText { Font = largeFont }), - new InfoColumn("max combo", maxComboText = new OsuSpriteText { Font = largeFont }) + totalScoreColumn = new TextColumn("total score", largeStatisticsFont), + accuracyColumn = new TextColumn("accuracy", largeStatisticsFont), + maxComboColumn = new TextColumn("max combo", largeStatisticsFont) } }, } @@ -355,6 +353,28 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } + private class TextColumn : InfoColumn + { + private readonly SpriteText text; + + public TextColumn(string title, FontUsage font) + : this(title, new OsuSpriteText { Font = font }) + { + } + + private TextColumn(string title, SpriteText text) + : base(title, text) + { + this.text = text; + } + + public LocalisedString Text + { + get => text.Text; + set => text.Text = value; + } + } + private class ModsInfoColumn : InfoColumn { private readonly FillFlowContainer modsContainer; From 2c18b6df1c21e0983bc7536d95923a41a404ea82 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Apr 2019 16:09:19 +0900 Subject: [PATCH 350/623] Fix score table using 300/100/50 --- .../TestCaseBeatmapScoresContainer.cs | 2 +- .../API/Requests/Responses/APILegacyScores.cs | 2 +- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 21 ++++++----- .../BeatmapSet/Scores/ScoreTableHeaderRow.cs | 22 ++++++++---- .../BeatmapSet/Scores/ScoreTableScoreRow.cs | 35 +++++-------------- .../BeatmapSet/Scores/ScoresContainer.cs | 8 ++--- 6 files changed, 41 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs index 4a38f98810..b30d6b1546 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.SongSelect } }; - IEnumerable scores = new[] + var scores = new List { new ScoreInfo { diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs index 15ec5d3b13..c629caaa6f 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs @@ -9,6 +9,6 @@ namespace osu.Game.Online.API.Requests.Responses public class APILegacyScores { [JsonProperty(@"scores")] - public IEnumerable Scores; + public List Scores; } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 08e73da841..ada442d50e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -48,24 +48,27 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }; } - public IEnumerable Scores + public IReadOnlyList Scores { set { if (value == null || !value.Any()) return; - var content = new List { new ScoreTableHeaderRow().CreateDrawables().ToArray() }; - - int index = 0; - foreach (var s in value) - content.Add(new ScoreTableScoreRow(index++, s).CreateDrawables().ToArray()); - - scoresGrid.Content = content.ToArray(); + var content = new List + { + new ScoreTableHeaderRow(value.First()).CreateDrawables().ToArray() + }; backgroundFlow.Clear(); - for (int i = 0; i < index; i++) + + for (int i = 0; i < value.Count; i++) + { + content.Add(new ScoreTableScoreRow(i, value[i]).CreateDrawables().ToArray()); backgroundFlow.Add(new ScoreTableRowBackground(i)); + } + + scoresGrid.Content = content.ToArray(); } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeaderRow.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeaderRow.cs index a48716b71a..c73543eb10 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeaderRow.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeaderRow.cs @@ -2,15 +2,24 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Scoring; namespace osu.Game.Overlays.BeatmapSet.Scores { public class ScoreTableHeaderRow : ScoreTableRow { + private readonly ScoreInfo score; + + public ScoreTableHeaderRow(ScoreInfo score) + { + this.score = score; + } + protected override Drawable CreateIndexCell() => new CellText("rank"); protected override Drawable CreateRankCell() => new Container(); @@ -21,14 +30,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores protected override Drawable CreatePlayerCell() => new CellText("player"); - protected override IEnumerable CreateStatisticsCells() => new[] + protected override IEnumerable CreateStatisticsCells() { - new CellText("max combo"), - new CellText("300"), - new CellText("100"), - new CellText("50"), - new CellText("miss"), - }; + yield return new CellText("max combo"); + + foreach (var kvp in score.Statistics) + yield return new CellText(kvp.Key.GetDescription()); + } protected override Drawable CreatePpCell() => new CellText("pp"); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs index 83901cc662..2cb16a5785 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs @@ -10,7 +10,6 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Leaderboards; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Users; @@ -74,33 +73,15 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Font = OsuFont.GetFont(size: TEXT_SIZE) }; - yield return new OsuSpriteText + foreach (var kvp in score.Statistics) { - Text = $"{score.Statistics[HitResult.Great]}", - Font = OsuFont.GetFont(size: TEXT_SIZE), - Colour = score.Statistics[HitResult.Great] == 0 ? Color4.Gray : Color4.White - }; - - yield return new OsuSpriteText - { - Text = $"{score.Statistics[HitResult.Good]}", - Font = OsuFont.GetFont(size: TEXT_SIZE), - Colour = score.Statistics[HitResult.Good] == 0 ? Color4.Gray : Color4.White - }; - - yield return new OsuSpriteText - { - Text = $"{score.Statistics[HitResult.Meh]}", - Font = OsuFont.GetFont(size: TEXT_SIZE), - Colour = score.Statistics[HitResult.Meh] == 0 ? Color4.Gray : Color4.White - }; - - yield return new OsuSpriteText - { - Text = $"{score.Statistics[HitResult.Miss]}", - Font = OsuFont.GetFont(size: TEXT_SIZE), - Colour = score.Statistics[HitResult.Miss] == 0 ? Color4.Gray : Color4.White - }; + yield return new OsuSpriteText + { + Text = $"{kvp.Value}", + Font = OsuFont.GetFont(size: TEXT_SIZE), + Colour = kvp.Value == 0 ? Color4.Gray : Color4.White + }; + } } protected override Drawable CreatePpCell() => new OsuSpriteText diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index a29d812088..de080732f9 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -83,9 +83,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } private GetScoresRequest getScoresRequest; - private IEnumerable scores; + private IReadOnlyList scores; - public IEnumerable Scores + public IReadOnlyList Scores { get => scores; set @@ -121,11 +121,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private void updateDisplay() { - scoreTable.Scores = Enumerable.Empty(); + scoreTable.Scores = new List(); loading = false; - var scoreCount = scores?.Count() ?? 0; + var scoreCount = scores?.Count ?? 0; if (scoreCount == 0) { From e246fad301616d1b97ec20a5f21a2ef4186e731f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Apr 2019 16:09:34 +0900 Subject: [PATCH 351/623] Clear table if no scores are provided --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index ada442d50e..09ab0e3f6a 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -52,6 +52,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { set { + scoresGrid.Content = new Drawable[0][]; + backgroundFlow.Clear(); + if (value == null || !value.Any()) return; @@ -60,8 +63,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new ScoreTableHeaderRow(value.First()).CreateDrawables().ToArray() }; - backgroundFlow.Clear(); - for (int i = 0; i < value.Count; i++) { content.Add(new ScoreTableScoreRow(i, value[i]).CreateDrawables().ToArray()); From 42c915190a968c340d6185ed10539ec2161b8c70 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Apr 2019 16:12:24 +0900 Subject: [PATCH 352/623] Reorder DrawableTopScore --- .../BeatmapSet/Scores/DrawableTopScore.cs | 62 +++++++++---------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 052eaf60e5..f22c6b3529 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -31,10 +31,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private const float avatar_size = 80; private const float margin = 10; - private OsuColour colours; - - private Color4 backgroundIdleColour => colours.Gray3; - private Color4 backgroundHoveredColour => colours.Gray4; + private Color4 backgroundIdleColour; + private Color4 backgroundHoveredColour; private readonly FontUsage smallStatisticsFont = OsuFont.GetFont(size: 20); private readonly FontUsage largeStatisticsFont = OsuFont.GetFont(size: 25); @@ -56,34 +54,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly ModsInfoColumn modsInfo; - private ScoreInfo score; - - public ScoreInfo Score - { - get => score; - set - { - if (score == value) - return; - - score = value; - - avatar.User = username.User = score.User; - flag.Country = score.User.Country; - date.Text = $@"achieved {score.Date.Humanize()}"; - rank.UpdateRank(score.Rank); - - totalScoreColumn.Text = $@"{score.TotalScore:N0}"; - accuracyColumn.Text = $@"{score.Accuracy:P2}"; - maxComboColumn.Text = $@"{score.MaxCombo:N0}x"; - ppColumn.Text = $@"{score.PP:N0}"; - - statisticsContainer.ChildrenEnumerable = score.Statistics.Select(kvp => new TextColumn(kvp.Key.GetDescription(), smallStatisticsFont) { Text = kvp.Value.ToString() }); - - modsInfo.Mods = score.Mods; - } - } - public DrawableTopScore() { RelativeSizeAxes = Axes.X; @@ -240,12 +210,36 @@ namespace osu.Game.Overlays.BeatmapSet.Scores [BackgroundDependencyLoader] private void load(OsuColour colours) { - this.colours = colours; - + backgroundIdleColour = colours.Gray3; + backgroundHoveredColour = colours.Gray4; rankText.Colour = colours.Yellow; + background.Colour = backgroundIdleColour; } + /// + /// Sets the score to be displayed. + /// + public ScoreInfo Score + { + set + { + avatar.User = username.User = value.User; + flag.Country = value.User.Country; + date.Text = $@"achieved {value.Date.Humanize()}"; + rank.UpdateRank(value.Rank); + + totalScoreColumn.Text = $@"{value.TotalScore:N0}"; + accuracyColumn.Text = $@"{value.Accuracy:P2}"; + maxComboColumn.Text = $@"{value.MaxCombo:N0}x"; + ppColumn.Text = $@"{value.PP:N0}"; + + statisticsContainer.ChildrenEnumerable = value.Statistics.Select(kvp => new TextColumn(kvp.Key.GetDescription(), smallStatisticsFont) { Text = kvp.Value.ToString() }); + + modsInfo.Mods = value.Mods; + } + } + protected override bool OnHover(HoverEvent e) { background.FadeColour(backgroundHoveredColour, fade_duration, Easing.OutQuint); From f8596e055ad8a4c0379666a0913add225a15c456 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Apr 2019 16:36:38 +0900 Subject: [PATCH 353/623] Separate into multiple files --- .../TestCaseBeatmapScoresContainer.cs | 2 + .../BeatmapSet/Scores/DrawableTopScore.cs | 329 ++---------------- .../Scores/TopScoreStatisticsSection.cs | 204 +++++++++++ .../BeatmapSet/Scores/TopScoreUserSection.cs | 181 ++++++++++ 4 files changed, 409 insertions(+), 307 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs create mode 100644 osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs index b30d6b1546..0457e1469a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs @@ -25,6 +25,8 @@ namespace osu.Game.Tests.Visual.SongSelect public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableTopScore), + typeof(TopScoreUserSection), + typeof(TopScoreStatisticsSection), typeof(ScoresContainer), typeof(ScoreTable), typeof(ScoreTableRow), diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index f22c6b3529..3667fc3f9f 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -1,58 +1,29 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using Humanizer; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Online.Leaderboards; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; using osu.Game.Scoring; -using osu.Game.Users; using osuTK; using osuTK.Graphics; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Extensions; -using osu.Framework.Localisation; -using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.BeatmapSet.Scores { public class DrawableTopScore : Container { private const float fade_duration = 100; - private const float avatar_size = 80; - private const float margin = 10; private Color4 backgroundIdleColour; private Color4 backgroundHoveredColour; - private readonly FontUsage smallStatisticsFont = OsuFont.GetFont(size: 20); - private readonly FontUsage largeStatisticsFont = OsuFont.GetFont(size: 25); - private readonly Box background; - private readonly UpdateableAvatar avatar; - private readonly DrawableFlag flag; - private readonly ClickableTopScoreUsername username; - private readonly SpriteText rankText; - private readonly SpriteText date; - private readonly DrawableRank rank; - - private readonly FillFlowContainer statisticsContainer; - - private readonly TextColumn totalScoreColumn; - private readonly TextColumn accuracyColumn; - private readonly TextColumn maxComboColumn; - private readonly TextColumn ppColumn; - - private readonly ModsInfoColumn modsInfo; + private readonly TopScoreUserSection userSection; + private readonly TopScoreStatisticsSection statisticsSection; public DrawableTopScore() { @@ -79,128 +50,30 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(margin), + Padding = new MarginPadding(10), Children = new Drawable[] { - new FillFlowContainer + new AutoSizingGrid { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(margin, 0), - Children = new Drawable[] - { - rankText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "#1", - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold, italics: true) - }, - rank = new DrawableRank(ScoreRank.F) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(40), - FillMode = FillMode.Fit, - }, - avatar = new UpdateableAvatar - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(avatar_size), - Masking = true, - CornerRadius = 5, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Offset = new Vector2(0, 2), - Radius = 1, - }, - }, - new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 3), - Children = new Drawable[] - { - username = new ClickableTopScoreUsername - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - date = new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold) - }, - flag = new DrawableFlag - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(20, 13), - }, - } - } - } - }, - new Container - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.65f, - Child = new FillFlowContainer + Content = new[] { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Spacing = new Vector2(margin), - Children = new Drawable[] + new Drawable[] { - new FillFlowContainer + userSection = new TopScoreUserSection { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(margin, 0), - Children = new Drawable[] - { - statisticsContainer = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(margin, 0), - }, - ppColumn = new TextColumn("pp", smallStatisticsFont), - modsInfo = new ModsInfoColumn(), - } + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, }, - new FillFlowContainer + statisticsSection = new TopScoreStatisticsSection { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(margin, 0), - Children = new Drawable[] - { - totalScoreColumn = new TextColumn("total score", largeStatisticsFont), - accuracyColumn = new TextColumn("accuracy", largeStatisticsFont), - maxComboColumn = new TextColumn("max combo", largeStatisticsFont) - } - }, - } - } + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } + }, + }, + ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, } } } @@ -212,7 +85,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { backgroundIdleColour = colours.Gray3; backgroundHoveredColour = colours.Gray4; - rankText.Colour = colours.Yellow; background.Colour = backgroundIdleColour; } @@ -224,19 +96,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { set { - avatar.User = username.User = value.User; - flag.Country = value.User.Country; - date.Text = $@"achieved {value.Date.Humanize()}"; - rank.UpdateRank(value.Rank); - - totalScoreColumn.Text = $@"{value.TotalScore:N0}"; - accuracyColumn.Text = $@"{value.Accuracy:P2}"; - maxComboColumn.Text = $@"{value.MaxCombo:N0}x"; - ppColumn.Text = $@"{value.PP:N0}"; - - statisticsContainer.ChildrenEnumerable = value.Statistics.Select(kvp => new TextColumn(kvp.Key.GetDescription(), smallStatisticsFont) { Text = kvp.Value.ToString() }); - - modsInfo.Mods = value.Mods; + userSection.Score = value; + statisticsSection.Score = value; } } @@ -252,157 +113,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores base.OnHoverLost(e); } - private class ClickableTopScoreUsername : ClickableUserContainer + private class AutoSizingGrid : GridContainer { - private const float username_fade_duration = 150; - - private readonly FillFlowContainer hoverContainer; - - private readonly SpriteText normalText; - private readonly SpriteText hoveredText; - - public ClickableTopScoreUsername() + public AutoSizingGrid() { - var font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold, italics: true); - - Children = new Drawable[] - { - normalText = new OsuSpriteText { Font = font }, - hoverContainer = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 1), - Children = new Drawable[] - { - hoveredText = new OsuSpriteText { Font = font }, - new Box - { - BypassAutoSizeAxes = Axes.Both, - RelativeSizeAxes = Axes.X, - Height = 1 - } - } - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - hoverContainer.Colour = colours.Blue; - } - - protected override void OnUserChanged(User user) => normalText.Text = hoveredText.Text = user.Username; - - protected override bool OnHover(HoverEvent e) - { - hoverContainer.FadeIn(username_fade_duration, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - hoverContainer.FadeOut(username_fade_duration, Easing.OutQuint); - base.OnHoverLost(e); - } - } - - private class InfoColumn : CompositeDrawable - { - private readonly Box separator; - - public InfoColumn(string title, Drawable content) - { - AutoSizeAxes = Axes.Both; - - InternalChild = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 2), - Children = new[] - { - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black), - Text = title.ToUpper() - }, - separator = new Box - { - RelativeSizeAxes = Axes.X, - Height = 2 - }, - content - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - separator.Colour = colours.Gray5; - } - } - - private class TextColumn : InfoColumn - { - private readonly SpriteText text; - - public TextColumn(string title, FontUsage font) - : this(title, new OsuSpriteText { Font = font }) - { - } - - private TextColumn(string title, SpriteText text) - : base(title, text) - { - this.text = text; - } - - public LocalisedString Text - { - get => text.Text; - set => text.Text = value; - } - } - - private class ModsInfoColumn : InfoColumn - { - private readonly FillFlowContainer modsContainer; - - public ModsInfoColumn() - : this(new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal - }) - { - } - - private ModsInfoColumn(FillFlowContainer modsContainer) - : base("mods", modsContainer) - { - this.modsContainer = modsContainer; - } - - public IEnumerable Mods - { - set - { - modsContainer.Clear(); - - foreach (Mod mod in value) - { - modsContainer.Add(new ModIcon(mod) - { - AutoSizeAxes = Axes.Both, - Scale = new Vector2(0.3f), - }); - } - } + AutoSizeAxes = Axes.Y; } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs new file mode 100644 index 0000000000..6761d0f710 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -0,0 +1,204 @@ +// Copyright (c) ppy Pty Ltd . 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; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; +using osuTK; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class TopScoreStatisticsSection : CompositeDrawable + { + private const float margin = 10; + + private readonly FontUsage smallFont = OsuFont.GetFont(size: 20); + private readonly FontUsage largeFont = OsuFont.GetFont(size: 25); + + private readonly TextColumn totalScoreColumn; + private readonly TextColumn accuracyColumn; + private readonly TextColumn maxComboColumn; + private readonly TextColumn ppColumn; + + private readonly FillFlowContainer statisticsColumns; + private readonly ModsInfoColumn modsColumn; + + public TopScoreStatisticsSection() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(margin, 0), + Children = new Drawable[] + { + statisticsColumns = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(margin, 0), + }, + ppColumn = new TextColumn("pp", smallFont), + modsColumn = new ModsInfoColumn(), + } + }, + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(margin, 0), + Children = new Drawable[] + { + totalScoreColumn = new TextColumn("total score", largeFont), + accuracyColumn = new TextColumn("accuracy", largeFont), + maxComboColumn = new TextColumn("max combo", largeFont) + } + }, + } + }; + } + + /// + /// Sets the score to be displayed. + /// + public ScoreInfo Score + { + set + { + totalScoreColumn.Text = $@"{value.TotalScore:N0}"; + accuracyColumn.Text = $@"{value.Accuracy:P2}"; + maxComboColumn.Text = $@"{value.MaxCombo:N0}x"; + ppColumn.Text = $@"{value.PP:N0}"; + + statisticsColumns.ChildrenEnumerable = value.Statistics.Select(kvp => createStatisticsColumn(kvp.Key, kvp.Value)); + modsColumn.Mods = value.Mods; + } + } + + private TextColumn createStatisticsColumn(HitResult hitResult, int count) => new TextColumn(hitResult.GetDescription(), smallFont) + { + Text = count.ToString() + }; + + private class InfoColumn : CompositeDrawable + { + private readonly Box separator; + + public InfoColumn(string title, Drawable content) + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 2), + Children = new[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black), + Text = title.ToUpper() + }, + separator = new Box + { + RelativeSizeAxes = Axes.X, + Height = 2 + }, + content + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + separator.Colour = colours.Gray5; + } + } + + private class TextColumn : InfoColumn + { + private readonly SpriteText text; + + public TextColumn(string title, FontUsage font) + : this(title, new OsuSpriteText { Font = font }) + { + } + + private TextColumn(string title, SpriteText text) + : base(title, text) + { + this.text = text; + } + + public LocalisedString Text + { + set => text.Text = value; + } + } + + private class ModsInfoColumn : InfoColumn + { + private readonly FillFlowContainer modsContainer; + + public ModsInfoColumn() + : this(new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal + }) + { + } + + private ModsInfoColumn(FillFlowContainer modsContainer) + : base("mods", modsContainer) + { + this.modsContainer = modsContainer; + } + + public IEnumerable Mods + { + set + { + modsContainer.Clear(); + + foreach (Mod mod in value) + { + modsContainer.Add(new ModIcon(mod) + { + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.3f), + }); + } + } + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs new file mode 100644 index 0000000000..1ec4b7197d --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -0,0 +1,181 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Humanizer; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Leaderboards; +using osu.Game.Scoring; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class TopScoreUserSection : CompositeDrawable + { + private readonly SpriteText rankText; + private readonly DrawableRank rank; + private readonly UpdateableAvatar avatar; + private readonly UsernameText usernameText; + private readonly SpriteText date; + private readonly DrawableFlag flag; + + public TopScoreUserSection() + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + rankText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "#1", + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold, italics: true) + }, + rank = new DrawableRank(ScoreRank.F) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(40), + FillMode = FillMode.Fit, + }, + avatar = new UpdateableAvatar + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(80), + Masking = true, + CornerRadius = 5, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.25f), + Offset = new Vector2(0, 2), + Radius = 1, + }, + }, + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 3), + Children = new Drawable[] + { + usernameText = new UsernameText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + date = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold) + }, + flag = new DrawableFlag + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(20, 13), + }, + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + rankText.Colour = colours.Yellow; + } + + /// + /// Sets the score to be displayed. + /// + public ScoreInfo Score + { + set + { + avatar.User = usernameText.User = value.User; + flag.Country = value.User.Country; + date.Text = $@"achieved {value.Date.Humanize()}"; + rank.UpdateRank(value.Rank); + } + } + + private class UsernameText : ClickableUserContainer + { + private const float username_fade_duration = 150; + + private readonly FillFlowContainer hoverContainer; + + private readonly SpriteText normalText; + private readonly SpriteText hoveredText; + + public UsernameText() + { + var font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold, italics: true); + + Children = new Drawable[] + { + normalText = new OsuSpriteText { Font = font }, + hoverContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 1), + Children = new Drawable[] + { + hoveredText = new OsuSpriteText { Font = font }, + new Box + { + BypassAutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + Height = 1 + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoverContainer.Colour = colours.Blue; + } + + protected override void OnUserChanged(User user) => normalText.Text = hoveredText.Text = user.Username; + + protected override bool OnHover(HoverEvent e) + { + hoverContainer.FadeIn(username_fade_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoverContainer.FadeOut(username_fade_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + } + } +} From 3da008d29380dac150b4cd1d6fb629cace86700c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Apr 2019 16:43:56 +0900 Subject: [PATCH 354/623] Add a bit of padding between the sections --- osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 3667fc3f9f..aace49e120 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -65,6 +65,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, + null, statisticsSection = new TopScoreStatisticsSection { Anchor = Anchor.CentreRight, @@ -72,7 +73,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } }, }, - ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.Absolute, 20) }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, } } From 50f8ab3dfb07c9cc12b0fb5598ecadfdecf3b43d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Apr 2019 16:58:20 +0900 Subject: [PATCH 355/623] Fix gameplay offset handling on seeks --- .../Screens/Play/GameplayClockContainer.cs | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index c13222c6de..fc7a9ddd20 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -36,6 +36,8 @@ namespace osu.Game.Screens.Play /// private readonly DecoupleableInterpolatingFramedClock adjustableClock; + private readonly double gameplayStartTime; + public readonly Bindable UserPlaybackRate = new BindableDouble(1) { Default = 1, @@ -52,11 +54,14 @@ namespace osu.Game.Screens.Play private Bindable userAudioOffset; - private readonly FramedOffsetClock offsetClock; + private readonly FramedOffsetClock userOffsetClock; + + private readonly FramedOffsetClock platformOffsetClock; public GameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime) { this.beatmap = beatmap; + this.gameplayStartTime = gameplayStartTime; RelativeSizeAxes = Axes.Both; @@ -64,30 +69,31 @@ namespace osu.Game.Screens.Play adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - adjustableClock.Seek(Math.Min(0, gameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn)); - - adjustableClock.ProcessFrame(); - // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. - var platformOffsetClock = new FramedOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 22 : 0 }; + platformOffsetClock = new FramedOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 22 : 0 }; // the final usable gameplay clock with user-set offsets applied. - offsetClock = new FramedOffsetClock(platformOffsetClock); + userOffsetClock = new FramedOffsetClock(platformOffsetClock); // the clock to be exposed via DI to children. - GameplayClock = new GameplayClock(offsetClock); + GameplayClock = new GameplayClock(userOffsetClock); GameplayClock.IsPaused.BindTo(IsPaused); } + private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); - userAudioOffset.BindValueChanged(offset => offsetClock.Offset = offset.NewValue, true); + userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); UserPlaybackRate.ValueChanged += _ => updateRate(); + + Seek(Math.Min(0, gameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn)); + adjustableClock.ProcessFrame(); } public void Restart() @@ -104,9 +110,7 @@ namespace osu.Game.Screens.Play this.Delay(750).Schedule(() => { if (!IsPaused.Value) - { - adjustableClock.Start(); - } + Start(); }); }); }); @@ -121,7 +125,12 @@ namespace osu.Game.Screens.Play IsPaused.Value = false; } - public void Seek(double time) => adjustableClock.Seek(time); + public void Seek(double time) + { + // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. + // we may want to consider reversing the application of offsets in the future as it may feel more correct. + adjustableClock.Seek(time - totalOffset); + } public void Stop() { @@ -138,7 +147,7 @@ namespace osu.Game.Screens.Play protected override void Update() { if (!IsPaused.Value) - offsetClock.ProcessFrame(); + userOffsetClock.ProcessFrame(); base.Update(); } From efd7bea7712463013746f1a9295402303d819120 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Apr 2019 17:01:11 +0900 Subject: [PATCH 356/623] Add further xmldoc --- osu.Game/Screens/Play/GameplayClockContainer.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index fc7a9ddd20..5ded375259 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -125,6 +125,13 @@ namespace osu.Game.Screens.Play IsPaused.Value = false; } + /// + /// Seek to a specific time in gameplay. + /// + /// Adjusts for any offsets which have been applied (so the seek may not be the expected point in time on the underlying audio track). + /// + /// + /// The destination time to seek to. public void Seek(double time) { // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. From 3b7d26cca805330a1a98bb1f86059890204999dc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Apr 2019 17:49:01 +0900 Subject: [PATCH 357/623] Remove custom styled text --- .../Online/TestCaseBeatmapSetOverlay.cs | 1 - .../Scores/ClickableUserContainer.cs | 51 ------------- .../BeatmapSet/Scores/ScoreTableScoreRow.cs | 74 ++++++------------- .../BeatmapSet/Scores/TopScoreUserSection.cs | 73 +++--------------- 4 files changed, 31 insertions(+), 168 deletions(-) delete mode 100644 osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs diff --git a/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs index 19d295cf12..9cbccbd603 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs @@ -24,7 +24,6 @@ namespace osu.Game.Tests.Visual.Online public override IReadOnlyList RequiredTypes => new[] { typeof(Header), - typeof(ClickableUserContainer), typeof(ScoreTable), typeof(ScoreTableRow), typeof(ScoreTableHeaderRow), diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs deleted file mode 100644 index 2448ae17f8..0000000000 --- a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUserContainer.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Events; -using osu.Game.Users; - -namespace osu.Game.Overlays.BeatmapSet.Scores -{ - public abstract class ClickableUserContainer : Container - { - private UserProfileOverlay profile; - - protected ClickableUserContainer() - { - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader(true)] - private void load(UserProfileOverlay profile) - { - this.profile = profile; - } - - private User user; - - public User User - { - get => user; - set - { - if (user == value) - return; - - user = value; - - OnUserChanged(user); - } - } - - protected abstract void OnUserChanged(User user); - - protected override bool OnClick(ClickEvent e) - { - profile?.ShowUser(user); - return true; - } - } -} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs index 2cb16a5785..adb79eedfa 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs @@ -5,10 +5,10 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets.UI; using osu.Game.Scoring; @@ -53,17 +53,27 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Colour = score.Accuracy == 1 ? Color4.GreenYellow : Color4.White }; - protected override Drawable CreatePlayerCell() => new FillFlowContainer + protected override Drawable CreatePlayerCell() { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), - Children = new Drawable[] + var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE)) { - new DrawableFlag(score.User.Country) { Size = new Vector2(20, 13) }, - new ClickableScoreUsername { User = score.User } - } - }; + AutoSizeAxes = Axes.Both, + }; + + username.AddLink(score.User.Username, null, LinkAction.OpenUserProfile, score.User.Id.ToString(), "Open profile"); + + return new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new DrawableFlag(score.User.Country) { Size = new Vector2(20, 13) }, + username + } + }; + } protected override IEnumerable CreateStatisticsCells() { @@ -100,47 +110,5 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Scale = new Vector2(0.3f) }) }; - - private class ClickableScoreUsername : ClickableUserContainer - { - private const int fade_duration = 100; - - private readonly SpriteText text; - private readonly SpriteText textBold; - - public ClickableScoreUsername() - { - Add(text = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: TEXT_SIZE) - }); - - Add(textBold = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold), - Alpha = 0, - }); - } - - protected override void OnUserChanged(User user) => text.Text = textBold.Text = user.Username; - - protected override bool OnHover(HoverEvent e) - { - textBold.Show(); - text.Hide(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - textBold.Hide(); - text.Show(); - base.OnHoverLost(e); - } - } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index 1ec4b7197d..f401dc93a7 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -6,11 +6,11 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; using osu.Game.Online.Leaderboards; using osu.Game.Scoring; using osu.Game.Users; @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly SpriteText rankText; private readonly DrawableRank rank; private readonly UpdateableAvatar avatar; - private readonly UsernameText usernameText; + private readonly LinkFlowContainer usernameText; private readonly SpriteText date; private readonly DrawableFlag flag; @@ -77,10 +77,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Spacing = new Vector2(0, 3), Children = new Drawable[] { - usernameText = new UsernameText + usernameText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold, italics: true)) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, }, date = new SpriteText { @@ -113,69 +114,15 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { set { - avatar.User = usernameText.User = value.User; + avatar.User = value.User; flag.Country = value.User.Country; date.Text = $@"achieved {value.Date.Humanize()}"; + + usernameText.Clear(); + usernameText.AddLink(value.User.Username, null, LinkAction.OpenUserProfile, value.User.Id.ToString(), "Open profile"); + rank.UpdateRank(value.Rank); } } - - private class UsernameText : ClickableUserContainer - { - private const float username_fade_duration = 150; - - private readonly FillFlowContainer hoverContainer; - - private readonly SpriteText normalText; - private readonly SpriteText hoveredText; - - public UsernameText() - { - var font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold, italics: true); - - Children = new Drawable[] - { - normalText = new OsuSpriteText { Font = font }, - hoverContainer = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 1), - Children = new Drawable[] - { - hoveredText = new OsuSpriteText { Font = font }, - new Box - { - BypassAutoSizeAxes = Axes.Both, - RelativeSizeAxes = Axes.X, - Height = 1 - } - } - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - hoverContainer.Colour = colours.Blue; - } - - protected override void OnUserChanged(User user) => normalText.Text = hoveredText.Text = user.Username; - - protected override bool OnHover(HoverEvent e) - { - hoverContainer.FadeIn(username_fade_duration, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - hoverContainer.FadeOut(username_fade_duration, Easing.OutQuint); - base.OnHoverLost(e); - } - } } } From 5d37851d34b716d361cd67fa8bef60e81e36a399 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Apr 2019 18:14:59 +0900 Subject: [PATCH 358/623] Rename and move test to correct location --- .../TestCaseScoresContainer.cs} | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) rename osu.Game.Tests/Visual/{SongSelect/TestCaseBeatmapScoresContainer.cs => Online/TestCaseScoresContainer.cs} (96%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestCaseScoresContainer.cs similarity index 96% rename from osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs rename to osu.Game.Tests/Visual/Online/TestCaseScoresContainer.cs index 0457e1469a..3d87e1743c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseScoresContainer.cs @@ -15,19 +15,16 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Users; -using NUnit.Framework; -namespace osu.Game.Tests.Visual.SongSelect +namespace osu.Game.Tests.Visual.Online { - [Description("in BeatmapOverlay")] - public class TestCaseBeatmapScoresContainer : OsuTestCase + public class TestCaseScoresContainer : OsuTestCase { public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableTopScore), typeof(TopScoreUserSection), typeof(TopScoreStatisticsSection), - typeof(ScoresContainer), typeof(ScoreTable), typeof(ScoreTableRow), typeof(ScoreTableHeaderRow), @@ -37,7 +34,7 @@ namespace osu.Game.Tests.Visual.SongSelect private readonly Box background; - public TestCaseBeatmapScoresContainer() + public TestCaseScoresContainer() { ScoresContainer scoresContainer; From 5c3c08566f4f99e6f414ae618a23739cd5a4df8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Apr 2019 18:20:03 +0900 Subject: [PATCH 359/623] DrawableTopScore can be composite --- osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index aace49e120..d8bd15a4a9 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -14,7 +14,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet.Scores { - public class DrawableTopScore : Container + public class DrawableTopScore : CompositeDrawable { private const float fade_duration = 100; @@ -40,7 +40,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Offset = new Vector2(0, 1), }; - Children = new Drawable[] + InternalChildren = new Drawable[] { background = new Box { From a15a9bd03aa95502172ac33958c2cb36e7e5c13f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Apr 2019 18:29:46 +0900 Subject: [PATCH 360/623] Rewrite updateDisplay logic to not fail on some cases --- .../BeatmapSet/Scores/ScoresContainer.cs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index de080732f9..ef2b14b8f0 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -121,25 +121,17 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private void updateDisplay() { - scoreTable.Scores = new List(); - loading = false; - var scoreCount = scores?.Count ?? 0; + scoreTable.Scores = scores?.Count > 1 ? scores : new List(); - if (scoreCount == 0) + if (scores.Any()) { - topScore.Hide(); - return; + topScore.Score = scores.FirstOrDefault(); + topScore.Show(); } - - topScore.Score = scores.FirstOrDefault(); - topScore.Show(); - - if (scoreCount < 2) - return; - - scoreTable.Scores = scores; + else + topScore.Hide(); } protected override void Dispose(bool isDisposing) From cb417ebd777085599a2d848e631db49b13a4c8d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Apr 2019 18:44:19 +0900 Subject: [PATCH 361/623] Player -> User --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeaderRow.cs | 2 +- osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs | 4 ++-- osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeaderRow.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeaderRow.cs index c73543eb10..f6f410a729 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeaderRow.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeaderRow.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores protected override Drawable CreateAccuracyCell() => new CellText("accuracy"); - protected override Drawable CreatePlayerCell() => new CellText("player"); + protected override Drawable CreateUserCell() => new CellText("player"); protected override IEnumerable CreateStatisticsCells() { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs index 8efda73270..15355512ba 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs @@ -53,7 +53,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Right = 20 }, - Child = CreatePlayerCell() + Child = CreateUserCell() }; foreach (var cell in CreateStatisticsCells()) @@ -93,7 +93,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores protected abstract Drawable CreateAccuracyCell(); - protected abstract Drawable CreatePlayerCell(); + protected abstract Drawable CreateUserCell(); protected abstract IEnumerable CreateStatisticsCells(); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs index adb79eedfa..75d45d0b23 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs @@ -53,7 +53,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Colour = score.Accuracy == 1 ? Color4.GreenYellow : Color4.White }; - protected override Drawable CreatePlayerCell() + protected override Drawable CreateUserCell() { var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE)) { From 84da708d44f2db71e7a50c93b5f49961787a8c3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Apr 2019 18:46:08 +0900 Subject: [PATCH 362/623] Fix nullref --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index ef2b14b8f0..8ef3f71fe3 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -125,7 +125,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores scoreTable.Scores = scores?.Count > 1 ? scores : new List(); - if (scores.Any()) + if (scores?.Any() == true) { topScore.Score = scores.FirstOrDefault(); topScore.Show(); From 2ed945605ec06da3c2ff57493a83c4bc953904b3 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 3 Apr 2019 19:57:22 +0900 Subject: [PATCH 363/623] Fix Axes.None requirement for FacadeContainer --- .../Visual/TestCaseLogoFacadeContainer.cs | 292 +++++++++++------- osu.Game.Tests/Visual/TestCasePositionAxes.cs | 33 ++ .../Containers/LogoFacadeContainer.cs | 21 +- osu.Game/Screens/Menu/ButtonSystem.cs | 2 + osu.Game/Screens/Play/PlayerLoader.cs | 6 +- 5 files changed, 229 insertions(+), 125 deletions(-) create mode 100644 osu.Game.Tests/Visual/TestCasePositionAxes.cs diff --git a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs index adbf22302b..b4d7976e4a 100644 --- a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Drawing; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -10,10 +11,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Graphics.Containers; -using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; using osuTK; @@ -21,7 +21,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual { - public class TestCaseLogoFacadeContainer : ScreenTestCase + public class TestCaseLogoFacadeContainer : OsuTestCase { public override IReadOnlyList RequiredTypes => new[] { @@ -34,23 +34,38 @@ namespace osu.Game.Tests.Visual typeof(MainMenu) }; - [Cached] private OsuLogo logo; private readonly Bindable uiScale = new Bindable(); + private LogoFacadeContainer logoFacadeContainer; + private Container transferContainer; + private Box visualBox; + private Box transferContainerBox; + private Container logoFacade; - public TestCaseLogoFacadeContainer() - { - Add(logo = new OsuLogo()); - } + private bool randomPositions = false; [BackgroundDependencyLoader] private void load(OsuConfigManager config) { + Add(logo = new OsuLogo { Scale = new Vector2(0.15f), RelativePositionAxes = Axes.None }); + config.BindWith(OsuSetting.UIScale, uiScale); AddSliderStep("Adjust scale", 0.8f, 1.5f, 1f, v => uiScale.Value = v); } + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Clear facades", () => + { + Clear(); + Add(logo = new OsuLogo { Scale = new Vector2(0.15f), RelativePositionAxes = Axes.None }); + logoFacadeContainer = null; + transferContainer = null; + }); + } + /// /// Move the facade to 0,0, then move it to a random new location while the logo is still transforming to it. /// Check if the logo is still tracking the facade. @@ -58,13 +73,11 @@ namespace osu.Game.Tests.Visual [Test] public void MoveFacadeTest() { - TestScreen screen = null; - bool randomPositions = false; AddToggleStep("Toggle move continuously", b => randomPositions = b); - AddStep("Move facade to random position", () => LoadScreen(screen = new TestScreen(randomPositions))); - AddUntilStep("Screen is current", () => screen.IsCurrentScreen()); + AddStep("Add facade containers", addFacadeContainers); + AddStep("Move facade to random position", StartTrackingRandom); waitForMove(); - AddAssert("Logo is tracking", () => screen.IsLogoTracking); + AddAssert("Logo is tracking", () => IsLogoTracking); } /// @@ -73,12 +86,11 @@ namespace osu.Game.Tests.Visual [Test] public void RemoveFacadeTest() { - TestScreen screen = null; - AddStep("Move facade to random position", () => LoadScreen(screen = new TestScreen())); - AddUntilStep("Screen is current", () => screen.IsCurrentScreen()); - AddStep("Remove facade from FacadeContainer", () => screen.RemoveFacade()); + AddStep("Add facade containers", addFacadeContainers); + AddStep("Move facade to random position", StartTrackingRandom); + AddStep("Remove facade from FacadeContainer", RemoveFacade); waitForMove(); - AddAssert("Logo is not tracking", () => !screen.IsLogoTracking); + AddAssert("Logo is not tracking", () => !IsLogoTracking); } /// @@ -87,114 +99,162 @@ namespace osu.Game.Tests.Visual [Test] public void TransferFacadeTest() { - TestScreen screen = null; - AddStep("Move facade to random position", () => LoadScreen(screen = new TestScreen())); - AddUntilStep("Screen is current", () => screen.IsCurrentScreen()); - AddStep("Remove facade from FacadeContainer", () => screen.RemoveFacade()); - AddStep("Transfer facade to a new container", () => screen.TransferFacade()); + AddStep("Add facade containers", addFacadeContainers); + AddStep("Move facade to random position", StartTrackingRandom); + AddStep("Remove facade from FacadeContainer", RemoveFacade); + AddStep("Transfer facade to a new container", TransferFacade); waitForMove(); - AddAssert("Logo is tracking", () => screen.IsLogoTracking); + AddAssert("Logo is tracking", () => IsLogoTracking); + } + + /// + /// Add a facade to a flow container then move logo to facade. + /// + [Test] + public void FlowContainerTest() + { + FillFlowContainer flowContainer; + + AddStep("Create new Logo Facade Container", () => + { + Add(logoFacadeContainer = new LogoFacadeContainer + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Child = flowContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Direction = FillDirection.Vertical, + } + }); + flowContainer.Children = new Drawable[] + { + new Box + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Colour = Color4.Azure, + Size = new Vector2(70) + }, + new Container + { + Alpha = 0.35f, + RelativeSizeAxes = Axes.None, + Size = new Vector2(72), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Children = new Drawable[] + { + visualBox = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, + logoFacadeContainer.LogoFacade, + } + }, + new Box + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Colour = Color4.Azure, + Size = new Vector2(70) + }, + }; + }); + + AddStep("Perform logo movements", () => + { + logoFacadeContainer.Tracking = false; + logo.RelativePositionAxes = Axes.Both; + logo.MoveTo(new Vector2(0.5f), 500, Easing.InOutExpo); + logoFacadeContainer.SetLogo(logo, 1.0f, 1000, Easing.InOutExpo); + visualBox.Colour = Color4.White; + + Scheduler.AddDelayed(() => + { + logoFacadeContainer.Tracking = true; + //logo.RelativePositionAxes = Axes.None; + visualBox.Colour = Color4.Tomato; + }, 700); + }); + } + + private void addFacadeContainers() + { + AddRange(new Drawable[] + { + logoFacadeContainer = new LogoFacadeContainer + { + Alpha = 0.35f, + RelativeSizeAxes = Axes.None, + Size = new Vector2(72), + Child = visualBox = new Box + { + Colour = Color4.Tomato, + RelativeSizeAxes = Axes.Both, + } + }, + transferContainer = new Container + { + Alpha = 0.35f, + RelativeSizeAxes = Axes.None, + Size = new Vector2(72), + Child = transferContainerBox = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + } + }, + }); + + logoFacadeContainer.Add(logoFacade = logoFacadeContainer.LogoFacade); + logoFacadeContainer.SetLogo(logo, 1.0f, 1000); } private void waitForMove() => AddWaitStep("Wait for transforms to finish", 5); - private class TestScreen : OsuScreen + private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(logoFacade.ScreenSpaceDrawQuad.Centre); + + /// + /// Check that the logo is tracking the position of the facade, with an acceptable precision lenience. + /// + public bool IsLogoTracking => Math.Abs(logo.Position.X - logoTrackingPosition.X) < 0.001f && Math.Abs(logo.Position.Y - logoTrackingPosition.Y) < 0.001f; + + public void RemoveFacade() { - private LogoFacadeContainer logoFacadeContainer; - private Container transferContainer; - private Container logoFacade; - private readonly bool randomPositions; - private OsuLogo logo; - private Box visualBox; - private Box transferContainerBox; + logoFacadeContainer.Remove(logoFacade); + visualBox.Colour = Color4.White; + moveLogoFacade(); + } - public TestScreen(bool randomPositions = false) + public void TransferFacade() + { + transferContainer.Add(logoFacade); + transferContainerBox.Colour = Color4.Tomato; + moveLogoFacade(); + } + + public void StartTrackingRandom() + { + logoFacadeContainer.Tracking = true; + moveLogoFacade(); + } + + private void moveLogoFacade() + { + Random random = new Random(); + if (logoFacade.Transforms.Count == 0 && transferContainer.Transforms.Count == 0) { - this.randomPositions = randomPositions; + logoFacadeContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300); + transferContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300); } - [BackgroundDependencyLoader] - private void load() - { - InternalChildren = new Drawable[] - { - logoFacadeContainer = new LogoFacadeContainer - { - Alpha = 0.35f, - RelativeSizeAxes = Axes.None, - Size = new Vector2(72), - Child = visualBox = new Box - { - Colour = Color4.Tomato, - RelativeSizeAxes = Axes.Both, - } - }, - transferContainer = new Container - { - Alpha = 0.35f, - RelativeSizeAxes = Axes.None, - Size = new Vector2(72), - Child = transferContainerBox = new Box - { - Colour = Color4.White, - RelativeSizeAxes = Axes.Both, - } - }, - }; - - logoFacadeContainer.Add(logoFacade = logoFacadeContainer.LogoFacade); - } - - private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(logoFacade.ScreenSpaceDrawQuad.Centre); - - /// - /// Check that the logo is tracking the position of the facade, with an acceptable precision lenience. - /// - public bool IsLogoTracking => Math.Abs(logo.Position.X - logoTrackingPosition.X) < 0.001f && Math.Abs(logo.Position.Y - logoTrackingPosition.Y) < 0.001f; - - public void RemoveFacade() - { - logoFacadeContainer.Remove(logoFacade); - visualBox.Colour = Color4.White; - moveLogoFacade(); - } - - public void TransferFacade() - { - transferContainer.Add(logoFacade); - transferContainerBox.Colour = Color4.Tomato; - moveLogoFacade(); - } - - protected override void LogoArriving(OsuLogo logo, bool resuming) - { - base.LogoArriving(logo, resuming); - this.logo = logo; - logo.FadeIn(350); - logo.ScaleTo(new Vector2(0.15f), 350, Easing.In); - logoFacadeContainer.SetLogo(logo, 1.0f, 1000, Easing.InOutQuint); - logoFacadeContainer.Tracking = true; - moveLogoFacade(); - } - - protected override void LogoExiting(OsuLogo logo) - { - base.LogoExiting(logo); - logoFacadeContainer.Tracking = false; - } - - private void moveLogoFacade() - { - Random random = new Random(); - if (logoFacade.Transforms.Count == 0 && transferContainer.Transforms.Count == 0) - { - logoFacadeContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)DrawWidth), random.Next(0, (int)DrawHeight)), 300); - transferContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)DrawWidth), random.Next(0, (int)DrawHeight)), 300); - } - - if (randomPositions) - Schedule(moveLogoFacade); - } + if (randomPositions) + Schedule(moveLogoFacade); } } } diff --git a/osu.Game.Tests/Visual/TestCasePositionAxes.cs b/osu.Game.Tests/Visual/TestCasePositionAxes.cs new file mode 100644 index 0000000000..04ff0dcf8c --- /dev/null +++ b/osu.Game.Tests/Visual/TestCasePositionAxes.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . 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.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osuTK; + +namespace osu.Game.Tests.Visual +{ + public class TestCasePositionAxes : OsuTestCase + { + private Box box; + + public TestCasePositionAxes() + { + Add(new Container() + { + Size = new Vector2(1), + Child = box = new Box + { + Size = new Vector2(25), + RelativePositionAxes = Axes.None, + Position = new Vector2(250) + } + }); + + AddStep("blank", () => { }); + AddStep("change axes", () => box.RelativePositionAxes = Axes.Both); + } + } +} diff --git a/osu.Game/Graphics/Containers/LogoFacadeContainer.cs b/osu.Game/Graphics/Containers/LogoFacadeContainer.cs index 547af87522..c9f41e75e7 100644 --- a/osu.Game/Graphics/Containers/LogoFacadeContainer.cs +++ b/osu.Game/Graphics/Containers/LogoFacadeContainer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Graphics.Containers /// The scale of the facade. Does not actually affect the logo itself. /// The duration of the initial transform. Default is instant. /// The easing type of the initial transform. - public void SetLogo(OsuLogo logo, float facadeScale, double duration = 0, Easing easing = Easing.None) + public void SetLogo(OsuLogo logo, float facadeScale = 1.0f, double duration = 0, Easing easing = Easing.None) { this.logo = logo ?? throw new ArgumentNullException(nameof(logo)); this.facadeScale = facadeScale; @@ -54,11 +54,19 @@ namespace osu.Game.Graphics.Containers startPosition = null; } - private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre); - - protected override void UpdateAfterChildren() + private Vector2 localSpaceConversion() { - base.UpdateAfterChildren(); + Vector2 local; + local.X = logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre).X / logo.Parent.RelativeToAbsoluteFactor.X; + local.Y = logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre).Y / logo.Parent.RelativeToAbsoluteFactor.Y; + return local; + } + + private Vector2 logoTrackingPosition => logo.RelativePositionAxes == Axes.None ? logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre) : localSpaceConversion(); + + protected override void Update() + { + base.Update(); if (logo == null || !Tracking) return; @@ -68,9 +76,6 @@ namespace osu.Game.Graphics.Containers if (LogoFacade.Parent != null && logo.Position != logoTrackingPosition) { - // Required for the correct position of the logo to be set with respect to logoTrackingPosition - logo.RelativePositionAxes = Axes.None; - // If this is our first update since tracking has started, initialize our starting values for interpolation if (startTime == null || startPosition == null) { diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index f13dadf3dd..607700649d 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -290,6 +290,7 @@ namespace osu.Game.Screens.Menu break; case ButtonSystemState.Initial: logo.ClearTransforms(targetMember: nameof(Position)); + logo.RelativePositionAxes = Axes.None; bool impact = logo.Scale.X > 0.6f; @@ -310,6 +311,7 @@ namespace osu.Game.Screens.Menu break; default: logo.ClearTransforms(targetMember: nameof(Position)); + logo.RelativePositionAxes = Axes.None; logoFacadeContainer.Tracking = true; logo.ScaleTo(0.5f, 200, Easing.OutQuint); break; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 9e6f7cf24e..19607129a0 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -172,7 +172,11 @@ namespace osu.Game.Screens.Play content.SetLogo(logo, 1.0f, 500, Easing.InOutExpo); - Scheduler.AddDelayed(() => content.Tracking = true, resuming ? 0 : 500); + Scheduler.AddDelayed(() => + { + content.Tracking = true; + //logo.RelativePositionAxes = Axes.None; + }, resuming ? 0 : 500); } protected override void LogoExiting(OsuLogo logo) From 8a40b27e8fc3cf9b17069439e8af130a72d7564e Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 3 Apr 2019 20:32:53 +0900 Subject: [PATCH 364/623] Remove need for logo relativePositionAxes none --- osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs | 1 - osu.Game/Graphics/Containers/LogoFacadeContainer.cs | 10 ++++------ osu.Game/Screens/Menu/ButtonSystem.cs | 2 -- osu.Game/Screens/Play/PlayerLoader.cs | 1 - 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs index b4d7976e4a..bd1fb932bb 100644 --- a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs @@ -177,7 +177,6 @@ namespace osu.Game.Tests.Visual Scheduler.AddDelayed(() => { logoFacadeContainer.Tracking = true; - //logo.RelativePositionAxes = Axes.None; visualBox.Colour = Color4.Tomato; }, 700); }); diff --git a/osu.Game/Graphics/Containers/LogoFacadeContainer.cs b/osu.Game/Graphics/Containers/LogoFacadeContainer.cs index c9f41e75e7..76903a20b3 100644 --- a/osu.Game/Graphics/Containers/LogoFacadeContainer.cs +++ b/osu.Game/Graphics/Containers/LogoFacadeContainer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Graphics.Containers startPosition = null; } - private Vector2 localSpaceConversion() + private Vector2 logoTrackingPosition() { Vector2 local; local.X = logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre).X / logo.Parent.RelativeToAbsoluteFactor.X; @@ -62,8 +62,6 @@ namespace osu.Game.Graphics.Containers return local; } - private Vector2 logoTrackingPosition => logo.RelativePositionAxes == Axes.None ? logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre) : localSpaceConversion(); - protected override void Update() { base.Update(); @@ -74,7 +72,7 @@ namespace osu.Game.Graphics.Containers // Account for the scale of the actual logo container, as SizeForFlow only accounts for the sprite scale. LogoFacade.Size = new Vector2(logo.SizeForFlow * logo.Scale.X * facadeScale); - if (LogoFacade.Parent != null && logo.Position != logoTrackingPosition) + if (LogoFacade.Parent != null && logo.Position != logoTrackingPosition()) { // If this is our first update since tracking has started, initialize our starting values for interpolation if (startTime == null || startPosition == null) @@ -90,11 +88,11 @@ namespace osu.Game.Graphics.Containers var mount = (float)Interpolation.ApplyEasing(easing, Math.Min(elapsedDuration / duration, 1)); // Interpolate the position of the logo, where mount 0 is where the logo was when it first began interpolating, and mount 1 is the target location. - logo.Position = Vector2.Lerp(startPosition ?? throw new ArgumentNullException(nameof(startPosition)), logoTrackingPosition, mount); + logo.Position = Vector2.Lerp(startPosition ?? throw new ArgumentNullException(nameof(startPosition)), logoTrackingPosition(), mount); } else { - logo.Position = logoTrackingPosition; + logo.Position = logoTrackingPosition(); } } } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 607700649d..f13dadf3dd 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -290,7 +290,6 @@ namespace osu.Game.Screens.Menu break; case ButtonSystemState.Initial: logo.ClearTransforms(targetMember: nameof(Position)); - logo.RelativePositionAxes = Axes.None; bool impact = logo.Scale.X > 0.6f; @@ -311,7 +310,6 @@ namespace osu.Game.Screens.Menu break; default: logo.ClearTransforms(targetMember: nameof(Position)); - logo.RelativePositionAxes = Axes.None; logoFacadeContainer.Tracking = true; logo.ScaleTo(0.5f, 200, Easing.OutQuint); break; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 19607129a0..2ba5bd81d1 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -175,7 +175,6 @@ namespace osu.Game.Screens.Play Scheduler.AddDelayed(() => { content.Tracking = true; - //logo.RelativePositionAxes = Axes.None; }, resuming ? 0 : 500); } From 73c7c6c31613175bcdcfe30d4169bdd160e2b52b Mon Sep 17 00:00:00 2001 From: Samuel Van Allen Date: Wed, 3 Apr 2019 21:44:36 +0800 Subject: [PATCH 365/623] Hides "Details" button when OnlineBeatmapID is null --- .../Carousel/DrawableCarouselBeatmap.cs | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 38ca9a9aed..a31e40dd8f 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -2,6 +2,7 @@ // 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.Graphics; using osu.Framework.Graphics.Colour; @@ -167,16 +168,21 @@ namespace osu.Game.Screens.Select.Carousel base.ApplyState(); } - - public MenuItem[] ContextMenuItems => new MenuItem[] + public MenuItem[] ContextMenuItems { - new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested?.Invoke(beatmap)), - new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap)), - new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested?.Invoke(beatmap)), - new OsuMenuItem("Details", MenuItemType.Standard, () => + get { - if (beatmap.OnlineBeatmapID.HasValue) beatmapOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value); - }), - }; + List items = new List(); + + items.Add(new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested?.Invoke(beatmap))); + items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap))); + items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested?.Invoke(beatmap))); + + if (beatmap.OnlineBeatmapID.HasValue) + items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); + + return items.ToArray(); + } + } } } From 55a6e43778da2a932f1b7ae41e713107c7fae54e Mon Sep 17 00:00:00 2001 From: Samuel Van Allen Date: Wed, 3 Apr 2019 21:49:33 +0800 Subject: [PATCH 366/623] Check against databasedSet instead of function param --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 9ab09d1122..b31b9e5e87 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -255,7 +255,7 @@ namespace osu.Game menuScreen.LoadToSolo(); // we might even already be at the song - if (Beatmap.Value.BeatmapSetInfo.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID) + if (Beatmap.Value.BeatmapSetInfo.Hash == databasedSet.Hash) { return; } From 4d60f6fb6a0cd17fb4e330a4f67177bab083d4e8 Mon Sep 17 00:00:00 2001 From: Samuel Van Allen Date: Wed, 3 Apr 2019 22:37:50 +0800 Subject: [PATCH 367/623] Use collection initializer and added missing blank line --- .../Select/Carousel/DrawableCarouselBeatmap.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index a31e40dd8f..0b60ca03a7 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -168,16 +168,18 @@ namespace osu.Game.Screens.Select.Carousel base.ApplyState(); } + public MenuItem[] ContextMenuItems { get { - List items = new List(); - - items.Add(new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested?.Invoke(beatmap))); - items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap))); - items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested?.Invoke(beatmap))); - + List items = new List() + { + new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested?.Invoke(beatmap)), + new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap)), + new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested?.Invoke(beatmap)), + }; + if (beatmap.OnlineBeatmapID.HasValue) items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); From 36609244410b25aa4f42596d126d3a66651998e3 Mon Sep 17 00:00:00 2001 From: Samuel Van Allen Date: Wed, 3 Apr 2019 22:40:20 +0800 Subject: [PATCH 368/623] Trimmed whitespace --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 0b60ca03a7..c19fa73ab1 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Select.Carousel new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap)), new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested?.Invoke(beatmap)), }; - + if (beatmap.OnlineBeatmapID.HasValue) items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); From bb516da5b6903c454c5b9197e57ab4db72b39a23 Mon Sep 17 00:00:00 2001 From: Samuel Van Allen Date: Wed, 3 Apr 2019 22:57:05 +0800 Subject: [PATCH 369/623] Removed redundant empty argument list --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index c19fa73ab1..0a20f2aa6d 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -173,7 +173,7 @@ namespace osu.Game.Screens.Select.Carousel { get { - List items = new List() + List items = new List { new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested?.Invoke(beatmap)), new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap)), From ccc804a9b2ac61e4f90448fd265dac4ec1db568f Mon Sep 17 00:00:00 2001 From: jorolf Date: Thu, 4 Apr 2019 00:24:42 +0200 Subject: [PATCH 370/623] get everything working again --- .../{ => Online}/TestCaseUserProfileHeader.cs | 4 +-- .../Graphics/UserInterface/ScreenTitle.cs | 6 +++-- .../Profile/Header/BottomHeaderContainer.cs | 18 ++++++------- .../Profile/Header/CenterHeaderContainer.cs | 6 ++--- .../Profile/Header/ProfileMessageButton.cs | 8 +++--- .../Overlays/Profile/Header/SupporterIcon.cs | 5 ++-- osu.Game/Overlays/Profile/ProfileHeader.cs | 27 +++++++++++++------ osu.Game/Screens/Multi/Header.cs | 2 +- osu.Game/Users/UserCoverBackground.cs | 7 ++--- osu.Game/Users/UserPanel.cs | 2 +- 10 files changed, 49 insertions(+), 36 deletions(-) rename osu.Game.Tests/Visual/{ => Online}/TestCaseUserProfileHeader.cs (96%) diff --git a/osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs rename to osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs index 8f36b4dda8..98bad9831f 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { public class TestCaseUserProfileHeader : OsuTestCase { @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual }; [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } private readonly ProfileHeader header; diff --git a/osu.Game/Graphics/UserInterface/ScreenTitle.cs b/osu.Game/Graphics/UserInterface/ScreenTitle.cs index 1574023068..b9d9b5427d 100644 --- a/osu.Game/Graphics/UserInterface/ScreenTitle.cs +++ b/osu.Game/Graphics/UserInterface/ScreenTitle.cs @@ -14,6 +14,8 @@ namespace osu.Game.Graphics.UserInterface { private readonly SpriteIcon iconSprite; private readonly OsuSpriteText titleText, pageText; + public const float ICON_WIDTH = icon_size + icon_spacing; + private const float icon_size = 25, icon_spacing = 10; protected IconUsage Icon { @@ -48,12 +50,12 @@ namespace osu.Game.Graphics.UserInterface new FillFlowContainer { AutoSizeAxes = Axes.Both, - Spacing = new Vector2(10, 0), + Spacing = new Vector2(icon_spacing, 0), Children = new Drawable[] { iconSprite = new SpriteIcon { - Size = new Vector2(25), + Size = new Vector2(icon_size), }, new FillFlowContainer { diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 1f9d741b8d..04b70ea10f 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -107,7 +107,7 @@ namespace osu.Game.Overlays.Profile.Header bottomTopLinkContainer.AddText("Contributed "); bottomTopLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: bold); - void tryAddInfo(FontAwesome icon, string content, string link = null) + void tryAddInfo(IconUsage icon, string content, string link = null) { if (string.IsNullOrEmpty(content)) return; @@ -138,16 +138,16 @@ namespace osu.Game.Overlays.Profile.Header websiteWithoutProtcol = websiteWithoutProtcol.Substring(protocolIndex + 2); } - tryAddInfo(FontAwesome.fa_map_marker, user.Location); - tryAddInfo(FontAwesome.fa_heart_o, user.Interests); - tryAddInfo(FontAwesome.fa_suitcase, user.Occupation); + tryAddInfo(FontAwesome.Solid.MapMarker, user.Location); + tryAddInfo(OsuIcon.Heart, user.Interests); + tryAddInfo(FontAwesome.Solid.Suitcase, user.Occupation); bottomLinkContainer.NewLine(); if (!string.IsNullOrEmpty(user.Twitter)) - tryAddInfo(FontAwesome.fa_twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); - tryAddInfo(FontAwesome.fa_gamepad, user.Discord); //todo: update fontawesome to include discord logo - tryAddInfo(FontAwesome.fa_skype, user.Skype, @"skype:" + user.Skype + @"?chat"); - tryAddInfo(FontAwesome.fa_lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); - tryAddInfo(FontAwesome.fa_link, websiteWithoutProtcol, user.Website); + tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); + tryAddInfo(FontAwesome.Brands.Discord, user.Discord); + tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat"); + tryAddInfo(FontAwesome.Brands.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); + tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtcol, user.Website); } } } diff --git a/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs index b5e04bee9e..3d0b9b9db4 100644 --- a/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs @@ -77,7 +77,7 @@ namespace osu.Game.Overlays.Profile.Header { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Icon = FontAwesome.fa_user, + Icon = FontAwesome.Solid.User, FillMode = FillMode.Fit, Size = new Vector2(50, 14) }, @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Profile.Header Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(20), - Icon = FontAwesome.fa_chevron_up, + Icon = FontAwesome.Solid.ChevronUp, }, } }, @@ -212,7 +212,7 @@ namespace osu.Game.Overlays.Profile.Header detailsToggleButton.Action = () => { detailsVisible = !detailsVisible; - expandButtonIcon.Icon = detailsVisible ? FontAwesome.fa_chevron_down : FontAwesome.fa_chevron_up; + expandButtonIcon.Icon = detailsVisible ? FontAwesome.Solid.ChevronDown : FontAwesome.Solid.ChevronUp; hiddenDetailContainer.Alpha = detailsVisible ? 1 : 0; expandedDetailContainer.Alpha = detailsVisible ? 0 : 1; DetailsVisibilityAction(detailsVisible); diff --git a/osu.Game/Overlays/Profile/Header/ProfileMessageButton.cs b/osu.Game/Overlays/Profile/Header/ProfileMessageButton.cs index a14c0324d7..3121eae727 100644 --- a/osu.Game/Overlays/Profile/Header/ProfileMessageButton.cs +++ b/osu.Game/Overlays/Profile/Header/ProfileMessageButton.cs @@ -4,7 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Online.API; using osu.Game.Online.Chat; using osu.Game.Users; @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Profile.Header private ChatOverlay chatOverlay { get; set; } [Resolved] - private APIAccess apiAccess { get; set; } + private IAPIProvider apiProvider { get; set; } public ProfileMessageButton() { @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Profile.Header { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Icon = FontAwesome.fa_envelope, + Icon = FontAwesome.Solid.Envelope, FillMode = FillMode.Fit, Size = new Vector2(50, 14) }; @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Profile.Header chatOverlay?.Show(); }; - User.ValueChanged += e => Content.Alpha = !e.NewValue.PMFriendsOnly && apiAccess.LocalUser.Value.Id != e.NewValue.Id ? 1 : 0; + User.ValueChanged += e => Content.Alpha = !e.NewValue.PMFriendsOnly && apiProvider.LocalUser.Value.Id != e.NewValue.Id ? 1 : 0; } } } diff --git a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs index 3cb30ab044..569ed252b7 100644 --- a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Profile.Header { Width = 12, RelativeSizeAxes = Axes.Y, - Icon = FontAwesome.fa_heart, + Icon = FontAwesome.Solid.Heart, }); } @@ -66,8 +66,7 @@ namespace osu.Game.Overlays.Profile.Header AutoSizeAxes = Axes.X, Height = 0.6f, Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = FontAwesome.Solid.Heart, + Origin = Anchor.Centre } } }; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 988fa3afd9..b75de39c4b 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -5,11 +5,11 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Profile @@ -17,7 +17,6 @@ namespace osu.Game.Overlays.Profile public class ProfileHeader : Container { private readonly UserCoverBackground coverContainer; - private readonly ScreenTitle coverTitle; private readonly ProfileHeaderTabControl infoTabControl; private const float cover_height = 150; @@ -52,10 +51,9 @@ namespace osu.Game.Overlays.Profile Depth = -float.MaxValue, Children = new Drawable[] { - coverTitle = new ScreenTitle + new ProfileHeaderTitle { - Title = "Player ", - Page = "Info" + X = -ScreenTitle.ICON_WIDTH, }, infoTabControl = new ProfileHeaderTabControl { @@ -113,10 +111,8 @@ namespace osu.Game.Overlays.Profile } [BackgroundDependencyLoader] - private void load(OsuColour colours, TextureStore textures) + private void load(OsuColour colours) { - coverTitle.AccentColour = colours.CommunityUserGreen; - infoTabControl.AccentColour = colours.CommunityUserGreen; } @@ -131,5 +127,20 @@ namespace osu.Game.Overlays.Profile { public string TooltipText { get; set; } } + + private class ProfileHeaderTitle : ScreenTitle + { + public ProfileHeaderTitle() + { + Title = "Player "; + Section = "Info"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.CommunityUserGreen; + } + } } } diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 7924086389..35edc5bac8 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Multi { Anchor = Anchor.CentreLeft, Origin = Anchor.BottomLeft, - X = -35, + X = -ScreenTitle.ICON_WIDTH, }, breadcrumbs = new HeaderBreadcrumbControl(stack) { diff --git a/osu.Game/Users/UserCoverBackground.cs b/osu.Game/Users/UserCoverBackground.cs index d037f809b4..dbc132995a 100644 --- a/osu.Game/Users/UserCoverBackground.cs +++ b/osu.Game/Users/UserCoverBackground.cs @@ -36,15 +36,16 @@ namespace osu.Game.Users } else { - return new Sprite + var sprite = new Sprite { RelativeSizeAxes = Axes.Both, Texture = textures.Get(user.CoverUrl), FillMode = FillMode.Fill, Anchor = Anchor.Centre, - Origin = Anchor.Centre, - OnLoadComplete = d => d.FadeInFromZero(400), + Origin = Anchor.Centre }; + sprite.OnLoadComplete += d => d.FadeInFromZero(400); + return sprite; } } } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 0e928e0aac..eb7f0941fb 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -76,7 +76,7 @@ namespace osu.Game.Users Children = new Drawable[] { - new DelayedLoadWrapper(coverBackground = new UserCoverBackground(user) + new DelayedLoadWrapper(coverBackground = new UserCoverBackground { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, From ba871364514ae468bc5e0a47a12c4adda5206dc0 Mon Sep 17 00:00:00 2001 From: jorolf Date: Thu, 4 Apr 2019 00:57:15 +0200 Subject: [PATCH 371/623] add gradient and fix remaining error --- .../Visual/Online/TestCaseUserProfile.cs | 10 +++------- osu.Game/Overlays/Profile/ProfileHeader.cs | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs index abb96b8e84..c455c092ed 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs @@ -20,7 +20,9 @@ namespace osu.Game.Tests.Visual.Online public class TestCaseUserProfile : OsuTestCase { private readonly TestUserProfileOverlay profile; - private IAPIProvider api; + + [Resolved] + private IAPIProvider api { get; set; } public override IReadOnlyList RequiredTypes => new[] { @@ -75,12 +77,6 @@ namespace osu.Game.Tests.Visual.Online Add(profile = new TestUserProfileOverlay()); } - [BackgroundDependencyLoader] - private void load(APIAccess api) - { - this.api = api; - } - protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index b75de39c4b..776fcbb8b7 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -9,6 +9,9 @@ using osu.Game.Graphics; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -35,10 +38,23 @@ namespace osu.Game.Overlays.Profile Children = new Drawable[] { - coverContainer = new UserCoverBackground + new Container { RelativeSizeAxes = Axes.X, Height = cover_height, + Masking = true, + Children = new Drawable[] + { + coverContainer = new UserCoverBackground + { + RelativeSizeAxes = Axes.Both, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(OsuColour.FromHex("222").Opacity(0.8f), OsuColour.FromHex("222").Opacity(0.2f)) + }, + } }, new Container { From 6b5458a625840759eb178278a88f423532359081 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 4 Apr 2019 11:22:05 +0900 Subject: [PATCH 372/623] Clean up test cases --- .../Visual/TestCaseLogoFacadeContainer.cs | 92 +++++++++---------- osu.Game.Tests/Visual/TestCasePositionAxes.cs | 33 ------- .../Containers/LogoFacadeContainer.cs | 23 ++--- 3 files changed, 57 insertions(+), 91 deletions(-) delete mode 100644 osu.Game.Tests/Visual/TestCasePositionAxes.cs diff --git a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs index bd1fb932bb..d66ac19aed 100644 --- a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Drawing; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -35,21 +34,18 @@ namespace osu.Game.Tests.Visual }; private OsuLogo logo; - private readonly Bindable uiScale = new Bindable(); - private LogoFacadeContainer logoFacadeContainer; + private TestLogoFacadeContainer facadeContainer; private Container transferContainer; private Box visualBox; private Box transferContainerBox; private Container logoFacade; - private bool randomPositions = false; + private bool randomPositions; [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - Add(logo = new OsuLogo { Scale = new Vector2(0.15f), RelativePositionAxes = Axes.None }); - config.BindWith(OsuSetting.UIScale, uiScale); AddSliderStep("Adjust scale", 0.8f, 1.5f, 1f, v => uiScale.Value = v); } @@ -60,8 +56,8 @@ namespace osu.Game.Tests.Visual AddStep("Clear facades", () => { Clear(); - Add(logo = new OsuLogo { Scale = new Vector2(0.15f), RelativePositionAxes = Axes.None }); - logoFacadeContainer = null; + Add(logo = new OsuLogo { Scale = new Vector2(0.15f), RelativePositionAxes = Axes.Both }); + facadeContainer = null; transferContainer = null; }); } @@ -75,9 +71,9 @@ namespace osu.Game.Tests.Visual { AddToggleStep("Toggle move continuously", b => randomPositions = b); AddStep("Add facade containers", addFacadeContainers); - AddStep("Move facade to random position", StartTrackingRandom); + AddStep("Move facade to random position", startTrackingRandom); waitForMove(); - AddAssert("Logo is tracking", () => IsLogoTracking); + AddAssert("Logo is tracking", () => facadeContainer.IsLogoTracking); } /// @@ -87,10 +83,10 @@ namespace osu.Game.Tests.Visual public void RemoveFacadeTest() { AddStep("Add facade containers", addFacadeContainers); - AddStep("Move facade to random position", StartTrackingRandom); - AddStep("Remove facade from FacadeContainer", RemoveFacade); + AddStep("Move facade to random position", startTrackingRandom); + AddStep("Remove facade from FacadeContainer", removeFacade); waitForMove(); - AddAssert("Logo is not tracking", () => !IsLogoTracking); + AddAssert("Logo is not tracking", () => !facadeContainer.IsLogoTracking); } /// @@ -100,11 +96,16 @@ namespace osu.Game.Tests.Visual public void TransferFacadeTest() { AddStep("Add facade containers", addFacadeContainers); - AddStep("Move facade to random position", StartTrackingRandom); - AddStep("Remove facade from FacadeContainer", RemoveFacade); - AddStep("Transfer facade to a new container", TransferFacade); + AddStep("Move facade to random position", startTrackingRandom); + AddStep("Remove facade from FacadeContainer", removeFacade); + AddStep("Transfer facade to a new container", () => + { + transferContainer.Add(logoFacade); + transferContainerBox.Colour = Color4.Tomato; + moveLogoFacade(); + }); waitForMove(); - AddAssert("Logo is tracking", () => IsLogoTracking); + AddAssert("Logo is tracking", () => facadeContainer.IsLogoTracking); } /// @@ -115,9 +116,9 @@ namespace osu.Game.Tests.Visual { FillFlowContainer flowContainer; - AddStep("Create new Logo Facade Container", () => + AddStep("Create new flow container with facade", () => { - Add(logoFacadeContainer = new LogoFacadeContainer + Add(facadeContainer = new TestLogoFacadeContainer { AutoSizeAxes = Axes.Both, Origin = Anchor.TopCentre, @@ -153,7 +154,7 @@ namespace osu.Game.Tests.Visual Colour = Color4.White, RelativeSizeAxes = Axes.Both, }, - logoFacadeContainer.LogoFacade, + facadeContainer.LogoFacade, } }, new Box @@ -168,25 +169,28 @@ namespace osu.Game.Tests.Visual AddStep("Perform logo movements", () => { - logoFacadeContainer.Tracking = false; + facadeContainer.Tracking = false; logo.RelativePositionAxes = Axes.Both; logo.MoveTo(new Vector2(0.5f), 500, Easing.InOutExpo); - logoFacadeContainer.SetLogo(logo, 1.0f, 1000, Easing.InOutExpo); + facadeContainer.SetLogo(logo, 1.0f, 1000, Easing.InOutExpo); visualBox.Colour = Color4.White; Scheduler.AddDelayed(() => { - logoFacadeContainer.Tracking = true; + facadeContainer.Tracking = true; visualBox.Colour = Color4.Tomato; }, 700); }); + + waitForMove(); + AddAssert("Logo is tracking", () => facadeContainer.IsLogoTracking); } private void addFacadeContainers() { AddRange(new Drawable[] { - logoFacadeContainer = new LogoFacadeContainer + facadeContainer = new TestLogoFacadeContainer { Alpha = 0.35f, RelativeSizeAxes = Axes.None, @@ -210,50 +214,44 @@ namespace osu.Game.Tests.Visual }, }); - logoFacadeContainer.Add(logoFacade = logoFacadeContainer.LogoFacade); - logoFacadeContainer.SetLogo(logo, 1.0f, 1000); + facadeContainer.Add(logoFacade = facadeContainer.LogoFacade); + facadeContainer.SetLogo(logo, 1.0f, 1000); } private void waitForMove() => AddWaitStep("Wait for transforms to finish", 5); - private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(logoFacade.ScreenSpaceDrawQuad.Centre); - - /// - /// Check that the logo is tracking the position of the facade, with an acceptable precision lenience. - /// - public bool IsLogoTracking => Math.Abs(logo.Position.X - logoTrackingPosition.X) < 0.001f && Math.Abs(logo.Position.Y - logoTrackingPosition.Y) < 0.001f; - - public void RemoveFacade() + private void removeFacade() { - logoFacadeContainer.Remove(logoFacade); + facadeContainer.Remove(logoFacade); visualBox.Colour = Color4.White; moveLogoFacade(); } - public void TransferFacade() + private void startTrackingRandom() { - transferContainer.Add(logoFacade); - transferContainerBox.Colour = Color4.Tomato; - moveLogoFacade(); - } - - public void StartTrackingRandom() - { - logoFacadeContainer.Tracking = true; + facadeContainer.Tracking = true; moveLogoFacade(); } private void moveLogoFacade() { - Random random = new Random(); - if (logoFacade.Transforms.Count == 0 && transferContainer.Transforms.Count == 0) + if (logoFacade?.Transforms.Count == 0 && transferContainer?.Transforms.Count == 0) { - logoFacadeContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300); + Random random = new Random(); + facadeContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300); transferContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300); } if (randomPositions) Schedule(moveLogoFacade); } + + private class TestLogoFacadeContainer : LogoFacadeContainer + { + /// + /// Check that the logo is tracking the position of the facade, with an acceptable precision lenience. + /// + public bool IsLogoTracking => Math.Abs(Logo.Position.X - LogoTrackingPosition().X) < 0.001f && Math.Abs(Logo.Position.Y - LogoTrackingPosition().Y) < 0.001f; + } } } diff --git a/osu.Game.Tests/Visual/TestCasePositionAxes.cs b/osu.Game.Tests/Visual/TestCasePositionAxes.cs deleted file mode 100644 index 04ff0dcf8c..0000000000 --- a/osu.Game.Tests/Visual/TestCasePositionAxes.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) ppy Pty Ltd . 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.Framework.Graphics.Shapes; -using osu.Framework.Testing; -using osuTK; - -namespace osu.Game.Tests.Visual -{ - public class TestCasePositionAxes : OsuTestCase - { - private Box box; - - public TestCasePositionAxes() - { - Add(new Container() - { - Size = new Vector2(1), - Child = box = new Box - { - Size = new Vector2(25), - RelativePositionAxes = Axes.None, - Position = new Vector2(250) - } - }); - - AddStep("blank", () => { }); - AddStep("change axes", () => box.RelativePositionAxes = Axes.Both); - } - } -} diff --git a/osu.Game/Graphics/Containers/LogoFacadeContainer.cs b/osu.Game/Graphics/Containers/LogoFacadeContainer.cs index 76903a20b3..a80b687057 100644 --- a/osu.Game/Graphics/Containers/LogoFacadeContainer.cs +++ b/osu.Game/Graphics/Containers/LogoFacadeContainer.cs @@ -24,7 +24,8 @@ namespace osu.Game.Graphics.Containers /// public bool Tracking = false; - private OsuLogo logo; + protected OsuLogo Logo; + private float facadeScale; private Easing easing; private Vector2? startPosition; @@ -45,7 +46,7 @@ namespace osu.Game.Graphics.Containers /// The easing type of the initial transform. public void SetLogo(OsuLogo logo, float facadeScale = 1.0f, double duration = 0, Easing easing = Easing.None) { - this.logo = logo ?? throw new ArgumentNullException(nameof(logo)); + Logo = logo ?? throw new ArgumentNullException(nameof(logo)); this.facadeScale = facadeScale; this.duration = duration; this.easing = easing; @@ -54,11 +55,11 @@ namespace osu.Game.Graphics.Containers startPosition = null; } - private Vector2 logoTrackingPosition() + protected Vector2 LogoTrackingPosition() { Vector2 local; - local.X = logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre).X / logo.Parent.RelativeToAbsoluteFactor.X; - local.Y = logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre).Y / logo.Parent.RelativeToAbsoluteFactor.Y; + local.X = Logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre).X / Logo.Parent.RelativeToAbsoluteFactor.X; + local.Y = Logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre).Y / Logo.Parent.RelativeToAbsoluteFactor.Y; return local; } @@ -66,19 +67,19 @@ namespace osu.Game.Graphics.Containers { base.Update(); - if (logo == null || !Tracking) + if (Logo == null || !Tracking) return; // Account for the scale of the actual logo container, as SizeForFlow only accounts for the sprite scale. - LogoFacade.Size = new Vector2(logo.SizeForFlow * logo.Scale.X * facadeScale); + LogoFacade.Size = new Vector2(Logo.SizeForFlow * Logo.Scale.X * facadeScale); - if (LogoFacade.Parent != null && logo.Position != logoTrackingPosition()) + if (LogoFacade.Parent != null && Logo.Position != LogoTrackingPosition()) { // If this is our first update since tracking has started, initialize our starting values for interpolation if (startTime == null || startPosition == null) { startTime = Time.Current; - startPosition = logo.Position; + startPosition = Logo.Position; } if (duration != 0) @@ -88,11 +89,11 @@ namespace osu.Game.Graphics.Containers var mount = (float)Interpolation.ApplyEasing(easing, Math.Min(elapsedDuration / duration, 1)); // Interpolate the position of the logo, where mount 0 is where the logo was when it first began interpolating, and mount 1 is the target location. - logo.Position = Vector2.Lerp(startPosition ?? throw new ArgumentNullException(nameof(startPosition)), logoTrackingPosition(), mount); + Logo.Position = Vector2.Lerp(startPosition ?? throw new ArgumentNullException(nameof(startPosition)), LogoTrackingPosition(), mount); } else { - logo.Position = logoTrackingPosition(); + Logo.Position = LogoTrackingPosition(); } } } From 15b2b6af7d8c8da6c371b14ff4c571b1c284d423 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 4 Apr 2019 11:28:36 +0900 Subject: [PATCH 373/623] Clean up remaining assignments of logo relativePositionAxes --- osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs | 1 - osu.Game/Screens/Menu/ButtonSystem.cs | 2 -- osu.Game/Screens/Play/PlayerLoader.cs | 5 +---- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs index d66ac19aed..3e9d52483d 100644 --- a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs @@ -170,7 +170,6 @@ namespace osu.Game.Tests.Visual AddStep("Perform logo movements", () => { facadeContainer.Tracking = false; - logo.RelativePositionAxes = Axes.Both; logo.MoveTo(new Vector2(0.5f), 500, Easing.InOutExpo); facadeContainer.SetLogo(logo, 1.0f, 1000, Easing.InOutExpo); visualBox.Colour = Color4.White; diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index f13dadf3dd..cb95ec1765 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -276,8 +276,6 @@ namespace osu.Game.Screens.Menu game?.Toolbar.Hide(); logo.ClearTransforms(targetMember: nameof(Position)); - logo.RelativePositionAxes = Axes.Both; - logo.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo); logo.ScaleTo(1, 800, Easing.OutExpo); }, buttonArea.Alpha * 150); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 2ba5bd81d1..ba27f87ee6 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -131,12 +131,9 @@ namespace osu.Game.Screens.Play private void contentOut() { - // Ensure the logo is no longer tracking before we scale the content, and that its RelativePositionAxes have been returned. + // Ensure the logo is no longer tracking before we scale the content content.Tracking = false; - if (logo != null) - logo.RelativePositionAxes = Axes.Both; - content.ScaleTo(0.7f, 300, Easing.InQuint); content.FadeOut(250); } From e89143d76b94610bf88c3a733e64e340f97713e5 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 4 Apr 2019 12:07:11 +0900 Subject: [PATCH 374/623] Add xmldoc for LogoTrackingPosition --- osu.Game/Graphics/Containers/LogoFacadeContainer.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Graphics/Containers/LogoFacadeContainer.cs b/osu.Game/Graphics/Containers/LogoFacadeContainer.cs index a80b687057..1664fdde4d 100644 --- a/osu.Game/Graphics/Containers/LogoFacadeContainer.cs +++ b/osu.Game/Graphics/Containers/LogoFacadeContainer.cs @@ -55,6 +55,12 @@ namespace osu.Game.Graphics.Containers startPosition = null; } + /// + /// Gets the position that the logo should move to with respect to the . + /// Manually performs a conversion of the Facade's position to the relative position of the Logo's Parent. + /// + /// Will only be correct if the logo's are set to Axes.Both + /// The position that the logo should move to in its parent's relative space. protected Vector2 LogoTrackingPosition() { Vector2 local; From b2857384b8f3c83be474b17cca9c7cc90342cf4e Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 4 Apr 2019 12:08:05 +0900 Subject: [PATCH 375/623] Remove unnecessary logo assignment --- osu.Game/Screens/Play/PlayerLoader.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index ba27f87ee6..ccfd965f53 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -37,8 +37,6 @@ namespace osu.Game.Screens.Play private BeatmapMetadataDisplay info; - private OsuLogo logo; - private bool hideOverlays; public override bool HideOverlaysOnEnter => hideOverlays; @@ -155,8 +153,6 @@ namespace osu.Game.Screens.Play { base.LogoArriving(logo, resuming); - this.logo = logo; - const double duration = 300; if (!resuming) From f2bbde83bf3788957ffb3ad7b7e35b1f2e4c9e6d Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 4 Apr 2019 13:05:34 +0900 Subject: [PATCH 376/623] Use precision almost equals --- .../{ => UserInterface}/TestCaseLogoFacadeContainer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename osu.Game.Tests/Visual/{ => UserInterface}/TestCaseLogoFacadeContainer.cs (97%) diff --git a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoFacadeContainer.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseLogoFacadeContainer.cs index 3e9d52483d..4bc4ae724a 100644 --- a/osu.Game.Tests/Visual/TestCaseLogoFacadeContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoFacadeContainer.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.MathUtils; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Graphics.Containers; @@ -18,7 +19,7 @@ using osu.Game.Screens.Play; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { public class TestCaseLogoFacadeContainer : OsuTestCase { @@ -250,7 +251,7 @@ namespace osu.Game.Tests.Visual /// /// Check that the logo is tracking the position of the facade, with an acceptable precision lenience. /// - public bool IsLogoTracking => Math.Abs(Logo.Position.X - LogoTrackingPosition().X) < 0.001f && Math.Abs(Logo.Position.Y - LogoTrackingPosition().Y) < 0.001f; + public bool IsLogoTracking => Precision.AlmostEquals(Logo.Position, LogoTrackingPosition()); } } } From b2e932dc74c5eae1184172758b3a62789d23e719 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 4 Apr 2019 13:13:03 +0900 Subject: [PATCH 377/623] Clean up tests, xmldoc --- .../UserInterface/TestCaseLogoFacadeContainer.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoFacadeContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoFacadeContainer.cs index 4bc4ae724a..5f864eeabb 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoFacadeContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoFacadeContainer.cs @@ -41,9 +41,10 @@ namespace osu.Game.Tests.Visual.UserInterface private Box visualBox; private Box transferContainerBox; private Container logoFacade; - private bool randomPositions; + private const float visual_box_size = 72; + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { @@ -110,7 +111,7 @@ namespace osu.Game.Tests.Visual.UserInterface } /// - /// Add a facade to a flow container then move logo to facade. + /// Add a facade to a flow container, move the logo to the center of the screen, then start tracking on the facade. /// [Test] public void FlowContainerTest() @@ -139,13 +140,13 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Colour = Color4.Azure, - Size = new Vector2(70) + Size = new Vector2(visual_box_size) }, new Container { Alpha = 0.35f, RelativeSizeAxes = Axes.None, - Size = new Vector2(72), + Size = new Vector2(visual_box_size), Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Children = new Drawable[] @@ -163,7 +164,7 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Colour = Color4.Azure, - Size = new Vector2(70) + Size = new Vector2(visual_box_size) }, }; }); @@ -194,7 +195,7 @@ namespace osu.Game.Tests.Visual.UserInterface { Alpha = 0.35f, RelativeSizeAxes = Axes.None, - Size = new Vector2(72), + Size = new Vector2(visual_box_size), Child = visualBox = new Box { Colour = Color4.Tomato, @@ -205,7 +206,7 @@ namespace osu.Game.Tests.Visual.UserInterface { Alpha = 0.35f, RelativeSizeAxes = Axes.None, - Size = new Vector2(72), + Size = new Vector2(visual_box_size), Child = transferContainerBox = new Box { Colour = Color4.White, From 456459cafa91f49981dd085e43312d10e9dc1255 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 4 Apr 2019 13:25:24 +0900 Subject: [PATCH 378/623] Give flow container test long enough to finish --- .../Visual/UserInterface/TestCaseLogoFacadeContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoFacadeContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoFacadeContainer.cs index 5f864eeabb..0b664a310a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoFacadeContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoFacadeContainer.cs @@ -183,7 +183,7 @@ namespace osu.Game.Tests.Visual.UserInterface }, 700); }); - waitForMove(); + waitForMove(8); AddAssert("Logo is tracking", () => facadeContainer.IsLogoTracking); } @@ -219,7 +219,7 @@ namespace osu.Game.Tests.Visual.UserInterface facadeContainer.SetLogo(logo, 1.0f, 1000); } - private void waitForMove() => AddWaitStep("Wait for transforms to finish", 5); + private void waitForMove(int count = 5) => AddWaitStep("Wait for transforms to finish", count); private void removeFacade() { From 6f5e9fe50d3976de9c1d686bce6e14c75ada64f8 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 4 Apr 2019 13:33:13 +0900 Subject: [PATCH 379/623] Correct xmldoc --- osu.Game/Graphics/Containers/LogoFacadeContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/LogoFacadeContainer.cs b/osu.Game/Graphics/Containers/LogoFacadeContainer.cs index 1664fdde4d..c37588cb55 100644 --- a/osu.Game/Graphics/Containers/LogoFacadeContainer.cs +++ b/osu.Game/Graphics/Containers/LogoFacadeContainer.cs @@ -57,7 +57,7 @@ namespace osu.Game.Graphics.Containers /// /// Gets the position that the logo should move to with respect to the . - /// Manually performs a conversion of the Facade's position to the relative position of the Logo's Parent. + /// Manually performs a conversion of the Facade's position to the Logo's parent's relative space. /// /// Will only be correct if the logo's are set to Axes.Both /// The position that the logo should move to in its parent's relative space. From d54750aa37e5a94a6f1c34c4168c1e4876181b74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Apr 2019 17:10:29 +0900 Subject: [PATCH 380/623] Remove incorrect class --- .../Configuration/OsuConfigManager.cs | 32 ------------------- .../Configuration/OsuRulesetConfigManager.cs | 1 - 2 files changed, 33 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Configuration/OsuConfigManager.cs diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuConfigManager.cs deleted file mode 100644 index 492cc7cc7f..0000000000 --- a/osu.Game.Rulesets.Osu/Configuration/OsuConfigManager.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Configuration; -using osu.Game.Rulesets.Configuration; - -namespace osu.Game.Rulesets.Osu.Configuration -{ - public class OsuConfigManager : RulesetConfigManager - { - public OsuConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) - : base(settings, ruleset, variant) - { - } - - protected override void InitialiseDefaults() - { - base.InitialiseDefaults(); - - Set(OsuSetting.SnakingInSliders, true); - Set(OsuSetting.SnakingOutSliders, true); - Set(OsuSetting.ShowCursorTrail, true); - } - } - - public enum OsuSetting - { - SnakingInSliders, - SnakingOutSliders, - ShowCursorTrail - } -} diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index 872e152e0d..f76635a932 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Osu.Configuration protected override void InitialiseDefaults() { base.InitialiseDefaults(); - Set(OsuRulesetSetting.SnakingInSliders, true); Set(OsuRulesetSetting.SnakingOutSliders, true); Set(OsuRulesetSetting.ShowCursorTrail, true); From 9802d8ab1133d33e267afcb173efac0da82579e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Apr 2019 17:18:53 +0900 Subject: [PATCH 381/623] Rename setting --- osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs index 48eb74a74d..88adf72551 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.UI }, new SettingsCheckbox { - LabelText = "Show cursor trail", + LabelText = "Cursor trail", Bindable = config.GetBindable(OsuRulesetSetting.ShowCursorTrail) }, }; From e13fffaca3dd5b520a5073234d83a99b44f10cc3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 4 Apr 2019 16:32:04 +0900 Subject: [PATCH 382/623] Make ScoreTable use TableContainer --- .../Online/TestCaseBeatmapSetOverlay.cs | 3 - .../Visual/Online/TestCaseScoresContainer.cs | 3 - .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 184 +++++++++++++----- .../BeatmapSet/Scores/ScoreTableHeaderRow.cs | 54 ----- .../BeatmapSet/Scores/ScoreTableRow.cs | 104 ---------- .../BeatmapSet/Scores/ScoreTableScoreRow.cs | 114 ----------- 6 files changed, 136 insertions(+), 326 deletions(-) delete mode 100644 osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeaderRow.cs delete mode 100644 osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs delete mode 100644 osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs diff --git a/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs index 9cbccbd603..8363f8be04 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseBeatmapSetOverlay.cs @@ -25,9 +25,6 @@ namespace osu.Game.Tests.Visual.Online { typeof(Header), typeof(ScoreTable), - typeof(ScoreTableRow), - typeof(ScoreTableHeaderRow), - typeof(ScoreTableScoreRow), typeof(ScoreTableRowBackground), typeof(DrawableTopScore), typeof(ScoresContainer), diff --git a/osu.Game.Tests/Visual/Online/TestCaseScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestCaseScoresContainer.cs index 3d87e1743c..1ef4558691 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseScoresContainer.cs @@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual.Online typeof(TopScoreUserSection), typeof(TopScoreStatisticsSection), typeof(ScoreTable), - typeof(ScoreTableRow), - typeof(ScoreTableHeaderRow), - typeof(ScoreTableScoreRow), typeof(ScoreTableRowBackground), }; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 09ab0e3f6a..08e8dc7ecc 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -5,13 +5,26 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; +using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets.UI; using osu.Game.Scoring; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet.Scores { - public class ScoreTable : CompositeDrawable + public class ScoreTable : TableContainer { - private readonly ScoresGrid scoresGrid; + private const float horizontal_inset = 20; + private const float row_height = 25; + private const int text_size = 14; + private readonly FillFlowContainer backgroundFlow; public ScoreTable() @@ -19,77 +32,152 @@ namespace osu.Game.Overlays.BeatmapSet.Scores RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChildren = new Drawable[] + Padding = new MarginPadding { Horizontal = horizontal_inset }; + RowSize = new Dimension(GridSizeMode.Absolute, row_height); + + AddInternal(backgroundFlow = new FillFlowContainer { - backgroundFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 25 } - }, - scoresGrid = new ScoresGrid - { - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 40), - new Dimension(GridSizeMode.Absolute, 70), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Distributed, minSize: 150), - new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 90), - new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), - new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), - new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), - new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70), - new Dimension(GridSizeMode.Distributed, minSize: 40, maxSize: 70), - new Dimension(GridSizeMode.AutoSize), - } - } - }; + RelativeSizeAxes = Axes.Both, + Depth = 1f, + Padding = new MarginPadding { Horizontal = -horizontal_inset }, + Margin = new MarginPadding { Top = row_height } + }); } public IReadOnlyList Scores { set { - scoresGrid.Content = new Drawable[0][]; + Content = null; backgroundFlow.Clear(); if (value == null || !value.Any()) return; - var content = new List - { - new ScoreTableHeaderRow(value.First()).CreateDrawables().ToArray() - }; - for (int i = 0; i < value.Count; i++) - { - content.Add(new ScoreTableScoreRow(i, value[i]).CreateDrawables().ToArray()); backgroundFlow.Add(new ScoreTableRowBackground(i)); - } - scoresGrid.Content = content.ToArray(); + Columns = createHeaders(value[0]); + Content = value.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); } } - private class ScoresGrid : GridContainer + private TableColumn[] createHeaders(ScoreInfo score) { - public ScoresGrid() + var columns = new List { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; + new TableColumn("rank", Anchor.CentreRight, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 70)), // grade + new TableColumn("score", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("accuracy", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("player", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 150)), + new TableColumn("max combo", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 90)) + }; + + foreach (var statistic in score.Statistics) + columns.Add(new TableColumn(statistic.Key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70))); + + columns.AddRange(new[] + { + new TableColumn("pp", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 40, maxSize: 70)), + new TableColumn("mods", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)), + }); + + return columns.ToArray(); + } + + private Drawable[] createContent(int index, ScoreInfo score) + { + var content = new List + { + new OsuSpriteText + { + Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new DrawableRank(score.Rank) + { + Size = new Vector2(30, 20) + }, + new OsuSpriteText + { + Margin = new MarginPadding { Right = horizontal_inset }, + Text = $@"{score.TotalScore:N0}", + Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) + }, + new OsuSpriteText + { + Margin = new MarginPadding { Right = horizontal_inset }, + Text = $@"{score.Accuracy:P2}", + Font = OsuFont.GetFont(size: text_size), + Colour = score.Accuracy == 1 ? Color4.GreenYellow : Color4.White + }, + }; + + var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: text_size)) { AutoSizeAxes = Axes.Both }; + username.AddLink(score.User.Username, null, LinkAction.OpenUserProfile, score.User.Id.ToString(), "Open profile"); + + content.AddRange(new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Right = horizontal_inset }, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new DrawableFlag(score.User.Country) { Size = new Vector2(20, 13) }, + username + } + }, + new OsuSpriteText + { + Text = $@"{score.MaxCombo:N0}x", + Font = OsuFont.GetFont(size: text_size) + } + }); + + foreach (var kvp in score.Statistics) + { + content.Add(new OsuSpriteText + { + Text = $"{kvp.Value}", + Font = OsuFont.GetFont(size: text_size), + Colour = kvp.Value == 0 ? Color4.Gray : Color4.White + }); } - public Drawable[][] Content + content.AddRange(new Drawable[] { - get => base.Content; - set + new OsuSpriteText { - base.Content = value; + Text = $@"{score.PP:N0}", + Font = OsuFont.GetFont(size: text_size) + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + ChildrenEnumerable = score.Mods.Select(m => new ModIcon(m) + { + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.3f) + }) + }, + }); - RowDimensions = Enumerable.Repeat(new Dimension(GridSizeMode.Absolute, 25), value.Length).ToArray(); - } + return content.ToArray(); + } + + protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); + + private class HeaderText : OsuSpriteText + { + public HeaderText(string text) + { + Text = text.ToUpper(); + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black); } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeaderRow.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeaderRow.cs deleted file mode 100644 index f6f410a729..0000000000 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableHeaderRow.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Collections.Generic; -using osu.Framework.Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Scoring; - -namespace osu.Game.Overlays.BeatmapSet.Scores -{ - public class ScoreTableHeaderRow : ScoreTableRow - { - private readonly ScoreInfo score; - - public ScoreTableHeaderRow(ScoreInfo score) - { - this.score = score; - } - - protected override Drawable CreateIndexCell() => new CellText("rank"); - - protected override Drawable CreateRankCell() => new Container(); - - protected override Drawable CreateScoreCell() => new CellText("score"); - - protected override Drawable CreateAccuracyCell() => new CellText("accuracy"); - - protected override Drawable CreateUserCell() => new CellText("player"); - - protected override IEnumerable CreateStatisticsCells() - { - yield return new CellText("max combo"); - - foreach (var kvp in score.Statistics) - yield return new CellText(kvp.Key.GetDescription()); - } - - protected override Drawable CreatePpCell() => new CellText("pp"); - - protected override Drawable CreateModsCell() => new CellText("mods"); - - private class CellText : OsuSpriteText - { - public CellText(string text) - { - Text = text.ToUpper(); - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black); - } - } - } -} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs deleted file mode 100644 index 15355512ba..0000000000 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRow.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Collections.Generic; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Overlays.BeatmapSet.Scores -{ - public abstract class ScoreTableRow - { - protected const int TEXT_SIZE = 14; - - public IEnumerable CreateDrawables() - { - yield return new Container - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - AutoSizeAxes = Axes.Both, - Child = CreateIndexCell() - }; - - yield return new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Child = CreateRankCell() - }; - - yield return new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Right = 20 }, - Child = CreateScoreCell() - }; - - yield return new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Right = 20 }, - Child = CreateAccuracyCell() - }; - - yield return new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Right = 20 }, - Child = CreateUserCell() - }; - - foreach (var cell in CreateStatisticsCells()) - { - yield return new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Child = cell - }; - } - - yield return new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Child = CreatePpCell() - }; - - yield return new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Right = 20 }, - Child = CreateModsCell() - }; - } - - protected abstract Drawable CreateIndexCell(); - - protected abstract Drawable CreateRankCell(); - - protected abstract Drawable CreateScoreCell(); - - protected abstract Drawable CreateAccuracyCell(); - - protected abstract Drawable CreateUserCell(); - - protected abstract IEnumerable CreateStatisticsCells(); - - protected abstract Drawable CreatePpCell(); - - protected abstract Drawable CreateModsCell(); - } -} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs deleted file mode 100644 index 75d45d0b23..0000000000 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableScoreRow.cs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) ppy Pty Ltd . 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.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.Chat; -using osu.Game.Online.Leaderboards; -using osu.Game.Rulesets.UI; -using osu.Game.Scoring; -using osu.Game.Users; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Overlays.BeatmapSet.Scores -{ - public class ScoreTableScoreRow : ScoreTableRow - { - private readonly int index; - private readonly ScoreInfo score; - - public ScoreTableScoreRow(int index, ScoreInfo score) - { - this.index = index; - this.score = score; - } - - protected override Drawable CreateIndexCell() => new OsuSpriteText - { - Text = $"#{index + 1}", - Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold) - }; - - protected override Drawable CreateRankCell() => new DrawableRank(score.Rank) - { - Size = new Vector2(30, 20), - }; - - protected override Drawable CreateScoreCell() => new OsuSpriteText - { - Text = $@"{score.TotalScore:N0}", - Font = OsuFont.GetFont(size: TEXT_SIZE, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) - }; - - protected override Drawable CreateAccuracyCell() => new OsuSpriteText - { - Text = $@"{score.Accuracy:P2}", - Font = OsuFont.GetFont(size: TEXT_SIZE), - Colour = score.Accuracy == 1 ? Color4.GreenYellow : Color4.White - }; - - protected override Drawable CreateUserCell() - { - var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE)) - { - AutoSizeAxes = Axes.Both, - }; - - username.AddLink(score.User.Username, null, LinkAction.OpenUserProfile, score.User.Id.ToString(), "Open profile"); - - return new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), - Children = new Drawable[] - { - new DrawableFlag(score.User.Country) { Size = new Vector2(20, 13) }, - username - } - }; - } - - protected override IEnumerable CreateStatisticsCells() - { - yield return new OsuSpriteText - { - Text = $@"{score.MaxCombo:N0}x", - Font = OsuFont.GetFont(size: TEXT_SIZE) - }; - - foreach (var kvp in score.Statistics) - { - yield return new OsuSpriteText - { - Text = $"{kvp.Value}", - Font = OsuFont.GetFont(size: TEXT_SIZE), - Colour = kvp.Value == 0 ? Color4.Gray : Color4.White - }; - } - } - - protected override Drawable CreatePpCell() => new OsuSpriteText - { - Text = $@"{score.PP:N0}", - Font = OsuFont.GetFont(size: TEXT_SIZE) - }; - - protected override Drawable CreateModsCell() => new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - ChildrenEnumerable = score.Mods.Select(m => new ModIcon(m) - { - AutoSizeAxes = Axes.Both, - Scale = new Vector2(0.3f) - }) - }; - } -} From a6bf076dc7fe9c9777bf546285973df963170cae Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 4 Apr 2019 17:58:34 +0900 Subject: [PATCH 383/623] Adjust green colour --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 08e8dc7ecc..3addef854a 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -27,6 +28,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly FillFlowContainer backgroundFlow; + private Color4 highAccuracyColour; + public ScoreTable() { RelativeSizeAxes = Axes.X; @@ -44,6 +47,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }); } + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + highAccuracyColour = colours.GreenLight; + } + public IReadOnlyList Scores { set @@ -110,7 +119,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Margin = new MarginPadding { Right = horizontal_inset }, Text = $@"{score.Accuracy:P2}", Font = OsuFont.GetFont(size: text_size), - Colour = score.Accuracy == 1 ? Color4.GreenYellow : Color4.White + Colour = score.Accuracy == 1 ? highAccuracyColour : Color4.White }, }; From 7047f305a1a7af92532d8902e4490f96119d23a9 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 5 Apr 2019 12:02:47 +0900 Subject: [PATCH 384/623] Apply reviews, add safety for multiple facades --- ...er.cs => TestCaseLogoTrackingContainer.cs} | 71 ++++---- .../Containers/LogoFacadeContainer.cs | 114 ------------- .../Containers/LogoTrackingContainer.cs | 156 ++++++++++++++++++ osu.Game/Screens/Menu/ButtonSystem.cs | 24 +-- osu.Game/Screens/Menu/OsuLogo.cs | 6 + osu.Game/Screens/Play/PlayerLoader.cs | 23 +-- 6 files changed, 223 insertions(+), 171 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{TestCaseLogoFacadeContainer.cs => TestCaseLogoTrackingContainer.cs} (77%) delete mode 100644 osu.Game/Graphics/Containers/LogoFacadeContainer.cs create mode 100644 osu.Game/Graphics/Containers/LogoTrackingContainer.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoFacadeContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs similarity index 77% rename from osu.Game.Tests/Visual/UserInterface/TestCaseLogoFacadeContainer.cs rename to osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs index 0b664a310a..4d255eb25a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoFacadeContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs @@ -21,13 +21,13 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface { - public class TestCaseLogoFacadeContainer : OsuTestCase + public class TestCaseLogoTrackingContainer : OsuTestCase { public override IReadOnlyList RequiredTypes => new[] { typeof(PlayerLoader), typeof(Player), - typeof(LogoFacadeContainer), + typeof(LogoTrackingContainer), typeof(ButtonSystem), typeof(ButtonSystemState), typeof(Menu), @@ -36,11 +36,11 @@ namespace osu.Game.Tests.Visual.UserInterface private OsuLogo logo; private readonly Bindable uiScale = new Bindable(); - private TestLogoFacadeContainer facadeContainer; + private TestLogoTrackingContainer trackingContainer; private Container transferContainer; private Box visualBox; private Box transferContainerBox; - private Container logoFacade; + private Drawable logoFacade; private bool randomPositions; private const float visual_box_size = 72; @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.UserInterface { Clear(); Add(logo = new OsuLogo { Scale = new Vector2(0.15f), RelativePositionAxes = Axes.Both }); - facadeContainer = null; + trackingContainer = null; transferContainer = null; }); } @@ -72,10 +72,10 @@ namespace osu.Game.Tests.Visual.UserInterface public void MoveFacadeTest() { AddToggleStep("Toggle move continuously", b => randomPositions = b); - AddStep("Add facade containers", addFacadeContainers); + AddStep("Add tracking containers", addFacadeContainers); AddStep("Move facade to random position", startTrackingRandom); waitForMove(); - AddAssert("Logo is tracking", () => facadeContainer.IsLogoTracking); + AddAssert("Logo is tracking", () => trackingContainer.IsLogoTracking); } /// @@ -84,11 +84,11 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void RemoveFacadeTest() { - AddStep("Add facade containers", addFacadeContainers); + AddStep("Add tracking containers", addFacadeContainers); AddStep("Move facade to random position", startTrackingRandom); AddStep("Remove facade from FacadeContainer", removeFacade); waitForMove(); - AddAssert("Logo is not tracking", () => !facadeContainer.IsLogoTracking); + AddAssert("Logo is not tracking", () => !trackingContainer.IsLogoTracking); } /// @@ -97,17 +97,12 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TransferFacadeTest() { - AddStep("Add facade containers", addFacadeContainers); + AddStep("Add tracking containers", addFacadeContainers); AddStep("Move facade to random position", startTrackingRandom); AddStep("Remove facade from FacadeContainer", removeFacade); - AddStep("Transfer facade to a new container", () => - { - transferContainer.Add(logoFacade); - transferContainerBox.Colour = Color4.Tomato; - moveLogoFacade(); - }); + AddStep("Transfer facade to a new container", addFacadeToNewContainer); waitForMove(); - AddAssert("Logo is tracking", () => facadeContainer.IsLogoTracking); + AddAssert("Logo is tracking", () => trackingContainer.IsLogoTracking); } /// @@ -120,7 +115,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Create new flow container with facade", () => { - Add(facadeContainer = new TestLogoFacadeContainer + Add(trackingContainer = new TestLogoTrackingContainer { AutoSizeAxes = Axes.Both, Origin = Anchor.TopCentre, @@ -156,7 +151,7 @@ namespace osu.Game.Tests.Visual.UserInterface Colour = Color4.White, RelativeSizeAxes = Axes.Both, }, - facadeContainer.LogoFacade, + trackingContainer.LogoFacade, } }, new Box @@ -171,27 +166,34 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Perform logo movements", () => { - facadeContainer.Tracking = false; + trackingContainer.Tracking = false; logo.MoveTo(new Vector2(0.5f), 500, Easing.InOutExpo); - facadeContainer.SetLogo(logo, 1.0f, 1000, Easing.InOutExpo); + trackingContainer.SetLogo(logo, 1.0f, 1000, Easing.InOutExpo); visualBox.Colour = Color4.White; Scheduler.AddDelayed(() => { - facadeContainer.Tracking = true; + trackingContainer.Tracking = true; visualBox.Colour = Color4.Tomato; }, 700); }); waitForMove(8); - AddAssert("Logo is tracking", () => facadeContainer.IsLogoTracking); + AddAssert("Logo is tracking", () => trackingContainer.IsLogoTracking); + } + + [Test] + public void SetFacadeSizeTest() + { + AddStep("Add tracking containers", addFacadeContainers); + AddStep("Break shit", () => { logoFacade.Size = new Vector2(0, 0); }); } private void addFacadeContainers() { AddRange(new Drawable[] { - facadeContainer = new TestLogoFacadeContainer + trackingContainer = new TestLogoTrackingContainer { Alpha = 0.35f, RelativeSizeAxes = Axes.None, @@ -215,22 +217,29 @@ namespace osu.Game.Tests.Visual.UserInterface }, }); - facadeContainer.Add(logoFacade = facadeContainer.LogoFacade); - facadeContainer.SetLogo(logo, 1.0f, 1000); + trackingContainer.Add(logoFacade = trackingContainer.LogoFacade); + trackingContainer.SetLogo(logo, 1.0f, 1000); } private void waitForMove(int count = 5) => AddWaitStep("Wait for transforms to finish", count); private void removeFacade() { - facadeContainer.Remove(logoFacade); + trackingContainer.Remove(logoFacade); visualBox.Colour = Color4.White; moveLogoFacade(); } private void startTrackingRandom() { - facadeContainer.Tracking = true; + trackingContainer.Tracking = true; + moveLogoFacade(); + } + + private void addFacadeToNewContainer() + { + transferContainer.Add(logoFacade); + transferContainerBox.Colour = Color4.Tomato; moveLogoFacade(); } @@ -239,7 +248,7 @@ namespace osu.Game.Tests.Visual.UserInterface if (logoFacade?.Transforms.Count == 0 && transferContainer?.Transforms.Count == 0) { Random random = new Random(); - facadeContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300); + trackingContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300); transferContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300); } @@ -247,12 +256,12 @@ namespace osu.Game.Tests.Visual.UserInterface Schedule(moveLogoFacade); } - private class TestLogoFacadeContainer : LogoFacadeContainer + private class TestLogoTrackingContainer : LogoTrackingContainer { /// /// Check that the logo is tracking the position of the facade, with an acceptable precision lenience. /// - public bool IsLogoTracking => Precision.AlmostEquals(Logo.Position, LogoTrackingPosition()); + public bool IsLogoTracking => Precision.AlmostEquals(Logo.Position, LogoTrackingPosition); } } } diff --git a/osu.Game/Graphics/Containers/LogoFacadeContainer.cs b/osu.Game/Graphics/Containers/LogoFacadeContainer.cs deleted file mode 100644 index c37588cb55..0000000000 --- a/osu.Game/Graphics/Containers/LogoFacadeContainer.cs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.MathUtils; -using osu.Game.Screens.Menu; -using osuTK; - -namespace osu.Game.Graphics.Containers -{ - /// - /// A container that creates a to be used to update and track the position of an . - /// - public class LogoFacadeContainer : Container - { - protected virtual Facade CreateFacade() => new Facade(); - - public Facade LogoFacade { get; } - - /// - /// Whether or not the logo assigned to this FacadeContainer should be tracking the position its facade. - /// - public bool Tracking = false; - - protected OsuLogo Logo; - - private float facadeScale; - private Easing easing; - private Vector2? startPosition; - private double? startTime; - private double duration; - - public LogoFacadeContainer() - { - LogoFacade = CreateFacade(); - } - - /// - /// Assign the logo that should track the Facade's position, as well as how it should transform to its initial position. - /// - /// The instance of the logo to be used for tracking. - /// The scale of the facade. Does not actually affect the logo itself. - /// The duration of the initial transform. Default is instant. - /// The easing type of the initial transform. - public void SetLogo(OsuLogo logo, float facadeScale = 1.0f, double duration = 0, Easing easing = Easing.None) - { - Logo = logo ?? throw new ArgumentNullException(nameof(logo)); - this.facadeScale = facadeScale; - this.duration = duration; - this.easing = easing; - - startTime = null; - startPosition = null; - } - - /// - /// Gets the position that the logo should move to with respect to the . - /// Manually performs a conversion of the Facade's position to the Logo's parent's relative space. - /// - /// Will only be correct if the logo's are set to Axes.Both - /// The position that the logo should move to in its parent's relative space. - protected Vector2 LogoTrackingPosition() - { - Vector2 local; - local.X = Logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre).X / Logo.Parent.RelativeToAbsoluteFactor.X; - local.Y = Logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre).Y / Logo.Parent.RelativeToAbsoluteFactor.Y; - return local; - } - - protected override void Update() - { - base.Update(); - - if (Logo == null || !Tracking) - return; - - // Account for the scale of the actual logo container, as SizeForFlow only accounts for the sprite scale. - LogoFacade.Size = new Vector2(Logo.SizeForFlow * Logo.Scale.X * facadeScale); - - if (LogoFacade.Parent != null && Logo.Position != LogoTrackingPosition()) - { - // If this is our first update since tracking has started, initialize our starting values for interpolation - if (startTime == null || startPosition == null) - { - startTime = Time.Current; - startPosition = Logo.Position; - } - - if (duration != 0) - { - double elapsedDuration = Time.Current - startTime ?? throw new ArgumentNullException(nameof(startTime)); - - var mount = (float)Interpolation.ApplyEasing(easing, Math.Min(elapsedDuration / duration, 1)); - - // Interpolate the position of the logo, where mount 0 is where the logo was when it first began interpolating, and mount 1 is the target location. - Logo.Position = Vector2.Lerp(startPosition ?? throw new ArgumentNullException(nameof(startPosition)), LogoTrackingPosition(), mount); - } - else - { - Logo.Position = LogoTrackingPosition(); - } - } - } - - /// - /// A placeholder container that serves as a dummy object to denote another object's location and size. - /// - public class Facade : Container - { - } - } -} diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs new file mode 100644 index 0000000000..afb30f981b --- /dev/null +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -0,0 +1,156 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.MathUtils; +using osu.Game.Screens.Menu; +using osuTK; + +namespace osu.Game.Graphics.Containers +{ + /// + /// A container that handles tracking of an through different layout scenarios + /// + public class LogoTrackingContainer : Container + { + public Facade LogoFacade { get; } + + /// + /// Whether or not the logo assigned to this FacadeContainer should be tracking the position of its facade. + /// + public bool Tracking { get; set; } + + protected OsuLogo Logo; + + private float facadeScale; + private Easing easing; + private Vector2? startPosition; + private double? startTime; + private double duration; + + public LogoTrackingContainer() + { + LogoFacade = new ExposedFacade(); + } + + /// + /// Assign the logo that should track the Facade's position, as well as how it should transform to its initial position. + /// + /// The instance of the logo to be used for tracking. + /// The scale of the facade. Does not actually affect the logo itself. + /// The duration of the initial transform. Default is instant. + /// The easing type of the initial transform. + public void SetLogo(OsuLogo logo, float facadeScale = 1.0f, double duration = 0, Easing easing = Easing.None) + { + if (Logo != logo) + { + if (logo?.HasTrackingContainer ?? throw new ArgumentNullException(nameof(logo))) + { + // Prevent the same logo from being added to multiple LogoTrackingContainers. + throw new InvalidOperationException($"Cannot add an instance of {typeof(OsuLogo)} to multiple {typeof(LogoTrackingContainer)}s"); + } + + if (Logo != null) + { + // If we're replacing the logo to be tracked, the old one no longer has a tracking container + Logo.HasTrackingContainer = false; + } + } + + Logo = logo; + Logo.HasTrackingContainer = true; + this.facadeScale = facadeScale; + this.duration = duration; + this.easing = easing; + + startTime = null; + startPosition = null; + } + + /// + /// Gets the position that the logo should move to with respect to the . + /// Manually performs a conversion of the Facade's position to the Logo's parent's relative space. + /// + /// Will only be correct if the logo's are set to Axes.Both + protected Vector2 LogoTrackingPosition => new Vector2(Logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre).X / Logo.Parent.RelativeToAbsoluteFactor.X, + Logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre).Y / Logo.Parent.RelativeToAbsoluteFactor.Y); + + protected override void Update() + { + base.Update(); + + if (Logo == null || !Tracking) + return; + + // Account for the scale of the actual OsuLogo, as SizeForFlow only accounts for the sprite scale. + ((ExposedFacade)LogoFacade).SetSize(new Vector2(Logo.SizeForFlow * Logo.Scale.X * facadeScale)); + + if (LogoFacade.Parent != null && Logo.Position != LogoTrackingPosition && Logo.RelativePositionAxes == Axes.Both) + { + // If this is our first update since tracking has started, initialize our starting values for interpolation + if (startTime == null || startPosition == null) + { + startTime = Time.Current; + startPosition = Logo.Position; + } + + if (duration != 0) + { + double elapsedDuration = (double)(Time.Current - startTime); + + var amount = (float)Interpolation.ApplyEasing(easing, Math.Min(elapsedDuration / duration, 1)); + + // Interpolate the position of the logo, where amount 0 is where the logo was when it first began interpolating, and amount 1 is the target location. + Logo.Position = Vector2.Lerp((Vector2)startPosition, LogoTrackingPosition, amount); + } + else + { + Logo.Position = LogoTrackingPosition; + } + } + } + + protected override void Dispose(bool isDisposing) + { + if (Logo != null) + Logo.HasTrackingContainer = false; + + base.Dispose(isDisposing); + } + + private Vector2 size; + + private class ExposedFacade : Facade + { + public override Vector2 Size + { + get => base.Size; + set => throw new InvalidOperationException($"Cannot set the Size of a {typeof(Facade)} outside of a {typeof(LogoTrackingContainer)}"); + } + + public void SetSize(Vector2 size) + { + base.SetSize(size); + } + } + + /// + /// A dummy object used to denote another object's location. + /// + public abstract class Facade : Drawable + { + public override Vector2 Size + { + get => base.Size; + set => throw new InvalidOperationException($"Cannot set the Size of a {typeof(Facade)} outside of a {typeof(LogoTrackingContainer)}"); + } + + protected void SetSize(Vector2 size) + { + base.Size = size; + } + } + } +} diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index cb95ec1765..a359893418 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Menu if (this.logo != null) { this.logo.Action = onOsuLogo; - logoFacadeContainer.SetLogo(logo, 0.74f); + logoTrackingContainer.SetLogo(logo, 0.74f); // osuLogo.SizeForFlow relies on loading to be complete. buttonArea.Flow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + this.logo.SizeForFlow / 4), 0); @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Menu else { // We should stop tracking as the facade is now out of scope. - logoFacadeContainer.Tracking = false; + logoTrackingContainer.Tracking = false; } } @@ -83,29 +83,29 @@ namespace osu.Game.Screens.Menu private SampleChannel sampleBack; - private readonly LogoFacadeContainer logoFacadeContainer; + private readonly LogoTrackingContainer logoTrackingContainer; public ButtonSystem() { RelativeSizeAxes = Axes.Both; - Child = logoFacadeContainer = new LogoFacadeContainer + Child = logoTrackingContainer = new LogoTrackingContainer { RelativeSizeAxes = Axes.Both, Child = buttonArea = new ButtonArea() }; - buttonArea.AddRange(new Container[] + buttonArea.AddRange(new Drawable[] { new Button(@"settings", string.Empty, FontAwesome.Gear, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), backButton = new Button(@"back", @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) { VisibleState = ButtonSystemState.Play, }, - logoFacadeContainer.LogoFacade + logoTrackingContainer.LogoFacade }); - buttonArea.Flow.CentreTarget = logoFacadeContainer.LogoFacade; + buttonArea.Flow.CentreTarget = logoTrackingContainer.LogoFacade; } [Resolved(CanBeNull = true)] @@ -271,7 +271,7 @@ namespace osu.Game.Screens.Menu logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => { - logoFacadeContainer.Tracking = false; + logoTrackingContainer.Tracking = false; game?.Toolbar.Hide(); @@ -294,8 +294,8 @@ namespace osu.Game.Screens.Menu if (lastState == ButtonSystemState.Initial) logo.ScaleTo(0.5f, 200, Easing.In); - logoFacadeContainer.SetLogo(logo, 0.74f, lastState == ButtonSystemState.EnteringMode ? 0 : 200, Easing.In); - logoFacadeContainer.Tracking = true; + logoTrackingContainer.SetLogo(logo, 0.74f, lastState == ButtonSystemState.EnteringMode ? 0 : 200, Easing.In); + logoTrackingContainer.Tracking = true; logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => @@ -308,14 +308,14 @@ namespace osu.Game.Screens.Menu break; default: logo.ClearTransforms(targetMember: nameof(Position)); - logoFacadeContainer.Tracking = true; + logoTrackingContainer.Tracking = true; logo.ScaleTo(0.5f, 200, Easing.OutQuint); break; } break; case ButtonSystemState.EnteringMode: - logoFacadeContainer.Tracking = true; + logoTrackingContainer.Tracking = true; break; } } diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index c54ccd21b5..ad70d34637 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -54,8 +54,14 @@ namespace osu.Game.Screens.Menu /// public Func Action; + /// + /// The size of the logo Sprite with respect to the scale of its hover and bounce containers. + /// + /// Does not account for the scale of this public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X; + public bool HasTrackingContainer { get; set; } + private readonly Sprite ripple; private readonly Container rippleContainer; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index ccfd965f53..f7feb3535d 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play private Player player; - private LogoFacadeContainer content; + private LogoTrackingContainer content; private BeatmapMetadataDisplay info; @@ -60,14 +60,12 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { - InternalChild = content = new LogoFacadeContainer + InternalChild = (content = new LogoTrackingContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - }; - - content.Children = new Drawable[] + }).WithChildren(new Drawable[] { info = new BeatmapMetadataDisplay(Beatmap.Value, content.LogoFacade) { @@ -89,7 +87,7 @@ namespace osu.Game.Screens.Play new InputSettings() } } - }; + }); loadNewPlayer(); } @@ -163,12 +161,9 @@ namespace osu.Game.Screens.Play logo.ScaleTo(new Vector2(0.15f), duration, Easing.In); logo.FadeIn(350); - content.SetLogo(logo, 1.0f, 500, Easing.InOutExpo); + content.SetLogo(logo, 1.0f, resuming ? 0 : 500, Easing.InOutExpo); - Scheduler.AddDelayed(() => - { - content.Tracking = true; - }, resuming ? 0 : 500); + Scheduler.AddDelayed(() => { content.Tracking = true; }, resuming ? 0 : 500); } protected override void LogoExiting(OsuLogo logo) @@ -321,7 +316,7 @@ namespace osu.Game.Screens.Play } private readonly WorkingBeatmap beatmap; - private readonly Container facade; + private readonly Drawable facade; private LoadingAnimation loading; private Sprite backgroundSprite; private ModDisplay modDisplay; @@ -343,7 +338,7 @@ namespace osu.Game.Screens.Play } } - public BeatmapMetadataDisplay(WorkingBeatmap beatmap, Container facade) + public BeatmapMetadataDisplay(WorkingBeatmap beatmap, Drawable facade) { this.beatmap = beatmap; this.facade = facade; @@ -366,7 +361,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Direction = FillDirection.Vertical, - Children = new Drawable[] + Children = new[] { facade, new OsuSpriteText From b1d74e57e58fc175fed9061dabed5f8e6d2e9642 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 5 Apr 2019 13:56:08 +0900 Subject: [PATCH 385/623] Add checks guarding against setting tracking on multiple trackingcongtainers and setting facade size --- .../Containers/LogoTrackingContainer.cs | 48 ++++++++++--------- osu.Game/Screens/Menu/OsuLogo.cs | 2 +- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs index afb30f981b..ee6d5dec20 100644 --- a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Reflection.Metadata.Ecma335; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; @@ -20,7 +21,24 @@ namespace osu.Game.Graphics.Containers /// /// Whether or not the logo assigned to this FacadeContainer should be tracking the position of its facade. /// - public bool Tracking { get; set; } + public bool Tracking + { + get => tracking; + set + { + if (Logo != null) + { + if (value && !tracking && Logo.IsTracking) + throw new InvalidOperationException($"Cannot track an instance of {typeof(OsuLogo)} to multiple {typeof(LogoTrackingContainer)}s"); + + Logo.IsTracking = value; + } + + tracking = value; + } + } + + private bool tracking; protected OsuLogo Logo; @@ -44,23 +62,15 @@ namespace osu.Game.Graphics.Containers /// The easing type of the initial transform. public void SetLogo(OsuLogo logo, float facadeScale = 1.0f, double duration = 0, Easing easing = Easing.None) { - if (Logo != logo) + if (Logo != logo && Logo != null) { - if (logo?.HasTrackingContainer ?? throw new ArgumentNullException(nameof(logo))) - { - // Prevent the same logo from being added to multiple LogoTrackingContainers. - throw new InvalidOperationException($"Cannot add an instance of {typeof(OsuLogo)} to multiple {typeof(LogoTrackingContainer)}s"); - } - - if (Logo != null) - { - // If we're replacing the logo to be tracked, the old one no longer has a tracking container - Logo.HasTrackingContainer = false; - } + // If we're replacing the logo to be tracked, the old one no longer has a tracking container + Logo.IsTracking = false; } - Logo = logo; - Logo.HasTrackingContainer = true; + Logo = logo ?? throw new ArgumentNullException(nameof(logo)); + Logo.IsTracking = Tracking; + this.facadeScale = facadeScale; this.duration = duration; this.easing = easing; @@ -115,7 +125,7 @@ namespace osu.Game.Graphics.Containers protected override void Dispose(bool isDisposing) { if (Logo != null) - Logo.HasTrackingContainer = false; + Tracking = false; base.Dispose(isDisposing); } @@ -124,12 +134,6 @@ namespace osu.Game.Graphics.Containers private class ExposedFacade : Facade { - public override Vector2 Size - { - get => base.Size; - set => throw new InvalidOperationException($"Cannot set the Size of a {typeof(Facade)} outside of a {typeof(LogoTrackingContainer)}"); - } - public void SetSize(Vector2 size) { base.SetSize(size); diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index ad70d34637..4631f4e222 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Menu /// Does not account for the scale of this public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X; - public bool HasTrackingContainer { get; set; } + public bool IsTracking { get; set; } private readonly Sprite ripple; From 15fbb6f176395ae2ef5cc76c9cfe12ba2c53f6e4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Apr 2019 14:15:36 +0900 Subject: [PATCH 386/623] Use common AddUserLink method --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 4 ++++ osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 3 +-- osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs | 3 +-- osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs | 3 +-- osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs | 5 ++--- osu.Game/Screens/Multi/Match/Components/HostInfo.cs | 4 +--- 6 files changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index dace873b92..eefbeea24c 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Users; namespace osu.Game.Graphics.Containers { @@ -75,6 +76,9 @@ namespace osu.Game.Graphics.Containers return createLink(text, null, url, linkType, linkArgument, tooltipText); } + public IEnumerable AddUserLink(User user, Action creationParameters = null) + => createLink(AddText(user.Username, creationParameters), user.Username, null, LinkAction.OpenUserProfile, user.Id.ToString(), "View profile"); + private IEnumerable createLink(IEnumerable drawables, string text, string url = null, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action action = null) { AddInternal(new DrawableLinkCompiler(drawables.OfType().ToList()) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 3addef854a..693ce958dd 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -10,7 +10,6 @@ using osu.Framework.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Online.Chat; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets.UI; using osu.Game.Scoring; @@ -124,7 +123,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }; var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: text_size)) { AutoSizeAxes = Axes.Both }; - username.AddLink(score.User.Username, null, LinkAction.OpenUserProfile, score.User.Id.ToString(), "Open profile"); + username.AddUserLink(score.User); content.AddRange(new Drawable[] { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index f401dc93a7..c0d9ecad3a 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Online.Chat; using osu.Game.Online.Leaderboards; using osu.Game.Scoring; using osu.Game.Users; @@ -119,7 +118,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores date.Text = $@"achieved {value.Date.Humanize()}"; usernameText.Clear(); - usernameText.AddLink(value.User.Username, null, LinkAction.OpenUserProfile, value.User.Id.ToString(), "Open profile"); + usernameText.AddUserLink(value.User); rank.UpdateRank(value.Rank); } diff --git a/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs index 23771451bd..d63f2fecd2 100644 --- a/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs +++ b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Online.Chat; using osuTK; namespace osu.Game.Screens.Multi.Components @@ -60,7 +59,7 @@ namespace osu.Game.Screens.Multi.Components if (beatmap != null) { beatmapAuthor.AddText("mapped by ", s => s.Colour = OsuColour.Gray(0.8f)); - beatmapAuthor.AddLink(beatmap.Metadata.Author.Username, null, LinkAction.OpenUserProfile, beatmap.Metadata.Author.Id.ToString(), "View Profile"); + beatmapAuthor.AddUserLink(beatmap.Metadata.Author); } }, true); } diff --git a/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs b/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs index 40e59de25d..51d3c93624 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Online.Chat; using osu.Game.Users; using osuTK; @@ -95,8 +94,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components if (host.NewValue != null) { hostText.AddText("hosted by "); - hostText.AddLink(host.NewValue.Username, null, LinkAction.OpenUserProfile, host.NewValue.Id.ToString(), "Open profile", - s => s.Font = s.Font.With(Typeface.Exo, weight: FontWeight.Bold, italics: true)); + hostText.AddUserLink(host.NewValue, s => s.Font = s.Font.With(Typeface.Exo, weight: FontWeight.Bold, italics: true)); + flagContainer.Child = new DrawableFlag(host.NewValue.Country) { RelativeSizeAxes = Axes.Both }; } }, true); diff --git a/osu.Game/Screens/Multi/Match/Components/HostInfo.cs b/osu.Game/Screens/Multi/Match/Components/HostInfo.cs index 02c8929f44..b898cd0466 100644 --- a/osu.Game/Screens/Multi/Match/Components/HostInfo.cs +++ b/osu.Game/Screens/Multi/Match/Components/HostInfo.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Online.Chat; using osu.Game.Users; using osuTK; @@ -54,8 +53,7 @@ namespace osu.Game.Screens.Multi.Match.Components { linkContainer.AddText("hosted by"); linkContainer.NewLine(); - linkContainer.AddLink(host.Username, null, LinkAction.OpenUserProfile, host.Id.ToString(), "View Profile", - s => s.Font = s.Font.With(Typeface.Exo, weight: FontWeight.Bold, italics: true)); + linkContainer.AddUserLink(host, s => s.Font = s.Font.With(Typeface.Exo, weight: FontWeight.Bold, italics: true)); } } } From a34a511f221e5d964772228d5feb1fb331fed543 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Apr 2019 14:55:25 +0900 Subject: [PATCH 387/623] Update framework --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f3c648cf02..74ed9f91dd 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 7ce7329246..9fff64c61c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 5fa93f4a050263be0a2a546bcbfbd1bbc97e2771 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 5 Apr 2019 15:05:11 +0900 Subject: [PATCH 388/623] Add test for checking exception --- .../UserInterface/TestCaseLogoTrackingContainer.cs | 12 +++++++++++- .../Graphics/Containers/LogoTrackingContainer.cs | 11 +++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs index 4d255eb25a..3b57d892ad 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs @@ -186,7 +186,17 @@ namespace osu.Game.Tests.Visual.UserInterface public void SetFacadeSizeTest() { AddStep("Add tracking containers", addFacadeContainers); - AddStep("Break shit", () => { logoFacade.Size = new Vector2(0, 0); }); + AddStep("Break stuff", () => { logoFacade.Size = new Vector2(0, 0); }); + } + + [Test] + public void SetMultipleContainers() + { + LogoTrackingContainer newContainer = new LogoTrackingContainer(); + AddStep("Add tracking containers", addFacadeContainers); + AddStep("Move facade to random position", startTrackingRandom); + AddStep("Add logo to new container", () => newContainer.SetLogo(logo)); + AddStep("Break stuff", () => newContainer.Tracking = true); } private void addFacadeContainers() diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs index ee6d5dec20..ab6f7ebc2a 100644 --- a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -12,7 +12,7 @@ using osuTK; namespace osu.Game.Graphics.Containers { /// - /// A container that handles tracking of an through different layout scenarios + /// A container that handles tracking of an through different layout scenarios. /// public class LogoTrackingContainer : Container { @@ -38,8 +38,6 @@ namespace osu.Game.Graphics.Containers } } - private bool tracking; - protected OsuLogo Logo; private float facadeScale; @@ -47,6 +45,7 @@ namespace osu.Game.Graphics.Containers private Vector2? startPosition; private double? startTime; private double duration; + private bool tracking; public LogoTrackingContainer() { @@ -69,7 +68,11 @@ namespace osu.Game.Graphics.Containers } Logo = logo ?? throw new ArgumentNullException(nameof(logo)); - Logo.IsTracking = Tracking; + + if (Tracking) + { + Logo.IsTracking = true; + } this.facadeScale = facadeScale; this.duration = duration; From e06fe7950b04bec9df82a6244281b06faaf4892d Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 5 Apr 2019 15:06:37 +0900 Subject: [PATCH 389/623] Cleanup --- .../TestCaseLogoTrackingContainer.cs | 17 ----------------- .../Containers/LogoTrackingContainer.cs | 3 --- 2 files changed, 20 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs index 3b57d892ad..19e430da3e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs @@ -182,23 +182,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("Logo is tracking", () => trackingContainer.IsLogoTracking); } - [Test] - public void SetFacadeSizeTest() - { - AddStep("Add tracking containers", addFacadeContainers); - AddStep("Break stuff", () => { logoFacade.Size = new Vector2(0, 0); }); - } - - [Test] - public void SetMultipleContainers() - { - LogoTrackingContainer newContainer = new LogoTrackingContainer(); - AddStep("Add tracking containers", addFacadeContainers); - AddStep("Move facade to random position", startTrackingRandom); - AddStep("Add logo to new container", () => newContainer.SetLogo(logo)); - AddStep("Break stuff", () => newContainer.Tracking = true); - } - private void addFacadeContainers() { AddRange(new Drawable[] diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs index ab6f7ebc2a..746710bcda 100644 --- a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Reflection.Metadata.Ecma335; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; @@ -133,8 +132,6 @@ namespace osu.Game.Graphics.Containers base.Dispose(isDisposing); } - private Vector2 size; - private class ExposedFacade : Facade { public void SetSize(Vector2 size) From 37ffe47e4b763140befcea82f29cfe662869b2c4 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 5 Apr 2019 15:30:09 +0900 Subject: [PATCH 390/623] Add back exception tests with better descriptions --- .../TestCaseLogoTrackingContainer.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs index 19e430da3e..cf84a34344 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs @@ -182,6 +182,49 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("Logo is tracking", () => trackingContainer.IsLogoTracking); } + [Test] + public void SetFacadeSizeTest() + { + bool failed = false; + AddStep("Add tracking containers", addFacadeContainers); + AddStep("Try setting facade size", () => + { + try + { + logoFacade.Size = new Vector2(0, 0); + } + catch (Exception e) + { + if (e is InvalidOperationException) + failed = true; + } + }); + AddAssert("Exception thrown", () => failed); + } + + [Test] + public void SetMultipleContainers() + { + bool failed = false; + LogoTrackingContainer newContainer = new LogoTrackingContainer(); + AddStep("Add tracking containers", addFacadeContainers); + AddStep("Move facade to random position", startTrackingRandom); + AddStep("Add logo to new container", () => newContainer.SetLogo(logo)); + AddStep("Try tracking new container", () => + { + try + { + newContainer.Tracking = true; + } + catch (Exception e) + { + if (e is InvalidOperationException) + failed = true; + } + }); + AddAssert("Exception thrown", () => failed); + } + private void addFacadeContainers() { AddRange(new Drawable[] From c693d1fad89ea3b3d4ab655808668a6350bb9f6c Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 5 Apr 2019 15:48:48 +0900 Subject: [PATCH 391/623] Further condense steps --- .../TestCaseLogoTrackingContainer.cs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs index cf84a34344..01a3113fe1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs @@ -100,7 +100,12 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Add tracking containers", addFacadeContainers); AddStep("Move facade to random position", startTrackingRandom); AddStep("Remove facade from FacadeContainer", removeFacade); - AddStep("Transfer facade to a new container", addFacadeToNewContainer); + AddStep("Transfer facade to a new container", () => + { + transferContainer.Add(logoFacade); + transferContainerBox.Colour = Color4.Tomato; + moveLogoFacade(); + }); waitForMove(); AddAssert("Logo is tracking", () => trackingContainer.IsLogoTracking); } @@ -186,7 +191,11 @@ namespace osu.Game.Tests.Visual.UserInterface public void SetFacadeSizeTest() { bool failed = false; - AddStep("Add tracking containers", addFacadeContainers); + AddStep("Set up scenario", () => + { + failed = false; + addFacadeContainers(); + }); AddStep("Try setting facade size", () => { try @@ -203,13 +212,18 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void SetMultipleContainers() + public void SetMultipleContainersTest() { bool failed = false; LogoTrackingContainer newContainer = new LogoTrackingContainer(); - AddStep("Add tracking containers", addFacadeContainers); - AddStep("Move facade to random position", startTrackingRandom); - AddStep("Add logo to new container", () => newContainer.SetLogo(logo)); + AddStep("Set up scenario", () => + { + failed = false; + newContainer = new LogoTrackingContainer(); + addFacadeContainers(); + startTrackingRandom(); + newContainer.SetLogo(logo); + }); AddStep("Try tracking new container", () => { try @@ -272,13 +286,6 @@ namespace osu.Game.Tests.Visual.UserInterface moveLogoFacade(); } - private void addFacadeToNewContainer() - { - transferContainer.Add(logoFacade); - transferContainerBox.Colour = Color4.Tomato; - moveLogoFacade(); - } - private void moveLogoFacade() { if (logoFacade?.Transforms.Count == 0 && transferContainer?.Transforms.Count == 0) From 53906e87ec35fcb657fd4700dd3039bc8d878347 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 5 Apr 2019 00:00:21 -0700 Subject: [PATCH 392/623] Split floatingOverlayContent --- osu.Game/OsuGame.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index f19860a884..bb279a0bc2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -399,7 +399,8 @@ namespace osu.Game } }, overlayContent = new Container { RelativeSizeAxes = Axes.Both }, - floatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, + rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, + leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, idleTracker = new GameIdleTracker(6000) }); @@ -427,6 +428,7 @@ namespace osu.Game }, }, topMostOverlayContent.Add); + loadComponentSingleFile(volume = new VolumeOverlay(), rightFloatingOverlayContent.Add); loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add); loadComponentSingleFile(loginOverlay = new LoginOverlay @@ -434,7 +436,7 @@ namespace osu.Game GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - }, floatingOverlayContent.Add); + }, rightFloatingOverlayContent.Add); loadComponentSingleFile(screenshotManager, Add); @@ -443,6 +445,7 @@ namespace osu.Game loadComponentSingleFile(social = new SocialOverlay(), overlayContent.Add); loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal); loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add); + loadComponentSingleFile(settings = new MainSettings { GetToolbarHeight = () => ToolbarOffset }, leftFloatingOverlayContent.Add); loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add); loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add); @@ -451,17 +454,14 @@ namespace osu.Game GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - }, floatingOverlayContent.Add); + }, rightFloatingOverlayContent.Add); loadComponentSingleFile(musicController = new MusicController { GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - }, floatingOverlayContent.Add); - - loadComponentSingleFile(volume = new VolumeOverlay(), floatingOverlayContent.Add); - loadComponentSingleFile(settings = new MainSettings { GetToolbarHeight = () => ToolbarOffset }, floatingOverlayContent.Add); + }, rightFloatingOverlayContent.Add); loadComponentSingleFile(accountCreation = new AccountCreationOverlay(), topMostOverlayContent.Add); loadComponentSingleFile(dialogOverlay = new DialogOverlay(), topMostOverlayContent.Add); @@ -710,7 +710,9 @@ namespace osu.Game private Container overlayContent; - private Container floatingOverlayContent; + private Container rightFloatingOverlayContent; + + private Container leftFloatingOverlayContent; private Container topMostOverlayContent; From debda7ae7de8c0b4bd57fc48a9dec0c01723dd45 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 5 Apr 2019 00:05:42 -0700 Subject: [PATCH 393/623] Fix volume overlay container reference --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index bb279a0bc2..15cd6d73e5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -428,7 +428,7 @@ namespace osu.Game }, }, topMostOverlayContent.Add); - loadComponentSingleFile(volume = new VolumeOverlay(), rightFloatingOverlayContent.Add); + loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add); loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add); loadComponentSingleFile(loginOverlay = new LoginOverlay From 0208526837180c9e19f7f297b7d5c49d0629398c Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 5 Apr 2019 18:21:54 +0900 Subject: [PATCH 394/623] Fix button system visual issue --- osu.Game/Screens/Menu/Button.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index 794fc093d3..383150cbd3 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -63,6 +63,8 @@ namespace osu.Game.Screens.Menu { box = new Container { + // box needs to be always present to ensure the button is always sized correctly for flow + AlwaysPresent = true, Masking = true, MaskingSmoothness = 2, EdgeEffect = new EdgeEffectParameters From cb3532e7d46a7b9614a4161463ac32a7c8aa2105 Mon Sep 17 00:00:00 2001 From: theFerdi265 Date: Sat, 6 Apr 2019 00:53:02 +0200 Subject: [PATCH 395/623] BeatmapLeaderboard: fix local scores not being sorted --- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index ebb1d78ba0..042ef0f5a1 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Select.Leaderboards { if (Scope == BeatmapLeaderboardScope.Local) { - Scores = scoreManager.QueryScores(s => !s.DeletePending && s.Beatmap.ID == Beatmap.ID).ToArray(); + Scores = scoreManager.QueryScores(s => !s.DeletePending && s.Beatmap.ID == Beatmap.ID).OrderBy(s => -s.TotalScore).ToArray(); PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; return null; } From d5b7865ab8c73ae2f3aad743a190ef12d5ef983c Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 6 Apr 2019 17:40:48 +0200 Subject: [PATCH 396/623] Invert notification and loginOverlay depth order --- osu.Game/OsuGame.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 15cd6d73e5..4f92ff831c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -431,7 +431,7 @@ namespace osu.Game loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add); loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add); - loadComponentSingleFile(loginOverlay = new LoginOverlay + loadComponentSingleFile(notifications = new NotificationOverlay { GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, @@ -449,7 +449,7 @@ namespace osu.Game loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add); loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add); - loadComponentSingleFile(notifications = new NotificationOverlay + loadComponentSingleFile(loginOverlay = new LoginOverlay { GetToolbarHeight = () => ToolbarOffset, Anchor = Anchor.TopRight, From 59410dbe3b739a5708604dddb8dd7e5df48236be Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 6 Apr 2019 18:05:13 +0200 Subject: [PATCH 397/623] Open login overlay when notification asking for signing in to play multi is clicked --- osu.Game/Screens/Menu/ButtonSystem.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index d3cf23dab8..77be5d08d9 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -103,6 +103,9 @@ namespace osu.Game.Screens.Menu [Resolved(CanBeNull = true)] private NotificationOverlay notifications { get; set; } + [Resolved] + private LoginOverlay loginOverlay { get; set; } + [BackgroundDependencyLoader(true)] private void load(AudioManager audio, IdleTracker idleTracker, GameHost host) { @@ -134,8 +137,13 @@ namespace osu.Game.Screens.Menu { notifications?.Post(new SimpleNotification { - Text = "You gotta be logged in to multi 'yo!", - Icon = FontAwesome.Solid.Globe + Text = "You gotta be logged in to multi 'yo!", + Icon = FontAwesome.Solid.Globe, + Activated = () => + { + loginOverlay.Show(); + return true; + } }); return; From 394f14965b88c745252a3932af9337c30d17037d Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 6 Apr 2019 18:23:56 +0200 Subject: [PATCH 398/623] Make appveyor happy with whitespaces --- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 77be5d08d9..d311664737 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -137,7 +137,7 @@ namespace osu.Game.Screens.Menu { notifications?.Post(new SimpleNotification { - Text = "You gotta be logged in to multi 'yo!", + Text = "You gotta be logged in to multi 'yo!", Icon = FontAwesome.Solid.Globe, Activated = () => { From 3f2e6a9376e024ada4a91e565f7b66b3eb232439 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 6 Apr 2019 18:52:30 +0200 Subject: [PATCH 399/623] Allow loginOverlay to be null if there's no cached instance in DI (for testing cases) --- osu.Game/Screens/Menu/ButtonSystem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index d311664737..80fbce2cf8 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Menu [Resolved(CanBeNull = true)] private NotificationOverlay notifications { get; set; } - [Resolved] + [Resolved(CanBeNull = true)] private LoginOverlay loginOverlay { get; set; } [BackgroundDependencyLoader(true)] @@ -141,7 +141,7 @@ namespace osu.Game.Screens.Menu Icon = FontAwesome.Solid.Globe, Activated = () => { - loginOverlay.Show(); + loginOverlay?.Show(); return true; } }); From d431ca4efc2f1cf384d0cf595213130792474445 Mon Sep 17 00:00:00 2001 From: Ferdinand Bachmann Date: Sun, 7 Apr 2019 13:26:35 +0200 Subject: [PATCH 400/623] BeatmapLeaderboard: use OrderByDescending instead of negation (suggested by peppy) --- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 042ef0f5a1..aafa6bb0eb 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Select.Leaderboards { if (Scope == BeatmapLeaderboardScope.Local) { - Scores = scoreManager.QueryScores(s => !s.DeletePending && s.Beatmap.ID == Beatmap.ID).OrderBy(s => -s.TotalScore).ToArray(); + Scores = scoreManager.QueryScores(s => !s.DeletePending && s.Beatmap.ID == Beatmap.ID).OrderByDescending(s => s.TotalScore).ToArray(); PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; return null; } From e76897d6a76091885e3318d7bfccae9fe9268277 Mon Sep 17 00:00:00 2001 From: DrabWeb Date: Sun, 7 Apr 2019 16:32:55 -0300 Subject: [PATCH 401/623] Fix ResumeContainers appearing when resuming during breaks. --- osu.Game/Screens/Play/BreakOverlay.cs | 4 ++++ osu.Game/Screens/Play/Player.cs | 8 +++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index d390787090..ab81e613cc 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -23,6 +23,8 @@ namespace osu.Game.Screens.Play private readonly Container fadeContainer; + public bool IsActive { get; private set; } + public List Breaks { get => breaks; @@ -125,6 +127,7 @@ namespace osu.Game.Screens.Play using (BeginAbsoluteSequence(b.StartTime, true)) { + Schedule(() => IsActive = true); fadeContainer.FadeIn(fade_duration); breakArrows.Show(fade_duration); @@ -142,6 +145,7 @@ namespace osu.Game.Screens.Play using (BeginDelayedSequence(b.Duration - fade_duration, true)) { + Schedule(() => IsActive = false); fadeContainer.FadeOut(fade_duration); breakArrows.Hide(fade_duration); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0eebefec86..60287f7a2e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -60,6 +60,8 @@ namespace osu.Game.Screens.Play private SampleChannel sampleRestart; + private BreakOverlay breakOverlay; + protected ScoreProcessor ScoreProcessor { get; private set; } protected DrawableRuleset DrawableRuleset { get; private set; } @@ -115,7 +117,7 @@ namespace osu.Game.Screens.Play Child = DrawableRuleset } }, - new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) + breakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -405,8 +407,8 @@ namespace osu.Game.Screens.Play IsResuming = true; PauseOverlay.Hide(); - // time-based conditions may allow instant resume. - if (GameplayClockContainer.GameplayClock.CurrentTime < Beatmap.Value.Beatmap.HitObjects.First().StartTime) + // breaks and time-based conditions may allow instant resume. + if (breakOverlay.IsActive || GameplayClockContainer.GameplayClock.CurrentTime < Beatmap.Value.Beatmap.HitObjects.First().StartTime) completeResume(); else DrawableRuleset.RequestResume(completeResume); From d7655bc579df0a5193caa5cad65b02fd7084c3d8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com> Date: Mon, 8 Apr 2019 11:22:01 +0900 Subject: [PATCH 402/623] Use .Value instead of cast Co-Authored-By: nyquillerium --- osu.Game/Graphics/Containers/LogoTrackingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs index 746710bcda..de70231989 100644 --- a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -115,7 +115,7 @@ namespace osu.Game.Graphics.Containers var amount = (float)Interpolation.ApplyEasing(easing, Math.Min(elapsedDuration / duration, 1)); // Interpolate the position of the logo, where amount 0 is where the logo was when it first began interpolating, and amount 1 is the target location. - Logo.Position = Vector2.Lerp((Vector2)startPosition, LogoTrackingPosition, amount); + Logo.Position = Vector2.Lerp(startPosition.Value, LogoTrackingPosition, amount); } else { From a690302d00cefdae17fa150e1d567cb9669fbc9d Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 8 Apr 2019 15:24:09 +0900 Subject: [PATCH 403/623] Apply reviews --- .../UserInterface/TestCaseButtonSystem.cs | 2 +- .../TestCaseLogoTrackingContainer.cs | 27 +++---- .../Containers/LogoTrackingContainer.cs | 78 +++++++++---------- osu.Game/Screens/Menu/ButtonSystem.cs | 13 ++-- .../Screens/Menu/FlowContainerWithOrigin.cs | 2 +- osu.Game/Screens/Play/PlayerLoader.cs | 8 +- 6 files changed, 59 insertions(+), 71 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseButtonSystem.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseButtonSystem.cs index 261e87ff07..04aa8bce7e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseButtonSystem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseButtonSystem.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.UserInterface RelativeSizeAxes = Axes.Both, }, buttons = new ButtonSystem(), - logo = new OsuLogo() + logo = new OsuLogo { RelativePositionAxes = Axes.Both } }; buttons.SetOsuLogo(logo); diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs index 01a3113fe1..8b70a34c85 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddToggleStep("Toggle move continuously", b => randomPositions = b); AddStep("Add tracking containers", addFacadeContainers); - AddStep("Move facade to random position", startTrackingRandom); + AddStep("Move facade to random position", moveLogoFacade); waitForMove(); AddAssert("Logo is tracking", () => trackingContainer.IsLogoTracking); } @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual.UserInterface public void RemoveFacadeTest() { AddStep("Add tracking containers", addFacadeContainers); - AddStep("Move facade to random position", startTrackingRandom); + AddStep("Move facade to random position", moveLogoFacade); AddStep("Remove facade from FacadeContainer", removeFacade); waitForMove(); AddAssert("Logo is not tracking", () => !trackingContainer.IsLogoTracking); @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.UserInterface public void TransferFacadeTest() { AddStep("Add tracking containers", addFacadeContainers); - AddStep("Move facade to random position", startTrackingRandom); + AddStep("Move facade to random position", moveLogoFacade); AddStep("Remove facade from FacadeContainer", removeFacade); AddStep("Transfer facade to a new container", () => { @@ -171,14 +171,14 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Perform logo movements", () => { - trackingContainer.Tracking = false; + trackingContainer.StopTracking(); logo.MoveTo(new Vector2(0.5f), 500, Easing.InOutExpo); - trackingContainer.SetLogo(logo, 1.0f, 1000, Easing.InOutExpo); + visualBox.Colour = Color4.White; Scheduler.AddDelayed(() => { - trackingContainer.Tracking = true; + trackingContainer.StartTracking(logo, 1000, Easing.InOutExpo); visualBox.Colour = Color4.Tomato; }, 700); }); @@ -221,14 +221,13 @@ namespace osu.Game.Tests.Visual.UserInterface failed = false; newContainer = new LogoTrackingContainer(); addFacadeContainers(); - startTrackingRandom(); - newContainer.SetLogo(logo); + moveLogoFacade(); }); AddStep("Try tracking new container", () => { try { - newContainer.Tracking = true; + newContainer.StartTracking(logo); } catch (Exception e) { @@ -268,7 +267,7 @@ namespace osu.Game.Tests.Visual.UserInterface }); trackingContainer.Add(logoFacade = trackingContainer.LogoFacade); - trackingContainer.SetLogo(logo, 1.0f, 1000); + trackingContainer.StartTracking(logo, 1000); } private void waitForMove(int count = 5) => AddWaitStep("Wait for transforms to finish", count); @@ -280,12 +279,6 @@ namespace osu.Game.Tests.Visual.UserInterface moveLogoFacade(); } - private void startTrackingRandom() - { - trackingContainer.Tracking = true; - moveLogoFacade(); - } - private void moveLogoFacade() { if (logoFacade?.Transforms.Count == 0 && transferContainer?.Transforms.Count == 0) @@ -304,7 +297,7 @@ namespace osu.Game.Tests.Visual.UserInterface /// /// Check that the logo is tracking the position of the facade, with an acceptable precision lenience. /// - public bool IsLogoTracking => Precision.AlmostEquals(Logo.Position, LogoTrackingPosition); + public bool IsLogoTracking => Precision.AlmostEquals(Logo.Position, ComputeLogoTrackingPosition()); } } } diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs index 746710bcda..ae585a2ba7 100644 --- a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -17,29 +17,9 @@ namespace osu.Game.Graphics.Containers { public Facade LogoFacade { get; } - /// - /// Whether or not the logo assigned to this FacadeContainer should be tracking the position of its facade. - /// - public bool Tracking - { - get => tracking; - set - { - if (Logo != null) - { - if (value && !tracking && Logo.IsTracking) - throw new InvalidOperationException($"Cannot track an instance of {typeof(OsuLogo)} to multiple {typeof(LogoTrackingContainer)}s"); + protected OsuLogo Logo => logo; - Logo.IsTracking = value; - } - - tracking = value; - } - } - - protected OsuLogo Logo; - - private float facadeScale; + private OsuLogo logo; private Easing easing; private Vector2? startPosition; private double? startTime; @@ -58,27 +38,38 @@ namespace osu.Game.Graphics.Containers /// The scale of the facade. Does not actually affect the logo itself. /// The duration of the initial transform. Default is instant. /// The easing type of the initial transform. - public void SetLogo(OsuLogo logo, float facadeScale = 1.0f, double duration = 0, Easing easing = Easing.None) + public void StartTracking(OsuLogo logo, double duration = 0, Easing easing = Easing.None) { - if (Logo != logo && Logo != null) + if (logo == null) + throw new ArgumentNullException(nameof(logo)); + + if (logo.IsTracking && tracking == false) + throw new InvalidOperationException($"Cannot track an instance of {typeof(OsuLogo)} to multiple {typeof(LogoTrackingContainer)}s"); + + if (this.logo != logo && this.logo != null) { // If we're replacing the logo to be tracked, the old one no longer has a tracking container - Logo.IsTracking = false; + this.logo.IsTracking = false; } - Logo = logo ?? throw new ArgumentNullException(nameof(logo)); + this.logo = logo; + this.logo.IsTracking = true; - if (Tracking) - { - Logo.IsTracking = true; - } - - this.facadeScale = facadeScale; this.duration = duration; this.easing = easing; startTime = null; startPosition = null; + + tracking = true; + } + + public void StopTracking() + { + if (logo != null) + logo.IsTracking = false; + + tracking = false; } /// @@ -86,20 +77,27 @@ namespace osu.Game.Graphics.Containers /// Manually performs a conversion of the Facade's position to the Logo's parent's relative space. /// /// Will only be correct if the logo's are set to Axes.Both - protected Vector2 LogoTrackingPosition => new Vector2(Logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre).X / Logo.Parent.RelativeToAbsoluteFactor.X, - Logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre).Y / Logo.Parent.RelativeToAbsoluteFactor.Y); + protected Vector2 ComputeLogoTrackingPosition() + { + var absolutePos = Logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre); + + return new Vector2(absolutePos.X / Logo.Parent.RelativeToAbsoluteFactor.X, + absolutePos.Y / Logo.Parent.RelativeToAbsoluteFactor.Y); + } protected override void Update() { base.Update(); - if (Logo == null || !Tracking) + if (Logo == null || !tracking) return; // Account for the scale of the actual OsuLogo, as SizeForFlow only accounts for the sprite scale. - ((ExposedFacade)LogoFacade).SetSize(new Vector2(Logo.SizeForFlow * Logo.Scale.X * facadeScale)); + ((ExposedFacade)LogoFacade).SetSize(new Vector2(Logo.SizeForFlow * Logo.Scale.X)); - if (LogoFacade.Parent != null && Logo.Position != LogoTrackingPosition && Logo.RelativePositionAxes == Axes.Both) + var localPos = ComputeLogoTrackingPosition(); + + if (LogoFacade.Parent != null && Logo.Position != localPos && Logo.RelativePositionAxes == Axes.Both) { // If this is our first update since tracking has started, initialize our starting values for interpolation if (startTime == null || startPosition == null) @@ -115,11 +113,11 @@ namespace osu.Game.Graphics.Containers var amount = (float)Interpolation.ApplyEasing(easing, Math.Min(elapsedDuration / duration, 1)); // Interpolate the position of the logo, where amount 0 is where the logo was when it first began interpolating, and amount 1 is the target location. - Logo.Position = Vector2.Lerp((Vector2)startPosition, LogoTrackingPosition, amount); + Logo.Position = Vector2.Lerp((Vector2)startPosition, localPos, amount); } else { - Logo.Position = LogoTrackingPosition; + Logo.Position = localPos; } } } @@ -127,7 +125,7 @@ namespace osu.Game.Graphics.Containers protected override void Dispose(bool isDisposing) { if (Logo != null) - Tracking = false; + Logo.IsTracking = false; base.Dispose(isDisposing); } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index d042e110e9..1d2ee3c284 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -60,17 +60,17 @@ namespace osu.Game.Screens.Menu if (this.logo != null) { this.logo.Action = onOsuLogo; - logoTrackingContainer.SetLogo(logo, 0.74f); // osuLogo.SizeForFlow relies on loading to be complete. buttonArea.Flow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + this.logo.SizeForFlow / 4), 0); + logoTrackingContainer.LogoFacade.Scale = new Vector2(0.74f); updateLogoState(); } else { // We should stop tracking as the facade is now out of scope. - logoTrackingContainer.Tracking = false; + logoTrackingContainer.StopTracking(); } } @@ -271,7 +271,7 @@ namespace osu.Game.Screens.Menu logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => { - logoTrackingContainer.Tracking = false; + logoTrackingContainer.StopTracking(); game?.Toolbar.Hide(); @@ -294,8 +294,7 @@ namespace osu.Game.Screens.Menu if (lastState == ButtonSystemState.Initial) logo.ScaleTo(0.5f, 200, Easing.In); - logoTrackingContainer.SetLogo(logo, 0.74f, lastState == ButtonSystemState.EnteringMode ? 0 : 200, Easing.In); - logoTrackingContainer.Tracking = true; + logoTrackingContainer.StartTracking(logo, lastState == ButtonSystemState.EnteringMode ? 0 : 200, Easing.In); logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => @@ -308,14 +307,14 @@ namespace osu.Game.Screens.Menu break; default: logo.ClearTransforms(targetMember: nameof(Position)); - logoTrackingContainer.Tracking = true; + logoTrackingContainer.StartTracking(logo, 0, Easing.In); logo.ScaleTo(0.5f, 200, Easing.OutQuint); break; } break; case ButtonSystemState.EnteringMode: - logoTrackingContainer.Tracking = true; + logoTrackingContainer.StartTracking(logo, 0, Easing.In); break; } } diff --git a/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs b/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs index ec7333ec02..8310ab06eb 100644 --- a/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs +++ b/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Menu if (CentreTarget == null) return base.OriginPosition; - return CentreTarget.DrawPosition + CentreTarget.DrawSize / 2; + return CentreTarget.DrawPosition + CentreTarget.DrawSize / 2 * CentreTarget.Scale; } } } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index f7feb3535d..4c8f9a3539 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Play private void contentOut() { // Ensure the logo is no longer tracking before we scale the content - content.Tracking = false; + content.StopTracking(); content.ScaleTo(0.7f, 300, Easing.InQuint); content.FadeOut(250); @@ -161,15 +161,13 @@ namespace osu.Game.Screens.Play logo.ScaleTo(new Vector2(0.15f), duration, Easing.In); logo.FadeIn(350); - content.SetLogo(logo, 1.0f, resuming ? 0 : 500, Easing.InOutExpo); - - Scheduler.AddDelayed(() => { content.Tracking = true; }, resuming ? 0 : 500); + Scheduler.AddDelayed(() => { content.StartTracking(logo, resuming ? 0 : 500, Easing.InOutExpo); }, resuming ? 0 : 500); } protected override void LogoExiting(OsuLogo logo) { base.LogoExiting(logo); - content.Tracking = false; + content.StopTracking(); } protected override void LoadComplete() From 8a01995668734e46e0630ce8b143fb446d868a1b Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 8 Apr 2019 16:14:41 +0900 Subject: [PATCH 404/623] Remove need for tracking bool and backing logo --- .../Containers/LogoTrackingContainer.cs | 27 +++++++++---------- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs index 0721011345..5ebdb7d45d 100644 --- a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -17,14 +17,12 @@ namespace osu.Game.Graphics.Containers { public Facade LogoFacade { get; } - protected OsuLogo Logo => logo; + protected OsuLogo Logo { get; private set; } - private OsuLogo logo; private Easing easing; private Vector2? startPosition; private double? startTime; private double duration; - private bool tracking; public LogoTrackingContainer() { @@ -43,33 +41,32 @@ namespace osu.Game.Graphics.Containers if (logo == null) throw new ArgumentNullException(nameof(logo)); - if (logo.IsTracking && tracking == false) + if (logo.IsTracking && Logo == null) throw new InvalidOperationException($"Cannot track an instance of {typeof(OsuLogo)} to multiple {typeof(LogoTrackingContainer)}s"); - if (this.logo != logo && this.logo != null) + if (Logo != logo && Logo != null) { // If we're replacing the logo to be tracked, the old one no longer has a tracking container - this.logo.IsTracking = false; + Logo.IsTracking = false; } - this.logo = logo; - this.logo.IsTracking = true; + Logo = logo; + Logo.IsTracking = true; this.duration = duration; this.easing = easing; startTime = null; startPosition = null; - - tracking = true; } public void StopTracking() { - if (logo != null) - logo.IsTracking = false; - - tracking = false; + if (Logo != null) + { + Logo.IsTracking = false; + Logo = null; + } } /// @@ -89,7 +86,7 @@ namespace osu.Game.Graphics.Containers { base.Update(); - if (Logo == null || !tracking) + if (Logo == null) return; // Account for the scale of the actual OsuLogo, as SizeForFlow only accounts for the sprite scale. diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 1d2ee3c284..a6157cdb64 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -63,7 +63,6 @@ namespace osu.Game.Screens.Menu // osuLogo.SizeForFlow relies on loading to be complete. buttonArea.Flow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + this.logo.SizeForFlow / 4), 0); - logoTrackingContainer.LogoFacade.Scale = new Vector2(0.74f); updateLogoState(); } @@ -106,6 +105,7 @@ namespace osu.Game.Screens.Menu }); buttonArea.Flow.CentreTarget = logoTrackingContainer.LogoFacade; + logoTrackingContainer.LogoFacade.Scale = new Vector2(0.74f); } [Resolved(CanBeNull = true)] From 93b6cc00dfe96e2b4e26564bba09a92a0a7d0481 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 9 Apr 2019 12:53:00 +0900 Subject: [PATCH 405/623] Fix OsuScreenDependencies using the incorrect parent --- osu.Game/Screens/OsuScreen.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 5034385969..fdbc1120bc 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -65,12 +65,12 @@ namespace osu.Game.Screens protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - var deps = new OsuScreenDependencies(DisallowExternalBeatmapRulesetChanges, base.CreateChildDependencies(parent)); + OsuScreenDependencies screenDependencies = new OsuScreenDependencies(DisallowExternalBeatmapRulesetChanges, parent); - Beatmap = deps.Beatmap; - Ruleset = deps.Ruleset; + Beatmap = screenDependencies.Beatmap; + Ruleset = screenDependencies.Ruleset; - return deps; + return base.CreateChildDependencies(screenDependencies); } protected BackgroundScreen Background => backgroundStack?.CurrentScreen as BackgroundScreen; From ca9f172a96f15ef9461418f6fc541ab50df3323c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 9 Apr 2019 12:58:10 +0900 Subject: [PATCH 406/623] Use var --- osu.Game/Screens/OsuScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index fdbc1120bc..e0a25deecf 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - OsuScreenDependencies screenDependencies = new OsuScreenDependencies(DisallowExternalBeatmapRulesetChanges, parent); + var screenDependencies = new OsuScreenDependencies(DisallowExternalBeatmapRulesetChanges, parent); Beatmap = screenDependencies.Beatmap; Ruleset = screenDependencies.Ruleset; From c584967eb12adb64c26111e43d4f2fa790e7ec8b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Apr 2019 18:32:05 +0900 Subject: [PATCH 407/623] Remove mods from workingbeatmap --- .../TestCaseAutoJuiceStream.cs | 2 +- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- .../UI/DrawableCatchRuleset.cs | 6 ++-- .../Edit/DrawableManiaEditRuleset.cs | 6 ++-- .../Edit/ManiaHitObjectComposer.cs | 5 +-- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../UI/DrawableManiaRuleset.cs | 5 +-- osu.Game.Rulesets.Osu.Tests/StackingTest.cs | 3 +- .../Edit/DrawableOsuEditRuleset.cs | 6 ++-- .../Edit/OsuHitObjectComposer.cs | 5 +-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../UI/DrawableOsuRuleset.cs | 6 ++-- .../TestCaseTaikoPlayfield.cs | 4 ++- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- .../UI/DrawableTaikoRuleset.cs | 6 ++-- .../Formats/LegacyBeatmapDecoderTest.cs | 3 +- .../TestCaseBackgroundScreenBeatmap.cs | 2 +- .../Visual/Gameplay/TestCaseAutoplay.cs | 2 +- .../Visual/Gameplay/TestCaseReplay.cs | 3 +- .../SongSelect/TestCasePlaySongSelect.cs | 4 +-- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 35 ++++++++----------- osu.Game/OsuGame.cs | 3 +- .../Difficulty/DifficultyCalculator.cs | 3 +- .../Difficulty/PerformanceCalculator.cs | 3 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 5 +-- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 19 +++++----- osu.Game/Rulesets/UI/Playfield.cs | 12 ++++--- .../UI/Scrolling/DrawableScrollingRuleset.cs | 5 +-- .../Screens/Multi/Match/MatchSubScreen.cs | 2 -- osu.Game/Screens/OsuScreen.cs | 5 +++ osu.Game/Screens/OsuScreenDependencies.cs | 13 ++++--- .../Screens/Play/GameplayClockContainer.cs | 9 ++--- osu.Game/Screens/Play/HUDOverlay.cs | 7 ++-- osu.Game/Screens/Play/Player.cs | 14 ++++---- osu.Game/Screens/Play/PlayerLoader.cs | 10 ++++-- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 5 +-- osu.Game/Screens/Select/MatchSongSelect.cs | 6 +--- osu.Game/Screens/Select/SongSelect.cs | 6 ++-- osu.Game/Tests/Visual/AllPlayersTestCase.cs | 2 +- osu.Game/Tests/Visual/OsuTestCase.cs | 17 +++++---- osu.Game/Tests/Visual/PlayerTestCase.cs | 2 +- 43 files changed, 147 insertions(+), 116 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs index fbb2db33b0..a696ba4e7e 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Tests protected override Player CreatePlayer(Ruleset ruleset) { - Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); return base.CreatePlayer(ruleset); } } diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index aa00e182a9..544694fc8f 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch { public class CatchRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableCatchRuleset(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IEnumerable mods) => new DrawableCatchRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index ba0f5b90ba..4b75b54d41 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -10,6 +11,7 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Scoring; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -23,8 +25,8 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool UserScrollSpeedAdjustment => false; - public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) + : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Down; TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs index acafaffee6..0bfbf38832 100644 --- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs +++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Graphics; using osuTK; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -14,8 +16,8 @@ namespace osu.Game.Rulesets.Mania.Edit { public new IScrollingInfo ScrollingInfo => base.ScrollingInfo; - public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) + : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 56c9471462..ebc94c86e2 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -41,9 +42,9 @@ namespace osu.Game.Rulesets.Mania.Edit public int TotalColumns => ((ManiaPlayfield)DrawableRuleset.Playfield).TotalColumns; - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap) + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) { - DrawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap); + DrawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods); // This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it dependencies.CacheAs(DrawableRuleset.ScrollingInfo); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 0ff79d2836..02a9b5ed30 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mania { public class ManiaRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableManiaRuleset(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IEnumerable mods) => new DrawableManiaRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 1c1ec604f6..9d10657680 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; @@ -39,8 +40,8 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Bindable configDirection = new Bindable(); - public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) + : base(ruleset, beatmap, mods) { // Generate the bar lines double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; diff --git a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs index e2f6b2164c..13c9985f47 100644 --- a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using NUnit.Framework; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; using Decoder = osu.Game.Beatmaps.Formats.Decoder; @@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Tests using (var reader = new StreamReader(stream)) { var beatmap = Decoder.GetDecoder(reader).Decode(reader); - var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo); + var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Enumerable.Empty()); var objects = converted.HitObjects.ToList(); diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index d9cb203bdf..0c3050bfb4 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osuTK; @@ -10,8 +12,8 @@ namespace osu.Game.Rulesets.Osu.Edit { public class DrawableOsuEditRuleset : DrawableOsuRuleset { - public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) + : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 039ec5585e..9c4b6ee7aa 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; @@ -23,8 +24,8 @@ namespace osu.Game.Rulesets.Osu.Edit { } - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - => new DrawableOsuEditRuleset(ruleset, beatmap); + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) + => new DrawableOsuEditRuleset(ruleset, beatmap, mods); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 44bce5bed8..115271da85 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu { public class OsuRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableOsuRuleset(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IEnumerable mods) => new DrawableOsuRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index f6a3be40b0..c2954e1b3b 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . 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.Input; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; using osu.Game.Replays; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Objects; @@ -22,8 +24,8 @@ namespace osu.Game.Rulesets.Osu.UI { protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config; - public DrawableOsuRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableOsuRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) + : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs index 369cdd49d2..c42faea9f9 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -11,6 +12,7 @@ using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Judgements; @@ -86,7 +88,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Height = 768, - Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap) } + Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap, Enumerable.Empty()) } }); } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 448b1b42bb..cb53ec890b 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko { public class TaikoRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableTaikoRuleset(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IEnumerable mods) => new DrawableTaikoRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap); public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index f4b9c46dfc..76bdd37ed3 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; @@ -16,6 +17,7 @@ using osu.Framework.Input; using osu.Game.Configuration; using osu.Game.Input.Handlers; using osu.Game.Replays; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Taiko.UI @@ -26,8 +28,8 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool UserScrollSpeedAdjustment => false; - public DrawableTaikoRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableTaikoRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) + : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Left; TimeRange.Value = 7000; diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 02dff6993d..e181130774 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Osu; @@ -39,7 +40,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(6, working.BeatmapInfo.BeatmapVersion); Assert.AreEqual(6, working.Beatmap.BeatmapInfo.BeatmapVersion); - Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapVersion); + Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Enumerable.Empty()).BeatmapInfo.BeatmapVersion); } } diff --git a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs index 891b89e72d..e3875421b1 100644 --- a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs @@ -267,7 +267,7 @@ namespace osu.Game.Tests.Visual.Background AddUntilStep("Song select has selection", () => songSelect.Carousel.SelectedBeatmap != null); AddStep("Set default user settings", () => { - Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { new OsuModNoFail() }); + SelectedMods.Value = SelectedMods.Value.Concat(new[] { new OsuModNoFail() }); songSelect.DimLevel.Value = 0.7f; songSelect.BlurLevel.Value = 0.4f; }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs index a2d92b7861..c930e77e9e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay { protected override Player CreatePlayer(Ruleset ruleset) { - Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); return new ScoreAccessiblePlayer(); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs index b98ce96fbb..2a72dc8242 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.Linq; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -15,7 +16,7 @@ namespace osu.Game.Tests.Visual.Gameplay { protected override Player CreatePlayer(Ruleset ruleset) { - var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo); + var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Enumerable.Empty()); return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap)); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs index d5bc452d75..bc644da9d5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs @@ -175,12 +175,12 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("change ruleset", () => { - songSelect.CurrentBeatmap.Mods.ValueChanged += onModChange; + SelectedMods.ValueChanged += onModChange; songSelect.Ruleset.ValueChanged += onRulesetChange; Ruleset.Value = new TaikoRuleset().RulesetInfo; - songSelect.CurrentBeatmap.Mods.ValueChanged -= onModChange; + SelectedMods.ValueChanged -= onModChange; songSelect.Ruleset.ValueChanged -= onRulesetChange; }); diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 73aa12a3db..3e6033da9c 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -52,7 +52,7 @@ namespace osu.Game.Beatmaps { public override IEnumerable GetModsFor(ModType type) => new Mod[] { }; - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IEnumerable mods) { throw new NotImplementedException(); } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 2e36d87024..bb13a8c0e7 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -11,7 +11,6 @@ using osu.Framework.IO.File; using System.IO; using System.Linq; using System.Threading; -using osu.Framework.Bindables; using osu.Game.IO.Serialization; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; @@ -28,16 +27,12 @@ namespace osu.Game.Beatmaps public readonly BeatmapMetadata Metadata; - public readonly Bindable> Mods = new Bindable>(new Mod[] { }); - protected WorkingBeatmap(BeatmapInfo beatmapInfo) { BeatmapInfo = beatmapInfo; BeatmapSetInfo = beatmapInfo.BeatmapSet; Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); - Mods.ValueChanged += _ => applyRateAdjustments(); - beatmap = new RecyclableLazy(() => { var b = GetBeatmap() ?? new Beatmap(); @@ -55,7 +50,7 @@ namespace osu.Game.Beatmaps { // we want to ensure that we always have a track, even if it's a fake one. var t = GetTrack() ?? new VirtualBeatmapTrack(Beatmap); - applyRateAdjustments(t); + // applyRateAdjustments(t); return t; }); @@ -87,7 +82,7 @@ namespace osu.Game.Beatmaps /// The to create a playable for. /// The converted . /// If could not be converted to . - public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset) + public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IEnumerable mods) { var rulesetInstance = ruleset.CreateInstance(); @@ -98,19 +93,19 @@ namespace osu.Game.Beatmaps throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset.InstantiationInfo}, converter: {converter})."); // Apply conversion mods - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in mods.OfType()) mod.ApplyToBeatmapConverter(converter); // Convert IBeatmap converted = converter.Convert(); // Apply difficulty mods - if (Mods.Value.Any(m => m is IApplicableToDifficulty)) + if (mods.Any(m => m is IApplicableToDifficulty)) { converted.BeatmapInfo = converted.BeatmapInfo.Clone(); converted.BeatmapInfo.BaseDifficulty = converted.BeatmapInfo.BaseDifficulty.Clone(); - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in mods.OfType()) mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty); } @@ -122,7 +117,7 @@ namespace osu.Game.Beatmaps foreach (var obj in converted.HitObjects) obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty); - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in mods.OfType()) foreach (var obj in converted.HitObjects) mod.ApplyToHitObject(obj); @@ -188,15 +183,15 @@ namespace osu.Game.Beatmaps /// public void RecycleTrack() => track.Recycle(); - private void applyRateAdjustments(Track t = null) - { - if (t == null && track.IsResultAvailable) t = Track; - if (t == null) return; - - t.ResetSpeedAdjustments(); - foreach (var mod in Mods.Value.OfType()) - mod.ApplyToClock(t); - } + // private void applyRateAdjustments(Track t = null) + // { + // if (t == null && track.IsResultAvailable) t = Track; + // if (t == null) return; + // + // t.ResetSpeedAdjustments(); + // foreach (var mod in Mods.Value.OfType()) + // mod.ApplyToClock(t); + // } public class RecyclableLazy { diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4f92ff831c..2cef4f66a9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -293,9 +293,8 @@ namespace osu.Game performFromMainMenu(() => { ruleset.Value = databasedScoreInfo.Ruleset; - Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); - Beatmap.Value.Mods.Value = databasedScoreInfo.Mods; + selectedMods.Value = databasedScoreInfo.Mods; menuScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore))); }, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true); diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index aad55f8a38..e181051737 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -39,8 +39,7 @@ namespace osu.Game.Rulesets.Difficulty { mods = mods.Select(m => m.CreateCopy()).ToArray(); - beatmap.Mods.Value = mods; - IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo); + IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); var clock = new StopwatchClock(); mods.OfType().ForEach(m => m.ApplyToClock(clock)); diff --git a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs index 2b627ee8e6..9ab81b9580 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs @@ -26,8 +26,7 @@ namespace osu.Game.Rulesets.Difficulty Ruleset = ruleset; Score = score; - beatmap.Mods.Value = score.Mods; - Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo); + Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, score.Mods); Attributes = ruleset.CreateDifficultyCalculator(beatmap).Calculate(score.Mods); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 41de0c36fc..5219cb9581 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -14,6 +14,7 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -185,8 +186,8 @@ namespace osu.Game.Rulesets.Edit } internal override DrawableEditRuleset CreateDrawableRuleset() - => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value)); + => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value, Enumerable.Empty())); - protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap); + protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods); } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index cdfe02b60b..90fe25accf 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets /// The beatmap to create the hit renderer for. /// Unable to successfully load the beatmap to be usable with this ruleset. /// - public abstract DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap); + public abstract DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IEnumerable mods); /// /// Creates a to convert a to one that is applicable for this . diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 27137d9f98..0ab07de1ac 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -11,7 +11,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Cursor; @@ -82,7 +81,9 @@ namespace osu.Game.Rulesets.UI /// /// The mods which are to be applied. /// - private readonly IEnumerable mods; + [Cached] + [Cached(typeof(IBindable>))] + private readonly Bindable> mods = new Bindable>(); private FrameStabilityContainer frameStabilityContainer; @@ -93,16 +94,18 @@ namespace osu.Game.Rulesets.UI /// /// The ruleset being represented. /// The beatmap to create the hit renderer for. - protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap workingBeatmap) + protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap workingBeatmap, IEnumerable mods) : base(ruleset) { - Debug.Assert(workingBeatmap != null, "DrawableRuleset initialized with a null beatmap."); + if (workingBeatmap == null) + throw new ArgumentException("Beatmap cannot be null.", nameof(workingBeatmap)); + + this.mods.Value = mods; RelativeSizeAxes = Axes.Both; - Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo); + Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); - mods = workingBeatmap.Mods.Value; applyBeatmapMods(mods); KeyBindingInputManager = CreateInputManager(); @@ -157,7 +160,7 @@ namespace osu.Game.Rulesets.UI .WithChild(ResumeOverlay))); } - applyRulesetMods(mods, config); + applyRulesetMods(mods.Value, config); loadObjects(); } @@ -172,7 +175,7 @@ namespace osu.Game.Rulesets.UI Playfield.PostProcess(); - foreach (var mod in mods.OfType()) + foreach (var mod in mods.Value.OfType()) mod.ApplyToDrawableHitObjects(Playfield.HitObjectContainer.Objects); } diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 48b950c070..6ea565af44 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -57,13 +57,15 @@ namespace osu.Game.Rulesets.UI hitObjectContainerLazy = new Lazy(CreateHitObjectContainer); } - private WorkingBeatmap beatmap; + [Resolved] + private IBindable beatmap { get; set; } + + [Resolved] + private IBindable> selectedMods { get; set; } [BackgroundDependencyLoader] - private void load(IBindable beatmap) + private void load() { - this.beatmap = beatmap.Value; - Cursor = CreateCursor(); if (Cursor != null) AddInternal(Cursor); @@ -123,7 +125,7 @@ namespace osu.Game.Rulesets.UI base.Update(); if (beatmap != null) - foreach (var mod in beatmap.Mods.Value) + foreach (var mod in selectedMods.Value) if (mod is IUpdatableByPlayfield updatable) updatable.Update(this); } diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 3b368652f2..2bb98dd679 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -13,6 +13,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Timing; @@ -80,8 +81,8 @@ namespace osu.Game.Rulesets.UI.Scrolling [Cached(Type = typeof(IScrollingInfo))] private readonly LocalScrollingInfo scrollingInfo; - protected DrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + protected DrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) + : base(ruleset, beatmap, mods) { scrollingInfo = new LocalScrollingInfo(); scrollingInfo.Direction.BindTo(Direction); diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index a71106872e..b3f9a4ae21 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -222,8 +222,6 @@ namespace osu.Game.Screens.Multi.Match private void onStart() { - Beatmap.Value.Mods.Value = SelectedMods.Value.ToArray(); - switch (type.Value) { default: diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index e0a25deecf..33e725eb41 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using Microsoft.EntityFrameworkCore.Internal; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -14,6 +15,7 @@ using osu.Game.Input.Bindings; using osu.Game.Rulesets; using osu.Game.Screens.Menu; using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; namespace osu.Game.Screens { @@ -63,12 +65,15 @@ namespace osu.Game.Screens public Bindable Ruleset { get; set; } + public Bindable> SelectedMods { get; set; } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var screenDependencies = new OsuScreenDependencies(DisallowExternalBeatmapRulesetChanges, parent); Beatmap = screenDependencies.Beatmap; Ruleset = screenDependencies.Ruleset; + SelectedMods = screenDependencies.SelectedMods; return base.CreateChildDependencies(screenDependencies); } diff --git a/osu.Game/Screens/OsuScreenDependencies.cs b/osu.Game/Screens/OsuScreenDependencies.cs index 84e5de76de..9cc415f581 100644 --- a/osu.Game/Screens/OsuScreenDependencies.cs +++ b/osu.Game/Screens/OsuScreenDependencies.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; namespace osu.Game.Screens { @@ -14,6 +16,8 @@ namespace osu.Game.Screens public Bindable Ruleset { get; } + public Bindable> SelectedMods { get; } + public OsuScreenDependencies(bool requireLease, IReadOnlyDependencyContainer parent) : base(parent) { @@ -21,20 +25,21 @@ namespace osu.Game.Screens { Beatmap = parent.Get>()?.GetBoundCopy(); if (Beatmap == null) - { Cache(Beatmap = parent.Get>().BeginLease(false)); - } Ruleset = parent.Get>()?.GetBoundCopy(); if (Ruleset == null) - { Cache(Ruleset = parent.Get>().BeginLease(true)); - } + + SelectedMods = parent.Get>>()?.GetBoundCopy(); + if (SelectedMods == null) + Cache(SelectedMods = parent.Get>>().BeginLease(true)); } else { Beatmap = (parent.Get>() ?? parent.Get>()).GetBoundCopy(); Ruleset = (parent.Get>() ?? parent.Get>()).GetBoundCopy(); + SelectedMods = (parent.Get>>() ?? parent.Get>>()).GetBoundCopy(); } } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index c13222c6de..b17286f69e 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Framework; @@ -22,7 +23,7 @@ namespace osu.Game.Screens.Play /// public class GameplayClockContainer : Container { - private readonly WorkingBeatmap beatmap; + private readonly IEnumerable mods; /// /// The original source (usually a 's track). @@ -54,9 +55,9 @@ namespace osu.Game.Screens.Play private readonly FramedOffsetClock offsetClock; - public GameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime) + public GameplayClockContainer(WorkingBeatmap beatmap, IEnumerable mods, double gameplayStartTime) { - this.beatmap = beatmap; + this.mods = mods; RelativeSizeAxes = Axes.Both; @@ -154,7 +155,7 @@ namespace osu.Game.Screens.Play else sourceClock.Rate = UserPlaybackRate.Value; - foreach (var mod in beatmap.Mods.Value.OfType()) + foreach (var mod in mods.OfType()) mod.ApplyToClock(sourceClock); } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index a7b7f96e7a..aa4375f652 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -2,16 +2,17 @@ // 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.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; @@ -42,7 +43,7 @@ namespace osu.Game.Screens.Play public Action RequestSeek; - public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, WorkingBeatmap working) + public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IEnumerable mods) { RelativeSizeAxes = Axes.Both; @@ -96,7 +97,7 @@ namespace osu.Game.Screens.Play Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value; Progress.RequestSeek = time => RequestSeek(time); - ModDisplay.Current.BindTo(working.Mods); + ModDisplay.Current.Value = mods; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0eebefec86..bf2bbc58b8 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Play if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - InternalChild = GameplayClockContainer = new GameplayClockContainer(working, DrawableRuleset.GameplayStartTime); + InternalChild = GameplayClockContainer = new GameplayClockContainer(working, SelectedMods.Value, DrawableRuleset.GameplayStartTime); GameplayClockContainer.Children = new[] { @@ -123,7 +123,7 @@ namespace osu.Game.Screens.Play }, // display the cursor above some HUD elements. DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), - HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, working) + HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, SelectedMods.Value) { HoldToQuit = { Action = performUserRequestedExit }, PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, @@ -170,7 +170,7 @@ namespace osu.Game.Screens.Play ScoreProcessor.AllJudged += onCompletion; ScoreProcessor.Failed += onFail; - foreach (var mod in Beatmap.Value.Mods.Value.OfType()) + foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToScoreProcessor(ScoreProcessor); } @@ -192,7 +192,7 @@ namespace osu.Game.Screens.Play try { - DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working); + DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working, SelectedMods.Value); } catch (BeatmapInvalidForRulesetException) { @@ -200,7 +200,7 @@ namespace osu.Game.Screens.Play // let's try again forcing the beatmap's ruleset. ruleset = beatmap.BeatmapInfo.Ruleset; rulesetInstance = ruleset.CreateInstance(); - DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value); + DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value, SelectedMods.Value); } if (!DrawableRuleset.Objects.Any()) @@ -271,7 +271,7 @@ namespace osu.Game.Screens.Play { Beatmap = Beatmap.Value.BeatmapInfo, Ruleset = ruleset, - Mods = Beatmap.Value.Mods.Value.ToArray(), + Mods = SelectedMods.Value.ToArray(), User = api.LocalUser.Value, }; @@ -325,7 +325,7 @@ namespace osu.Game.Screens.Play private bool onFail() { - if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) + if (SelectedMods.Value.OfType().Any(m => !m.AllowFail)) return false; GameplayClockContainer.Stop(); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index e9ee5d3fa8..70f5736786 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -16,6 +17,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; @@ -66,7 +68,7 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - info = new BeatmapMetadataDisplay(Beatmap.Value) + info = new BeatmapMetadataDisplay(Beatmap.Value, SelectedMods.Value) { Alpha = 0, Anchor = Anchor.Centre, @@ -299,6 +301,7 @@ namespace osu.Game.Screens.Play } private readonly WorkingBeatmap beatmap; + private readonly IEnumerable mods; private LoadingAnimation loading; private Sprite backgroundSprite; private ModDisplay modDisplay; @@ -320,9 +323,10 @@ namespace osu.Game.Screens.Play } } - public BeatmapMetadataDisplay(WorkingBeatmap beatmap) + public BeatmapMetadataDisplay(WorkingBeatmap beatmap, IEnumerable mods) { this.beatmap = beatmap; + this.mods = mods; } [BackgroundDependencyLoader] @@ -403,7 +407,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Top = 20 }, - Current = beatmap.Mods + Current = { Value = mods } } }, } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index d32387c1d3..03cf767062 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -25,6 +25,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Select @@ -309,12 +310,12 @@ namespace osu.Game.Screens.Select try { // Try to get the beatmap with the user's ruleset - playableBeatmap = beatmap.GetPlayableBeatmap(ruleset); + playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Enumerable.Empty()); } catch (BeatmapInvalidForRulesetException) { // Can't be converted to the user's ruleset, so use the beatmap's own ruleset - playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset); + playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Enumerable.Empty()); } labels.AddRange(playableBeatmap.GetStatistics().Select(s => new InfoLabel(s))); diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index fa5dc4c1d1..bc33750d6f 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using Humanizer; using osu.Framework.Allocation; @@ -26,9 +25,6 @@ namespace osu.Game.Screens.Select [Resolved(typeof(Room))] protected Bindable CurrentItem { get; private set; } - [Resolved] - private Bindable> selectedMods { get; set; } - [Resolved] private BeatmapManager beatmaps { get; set; } @@ -65,7 +61,7 @@ namespace osu.Game.Screens.Select { Ruleset.Value = CurrentItem.Value.Ruleset; Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap); - Beatmap.Value.Mods.Value = selectedMods.Value = CurrentItem.Value.RequiredMods ?? Enumerable.Empty(); + SelectedMods.Value = CurrentItem.Value.RequiredMods ?? Enumerable.Empty(); } Beatmap.Disabled = true; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b60e693cbf..64e64edce2 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -394,7 +394,7 @@ namespace osu.Game.Screens.Select { Logger.Log($"ruleset changed from \"{decoupledRuleset.Value}\" to \"{ruleset}\""); - Beatmap.Value.Mods.Value = Enumerable.Empty(); + SelectedMods.Value = Enumerable.Empty(); decoupledRuleset.Value = ruleset; // force a filter before attempting to change the beatmap. @@ -530,7 +530,7 @@ namespace osu.Game.Screens.Select Beatmap.Value.Track.Looping = false; SelectedMods.UnbindAll(); - Beatmap.Value.Mods.Value = new Mod[] { }; + base.SelectedMods.Value = Enumerable.Empty(); return false; } @@ -557,8 +557,6 @@ namespace osu.Game.Screens.Select /// The working beatmap. protected virtual void UpdateBeatmap(WorkingBeatmap beatmap) { - beatmap.Mods.BindTo(SelectedMods); - Logger.Log($"working beatmap updated to {beatmap}"); if (Background is BackgroundScreenBeatmap backgroundModeBeatmap) diff --git a/osu.Game/Tests/Visual/AllPlayersTestCase.cs b/osu.Game/Tests/Visual/AllPlayersTestCase.cs index 4ef9b346b0..21955da6e9 100644 --- a/osu.Game/Tests/Visual/AllPlayersTestCase.cs +++ b/osu.Game/Tests/Visual/AllPlayersTestCase.cs @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual var working = CreateWorkingBeatmap(beatmap, Clock); Beatmap.Value = working; - Beatmap.Value.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; + SelectedMods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; Player?.Exit(); Player = null; diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs index 495c5dfbad..9dab981ed5 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestCase.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -10,16 +11,26 @@ using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; namespace osu.Game.Tests.Visual { public abstract class OsuTestCase : TestCase { + [Cached(typeof(Bindable))] + [Cached(typeof(IBindable))] private readonly OsuTestBeatmap beatmap = new OsuTestBeatmap(new DummyWorkingBeatmap()); + protected BindableBeatmap Beatmap => beatmap; + [Cached] + [Cached(typeof(IBindable))] protected readonly Bindable Ruleset = new Bindable(); + [Cached] + [Cached(Type = typeof(IBindable>))] + protected readonly Bindable> SelectedMods = new Bindable>(); + protected DependencyContainer Dependencies { get; private set; } private readonly Lazy localStorage; @@ -32,12 +43,6 @@ namespace osu.Game.Tests.Visual // This is the earliest we can get OsuGameBase, which is used by the dummy working beatmap to find textures beatmap.Default = new DummyWorkingBeatmap(Dependencies.Get()); - Dependencies.CacheAs>(beatmap); - Dependencies.CacheAs>(beatmap); - - Dependencies.CacheAs(Ruleset); - Dependencies.CacheAs>(Ruleset); - return Dependencies; } diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index 3bf707fade..409e79b4a5 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual Beatmap.Value = new TestWorkingBeatmap(beatmap, Clock); if (!AllowFail) - Beatmap.Value.Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; + SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; Player = CreatePlayer(ruleset); LoadScreen(Player); From ad124bfeec6745939b172f310618d2b6c5a99656 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Apr 2019 19:16:34 +0900 Subject: [PATCH 408/623] Reimplement select mod track adjustments --- osu.Game/Beatmaps/WorkingBeatmap.cs | 19 +------------------ osu.Game/OsuGame.cs | 2 +- osu.Game/Overlays/MusicController.cs | 22 ++++++++++++++++++++-- osu.Game/Screens/OsuScreen.cs | 6 +++--- osu.Game/Screens/Select/SongSelect.cs | 15 ++++++++------- 5 files changed, 33 insertions(+), 31 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index bb13a8c0e7..ec0a76b52b 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -46,14 +46,7 @@ namespace osu.Game.Beatmaps return b; }); - track = new RecyclableLazy(() => - { - // we want to ensure that we always have a track, even if it's a fake one. - var t = GetTrack() ?? new VirtualBeatmapTrack(Beatmap); - // applyRateAdjustments(t); - return t; - }); - + track = new RecyclableLazy(() => GetTrack() ?? new VirtualBeatmapTrack(Beatmap)); background = new RecyclableLazy(GetBackground, BackgroundStillValid); waveform = new RecyclableLazy(GetWaveform); storyboard = new RecyclableLazy(GetStoryboard); @@ -183,16 +176,6 @@ namespace osu.Game.Beatmaps /// public void RecycleTrack() => track.Recycle(); - // private void applyRateAdjustments(Track t = null) - // { - // if (t == null && track.IsResultAvailable) t = Track; - // if (t == null) return; - // - // t.ResetSpeedAdjustments(); - // foreach (var mod in Mods.Value.OfType()) - // mod.ApplyToClock(t); - // } - public class RecyclableLazy { private Lazy lazy; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2cef4f66a9..4d889677a5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -113,7 +113,7 @@ namespace osu.Game // todo: move this to SongSelect once Screen has the ability to unsuspend. [Cached] [Cached(Type = typeof(IBindable>))] - private readonly Bindable> selectedMods = new Bindable>(new Mod[] { }); + private readonly Bindable> selectedMods = new Bindable>(Enumerable.Empty()); public OsuGame(string[] args = null) { diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index b24c6c3508..0ad4da2ce9 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -22,6 +22,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Music; +using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; @@ -54,7 +55,11 @@ namespace osu.Game.Overlays private Container dragContainer; private Container playerContainer; - private readonly Bindable beatmap = new Bindable(); + [Resolved] + private Bindable beatmap { get; set; } + + [Resolved] + private IBindable> selectedMods { get; set; } /// /// Provide a source for the toolbar height. @@ -73,7 +78,6 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load(Bindable beatmap, BeatmapManager beatmaps, OsuColour colours) { - this.beatmap.BindTo(beatmap); this.beatmaps = beatmaps; Children = new Drawable[] @@ -231,6 +235,7 @@ namespace osu.Game.Overlays { beatmap.BindValueChanged(beatmapChanged, true); beatmap.BindDisabledChanged(beatmapDisabledChanged, true); + selectedMods.BindValueChanged(_ => updateAudioAdjustments(), true); base.LoadComplete(); } @@ -354,10 +359,23 @@ namespace osu.Game.Overlays progressBar.CurrentTime = 0; updateDisplay(current, direction); + updateAudioAdjustments(); queuedDirection = null; } + private void updateAudioAdjustments() + { + var track = current?.Track; + if (track == null) + return; + + track.ResetSpeedAdjustments(); + + foreach (var mod in selectedMods.Value.OfType()) + mod.ApplyToClock(track); + } + private void currentTrackCompleted() => Schedule(() => { if (!current.Track.Looping && !beatmap.Disabled && beatmapSets.Any()) diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 33e725eb41..bd7f326b50 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -61,11 +61,11 @@ namespace osu.Game.Screens public virtual float BackgroundParallaxAmount => 1; - public Bindable Beatmap { get; set; } + public Bindable Beatmap { get; private set; } - public Bindable Ruleset { get; set; } + public Bindable Ruleset { get; private set; } - public Bindable> SelectedMods { get; set; } + public Bindable> SelectedMods { get; private set; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 64e64edce2..0b6bb6f570 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -83,9 +83,7 @@ namespace osu.Game.Screens.Select private readonly Bindable decoupledRuleset = new Bindable(); - [Cached] - [Cached(Type = typeof(IBindable>))] - protected readonly Bindable> SelectedMods = new Bindable>(new Mod[] { }); + protected readonly Bindable> SelectedMods = new Bindable>(Enumerable.Empty()); protected SongSelect() { @@ -217,11 +215,8 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader(true)] - private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, Bindable> selectedMods) + private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins) { - if (selectedMods != null) - SelectedMods.BindTo(selectedMods); - if (Footer != null) { Footer.AddButton(@"mods", colours.Yellow, ModSelect, Key.F1); @@ -269,6 +264,12 @@ namespace osu.Game.Screens.Select protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + SelectedMods.BindTo(base.SelectedMods); + + dependencies.CacheAs(SelectedMods); + dependencies.CacheAs>>(SelectedMods); + dependencies.CacheAs(this); dependencies.CacheAs(decoupledRuleset); dependencies.CacheAs>(decoupledRuleset); From cbb3fdaca8f0fe2bc05f7839c5e9d57c07dac573 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 9 Apr 2019 12:44:03 +0900 Subject: [PATCH 409/623] Fix various crashes due to bindable being disabled --- .../Visual/SongSelect/TestCasePlaySongSelect.cs | 8 ++------ osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 4 ---- osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs | 7 +------ osu.Game/Screens/Select/MatchSongSelect.cs | 2 ++ osu.Game/Screens/Select/SongSelect.cs | 17 ++++++++--------- 5 files changed, 13 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs index bc644da9d5..a70529b384 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs @@ -35,10 +35,6 @@ namespace osu.Game.Tests.Visual.SongSelect private WorkingBeatmap defaultBeatmap; private DatabaseContextFactory factory; - [Cached] - [Cached(Type = typeof(IBindable>))] - private readonly Bindable> selectedMods = new Bindable>(new Mod[] { }); - public override IReadOnlyList RequiredTypes => new[] { typeof(Screens.Select.SongSelect), @@ -185,7 +181,7 @@ namespace osu.Game.Tests.Visual.SongSelect }); AddAssert("mods changed before ruleset", () => modChangeIndex < rulesetChangeIndex); - AddAssert("empty mods", () => !selectedMods.Value.Any()); + AddAssert("empty mods", () => !SelectedMods.Value.Any()); void onModChange(ValueChangedEvent> e) => modChangeIndex = actionIndex++; void onRulesetChange(ValueChangedEvent e) => rulesetChangeIndex = actionIndex--; @@ -218,7 +214,7 @@ namespace osu.Game.Tests.Visual.SongSelect private static int importId; private int getImportId() => ++importId; - private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => selectedMods.Value = mods); + private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => SelectedMods.Value = mods); private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id)); diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index b3f9a4ae21..e5ab46ebe2 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . 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.Bindables; @@ -42,9 +41,6 @@ namespace osu.Game.Screens.Multi.Match [Resolved(typeof(Room))] protected Bindable CurrentItem { get; private set; } - [Resolved] - protected Bindable> SelectedMods { get; private set; } - [Resolved] private BeatmapManager beatmapManager { get; set; } diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index d5b8f1f0c8..8ce68ac593 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; @@ -14,7 +13,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Multi.Ranking; using osu.Game.Screens.Play; @@ -37,9 +35,6 @@ namespace osu.Game.Screens.Multi.Play [Resolved] private IBindable ruleset { get; set; } - [Resolved] - private Bindable> selectedMods { get; set; } - public TimeshiftPlayer(PlaylistItem playlistItem) { this.playlistItem = playlistItem; @@ -61,7 +56,7 @@ namespace osu.Game.Screens.Multi.Play if (ruleset.Value.ID != playlistItem.Ruleset.ID) throw new InvalidOperationException("Current Ruleset does not match PlaylistItem's Ruleset"); - if (!playlistItem.RequiredMods.All(m => selectedMods.Value.Contains(m))) + if (!playlistItem.RequiredMods.All(m => SelectedMods.Value.Contains(m))) throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods"); var req = new CreateRoomScoreRequest(roomId.Value ?? 0, playlistItem.ID); diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index bc33750d6f..1011f6f32d 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -66,6 +66,7 @@ namespace osu.Game.Screens.Select Beatmap.Disabled = true; Ruleset.Disabled = true; + SelectedMods.Disabled = true; return false; } @@ -76,6 +77,7 @@ namespace osu.Game.Screens.Select Beatmap.Disabled = false; Ruleset.Disabled = false; + SelectedMods.Disabled = false; } } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 0b6bb6f570..2bb646750c 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -83,7 +83,9 @@ namespace osu.Game.Screens.Select private readonly Bindable decoupledRuleset = new Bindable(); - protected readonly Bindable> SelectedMods = new Bindable>(Enumerable.Empty()); + [Cached] + [Cached(Type = typeof(IBindable>))] + private readonly Bindable> selectedMods = new Bindable>(Enumerable.Empty()); // Bound to the game's mods, but is not reset on exiting protected SongSelect() { @@ -217,6 +219,8 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(true)] private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins) { + selectedMods.BindTo(SelectedMods); + if (Footer != null) { Footer.AddButton(@"mods", colours.Yellow, ModSelect, Key.F1); @@ -265,11 +269,6 @@ namespace osu.Game.Screens.Select { dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - SelectedMods.BindTo(base.SelectedMods); - - dependencies.CacheAs(SelectedMods); - dependencies.CacheAs>>(SelectedMods); - dependencies.CacheAs(this); dependencies.CacheAs(decoupledRuleset); dependencies.CacheAs>(decoupledRuleset); @@ -395,7 +394,7 @@ namespace osu.Game.Screens.Select { Logger.Log($"ruleset changed from \"{decoupledRuleset.Value}\" to \"{ruleset}\""); - SelectedMods.Value = Enumerable.Empty(); + selectedMods.Value = Enumerable.Empty(); decoupledRuleset.Value = ruleset; // force a filter before attempting to change the beatmap. @@ -530,8 +529,8 @@ namespace osu.Game.Screens.Select if (Beatmap.Value.Track != null) Beatmap.Value.Track.Looping = false; - SelectedMods.UnbindAll(); - base.SelectedMods.Value = Enumerable.Empty(); + selectedMods.UnbindAll(); + SelectedMods.Value = Enumerable.Empty(); return false; } From 56496d28ba9575ea75b37dd0e02243433a292bea Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 9 Apr 2019 12:50:54 +0900 Subject: [PATCH 410/623] Reset mods when exiting match --- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index e5ab46ebe2..5f29044c76 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -178,6 +178,9 @@ namespace osu.Game.Screens.Multi.Match public override bool OnExiting(IScreen next) { RoomManager?.PartRoom(); + + SelectedMods.Value = Enumerable.Empty(); + return base.OnExiting(next); } From 1c952e58cc45710bf63d0226972d71818b33e778 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 9 Apr 2019 13:15:48 +0900 Subject: [PATCH 411/623] Fix testcase failures --- osu.Game/Tests/Visual/OsuTestCase.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs index 9dab981ed5..1a8403db40 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestCase.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual [Cached] [Cached(Type = typeof(IBindable>))] - protected readonly Bindable> SelectedMods = new Bindable>(); + protected readonly Bindable> SelectedMods = new Bindable>(Enumerable.Empty()); protected DependencyContainer Dependencies { get; private set; } @@ -38,12 +38,10 @@ namespace osu.Game.Tests.Visual protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - Dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - // This is the earliest we can get OsuGameBase, which is used by the dummy working beatmap to find textures - beatmap.Default = new DummyWorkingBeatmap(Dependencies.Get()); + beatmap.Default = new DummyWorkingBeatmap(parent.Get()); - return Dependencies; + return Dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); } protected OsuTestCase() From 4c571acd671a293dfbe3a6aee6963475bcbb67cc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 9 Apr 2019 13:33:16 +0900 Subject: [PATCH 412/623] Reinstantiate mods for every player --- osu.Game/Screens/Play/Player.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index bf2bbc58b8..d8e31b7ad6 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -69,6 +70,10 @@ namespace osu.Game.Screens.Play protected GameplayClockContainer GameplayClockContainer { get; private set; } + [Cached] + [Cached(Type = typeof(IBindable>))] + protected readonly Bindable> SelectedMods = new Bindable>(Enumerable.Empty()); + private readonly bool allowPause; private readonly bool showResults; @@ -88,6 +93,8 @@ namespace osu.Game.Screens.Play { this.api = api; + SelectedMods.Value = base.SelectedMods.Value.Select(m => m.CreateCopy()).ToArray(); + WorkingBeatmap working = loadBeatmap(); if (working == null) From d8ec1e73a3025c162e10cebbf8606af91c0ac88c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 9 Apr 2019 13:50:54 +0900 Subject: [PATCH 413/623] Cleanup TestCasePlayerLoader --- .../Visual/Gameplay/TestCasePlayerLoader.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs index 41d484e21f..aba689b241 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs @@ -2,10 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System.Threading; +using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Screens; using osu.Game.Screens.Play; @@ -21,23 +21,15 @@ namespace osu.Game.Tests.Visual.Gameplay InputManager.Add(stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }); } - [BackgroundDependencyLoader] - private void load(OsuGameBase game) + [Test] + public void TestLoadContinuation() { - Beatmap.Value = new DummyWorkingBeatmap(game); - AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player(false, false)))); - AddUntilStep("wait for current", () => loader.IsCurrentScreen()); - AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); - AddStep("exit loader", () => loader.Exit()); - AddUntilStep("wait for no longer alive", () => !loader.IsAlive); - AddStep("load slow dummy beatmap", () => { SlowLoadPlayer slow = null; From aa2c97b859eec9cc6778520a45b13bb5d1c21341 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 9 Apr 2019 15:05:03 +0900 Subject: [PATCH 414/623] Add mod reinstantiation testcase --- .../Visual/Gameplay/TestCasePlayerLoader.cs | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs index aba689b241..fba4aca343 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs @@ -1,11 +1,16 @@ // Copyright (c) ppy Pty Ltd . 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 System.Threading; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Screens; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; @@ -42,6 +47,81 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); } + [Test] + public void TestModReinstantiation() + { + TestPlayer player = null; + TestMod gameMod = null; + TestMod playerMod1 = null; + TestMod playerMod2 = null; + + AddStep("load player", () => + { + SelectedMods.Value = new[] { gameMod = new TestMod() }; + InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre); + stack.Push(new PlayerLoader(() => player = new TestPlayer())); + }); + + AddUntilStep("wait for player to become current", () => + { + if (player.IsCurrentScreen()) + { + playerMod1 = (TestMod)player.SelectedMods.Value.Single(); + return true; + } + + return false; + }); + + AddAssert("game mods not applied", () => gameMod.Applied == false); + AddAssert("player mods applied", () => playerMod1.Applied); + + AddStep("restart player", () => + { + player = null; + player.Restart(); + }); + + AddUntilStep("wait for player to become current", () => + { + if (player.IsCurrentScreen()) + { + playerMod2 = (TestMod)player.SelectedMods.Value.Single(); + return true; + } + + return false; + }); + + AddAssert("game mods not applied", () => gameMod.Applied == false); + AddAssert("player has different mods", () => playerMod1 != playerMod2); + AddAssert("player mods applied", () => playerMod2.Applied); + } + + private class TestMod : Mod, IApplicableToScoreProcessor + { + public override string Name => string.Empty; + public override string Acronym => string.Empty; + public override double ScoreMultiplier => 1; + + public bool Applied { get; private set; } + + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + Applied = true; + } + } + + private class TestPlayer : Player + { + public new Bindable> SelectedMods => base.SelectedMods; + + public TestPlayer() + : base(false, false) + { + } + } + protected class SlowLoadPlayer : Player { public bool Ready; From 0603ed9ab755dc45c0d25822e4de2c5ec84c9721 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 9 Apr 2019 16:27:10 +0900 Subject: [PATCH 415/623] Fix post-merge errors --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 1 + osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index ecdafb0fa2..27546fa424 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Configuration; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index c0d9ecad3a..e70bf4c572 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; From 4310f07a5cbba63eba921db02ed3e4ff9ce9498d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Apr 2019 12:03:57 +0900 Subject: [PATCH 416/623] Rename SelectedMods -> Mods --- .../TestCaseAutoJuiceStream.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs | 3 +-- .../TestCaseHitCircleHidden.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs | 3 +-- .../TestCaseSliderHidden.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs | 3 +-- .../TestCaseSpinnerHidden.cs | 2 +- .../Background/TestCaseBackgroundScreenBeatmap.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs | 2 +- .../Visual/SongSelect/TestCasePlaySongSelect.cs | 8 ++++---- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 +++--- osu.Game/Overlays/MusicController.cs | 6 +++--- osu.Game/Rulesets/UI/Playfield.cs | 4 ++-- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 4 ++-- osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs | 2 +- osu.Game/Screens/OsuScreen.cs | 4 ++-- osu.Game/Screens/OsuScreenDependencies.cs | 10 +++++----- osu.Game/Screens/Play/Player.cs | 14 +++++++------- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- osu.Game/Screens/Select/MatchSongSelect.cs | 8 ++++---- osu.Game/Screens/Select/PlaySongSelect.cs | 4 ++-- osu.Game/Screens/Select/SongSelect.cs | 10 +++++----- osu.Game/Tests/Visual/AllPlayersTestCase.cs | 2 +- osu.Game/Tests/Visual/OsuTestCase.cs | 2 +- osu.Game/Tests/Visual/PlayerTestCase.cs | 2 +- 26 files changed, 55 insertions(+), 58 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs index a696ba4e7e..0b20f34eb1 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Tests protected override Player CreatePlayer(Ruleset ruleset) { - SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); return base.CreatePlayer(ruleset); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs index e1e854e8dc..31f3146046 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs @@ -30,7 +30,6 @@ namespace osu.Game.Rulesets.Osu.Tests protected override Container Content => content; private int depthIndex; - protected readonly List Mods = new List(); public TestCaseHitCircle() { @@ -68,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Tests Depth = depthIndex++ }; - foreach (var mod in Mods.OfType()) + foreach (var mod in Mods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); Add(drawable); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs index 26d9b5ae91..7391c0f11a 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Tests public TestCaseHitCircleHidden() { - Mods.Add(new OsuModHidden()); + Mods.Value = new[] { new OsuModHidden() }; } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs index 35e8f3e17e..0f02050605 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs @@ -44,7 +44,6 @@ namespace osu.Game.Rulesets.Osu.Tests protected override Container Content => content; private int depthIndex; - protected readonly List Mods = new List(); public TestCaseSlider() { @@ -292,7 +291,7 @@ namespace osu.Game.Rulesets.Osu.Tests Depth = depthIndex++ }; - foreach (var mod in Mods.OfType()) + foreach (var mod in Mods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); drawable.OnNewResult += onNewResult; diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs index ba5bd48c51..65a8005407 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Tests public TestCaseSliderHidden() { - Mods.Add(new OsuModHidden()); + Mods.Value = new[] { new OsuModHidden() }; } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs index e8b534bba9..ab33d1e518 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs @@ -31,7 +31,6 @@ namespace osu.Game.Rulesets.Osu.Tests protected override Container Content => content; private int depthIndex; - protected readonly List Mods = new List(); public TestCaseSpinner() { @@ -57,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Tests Depth = depthIndex++ }; - foreach (var mod in Mods.OfType()) + foreach (var mod in Mods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); Add(drawable); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs index 6136ce1639..24e3bcb47b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Tests public TestCaseSpinnerHidden() { - Mods.Add(new OsuModHidden()); + Mods.Value = new[] { new OsuModHidden() }; } } } diff --git a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs index e3875421b1..283fd4c8b9 100644 --- a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs @@ -267,7 +267,7 @@ namespace osu.Game.Tests.Visual.Background AddUntilStep("Song select has selection", () => songSelect.Carousel.SelectedBeatmap != null); AddStep("Set default user settings", () => { - SelectedMods.Value = SelectedMods.Value.Concat(new[] { new OsuModNoFail() }); + Mods.Value = Mods.Value.Concat(new[] { new OsuModNoFail() }); songSelect.DimLevel.Value = 0.7f; songSelect.BlurLevel.Value = 0.4f; }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs index c930e77e9e..df48834e1f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay { protected override Player CreatePlayer(Ruleset ruleset) { - SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); return new ScoreAccessiblePlayer(); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs index a70529b384..e89b361104 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs @@ -171,17 +171,17 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("change ruleset", () => { - SelectedMods.ValueChanged += onModChange; + Mods.ValueChanged += onModChange; songSelect.Ruleset.ValueChanged += onRulesetChange; Ruleset.Value = new TaikoRuleset().RulesetInfo; - SelectedMods.ValueChanged -= onModChange; + Mods.ValueChanged -= onModChange; songSelect.Ruleset.ValueChanged -= onRulesetChange; }); AddAssert("mods changed before ruleset", () => modChangeIndex < rulesetChangeIndex); - AddAssert("empty mods", () => !SelectedMods.Value.Any()); + AddAssert("empty mods", () => !Mods.Value.Any()); void onModChange(ValueChangedEvent> e) => modChangeIndex = actionIndex++; void onRulesetChange(ValueChangedEvent e) => rulesetChangeIndex = actionIndex--; @@ -214,7 +214,7 @@ namespace osu.Game.Tests.Visual.SongSelect private static int importId; private int getImportId() => ++importId; - private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => SelectedMods.Value = mods); + private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => Mods.Value = mods); private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id)); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4d889677a5..0785c588e5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -113,7 +113,7 @@ namespace osu.Game // todo: move this to SongSelect once Screen has the ability to unsuspend. [Cached] [Cached(Type = typeof(IBindable>))] - private readonly Bindable> selectedMods = new Bindable>(Enumerable.Empty()); + private readonly Bindable> mods = new Bindable>(Enumerable.Empty()); public OsuGame(string[] args = null) { @@ -294,7 +294,7 @@ namespace osu.Game { ruleset.Value = databasedScoreInfo.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); - selectedMods.Value = databasedScoreInfo.Mods; + mods.Value = databasedScoreInfo.Mods; menuScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore))); }, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index aa41723ca6..9e97f4551a 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -42,19 +42,19 @@ namespace osu.Game.Overlays.Mods protected readonly FillFlowContainer ModSectionsContainer; - protected readonly Bindable> SelectedMods = new Bindable>(new Mod[] { }); + protected readonly Bindable> SelectedMods = new Bindable>(Enumerable.Empty()); protected readonly IBindable Ruleset = new Bindable(); [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, IBindable ruleset, AudioManager audio, Bindable> selectedMods) + private void load(OsuColour colours, IBindable ruleset, AudioManager audio, Bindable> mods) { LowMultiplierColour = colours.Red; HighMultiplierColour = colours.Green; UnrankedLabel.Colour = colours.Blue; Ruleset.BindTo(ruleset); - if (selectedMods != null) SelectedMods.BindTo(selectedMods); + if (mods != null) SelectedMods.BindTo(mods); sampleOn = audio.Sample.Get(@"UI/check-on"); sampleOff = audio.Sample.Get(@"UI/check-off"); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 0ad4da2ce9..d9b669f753 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays private Bindable beatmap { get; set; } [Resolved] - private IBindable> selectedMods { get; set; } + private IBindable> mods { get; set; } /// /// Provide a source for the toolbar height. @@ -235,7 +235,7 @@ namespace osu.Game.Overlays { beatmap.BindValueChanged(beatmapChanged, true); beatmap.BindDisabledChanged(beatmapDisabledChanged, true); - selectedMods.BindValueChanged(_ => updateAudioAdjustments(), true); + mods.BindValueChanged(_ => updateAudioAdjustments(), true); base.LoadComplete(); } @@ -372,7 +372,7 @@ namespace osu.Game.Overlays track.ResetSpeedAdjustments(); - foreach (var mod in selectedMods.Value.OfType()) + foreach (var mod in mods.Value.OfType()) mod.ApplyToClock(track); } diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 6ea565af44..c9a05dae2c 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.UI private IBindable beatmap { get; set; } [Resolved] - private IBindable> selectedMods { get; set; } + private IBindable> mods { get; set; } [BackgroundDependencyLoader] private void load() @@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.UI base.Update(); if (beatmap != null) - foreach (var mod in selectedMods.Value) + foreach (var mod in mods.Value) if (mod is IUpdatableByPlayfield updatable) updatable.Update(this); } diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 5f29044c76..aad571cf87 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Multi.Match { RoomManager?.PartRoom(); - SelectedMods.Value = Enumerable.Empty(); + Mods.Value = Enumerable.Empty(); return base.OnExiting(next); } @@ -193,7 +193,7 @@ namespace osu.Game.Screens.Multi.Match var localBeatmap = e.NewValue?.Beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == e.NewValue.Beatmap.OnlineBeatmapID); Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); - SelectedMods.Value = e.NewValue?.RequiredMods ?? Enumerable.Empty(); + Mods.Value = e.NewValue?.RequiredMods ?? Enumerable.Empty(); if (e.NewValue?.Ruleset != null) Ruleset.Value = e.NewValue.Ruleset; } diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index 8ce68ac593..9692dc513d 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Multi.Play if (ruleset.Value.ID != playlistItem.Ruleset.ID) throw new InvalidOperationException("Current Ruleset does not match PlaylistItem's Ruleset"); - if (!playlistItem.RequiredMods.All(m => SelectedMods.Value.Contains(m))) + if (!playlistItem.RequiredMods.All(m => Mods.Value.Contains(m))) throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods"); var req = new CreateRoomScoreRequest(roomId.Value ?? 0, playlistItem.ID); diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index bd7f326b50..d1e700cf92 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens public Bindable Ruleset { get; private set; } - public Bindable> SelectedMods { get; private set; } + public Bindable> Mods { get; private set; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { @@ -73,7 +73,7 @@ namespace osu.Game.Screens Beatmap = screenDependencies.Beatmap; Ruleset = screenDependencies.Ruleset; - SelectedMods = screenDependencies.SelectedMods; + Mods = screenDependencies.Mods; return base.CreateChildDependencies(screenDependencies); } diff --git a/osu.Game/Screens/OsuScreenDependencies.cs b/osu.Game/Screens/OsuScreenDependencies.cs index 9cc415f581..d7e1862c05 100644 --- a/osu.Game/Screens/OsuScreenDependencies.cs +++ b/osu.Game/Screens/OsuScreenDependencies.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens public Bindable Ruleset { get; } - public Bindable> SelectedMods { get; } + public Bindable> Mods { get; } public OsuScreenDependencies(bool requireLease, IReadOnlyDependencyContainer parent) : base(parent) @@ -31,15 +31,15 @@ namespace osu.Game.Screens if (Ruleset == null) Cache(Ruleset = parent.Get>().BeginLease(true)); - SelectedMods = parent.Get>>()?.GetBoundCopy(); - if (SelectedMods == null) - Cache(SelectedMods = parent.Get>>().BeginLease(true)); + Mods = parent.Get>>()?.GetBoundCopy(); + if (Mods == null) + Cache(Mods = parent.Get>>().BeginLease(true)); } else { Beatmap = (parent.Get>() ?? parent.Get>()).GetBoundCopy(); Ruleset = (parent.Get>() ?? parent.Get>()).GetBoundCopy(); - SelectedMods = (parent.Get>>() ?? parent.Get>>()).GetBoundCopy(); + Mods = (parent.Get>>() ?? parent.Get>>()).GetBoundCopy(); } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index bf2bbc58b8..5add641404 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Play if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - InternalChild = GameplayClockContainer = new GameplayClockContainer(working, SelectedMods.Value, DrawableRuleset.GameplayStartTime); + InternalChild = GameplayClockContainer = new GameplayClockContainer(working, Mods.Value, DrawableRuleset.GameplayStartTime); GameplayClockContainer.Children = new[] { @@ -123,7 +123,7 @@ namespace osu.Game.Screens.Play }, // display the cursor above some HUD elements. DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), - HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, SelectedMods.Value) + HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, Mods.Value) { HoldToQuit = { Action = performUserRequestedExit }, PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, @@ -170,7 +170,7 @@ namespace osu.Game.Screens.Play ScoreProcessor.AllJudged += onCompletion; ScoreProcessor.Failed += onFail; - foreach (var mod in SelectedMods.Value.OfType()) + foreach (var mod in Mods.Value.OfType()) mod.ApplyToScoreProcessor(ScoreProcessor); } @@ -192,7 +192,7 @@ namespace osu.Game.Screens.Play try { - DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working, SelectedMods.Value); + DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working, Mods.Value); } catch (BeatmapInvalidForRulesetException) { @@ -200,7 +200,7 @@ namespace osu.Game.Screens.Play // let's try again forcing the beatmap's ruleset. ruleset = beatmap.BeatmapInfo.Ruleset; rulesetInstance = ruleset.CreateInstance(); - DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value, SelectedMods.Value); + DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value, Mods.Value); } if (!DrawableRuleset.Objects.Any()) @@ -271,7 +271,7 @@ namespace osu.Game.Screens.Play { Beatmap = Beatmap.Value.BeatmapInfo, Ruleset = ruleset, - Mods = SelectedMods.Value.ToArray(), + Mods = Mods.Value.ToArray(), User = api.LocalUser.Value, }; @@ -325,7 +325,7 @@ namespace osu.Game.Screens.Play private bool onFail() { - if (SelectedMods.Value.OfType().Any(m => !m.AllowFail)) + if (Mods.Value.OfType().Any(m => !m.AllowFail)) return false; GameplayClockContainer.Stop(); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 70f5736786..2096f3f0f8 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - info = new BeatmapMetadataDisplay(Beatmap.Value, SelectedMods.Value) + info = new BeatmapMetadataDisplay(Beatmap.Value, Mods.Value) { Alpha = 0, Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 1011f6f32d..4468f5704f 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Select RulesetID = Ruleset.Value.ID ?? 0 }; - item.RequiredMods.AddRange(SelectedMods.Value); + item.RequiredMods.AddRange(Mods.Value); Selected?.Invoke(item); @@ -61,12 +61,12 @@ namespace osu.Game.Screens.Select { Ruleset.Value = CurrentItem.Value.Ruleset; Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap); - SelectedMods.Value = CurrentItem.Value.RequiredMods ?? Enumerable.Empty(); + Mods.Value = CurrentItem.Value.RequiredMods ?? Enumerable.Empty(); } Beatmap.Disabled = true; Ruleset.Disabled = true; - SelectedMods.Disabled = true; + Mods.Disabled = true; return false; } @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Select Beatmap.Disabled = false; Ruleset.Disabled = false; - SelectedMods.Disabled = false; + Mods.Disabled = false; } } } diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 340ceb6864..44e38edda8 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -52,10 +52,10 @@ namespace osu.Game.Screens.Select var auto = Ruleset.Value.CreateInstance().GetAutoplayMod(); var autoType = auto.GetType(); - var mods = SelectedMods.Value; + var mods = Mods.Value; if (mods.All(m => m.GetType() != autoType)) { - SelectedMods.Value = mods.Append(auto); + Mods.Value = mods.Append(auto); removeAutoModOnResume = true; } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 2bb646750c..6ad5b54e65 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Select [Cached] [Cached(Type = typeof(IBindable>))] - private readonly Bindable> selectedMods = new Bindable>(Enumerable.Empty()); // Bound to the game's mods, but is not reset on exiting + private readonly Bindable> mods = new Bindable>(Enumerable.Empty()); // Bound to the game's mods, but is not reset on exiting protected SongSelect() { @@ -219,7 +219,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(true)] private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins) { - selectedMods.BindTo(SelectedMods); + mods.BindTo(Mods); if (Footer != null) { @@ -394,7 +394,7 @@ namespace osu.Game.Screens.Select { Logger.Log($"ruleset changed from \"{decoupledRuleset.Value}\" to \"{ruleset}\""); - selectedMods.Value = Enumerable.Empty(); + mods.Value = Enumerable.Empty(); decoupledRuleset.Value = ruleset; // force a filter before attempting to change the beatmap. @@ -529,8 +529,8 @@ namespace osu.Game.Screens.Select if (Beatmap.Value.Track != null) Beatmap.Value.Track.Looping = false; - selectedMods.UnbindAll(); - SelectedMods.Value = Enumerable.Empty(); + mods.UnbindAll(); + Mods.Value = Enumerable.Empty(); return false; } diff --git a/osu.Game/Tests/Visual/AllPlayersTestCase.cs b/osu.Game/Tests/Visual/AllPlayersTestCase.cs index 21955da6e9..af190ec7d5 100644 --- a/osu.Game/Tests/Visual/AllPlayersTestCase.cs +++ b/osu.Game/Tests/Visual/AllPlayersTestCase.cs @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual var working = CreateWorkingBeatmap(beatmap, Clock); Beatmap.Value = working; - SelectedMods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; + Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; Player?.Exit(); Player = null; diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs index 1a8403db40..8ec440db8b 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestCase.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual [Cached] [Cached(Type = typeof(IBindable>))] - protected readonly Bindable> SelectedMods = new Bindable>(Enumerable.Empty()); + protected readonly Bindable> Mods = new Bindable>(Enumerable.Empty()); protected DependencyContainer Dependencies { get; private set; } diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index 409e79b4a5..719b1d6892 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual Beatmap.Value = new TestWorkingBeatmap(beatmap, Clock); if (!AllowFail) - SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; + Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; Player = CreatePlayer(ruleset); LoadScreen(Player); From 7845d542e3b63d86c2c9364c14021bbd9c96a239 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Apr 2019 17:11:17 +0900 Subject: [PATCH 417/623] Cache mods as array in DrawableRuleset --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 11 +++++------ osu.Game/Rulesets/UI/Playfield.cs | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 0ab07de1ac..654f330a08 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -81,9 +81,8 @@ namespace osu.Game.Rulesets.UI /// /// The mods which are to be applied. /// - [Cached] - [Cached(typeof(IBindable>))] - private readonly Bindable> mods = new Bindable>(); + [Cached(typeof(IReadOnlyList))] + private readonly IReadOnlyList mods; private FrameStabilityContainer frameStabilityContainer; @@ -100,7 +99,7 @@ namespace osu.Game.Rulesets.UI if (workingBeatmap == null) throw new ArgumentException("Beatmap cannot be null.", nameof(workingBeatmap)); - this.mods.Value = mods; + this.mods = mods.ToArray(); RelativeSizeAxes = Axes.Both; @@ -160,7 +159,7 @@ namespace osu.Game.Rulesets.UI .WithChild(ResumeOverlay))); } - applyRulesetMods(mods.Value, config); + applyRulesetMods(mods, config); loadObjects(); } @@ -175,7 +174,7 @@ namespace osu.Game.Rulesets.UI Playfield.PostProcess(); - foreach (var mod in mods.Value.OfType()) + foreach (var mod in mods.OfType()) mod.ApplyToDrawableHitObjects(Playfield.HitObjectContainer.Objects); } diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index c9a05dae2c..13689153f0 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.UI private IBindable beatmap { get; set; } [Resolved] - private IBindable> mods { get; set; } + private IReadOnlyList mods { get; set; } [BackgroundDependencyLoader] private void load() @@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.UI base.Update(); if (beatmap != null) - foreach (var mod in mods.Value) + foreach (var mod in mods) if (mod is IUpdatableByPlayfield updatable) updatable.Update(this); } From 0222424aef8cf51e5f201633938880d8d756f25f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Apr 2019 17:13:12 +0900 Subject: [PATCH 418/623] Make mods IReadOnlyList gamewide Prevents potential multiple evaluations of enumerable. --- osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs | 2 +- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 2 +- osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs | 2 +- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 2 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu.Tests/StackingTest.cs | 3 ++- osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs | 2 +- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 2 +- osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs | 3 +-- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 3 ++- .../Visual/Background/TestCaseBackgroundScreenBeatmap.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs | 3 ++- .../Visual/SongSelect/TestCasePlaySongSelect.cs | 2 +- osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs | 2 +- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 8 ++++---- osu.Game/Overlays/MusicController.cs | 2 +- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 ++-- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 6 +++--- .../Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs | 2 +- osu.Game/Screens/Multi/Match/Components/Header.cs | 3 +-- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 6 +++--- osu.Game/Screens/OsuScreen.cs | 2 +- osu.Game/Screens/OsuScreenDependencies.cs | 8 ++++---- osu.Game/Screens/Play/GameplayClockContainer.cs | 4 ++-- osu.Game/Screens/Play/HUD/ModDisplay.cs | 6 +++--- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++-- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 4 ++-- osu.Game/Screens/Select/MatchSongSelect.cs | 3 +-- osu.Game/Screens/Select/PlaySongSelect.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 8 ++++---- osu.Game/Tests/Visual/OsuTestCase.cs | 4 ++-- 44 files changed, 68 insertions(+), 68 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs index 0b20f34eb1..102afa9ca6 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Tests protected override Player CreatePlayer(Ruleset ruleset) { - Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); return base.CreatePlayer(ruleset); } } diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 544694fc8f..404766051f 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch { public class CatchRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IEnumerable mods) => new DrawableCatchRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 4b75b54d41..0324ba1d0b 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool UserScrollSpeedAdjustment => false; - public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) + public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Down; diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs index 0bfbf38832..e5f379f608 100644 --- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs +++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Edit { public new IScrollingInfo ScrollingInfo => base.ScrollingInfo; - public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) + public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index ebc94c86e2..5a8af60aab 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Edit public int TotalColumns => ((ManiaPlayfield)DrawableRuleset.Playfield).TotalColumns; - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) { DrawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 02a9b5ed30..36381294f5 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mania { public class ManiaRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IEnumerable mods) => new DrawableManiaRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableManiaRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 9d10657680..f592023300 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Bindable configDirection = new Bindable(); - public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) + public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { // Generate the bar lines diff --git a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs index 13c9985f47..e8b99e86f9 100644 --- a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.IO; using System.Linq; using System.Text; @@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Tests using (var reader = new StreamReader(stream)) { var beatmap = Decoder.GetDecoder(reader).Decode(reader); - var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Enumerable.Empty()); + var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()); var objects = converted.HitObjects.ToList(); diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 0c3050bfb4..bcb6099cfb 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Edit { public class DrawableOsuEditRuleset : DrawableOsuRuleset { - public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) + public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 9c4b6ee7aa..12e15be9b9 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit { } - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuEditRuleset(ruleset, beatmap, mods); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 115271da85..40155cf8a9 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu { public class OsuRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IEnumerable mods) => new DrawableOsuRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index c2954e1b3b..ba7241c165 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.UI { protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config; - public DrawableOsuRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) + public DrawableOsuRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs index c42faea9f9..9ceb1a9846 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -88,7 +87,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Height = 768, - Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap, Enumerable.Empty()) } + Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap, Array.Empty()) } }); } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index cb53ec890b..f90894ff5d 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko { public class TaikoRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IEnumerable mods) => new DrawableTaikoRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableTaikoRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap); public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 76bdd37ed3..adff869d26 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool UserScrollSpeedAdjustment => false; - public DrawableTaikoRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) + public DrawableTaikoRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Left; diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index e181130774..6738e0e7c2 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.IO; using NUnit.Framework; using osuTK; @@ -40,7 +41,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(6, working.BeatmapInfo.BeatmapVersion); Assert.AreEqual(6, working.Beatmap.BeatmapInfo.BeatmapVersion); - Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Enumerable.Empty()).BeatmapInfo.BeatmapVersion); + Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()).BeatmapInfo.BeatmapVersion); } } diff --git a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs index 283fd4c8b9..81fab5b4b3 100644 --- a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs @@ -267,7 +267,7 @@ namespace osu.Game.Tests.Visual.Background AddUntilStep("Song select has selection", () => songSelect.Carousel.SelectedBeatmap != null); AddStep("Set default user settings", () => { - Mods.Value = Mods.Value.Concat(new[] { new OsuModNoFail() }); + Mods.Value = Mods.Value.Concat(new[] { new OsuModNoFail() }).ToArray(); songSelect.DimLevel.Value = 0.7f; songSelect.BlurLevel.Value = 0.4f; }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs index df48834e1f..624e5f08bd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay { protected override Player CreatePlayer(Ruleset ruleset) { - Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); return new ScoreAccessiblePlayer(); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs index 2a72dc8242..263070ab21 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.ComponentModel; using System.Linq; using osu.Game.Rulesets; @@ -16,7 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay { protected override Player CreatePlayer(Ruleset ruleset) { - var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Enumerable.Empty()); + var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty()); return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap)); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs index e89b361104..7e33f6ce02 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestCasePlaySongSelect.cs @@ -183,7 +183,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("mods changed before ruleset", () => modChangeIndex < rulesetChangeIndex); AddAssert("empty mods", () => !Mods.Value.Any()); - void onModChange(ValueChangedEvent> e) => modChangeIndex = actionIndex++; + void onModChange(ValueChangedEvent> e) => modChangeIndex = actionIndex++; void onRulesetChange(ValueChangedEvent e) => rulesetChangeIndex = actionIndex--; } diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs index aab44f7d92..fd003c7ea2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseMods.cs @@ -253,7 +253,7 @@ namespace osu.Game.Tests.Visual.UserInterface private class TestModSelectOverlay : ModSelectOverlay { - public new Bindable> SelectedMods => base.SelectedMods; + public new Bindable> SelectedMods => base.SelectedMods; public ModButton GetModButton(Mod mod) { diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 3e6033da9c..58463d2219 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -52,7 +52,7 @@ namespace osu.Game.Beatmaps { public override IEnumerable GetModsFor(ModType type) => new Mod[] { }; - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IEnumerable mods) + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) { throw new NotImplementedException(); } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index ec0a76b52b..8989785dcd 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -75,7 +75,7 @@ namespace osu.Game.Beatmaps /// The to create a playable for. /// The converted . /// If could not be converted to . - public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IEnumerable mods) + public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) { var rulesetInstance = ruleset.CreateInstance(); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0785c588e5..30f98aa1ce 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -112,8 +112,8 @@ namespace osu.Game // todo: move this to SongSelect once Screen has the ability to unsuspend. [Cached] - [Cached(Type = typeof(IBindable>))] - private readonly Bindable> mods = new Bindable>(Enumerable.Empty()); + [Cached(typeof(IBindable>))] + private readonly Bindable> mods = new Bindable>(Array.Empty()); public OsuGame(string[] args = null) { diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 9e97f4551a..97769fe5aa 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -42,12 +42,12 @@ namespace osu.Game.Overlays.Mods protected readonly FillFlowContainer ModSectionsContainer; - protected readonly Bindable> SelectedMods = new Bindable>(Enumerable.Empty()); + protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); protected readonly IBindable Ruleset = new Bindable(); [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, IBindable ruleset, AudioManager audio, Bindable> mods) + private void load(OsuColour colours, IBindable ruleset, AudioManager audio, Bindable> mods) { LowMultiplierColour = colours.Red; HighMultiplierColour = colours.Green; @@ -87,14 +87,14 @@ namespace osu.Game.Overlays.Mods // attempt to re-select any already selected mods. // this may be the first time we are receiving the ruleset, in which case they will still match. - selectedModsChanged(new ValueChangedEvent>(SelectedMods.Value, SelectedMods.Value)); + selectedModsChanged(new ValueChangedEvent>(SelectedMods.Value, SelectedMods.Value)); // write the mods back to the SelectedMods bindable in the case a change was not applicable. // this generally isn't required as the previous line will perform deselection; just here for safety. refreshSelectedMods(); } - private void selectedModsChanged(ValueChangedEvent> e) + private void selectedModsChanged(ValueChangedEvent> e) { foreach (ModSection section in ModSectionsContainer.Children) section.SelectTypes(e.NewValue.Select(m => m.GetType()).ToList()); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index d9b669f753..c250d3b62a 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays private Bindable beatmap { get; set; } [Resolved] - private IBindable> mods { get; set; } + private IBindable> mods { get; set; } /// /// Provide a source for the toolbar height. diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index e181051737..14f7665e05 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Difficulty /// public Mod[] CreateDifficultyAdjustmentModCombinations() { - return createDifficultyAdjustmentModCombinations(Enumerable.Empty(), DifficultyAdjustmentMods).ToArray(); + return createDifficultyAdjustmentModCombinations(Array.Empty(), DifficultyAdjustmentMods).ToArray(); IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) { diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 5219cb9581..38ec09535d 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -186,8 +186,8 @@ namespace osu.Game.Rulesets.Edit } internal override DrawableEditRuleset CreateDrawableRuleset() - => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value, Enumerable.Empty())); + => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value, Array.Empty())); - protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods); + protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods); } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 90fe25accf..3521c17b23 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets /// The beatmap to create the hit renderer for. /// Unable to successfully load the beatmap to be usable with this ruleset. /// - public abstract DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IEnumerable mods); + public abstract DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods); /// /// Creates a to convert a to one that is applicable for this . diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 654f330a08..2866e81682 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.UI /// /// The ruleset being represented. /// The beatmap to create the hit renderer for. - protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap workingBeatmap, IEnumerable mods) + protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap workingBeatmap, IReadOnlyList mods) : base(ruleset) { if (workingBeatmap == null) @@ -257,7 +257,7 @@ namespace osu.Game.Rulesets.UI /// Applies the active mods to the Beatmap. /// /// - private void applyBeatmapMods(IEnumerable mods) + private void applyBeatmapMods(IReadOnlyList mods) { if (mods == null) return; @@ -270,7 +270,7 @@ namespace osu.Game.Rulesets.UI /// Applies the active mods to this DrawableRuleset. /// /// - private void applyRulesetMods(IEnumerable mods, OsuConfigManager config) + private void applyRulesetMods(IReadOnlyList mods, OsuConfigManager config) { if (mods == null) return; diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 2bb98dd679..dbe8d8c299 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.UI.Scrolling [Cached(Type = typeof(IScrollingInfo))] private readonly LocalScrollingInfo scrollingInfo; - protected DrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IEnumerable mods) + protected DrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { scrollingInfo = new LocalScrollingInfo(); diff --git a/osu.Game/Screens/Multi/Match/Components/Header.cs b/osu.Game/Screens/Multi/Match/Components/Header.cs index e1592532a3..2a6074882d 100644 --- a/osu.Game/Screens/Multi/Match/Components/Header.cs +++ b/osu.Game/Screens/Multi/Match/Components/Header.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -110,7 +109,7 @@ namespace osu.Game.Screens.Multi.Match.Components }, }; - CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods ?? Enumerable.Empty(), true); + CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods?.ToArray() ?? Array.Empty(), true); beatmapButton.Action = () => RequestBeatmapSelection?.Invoke(); } diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index aad571cf87..6271693a6a 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Multi.Match { RoomManager?.PartRoom(); - Mods.Value = Enumerable.Empty(); + Mods.Value = Array.Empty(); return base.OnExiting(next); } @@ -193,7 +193,7 @@ namespace osu.Game.Screens.Multi.Match var localBeatmap = e.NewValue?.Beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == e.NewValue.Beatmap.OnlineBeatmapID); Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); - Mods.Value = e.NewValue?.RequiredMods ?? Enumerable.Empty(); + Mods.Value = e.NewValue?.RequiredMods?.ToArray() ?? Array.Empty(); if (e.NewValue?.Ruleset != null) Ruleset.Value = e.NewValue.Ruleset; } diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index d1e700cf92..c1a822c75c 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens public Bindable Ruleset { get; private set; } - public Bindable> Mods { get; private set; } + public Bindable> Mods { get; private set; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { diff --git a/osu.Game/Screens/OsuScreenDependencies.cs b/osu.Game/Screens/OsuScreenDependencies.cs index d7e1862c05..4167faba83 100644 --- a/osu.Game/Screens/OsuScreenDependencies.cs +++ b/osu.Game/Screens/OsuScreenDependencies.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens public Bindable Ruleset { get; } - public Bindable> Mods { get; } + public Bindable> Mods { get; } public OsuScreenDependencies(bool requireLease, IReadOnlyDependencyContainer parent) : base(parent) @@ -31,15 +31,15 @@ namespace osu.Game.Screens if (Ruleset == null) Cache(Ruleset = parent.Get>().BeginLease(true)); - Mods = parent.Get>>()?.GetBoundCopy(); + Mods = parent.Get>>()?.GetBoundCopy(); if (Mods == null) - Cache(Mods = parent.Get>>().BeginLease(true)); + Cache(Mods = parent.Get>>().BeginLease(true)); } else { Beatmap = (parent.Get>() ?? parent.Get>()).GetBoundCopy(); Ruleset = (parent.Get>() ?? parent.Get>()).GetBoundCopy(); - Mods = (parent.Get>>() ?? parent.Get>>()).GetBoundCopy(); + Mods = (parent.Get>>() ?? parent.Get>>()).GetBoundCopy(); } } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 2b18ef5ecd..29974b728e 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Play public class GameplayClockContainer : Container { private readonly WorkingBeatmap beatmap; - private readonly IEnumerable mods; + private readonly IReadOnlyList mods; /// /// The original source (usually a 's track). @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Play private readonly FramedOffsetClock platformOffsetClock; - public GameplayClockContainer(WorkingBeatmap beatmap, IEnumerable mods, double gameplayStartTime) + public GameplayClockContainer(WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime) { this.beatmap = beatmap; this.mods = mods; diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 2c1293833f..878d2b7c38 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -18,15 +18,15 @@ using osu.Game.Graphics; namespace osu.Game.Screens.Play.HUD { - public class ModDisplay : Container, IHasCurrentValue> + public class ModDisplay : Container, IHasCurrentValue> { private const int fade_duration = 1000; public bool DisplayUnrankedText = true; - private readonly Bindable> current = new Bindable>(); + private readonly Bindable> current = new Bindable>(); - public Bindable> Current + public Bindable> Current { get => current; set diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index aa4375f652..3c1b33297a 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Play public Action RequestSeek; - public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IEnumerable mods) + public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 2096f3f0f8..1c558eae2e 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -301,7 +301,7 @@ namespace osu.Game.Screens.Play } private readonly WorkingBeatmap beatmap; - private readonly IEnumerable mods; + private readonly IReadOnlyList mods; private LoadingAnimation loading; private Sprite backgroundSprite; private ModDisplay modDisplay; @@ -323,7 +323,7 @@ namespace osu.Game.Screens.Play } } - public BeatmapMetadataDisplay(WorkingBeatmap beatmap, IEnumerable mods) + public BeatmapMetadataDisplay(WorkingBeatmap beatmap, IReadOnlyList mods) { this.beatmap = beatmap; this.mods = mods; diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 03cf767062..51f87dcc6d 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -310,12 +310,12 @@ namespace osu.Game.Screens.Select try { // Try to get the beatmap with the user's ruleset - playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Enumerable.Empty()); + playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Array.Empty()); } catch (BeatmapInvalidForRulesetException) { // Can't be converted to the user's ruleset, so use the beatmap's own ruleset - playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Enumerable.Empty()); + playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Array.Empty()); } labels.AddRange(playableBeatmap.GetStatistics().Select(s => new InfoLabel(s))); diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 4468f5704f..c5fa9e2396 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -61,7 +60,7 @@ namespace osu.Game.Screens.Select { Ruleset.Value = CurrentItem.Value.Ruleset; Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap); - Mods.Value = CurrentItem.Value.RequiredMods ?? Enumerable.Empty(); + Mods.Value = CurrentItem.Value.RequiredMods?.ToArray() ?? Array.Empty(); } Beatmap.Disabled = true; diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 44e38edda8..bf4f898323 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Select var mods = Mods.Value; if (mods.All(m => m.GetType() != autoType)) { - Mods.Value = mods.Append(auto); + Mods.Value = mods.Append(auto).ToArray(); removeAutoModOnResume = true; } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 6ad5b54e65..a78238c584 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -84,8 +84,8 @@ namespace osu.Game.Screens.Select private readonly Bindable decoupledRuleset = new Bindable(); [Cached] - [Cached(Type = typeof(IBindable>))] - private readonly Bindable> mods = new Bindable>(Enumerable.Empty()); // Bound to the game's mods, but is not reset on exiting + [Cached(Type = typeof(IBindable>))] + private readonly Bindable> mods = new Bindable>(Array.Empty()); // Bound to the game's mods, but is not reset on exiting protected SongSelect() { @@ -394,7 +394,7 @@ namespace osu.Game.Screens.Select { Logger.Log($"ruleset changed from \"{decoupledRuleset.Value}\" to \"{ruleset}\""); - mods.Value = Enumerable.Empty(); + mods.Value = Array.Empty(); decoupledRuleset.Value = ruleset; // force a filter before attempting to change the beatmap. @@ -530,7 +530,7 @@ namespace osu.Game.Screens.Select Beatmap.Value.Track.Looping = false; mods.UnbindAll(); - Mods.Value = Enumerable.Empty(); + Mods.Value = Array.Empty(); return false; } diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs index 8ec440db8b..c08a6a4bcb 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestCase.cs @@ -28,8 +28,8 @@ namespace osu.Game.Tests.Visual protected readonly Bindable Ruleset = new Bindable(); [Cached] - [Cached(Type = typeof(IBindable>))] - protected readonly Bindable> Mods = new Bindable>(Enumerable.Empty()); + [Cached(Type = typeof(IBindable>))] + protected readonly Bindable> Mods = new Bindable>(Array.Empty()); protected DependencyContainer Dependencies { get; private set; } From 1db2d49696a8cc33b54968e6ec6ab3dbfe5ece70 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Apr 2019 17:54:57 +0900 Subject: [PATCH 419/623] Fix testcases --- .../ManiaPlacementBlueprintTestCase.cs | 6 ++++++ osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs | 4 ++++ osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs | 5 +++++ .../Visual/Gameplay/TestCaseScrollingHitObjects.cs | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs index 13bbe87513..9ad22498a9 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . 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.Graphics; using osu.Framework.Graphics.Containers; @@ -8,6 +10,7 @@ using osu.Framework.Timing; using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; @@ -21,6 +24,9 @@ namespace osu.Game.Rulesets.Mania.Tests { private readonly Column column; + [Cached(typeof(IReadOnlyList))] + private IReadOnlyList mods { get; set; } = Array.Empty(); + protected ManiaPlacementBlueprintTestCase() { Add(column = new Column(0) diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs index b14f999f61..d46b661eea 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI.Components; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; using osuTK; @@ -31,6 +32,9 @@ namespace osu.Game.Rulesets.Mania.Tests typeof(ColumnHitObjectArea) }; + [Cached(typeof(IReadOnlyList))] + private IReadOnlyList mods { get; set; } = Array.Empty(); + private readonly List columns = new List(); public TestCaseColumn() diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs index ac430037e4..9a7a3d1c5c 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -13,6 +14,7 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; using osuTK; @@ -24,6 +26,9 @@ namespace osu.Game.Rulesets.Mania.Tests { private const int columns = 4; + [Cached(typeof(IReadOnlyList))] + private IReadOnlyList mods { get; set; } = Array.Empty(); + private readonly List stages = new List(); private FillFlowContainer fill; diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs index c99a4bb89b..3cfc5ac7c8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseScrollingHitObjects.cs @@ -4,11 +4,13 @@ using System; using System.Collections.Generic; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Timing; @@ -23,6 +25,9 @@ namespace osu.Game.Tests.Visual.Gameplay { public override IReadOnlyList RequiredTypes => new[] { typeof(Playfield) }; + [Cached(typeof(IReadOnlyList))] + private IReadOnlyList mods { get; set; } = Array.Empty(); + private readonly ScrollingTestContainer[] scrollContainers = new ScrollingTestContainer[4]; private readonly TestPlayfield[] playfields = new TestPlayfield[4]; From 22f9339b01ff091b1e538050912c85377fbe38bf Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Wed, 10 Apr 2019 21:53:13 +0800 Subject: [PATCH 420/623] let mods button have selected mod icons --- osu.Game/Screens/Select/Footer.cs | 31 ++++++------- osu.Game/Screens/Select/FooterButton.cs | 16 ++++--- osu.Game/Screens/Select/FooterButtonMods.cs | 48 +++++++++++++++++++++ osu.Game/Screens/Select/SongSelect.cs | 6 +-- 4 files changed, 74 insertions(+), 27 deletions(-) create mode 100644 osu.Game/Screens/Select/FooterButtonMods.cs diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 03b9826e9b..c991e5c350 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -20,9 +20,6 @@ namespace osu.Game.Screens.Select { private readonly Box modeLight; - private const float play_song_select_button_width = 100; - private const float play_song_select_button_height = 50; - public const float HEIGHT = 50; public const int TRANSITION_LENGTH = 300; @@ -33,6 +30,7 @@ namespace osu.Game.Screens.Select private readonly FillFlowContainer buttons; + /// Button to be added. /// Text on the button. /// Colour of the button. /// Hotkey of the button. @@ -41,21 +39,16 @@ namespace osu.Game.Screens.Select /// Higher depth to be put on the left, and lower to be put on the right. /// Notice this is different to ! /// - public void AddButton(string text, Color4 colour, Action action, Key? hotkey = null, float depth = 0) + public void AddButton(FooterButton button, string text, Color4 colour, Action action, Key? hotkey = null, float depth = 0) { - var button = new FooterButton - { - Text = text, - Height = play_song_select_button_height, - Width = play_song_select_button_width, - Depth = depth, - SelectedColour = colour, - DeselectedColour = colour.Opacity(0.5f), - Hotkey = hotkey, - Hovered = updateModeLight, - HoverLost = updateModeLight, - Action = action, - }; + button.Text = text; + button.Depth = depth; + button.SelectedColour = colour; + button.DeselectedColour = colour.Opacity(0.5f); + button.Hotkey = hotkey; + button.Hovered = updateModeLight; + button.HoverLost = updateModeLight; + button.Action = action; buttons.Add(button); buttons.SetLayoutPosition(button, -depth); @@ -71,10 +64,10 @@ namespace osu.Game.Screens.Select /// Higher depth to be put on the left, and lower to be put on the right. /// Notice this is different to ! /// - public void AddButton(string text, Color4 colour, OverlayContainer overlay, Key? hotkey = null, float depth = 0) + public void AddButton(FooterButton button, string text, Color4 colour, OverlayContainer overlay, Key? hotkey = null, float depth = 0) { overlays.Add(overlay); - AddButton(text, colour, () => + AddButton(button, text, colour, () => { foreach (var o in overlays) { diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 9b98e344ce..0000bb95dd 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -6,6 +6,7 @@ using osuTK; using osuTK.Graphics; using osuTK.Input; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; @@ -61,8 +62,18 @@ namespace osu.Game.Screens.Select public FooterButton() { + AutoSizeAxes = Axes.Both; Children = new Drawable[] { + new Container + { + Size = new Vector2(100, 50), + Child = spriteText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }, box = new Box { RelativeSizeAxes = Axes.Both, @@ -78,11 +89,6 @@ namespace osu.Game.Screens.Select EdgeSmoothness = new Vector2(2, 0), RelativeSizeAxes = Axes.X, }, - spriteText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } }; } diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs new file mode 100644 index 0000000000..a08870ba03 --- /dev/null +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using System; +using System.Collections.Generic; +using osuTK; + +namespace osu.Game.Screens.Select +{ + public class FooterButtonMods : FooterButton + { + private readonly Bindable> selectedMods = new Bindable>(); + + private readonly FillFlowContainer modIcons; + + public FooterButtonMods(Bindable> mods) : base() + { + Add(modIcons = new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding {Left = 80, Right = 20} + }); + + if (mods != null) + { + selectedMods.BindTo(mods); + selectedMods.ValueChanged += updateModIcons; + } + } + + private void updateModIcons(ValueChangedEvent> mods) + { + modIcons.Clear(); + foreach (Mod mod in mods.NewValue) + { + modIcons.Add(new ModIcon(mod) { Scale = new Vector2(0.4f) }); + } + } + } +} diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b60e693cbf..6fc95ea2e1 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -224,9 +224,9 @@ namespace osu.Game.Screens.Select if (Footer != null) { - Footer.AddButton(@"mods", colours.Yellow, ModSelect, Key.F1); - Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2); - Footer.AddButton(@"options", colours.Blue, BeatmapOptions, Key.F3); + Footer.AddButton(new FooterButtonMods(selectedMods), @"mods", colours.Yellow, ModSelect, Key.F1); + Footer.AddButton(new FooterButton(), @"random", colours.Green, triggerRandom, Key.F2); + Footer.AddButton(new FooterButton(), @"options", colours.Blue, BeatmapOptions, Key.F3); BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null, Key.Number1); From b4d07558186a9d8dc3627ac1af564e375cab2f95 Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Wed, 10 Apr 2019 22:10:09 +0800 Subject: [PATCH 421/623] please appveyor --- osu.Game/Screens/Select/FooterButtonMods.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index a08870ba03..51973b1e3d 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; -using System; using System.Collections.Generic; using osuTK; @@ -18,7 +17,7 @@ namespace osu.Game.Screens.Select private readonly FillFlowContainer modIcons; - public FooterButtonMods(Bindable> mods) : base() + public FooterButtonMods(Bindable> mods) { Add(modIcons = new FillFlowContainer { @@ -26,7 +25,7 @@ namespace osu.Game.Screens.Select Origin = Anchor.CentreLeft, Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, - Margin = new MarginPadding {Left = 80, Right = 20} + Margin = new MarginPadding { Left = 80, Right = 20 } }); if (mods != null) From 01cc78108c1bd0d2fa3a21d6327e85354ccc6e8b Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Thu, 11 Apr 2019 05:47:32 +0800 Subject: [PATCH 422/623] add random button --- osu.Game/Screens/Select/FooterButton.cs | 5 +- osu.Game/Screens/Select/FooterButtonRandom.cs | 56 +++++++++++++++++++ osu.Game/Screens/Select/SongSelect.cs | 2 +- 3 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Screens/Select/FooterButtonRandom.cs diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 0000bb95dd..aa547c658f 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -54,7 +54,8 @@ namespace osu.Game.Screens.Select } } - private readonly SpriteText spriteText; + protected readonly Container textContainer; + protected readonly SpriteText spriteText; private readonly Box box; private readonly Box light; @@ -65,7 +66,7 @@ namespace osu.Game.Screens.Select AutoSizeAxes = Axes.Both; Children = new Drawable[] { - new Container + textContainer = new Container { Size = new Vector2(100, 50), Child = spriteText = new OsuSpriteText diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs new file mode 100644 index 0000000000..7466e1f243 --- /dev/null +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . 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.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Sprites; +using System; + +namespace osu.Game.Screens.Select +{ + public class FooterButtonRandom : FooterButton + { + private readonly SpriteText secondaryText; + private bool secondaryActive; + + public FooterButtonRandom() + { + textContainer.Add(secondaryText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = @"rewind", + Alpha = 0 + }); + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + secondaryActive = e.ShiftPressed; + updateText(); + return base.OnKeyDown(e); + } + + protected override bool OnKeyUp(KeyUpEvent e) + { + secondaryActive = e.ShiftPressed; + updateText(); + return base.OnKeyUp(e); + } + + private void updateText() + { + if (secondaryActive) + { + spriteText.FadeOut(120, Easing.InQuad); + secondaryText.FadeIn(120, Easing.InQuad); + } + else + { + spriteText.FadeIn(120, Easing.InQuad); + secondaryText.FadeOut(120, Easing.InQuad); + } + } + } +} diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 6fc95ea2e1..7fb49e6dc6 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -225,7 +225,7 @@ namespace osu.Game.Screens.Select if (Footer != null) { Footer.AddButton(new FooterButtonMods(selectedMods), @"mods", colours.Yellow, ModSelect, Key.F1); - Footer.AddButton(new FooterButton(), @"random", colours.Green, triggerRandom, Key.F2); + Footer.AddButton(new FooterButtonRandom(), @"random", colours.Green, triggerRandom, Key.F2); Footer.AddButton(new FooterButton(), @"options", colours.Blue, BeatmapOptions, Key.F3); BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); From 664a4ba540677fb6851d7a6c7277bc1eb788928c Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 12 Apr 2019 10:47:22 +0900 Subject: [PATCH 423/623] Implement flashlight dimming on slider slide --- .../Mods/OsuModFlashlight.cs | 30 +++++++++++++++++-- .../Objects/Drawables/DrawableRepeatPoint.cs | 2 +- .../Objects/Drawables/DrawableSlider.cs | 4 +-- osu.Game/Rulesets/Mods/ModFlashlight.cs | 17 +++++++++++ 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 2c40d18f1b..26c0c26f0f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -1,23 +1,49 @@ // Copyright (c) ppy Pty Ltd . 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.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModFlashlight : ModFlashlight + public class OsuModFlashlight : ModFlashlight, IApplicableToDrawableHitObjects { public override double ScoreMultiplier => 1.12; private const float default_flashlight_size = 180; - public override Flashlight CreateFlashlight() => new OsuFlashlight(); + private int trackingSliders; + + private OsuFlashlight flashlight; + + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(); + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (DrawableSlider drawable in drawables.OfType()) + { + drawable.Tracking.ValueChanged += updateTrackingSliders; + } + } + + private void updateTrackingSliders(ValueChangedEvent value) + { + if (value.NewValue) + trackingSliders++; + else + trackingSliders--; + + flashlight.FlashlightLightness = trackingSliders > 0 ? 0.2f : 1.0f; + } private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index edf2d90c08..bece2a49cd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { if (repeatPoint.StartTime <= Time.Current) - ApplyResult(r => r.Type = drawableSlider.Tracking ? HitResult.Great : HitResult.Miss); + ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? HitResult.Great : HitResult.Miss); } protected override void UpdatePreemptState() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 57ea0abdd8..c1a4c1981f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -130,13 +130,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public bool Tracking; + public readonly Bindable Tracking = new Bindable(); protected override void Update() { base.Update(); - Tracking = Ball.Tracking; + Tracking.Value = Ball.Tracking; double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 0ad99d13ff..fa070dc9e8 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -80,6 +80,7 @@ namespace osu.Game.Rulesets.Mods flashNode.ScreenSpaceDrawQuad = ScreenSpaceDrawQuad; flashNode.FlashlightPosition = Vector2Extensions.Transform(FlashlightPosition, DrawInfo.Matrix); flashNode.FlashlightSize = Vector2Extensions.Transform(FlashlightSize, DrawInfo.Matrix); + flashNode.FlashlightLightness = FlashlightLightness; } [BackgroundDependencyLoader] @@ -136,6 +137,20 @@ namespace osu.Game.Rulesets.Mods Invalidate(Invalidation.DrawNode); } } + + private float flashlightLightness = 1.0f; + + public float FlashlightLightness + { + get => flashlightLightness; + set + { + if (flashlightLightness == value) return; + + flashlightLightness = value; + Invalidate(Invalidation.DrawNode); + } + } } private class FlashlightDrawNode : DrawNode @@ -144,6 +159,7 @@ namespace osu.Game.Rulesets.Mods public Quad ScreenSpaceDrawQuad; public Vector2 FlashlightPosition; public Vector2 FlashlightSize; + public float FlashlightLightness; public override void Draw(Action vertexAction) { @@ -153,6 +169,7 @@ namespace osu.Game.Rulesets.Mods Shader.GetUniform("flashlightPos").UpdateValue(ref FlashlightPosition); Shader.GetUniform("flashlightSize").UpdateValue(ref FlashlightSize); + Shader.GetUniform("flashlightLightness").UpdateValue(ref FlashlightLightness); Texture.WhitePixel.DrawQuad(ScreenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); From 846a4835ca3a5915a31318d8b67472eb0ab636d2 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 12 Apr 2019 11:23:40 +0900 Subject: [PATCH 424/623] Invert flashlight dim --- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 2 +- osu.Game/Rulesets/Mods/ModFlashlight.cs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 26c0c26f0f..6a57e39616 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Mods else trackingSliders--; - flashlight.FlashlightLightness = trackingSliders > 0 ? 0.2f : 1.0f; + flashlight.FlashlightDim = trackingSliders > 0 ? 0.8f : 0.0f; } private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index fa070dc9e8..e454c59fab 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Mods flashNode.ScreenSpaceDrawQuad = ScreenSpaceDrawQuad; flashNode.FlashlightPosition = Vector2Extensions.Transform(FlashlightPosition, DrawInfo.Matrix); flashNode.FlashlightSize = Vector2Extensions.Transform(FlashlightSize, DrawInfo.Matrix); - flashNode.FlashlightLightness = FlashlightLightness; + flashNode.FlashlightDim = FlashlightDim; } [BackgroundDependencyLoader] @@ -138,16 +138,16 @@ namespace osu.Game.Rulesets.Mods } } - private float flashlightLightness = 1.0f; + private float flashlightDim; - public float FlashlightLightness + public float FlashlightDim { - get => flashlightLightness; + get => flashlightDim; set { - if (flashlightLightness == value) return; + if (flashlightDim == value) return; - flashlightLightness = value; + flashlightDim = value; Invalidate(Invalidation.DrawNode); } } @@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.Mods public Quad ScreenSpaceDrawQuad; public Vector2 FlashlightPosition; public Vector2 FlashlightSize; - public float FlashlightLightness; + public float FlashlightDim; public override void Draw(Action vertexAction) { @@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.Mods Shader.GetUniform("flashlightPos").UpdateValue(ref FlashlightPosition); Shader.GetUniform("flashlightSize").UpdateValue(ref FlashlightSize); - Shader.GetUniform("flashlightLightness").UpdateValue(ref FlashlightLightness); + Shader.GetUniform("flashlightDim").UpdateValue(ref FlashlightDim); Texture.WhitePixel.DrawQuad(ScreenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); From dba4ccdf74b401f6f07584df67acd4ead0c49c89 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 12 Apr 2019 14:53:23 +0900 Subject: [PATCH 425/623] Add back flashlight testcase --- .../TestCaseFlashlight.cs | 18 ++++++++++++++++++ osu.Game/Tests/Visual/AllPlayersTestCase.cs | 12 +++++------- osu.Game/Tests/Visual/PlayerTestCase.cs | 13 ++++--------- 3 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestCaseFlashlight.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseFlashlight.cs new file mode 100644 index 0000000000..28a732cc49 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseFlashlight.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseFlashlight : TestCaseOsuPlayer + { + protected override Player CreatePlayer(Ruleset ruleset) + { + Beatmap.Value.Mods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), }; + return base.CreatePlayer(ruleset); + } + } +} diff --git a/osu.Game/Tests/Visual/AllPlayersTestCase.cs b/osu.Game/Tests/Visual/AllPlayersTestCase.cs index 4ef9b346b0..6747493509 100644 --- a/osu.Game/Tests/Visual/AllPlayersTestCase.cs +++ b/osu.Game/Tests/Visual/AllPlayersTestCase.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; @@ -26,13 +27,6 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - Add(new Box - { - RelativeSizeAxes = Framework.Graphics.Axes.Both, - Colour = Color4.Black, - Depth = int.MaxValue - }); - foreach (var r in rulesets.AvailableRulesets) { Player p = null; @@ -50,6 +44,10 @@ namespace osu.Game.Tests.Visual AddCheckSteps(); } + + OsuConfigManager manager; + Dependencies.Cache(manager = new OsuConfigManager(LocalStorage)); + manager.GetBindable(OsuSetting.DimLevel).Value = 1.0; } protected abstract void AddCheckSteps(); diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index 3bf707fade..8ec822957f 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -3,15 +3,13 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; -using osuTK.Graphics; namespace osu.Game.Tests.Visual { @@ -29,12 +27,9 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { - Add(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Depth = int.MaxValue - }); + OsuConfigManager manager; + Dependencies.Cache(manager = new OsuConfigManager(LocalStorage)); + manager.GetBindable(OsuSetting.DimLevel).Value = 1.0; } [SetUpSteps] From 69748abedcd1e8e018afd8c55da4e6a36297018d Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 12 Apr 2019 15:09:43 +0900 Subject: [PATCH 426/623] Rename to TestCaseOsuFlashlight --- .../{TestCaseFlashlight.cs => TestCaseOsuFlashlight.cs} | 2 +- osu.Game/Tests/Visual/AllPlayersTestCase.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) rename osu.Game.Rulesets.Osu.Tests/{TestCaseFlashlight.cs => TestCaseOsuFlashlight.cs} (89%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuFlashlight.cs similarity index 89% rename from osu.Game.Rulesets.Osu.Tests/TestCaseFlashlight.cs rename to osu.Game.Rulesets.Osu.Tests/TestCaseOsuFlashlight.cs index 28a732cc49..6f198084f5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuFlashlight.cs @@ -7,7 +7,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Osu.Tests { - public class TestCaseFlashlight : TestCaseOsuPlayer + public class TestCaseOsuFlashlight : TestCaseOsuPlayer { protected override Player CreatePlayer(Ruleset ruleset) { diff --git a/osu.Game/Tests/Visual/AllPlayersTestCase.cs b/osu.Game/Tests/Visual/AllPlayersTestCase.cs index 6747493509..882f510b64 100644 --- a/osu.Game/Tests/Visual/AllPlayersTestCase.cs +++ b/osu.Game/Tests/Visual/AllPlayersTestCase.cs @@ -3,7 +3,6 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -12,7 +11,6 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; -using osuTK.Graphics; namespace osu.Game.Tests.Visual { From d9ed68b18968ce1c1eafd6c3dc466b7119c5e134 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 12 Apr 2019 15:33:31 +0900 Subject: [PATCH 427/623] Add short fade to flashlight dimming --- .../Mods/OsuModFlashlight.cs | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 6a57e39616..5d136e4cbe 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -21,8 +21,6 @@ namespace osu.Game.Rulesets.Osu.Mods private const float default_flashlight_size = 180; - private int trackingSliders; - private OsuFlashlight flashlight; public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(); @@ -31,27 +29,30 @@ namespace osu.Game.Rulesets.Osu.Mods { foreach (DrawableSlider drawable in drawables.OfType()) { - drawable.Tracking.ValueChanged += updateTrackingSliders; + drawable.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; } } - private void updateTrackingSliders(ValueChangedEvent value) - { - if (value.NewValue) - trackingSliders++; - else - trackingSliders--; - - flashlight.FlashlightDim = trackingSliders > 0 ? 0.8f : 0.0f; - } - private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition { + private int trackingSliders; + public OsuFlashlight() { FlashlightSize = new Vector2(0, getSizeFor(0)); } + public void OnSliderTrackingChange(ValueChangedEvent e) + { + if (e.NewValue) + trackingSliders++; + else + trackingSliders--; + + // If there are any sliders in a tracking state, apply a dim to the entire playfield over a brief duration. + this.TransformTo(nameof(FlashlightDim), trackingSliders > 0 ? 0.8f : 0.0f, 50); + } + protected override bool OnMouseMove(MouseMoveEvent e) { FlashlightPosition = e.MousePosition; From e25c7fdb9b5c8de23ef51c39065fbde9c7af2bf1 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 12 Apr 2019 17:39:25 +0900 Subject: [PATCH 428/623] Use absolute sequence for flashlight breaks --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index e454c59fab..b5a907c0ef 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -95,14 +95,17 @@ namespace osu.Game.Rulesets.Mods Combo.ValueChanged += OnComboChange; - this.FadeInFromZero(FLASHLIGHT_FADE_DURATION); - - foreach (var breakPeriod in Breaks) + using (BeginAbsoluteSequence(0)) { - if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; + this.FadeInFromZero(FLASHLIGHT_FADE_DURATION); - this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); - this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION); + foreach (var breakPeriod in Breaks) + { + if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; + + this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); + this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION); + } } } From c766631ad6a163e04c531d20d4ed24db47d0e4d3 Mon Sep 17 00:00:00 2001 From: DrabWeb Date: Sat, 13 Apr 2019 11:39:22 -0300 Subject: [PATCH 429/623] Check for breaks using GameplayClock time. --- osu.Game/Screens/Play/BreakOverlay.cs | 4 ---- osu.Game/Screens/Play/Player.cs | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index ab81e613cc..d390787090 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -23,8 +23,6 @@ namespace osu.Game.Screens.Play private readonly Container fadeContainer; - public bool IsActive { get; private set; } - public List Breaks { get => breaks; @@ -127,7 +125,6 @@ namespace osu.Game.Screens.Play using (BeginAbsoluteSequence(b.StartTime, true)) { - Schedule(() => IsActive = true); fadeContainer.FadeIn(fade_duration); breakArrows.Show(fade_duration); @@ -145,7 +142,6 @@ namespace osu.Game.Screens.Play using (BeginDelayedSequence(b.Duration - fade_duration, true)) { - Schedule(() => IsActive = false); fadeContainer.FadeOut(fade_duration); breakArrows.Hide(fade_duration); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 60287f7a2e..1878b19a1c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -60,8 +60,6 @@ namespace osu.Game.Screens.Play private SampleChannel sampleRestart; - private BreakOverlay breakOverlay; - protected ScoreProcessor ScoreProcessor { get; private set; } protected DrawableRuleset DrawableRuleset { get; private set; } @@ -117,7 +115,7 @@ namespace osu.Game.Screens.Play Child = DrawableRuleset } }, - breakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) + new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -408,7 +406,9 @@ namespace osu.Game.Screens.Play PauseOverlay.Hide(); // breaks and time-based conditions may allow instant resume. - if (breakOverlay.IsActive || GameplayClockContainer.GameplayClock.CurrentTime < Beatmap.Value.Beatmap.HitObjects.First().StartTime) + double time = GameplayClockContainer.GameplayClock.CurrentTime; + if (Beatmap.Value.Beatmap.Breaks.Any(b => time >= b.StartTime && time <= b.EndTime) || + time < Beatmap.Value.Beatmap.HitObjects.First().StartTime) completeResume(); else DrawableRuleset.RequestResume(completeResume); From 91c327a90f14b151ff3a62710559ab3f96153aa5 Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Sun, 14 Apr 2019 07:22:31 +0800 Subject: [PATCH 430/623] use ModDisplay --- osu.Game/Screens/Select/FooterButtonMods.cs | 28 +++++++++------------ 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 51973b1e3d..d4bb7d3f21 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -4,44 +4,40 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Screens.Play.HUD; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using System.Collections.Generic; using osuTK; +using osu.Framework.Input.Events; namespace osu.Game.Screens.Select { public class FooterButtonMods : FooterButton { - private readonly Bindable> selectedMods = new Bindable>(); - - private readonly FillFlowContainer modIcons; + private readonly FooterModDisplay modDisplay; public FooterButtonMods(Bindable> mods) { - Add(modIcons = new FillFlowContainer + Add(new Container { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Direction = FillDirection.Horizontal, + Child = modDisplay = new FooterModDisplay { + DisplayUnrankedText = false, + Scale = new Vector2(0.8f) + }, AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Left = 80, Right = 20 } + Margin = new MarginPadding { Left = 70 } }); if (mods != null) - { - selectedMods.BindTo(mods); - selectedMods.ValueChanged += updateModIcons; - } + modDisplay.Current = mods; } - private void updateModIcons(ValueChangedEvent> mods) + private class FooterModDisplay : ModDisplay { - modIcons.Clear(); - foreach (Mod mod in mods.NewValue) - { - modIcons.Add(new ModIcon(mod) { Scale = new Vector2(0.4f) }); - } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false; } } } From 2d227d25ccf45132e813e5293cd165fa30a6606f Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Sun, 14 Apr 2019 07:55:15 +0800 Subject: [PATCH 431/623] fix appveyor warnings --- osu.Game/Screens/Select/FooterButton.cs | 14 +++++++------- osu.Game/Screens/Select/FooterButtonMods.cs | 6 ++---- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index aa547c658f..e18a086a10 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -21,11 +21,11 @@ namespace osu.Game.Screens.Select public string Text { - get => spriteText?.Text; + get => SpriteText?.Text; set { - if (spriteText != null) - spriteText.Text = value; + if (SpriteText != null) + SpriteText.Text = value; } } @@ -54,8 +54,8 @@ namespace osu.Game.Screens.Select } } - protected readonly Container textContainer; - protected readonly SpriteText spriteText; + protected readonly Container TextContainer; + protected readonly SpriteText SpriteText; private readonly Box box; private readonly Box light; @@ -66,10 +66,10 @@ namespace osu.Game.Screens.Select AutoSizeAxes = Axes.Both; Children = new Drawable[] { - textContainer = new Container + TextContainer = new Container { Size = new Vector2(100, 50), - Child = spriteText = new OsuSpriteText + Child = SpriteText = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index d4bb7d3f21..0a5b8c0929 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -6,19 +6,17 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Screens.Play.HUD; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; using System.Collections.Generic; using osuTK; -using osu.Framework.Input.Events; namespace osu.Game.Screens.Select { public class FooterButtonMods : FooterButton { - private readonly FooterModDisplay modDisplay; - public FooterButtonMods(Bindable> mods) { + FooterModDisplay modDisplay; + Add(new Container { Anchor = Anchor.CentreLeft, From ac2eabc9bfb8bf7c5d52b5b8a1f97ca37fc1c585 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Apr 2019 17:47:00 +0900 Subject: [PATCH 432/623] Fix replay rewinding not respecting 60fps playback --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index deec2b8eac..c307520aca 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.UI { if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) { - newProposedTime = manualClock.Rate > 0 + newProposedTime = newProposedTime > manualClock.CurrentTime ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); } From b684cd49e6fdd0233f91be155c1bdd182a76ddf7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Apr 2019 22:51:57 +0800 Subject: [PATCH 433/623] Update framework --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 74ed9f91dd..1fcbe7c4c1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9fff64c61c..831f33f0b8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 5a3d6a02585b2d3b97e5846359e28a7b317cf6e3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Apr 2019 16:11:59 +0900 Subject: [PATCH 434/623] Fix post-merge errors --- osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs | 8 ++++---- osu.Game/Screens/Play/Player.cs | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs index fba4aca343..f58c0d35b3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("load player", () => { - SelectedMods.Value = new[] { gameMod = new TestMod() }; + Mods.Value = new[] { gameMod = new TestMod() }; InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre); stack.Push(new PlayerLoader(() => player = new TestPlayer())); }); @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Gameplay { if (player.IsCurrentScreen()) { - playerMod1 = (TestMod)player.SelectedMods.Value.Single(); + playerMod1 = (TestMod)player.Mods.Value.Single(); return true; } @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual.Gameplay { if (player.IsCurrentScreen()) { - playerMod2 = (TestMod)player.SelectedMods.Value.Single(); + playerMod2 = (TestMod)player.Mods.Value.Single(); return true; } @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestPlayer : Player { - public new Bindable> SelectedMods => base.SelectedMods; + public new Bindable> Mods => base.Mods; public TestPlayer() : base(false, false) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index f8b3efb781..f833aa2bb7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -71,8 +71,8 @@ namespace osu.Game.Screens.Play protected GameplayClockContainer GameplayClockContainer { get; private set; } [Cached] - [Cached(Type = typeof(IBindable>))] - protected readonly Bindable> SelectedMods = new Bindable>(Enumerable.Empty()); + [Cached(Type = typeof(IBindable>))] + protected readonly Bindable> Mods = new Bindable>(Array.Empty()); private readonly bool allowPause; private readonly bool showResults; @@ -93,7 +93,7 @@ namespace osu.Game.Screens.Play { this.api = api; - SelectedMods.Value = base.SelectedMods.Value.Select(m => m.CreateCopy()).ToArray(); + Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); WorkingBeatmap working = loadBeatmap(); From 9f92b3a8ba503940bbd559388149812086a4504f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Apr 2019 16:34:53 +0900 Subject: [PATCH 435/623] Add xmldoc --- osu.Game/Graphics/Containers/LogoTrackingContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs index 5ebdb7d45d..f139040abd 100644 --- a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -60,6 +60,9 @@ namespace osu.Game.Graphics.Containers startPosition = null; } + /// + /// Stops the logo assigned in from tracking the facade's position. + /// public void StopTracking() { if (Logo != null) From 3a1587fa53b53ce5490032469d8e19fce34d3f47 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Apr 2019 16:40:47 +0900 Subject: [PATCH 436/623] Throw exception when not relatively positioned --- osu.Game/Graphics/Containers/LogoTrackingContainer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs index f139040abd..ddf257776f 100644 --- a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -92,12 +92,15 @@ namespace osu.Game.Graphics.Containers if (Logo == null) return; + if (Logo.RelativePositionAxes != Axes.Both) + throw new InvalidOperationException($"Tracking logo must have {nameof(RelativePositionAxes)} = Axes.Both"); + // Account for the scale of the actual OsuLogo, as SizeForFlow only accounts for the sprite scale. ((ExposedFacade)LogoFacade).SetSize(new Vector2(Logo.SizeForFlow * Logo.Scale.X)); var localPos = ComputeLogoTrackingPosition(); - if (LogoFacade.Parent != null && Logo.Position != localPos && Logo.RelativePositionAxes == Axes.Both) + if (LogoFacade.Parent != null && Logo.Position != localPos) { // If this is our first update since tracking has started, initialize our starting values for interpolation if (startTime == null || startPosition == null) From 4106da24303cf0289293d0d87ccc16fc4aee0b93 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Apr 2019 16:41:20 +0900 Subject: [PATCH 437/623] Rename facade + cleanup usage --- .../Graphics/Containers/LogoTrackingContainer.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs index ddf257776f..fb23038dde 100644 --- a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -15,22 +15,19 @@ namespace osu.Game.Graphics.Containers /// public class LogoTrackingContainer : Container { - public Facade LogoFacade { get; } + public Facade LogoFacade => facade; protected OsuLogo Logo { get; private set; } + private readonly InternalFacade facade = new InternalFacade(); + private Easing easing; private Vector2? startPosition; private double? startTime; private double duration; - public LogoTrackingContainer() - { - LogoFacade = new ExposedFacade(); - } - /// - /// Assign the logo that should track the Facade's position, as well as how it should transform to its initial position. + /// Assign the logo that should track the facade's position, as well as how it should transform to its initial position. /// /// The instance of the logo to be used for tracking. /// The scale of the facade. Does not actually affect the logo itself. @@ -96,7 +93,7 @@ namespace osu.Game.Graphics.Containers throw new InvalidOperationException($"Tracking logo must have {nameof(RelativePositionAxes)} = Axes.Both"); // Account for the scale of the actual OsuLogo, as SizeForFlow only accounts for the sprite scale. - ((ExposedFacade)LogoFacade).SetSize(new Vector2(Logo.SizeForFlow * Logo.Scale.X)); + facade.SetSize(new Vector2(Logo.SizeForFlow * Logo.Scale.X)); var localPos = ComputeLogoTrackingPosition(); @@ -133,7 +130,7 @@ namespace osu.Game.Graphics.Containers base.Dispose(isDisposing); } - private class ExposedFacade : Facade + private class InternalFacade : Facade { public void SetSize(Vector2 size) { From 897bfa60db361b01682be36b55640ed3bf4a4d9f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Apr 2019 17:02:47 +0900 Subject: [PATCH 438/623] Fix tracking position during 150ms state change delay --- osu.Game/Screens/Menu/ButtonArea.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Menu/ButtonArea.cs b/osu.Game/Screens/Menu/ButtonArea.cs index d6e1aef63c..eada1e0777 100644 --- a/osu.Game/Screens/Menu/ButtonArea.cs +++ b/osu.Game/Screens/Menu/ButtonArea.cs @@ -32,6 +32,7 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.X, Size = new Vector2(1, BUTTON_AREA_HEIGHT), Alpha = 0, + AlwaysPresent = true, // Always needs to be present for correct tracking on initial -> toplevel state change Children = new Drawable[] { buttonAreaBackground = new ButtonAreaBackground(), From 106e77c3d7794ab923380cbeb2cbe72c7b289741 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Apr 2019 17:15:23 +0900 Subject: [PATCH 439/623] Cleanup testcase --- .../TestCaseLogoTrackingContainer.cs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs index 8b70a34c85..088a46d5e4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs @@ -35,7 +35,6 @@ namespace osu.Game.Tests.Visual.UserInterface }; private OsuLogo logo; - private readonly Bindable uiScale = new Bindable(); private TestLogoTrackingContainer trackingContainer; private Container transferContainer; private Box visualBox; @@ -45,13 +44,6 @@ namespace osu.Game.Tests.Visual.UserInterface private const float visual_box_size = 72; - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - config.BindWith(OsuSetting.UIScale, uiScale); - AddSliderStep("Adjust scale", 0.8f, 1.5f, 1f, v => uiScale.Value = v); - } - [SetUpSteps] public void SetUpSteps() { @@ -69,7 +61,7 @@ namespace osu.Game.Tests.Visual.UserInterface /// Check if the logo is still tracking the facade. /// [Test] - public void MoveFacadeTest() + public void TestMoveFacade() { AddToggleStep("Toggle move continuously", b => randomPositions = b); AddStep("Add tracking containers", addFacadeContainers); @@ -82,7 +74,7 @@ namespace osu.Game.Tests.Visual.UserInterface /// Check if the facade is removed from the container, the logo stops tracking. /// [Test] - public void RemoveFacadeTest() + public void TestRemoveFacade() { AddStep("Add tracking containers", addFacadeContainers); AddStep("Move facade to random position", moveLogoFacade); @@ -95,7 +87,7 @@ namespace osu.Game.Tests.Visual.UserInterface /// Check if the facade gets added to a new container, tracking starts on the new facade. /// [Test] - public void TransferFacadeTest() + public void TestTransferFacade() { AddStep("Add tracking containers", addFacadeContainers); AddStep("Move facade to random position", moveLogoFacade); @@ -106,6 +98,7 @@ namespace osu.Game.Tests.Visual.UserInterface transferContainerBox.Colour = Color4.Tomato; moveLogoFacade(); }); + waitForMove(); AddAssert("Logo is tracking", () => trackingContainer.IsLogoTracking); } @@ -114,7 +107,7 @@ namespace osu.Game.Tests.Visual.UserInterface /// Add a facade to a flow container, move the logo to the center of the screen, then start tracking on the facade. /// [Test] - public void FlowContainerTest() + public void TestFlowContainer() { FillFlowContainer flowContainer; @@ -188,14 +181,16 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void SetFacadeSizeTest() + public void TestSetFacadeSize() { bool failed = false; + AddStep("Set up scenario", () => { failed = false; addFacadeContainers(); }); + AddStep("Try setting facade size", () => { try @@ -208,14 +203,16 @@ namespace osu.Game.Tests.Visual.UserInterface failed = true; } }); + AddAssert("Exception thrown", () => failed); } [Test] - public void SetMultipleContainersTest() + public void TestSetMultipleContainers() { bool failed = false; LogoTrackingContainer newContainer = new LogoTrackingContainer(); + AddStep("Set up scenario", () => { failed = false; @@ -223,6 +220,7 @@ namespace osu.Game.Tests.Visual.UserInterface addFacadeContainers(); moveLogoFacade(); }); + AddStep("Try tracking new container", () => { try @@ -235,6 +233,7 @@ namespace osu.Game.Tests.Visual.UserInterface failed = true; } }); + AddAssert("Exception thrown", () => failed); } From c145ce2d005ac723d1625fab276d23cc4384855d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Apr 2019 17:19:55 +0900 Subject: [PATCH 440/623] Remove usings --- .../Visual/UserInterface/TestCaseLogoTrackingContainer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs index 088a46d5e4..e45e2e24da 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseLogoTrackingContainer.cs @@ -4,15 +4,12 @@ using System; using System.Collections.Generic; using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.MathUtils; using osu.Framework.Testing; -using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; From 12b6bc48bd780a20b563f7c45cd9c8e5662e7c19 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Apr 2019 17:24:09 +0900 Subject: [PATCH 441/623] Use .With() wherever possible --- osu.Game/Screens/Menu/ButtonSystem.cs | 3 +-- osu.Game/Screens/Play/PlayerLoader.cs | 9 +++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index f6778691bf..519fadb34b 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -101,11 +101,10 @@ namespace osu.Game.Screens.Menu { VisibleState = ButtonSystemState.Play, }, - logoTrackingContainer.LogoFacade + logoTrackingContainer.LogoFacade.With(d => d.Scale = new Vector2(0.74f)) }); buttonArea.Flow.CentreTarget = logoTrackingContainer.LogoFacade; - logoTrackingContainer.LogoFacade.Scale = new Vector2(0.74f); } [Resolved(CanBeNull = true)] diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 7ff214c607..6a55fe278b 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -351,9 +351,6 @@ namespace osu.Game.Screens.Play { var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); - facade.Anchor = Anchor.TopCentre; - facade.Origin = Anchor.TopCentre; - AutoSizeAxes = Axes.Both; Children = new Drawable[] { @@ -365,7 +362,11 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Vertical, Children = new[] { - facade, + facade.With(d => + { + d.Anchor = Anchor.TopCentre; + d.Origin = Anchor.TopCentre; + }), new OsuSpriteText { Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)), From d1799d197ddc5d0beee07edb14f5bac7537d039a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Apr 2019 09:27:30 +0800 Subject: [PATCH 442/623] Update resources and mods usage --- osu.Game.Rulesets.Osu.Tests/TestCaseOsuFlashlight.cs | 3 ++- osu.Game/osu.Game.csproj | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuFlashlight.cs index 6f198084f5..1e72591b87 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuFlashlight.cs @@ -11,7 +11,8 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override Player CreatePlayer(Ruleset ruleset) { - Beatmap.Value.Mods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), }; + Mods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), }; + return base.CreatePlayer(ruleset); } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1fcbe7c4c1..25a98c9b74 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + From dfa5beea89ef6e2f7398723cf60ae386fd2bf738 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 18 Apr 2019 14:24:19 +0900 Subject: [PATCH 443/623] Use the actual scale of Flashlight for FlashlightSize --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index b5a907c0ef..22c2f6dd51 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.MatrixExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; @@ -79,7 +80,7 @@ namespace osu.Game.Rulesets.Mods flashNode.Shader = shader; flashNode.ScreenSpaceDrawQuad = ScreenSpaceDrawQuad; flashNode.FlashlightPosition = Vector2Extensions.Transform(FlashlightPosition, DrawInfo.Matrix); - flashNode.FlashlightSize = Vector2Extensions.Transform(FlashlightSize, DrawInfo.Matrix); + flashNode.FlashlightSize = FlashlightSize * DrawInfo.Matrix.ExtractScale().Xy; flashNode.FlashlightDim = FlashlightDim; } From 037e23247f766ce5d0bc71eeeaac4f4787f99dfe Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 18 Apr 2019 14:31:47 +0900 Subject: [PATCH 444/623] Remove unused using --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 22c2f6dd51..c0816b2457 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.MatrixExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; From 9d6912a6920848036a617f241c4c4ff154e04149 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Apr 2019 21:23:40 +0800 Subject: [PATCH 445/623] Update framework --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1fcbe7c4c1..2b7ded6d8c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 831f33f0b8..958191d708 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From c8d760b555b52265f0cf2dc3f3ec3832ead211e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Apr 2019 21:33:29 +0800 Subject: [PATCH 446/623] Attempt using previous appveyor image --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 1f485485da..4dcaa7b45e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ clone_depth: 1 version: '{branch}-{build}' -image: Visual Studio 2017 +image: Previous Visual Studio 2017 test: off install: - cmd: git submodule update --init --recursive --depth=5 From 7a0320d39e29c66824a04ef7b6e06a4aaa35cfe9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Apr 2019 00:01:26 +0800 Subject: [PATCH 447/623] Increase diagnostic level --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 4dcaa7b45e..51e946219f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,8 @@ clone_depth: 1 version: '{branch}-{build}' -image: Previous Visual Studio 2017 +image: Visual Studio 2017 test: off install: - cmd: git submodule update --init --recursive --depth=5 build_script: - - cmd: PowerShell -Version 2.0 .\build.ps1 + - cmd: PowerShell -Version 2.0 .\build.ps1 -Verbosity Diagnostic From 6502e26d09020a3ee564b40e74e0fa937aca70ea Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 19 Apr 2019 16:21:15 +0900 Subject: [PATCH 448/623] Add draw size invalidation to ZoomableScrollContainer --- .../Components/Timeline/ZoomableScrollContainer.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 1e94a20dc7..f41b3cddc0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -92,13 +92,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected override void Update() - { - base.Update(); - - zoomedContent.Width = DrawWidth * currentZoom; - } - protected override bool OnScroll(ScrollEvent e) { if (e.IsPrecise) @@ -169,6 +162,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset; d.currentZoom = newZoom; + + d.zoomedContent.Width = d.DrawWidth * d.currentZoom; + // Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area. + // TODO: Make sure draw size gets invalidated properly on the framework side, and remove this once it is. + d.Invalidate(Invalidation.DrawSize); d.ScrollTo(targetOffset, false); } From d3920d652d82b1d7603bec9e94ba9f9500d9879e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Apr 2019 12:44:57 +0900 Subject: [PATCH 449/623] Fix loader animation test case --- osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs b/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs index df12e14891..eb275cbceb 100644 --- a/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs +++ b/osu.Game.Tests/Visual/Menus/TestCaseLoaderAnimation.cs @@ -23,7 +23,11 @@ namespace osu.Game.Tests.Visual.Menus public TestCaseLoaderAnimation() { - Child = logo = new OsuLogo { Depth = float.MinValue }; + Child = logo = new OsuLogo + { + Alpha = 0, + Depth = float.MinValue + }; } [Test] @@ -39,7 +43,7 @@ namespace osu.Game.Tests.Visual.Menus LoadScreen(loader); }); - AddAssert("loaded", () => + AddUntilStep("loaded", () => { logoVisible = loader.Logo?.Alpha > 0; return loader.Logo != null && loader.ScreenLoaded; From 489929d25ce7eb0907ee8a848a5aa3094c22bde1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Apr 2019 19:45:17 +0900 Subject: [PATCH 450/623] Fix PlayerLoader testcase being completely broken --- .../Visual/Gameplay/TestCasePlayerLoader.cs | 48 +++++++------------ 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs index f58c0d35b3..1f0a97cc58 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs @@ -10,21 +10,25 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Gameplay { public class TestCasePlayerLoader : ManualInputManagerTestCase { private PlayerLoader loader; - private readonly OsuScreenStack stack; + private OsuScreenStack stack; - public TestCasePlayerLoader() + [SetUp] + public void Setup() => Schedule(() => { - InputManager.Add(stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }); - } + InputManager.Child = stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; + Beatmap.Value = new TestWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), Clock); + }); [Test] public void TestLoadContinuation() @@ -33,8 +37,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); - AddStep("exit loader", () => loader.Exit()); - AddUntilStep("wait for no longer alive", () => !loader.IsAlive); AddStep("load slow dummy beatmap", () => { SlowLoadPlayer slow = null; @@ -58,41 +60,25 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("load player", () => { Mods.Value = new[] { gameMod = new TestMod() }; - InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre); - stack.Push(new PlayerLoader(() => player = new TestPlayer())); - }); - - AddUntilStep("wait for player to become current", () => - { - if (player.IsCurrentScreen()) - { - playerMod1 = (TestMod)player.Mods.Value.Single(); - return true; - } - - return false; + stack.Push(loader = new PlayerLoader(() => player = new TestPlayer())); }); + AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen()); + AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); + AddStep("retrieve mods", () => playerMod1 = (TestMod)player.Mods.Value.Single()); AddAssert("game mods not applied", () => gameMod.Applied == false); AddAssert("player mods applied", () => playerMod1.Applied); AddStep("restart player", () => { + var lastPlayer = player; player = null; - player.Restart(); - }); - - AddUntilStep("wait for player to become current", () => - { - if (player.IsCurrentScreen()) - { - playerMod2 = (TestMod)player.Mods.Value.Single(); - return true; - } - - return false; + lastPlayer.Restart(); }); + AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); + AddStep("retrieve mods", () => playerMod2 = (TestMod)player.Mods.Value.Single()); AddAssert("game mods not applied", () => gameMod.Applied == false); AddAssert("player has different mods", () => playerMod1 != playerMod2); AddAssert("player mods applied", () => playerMod2.Applied); From 5989cf284904b5affa224656412a9b687f1f880d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Apr 2019 20:39:25 +0900 Subject: [PATCH 451/623] Revert "Increase diagnostic level" This reverts commit 7a0320d39e29c66824a04ef7b6e06a4aaa35cfe9. --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 51e946219f..4dcaa7b45e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,8 @@ clone_depth: 1 version: '{branch}-{build}' -image: Visual Studio 2017 +image: Previous Visual Studio 2017 test: off install: - cmd: git submodule update --init --recursive --depth=5 build_script: - - cmd: PowerShell -Version 2.0 .\build.ps1 -Verbosity Diagnostic + - cmd: PowerShell -Version 2.0 .\build.ps1 From 4cf234295c67e05bc598299e70212bf55f7ceb9f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Apr 2019 21:27:24 +0900 Subject: [PATCH 452/623] Fix appveyor temporarily --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 1f485485da..4dcaa7b45e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ clone_depth: 1 version: '{branch}-{build}' -image: Visual Studio 2017 +image: Previous Visual Studio 2017 test: off install: - cmd: git submodule update --init --recursive --depth=5 From 8e485f32835d6d0c72b9e04d52c3ab1b42879b8c Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 20 Apr 2019 06:41:09 +0300 Subject: [PATCH 453/623] Fix issue --- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index f3c7939a94..9e3f28918c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osuTK; using osuTK.Graphics; @@ -40,6 +41,10 @@ namespace osu.Game.Rulesets.Osu.Mods { scoreProcessor.Health.ValueChanged += health => { blinds.AnimateClosedness((float)health.NewValue); }; } + + public ScoreRank AdjustRank(ScoreRank rank) + { + } /// /// Element for the Blinds mod drawing 2 black boxes covering the whole screen which resize inside a restricted area with some leniency. From 59da78b0d4fd31a3df67b77d973fc66fbc0a94f7 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 20 Apr 2019 06:44:59 +0300 Subject: [PATCH 454/623] nothing. --- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 9e3f28918c..f3c7939a94 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -12,7 +12,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; -using osu.Game.Scoring; using osuTK; using osuTK.Graphics; @@ -41,10 +40,6 @@ namespace osu.Game.Rulesets.Osu.Mods { scoreProcessor.Health.ValueChanged += health => { blinds.AnimateClosedness((float)health.NewValue); }; } - - public ScoreRank AdjustRank(ScoreRank rank) - { - } /// /// Element for the Blinds mod drawing 2 black boxes covering the whole screen which resize inside a restricted area with some leniency. From 6f2bc943eb57c0a0ec97de5053c1d9cbd91249d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 21 Apr 2019 12:04:15 +0800 Subject: [PATCH 455/623] Fix rank display on break info display --- osu.Game/Screens/Play/Break/BreakInfoLine.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Break/BreakInfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs index 4b07405812..70e7b8f297 100644 --- a/osu.Game/Screens/Play/Break/BreakInfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -60,7 +62,13 @@ namespace osu.Game.Screens.Play.Break valueText.Text = newText; } - protected virtual string Format(T count) => count.ToString(); + protected virtual string Format(T count) + { + if (count is Enum countEnum) + return countEnum.GetDescription(); + + return count.ToString(); + } [BackgroundDependencyLoader] private void load(OsuColour colours) From 30d4dd93558f00bb806612040568e3827ec41d3e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 21 Apr 2019 21:38:12 +0900 Subject: [PATCH 456/623] Change + rank strings to be cleaner for the end-user --- osu.Game/Scoring/ScoreRank.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/ScoreRank.cs b/osu.Game/Scoring/ScoreRank.cs index 82c33748bb..a93d015f1b 100644 --- a/osu.Game/Scoring/ScoreRank.cs +++ b/osu.Game/Scoring/ScoreRank.cs @@ -25,13 +25,13 @@ namespace osu.Game.Scoring [Description(@"S")] S, - [Description(@"SPlus")] + [Description(@"S+")] SH, [Description(@"SS")] X, - [Description(@"SSPlus")] + [Description(@"SS+")] XH, } } From 73da4236391df7209c476ff769faf2a480e8f20b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 21 Apr 2019 15:58:19 +0300 Subject: [PATCH 457/623] Adjust rank --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 0fddb19a6c..4a1eba9c5e 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -95,6 +95,11 @@ namespace osu.Game.Rulesets.Scoring /// protected virtual bool DefaultFailCondition => Health.Value == Health.MinValue; + /// + /// Used by specific mods to adjust . + /// + public bool AdjustRank { get; set; } + protected ScoreProcessor() { Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); }; @@ -104,9 +109,9 @@ namespace osu.Game.Rulesets.Scoring private ScoreRank rankFrom(double acc) { if (acc == 1) - return ScoreRank.X; + return (AdjustRank ? ScoreRank.XH : ScoreRank.X); if (acc > 0.95) - return ScoreRank.S; + return (AdjustRank ? ScoreRank.SH : ScoreRank.S); if (acc > 0.9) return ScoreRank.A; if (acc > 0.8) From 4e07975e7ca88c49d4c4fa83206bc27d254e7456 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 21 Apr 2019 15:58:40 +0300 Subject: [PATCH 458/623] Ranks must be adjusted in Hidden Mod --- osu.Game/Rulesets/Mods/ModHidden.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index c7e3f0a78f..744da30f0a 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -8,10 +8,11 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModHidden : Mod, IReadFromConfig, IApplicableToDrawableHitObjects + public abstract class ModHidden : Mod, IReadFromConfig, IApplicableToDrawableHitObjects, IApplicableToScoreProcessor { public override string Name => "Hidden"; public override string Acronym => "HD"; @@ -31,6 +32,11 @@ namespace osu.Game.Rulesets.Mods foreach (var d in drawables.Skip(IncreaseFirstObjectVisibility.Value ? 1 : 0)) d.ApplyCustomUpdateState += ApplyHiddenState; } + + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + scoreProcessor.AdjustRank = true; + } protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state) { From 77614189c978e4bffa18b4dee16dbfa90351944c Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 21 Apr 2019 17:48:50 +0300 Subject: [PATCH 459/623] Make AdjustRank Bindable --- osu.Game/Rulesets/Mods/ModHidden.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 744da30f0a..4c4177a779 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mods public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { - scoreProcessor.AdjustRank = true; + scoreProcessor.AdjustRank.Value = true; } protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state) From b200142afb174e4c16a64403cb23575ab3692fd3 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 21 Apr 2019 17:55:01 +0300 Subject: [PATCH 460/623] Make AdjustRank Bindable and fix issue --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 4a1eba9c5e..617e643d41 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -98,20 +98,21 @@ namespace osu.Game.Rulesets.Scoring /// /// Used by specific mods to adjust . /// - public bool AdjustRank { get; set; } + public BindableBool AdjustRank = new BindableBool(); protected ScoreProcessor() { Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); }; Accuracy.ValueChanged += delegate { Rank.Value = rankFrom(Accuracy.Value); }; + AdjustRank.ValueChanged += delegate { Rank.Value = rankFrom(Accuracy.Value); }; // Update rank immediately if AdjustRank was changed } private ScoreRank rankFrom(double acc) { if (acc == 1) - return (AdjustRank ? ScoreRank.XH : ScoreRank.X); + return (AdjustRank.Value ? ScoreRank.XH : ScoreRank.X); if (acc > 0.95) - return (AdjustRank ? ScoreRank.SH : ScoreRank.S); + return (AdjustRank.Value ? ScoreRank.SH : ScoreRank.S); if (acc > 0.9) return ScoreRank.A; if (acc > 0.8) @@ -128,6 +129,7 @@ namespace osu.Game.Rulesets.Scoring /// Whether to store the current state of the for future use. protected virtual void Reset(bool storeResults) { + AdjustRank.Value = false; TotalScore.Value = 0; Accuracy.Value = 1; Health.Value = 1; From c5bf01cc5ccaf8a53f57fce21cc1fb16cf6cfc0b Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 21 Apr 2019 18:05:52 +0300 Subject: [PATCH 461/623] Fix issue --- osu.Game/Rulesets/Mods/ModHidden.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 4c4177a779..b36b2362ca 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mods foreach (var d in drawables.Skip(IncreaseFirstObjectVisibility.Value ? 1 : 0)) d.ApplyCustomUpdateState += ApplyHiddenState; } - + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { scoreProcessor.AdjustRank.Value = true; From cff319e0d8cd22d12f7e44a4754cca2a2f8051ec Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 21 Apr 2019 18:12:50 +0300 Subject: [PATCH 462/623] Fix issue --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 617e643d41..e60ca70969 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Scoring protected virtual bool DefaultFailCondition => Health.Value == Health.MinValue; /// - /// Used by specific mods to adjust . + /// Used by specific mods to adjust . /// public BindableBool AdjustRank = new BindableBool(); From ae51a9e45122e453dcfc49bb237c55882854b140 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Apr 2019 09:57:33 +0900 Subject: [PATCH 463/623] Fix drawable rank texture lookup --- osu.Game/Online/Leaderboards/DrawableRank.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/DrawableRank.cs b/osu.Game/Online/Leaderboards/DrawableRank.cs index 9155df69b4..5224150181 100644 --- a/osu.Game/Online/Leaderboards/DrawableRank.cs +++ b/osu.Game/Online/Leaderboards/DrawableRank.cs @@ -43,7 +43,21 @@ namespace osu.Game.Online.Leaderboards private void updateTexture() { - rankSprite.Texture = textures.Get($@"Grades/{Rank.GetDescription()}"); + string textureName; + switch (Rank) + { + default: + textureName = Rank.GetDescription(); + break; + case ScoreRank.SH: + textureName = "SPlus"; + break; + case ScoreRank.XH: + textureName = "SSPlus"; + break; + } + + rankSprite.Texture = textures.Get($@"Grades/{textureName}"); } public void UpdateRank(ScoreRank newRank) From 732c38fa794a81ab3c2b2e8be8a9abec8f189f89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Apr 2019 10:45:56 +0900 Subject: [PATCH 464/623] Update framework again --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2b7ded6d8c..a45cac2cb6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 958191d708..70edc01821 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 8ffb2f4224d5ba340fa6e42dd3518edbd938df2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Apr 2019 10:55:01 +0900 Subject: [PATCH 465/623] Remove black box blocking some visual tests from being visible --- osu.Game/Tests/Visual/AllPlayersTestCase.cs | 9 --------- osu.Game/Tests/Visual/PlayerTestCase.cs | 15 --------------- 2 files changed, 24 deletions(-) diff --git a/osu.Game/Tests/Visual/AllPlayersTestCase.cs b/osu.Game/Tests/Visual/AllPlayersTestCase.cs index af190ec7d5..3e1f408a16 100644 --- a/osu.Game/Tests/Visual/AllPlayersTestCase.cs +++ b/osu.Game/Tests/Visual/AllPlayersTestCase.cs @@ -3,7 +3,6 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -11,7 +10,6 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; -using osuTK.Graphics; namespace osu.Game.Tests.Visual { @@ -26,13 +24,6 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - Add(new Box - { - RelativeSizeAxes = Framework.Graphics.Axes.Both, - Colour = Color4.Black, - Depth = int.MaxValue - }); - foreach (var r in rulesets.AvailableRulesets) { Player p = null; diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index 719b1d6892..d308b86b5a 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -2,16 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; -using osuTK.Graphics; namespace osu.Game.Tests.Visual { @@ -26,17 +22,6 @@ namespace osu.Game.Tests.Visual this.ruleset = ruleset; } - [BackgroundDependencyLoader] - private void load() - { - Add(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Depth = int.MaxValue - }); - } - [SetUpSteps] public void SetUpSteps() { From 26f0b2a4fe65342757a52476367b6ae432b10b40 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 22 Apr 2019 06:12:41 +0300 Subject: [PATCH 466/623] Remove bindable Bad usage --- osu.Game/Rulesets/Mods/ModHidden.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index b36b2362ca..389edb5a35 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mods public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { - scoreProcessor.AdjustRank.Value = true; + scoreProcessor.AdjustRank = true; } protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state) From de542ca20251951df7dea4cd449018aa03b2c29d Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 22 Apr 2019 06:14:14 +0300 Subject: [PATCH 467/623] Remove bindable and use property --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index e60ca70969..c37b5d189f 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -95,24 +95,29 @@ namespace osu.Game.Rulesets.Scoring /// protected virtual bool DefaultFailCondition => Health.Value == Health.MinValue; + private bool adjustRank = false; + /// /// Used by specific mods to adjust . /// - public BindableBool AdjustRank = new BindableBool(); + public bool AdjustRank + { + get { return adjustRank; } + set { adjustRank = value; Rank.Value = rankFrom(Accuracy.Value); } // Update rank immediately if AdjustRank was changed + } protected ScoreProcessor() { Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); }; Accuracy.ValueChanged += delegate { Rank.Value = rankFrom(Accuracy.Value); }; - AdjustRank.ValueChanged += delegate { Rank.Value = rankFrom(Accuracy.Value); }; // Update rank immediately if AdjustRank was changed } private ScoreRank rankFrom(double acc) { if (acc == 1) - return (AdjustRank.Value ? ScoreRank.XH : ScoreRank.X); + return (adjustRank ? ScoreRank.XH : ScoreRank.X); if (acc > 0.95) - return (AdjustRank.Value ? ScoreRank.SH : ScoreRank.S); + return (adjustRank ? ScoreRank.SH : ScoreRank.S); if (acc > 0.9) return ScoreRank.A; if (acc > 0.8) @@ -129,7 +134,6 @@ namespace osu.Game.Rulesets.Scoring /// Whether to store the current state of the for future use. protected virtual void Reset(bool storeResults) { - AdjustRank.Value = false; TotalScore.Value = 0; Accuracy.Value = 1; Health.Value = 1; @@ -137,6 +141,7 @@ namespace osu.Game.Rulesets.Scoring Rank.Value = ScoreRank.X; HighestCombo.Value = 0; + AdjustRank = false; HasFailed = false; } From c784bc4418c9908099a1ecc08fb6194140743224 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 22 Apr 2019 06:15:37 +0300 Subject: [PATCH 468/623] Remove whitespaces --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index c37b5d189f..e563d3a848 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Scoring protected virtual bool DefaultFailCondition => Health.Value == Health.MinValue; private bool adjustRank = false; - + /// /// Used by specific mods to adjust . /// From a47f5040af2b157f58aeabc6ac9bbae97d09b0e2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Apr 2019 13:58:05 +0900 Subject: [PATCH 469/623] Improve waveform graph testing --- .../Visual/Editor/TestCaseWaveform.cs | 79 +++++++++++++------ 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestCaseWaveform.cs b/osu.Game.Tests/Visual/Editor/TestCaseWaveform.cs index c35e8741c1..ce6ca08a61 100644 --- a/osu.Game.Tests/Visual/Editor/TestCaseWaveform.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseWaveform.cs @@ -3,12 +3,13 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; -using osuTK; using osuTK.Graphics; namespace osu.Game.Tests.Visual.Editor @@ -16,35 +17,38 @@ namespace osu.Game.Tests.Visual.Editor [TestFixture] public class TestCaseWaveform : OsuTestCase { + private WorkingBeatmap waveformBeatmap; + [BackgroundDependencyLoader] private void load() { - Beatmap.Value = new WaveformTestBeatmap(); + waveformBeatmap = new WaveformTestBeatmap(); + } - FillFlowContainer flow; - Child = flow = new FillFlowContainer + [TestCase(1f)] + [TestCase(1f / 2)] + [TestCase(1f / 4)] + [TestCase(1f / 8)] + [TestCase(1f / 16)] + [TestCase(0f)] + public void TestResolution(float resolution) + { + TestWaveformGraph graph = null; + + AddStep("add graph", () => { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - }; - - for (int i = 1; i <= 16; i *= 2) - { - var newDisplay = new WaveformGraph - { - RelativeSizeAxes = Axes.Both, - Resolution = 1f / i, - Waveform = Beatmap.Value.Waveform, - }; - - flow.Add(new Container + Child = new Container { RelativeSizeAxes = Axes.X, Height = 100, Children = new Drawable[] { - newDisplay, + graph = new TestWaveformGraph + { + RelativeSizeAxes = Axes.Both, + Resolution = resolution, + Waveform = waveformBeatmap.Waveform, + }, new Container { Anchor = Anchor.Centre, @@ -62,13 +66,42 @@ namespace osu.Game.Tests.Visual.Editor { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = $"Resolution: {1f / i:0.00}" + Text = $"Resolution: {resolution:0.00}" } } } } - }); - } + }; + }); + + AddUntilStep("wait for load", () => graph.ResampledWaveform != null); + } + + [Test] + public void TestDefaultBeatmap() + { + TestWaveformGraph graph = null; + + AddStep("add graph", () => + { + Child = new Container + { + RelativeSizeAxes = Axes.X, + Height = 100, + Child = graph = new TestWaveformGraph + { + RelativeSizeAxes = Axes.Both, + Waveform = new DummyWorkingBeatmap().Waveform, + }, + }; + }); + + AddUntilStep("wait for load", () => graph.ResampledWaveform != null); + } + + public class TestWaveformGraph : WaveformGraph + { + public new Waveform ResampledWaveform => base.ResampledWaveform; } } } From 7a385e56ecd0089689ce63d145eb5fbd44a8f5da Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 22 Apr 2019 09:58:29 +0300 Subject: [PATCH 470/623] Fix AppVeyor Errors --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index e563d3a848..99178409fb 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -95,15 +95,20 @@ namespace osu.Game.Rulesets.Scoring /// protected virtual bool DefaultFailCondition => Health.Value == Health.MinValue; - private bool adjustRank = false; + private bool adjustRank; /// /// Used by specific mods to adjust . /// public bool AdjustRank { - get { return adjustRank; } - set { adjustRank = value; Rank.Value = rankFrom(Accuracy.Value); } // Update rank immediately if AdjustRank was changed + get => return adjustRank; + + set + { + adjustRank = value; + Rank.Value = rankFrom(Accuracy.Value); // Update rank immediately if AdjustRank was changed + } } protected ScoreProcessor() From 3c252d79ead8fc1e7a13e8d12b3210bce989d402 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 22 Apr 2019 15:59:47 +0900 Subject: [PATCH 471/623] Use var, rework dim application logic --- .../Mods/OsuModFlashlight.cs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 5d136e4cbe..b2bc5f4320 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; @@ -27,9 +28,9 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableHitObjects(IEnumerable drawables) { - foreach (DrawableSlider drawable in drawables.OfType()) + foreach (var s in drawables.OfType()) { - drawable.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; + s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; } } @@ -44,13 +45,28 @@ namespace osu.Game.Rulesets.Osu.Mods public void OnSliderTrackingChange(ValueChangedEvent e) { + // If any sliders are in a tracking state, apply a dim to the entire playfield over a brief duration. if (e.NewValue) + { trackingSliders++; + // This check being here ensures we're only applying a dim if and only if a slider begins tracking. + if (trackingSliders == 1) + { + this.TransformTo(nameof(FlashlightDim), 0.8f, 50); + } + } else + { trackingSliders--; - // If there are any sliders in a tracking state, apply a dim to the entire playfield over a brief duration. - this.TransformTo(nameof(FlashlightDim), trackingSliders > 0 ? 0.8f : 0.0f, 50); + if (trackingSliders == 0) + { + this.TransformTo(nameof(FlashlightDim), 0.0f, 50); + } + } + + if (trackingSliders < 0) + throw new InvalidOperationException($"The number of {nameof(trackingSliders)} cannot be below 0."); } protected override bool OnMouseMove(MouseMoveEvent e) From 64429aa96877c96ca67f53d6a95b0b98085fe425 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 22 Apr 2019 09:59:51 +0300 Subject: [PATCH 472/623] Fix AppVeyor Errors --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 99178409fb..fc7e8746b7 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Scoring public bool AdjustRank { get => return adjustRank; - + set { adjustRank = value; From 903093503041d6fd3f2061615cbc2cda56239b9d Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 22 Apr 2019 10:07:45 +0300 Subject: [PATCH 473/623] Fix AppVeyor Errors --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index fc7e8746b7..b3e1beed04 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -104,8 +104,8 @@ namespace osu.Game.Rulesets.Scoring { get => return adjustRank; - set - { + set + { adjustRank = value; Rank.Value = rankFrom(Accuracy.Value); // Update rank immediately if AdjustRank was changed } From cfb3c38c3a086dd1d892fe471ca1c80da1e1f710 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Mon, 22 Apr 2019 10:14:19 +0300 Subject: [PATCH 474/623] Fix. --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index b3e1beed04..b1cd78dde6 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Scoring /// public bool AdjustRank { - get => return adjustRank; + get => adjustRank; set { From 3fbbb7dcf9901fcd5595d5e1a88d22ce667aa94e Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 22 Apr 2019 16:39:28 +0900 Subject: [PATCH 475/623] Move intiial fade outside of absolute sequence --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index c0816b2457..c3ac00f3ad 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -95,10 +95,10 @@ namespace osu.Game.Rulesets.Mods Combo.ValueChanged += OnComboChange; + this.FadeInFromZero(FLASHLIGHT_FADE_DURATION); + using (BeginAbsoluteSequence(0)) { - this.FadeInFromZero(FLASHLIGHT_FADE_DURATION); - foreach (var breakPeriod in Breaks) { if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; From fbb4e9df044b2e6d3d911de3846cc6140dd7ef6f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Apr 2019 16:51:38 +0900 Subject: [PATCH 476/623] Implement hp at base ScoreProcessor --- osu.Game/Rulesets/Judgements/Judgement.cs | 5 +++++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index e14eedd3dc..f07f76a2b8 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -31,6 +31,11 @@ namespace osu.Game.Rulesets.Judgements /// public int MaxNumericResult => NumericResultFor(MaxResult); + /// + /// The health increase for the maximum achievable result. + /// + public double MaxHealthIncrease => HealthIncreaseFor(MaxResult); + /// /// Retrieves the numeric score representation of a . /// diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 0fddb19a6c..1a682080bf 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -206,6 +206,9 @@ namespace osu.Game.Rulesets.Scoring private double baseScore; private double bonusScore; + private double rollingHp; + private double rollingMaxHp; + protected ScoreProcessor() { } @@ -332,6 +335,9 @@ namespace osu.Game.Rulesets.Scoring baseScore += result.Judgement.NumericResultFor(result); rollingMaxBaseScore += result.Judgement.MaxNumericResult; } + + rollingHp += HpFactorFor(result.Judgement, result.Type) * result.Judgement.HealthIncreaseFor(result); + rollingMaxHp += HpFactorFor(result.Judgement, result.Judgement.MaxResult) * result.Judgement.MaxHealthIncrease; } /// @@ -356,8 +362,13 @@ namespace osu.Game.Rulesets.Scoring baseScore -= result.Judgement.NumericResultFor(result); rollingMaxBaseScore -= result.Judgement.MaxNumericResult; } + + rollingHp -= HpFactorFor(result.Judgement, result.Type) * result.Judgement.HealthIncreaseFor(result); + rollingMaxHp -= HpFactorFor(result.Judgement, result.Judgement.MaxResult) * result.Judgement.MaxHealthIncrease; } + protected virtual double HpFactorFor(Judgement judgement, HitResult result) => 1; + private void updateScore() { if (rollingMaxBaseScore != 0) From d7919544fe8e5e6f78fb04834c44fbb7ff721b89 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Apr 2019 16:59:14 +0900 Subject: [PATCH 477/623] Implement approximate hp increase for catch --- .../Judgements/CatchBananaJudgement.cs | 2 +- .../Judgements/CatchDropletJudgement.cs | 4 ++-- .../Judgements/CatchJudgement.cs | 4 ++-- .../Judgements/CatchTinyDropletJudgement.cs | 2 +- .../Scoring/CatchScoreProcessor.cs | 20 ++++++++----------- 5 files changed, 14 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs index 4e64753a65..31f825c3ef 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Judgements default: return 0; case HitResult.Perfect: - return 8; + return 0.08; } } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs index 2598dee156..f03897d611 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs @@ -23,9 +23,9 @@ namespace osu.Game.Rulesets.Catch.Judgements switch (result) { default: - return 0; + return base.HealthIncreaseFor(result); case HitResult.Perfect: - return 7; + return 0.07; } } } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs index 5d7ef04dd2..abc38fe258 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs @@ -27,9 +27,9 @@ namespace osu.Game.Rulesets.Catch.Judgements switch (result) { default: - return 0; + return -0.02; case HitResult.Perfect: - return 10.2; + return 0.01; } } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs index b8c51b7b60..bd1a74bc14 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.Judgements default: return 0; case HitResult.Perfect: - return 4; + return 0.004; } } } diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index af614f95d0..40585f2054 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Judgements; @@ -27,20 +26,17 @@ namespace osu.Game.Rulesets.Catch.Scoring hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate; } - private const double harshness = 0.01; - - protected override void ApplyResult(JudgementResult result) + protected override double HpFactorFor(Judgement judgement, HitResult result) { - base.ApplyResult(result); - - if (result.Type == HitResult.Miss) + switch (result) { - if (!result.Judgement.IsBonus) - Health.Value -= hpDrainRate * (harshness * 2); - return; + case HitResult.Miss when judgement.IsBonus: + return 0; + case HitResult.Miss: + return hpDrainRate; + default: + return 10 - hpDrainRate; // Award less HP as drain rate is increased } - - Health.Value += Math.Max(result.Judgement.HealthIncreaseFor(result) - hpDrainRate, 0) * harshness; } public override HitWindows CreateHitWindows() => new CatchHitWindows(); From 4c5f41e40f1646df96c8fe1b87cafbfe5e775749 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Apr 2019 17:04:18 +0900 Subject: [PATCH 478/623] Implement hp increase for mania --- .../Judgements/HoldNoteTickJudgement.cs | 11 ++++++ .../Judgements/ManiaJudgement.cs | 21 ++++++++++ .../Scoring/ManiaScoreProcessor.cs | 38 +------------------ 3 files changed, 34 insertions(+), 36 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs index 015eb1310e..48c2eb547b 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs @@ -10,5 +10,16 @@ namespace osu.Game.Rulesets.Mania.Judgements public override bool AffectsCombo => false; protected override int NumericResultFor(HitResult result) => 20; + + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return 0; + default: + return 0.040; + } + } } } diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs index b6fb37f054..0548dc9eed 100644 --- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs @@ -25,5 +25,26 @@ namespace osu.Game.Rulesets.Mania.Judgements return 300; } } + + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return -0.125; + case HitResult.Meh: + return 0.005; + case HitResult.Ok: + return 0.010; + case HitResult.Good: + return 0.035; + case HitResult.Great: + return 0.055; + case HitResult.Perfect: + return 0.065; + default: + return 0; + } + } } } diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 5c914d8eac..75a73614f0 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -122,42 +122,8 @@ namespace osu.Game.Rulesets.Mania.Scoring } } - protected override void ApplyResult(JudgementResult result) - { - base.ApplyResult(result); - - bool isTick = result.Judgement is HoldNoteTickJudgement; - - if (isTick) - { - if (result.IsHit) - Health.Value += hpMultiplier * hp_increase_tick; - } - else - { - switch (result.Type) - { - case HitResult.Miss: - Health.Value += hpMissMultiplier * hp_increase_miss; - break; - case HitResult.Meh: - Health.Value += hpMultiplier * hp_increase_bad; - break; - case HitResult.Ok: - Health.Value += hpMultiplier * hp_increase_ok; - break; - case HitResult.Good: - Health.Value += hpMultiplier * hp_increase_good; - break; - case HitResult.Great: - Health.Value += hpMultiplier * hp_increase_great; - break; - case HitResult.Perfect: - Health.Value += hpMultiplier * hp_increase_perfect; - break; - } - } - } + protected override double HpFactorFor(Judgement judgement, HitResult result) + => result == HitResult.Miss ? hpMissMultiplier : hpMultiplier; public override HitWindows CreateHitWindows() => new ManiaHitWindows(); } From b59f23d0944a5aa512c2bf3096d4ef7390f32e72 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Apr 2019 17:04:46 +0900 Subject: [PATCH 479/623] Implement hp increase for taiko --- .../Scoring/TaikoScoreProcessor.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 442cca49f8..a0055a93aa 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -46,19 +46,8 @@ namespace osu.Game.Rulesets.Taiko.Scoring hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120); } - protected override void ApplyResult(JudgementResult result) - { - base.ApplyResult(result); - - double hpIncrease = result.Judgement.HealthIncreaseFor(result); - - if (result.Type == HitResult.Miss) - hpIncrease *= hpMissMultiplier; - else - hpIncrease *= hpMultiplier; - - Health.Value += hpIncrease; - } + protected override double HpFactorFor(Judgement judgement, HitResult result) + => result == HitResult.Miss ? hpMissMultiplier : hpMultiplier; protected override void Reset(bool storeResults) { From 034643b8356bf2325d207ec0aab91179f402f3c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Apr 2019 17:06:01 +0900 Subject: [PATCH 480/623] Fix pause tests --- osu.Game/Rulesets/UI/Playfield.cs | 5 +++++ osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 13689153f0..a99c16a610 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -68,7 +68,12 @@ namespace osu.Game.Rulesets.UI { Cursor = CreateCursor(); if (Cursor != null) + { + // initial showing of the cursor will be handed by MenuCursorContainer (via DrawableRuleset's IProvideCursor implementation). + Cursor.Hide(); + AddInternal(Cursor); + } } /// diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 29974b728e..3654dc679c 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -123,7 +123,7 @@ namespace osu.Game.Screens.Play { // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time // This accounts for the audio clock source potentially taking time to enter a completely stopped state - adjustableClock.Seek(adjustableClock.CurrentTime); + adjustableClock.Seek(GameplayClock.CurrentTime); adjustableClock.Start(); IsPaused.Value = false; } From b3c496d72cb6d3a381098f357edcfd940c12a8ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Apr 2019 17:06:40 +0900 Subject: [PATCH 481/623] Remove delay on entering player --- osu.Game/Screens/Play/GameplayClockContainer.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 3654dc679c..2d1ba3f2ab 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -110,11 +110,8 @@ namespace osu.Game.Screens.Play adjustableClock.ChangeSource(sourceClock); updateRate(); - this.Delay(750).Schedule(() => - { - if (!IsPaused.Value) - Start(); - }); + if (!IsPaused.Value) + Start(); }); }); } From 78c844e25930af377c3c28162c58551d03c374b8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Apr 2019 17:24:22 +0900 Subject: [PATCH 482/623] Make catch provide some HP at DrainRate=10 --- osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 40585f2054..13c5028b23 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Scoring case HitResult.Miss: return hpDrainRate; default: - return 10 - hpDrainRate; // Award less HP as drain rate is increased + return 10.2 - hpDrainRate; // Award less HP as drain rate is increased } } From 144e6012dc333153c7e41ad37df544a23ea4b827 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Apr 2019 17:24:42 +0900 Subject: [PATCH 483/623] Implement hp increase for osu! --- .../Judgements/OsuJudgement.cs | 15 +++++++++++ .../Scoring/OsuScoreProcessor.cs | 25 ++++++++++--------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs index 81fedf9f4a..7a98c5003d 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs @@ -24,5 +24,20 @@ namespace osu.Game.Rulesets.Osu.Judgements return 300; } } + + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return -0.02; + case HitResult.Meh: + case HitResult.Good: + case HitResult.Great: + return 0.01; + default: + return 0; + } + } } } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 2c8bf11016..a4975ef3b3 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -47,28 +47,29 @@ namespace osu.Game.Rulesets.Osu.Scoring if (result.Type != HitResult.None) comboResultCounts[osuResult.ComboType] = comboResultCounts.GetOrDefault(osuResult.ComboType) + 1; + } - switch (result.Type) + protected override double HpFactorFor(Judgement judgement, HitResult result) + { + switch (result) { case HitResult.Great: - Health.Value += (10.2 - hpDrainRate) * harshness; - break; + return 10.2 - hpDrainRate; case HitResult.Good: - Health.Value += (8 - hpDrainRate) * harshness; - break; + return 8 - hpDrainRate; case HitResult.Meh: - Health.Value += (4 - hpDrainRate) * harshness; - break; + return 4 - hpDrainRate; - /*case HitResult.SliderTick: - Health.Value += Math.Max(7 - hpDrainRate, 0) * 0.01; - break;*/ + // case HitResult.SliderTick: + // return Math.Max(7 - hpDrainRate, 0) * 0.01; case HitResult.Miss: - Health.Value -= hpDrainRate * (harshness * 2); - break; + return hpDrainRate; + + default: + return 0; } } From 4edb17a88a9ac82ac06fb2c7a172e4ad4bc0dc2c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Apr 2019 17:51:43 +0900 Subject: [PATCH 484/623] Make hp work + cleanup --- .../Scoring/CatchScoreProcessor.cs | 6 +-- .../Scoring/ManiaScoreProcessor.cs | 44 +------------------ .../Scoring/OsuScoreProcessor.cs | 6 +-- .../Scoring/TaikoScoreProcessor.cs | 4 +- .../Rulesets/Judgements/JudgementResult.cs | 5 +++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 13 ++---- 6 files changed, 17 insertions(+), 61 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 13c5028b23..778a7426aa 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -26,12 +26,10 @@ namespace osu.Game.Rulesets.Catch.Scoring hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate; } - protected override double HpFactorFor(Judgement judgement, HitResult result) + protected override double HpFactorFor(JudgementResult result) { - switch (result) + switch (result.Type) { - case HitResult.Miss when judgement.IsBonus: - return 0; case HitResult.Miss: return hpDrainRate; default: diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 75a73614f0..ee4618e5c2 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -3,7 +3,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -28,36 +27,6 @@ namespace osu.Game.Rulesets.Mania.Scoring /// private const double hp_multiplier_max = 1; - /// - /// The default BAD hit HP increase. - /// - private const double hp_increase_bad = 0.005; - - /// - /// The default OK hit HP increase. - /// - private const double hp_increase_ok = 0.010; - - /// - /// The default GOOD hit HP increase. - /// - private const double hp_increase_good = 0.035; - - /// - /// The default tick hit HP increase. - /// - private const double hp_increase_tick = 0.040; - - /// - /// The default GREAT hit HP increase. - /// - private const double hp_increase_great = 0.055; - - /// - /// The default PERFECT hit HP increase. - /// - private const double hp_increase_perfect = 0.065; - /// /// The MISS HP multiplier at OD = 0. /// @@ -73,11 +42,6 @@ namespace osu.Game.Rulesets.Mania.Scoring /// private const double hp_multiplier_miss_max = 1; - /// - /// The default MISS HP increase. - /// - private const double hp_increase_miss = -0.125; - /// /// The MISS HP multiplier. This is multiplied to the miss hp increase. /// @@ -88,10 +52,6 @@ namespace osu.Game.Rulesets.Mania.Scoring /// private double hpMultiplier = 1; - public ManiaScoreProcessor() - { - } - public ManiaScoreProcessor(DrawableRuleset drawableRuleset) : base(drawableRuleset) { @@ -122,8 +82,8 @@ namespace osu.Game.Rulesets.Mania.Scoring } } - protected override double HpFactorFor(Judgement judgement, HitResult result) - => result == HitResult.Miss ? hpMissMultiplier : hpMultiplier; + protected override double HpFactorFor(JudgementResult result) + => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier; public override HitWindows CreateHitWindows() => new ManiaHitWindows(); } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index a4975ef3b3..2162663539 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Osu.Scoring comboResultCounts.Clear(); } - private const double harshness = 0.01; - protected override void ApplyResult(JudgementResult result) { base.ApplyResult(result); @@ -49,9 +47,9 @@ namespace osu.Game.Rulesets.Osu.Scoring comboResultCounts[osuResult.ComboType] = comboResultCounts.GetOrDefault(osuResult.ComboType) + 1; } - protected override double HpFactorFor(Judgement judgement, HitResult result) + protected override double HpFactorFor(JudgementResult result) { - switch (result) + switch (result.Type) { case HitResult.Great: return 10.2 - hpDrainRate; diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index a0055a93aa..b2b866db4b 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -46,8 +46,8 @@ namespace osu.Game.Rulesets.Taiko.Scoring hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120); } - protected override double HpFactorFor(Judgement judgement, HitResult result) - => result == HitResult.Miss ? hpMissMultiplier : hpMultiplier; + protected override double HpFactorFor(JudgementResult result) + => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier; protected override void Reset(bool storeResults) { diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index d4ef5750b1..195fe316ac 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -37,6 +37,11 @@ namespace osu.Game.Rulesets.Judgements /// public int HighestComboAtJudgement { get; internal set; } + /// + /// The health prior to this occurring. + /// + public double HealthAtJudgement { get; internal set; } + /// /// Whether a miss or hit occurred. /// diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 1a682080bf..429aa620f6 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -206,9 +206,6 @@ namespace osu.Game.Rulesets.Scoring private double baseScore; private double bonusScore; - private double rollingHp; - private double rollingMaxHp; - protected ScoreProcessor() { } @@ -304,6 +301,7 @@ namespace osu.Game.Rulesets.Scoring { result.ComboAtJudgement = Combo.Value; result.HighestComboAtJudgement = HighestCombo.Value; + result.HealthAtJudgement = Health.Value; JudgedHits++; @@ -336,8 +334,7 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore += result.Judgement.MaxNumericResult; } - rollingHp += HpFactorFor(result.Judgement, result.Type) * result.Judgement.HealthIncreaseFor(result); - rollingMaxHp += HpFactorFor(result.Judgement, result.Judgement.MaxResult) * result.Judgement.MaxHealthIncrease; + Health.Value += HpFactorFor(result) * result.Judgement.HealthIncreaseFor(result); } /// @@ -349,6 +346,7 @@ namespace osu.Game.Rulesets.Scoring { Combo.Value = result.ComboAtJudgement; HighestCombo.Value = result.HighestComboAtJudgement; + Health.Value = result.HealthAtJudgement; JudgedHits--; @@ -362,12 +360,9 @@ namespace osu.Game.Rulesets.Scoring baseScore -= result.Judgement.NumericResultFor(result); rollingMaxBaseScore -= result.Judgement.MaxNumericResult; } - - rollingHp -= HpFactorFor(result.Judgement, result.Type) * result.Judgement.HealthIncreaseFor(result); - rollingMaxHp -= HpFactorFor(result.Judgement, result.Judgement.MaxResult) * result.Judgement.MaxHealthIncrease; } - protected virtual double HpFactorFor(Judgement judgement, HitResult result) => 1; + protected virtual double HpFactorFor(JudgementResult result) => 1; private void updateScore() { From 910b9df2d54b93e28c6e729807337d9a14f161bf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Apr 2019 18:02:57 +0900 Subject: [PATCH 485/623] Fix catch awarding too much hp --- osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs | 2 +- osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs index 31f825c3ef..5835e746b9 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Judgements default: return 0; case HitResult.Perfect: - return 0.08; + return 0.008; } } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs index f03897d611..679691fcd6 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.Judgements default: return base.HealthIncreaseFor(result); case HitResult.Perfect: - return 0.07; + return 0.007; } } } From aeae759fcd5b9949815b2bba02a799e9d6c277e8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Apr 2019 18:08:15 +0900 Subject: [PATCH 486/623] Rename method + add xmldoc --- osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs | 2 +- osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs | 2 +- osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs | 2 +- osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 9 +++++++-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 778a7426aa..0d0ca6506c 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Catch.Scoring hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate; } - protected override double HpFactorFor(JudgementResult result) + protected override double HealthAdjustmentFactorFor(JudgementResult result) { switch (result.Type) { diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index ee4618e5c2..5caf08fb1e 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Mania.Scoring } } - protected override double HpFactorFor(JudgementResult result) + protected override double HealthAdjustmentFactorFor(JudgementResult result) => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier; public override HitWindows CreateHitWindows() => new ManiaHitWindows(); diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 2162663539..cf0565c6da 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Scoring comboResultCounts[osuResult.ComboType] = comboResultCounts.GetOrDefault(osuResult.ComboType) + 1; } - protected override double HpFactorFor(JudgementResult result) + protected override double HealthAdjustmentFactorFor(JudgementResult result) { switch (result.Type) { diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index b2b866db4b..68ddf2db19 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120); } - protected override double HpFactorFor(JudgementResult result) + protected override double HealthAdjustmentFactorFor(JudgementResult result) => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier; protected override void Reset(bool storeResults) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 429aa620f6..ba71e1e9b2 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -334,7 +334,7 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore += result.Judgement.MaxNumericResult; } - Health.Value += HpFactorFor(result) * result.Judgement.HealthIncreaseFor(result); + Health.Value += HealthAdjustmentFactorFor(result) * result.Judgement.HealthIncreaseFor(result); } /// @@ -362,7 +362,12 @@ namespace osu.Game.Rulesets.Scoring } } - protected virtual double HpFactorFor(JudgementResult result) => 1; + /// + /// An adjustment factor which is multiplied into the health increase provided by a . + /// + /// The for which the adjustment should apply. + /// The adjustment factor. + protected virtual double HealthAdjustmentFactorFor(JudgementResult result) => 1; private void updateScore() { From 908eee9942653eabd72e68a3a60b93f7ec35cccd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Apr 2019 17:06:01 +0900 Subject: [PATCH 487/623] Fix pause tests --- osu.Game/Rulesets/UI/Playfield.cs | 5 +++++ osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 13689153f0..a99c16a610 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -68,7 +68,12 @@ namespace osu.Game.Rulesets.UI { Cursor = CreateCursor(); if (Cursor != null) + { + // initial showing of the cursor will be handed by MenuCursorContainer (via DrawableRuleset's IProvideCursor implementation). + Cursor.Hide(); + AddInternal(Cursor); + } } /// diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 29974b728e..3654dc679c 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -123,7 +123,7 @@ namespace osu.Game.Screens.Play { // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time // This accounts for the audio clock source potentially taking time to enter a completely stopped state - adjustableClock.Seek(adjustableClock.CurrentTime); + adjustableClock.Seek(GameplayClock.CurrentTime); adjustableClock.Start(); IsPaused.Value = false; } From 55c5ef898d2cba265b877bf1d4a5195a1b14c6bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Apr 2019 17:06:40 +0900 Subject: [PATCH 488/623] Remove delay on entering player --- osu.Game/Screens/Play/GameplayClockContainer.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 3654dc679c..2d1ba3f2ab 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -110,11 +110,8 @@ namespace osu.Game.Screens.Play adjustableClock.ChangeSource(sourceClock); updateRate(); - this.Delay(750).Schedule(() => - { - if (!IsPaused.Value) - Start(); - }); + if (!IsPaused.Value) + Start(); }); }); } From 6856571f17d06cd8080b19d967846347a54565af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Apr 2019 18:45:58 +0900 Subject: [PATCH 489/623] Fix incorrect seek target --- osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 2d1ba3f2ab..6b12430552 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -120,7 +120,7 @@ namespace osu.Game.Screens.Play { // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time // This accounts for the audio clock source potentially taking time to enter a completely stopped state - adjustableClock.Seek(GameplayClock.CurrentTime); + Seek(GameplayClock.CurrentTime); adjustableClock.Start(); IsPaused.Value = false; } From 48e82d4b1c742dff5bbe101ebf6ffefcfa044809 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Apr 2019 18:47:28 +0900 Subject: [PATCH 490/623] Fix hold for menu button's icon being incorrect --- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 6883f246e4..c0ee5e6142 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(15), - Icon = FontAwesome.Solid.TimesCircle + Icon = FontAwesome.Solid.Times }, } }; From 1f18c08cfc15a8184306ecce7915afff16c2061b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Apr 2019 19:17:58 +0900 Subject: [PATCH 491/623] Update framework --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a45cac2cb6..303587bf88 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 70edc01821..e45a76dbac 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 414d55548439b4a1e5e16f1da7830bd5aedc3296 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Apr 2019 19:24:57 +0900 Subject: [PATCH 492/623] Fix possible exit from non-current screen --- osu.Game/Screens/Multi/Multiplayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 5e019a7b3a..1183396369 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -276,7 +276,7 @@ namespace osu.Game.Screens.Multi updatePollingRate(isIdle.Value); - if (screenStack.CurrentScreen == null) + if (screenStack.CurrentScreen == null && this.IsCurrentScreen()) this.Exit(); } From 52336e9062affa6742ad49c7802f31153364e04a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Apr 2019 11:52:42 +0900 Subject: [PATCH 493/623] Fix pause tests --- osu.Game/Tests/Visual/PlayerTestCase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index d308b86b5a..fca4fccae0 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual public void SetUpSteps() { AddStep(ruleset.RulesetInfo.Name, loadPlayer); - AddUntilStep(() => Player.IsLoaded, "player loaded"); + AddUntilStep(() => Player.IsLoaded && Player.Alpha == 1, "player loaded"); } protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); From 6add3952883f7d8b40f7e2cfd3ec0061cffa0f9b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 23 Apr 2019 13:32:44 +0900 Subject: [PATCH 494/623] Fix gameplay cursor being hidden in tests/replays --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 3 +++ osu.Game/Tests/Visual/ManualInputManagerTestCase.cs | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 2866e81682..302380744a 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -225,6 +225,9 @@ namespace osu.Game.Rulesets.UI if (replayInputManager.ReplayInputHandler != null) replayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; + + if (!ProvidingUserCursor) + Playfield.Cursor?.Show(); } /// diff --git a/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs b/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs index f14ac833e4..9b1ccdd6a4 100644 --- a/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs +++ b/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs @@ -4,17 +4,24 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing.Input; +using osu.Game.Graphics.Cursor; namespace osu.Game.Tests.Visual { public abstract class ManualInputManagerTestCase : OsuTestCase { - protected override Container Content => InputManager; + protected override Container Content => content; + private readonly Container content; + protected readonly ManualInputManager InputManager; protected ManualInputManagerTestCase() { - base.Content.Add(InputManager = new ManualInputManager { UseParentInput = true }); + base.Content.Add(InputManager = new ManualInputManager + { + UseParentInput = true, + Child = content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }, + }); } /// From 6c568d228fe7edb316492c4b4c32aae595c3b333 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 23 Apr 2019 13:45:51 +0900 Subject: [PATCH 495/623] Add comment --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 302380744a..01ae637158 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -227,7 +227,10 @@ namespace osu.Game.Rulesets.UI replayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; if (!ProvidingUserCursor) + { + // The cursor is hidden by default (see Playfield.load()), but should be shown when there's a replay Playfield.Cursor?.Show(); + } } /// From 9890884726181bcb8605a39c5cf3bb2c9be38c6b Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 23 Apr 2019 14:23:09 +0900 Subject: [PATCH 496/623] Remove fade in, update comment --- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 3 ++- osu.Game/Rulesets/Mods/ModFlashlight.cs | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index b2bc5f4320..597c430e16 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -49,7 +49,8 @@ namespace osu.Game.Rulesets.Osu.Mods if (e.NewValue) { trackingSliders++; - // This check being here ensures we're only applying a dim if and only if a slider begins tracking. + // The fade should only be applied if tracking sliders is increasing from 0 to 1, and cannot be a result of a slider losing tracking. + // As a result, this logic must be exclusive to when e.NewValue is true. if (trackingSliders == 1) { this.TransformTo(nameof(FlashlightDim), 0.8f, 50); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index c3ac00f3ad..54331508a0 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -95,8 +95,6 @@ namespace osu.Game.Rulesets.Mods Combo.ValueChanged += OnComboChange; - this.FadeInFromZero(FLASHLIGHT_FADE_DURATION); - using (BeginAbsoluteSequence(0)) { foreach (var breakPeriod in Breaks) From 0838206ddd3c54d2ec9f2d7cfc015da931a97a0c Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 22 Apr 2019 22:44:43 -0700 Subject: [PATCH 497/623] Shorten multiplayer header to multi --- osu.Game/Screens/Multi/Header.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 7924086389..b9354cac7d 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -79,7 +79,7 @@ namespace osu.Game.Screens.Multi [BackgroundDependencyLoader] private void load(OsuColour colours) { - Title = "multiplayer"; + Title = "multi"; Icon = OsuIcon.Multi; AccentColour = colours.Yellow; } From 80e1568e974f0cfa5efd0e8a4b1c2298c7fffefc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Apr 2019 13:44:21 +0900 Subject: [PATCH 498/623] Fix FrameStabilityContainer performing frame-stable seeks --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index c307520aca..2866ce08ed 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -68,6 +68,8 @@ namespace osu.Game.Rulesets.UI private const double sixty_frame_time = 1000.0 / 60; + private bool firstConsumption = true; + public override bool UpdateSubTree() { requireMoreUpdateLoops = true; @@ -103,7 +105,14 @@ namespace osu.Game.Rulesets.UI try { - if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) + if (firstConsumption) + { + // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. + // Instead we perform an initial seek to the proposed time. + manualClock.CurrentTime = newProposedTime; + firstConsumption = false; + } + else if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) { newProposedTime = newProposedTime > manualClock.CurrentTime ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) From 60328cf1fb840344d3919ee80638ffe01b5010cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Apr 2019 15:23:00 +0900 Subject: [PATCH 499/623] Ensure FrameStabilityContainer's ElapsedTime is zero on initial seek --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 2866ce08ed..ad15bcf057 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -110,6 +110,10 @@ namespace osu.Game.Rulesets.UI // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. // Instead we perform an initial seek to the proposed time. manualClock.CurrentTime = newProposedTime; + + // do a second process to clear out ElapsedTime + framedClock.ProcessFrame(); + firstConsumption = false; } else if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) From a3e7ec0a14b85acec83e68963bb5d44e99a96300 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Apr 2019 15:23:31 +0900 Subject: [PATCH 500/623] Add tests for FrameStabilityContainer --- .../TestCaseFrameStabilityContainer.cs | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs new file mode 100644 index 0000000000..ca0607cd6b --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs @@ -0,0 +1,149 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestCaseFrameStabilityContainer : OsuTestCase + { + private ManualClock manualClock; + + private Container mainContainer; + + private ClockConsumingChild consumer; + + [SetUp] + public void SetUp() + { + Child = mainContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Clock = new FramedClock(manualClock = new ManualClock()), + }; + } + + [Test] + public void TestLargeJumps() + { + createStabilityContainer(); + seekManualTo(100000); + + confirmSeek(100000); + checkFrameCount(6000); + + seekManualTo(0); + + confirmSeek(0); + checkFrameCount(12000); + } + + [Test] + public void TestSmallJumps() + { + createStabilityContainer(); + seekManualTo(40); + + confirmSeek(40); + checkFrameCount(3); + + seekManualTo(0); + + confirmSeek(0); + checkFrameCount(6); + } + + [Test] + public void TestSingleFrameJump() + { + createStabilityContainer(); + seekManualTo(8); + confirmSeek(8); + checkFrameCount(1); + + seekManualTo(16); + confirmSeek(16); + checkFrameCount(2); + } + + [Test] + public void TestInitialSeek() + { + seekManualTo(100000); + + createStabilityContainer(); + + confirmSeek(100000); + checkFrameCount(0); + } + + private void createStabilityContainer() => AddStep("create container", () => mainContainer.Child = new FrameStabilityContainer().WithChild(consumer = new ClockConsumingChild())); + + private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time); + + private void confirmSeek(double time) => AddUntilStep($"wait for seek to {time}", () => consumer.Clock.CurrentTime == time); + + private void checkFrameCount(int frames) => + AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames == frames); + + public class ClockConsumingChild : CompositeDrawable + { + private readonly OsuSpriteText text; + private readonly OsuSpriteText text2; + private readonly OsuSpriteText text3; + + public ClockConsumingChild() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + text2 = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + text3 = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }, + }; + } + + public int ElapsedFrames; + + protected override void Update() + { + base.Update(); + + if (Clock.ElapsedFrameTime != 0) + ElapsedFrames++; + + text.Text = $"current time: {Clock.CurrentTime:F0}"; + if (Clock.ElapsedFrameTime != 0) + text2.Text = $"last elapsed frame time: {Clock.ElapsedFrameTime:F0}"; + text3.Text = $"total frames: {ElapsedFrames:F0}"; + } + } + } +} From e69963e60e2627f9682de1835ea1f3c2388b50c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Apr 2019 12:35:34 +0900 Subject: [PATCH 501/623] Ensure there is enough time before the first object in osu! (roughly following osu-stable specs) --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index ba7241c165..72adb4624d 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Input; @@ -61,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.UI get { var first = (OsuHitObject)Objects.First(); - return first.StartTime - first.TimePreempt; + return first.StartTime - Math.Max(2000, first.TimePreempt); } } } From efaedafc0870bdff673a37034b57a615a6e59d72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Apr 2019 13:53:05 +0900 Subject: [PATCH 502/623] Fix (legacy) AudioLeadIn being used incorrectly. This lead-in is intended to specify a value before zero, not a value before the start time. Also removes an unnecessary ProcessFrame call (it happens within Seek itself). --- osu.Game/Screens/Play/GameplayClockContainer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 6b12430552..f1eed3f662 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -95,8 +95,7 @@ namespace osu.Game.Screens.Play UserPlaybackRate.ValueChanged += _ => updateRate(); - Seek(Math.Min(0, gameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn)); - adjustableClock.ProcessFrame(); + Seek(Math.Min(-beatmap.BeatmapInfo.AudioLeadIn, gameplayStartTime)); } public void Restart() From bb69330e9f85e1e76140e225e497a2cd45a1311f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Apr 2019 14:01:46 +0900 Subject: [PATCH 503/623] Call ProcessFrame on the userOffsetClock after a seek Without doing this, GameplayClock can be left in an incorrect state after a seek (due to the userOffsetClock being its own FramedClock). --- osu.Game/Screens/Play/GameplayClockContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index f1eed3f662..c151e598f7 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -136,6 +136,9 @@ namespace osu.Game.Screens.Play // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. // we may want to consider reversing the application of offsets in the future as it may feel more correct. adjustableClock.Seek(time - totalOffset); + + // manually process frame to ensure GameplayClock is correctly updated after a seek. + userOffsetClock.ProcessFrame(); } public void Stop() From 3b36a4982deabef006ccd97909c3f2730abbe51e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Apr 2019 15:46:21 +0900 Subject: [PATCH 504/623] Fix tests running under nUnit and running multiple times in concession --- .../Gameplay/TestCaseFrameStabilityContainer.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs index ca0607cd6b..bc30648566 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.UI; @@ -12,14 +13,13 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestCaseFrameStabilityContainer : OsuTestCase { - private ManualClock manualClock; + private readonly ManualClock manualClock; - private Container mainContainer; + private readonly Container mainContainer; private ClockConsumingChild consumer; - [SetUp] - public void SetUp() + public TestCaseFrameStabilityContainer() { Child = mainContainer = new Container { @@ -31,6 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestLargeJumps() { + seekManualTo(0); createStabilityContainer(); seekManualTo(100000); @@ -46,6 +47,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSmallJumps() { + seekManualTo(0); createStabilityContainer(); seekManualTo(40); @@ -61,6 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSingleFrameJump() { + seekManualTo(0); createStabilityContainer(); seekManualTo(8); confirmSeek(8); @@ -75,7 +78,6 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestInitialSeek() { seekManualTo(100000); - createStabilityContainer(); confirmSeek(100000); From f273f5daae7e6224ead6491e491b391fc261db07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Apr 2019 15:55:51 +0900 Subject: [PATCH 505/623] Remove unnecessary using statement --- .../Visual/Gameplay/TestCaseFrameStabilityContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs index bc30648566..5cd01fe9a8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs @@ -4,7 +4,6 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.UI; From 8222923ab8838cf754874df7edbe62fa37679291 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 24 Apr 2019 16:20:51 +0900 Subject: [PATCH 506/623] Only track logo if we're still the current screen --- osu.Game/Screens/Play/PlayerLoader.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 6a55fe278b..4403cae883 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -163,7 +163,11 @@ namespace osu.Game.Screens.Play logo.ScaleTo(new Vector2(0.15f), duration, Easing.In); logo.FadeIn(350); - Scheduler.AddDelayed(() => { content.StartTracking(logo, resuming ? 0 : 500, Easing.InOutExpo); }, resuming ? 0 : 500); + Scheduler.AddDelayed(() => + { + if (this.IsCurrentScreen()) + content.StartTracking(logo, resuming ? 0 : 500, Easing.InOutExpo); + }, resuming ? 0 : 500); } protected override void LogoExiting(OsuLogo logo) From 67382724f633e1f79e4e7f18204de3f49200acf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Apr 2019 16:58:13 +0900 Subject: [PATCH 507/623] Reword and reoganise logic --- .../Mods/OsuModFlashlight.cs | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 597c430e16..c2d6ec7934 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Mods private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition { - private int trackingSliders; + private int slidersCurrentlyTracking; public OsuFlashlight() { @@ -45,29 +45,22 @@ namespace osu.Game.Rulesets.Osu.Mods public void OnSliderTrackingChange(ValueChangedEvent e) { - // If any sliders are in a tracking state, apply a dim to the entire playfield over a brief duration. + // If any sliders are in a tracking state, a further dim should be applied to the (remaining) visible portion of the playfield over a brief duration. if (e.NewValue) { - trackingSliders++; - // The fade should only be applied if tracking sliders is increasing from 0 to 1, and cannot be a result of a slider losing tracking. - // As a result, this logic must be exclusive to when e.NewValue is true. - if (trackingSliders == 1) - { + // The transform should only be applied when the first slider begins tracking. + if (++slidersCurrentlyTracking == 1) this.TransformTo(nameof(FlashlightDim), 0.8f, 50); - } } else { - trackingSliders--; + if (slidersCurrentlyTracking == 0) + throw new InvalidOperationException($"The number of {nameof(slidersCurrentlyTracking)} cannot be below 0."); - if (trackingSliders == 0) - { + // The fade is restored after the last slider exits a tracked state. + if (--slidersCurrentlyTracking == 0) this.TransformTo(nameof(FlashlightDim), 0.0f, 50); - } } - - if (trackingSliders < 0) - throw new InvalidOperationException($"The number of {nameof(trackingSliders)} cannot be below 0."); } protected override bool OnMouseMove(MouseMoveEvent e) From 8f101e4f604bac362799fe8c171b8cdaee4aef28 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 24 Apr 2019 18:25:38 +0900 Subject: [PATCH 508/623] Remove unnecessary multi-slider tracking logic --- .../Mods/OsuModFlashlight.cs | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index c2d6ec7934..8e98a02392 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -36,8 +36,6 @@ namespace osu.Game.Rulesets.Osu.Mods private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition { - private int slidersCurrentlyTracking; - public OsuFlashlight() { FlashlightSize = new Vector2(0, getSizeFor(0)); @@ -45,22 +43,8 @@ namespace osu.Game.Rulesets.Osu.Mods public void OnSliderTrackingChange(ValueChangedEvent e) { - // If any sliders are in a tracking state, a further dim should be applied to the (remaining) visible portion of the playfield over a brief duration. - if (e.NewValue) - { - // The transform should only be applied when the first slider begins tracking. - if (++slidersCurrentlyTracking == 1) - this.TransformTo(nameof(FlashlightDim), 0.8f, 50); - } - else - { - if (slidersCurrentlyTracking == 0) - throw new InvalidOperationException($"The number of {nameof(slidersCurrentlyTracking)} cannot be below 0."); - - // The fade is restored after the last slider exits a tracked state. - if (--slidersCurrentlyTracking == 0) - this.TransformTo(nameof(FlashlightDim), 0.0f, 50); - } + // If a slider is in a tracking state, a further dim should be applied to the (remaining) visible portion of the playfield over a brief duration. + this.TransformTo(nameof(FlashlightDim), e.NewValue ? 0.8f : 0.0f, 50); } protected override bool OnMouseMove(MouseMoveEvent e) From 90d5c64cf36df3ab147383b3d8e03ae93e6c44cf Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 24 Apr 2019 18:25:55 +0900 Subject: [PATCH 509/623] Remove unused usings --- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 8e98a02392..c0332fbf60 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; From 8dc7fd8223235fa56c335840b79354c1e1d46be6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Apr 2019 12:16:12 +0900 Subject: [PATCH 510/623] Update framework --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 303587bf88..22afce9c86 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index e45a76dbac..eaebcc3361 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 6fdcd98caadb0415e4bddd198a1e5e5804a56ea7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Apr 2019 14:15:07 +0900 Subject: [PATCH 511/623] Don't play screen "back" sample when retrying --- osu.Game/Screens/OsuScreen.cs | 5 ++++- osu.Game/Screens/Play/PlayerLoader.cs | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index c1a822c75c..9d53e43b80 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -59,6 +59,8 @@ namespace osu.Game.Screens private SampleChannel sampleExit; + protected virtual bool PlayResumeSound => true; + public virtual float BackgroundParallaxAmount => 1; public Bindable Beatmap { get; private set; } @@ -117,7 +119,8 @@ namespace osu.Game.Screens public override void OnResuming(IScreen last) { - sampleExit?.Play(); + if (PlayResumeSound) + sampleExit?.Play(); applyArrivingDefaults(true); base.OnResuming(last); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 6a55fe278b..d49ac0ed16 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -44,6 +44,8 @@ namespace osu.Game.Screens.Play public override bool DisallowExternalBeatmapRulesetChanges => true; + protected override bool PlayResumeSound => false; + private Task loadTask; private InputManager inputManager; From 0bd35ab7bbdb29705813c02474c16d2db35dd36f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Apr 2019 17:36:17 +0900 Subject: [PATCH 512/623] Turn on warnings, resolve issues --- .../Visual/Gameplay/TestCaseSkinReloadable.cs | 12 ++-------- osu.Game/Audio/IPreviewTrackOwner.cs | 2 +- osu.Game/Beatmaps/BeatmapConverter.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 3 +-- osu.Game/Beatmaps/BindableBeatmap.cs | 4 ++-- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 12 +++++++++- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 1 + osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/Database/DatabaseContextFactory.cs | 3 +-- osu.Game/Database/OsuDbContext.cs | 2 +- .../Graphics/Containers/LinkFlowContainer.cs | 2 +- .../Containers/LogoTrackingContainer.cs | 3 +-- osu.Game/Graphics/OsuFont.cs | 5 ++-- .../UserInterface/ScreenBreadcrumbControl.cs | 2 +- osu.Game/Online/API/APIAccess.cs | 2 +- .../Requests/Responses/APILegacyScoreInfo.cs | 2 -- .../Responses/APIUserMostPlayedBeatmap.cs | 8 +++---- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 2 -- osu.Game/OsuGame.cs | 1 - .../Chat/Tabs/ChannelSelectorTabItem.cs | 2 +- .../Chat/Tabs/PrivateChannelTabItem.cs | 4 ---- osu.Game/Overlays/HoldToConfirmOverlay.cs | 2 +- osu.Game/Overlays/MusicController.cs | 1 - .../Settings/RulesetSettingsSubsection.cs | 3 ++- .../Overlays/Settings/SettingsEnumDropdown.cs | 2 +- .../Difficulty/DifficultyCalculator.cs | 2 +- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 4 +++- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 6 ++++- .../Mods/IApplicableToBeatmapConverter.cs | 2 -- .../Rulesets/Mods/IApplicableToHitObject.cs | 2 +- .../Objects/Drawables/DrawableHitObject.cs | 5 ++-- osu.Game/Rulesets/Objects/HitObject.cs | 4 +--- osu.Game/Rulesets/Objects/HitWindows.cs | 2 +- osu.Game/Rulesets/Objects/SliderPath.cs | 7 ------ osu.Game/Rulesets/Ruleset.cs | 1 + osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 3 --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 +++- .../Rulesets/UI/GameplayCursorContainer.cs | 2 +- osu.Game/Rulesets/UI/Playfield.cs | 1 - .../Scrolling/Algorithms/IScrollAlgorithm.cs | 2 +- .../UI/Scrolling/DrawableScrollingRuleset.cs | 2 +- osu.Game/Screens/BackgroundScreenStack.cs | 2 +- .../Compose/Components/SelectionHandler.cs | 3 +-- .../Timeline/ZoomableScrollContainer.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 3 --- .../LabelledComponents/LabelledTextBox.cs | 7 ------ .../Multi/Components/DisableableTabControl.cs | 6 ++--- .../Lounge/Components/ParticipantInfo.cs | 24 ------------------- .../Multi/Lounge/Components/RoomInspector.cs | 2 +- .../Multi/Match/Components/GameTypePicker.cs | 2 +- .../Screens/Multi/Match/Components/Info.cs | 3 +-- .../Match/Components/MatchSettingsOverlay.cs | 4 ++-- .../Components/RoomAvailabilityPicker.cs | 2 +- .../Screens/Multi/MultiplayerComposite.cs | 4 ++-- .../Screens/Multi/MultiplayerSubScreen.cs | 22 +---------------- osu.Game/Screens/Multi/RoomManager.cs | 2 +- osu.Game/Screens/Play/GameplayClock.cs | 2 +- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/PlayerLoader.cs | 1 - .../Screens/Select/Carousel/CarouselItem.cs | 2 -- osu.Game/Screens/Select/SongSelect.cs | 2 -- osu.Game/Skinning/ISkin.cs | 24 +++++++++++++++++++ osu.Game/Skinning/ISkinSource.cs | 13 +--------- .../Skinning/LocalSkinOverrideContainer.cs | 14 +++++------ osu.Game/Skinning/Skin.cs | 4 +--- osu.Game/Tests/Visual/AllPlayersTestCase.cs | 4 ++-- osu.Game/Tests/Visual/EditorClockTestCase.cs | 2 +- osu.Game/Tests/Visual/OsuTestCase.cs | 2 +- osu.Game/Tests/Visual/PlayerTestCase.cs | 2 +- osu.Game/osu.Game.csproj | 1 - 71 files changed, 116 insertions(+), 182 deletions(-) create mode 100644 osu.Game/Skinning/ISkin.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseSkinReloadable.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseSkinReloadable.cs index a9fbf35d37..56ab70b400 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseSkinReloadable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseSkinReloadable.cs @@ -120,12 +120,8 @@ namespace osu.Game.Tests.Visual.Gameplay } } - private class SecondarySource : ISkinSource + private class SecondarySource : ISkin { - public event Action SourceChanged; - - public void TriggerSourceChanged() => SourceChanged?.Invoke(); - public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox(); public Texture GetTexture(string componentName) => throw new NotImplementedException(); @@ -135,12 +131,8 @@ namespace osu.Game.Tests.Visual.Gameplay public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); } - private class SkinSourceContainer : Container, ISkinSource + private class SkinSourceContainer : Container, ISkin { - public event Action SourceChanged; - - public void TriggerSourceChanged() => SourceChanged?.Invoke(); - public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox(); public Texture GetTexture(string componentName) => throw new NotImplementedException(); diff --git a/osu.Game/Audio/IPreviewTrackOwner.cs b/osu.Game/Audio/IPreviewTrackOwner.cs index fdcae65e3c..8ab93257a5 100644 --- a/osu.Game/Audio/IPreviewTrackOwner.cs +++ b/osu.Game/Audio/IPreviewTrackOwner.cs @@ -4,7 +4,7 @@ namespace osu.Game.Audio { /// - /// Interface for objects that can own s. + /// Interface for objects that can own s. /// /// /// s can cancel the currently playing through the diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index b6fa6674f6..7922843626 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -98,7 +98,7 @@ namespace osu.Game.Beatmaps protected abstract IEnumerable ValidConversionTypes { get; } /// - /// Creates the that will be returned by this . + /// Creates the that will be returned by this . /// protected virtual Beatmap CreateBeatmap() => new Beatmap(); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 9caa64ec96..a36a8ea7dd 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -217,7 +217,7 @@ namespace osu.Game.Beatmaps { request.Perform(api); } - catch (Exception e) + catch { // no need to handle here as exceptions will filter down to request.Failure above. } @@ -382,7 +382,6 @@ namespace osu.Game.Beatmaps /// Query the API to populate missing values like OnlineBeatmapID / OnlineBeatmapSetID or (Rank-)Status. /// /// The beatmap to populate. - /// The other beatmaps contained within this set. /// Whether to re-query if the provided beatmap already has populated values. /// True if population was successful. private bool fetchAndPopulateOnlineValues(BeatmapInfo beatmap, bool force = false) diff --git a/osu.Game/Beatmaps/BindableBeatmap.cs b/osu.Game/Beatmaps/BindableBeatmap.cs index 657dc06297..27bad65062 100644 --- a/osu.Game/Beatmaps/BindableBeatmap.cs +++ b/osu.Game/Beatmaps/BindableBeatmap.cs @@ -12,7 +12,7 @@ namespace osu.Game.Beatmaps { /// /// A for the beatmap. - /// This should be used sparingly in-favour of . + /// This should be used sparingly in-favour of . /// public abstract class BindableBeatmap : NonNullableBindable { @@ -67,6 +67,6 @@ namespace osu.Game.Beatmaps /// If you are further binding to events of the retrieved , ensure a local reference is held. /// [NotNull] - public abstract BindableBeatmap GetBoundCopy(); + public new abstract BindableBeatmap GetBoundCopy(); } } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 58463d2219..7d25ca3ede 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Audio.Track; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Textures; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; @@ -73,9 +74,18 @@ namespace osu.Game.Beatmaps private class DummyBeatmapConverter : IBeatmapConverter { public event Action> ObjectConverted; + public IBeatmap Beatmap { get; set; } + public bool CanConvert => true; - public IBeatmap Convert() => Beatmap; + + public IBeatmap Convert() + { + foreach (var obj in Beatmap.HitObjects) + ObjectConverted?.Invoke(obj, obj.Yield()); + + return Beatmap; + } } } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 040f582e3b..31cfe076cd 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -95,7 +95,7 @@ namespace osu.Game.Beatmaps.Formats { colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), split.Length == 4 ? byte.Parse(split[3]) : (byte)255); } - catch (Exception e) + catch { throw new InvalidOperationException(@"Color must be specified with 8-bit integer components"); } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 8989785dcd..4b0720d867 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -73,6 +73,7 @@ namespace osu.Game.Beatmaps /// /// /// The to create a playable for. + /// The s to apply to the . /// The converted . /// If could not be converted to . public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 3805921ac2..41f8c64853 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -563,7 +563,7 @@ namespace osu.Game.Database /// /// Check whether an existing model already exists for a new import item. /// - /// The new model proposed for import. + /// The new model proposed for import. /// An existing model which matches the criteria to skip importing, else null. protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index f6250732d9..554337c477 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using System.Threading; using Microsoft.EntityFrameworkCore.Storage; @@ -67,7 +66,7 @@ namespace osu.Game.Database context = threadContexts.Value; } } - catch (Exception e) + catch { // retrieval of a context could trigger a fatal error. Monitor.Exit(writeLock); diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 17efe2c839..f4cd23f5a5 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -70,7 +70,7 @@ namespace osu.Game.Database cmd.ExecuteNonQuery(); } } - catch (Exception e) + catch { connection.Close(); throw; diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index eefbeea24c..222336d663 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -103,7 +103,7 @@ namespace osu.Game.Graphics.Containers { channelManager?.OpenChannel(linkArgument); } - catch (ChannelNotFoundException e) + catch (ChannelNotFoundException) { Logger.Log($"The requested channel \"{linkArgument}\" does not exist"); } diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs index fb23038dde..23015e8bf5 100644 --- a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -30,7 +30,6 @@ namespace osu.Game.Graphics.Containers /// Assign the logo that should track the facade's position, as well as how it should transform to its initial position. /// /// The instance of the logo to be used for tracking. - /// The scale of the facade. Does not actually affect the logo itself. /// The duration of the initial transform. Default is instant. /// The easing type of the initial transform. public void StartTracking(OsuLogo logo, double duration = 0, Easing easing = Easing.None) @@ -132,7 +131,7 @@ namespace osu.Game.Graphics.Containers private class InternalFacade : Facade { - public void SetSize(Vector2 size) + public new void SetSize(Vector2 size) { base.SetSize(size); } diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index c8a736f49a..5324b269ee 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -61,9 +61,9 @@ namespace osu.Game.Graphics /// /// Retrieves the string representation of a . /// - /// The . + /// The family string. /// The . - /// The string representation of in the specified . + /// The string representation of in the specified . public static string GetWeightString(string family, FontWeight weight) { string weightString = weight.ToString(); @@ -81,6 +81,7 @@ namespace osu.Game.Graphics /// /// Creates a new by applying adjustments to this . /// + /// The base . /// The font typeface. If null, the value is copied from this . /// The text size. If null, the value is copied from this . /// The font weight. If null, the value is copied from this . diff --git a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs index f564a4b5a8..3e0a6c3265 100644 --- a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs @@ -8,7 +8,7 @@ using osu.Framework.Screens; namespace osu.Game.Graphics.UserInterface { /// - /// A which follows the active screen (and allows navigation) in a stack. + /// A which follows the active screen (and allows navigation) in a stack. /// public class ScreenBreadcrumbControl : BreadcrumbControl { diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index c5f6ef41c2..d5a496dc17 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -253,7 +253,7 @@ namespace osu.Game.Online.API handleWebException(we); return false; } - catch (Exception e) + catch { return false; } diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index 8ee71ce9ac..ca3a77a140 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -7,7 +7,6 @@ using System.Linq; using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Users; @@ -71,7 +70,6 @@ namespace osu.Game.Online.API.Requests.Responses { foreach (var kvp in value) { - HitResult newKey; switch (kvp.Key) { case @"count_geki": diff --git a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs index 8177f99abe..4614fe29b7 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs @@ -10,16 +10,16 @@ namespace osu.Game.Online.API.Requests.Responses public class APIUserMostPlayedBeatmap { [JsonProperty("beatmap_id")] - public int BeatmapID; + public int BeatmapID { get; set; } [JsonProperty("count")] - public int PlayCount; + public int PlayCount { get; set; } [JsonProperty] - private BeatmapInfo beatmap; + private BeatmapInfo beatmap { get; set; } [JsonProperty] - private APIBeatmapSet beatmapSet; + private APIBeatmapSet beatmapSet { get; set; } public BeatmapInfo GetBeatmapInfo(RulesetStore rulesets) { diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 438bf231c4..ae4a056033 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -27,8 +27,6 @@ namespace osu.Game.Online.Chat protected ChannelManager ChannelManager; - private ScrollContainer scroll; - private DrawableChannel drawableChannel; private readonly bool postingTextbox; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 30f98aa1ce..4ce056195c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -272,7 +272,6 @@ namespace osu.Game /// Present a score's replay immediately. /// The user should have already requested this interactively. /// - /// The beatmap to select. public void PresentScore(ScoreInfo score) { var databasedScore = ScoreManager.GetScore(score); diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs index 52260506fe..c26ecfd86f 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Chat.Tabs } [BackgroundDependencyLoader] - private new void load(OsuColour colour) + private void load(OsuColour colour) { BackgroundInactive = colour.Gray2; BackgroundActive = colour.Gray3; diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs index 8aa6d6fecd..b8165e70cb 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; using osu.Game.Users; using osuTK; @@ -18,9 +17,6 @@ namespace osu.Game.Overlays.Chat.Tabs { public class PrivateChannelTabItem : ChannelTabItem { - private readonly OsuSpriteText username; - private readonly Avatar avatarContainer; - protected override IconUsage DisplayIcon => FontAwesome.Solid.At; public PrivateChannelTabItem(Channel value) diff --git a/osu.Game/Overlays/HoldToConfirmOverlay.cs b/osu.Game/Overlays/HoldToConfirmOverlay.cs index 154aff605a..fb38ddcbd1 100644 --- a/osu.Game/Overlays/HoldToConfirmOverlay.cs +++ b/osu.Game/Overlays/HoldToConfirmOverlay.cs @@ -11,7 +11,7 @@ namespace osu.Game.Overlays { /// /// An overlay which will display a black screen that dims over a period before confirming an exit action. - /// Action is BYO (derived class will need to call and from a user event). + /// Action is BYO (derived class will need to call and from a user event). /// public abstract class HoldToConfirmOverlay : HoldToConfirmContainer { diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index c250d3b62a..9046a196da 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -50,7 +50,6 @@ namespace osu.Game.Overlays private BeatmapManager beatmaps; private List beatmapSets; - private BeatmapSetInfo currentSet; private Container dragContainer; private Container playerContainer; diff --git a/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs b/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs index a16e852902..93b07fbac7 100644 --- a/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Configuration; @@ -9,7 +10,7 @@ namespace osu.Game.Overlays.Settings { /// /// A which provides subclasses with the - /// from the 's . + /// from the 's . /// public abstract class RulesetSettingsSubsection : SettingsSubsection { diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index 11cdbf6e0a..9f09f251c2 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings { protected override OsuDropdown CreateDropdown() => new DropdownControl(); - protected class DropdownControl : OsuEnumDropdown + protected new class DropdownControl : OsuEnumDropdown { public DropdownControl() { diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 14f7665e05..5eabe1e936 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -165,7 +165,7 @@ namespace osu.Game.Rulesets.Difficulty /// /// Creates the s to calculate the difficulty of an . /// - /// The whose difficulty will be calculated.The whose difficulty will be calculated. /// The s. protected abstract Skill[] CreateSkills(IBeatmap beatmap); } diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 74aa9ace2d..f12591cef4 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Compose; using osuTK; @@ -108,7 +109,8 @@ namespace osu.Game.Rulesets.Edit } /// - /// Invokes , refreshing and parameters for the . + /// Invokes , + /// refreshing and parameters for the . /// protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.Value.Beatmap.ControlPointInfo, beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty); diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 85b6c91a07..01992cbbd3 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -68,9 +68,11 @@ namespace osu.Game.Rulesets.Edit get => state; set { - if (state == value) return; + if (state == value) + return; state = value; + switch (state) { case SelectionState.Selected: @@ -82,6 +84,8 @@ namespace osu.Game.Rulesets.Edit Deselected?.Invoke(this); break; } + + StateChanged?.Invoke(state); } } diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs index eb80fa131a..8cefb02904 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs @@ -2,14 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mods { /// /// Interface for a that applies changes to a . /// - /// The type of converted . public interface IApplicableToBeatmapConverter : IApplicableMod { /// diff --git a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs index c13b62812b..f7f81c92c0 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods public interface IApplicableToHitObject : IApplicableMod { /// - /// Applies this to a . + /// Applies this to a . /// /// The to apply to. void ApplyToHitObject(HitObject hitObject); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index a7cfbd3300..2e983b8fe1 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Game.Audio; using osu.Game.Graphics; @@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Objects.Drawables public bool AllJudged => Judged && NestedHitObjects.All(h => h.AllJudged); /// - /// Whether this has been hit. This occurs if is . + /// Whether this has been hit. This occurs if is hit. /// Note: This does NOT include nested hitobjects. /// public bool IsHit => Result?.IsHit ?? false; @@ -223,7 +224,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } /// - /// Will called at least once after the of this has been passed. + /// Will called at least once after the of this has been passed. /// internal void OnLifetimeEnd() { diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index fd542be67d..cede2e50d0 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -53,8 +53,6 @@ namespace osu.Game.Rulesets.Objects [JsonIgnore] public bool Kiai { get; private set; } - private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY; - /// /// The hit windows for this . /// @@ -115,7 +113,7 @@ namespace osu.Game.Rulesets.Objects /// Creates the for this . /// This can be null to indicate that the has no . /// - /// This will only be invoked if hasn't been set externally (e.g. from a . + /// This will only be invoked if hasn't been set externally (e.g. from a . /// /// protected virtual HitWindows CreateHitWindows() => new HitWindows(); diff --git a/osu.Game/Rulesets/Objects/HitWindows.cs b/osu.Game/Rulesets/Objects/HitWindows.cs index c5b7686da6..589c72957b 100644 --- a/osu.Game/Rulesets/Objects/HitWindows.cs +++ b/osu.Game/Rulesets/Objects/HitWindows.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Objects /// /// Given a time offset, whether the can ever be hit in the future with a non- result. - /// This happens if is less than what is required for a result. + /// This happens if is less than what is required for . /// /// The time offset. /// Whether the can be hit at any point in the future from this time offset. diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 1e9767a54f..e312b004ba 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -277,12 +277,5 @@ namespace osu.Game.Rulesets.Objects return ControlPoints.SequenceEqual(other.ControlPoints) && ExpectedDistance.Equals(other.ExpectedDistance) && Type == other.Type; } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - - return obj is SliderPath other && Equals(other); - } } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 3521c17b23..42b1322cae 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -53,6 +53,7 @@ namespace osu.Game.Rulesets /// Attempt to create a hit renderer for a beatmap /// /// The beatmap to create the hit renderer for. + /// The s to apply. /// Unable to successfully load the beatmap to be usable with this ruleset. /// public abstract DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods); diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index ba71e1e9b2..a2937ff959 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -154,7 +154,6 @@ namespace osu.Game.Rulesets.Scoring /// /// Notifies subscribers of that a new judgement has occurred. /// - /// The judgement to notify subscribers of. /// The judgement scoring result to notify subscribers of. protected void NotifyNewJudgement(JudgementResult result) { @@ -283,7 +282,6 @@ namespace osu.Game.Rulesets.Scoring /// /// Reverts the score change of a that was applied to this . /// - /// The judgement to remove. /// The judgement scoring result. private void revertResult(JudgementResult result) { @@ -340,7 +338,6 @@ namespace osu.Game.Rulesets.Scoring /// /// Reverts the score change of a that was applied to this . /// - /// The judgement to remove. /// The judgement scoring result. protected virtual void RevertResult(JudgementResult result) { diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 01ae637158..df9effb321 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -93,6 +93,7 @@ namespace osu.Game.Rulesets.UI /// /// The ruleset being represented. /// The beatmap to create the hit renderer for. + /// The s to apply. protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap workingBeatmap, IReadOnlyList mods) : base(ruleset) { @@ -275,7 +276,8 @@ namespace osu.Game.Rulesets.UI /// /// Applies the active mods to this DrawableRuleset. /// - /// + /// The s to apply. + /// The to apply. private void applyRulesetMods(IReadOnlyList mods, OsuConfigManager config) { if (mods == null) diff --git a/osu.Game/Rulesets/UI/GameplayCursorContainer.cs b/osu.Game/Rulesets/UI/GameplayCursorContainer.cs index de73c08809..41edfa0b68 100644 --- a/osu.Game/Rulesets/UI/GameplayCursorContainer.cs +++ b/osu.Game/Rulesets/UI/GameplayCursorContainer.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.UI public class GameplayCursorContainer : CursorContainer { /// - /// Because Show/Hide are executed by a parent, is updated immediately even if the cursor + /// Because Show/Hide are executed by a parent, is updated immediately even if the cursor /// is in a non-updating state (via limitations). /// /// This holds the true visibility value. diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index a99c16a610..a073ad246b 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -100,7 +100,6 @@ namespace osu.Game.Rulesets.UI /// /// Provide an optional cursor which is to be used for gameplay. - /// If providing a cursor, must also point to a valid target container. /// /// The cursor, or null if a cursor is not rqeuired. protected virtual GameplayCursorContainer CreateCursor() => null; diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs index a104b0629f..b7a5eedc22 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms /// The current time. /// The amount of visible time. /// The absolute spatial length through . - /// The time at which == . + /// The time at which == . double TimeAt(float position, double currentTime, double timeRange, float scrollLength); /// diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index dbe8d8c299..f21d0b4a66 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.UI.Scrolling protected virtual ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Sequential; /// - /// Whether the player can change . + /// Whether the player can change . /// protected virtual bool UserScrollSpeedAdjustment => true; diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs index 5f82329496..9c0c5da0fb 100644 --- a/osu.Game/Screens/BackgroundScreenStack.cs +++ b/osu.Game/Screens/BackgroundScreenStack.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens //public float ParallaxAmount { set => parallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * value; } - public new void Push(BackgroundScreen screen) + public void Push(BackgroundScreen screen) { if (screen == null) return; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index bcb2bee601..11e649168f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -121,6 +121,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Handle a blueprint requesting selection. /// /// The blueprint. + /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ControlPressed) @@ -166,8 +167,6 @@ namespace osu.Game.Screens.Edit.Compose.Components var topLeft = new Vector2(float.MaxValue, float.MaxValue); var bottomRight = new Vector2(float.MinValue, float.MinValue); - bool hasSelection = false; - foreach (var blueprint in selectedBlueprints) { topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(blueprint.SelectionQuad.TopLeft)); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index f41b3cddc0..9b00a3998d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly float scrollOffset; /// - /// Transforms to a new value. + /// Transforms to a new value. /// /// The focus point in absolute coordinates local to the content. /// The size of the content. diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0ba1e74aca..09977454f0 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -65,9 +65,6 @@ namespace osu.Game.Screens.Edit dependencies.Cache(beatDivisor); EditorMenuBar menuBar; - TimeInfoContainer timeInfo; - SummaryTimeline timeline; - PlaybackControl playback; var fileMenuItems = new List(); if (RuntimeInfo.IsDesktop) diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs index 50d524d1f5..1c53fc7088 100644 --- a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs @@ -60,14 +60,7 @@ namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents set => label.Colour = value; } - public Color4 BackgroundColour - { - get => content.Colour; - set => content.Colour = value; - } - private readonly OsuTextBox textBox; - private readonly Container content; private readonly OsuSpriteText label; public LabelledTextBox() diff --git a/osu.Game/Screens/Multi/Components/DisableableTabControl.cs b/osu.Game/Screens/Multi/Components/DisableableTabControl.cs index b6b0332cf3..27b5aec4d3 100644 --- a/osu.Game/Screens/Multi/Components/DisableableTabControl.cs +++ b/osu.Game/Screens/Multi/Components/DisableableTabControl.cs @@ -13,15 +13,13 @@ namespace osu.Game.Screens.Multi.Components protected override void AddTabItem(TabItem tab, bool addToDropdown = true) { - if (tab is DisableableTabItem disableable) + if (tab is DisableableTabItem disableable) disableable.Enabled.BindTo(Enabled); base.AddTabItem(tab, addToDropdown); } - protected abstract class DisableableTabItem : TabItem + protected abstract class DisableableTabItem : TabItem { - public readonly BindableBool Enabled = new BindableBool(); - protected DisableableTabItem(T value) : base(value) { diff --git a/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs b/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs index 51d3c93624..6570051040 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs @@ -25,8 +25,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components private void load(OsuColour colours) { OsuSpriteText summary; - OsuSpriteText levelRangeHigher; - OsuSpriteText levelRangeLower; Container flagContainer; LinkFlowContainer hostText; @@ -45,21 +43,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components Width = 22f, RelativeSizeAxes = Axes.Y, }, - /*new Container //todo: team banners - { - Width = 38f, - RelativeSizeAxes = Axes.Y, - CornerRadius = 2f, - Masking = true, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"ad387e"), - }, - }, - },*/ hostText = new LinkFlowContainer { Anchor = Anchor.CentreLeft, @@ -101,13 +84,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components }, true); ParticipantCount.BindValueChanged(count => summary.Text = "participant".ToQuantity(count.NewValue), true); - - /*Participants.BindValueChanged(e => - { - var ranks = v.Select(u => u.Statistics.Ranks.Global); - levelRangeLower.Text = ranks.Min().ToString(); - levelRangeHigher.Text = ranks.Max().ToString(); - });*/ } } } diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs index 5798fce457..1297090a32 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs @@ -97,7 +97,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Font = OsuFont.GetFont(size: 30), - Current = Name + Current = RoomName }, }, }, diff --git a/osu.Game/Screens/Multi/Match/Components/GameTypePicker.cs b/osu.Game/Screens/Multi/Match/Components/GameTypePicker.cs index ccb957734f..b69cb9705d 100644 --- a/osu.Game/Screens/Multi/Match/Components/GameTypePicker.cs +++ b/osu.Game/Screens/Multi/Match/Components/GameTypePicker.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Multi.Match.Components AddItem(new GameTypeTimeshift()); } - private class GameTypePickerItem : DisableableTabItem + private class GameTypePickerItem : DisableableTabItem { private const float transition_duration = 200; diff --git a/osu.Game/Screens/Multi/Match/Components/Info.cs b/osu.Game/Screens/Multi/Match/Components/Info.cs index a944d965bd..a185c4db50 100644 --- a/osu.Game/Screens/Multi/Match/Components/Info.cs +++ b/osu.Game/Screens/Multi/Match/Components/Info.cs @@ -30,7 +30,6 @@ namespace osu.Game.Screens.Multi.Match.Components ReadyButton readyButton; ViewBeatmapButton viewBeatmapButton; HostInfo hostInfo; - RoomStatusInfo statusInfo; InternalChildren = new Drawable[] { @@ -63,7 +62,7 @@ namespace osu.Game.Screens.Multi.Match.Components new OsuSpriteText { Font = OsuFont.GetFont(size: 30), - Current = Name + Current = RoomName }, new RoomStatusInfo(), } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs index 586a986111..359b5824c0 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs @@ -265,7 +265,7 @@ namespace osu.Game.Screens.Multi.Match.Components }; TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue?.Name ?? string.Empty, true); - Name.BindValueChanged(name => NameField.Text = name.NewValue, true); + RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true); Availability.BindValueChanged(availability => AvailabilityPicker.Current.Value = availability.NewValue, true); Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); @@ -285,7 +285,7 @@ namespace osu.Game.Screens.Multi.Match.Components { hideError(); - Name.Value = NameField.Text; + RoomName.Value = NameField.Text; Availability.Value = AvailabilityPicker.Current.Value; Type.Value = TypePicker.Current.Value; diff --git a/osu.Game/Screens/Multi/Match/Components/RoomAvailabilityPicker.cs b/osu.Game/Screens/Multi/Match/Components/RoomAvailabilityPicker.cs index 8751e27552..9de4a61cde 100644 --- a/osu.Game/Screens/Multi/Match/Components/RoomAvailabilityPicker.cs +++ b/osu.Game/Screens/Multi/Match/Components/RoomAvailabilityPicker.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Multi.Match.Components AddItem(RoomAvailability.InviteOnly); } - private class RoomAvailabilityPickerItem : DisableableTabItem + private class RoomAvailabilityPickerItem : DisableableTabItem { private const float transition_duration = 200; diff --git a/osu.Game/Screens/Multi/MultiplayerComposite.cs b/osu.Game/Screens/Multi/MultiplayerComposite.cs index da6bba7865..8c09d576ff 100644 --- a/osu.Game/Screens/Multi/MultiplayerComposite.cs +++ b/osu.Game/Screens/Multi/MultiplayerComposite.cs @@ -16,8 +16,8 @@ namespace osu.Game.Screens.Multi [Resolved(typeof(Room))] protected Bindable RoomID { get; private set; } - [Resolved(typeof(Room))] - protected Bindable Name { get; private set; } + [Resolved(typeof(Room), nameof(Room.Name))] + protected Bindable RoomName { get; private set; } [Resolved(typeof(Room))] protected Bindable Host { get; private set; } diff --git a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs index 65e501b114..ff94f63f01 100644 --- a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs +++ b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs @@ -3,22 +3,17 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Input.Bindings; using osu.Framework.Screens; using osu.Game.Graphics.Containers; -using osu.Game.Input.Bindings; namespace osu.Game.Screens.Multi { - public abstract class MultiplayerSubScreen : OsuScreen, IMultiplayerSubScreen, IKeyBindingHandler + public abstract class MultiplayerSubScreen : OsuScreen, IMultiplayerSubScreen { public override bool DisallowExternalBeatmapRulesetChanges => false; public virtual string ShortTitle => Title; - [Resolved(CanBeNull = true)] - protected OsuGame Game { get; private set; } - [Resolved(CanBeNull = true)] protected IRoomManager RoomManager { get; private set; } @@ -56,21 +51,6 @@ namespace osu.Game.Screens.Multi this.MoveToX(-200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); } - public override bool OnPressed(GlobalAction action) - { - if (!this.IsCurrentScreen()) return false; - - if (action == GlobalAction.Back) - { - this.Exit(); - return true; - } - - return false; - } - - public bool OnReleased(GlobalAction action) => action == GlobalAction.Back; - public override string ToString() => Title; } } diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index 385cbe20e5..6f473aaafa 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -171,7 +171,7 @@ namespace osu.Game.Screens.Multi /// /// Adds a to the list of available rooms. /// - /// The to add.< + /// The to add. private void addRoom(Room room) { var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value); diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 3efcfa0f65..b1948d02d5 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -8,7 +8,7 @@ namespace osu.Game.Screens.Play { /// /// A clock which is used for gameplay elements that need to follow audio time 1:1. - /// Exposed via DI by . + /// Exposed via DI by . /// /// The main purpose of this clock is to stop components using it from accidentally processing the main /// , as this should only be done once to ensure accuracy. diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index f833aa2bb7..5bf54877fc 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Play [Cached] [Cached(Type = typeof(IBindable>))] - protected readonly Bindable> Mods = new Bindable>(Array.Empty()); + protected new readonly Bindable> Mods = new Bindable>(Array.Empty()); private readonly bool allowPause; private readonly bool showResults; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 6a55fe278b..ebc6459abe 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -320,7 +320,6 @@ namespace osu.Game.Screens.Play private readonly Drawable facade; private LoadingAnimation loading; private Sprite backgroundSprite; - private ModDisplay modDisplay; public bool Loading { diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index a0f5969b3c..79c1a4cb6b 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -31,8 +31,6 @@ namespace osu.Game.Screens.Select.Carousel } } - private int creationOrder; - protected CarouselItem() { DrawableRepresentation = new Lazy(CreateDrawableRepresentation); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index a78238c584..14c362b8ca 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -388,8 +388,6 @@ namespace osu.Game.Screens.Select { Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ID.ToString() ?? "null"}"); - bool preview = false; - if (ruleset?.Equals(decoupledRuleset.Value) == false) { Logger.Log($"ruleset changed from \"{decoupledRuleset.Value}\" to \"{ruleset}\""); diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs new file mode 100644 index 0000000000..0e67a1897c --- /dev/null +++ b/osu.Game/Skinning/ISkin.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Skinning +{ + /// + /// Provides access to skinnable elements. + /// + public interface ISkin + { + Drawable GetDrawableComponent(string componentName); + + Texture GetTexture(string componentName); + + SampleChannel GetSample(string sampleName); + + TValue GetValue(Func query) where TConfiguration : SkinConfiguration; + } +} diff --git a/osu.Game/Skinning/ISkinSource.cs b/osu.Game/Skinning/ISkinSource.cs index 6d2b9e6fe2..337d2a87a4 100644 --- a/osu.Game/Skinning/ISkinSource.cs +++ b/osu.Game/Skinning/ISkinSource.cs @@ -2,25 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Audio.Sample; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; namespace osu.Game.Skinning { /// /// Provides access to skinnable elements. /// - public interface ISkinSource + public interface ISkinSource : ISkin { event Action SourceChanged; - - Drawable GetDrawableComponent(string componentName); - - Texture GetTexture(string componentName); - - SampleChannel GetSample(string sampleName); - - TValue GetValue(Func query) where TConfiguration : SkinConfiguration; } } diff --git a/osu.Game/Skinning/LocalSkinOverrideContainer.cs b/osu.Game/Skinning/LocalSkinOverrideContainer.cs index 955ef7b65b..f1ed14595e 100644 --- a/osu.Game/Skinning/LocalSkinOverrideContainer.cs +++ b/osu.Game/Skinning/LocalSkinOverrideContainer.cs @@ -22,18 +22,18 @@ namespace osu.Game.Skinning private readonly Bindable beatmapSkins = new Bindable(); private readonly Bindable beatmapHitsounds = new Bindable(); - private readonly ISkinSource source; + private readonly ISkin skin; private ISkinSource fallbackSource; - public LocalSkinOverrideContainer(ISkinSource source) + public LocalSkinOverrideContainer(ISkin skin) { - this.source = source; + this.skin = skin; } public Drawable GetDrawableComponent(string componentName) { Drawable sourceDrawable; - if (beatmapSkins.Value && (sourceDrawable = source.GetDrawableComponent(componentName)) != null) + if (beatmapSkins.Value && (sourceDrawable = skin.GetDrawableComponent(componentName)) != null) return sourceDrawable; return fallbackSource?.GetDrawableComponent(componentName); @@ -42,7 +42,7 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName) { Texture sourceTexture; - if (beatmapSkins.Value && (sourceTexture = source.GetTexture(componentName)) != null) + if (beatmapSkins.Value && (sourceTexture = skin.GetTexture(componentName)) != null) return sourceTexture; return fallbackSource.GetTexture(componentName); @@ -51,7 +51,7 @@ namespace osu.Game.Skinning public SampleChannel GetSample(string sampleName) { SampleChannel sourceChannel; - if (beatmapHitsounds.Value && (sourceChannel = source.GetSample(sampleName)) != null) + if (beatmapHitsounds.Value && (sourceChannel = skin.GetSample(sampleName)) != null) return sourceChannel; return fallbackSource?.GetSample(sampleName); @@ -60,7 +60,7 @@ namespace osu.Game.Skinning public TValue GetValue(Func query) where TConfiguration : SkinConfiguration { TValue val; - if ((source as Skin)?.Configuration is TConfiguration conf) + if ((skin as Skin)?.Configuration is TConfiguration conf) if (beatmapSkins.Value && (val = query.Invoke(conf)) != null) return val; diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 1d14f9cd6a..09c0d3d0bc 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -8,14 +8,12 @@ using osu.Framework.Graphics.Textures; namespace osu.Game.Skinning { - public abstract class Skin : IDisposable, ISkinSource + public abstract class Skin : IDisposable, ISkin { public readonly SkinInfo SkinInfo; public virtual SkinConfiguration Configuration { get; protected set; } - public event Action SourceChanged; - public abstract Drawable GetDrawableComponent(string componentName); public abstract SampleChannel GetSample(string sampleName); diff --git a/osu.Game/Tests/Visual/AllPlayersTestCase.cs b/osu.Game/Tests/Visual/AllPlayersTestCase.cs index 3e1f408a16..6e78851e31 100644 --- a/osu.Game/Tests/Visual/AllPlayersTestCase.cs +++ b/osu.Game/Tests/Visual/AllPlayersTestCase.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual { Player p = null; AddStep(r.Name, () => p = loadPlayerFor(r)); - AddUntilStep(() => + AddUntilStep("player loaded", () => { if (p?.IsLoaded == true) { @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual } return false; - }, "player loaded"); + }); AddCheckSteps(); } diff --git a/osu.Game/Tests/Visual/EditorClockTestCase.cs b/osu.Game/Tests/Visual/EditorClockTestCase.cs index 7f36a0e142..c71c2ae857 100644 --- a/osu.Game/Tests/Visual/EditorClockTestCase.cs +++ b/osu.Game/Tests/Visual/EditorClockTestCase.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual public abstract class EditorClockTestCase : OsuTestCase { protected readonly BindableBeatDivisor BeatDivisor = new BindableBeatDivisor(); - protected readonly EditorClock Clock; + protected new readonly EditorClock Clock; protected EditorClockTestCase() { diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs index c08a6a4bcb..1f475209a4 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestCase.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual [Cached(Type = typeof(IBindable>))] protected readonly Bindable> Mods = new Bindable>(Array.Empty()); - protected DependencyContainer Dependencies { get; private set; } + protected new DependencyContainer Dependencies { get; private set; } private readonly Lazy localStorage; protected Storage LocalStorage => localStorage.Value; diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index fca4fccae0..b9c7933cfb 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual public void SetUpSteps() { AddStep(ruleset.RulesetInfo.Name, loadPlayer); - AddUntilStep(() => Player.IsLoaded && Player.Alpha == 1, "player loaded"); + AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); } protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 22afce9c86..e25921c486 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -5,7 +5,6 @@ Library AnyCPU true - 0 From 1766ed8f9e2d28de824a20432c79e050d012dc34 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Apr 2019 18:05:46 +0900 Subject: [PATCH 513/623] Fix warnings/remove obsolete usages --- .../Profile/Header/BottomHeaderContainer.cs | 8 ++++---- .../Profile/Header/DetailHeaderContainer.cs | 3 +-- osu.Game/Overlays/Profile/Header/RankGraph.cs | 12 ++++-------- .../Overlays/Profile/Header/TopHeaderContainer.cs | 15 +++++---------- osu.Game/Overlays/Profile/ProfileHeader.cs | 4 ---- 5 files changed, 14 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 04b70ea10f..9eb6feec8e 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -48,12 +48,12 @@ namespace osu.Game.Overlays.Profile.Header Spacing = new Vector2(0, 10), Children = new Drawable[] { - bottomTopLinkContainer = new LinkFlowContainer(text => text.TextSize = 12) + bottomTopLinkContainer = new LinkFlowContainer(text => text.Font = text.Font.With(size: 12)) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, }, - bottomLinkContainer = new LinkFlowContainer(text => text.TextSize = 12) + bottomLinkContainer = new LinkFlowContainer(text => text.Font = text.Font.With(size: 12)) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -68,7 +68,7 @@ namespace osu.Game.Overlays.Profile.Header private void updateDisplay(User user) { - void bold(SpriteText t) => t.Font = @"Exo2.0-Bold"; + void bold(SpriteText t) => t.Font = t.Font.With(weight: FontWeight.Bold); void addSpacer(OsuTextFlowContainer textFlow) => textFlow.AddArbitraryDrawable(new Container { Width = 15 }); bottomTopLinkContainer.Clear(); @@ -113,7 +113,7 @@ namespace osu.Game.Overlays.Profile.Header bottomLinkContainer.AddIcon(icon, text => { - text.TextSize = 10; + text.Font = text.Font.With(size: 10); text.Colour = communityUserGrayGreenLighter; }); if (link != null) diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index 19894f0301..84611b3bf1 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -213,8 +213,7 @@ namespace osu.Game.Overlays.Profile.Header }, rankCount = new OsuSpriteText { - Font = "Exo2.0-Bold", - TextSize = 12, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre } diff --git a/osu.Game/Overlays/Profile/Header/RankGraph.cs b/osu.Game/Overlays/Profile/Header/RankGraph.cs index 01f16fe942..d66f2306a0 100644 --- a/osu.Game/Overlays/Profile/Header/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/RankGraph.cs @@ -43,8 +43,7 @@ namespace osu.Game.Overlays.Profile.Header Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "No recent plays", - TextSize = 12, - Font = @"Exo2.0-Regular", + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular) }, graph = new RankChartLineGraph { @@ -227,14 +226,12 @@ namespace osu.Game.Overlays.Profile.Header { new OsuSpriteText { - Font = "Exo2.0-Bold", - TextSize = 12, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), Text = "Global Ranking " }, globalRankingText = new OsuSpriteText { - Font = "Exo2.0-Regular", - TextSize = 12, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, } @@ -242,8 +239,7 @@ namespace osu.Game.Overlays.Profile.Header }, timeText = new OsuSpriteText { - TextSize = 12, - Font = "Exo2.0-Regular" + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), } } } diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 8e4d72c702..80721af42f 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -75,8 +75,7 @@ namespace osu.Game.Overlays.Profile.Header { usernameText = new OsuSpriteText { - Font = "Exo2.0-Regular", - TextSize = 24 + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) }, openUserExternally = new ExternalLinkButton { @@ -96,8 +95,7 @@ namespace osu.Game.Overlays.Profile.Header { titleText = new OsuSpriteText { - TextSize = 18, - Font = "Exo2.0-Regular" + Font = OsuFont.GetFont(size: 18, weight: FontWeight.Regular) }, supporterTag = new SupporterIcon { @@ -123,8 +121,7 @@ namespace osu.Game.Overlays.Profile.Header }, userCountryText = new OsuSpriteText { - Font = "Exo2.0-Regular", - TextSize = 17.5f, + Font = OsuFont.GetFont(size: 17.5f, weight: FontWeight.Regular), Margin = new MarginPadding { Left = 40 }, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, @@ -185,17 +182,15 @@ namespace osu.Game.Overlays.Profile.Header { new OsuSpriteText { - TextSize = 15, + Font = OsuFont.GetFont(size: 15), Text = left, - Font = "Exo2.0-Medium" }, new OsuSpriteText { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - TextSize = 15, + Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), Text = right, - Font = "Exo2.0-Bold" }, }; } diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 776fcbb8b7..6238d1bc53 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -12,7 +12,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Profile @@ -29,9 +28,6 @@ namespace osu.Game.Overlays.Profile { CenterHeaderContainer centerHeaderContainer; DetailHeaderContainer detailHeaderContainer; - Container expandedDetailContainer; - FillFlowContainer hiddenDetailContainer, headerDetailContainer; - SpriteIcon expandButtonIcon; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; From b33c0e9a9337763df37951623d29705d132b5f5a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Apr 2019 18:42:19 +0900 Subject: [PATCH 514/623] Cleanup bottom header container --- .../Online/TestCaseUserProfileHeader.cs | 1 + .../Profile/Header/BottomHeaderContainer.cs | 108 +++++++++--------- 2 files changed, 55 insertions(+), 54 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs index 98bad9831f..bc2ff708c5 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs @@ -21,6 +21,7 @@ namespace osu.Game.Tests.Visual.Online typeof(RankGraph), typeof(LineGraph), typeof(ProfileHeaderTabControl), + typeof(BottomHeaderContainer) }; [Resolved] diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 9eb6feec8e..39dd1bd028 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -20,18 +20,21 @@ namespace osu.Game.Overlays.Profile.Header { public class BottomHeaderContainer : CompositeDrawable { - private LinkFlowContainer bottomTopLinkContainer; - private LinkFlowContainer bottomLinkContainer; - private Color4 linkBlue, communityUserGrayGreenLighter; - public readonly Bindable User = new Bindable(); + private LinkFlowContainer topLinkContainer; + private LinkFlowContainer bottomLinkContainer; + + private Color4 iconColour; + + public BottomHeaderContainer() + { + AutoSizeAxes = Axes.Y; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { - AutoSizeAxes = Axes.Y; - User.ValueChanged += e => updateDisplay(e.NewValue); - InternalChildren = new Drawable[] { new Box @@ -48,7 +51,7 @@ namespace osu.Game.Overlays.Profile.Header Spacing = new Vector2(0, 10), Children = new Drawable[] { - bottomTopLinkContainer = new LinkFlowContainer(text => text.Font = text.Font.With(size: 12)) + topLinkContainer = new LinkFlowContainer(text => text.Font = text.Font.With(size: 12)) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -62,80 +65,55 @@ namespace osu.Game.Overlays.Profile.Header } }; - linkBlue = colours.BlueLight; - communityUserGrayGreenLighter = colours.CommunityUserGrayGreenLighter; + iconColour = colours.CommunityUserGrayGreenLighter; + + User.BindValueChanged(user => updateDisplay(user.NewValue)); } private void updateDisplay(User user) { - void bold(SpriteText t) => t.Font = t.Font.With(weight: FontWeight.Bold); - void addSpacer(OsuTextFlowContainer textFlow) => textFlow.AddArbitraryDrawable(new Container { Width = 15 }); - - bottomTopLinkContainer.Clear(); + topLinkContainer.Clear(); bottomLinkContainer.Clear(); if (user == null) return; if (user.JoinDate.ToUniversalTime().Year < 2008) - { - bottomTopLinkContainer.AddText("Here since the beginning"); - } + topLinkContainer.AddText("Here since the beginning"); else { - bottomTopLinkContainer.AddText("Joined "); - bottomTopLinkContainer.AddText(new DrawableDate(user.JoinDate), bold); + topLinkContainer.AddText("Joined "); + topLinkContainer.AddText(new DrawableDate(user.JoinDate), embolden); } - addSpacer(bottomTopLinkContainer); + addSpacer(topLinkContainer); if (user.PlayStyles?.Length > 0) { - bottomTopLinkContainer.AddText("Plays with "); - bottomTopLinkContainer.AddText(string.Join(", ", user.PlayStyles.Select(style => style.GetDescription())), bold); + topLinkContainer.AddText("Plays with "); + topLinkContainer.AddText(string.Join(", ", user.PlayStyles.Select(style => style.GetDescription())), embolden); - addSpacer(bottomTopLinkContainer); + addSpacer(topLinkContainer); } if (user.LastVisit.HasValue) { - bottomTopLinkContainer.AddText("Last seen "); - bottomTopLinkContainer.AddText(new DrawableDate(user.LastVisit.Value), bold); + topLinkContainer.AddText("Last seen "); + topLinkContainer.AddText(new DrawableDate(user.LastVisit.Value), embolden); - addSpacer(bottomTopLinkContainer); + addSpacer(topLinkContainer); } - bottomTopLinkContainer.AddText("Contributed "); - bottomTopLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: bold); - - void tryAddInfo(IconUsage icon, string content, string link = null) - { - if (string.IsNullOrEmpty(content)) return; - - bottomLinkContainer.AddIcon(icon, text => - { - text.Font = text.Font.With(size: 10); - text.Colour = communityUserGrayGreenLighter; - }); - if (link != null) - { - bottomLinkContainer.AddLink(" " + content, link, creationParameters: text => - { - bold(text); - text.Colour = linkBlue; - }); - } - else - bottomLinkContainer.AddText(" " + content, bold); - - addSpacer(bottomLinkContainer); - } + topLinkContainer.AddText("Contributed "); + topLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: embolden); string websiteWithoutProtcol = user.Website; if (!string.IsNullOrEmpty(websiteWithoutProtcol)) { - int protocolIndex = websiteWithoutProtcol.IndexOf("//", StringComparison.Ordinal); - if (protocolIndex >= 0) - websiteWithoutProtcol = websiteWithoutProtcol.Substring(protocolIndex + 2); + if (Uri.TryCreate(websiteWithoutProtcol, UriKind.Absolute, out var uri)) + { + websiteWithoutProtcol = uri.Host + uri.PathAndQuery + uri.Fragment; + websiteWithoutProtcol = websiteWithoutProtcol.TrimEnd('/'); + } } tryAddInfo(FontAwesome.Solid.MapMarker, user.Location); @@ -149,5 +127,27 @@ namespace osu.Game.Overlays.Profile.Header tryAddInfo(FontAwesome.Brands.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtcol, user.Website); } + + private void addSpacer(OsuTextFlowContainer textFlow) => textFlow.AddArbitraryDrawable(new Container { Width = 15 }); + + private void tryAddInfo(IconUsage icon, string content, string link = null) + { + if (string.IsNullOrEmpty(content)) return; + + bottomLinkContainer.AddIcon(icon, text => + { + text.Font = text.Font.With(size: 10); + text.Colour = iconColour; + }); + + if (link != null) + bottomLinkContainer.AddLink(" " + content, link, creationParameters: embolden); + else + bottomLinkContainer.AddText(" " + content, embolden); + + addSpacer(bottomLinkContainer); + } + + private void embolden(SpriteText text) => text.Font = text.Font.With(weight: FontWeight.Bold); } } From 0eca9b9683e25c81f6d509d9bd8f749967ca56ec Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Apr 2019 18:43:29 +0900 Subject: [PATCH 515/623] Center -> centre --- osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs | 1 + .../{CenterHeaderContainer.cs => CentreHeaderContainer.cs} | 2 +- osu.Game/Overlays/Profile/ProfileHeader.cs | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) rename osu.Game/Overlays/Profile/Header/{CenterHeaderContainer.cs => CentreHeaderContainer.cs} (99%) diff --git a/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs index bc2ff708c5..531f30de83 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs @@ -21,6 +21,7 @@ namespace osu.Game.Tests.Visual.Online typeof(RankGraph), typeof(LineGraph), typeof(ProfileHeaderTabControl), + typeof(CentreHeaderContainer), typeof(BottomHeaderContainer) }; diff --git a/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs similarity index 99% rename from osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs rename to osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index 3d0b9b9db4..0ad343bb7e 100644 --- a/osu.Game/Overlays/Profile/Header/CenterHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -18,7 +18,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Profile.Header { - public class CenterHeaderContainer : CompositeDrawable + public class CentreHeaderContainer : CompositeDrawable { public Action DetailsVisibilityAction; private bool detailsVisible; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 6238d1bc53..3e257e19bf 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Profile public ProfileHeader() { - CenterHeaderContainer centerHeaderContainer; + CentreHeaderContainer centreHeaderContainer; DetailHeaderContainer detailHeaderContainer; RelativeSizeAxes = Axes.X; @@ -91,7 +91,7 @@ namespace osu.Game.Overlays.Profile RelativeSizeAxes = Axes.X, User = { BindTarget = User }, }, - centerHeaderContainer = new CenterHeaderContainer + centreHeaderContainer = new CentreHeaderContainer { RelativeSizeAxes = Axes.X, User = { BindTarget = User }, @@ -118,7 +118,7 @@ namespace osu.Game.Overlays.Profile infoTabControl.AddItem("Info"); infoTabControl.AddItem("Modding"); - centerHeaderContainer.DetailsVisibilityAction = visible => detailHeaderContainer.Alpha = visible ? 0 : 1; + centreHeaderContainer.DetailsVisibilityAction = visible => detailHeaderContainer.Alpha = visible ? 0 : 1; User.ValueChanged += e => updateDisplay(e.NewValue); } From 9d5b81165e4432b8e8299b43f8fd2c63ba8eab2a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Apr 2019 19:51:05 +0900 Subject: [PATCH 516/623] Adjust button stylings --- .../Visual/Online/TestCaseUserProfileHeader.cs | 3 ++- .../Profile/Header/CentreHeaderContainer.cs | 15 +++++++++++++-- .../Profile/Header/ProfileHeaderButton.cs | 7 ++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs index 531f30de83..e425c25787 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs @@ -22,7 +22,8 @@ namespace osu.Game.Tests.Visual.Online typeof(LineGraph), typeof(ProfileHeaderTabControl), typeof(CentreHeaderContainer), - typeof(BottomHeaderContainer) + typeof(BottomHeaderContainer), + typeof(ProfileHeaderButton) }; [Resolved] diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index 0ad343bb7e..658dd79570 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -104,7 +105,7 @@ namespace osu.Game.Overlays.Profile.Header RelativeSizeAxes = Axes.Y, Padding = new MarginPadding { Vertical = 10 }, Width = UserProfileOverlay.CONTENT_X_MARGIN, - Child = detailsToggleButton = new ProfileHeaderButton + Child = detailsToggleButton = new ExpandButton { RelativeSizeAxes = Axes.Y, Anchor = Anchor.Centre, @@ -115,7 +116,7 @@ namespace osu.Game.Overlays.Profile.Header { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(20), + Size = new Vector2(20, 12), Icon = FontAwesome.Solid.ChevronUp, }, } @@ -230,5 +231,15 @@ namespace osu.Game.Overlays.Profile.Header hiddenDetailGlobal.Content = user.Statistics?.Ranks.Global?.ToString("#,##0") ?? "-"; hiddenDetailCountry.Content = user.Statistics?.Ranks.Country?.ToString("#,##0") ?? "-"; } + + private class ExpandButton : ProfileHeaderButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + IdleColour = colours.CommunityUserGrayGreen; + HoverColour = colours.CommunityUserGrayGreen.Darken(0.2f); + } + } } } diff --git a/osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs b/osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs index 6d9ab7a4d8..e8c8788a10 100644 --- a/osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs +++ b/osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs @@ -2,10 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK.Graphics; @@ -22,8 +22,9 @@ namespace osu.Game.Overlays.Profile.Header public ProfileHeaderButton() { - HoverColour = Color4.Black.Opacity(0.75f); - IdleColour = Color4.Black.Opacity(0.7f); + IdleColour = Color4.Black; + HoverColour = OsuColour.Gray(0.1f); + AutoSizeAxes = Axes.X; base.Content.Add(new CircularContainer From c6b3197dd0081aec6db80a2b1c7f00cb13124866 Mon Sep 17 00:00:00 2001 From: KingLuigi4932 Date: Thu, 25 Apr 2019 13:56:57 +0300 Subject: [PATCH 517/623] Add AdjustRank and use it in Hidden Mod --- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 3 ++ .../Visual/Gameplay/TestCasePlayerLoader.cs | 3 ++ .../Mods/IApplicableToScoreProcessor.cs | 3 ++ osu.Game/Rulesets/Mods/ModFlashlight.cs | 3 ++ osu.Game/Rulesets/Mods/ModHidden.cs | 18 +++++++++- osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 3 ++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 34 ++++++++----------- osu.Game/Screens/Play/Player.cs | 2 ++ 8 files changed, 48 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index f3c7939a94..445f81c6d4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osuTK; using osuTK.Graphics; @@ -41,6 +42,8 @@ namespace osu.Game.Rulesets.Osu.Mods scoreProcessor.Health.ValueChanged += health => { blinds.AnimateClosedness((float)health.NewValue); }; } + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + /// /// Element for the Blinds mod drawing 2 black boxes covering the whole screen which resize inside a restricted area with some leniency. /// diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs index f58c0d35b3..425d8fcda1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; @@ -110,6 +111,8 @@ namespace osu.Game.Tests.Visual.Gameplay { Applied = true; } + + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; } private class TestPlayer : Player diff --git a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs index 1d0ed94ef4..34e8d858f6 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { @@ -11,5 +12,7 @@ namespace osu.Game.Rulesets.Mods public interface IApplicableToScoreProcessor : IApplicableMod { void ApplyToScoreProcessor(ScoreProcessor scoreProcessor); + + ScoreRank AdjustRank(ScoreRank rank, double accuracy); } } diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 0ad99d13ff..f47306d16e 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osuTK; using osuTK.Graphics; @@ -46,6 +47,8 @@ namespace osu.Game.Rulesets.Mods Combo.BindTo(scoreProcessor.Combo); } + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { var flashlight = CreateFlashlight(); diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 389edb5a35..c55c45c9fa 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -9,6 +9,7 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { @@ -35,7 +36,22 @@ namespace osu.Game.Rulesets.Mods public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { - scoreProcessor.AdjustRank = true; + // Default value of ScoreProcessor's Rank in Hidden Mod should bes SS+ + scoreProcessor.Rank.Value = ScoreRank.XH; + } + + // TODO: Other mods that uses AdjustRank might have some issues due to rank changes + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) + { + switch (rank) + { + case ScoreRank.X: + return ScoreRank.XH; + case ScoreRank.S: + return ScoreRank.SH; + default: + return rank; + } } protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state) diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 6a82050d26..809661db8e 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { @@ -24,6 +25,8 @@ namespace osu.Game.Rulesets.Mods scoreProcessor.FailConditions += FailCondition; } + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + protected virtual bool FailCondition(ScoreProcessor scoreProcessor) => scoreProcessor.Combo.Value == 0; } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index b1cd78dde6..9f31cb8d7c 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -7,9 +7,11 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.TypeExtensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Scoring; @@ -60,6 +62,11 @@ namespace osu.Game.Rulesets.Scoring /// public readonly BindableInt Combo = new BindableInt(); + /// + /// The current selected mods + /// + public readonly Bindable> Mods = new Bindable>(Array.Empty()); + /// /// Create a for this processor. /// @@ -95,34 +102,22 @@ namespace osu.Game.Rulesets.Scoring /// protected virtual bool DefaultFailCondition => Health.Value == Health.MinValue; - private bool adjustRank; - - /// - /// Used by specific mods to adjust . - /// - public bool AdjustRank - { - get => adjustRank; - - set - { - adjustRank = value; - Rank.Value = rankFrom(Accuracy.Value); // Update rank immediately if AdjustRank was changed - } - } - protected ScoreProcessor() { Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); }; - Accuracy.ValueChanged += delegate { Rank.Value = rankFrom(Accuracy.Value); }; + Accuracy.ValueChanged += delegate + { + foreach (var mod in Mods.Value.OfType()) + Rank.Value = mod.AdjustRank(rankFrom(Accuracy.Value), Accuracy.Value); + }; } private ScoreRank rankFrom(double acc) { if (acc == 1) - return (adjustRank ? ScoreRank.XH : ScoreRank.X); + return ScoreRank.X; if (acc > 0.95) - return (adjustRank ? ScoreRank.SH : ScoreRank.S); + return ScoreRank.S; if (acc > 0.9) return ScoreRank.A; if (acc > 0.8) @@ -146,7 +141,6 @@ namespace osu.Game.Rulesets.Scoring Rank.Value = ScoreRank.X; HighestCombo.Value = 0; - AdjustRank = false; HasFailed = false; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index f833aa2bb7..5e2880e24b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -106,6 +106,8 @@ namespace osu.Game.Screens.Play showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); ScoreProcessor = DrawableRuleset.CreateScoreProcessor(); + ScoreProcessor.Mods.BindTo(Mods); + if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); From 2caea38f8c4f4e672a12197d03aee73e18e646cc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Apr 2019 20:05:59 +0900 Subject: [PATCH 518/623] Cleanup centre header container --- .../Profile/Header/BottomHeaderContainer.cs | 4 +-- .../Profile/Header/CentreHeaderContainer.cs | 36 ++++++++++--------- .../Profile/Header/OverlinedInfoContainer.cs | 3 +- .../Profile/Header/ProfileHeaderButton.cs | 4 +-- osu.Game/Overlays/Profile/ProfileHeader.cs | 2 +- 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 39dd1bd028..f97fecb913 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -35,6 +35,8 @@ namespace osu.Game.Overlays.Profile.Header [BackgroundDependencyLoader] private void load(OsuColour colours) { + iconColour = colours.CommunityUserGrayGreenLighter; + InternalChildren = new Drawable[] { new Box @@ -65,8 +67,6 @@ namespace osu.Game.Overlays.Profile.Header } }; - iconColour = colours.CommunityUserGrayGreenLighter; - User.BindValueChanged(user => updateDisplay(user.NewValue)); } diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index 658dd79570..935e25e4b8 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -21,8 +20,8 @@ namespace osu.Game.Overlays.Profile.Header { public class CentreHeaderContainer : CompositeDrawable { - public Action DetailsVisibilityAction; - private bool detailsVisible; + public readonly BindableBool DetailsVisible = new BindableBool(true); + public readonly Bindable User = new Bindable(); private OsuSpriteText followerText; private OsuSpriteText levelBadgeText; @@ -30,18 +29,20 @@ namespace osu.Game.Overlays.Profile.Header private Bar levelProgressBar; private OsuSpriteText levelProgressText; - private OverlinedInfoContainer hiddenDetailGlobal, hiddenDetailCountry; + private OverlinedInfoContainer hiddenDetailGlobal; + private OverlinedInfoContainer hiddenDetailCountry; - public readonly Bindable User = new Bindable(); + public CentreHeaderContainer() + { + Height = 60; + } [BackgroundDependencyLoader] private void load(OsuColour colours, TextureStore textures) { - Container hiddenDetailContainer, expandedDetailContainer; + Container hiddenDetailContainer; + Container expandedDetailContainer; SpriteIcon expandButtonIcon; - ProfileHeaderButton detailsToggleButton; - Height = 60; - User.ValueChanged += e => updateDisplay(e.NewValue); InternalChildren = new Drawable[] { @@ -105,11 +106,12 @@ namespace osu.Game.Overlays.Profile.Header RelativeSizeAxes = Axes.Y, Padding = new MarginPadding { Vertical = 10 }, Width = UserProfileOverlay.CONTENT_X_MARGIN, - Child = detailsToggleButton = new ExpandButton + Child = new ExpandButton { RelativeSizeAxes = Axes.Y, Anchor = Anchor.Centre, Origin = Anchor.Centre, + Action = () => DetailsVisible.Toggle(), Children = new Drawable[] { expandButtonIcon = new SpriteIcon @@ -210,14 +212,14 @@ namespace osu.Game.Overlays.Profile.Header } }; - detailsToggleButton.Action = () => + DetailsVisible.BindValueChanged(visible => { - detailsVisible = !detailsVisible; - expandButtonIcon.Icon = detailsVisible ? FontAwesome.Solid.ChevronDown : FontAwesome.Solid.ChevronUp; - hiddenDetailContainer.Alpha = detailsVisible ? 1 : 0; - expandedDetailContainer.Alpha = detailsVisible ? 0 : 1; - DetailsVisibilityAction(detailsVisible); - }; + expandButtonIcon.Icon = visible.NewValue ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown; + hiddenDetailContainer.Alpha = visible.NewValue ? 1 : 0; + expandedDetailContainer.Alpha = visible.NewValue ? 0 : 1; + }, true); + + User.BindValueChanged(user => updateDisplay(user.NewValue)); } private void updateDisplay(User user) diff --git a/osu.Game/Overlays/Profile/Header/OverlinedInfoContainer.cs b/osu.Game/Overlays/Profile/Header/OverlinedInfoContainer.cs index 6d15f96eea..2eb84c9d71 100644 --- a/osu.Game/Overlays/Profile/Header/OverlinedInfoContainer.cs +++ b/osu.Game/Overlays/Profile/Header/OverlinedInfoContainer.cs @@ -13,7 +13,8 @@ namespace osu.Game.Overlays.Profile.Header public class OverlinedInfoContainer : CompositeDrawable { private readonly Circle line; - private readonly OsuSpriteText title, content; + private readonly OsuSpriteText title; + private readonly OsuSpriteText content; public string Title { diff --git a/osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs b/osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs index e8c8788a10..300767cf0d 100644 --- a/osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs +++ b/osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs @@ -22,11 +22,11 @@ namespace osu.Game.Overlays.Profile.Header public ProfileHeaderButton() { + AutoSizeAxes = Axes.X; + IdleColour = Color4.Black; HoverColour = OsuColour.Gray(0.1f); - AutoSizeAxes = Axes.X; - base.Content.Add(new CircularContainer { Masking = true, diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 3e257e19bf..3ccf2af061 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -118,7 +118,7 @@ namespace osu.Game.Overlays.Profile infoTabControl.AddItem("Info"); infoTabControl.AddItem("Modding"); - centreHeaderContainer.DetailsVisibilityAction = visible => detailHeaderContainer.Alpha = visible ? 0 : 1; + centreHeaderContainer.DetailsVisible.BindValueChanged(visible => detailHeaderContainer.Alpha = visible.NewValue ? 1 : 0, true); User.ValueChanged += e => updateDisplay(e.NewValue); } From 8329e53b6ceea408c7c13fb1153ba4b29f1d1a09 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Apr 2019 20:08:14 +0900 Subject: [PATCH 519/623] Remove extra space from header title --- osu.Game/Overlays/Profile/ProfileHeader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 3ccf2af061..d9557952df 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -144,7 +144,7 @@ namespace osu.Game.Overlays.Profile { public ProfileHeaderTitle() { - Title = "Player "; + Title = "Player"; Section = "Info"; } From f8c5ee457bcdc3f46215860d74c91fa3394340dd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Apr 2019 20:09:42 +0900 Subject: [PATCH 520/623] Reduce tabcontrol spacing --- osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs index e7c9676550..c6b66b48d0 100644 --- a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs +++ b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Profile.Header public ProfileHeaderTabControl() { TabContainer.Masking = false; - TabContainer.Spacing = new Vector2(20, 0); + TabContainer.Spacing = new Vector2(15, 0); AddInternal(bar = new Box { From 619071b7ee1fb4d241e264feeb22b32d43952e11 Mon Sep 17 00:00:00 2001 From: KingLuigi4932 Date: Thu, 25 Apr 2019 14:23:00 +0300 Subject: [PATCH 521/623] Unnecessary 'using' directive --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index a4ba541499..aed5631f0f 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.TypeExtensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; From 9ce7d0416bc4dfdeb865507b5926565488b92970 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 25 Apr 2019 23:16:08 +0900 Subject: [PATCH 522/623] Fix framework version --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 25a98c9b74..2f0266ce07 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + From 838325fed41503c00b909da2067bae8b2bb257c1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Apr 2019 20:30:16 +0900 Subject: [PATCH 523/623] Remove HasTooltipContainer, use separate composites --- .../Online/TestCaseUserProfileHeader.cs | 1 + .../Profile/Header/CentreHeaderContainer.cs | 55 ++------------- .../Profile/Header/DetailHeaderContainer.cs | 38 +--------- .../Overlays/Profile/Header/LevelBadge.cs | 57 +++++++++++++++ .../Profile/Header/LevelProgressBar.cs | 65 +++++++++++++++++ .../Profile/Header/OverlinedTotalPlayTime.cs | 69 +++++++++++++++++++ osu.Game/Overlays/Profile/ProfileHeader.cs | 6 -- 7 files changed, 201 insertions(+), 90 deletions(-) create mode 100644 osu.Game/Overlays/Profile/Header/LevelBadge.cs create mode 100644 osu.Game/Overlays/Profile/Header/LevelProgressBar.cs create mode 100644 osu.Game/Overlays/Profile/Header/OverlinedTotalPlayTime.cs diff --git a/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs index e425c25787..0f1e954224 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs @@ -23,6 +23,7 @@ namespace osu.Game.Tests.Visual.Online typeof(ProfileHeaderTabControl), typeof(CentreHeaderContainer), typeof(BottomHeaderContainer), + typeof(DetailHeaderContainer), typeof(ProfileHeaderButton) }; diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index 935e25e4b8..e10d259ca9 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -11,10 +11,8 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Users; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays.Profile.Header { @@ -24,11 +22,6 @@ namespace osu.Game.Overlays.Profile.Header public readonly Bindable User = new Bindable(); private OsuSpriteText followerText; - private OsuSpriteText levelBadgeText; - - private Bar levelProgressBar; - private OsuSpriteText levelProgressText; - private OverlinedInfoContainer hiddenDetailGlobal; private OverlinedInfoContainer hiddenDetailCountry; @@ -132,56 +125,24 @@ namespace osu.Game.Overlays.Profile.Header Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN }, Children = new Drawable[] { - new ProfileHeader.HasTooltipContainer + new LevelBadge { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Size = new Vector2(40), - TooltipText = "Level", - Children = new Drawable[] - { - new Sprite - { - RelativeSizeAxes = Axes.Both, - Texture = textures.Get("Profile/levelbadge"), - Colour = colours.Yellow, - }, - levelBadgeText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 20) - } - } + User = { BindTarget = User } }, - expandedDetailContainer = new ProfileHeader.HasTooltipContainer + expandedDetailContainer = new Container { - TooltipText = "Progress to next level", Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Width = 200, Height = 6, Margin = new MarginPadding { Right = 50 }, - Children = new Drawable[] + Child = new LevelProgressBar { - new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = levelProgressBar = new Bar - { - RelativeSizeAxes = Axes.Both, - BackgroundColour = Color4.Black, - Direction = BarDirection.LeftToRight, - AccentColour = colours.Yellow - } - }, - levelProgressText = new OsuSpriteText - { - Anchor = Anchor.BottomRight, - Origin = Anchor.TopRight, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold) - } + RelativeSizeAxes = Axes.Both, + User = { BindTarget = User } } }, hiddenDetailContainer = new FillFlowContainer @@ -226,10 +187,6 @@ namespace osu.Game.Overlays.Profile.Header { followerText.Text = user.FollowerCount?.Length > 0 ? user.FollowerCount[0].ToString("#,##0") : "0"; - levelBadgeText.Text = user.Statistics?.Level.Current.ToString() ?? "0"; - levelProgressBar.Length = user.Statistics?.Level.Progress / 100f ?? 0; - levelProgressText.Text = user.Statistics?.Level.Progress.ToString("0'%'"); - hiddenDetailGlobal.Content = user.Statistics?.Ranks.Global?.ToString("#,##0") ?? "-"; hiddenDetailCountry.Content = user.Statistics?.Ranks.Country?.ToString("#,##0") ?? "-"; } diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index 84611b3bf1..62e57fef79 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -20,8 +20,7 @@ namespace osu.Game.Overlays.Profile.Header { public class DetailHeaderContainer : CompositeDrawable { - private ProfileHeader.HasTooltipContainer totalPlayTimeTooltip; - private OverlinedInfoContainer totalPlayTimeInfo, medalInfo, ppInfo; + private OverlinedInfoContainer medalInfo, ppInfo; private readonly Dictionary scoreRankInfos = new Dictionary(); private OverlinedInfoContainer detailGlobalRank, detailCountryRank; private RankGraph rankGraph; @@ -65,15 +64,9 @@ namespace osu.Game.Overlays.Profile.Header Spacing = new Vector2(10, 0), Children = new Drawable[] { - totalPlayTimeTooltip = new ProfileHeader.HasTooltipContainer + new OverlinedTotalPlayTime { - AutoSizeAxes = Axes.Both, - TooltipText = "0 hours", - Child = totalPlayTimeInfo = new OverlinedInfoContainer - { - Title = "Total Play Time", - LineColour = colours.Yellow, - }, + User = { BindTarget = User } }, medalInfo = new OverlinedInfoContainer { @@ -149,31 +142,6 @@ namespace osu.Game.Overlays.Profile.Header medalInfo.Content = user?.Achievements?.Length.ToString() ?? "0"; ppInfo.Content = user?.Statistics?.PP?.ToString("#,##0") ?? "0"; - string formatTime(int? secondsNull) - { - if (secondsNull == null) return "0h 0m"; - - int seconds = secondsNull.Value; - string time = ""; - - int days = seconds / 86400; - seconds -= days * 86400; - if (days > 0) - time += days + "d "; - - int hours = seconds / 3600; - seconds -= hours * 3600; - time += hours + "h "; - - int minutes = seconds / 60; - time += minutes + "m"; - - return time; - } - - totalPlayTimeInfo.Content = formatTime(user?.Statistics?.PlayTime); - totalPlayTimeTooltip.TooltipText = (user?.Statistics?.PlayTime ?? 0) / 3600 + " hours"; - foreach (var scoreRankInfo in scoreRankInfos) scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0; diff --git a/osu.Game/Overlays/Profile/Header/LevelBadge.cs b/osu.Game/Overlays/Profile/Header/LevelBadge.cs new file mode 100644 index 0000000000..8990956811 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/LevelBadge.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Users; + +namespace osu.Game.Overlays.Profile.Header +{ + public class LevelBadge : CompositeDrawable, IHasTooltip + { + public readonly Bindable User = new Bindable(); + + public string TooltipText { get; } + + private OsuSpriteText levelText; + + public LevelBadge() + { + TooltipText = "Level"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, TextureStore textures) + { + InternalChildren = new Drawable[] + { + new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = textures.Get("Profile/levelbadge"), + Colour = colours.Yellow, + }, + levelText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 20) + } + }; + + User.BindValueChanged(updateLevel); + } + + private void updateLevel(ValueChangedEvent user) + { + levelText.Text = user.NewValue?.Statistics?.Level.Current.ToString() ?? "0"; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/LevelProgressBar.cs b/osu.Game/Overlays/Profile/Header/LevelProgressBar.cs new file mode 100644 index 0000000000..20d30bd993 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/LevelProgressBar.cs @@ -0,0 +1,65 @@ +// Copyright (c) ppy Pty Ltd . 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Users; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header +{ + public class LevelProgressBar : CompositeDrawable, IHasTooltip + { + public readonly Bindable User = new Bindable(); + + public string TooltipText { get; } + + private Bar levelProgressBar; + private OsuSpriteText levelProgressText; + + public LevelProgressBar() + { + TooltipText = "Progress to next level"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChildren = new Drawable[] + { + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = levelProgressBar = new Bar + { + RelativeSizeAxes = Axes.Both, + BackgroundColour = Color4.Black, + Direction = BarDirection.LeftToRight, + AccentColour = colours.Yellow + } + }, + levelProgressText = new OsuSpriteText + { + Anchor = Anchor.BottomRight, + Origin = Anchor.TopRight, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold) + } + }; + + User.BindValueChanged(updateProgress); + } + + private void updateProgress(ValueChangedEvent user) + { + levelProgressBar.Length = user.NewValue?.Statistics?.Level.Progress / 100f ?? 0; + levelProgressText.Text = user.NewValue?.Statistics?.Level.Progress.ToString("0'%'"); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/OverlinedTotalPlayTime.cs b/osu.Game/Overlays/Profile/Header/OverlinedTotalPlayTime.cs new file mode 100644 index 0000000000..80c25ef4e5 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/OverlinedTotalPlayTime.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics; +using osu.Game.Users; + +namespace osu.Game.Overlays.Profile.Header +{ + public class OverlinedTotalPlayTime : CompositeDrawable, IHasTooltip + { + public readonly Bindable User = new Bindable(); + + public string TooltipText { get; set; } + + private OverlinedInfoContainer info; + + public OverlinedTotalPlayTime() + { + AutoSizeAxes = Axes.Both; + + TooltipText = "0 hours"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChild = info = new OverlinedInfoContainer + { + Title = "Total Play Time", + LineColour = colours.Yellow, + }; + + User.BindValueChanged(updateTime, true); + } + + private void updateTime(ValueChangedEvent user) + { + TooltipText = (user.NewValue?.Statistics?.PlayTime ?? 0) / 3600 + " hours"; + info.Content = formatTime(user.NewValue?.Statistics?.PlayTime); + } + + private string formatTime(int? secondsNull) + { + if (secondsNull == null) return "0h 0m"; + + int seconds = secondsNull.Value; + string time = ""; + + int days = seconds / 86400; + seconds -= days * 86400; + if (days > 0) + time += days + "d "; + + int hours = seconds / 3600; + seconds -= hours * 3600; + time += hours + "h "; + + int minutes = seconds / 60; + time += minutes + "m"; + + return time; + } + } +} diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index d9557952df..7969c645ec 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Game.Graphics; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; @@ -135,11 +134,6 @@ namespace osu.Game.Overlays.Profile coverContainer.User = user; } - public class HasTooltipContainer : Container, IHasTooltip - { - public string TooltipText { get; set; } - } - private class ProfileHeaderTitle : ScreenTitle { public ProfileHeaderTitle() From d5b91c6455b1fe9cae8ef37f98a2c4fffa6b3070 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Apr 2019 12:32:15 +0900 Subject: [PATCH 524/623] Cleanup top header container + user handling --- .../Profile/Header/CentreHeaderContainer.cs | 6 ++-- .../Overlays/Profile/Header/LevelBadge.cs | 6 ++-- .../Profile/Header/LevelProgressBar.cs | 8 ++--- .../Profile/Header/TopHeaderContainer.cs | 29 ++++++++++--------- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index e10d259ca9..7964d25db6 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -185,10 +185,10 @@ namespace osu.Game.Overlays.Profile.Header private void updateDisplay(User user) { - followerText.Text = user.FollowerCount?.Length > 0 ? user.FollowerCount[0].ToString("#,##0") : "0"; + followerText.Text = user?.FollowerCount?.Length > 0 ? user.FollowerCount[0].ToString("#,##0") : "0"; - hiddenDetailGlobal.Content = user.Statistics?.Ranks.Global?.ToString("#,##0") ?? "-"; - hiddenDetailCountry.Content = user.Statistics?.Ranks.Country?.ToString("#,##0") ?? "-"; + hiddenDetailGlobal.Content = user?.Statistics?.Ranks.Global?.ToString("#,##0") ?? "-"; + hiddenDetailCountry.Content = user?.Statistics?.Ranks.Country?.ToString("#,##0") ?? "-"; } private class ExpandButton : ProfileHeaderButton diff --git a/osu.Game/Overlays/Profile/Header/LevelBadge.cs b/osu.Game/Overlays/Profile/Header/LevelBadge.cs index 8990956811..cc05926be4 100644 --- a/osu.Game/Overlays/Profile/Header/LevelBadge.cs +++ b/osu.Game/Overlays/Profile/Header/LevelBadge.cs @@ -46,12 +46,12 @@ namespace osu.Game.Overlays.Profile.Header } }; - User.BindValueChanged(updateLevel); + User.BindValueChanged(user => updateLevel(user.NewValue)); } - private void updateLevel(ValueChangedEvent user) + private void updateLevel(User user) { - levelText.Text = user.NewValue?.Statistics?.Level.Current.ToString() ?? "0"; + levelText.Text = user?.Statistics?.Level.Current.ToString() ?? "0"; } } } diff --git a/osu.Game/Overlays/Profile/Header/LevelProgressBar.cs b/osu.Game/Overlays/Profile/Header/LevelProgressBar.cs index 20d30bd993..c043efb423 100644 --- a/osu.Game/Overlays/Profile/Header/LevelProgressBar.cs +++ b/osu.Game/Overlays/Profile/Header/LevelProgressBar.cs @@ -53,13 +53,13 @@ namespace osu.Game.Overlays.Profile.Header } }; - User.BindValueChanged(updateProgress); + User.BindValueChanged(user => updateProgress(user.NewValue)); } - private void updateProgress(ValueChangedEvent user) + private void updateProgress(User user) { - levelProgressBar.Length = user.NewValue?.Statistics?.Level.Progress / 100f ?? 0; - levelProgressText.Text = user.NewValue?.Statistics?.Level.Progress.ToString("0'%'"); + levelProgressBar.Length = user?.Statistics?.Level.Progress / 100f ?? 0; + levelProgressText.Text = user?.Statistics?.Level.Progress.ToString("0'%'"); } } } diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 80721af42f..50e19d430b 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -17,6 +17,10 @@ namespace osu.Game.Overlays.Profile.Header { public class TopHeaderContainer : CompositeDrawable { + private const float avatar_size = 110; + + public readonly Bindable User = new Bindable(); + private SupporterIcon supporterTag; private UpdateableAvatar avatar; private OsuSpriteText usernameText; @@ -26,15 +30,10 @@ namespace osu.Game.Overlays.Profile.Header private OsuSpriteText userCountryText; private FillFlowContainer userStats; - private const float avatar_size = 110; - - public readonly Bindable User = new Bindable(); - [BackgroundDependencyLoader] private void load(OsuColour colours) { Height = 150; - User.ValueChanged += e => updateDisplay(e.NewValue); InternalChildren = new Drawable[] { @@ -146,21 +145,23 @@ namespace osu.Game.Overlays.Profile.Header Spacing = new Vector2(0, 2) } }; + + User.BindValueChanged(user => updateUser(user.NewValue)); } - private void updateDisplay(User user) + private void updateUser(User user) { avatar.User = user; - usernameText.Text = user.Username; - openUserExternally.Link = $@"https://osu.ppy.sh/users/{user.Id}"; - userFlag.Country = user.Country; - userCountryText.Text = user.Country?.FullName ?? "Alien"; - supporterTag.SupporterLevel = user.SupportLevel; - titleText.Text = user.Title; - titleText.Colour = OsuColour.FromHex(user.Colour ?? "fff"); + usernameText.Text = user?.Username ?? string.Empty; + openUserExternally.Link = $@"https://osu.ppy.sh/users/{user?.Id ?? 0}"; + userFlag.Country = user?.Country; + userCountryText.Text = user?.Country?.FullName ?? "Alien"; + supporterTag.SupporterLevel = user?.SupportLevel ?? 0; + titleText.Text = user?.Title ?? string.Empty; + titleText.Colour = OsuColour.FromHex(user?.Colour ?? "fff"); userStats.Clear(); - if (user.Statistics != null) + if (user?.Statistics != null) { userStats.Add(new UserStatsLine("Ranked Score", user.Statistics.RankedScore.ToString("#,##0"))); userStats.Add(new UserStatsLine("Hit Accuracy", Math.Round(user.Statistics.Accuracy, 2).ToString("#0.00'%'"))); From 4adf590036e6b9a6674175e0a15870079601373b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Apr 2019 12:41:00 +0900 Subject: [PATCH 525/623] Combine hover/active state handling in tab control --- .../Profile/Header/ProfileHeaderTabControl.cs | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs index c6b66b48d0..3b16b102d5 100644 --- a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs +++ b/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs @@ -24,10 +24,10 @@ namespace osu.Game.Overlays.Profile.Header get => accentColour; set { - if (accentColour == value) return; + if (accentColour == value) + return; accentColour = value; - bar.Colour = value; foreach (TabItem tabItem in TabContainer) @@ -76,10 +76,13 @@ namespace osu.Game.Overlays.Profile.Header get => accentColour; set { - accentColour = value; + if (accentColour == value) + return; + accentColour = value; bar.Colour = value; - if (!Active.Value) text.Colour = value; + + updateState(); } } @@ -112,37 +115,40 @@ namespace osu.Game.Overlays.Profile.Header protected override bool OnHover(HoverEvent e) { - if (!Active.Value) - onActivated(true); - return base.OnHover(e); + base.OnHover(e); + + updateState(); + + return true; } protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - if (!Active.Value) - OnDeactivated(); + updateState(); } - protected override void OnActivated() - { - onActivated(); - } + protected override void OnActivated() => updateState(); - protected override void OnDeactivated() - { - text.FadeColour(AccentColour, 120, Easing.InQuad); - bar.ResizeHeightTo(0, 120, Easing.InQuad); - text.Font = text.Font.With(weight: FontWeight.Medium); - } + protected override void OnDeactivated() => updateState(); - private void onActivated(bool fake = false) + private void updateState() { - text.FadeColour(Color4.White, 120, Easing.InQuad); - bar.ResizeHeightTo(7.5f, 120, Easing.InQuad); - if (!fake) - text.Font = text.Font.With(weight: FontWeight.Bold); + if (Active.Value || IsHovered) + { + text.FadeColour(Color4.White, 120, Easing.InQuad); + bar.ResizeHeightTo(7.5f, 120, Easing.InQuad); + + if (Active.Value) + text.Font = text.Font.With(weight: FontWeight.Bold); + } + else + { + text.FadeColour(AccentColour, 120, Easing.InQuad); + bar.ResizeHeightTo(0, 120, Easing.InQuad); + text.Font = text.Font.With(weight: FontWeight.Medium); + } } } } From 7047303b0fcf68378f6ea315570ab8a279d0061e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Apr 2019 13:29:58 +0900 Subject: [PATCH 526/623] Add tooltips to all buttons --- .../Profile/Header/CentreHeaderContainer.cs | 63 ++----------------- .../Profile/Header/ExpandDetailsButton.cs | 45 +++++++++++++ .../Overlays/Profile/Header/FriendButton.cs | 58 +++++++++++++++++ .../Profile/Header/ProfileHeaderButton.cs | 7 ++- .../Profile/Header/ProfileMessageButton.cs | 2 + 5 files changed, 114 insertions(+), 61 deletions(-) create mode 100644 osu.Game/Overlays/Profile/Header/ExpandDetailsButton.cs create mode 100644 osu.Game/Overlays/Profile/Header/FriendButton.cs diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index 7964d25db6..1d947383a1 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -3,14 +3,11 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Users; using osuTK; @@ -21,7 +18,6 @@ namespace osu.Game.Overlays.Profile.Header public readonly BindableBool DetailsVisible = new BindableBool(true); public readonly Bindable User = new Bindable(); - private OsuSpriteText followerText; private OverlinedInfoContainer hiddenDetailGlobal; private OverlinedInfoContainer hiddenDetailCountry; @@ -35,7 +31,6 @@ namespace osu.Game.Overlays.Profile.Header { Container hiddenDetailContainer; Container expandedDetailContainer; - SpriteIcon expandButtonIcon; InternalChildren = new Drawable[] { @@ -54,37 +49,10 @@ namespace osu.Game.Overlays.Profile.Header Spacing = new Vector2(10, 0), Children = new Drawable[] { - new ProfileHeaderButton + new FriendButton { RelativeSizeAxes = Axes.Y, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Direction = FillDirection.Horizontal, - Padding = new MarginPadding { Right = 10 }, - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.Solid.User, - FillMode = FillMode.Fit, - Size = new Vector2(50, 14) - }, - followerText = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(weight: FontWeight.Bold) - } - } - } - } + User = { BindTarget = User } }, new ProfileMessageButton { @@ -99,22 +67,12 @@ namespace osu.Game.Overlays.Profile.Header RelativeSizeAxes = Axes.Y, Padding = new MarginPadding { Vertical = 10 }, Width = UserProfileOverlay.CONTENT_X_MARGIN, - Child = new ExpandButton + Child = new ExpandDetailsButton { RelativeSizeAxes = Axes.Y, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Action = () => DetailsVisible.Toggle(), - Children = new Drawable[] - { - expandButtonIcon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(20, 12), - Icon = FontAwesome.Solid.ChevronUp, - }, - } + DetailsVisible = { BindTarget = DetailsVisible } }, }, new Container @@ -175,7 +133,6 @@ namespace osu.Game.Overlays.Profile.Header DetailsVisible.BindValueChanged(visible => { - expandButtonIcon.Icon = visible.NewValue ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown; hiddenDetailContainer.Alpha = visible.NewValue ? 1 : 0; expandedDetailContainer.Alpha = visible.NewValue ? 0 : 1; }, true); @@ -185,20 +142,8 @@ namespace osu.Game.Overlays.Profile.Header private void updateDisplay(User user) { - followerText.Text = user?.FollowerCount?.Length > 0 ? user.FollowerCount[0].ToString("#,##0") : "0"; - hiddenDetailGlobal.Content = user?.Statistics?.Ranks.Global?.ToString("#,##0") ?? "-"; hiddenDetailCountry.Content = user?.Statistics?.Ranks.Country?.ToString("#,##0") ?? "-"; } - - private class ExpandButton : ProfileHeaderButton - { - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - IdleColour = colours.CommunityUserGrayGreen; - HoverColour = colours.CommunityUserGrayGreen.Darken(0.2f); - } - } } } diff --git a/osu.Game/Overlays/Profile/Header/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/ExpandDetailsButton.cs new file mode 100644 index 0000000000..dc507be0b1 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/ExpandDetailsButton.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header +{ + public class ExpandDetailsButton : ProfileHeaderButton + { + public readonly BindableBool DetailsVisible = new BindableBool(); + + public override string TooltipText => DetailsVisible.Value ? "collapse" : "expand"; + + private SpriteIcon icon; + + public ExpandDetailsButton() + { + Action = () => DetailsVisible.Toggle(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + IdleColour = colours.CommunityUserGrayGreen; + HoverColour = colours.CommunityUserGrayGreen.Darken(0.2f); + + Child = icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(20, 12) + }; + + DetailsVisible.BindValueChanged(visible => updateState(visible.NewValue), true); + } + + private void updateState(bool detailsVisible) => icon.Icon = detailsVisible ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown; + } +} diff --git a/osu.Game/Overlays/Profile/Header/FriendButton.cs b/osu.Game/Overlays/Profile/Header/FriendButton.cs new file mode 100644 index 0000000000..3b2f192fb1 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/FriendButton.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header +{ + public class FriendButton : ProfileHeaderButton + { + public readonly Bindable User = new Bindable(); + + public override string TooltipText => "friends"; + + private OsuSpriteText followerText; + + [BackgroundDependencyLoader] + private void load() + { + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Right = 10 }, + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.User, + FillMode = FillMode.Fit, + Size = new Vector2(50, 14) + }, + followerText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Bold) + } + } + }; + + User.BindValueChanged(user => updateFollowers(user.NewValue), true); + } + + private void updateFollowers(User user) => followerText.Text = user?.FollowerCount?.Length > 0 ? user.FollowerCount[0].ToString("#,##0") : "0"; + } +} diff --git a/osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs b/osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs index 300767cf0d..ddf2338873 100644 --- a/osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs +++ b/osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -11,8 +12,10 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Profile.Header { - public class ProfileHeaderButton : OsuHoverContainer + public abstract class ProfileHeaderButton : OsuHoverContainer, IHasTooltip { + public abstract string TooltipText { get; } + private readonly Box background; private readonly Container content; @@ -20,7 +23,7 @@ namespace osu.Game.Overlays.Profile.Header protected override IEnumerable EffectTargets => new[] { background }; - public ProfileHeaderButton() + protected ProfileHeaderButton() { AutoSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/Profile/Header/ProfileMessageButton.cs b/osu.Game/Overlays/Profile/Header/ProfileMessageButton.cs index 3121eae727..162d49cf1b 100644 --- a/osu.Game/Overlays/Profile/Header/ProfileMessageButton.cs +++ b/osu.Game/Overlays/Profile/Header/ProfileMessageButton.cs @@ -16,6 +16,8 @@ namespace osu.Game.Overlays.Profile.Header { public readonly Bindable User = new Bindable(); + public override string TooltipText => "send message"; + [Resolved(CanBeNull = true)] private ChannelManager channelManager { get; set; } From 2f4bf423a4d4ef4d56a91046ef4d0e70074188ee Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Apr 2019 13:49:44 +0900 Subject: [PATCH 527/623] Renamespace --- osu.Game.Tests/Visual/Online/TestCaseRankGraph.cs | 2 +- osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs | 2 +- .../Visual/Online/TestCaseUserProfileHeader.cs | 1 + .../Overlays/Profile/Header/CentreHeaderContainer.cs | 5 +++-- .../{FriendButton.cs => Components/AddFriendButton.cs} | 4 ++-- .../Profile/Header/{ => Components}/DrawableBadge.cs | 2 +- .../Header/{ => Components}/ExpandDetailsButton.cs | 2 +- .../Profile/Header/{ => Components}/LevelBadge.cs | 2 +- .../Header/{ => Components}/LevelProgressBar.cs | 2 +- .../MessageUserButton.cs} | 6 +++--- .../Header/{ => Components}/OverlinedInfoContainer.cs | 2 +- .../Header/{ => Components}/OverlinedTotalPlayTime.cs | 2 +- .../Header/{ => Components}/ProfileHeaderButton.cs | 2 +- .../Profile/Header/{ => Components}/RankGraph.cs | 2 +- .../Profile/Header/{ => Components}/SupporterIcon.cs | 2 +- .../Overlays/Profile/Header/DetailHeaderContainer.cs | 1 + .../Overlays/Profile/Header/MedalHeaderContainer.cs | 1 + osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs | 1 + osu.Game/Overlays/Profile/ProfileHeader.cs | 10 +++++----- osu.Game/Overlays/Profile/ProfileSection.cs | 2 +- osu.Game/Users/UserPanel.cs | 2 +- 21 files changed, 30 insertions(+), 25 deletions(-) rename osu.Game/Overlays/Profile/Header/{FriendButton.cs => Components/AddFriendButton.cs} (94%) rename osu.Game/Overlays/Profile/Header/{ => Components}/DrawableBadge.cs (95%) rename osu.Game/Overlays/Profile/Header/{ => Components}/ExpandDetailsButton.cs (96%) rename osu.Game/Overlays/Profile/Header/{ => Components}/LevelBadge.cs (96%) rename osu.Game/Overlays/Profile/Header/{ => Components}/LevelProgressBar.cs (97%) rename osu.Game/Overlays/Profile/Header/{ProfileMessageButton.cs => Components/MessageUserButton.cs} (91%) rename osu.Game/Overlays/Profile/Header/{ => Components}/OverlinedInfoContainer.cs (97%) rename osu.Game/Overlays/Profile/Header/{ => Components}/OverlinedTotalPlayTime.cs (97%) rename osu.Game/Overlays/Profile/Header/{ => Components}/ProfileHeaderButton.cs (96%) rename osu.Game/Overlays/Profile/Header/{ => Components}/RankGraph.cs (99%) rename osu.Game/Overlays/Profile/Header/{ => Components}/SupporterIcon.cs (97%) diff --git a/osu.Game.Tests/Visual/Online/TestCaseRankGraph.cs b/osu.Game.Tests/Visual/Online/TestCaseRankGraph.cs index dff018bf91..a92b788e83 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseRankGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseRankGraph.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Profile.Header; +using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Users; using osuTK; diff --git a/osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs index c455c092ed..0789c14b32 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseUserProfile.cs @@ -11,7 +11,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Profile; -using osu.Game.Overlays.Profile.Header; +using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Users; namespace osu.Game.Tests.Visual.Online diff --git a/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs index 0f1e954224..5f5ba89186 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseUserProfileHeader.cs @@ -9,6 +9,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile.Header; +using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Users; namespace osu.Game.Tests.Visual.Online diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index 1d947383a1..c1ad2011f8 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; +using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Users; using osuTK; @@ -49,12 +50,12 @@ namespace osu.Game.Overlays.Profile.Header Spacing = new Vector2(10, 0), Children = new Drawable[] { - new FriendButton + new AddFriendButton { RelativeSizeAxes = Axes.Y, User = { BindTarget = User } }, - new ProfileMessageButton + new MessageUserButton { User = { BindTarget = User } }, diff --git a/osu.Game/Overlays/Profile/Header/FriendButton.cs b/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs similarity index 94% rename from osu.Game/Overlays/Profile/Header/FriendButton.cs rename to osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs index 3b2f192fb1..2e4fd6fe3d 100644 --- a/osu.Game/Overlays/Profile/Header/FriendButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs @@ -11,9 +11,9 @@ using osu.Game.Graphics.Sprites; using osu.Game.Users; using osuTK; -namespace osu.Game.Overlays.Profile.Header +namespace osu.Game.Overlays.Profile.Header.Components { - public class FriendButton : ProfileHeaderButton + public class AddFriendButton : ProfileHeaderButton { public readonly Bindable User = new Bindable(); diff --git a/osu.Game/Overlays/Profile/Header/DrawableBadge.cs b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs similarity index 95% rename from osu.Game/Overlays/Profile/Header/DrawableBadge.cs rename to osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs index 75a8f4e415..ea259fe49a 100644 --- a/osu.Game/Overlays/Profile/Header/DrawableBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Textures; using osu.Game.Users; using osuTK; -namespace osu.Game.Overlays.Profile.Header +namespace osu.Game.Overlays.Profile.Header.Components { public class DrawableBadge : CompositeDrawable, IHasTooltip { diff --git a/osu.Game/Overlays/Profile/Header/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs similarity index 96% rename from osu.Game/Overlays/Profile/Header/ExpandDetailsButton.cs rename to osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs index dc507be0b1..089228b2cd 100644 --- a/osu.Game/Overlays/Profile/Header/ExpandDetailsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osuTK; -namespace osu.Game.Overlays.Profile.Header +namespace osu.Game.Overlays.Profile.Header.Components { public class ExpandDetailsButton : ProfileHeaderButton { diff --git a/osu.Game/Overlays/Profile/Header/LevelBadge.cs b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs similarity index 96% rename from osu.Game/Overlays/Profile/Header/LevelBadge.cs rename to osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs index cc05926be4..8069937810 100644 --- a/osu.Game/Overlays/Profile/Header/LevelBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs @@ -12,7 +12,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Users; -namespace osu.Game.Overlays.Profile.Header +namespace osu.Game.Overlays.Profile.Header.Components { public class LevelBadge : CompositeDrawable, IHasTooltip { diff --git a/osu.Game/Overlays/Profile/Header/LevelProgressBar.cs b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs similarity index 97% rename from osu.Game/Overlays/Profile/Header/LevelProgressBar.cs rename to osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs index c043efb423..6a6532764f 100644 --- a/osu.Game/Overlays/Profile/Header/LevelProgressBar.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs @@ -12,7 +12,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Users; using osuTK.Graphics; -namespace osu.Game.Overlays.Profile.Header +namespace osu.Game.Overlays.Profile.Header.Components { public class LevelProgressBar : CompositeDrawable, IHasTooltip { diff --git a/osu.Game/Overlays/Profile/Header/ProfileMessageButton.cs b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs similarity index 91% rename from osu.Game/Overlays/Profile/Header/ProfileMessageButton.cs rename to osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs index 162d49cf1b..cc6edcdd6a 100644 --- a/osu.Game/Overlays/Profile/Header/ProfileMessageButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs @@ -10,9 +10,9 @@ using osu.Game.Online.Chat; using osu.Game.Users; using osuTK; -namespace osu.Game.Overlays.Profile.Header +namespace osu.Game.Overlays.Profile.Header.Components { - public class ProfileMessageButton : ProfileHeaderButton + public class MessageUserButton : ProfileHeaderButton { public readonly Bindable User = new Bindable(); @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Profile.Header [Resolved] private IAPIProvider apiProvider { get; set; } - public ProfileMessageButton() + public MessageUserButton() { Content.Alpha = 0; RelativeSizeAxes = Axes.Y; diff --git a/osu.Game/Overlays/Profile/Header/OverlinedInfoContainer.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs similarity index 97% rename from osu.Game/Overlays/Profile/Header/OverlinedInfoContainer.cs rename to osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs index 2eb84c9d71..c40ddca688 100644 --- a/osu.Game/Overlays/Profile/Header/OverlinedInfoContainer.cs +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK.Graphics; -namespace osu.Game.Overlays.Profile.Header +namespace osu.Game.Overlays.Profile.Header.Components { public class OverlinedInfoContainer : CompositeDrawable { diff --git a/osu.Game/Overlays/Profile/Header/OverlinedTotalPlayTime.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs similarity index 97% rename from osu.Game/Overlays/Profile/Header/OverlinedTotalPlayTime.cs rename to osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs index 80c25ef4e5..2c88a83680 100644 --- a/osu.Game/Overlays/Profile/Header/OverlinedTotalPlayTime.cs +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.Cursor; using osu.Game.Graphics; using osu.Game.Users; -namespace osu.Game.Overlays.Profile.Header +namespace osu.Game.Overlays.Profile.Header.Components { public class OverlinedTotalPlayTime : CompositeDrawable, IHasTooltip { diff --git a/osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs similarity index 96% rename from osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs rename to osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs index ddf2338873..1650f11523 100644 --- a/osu.Game/Overlays/Profile/Header/ProfileHeaderButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs @@ -10,7 +10,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK.Graphics; -namespace osu.Game.Overlays.Profile.Header +namespace osu.Game.Overlays.Profile.Header.Components { public abstract class ProfileHeaderButton : OsuHoverContainer, IHasTooltip { diff --git a/osu.Game/Overlays/Profile/Header/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs similarity index 99% rename from osu.Game/Overlays/Profile/Header/RankGraph.cs rename to osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index d66f2306a0..bb54d0ac51 100644 --- a/osu.Game/Overlays/Profile/Header/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -17,7 +17,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Users; using osuTK; -namespace osu.Game.Overlays.Profile.Header +namespace osu.Game.Overlays.Profile.Header.Components { public class RankGraph : Container, IHasCustomTooltip { diff --git a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs similarity index 97% rename from osu.Game/Overlays/Profile/Header/SupporterIcon.cs rename to osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs index 569ed252b7..99b45aca93 100644 --- a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; -namespace osu.Game.Overlays.Profile.Header +namespace osu.Game.Overlays.Profile.Header.Components { public class SupporterIcon : CompositeDrawable, IHasTooltip { diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index 62e57fef79..89a9d7ddd1 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Scoring; using osu.Game.Users; using osuTK; diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs index 69b1203b9f..1e214b2d0c 100644 --- a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Users; using osuTK; using osuTK.Graphics; diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 50e19d430b..ce02e61d82 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Users; using osuTK; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 7969c645ec..f5233cf70c 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -2,16 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Overlays.Profile.Header; -using osu.Game.Users; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Profile.Header; +using osu.Game.Users; namespace osu.Game.Overlays.Profile { diff --git a/osu.Game/Overlays/Profile/ProfileSection.cs b/osu.Game/Overlays/Profile/ProfileSection.cs index 6da736432f..4d891384e8 100644 --- a/osu.Game/Overlays/Profile/ProfileSection.cs +++ b/osu.Game/Overlays/Profile/ProfileSection.cs @@ -2,13 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Users; +using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.Profile diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index eb7f0941fb..1a10e035cd 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -18,7 +18,7 @@ using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; -using osu.Game.Overlays.Profile.Header; +using osu.Game.Overlays.Profile.Header.Components; namespace osu.Game.Users { From 50a775145cb3eb8de776799c35ae35f1c03715d6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Apr 2019 13:56:26 +0900 Subject: [PATCH 528/623] Separate variables --- osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index 89a9d7ddd1..9bb0affe7d 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -21,9 +21,11 @@ namespace osu.Game.Overlays.Profile.Header { public class DetailHeaderContainer : CompositeDrawable { - private OverlinedInfoContainer medalInfo, ppInfo; private readonly Dictionary scoreRankInfos = new Dictionary(); - private OverlinedInfoContainer detailGlobalRank, detailCountryRank; + private OverlinedInfoContainer medalInfo; + private OverlinedInfoContainer ppInfo; + private OverlinedInfoContainer detailGlobalRank; + private OverlinedInfoContainer detailCountryRank; private RankGraph rankGraph; public readonly Bindable User = new Bindable(); From 32e71a6314c485107d24a9de9e2e4598d246fe88 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Apr 2019 16:15:42 +0900 Subject: [PATCH 529/623] Fix incorrect seeking behaviour of TrackVirtualManual --- osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index 78f9103a74..c558275f62 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osuTK; namespace osu.Game.Tests.Beatmaps { @@ -67,9 +68,10 @@ namespace osu.Game.Tests.Beatmaps public override bool Seek(double seek) { - offset = Math.Min(seek, Length); + offset = MathHelper.Clamp(seek, 0, Length); lastReferenceTime = null; - return true; + + return offset == seek; } public override void Start() From 41d0b00120c9da14bbbac7fb514e64e779cb6ab0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 27 Apr 2019 16:43:23 +0900 Subject: [PATCH 530/623] Update framework --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 74ed9f91dd..3b47005049 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9fff64c61c..20810886f3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 27c1c368ac715470e22b8dccfc5b42eed2745464 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 27 Apr 2019 20:31:00 +0900 Subject: [PATCH 531/623] Remove unused using --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index fd65d8f9cc..d72c334ed3 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -5,7 +5,6 @@ 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.Input.Bindings; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.UI; From 27ba89444ee7af1a82988fea3b3ecae91e7f1d8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 28 Apr 2019 01:08:14 +0900 Subject: [PATCH 532/623] Remove unnecessary using statements --- osu.Game/Overlays/Profile/ProfileHeader.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 8c220b2cb4..f5233cf70c 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -12,8 +12,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; -using Humanizer; -using osu.Framework.Graphics.Effects; namespace osu.Game.Overlays.Profile { From 8ab514933673967a02ee873cb34d40755ceac527 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 28 Apr 2019 20:11:36 +0900 Subject: [PATCH 533/623] SupporterLevel -> SupportLevel --- osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs | 2 +- osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs | 2 +- osu.Game/Users/UserPanel.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs index 99b45aca93..d4ccef8b69 100644 --- a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Header.Components public string TooltipText => "osu!supporter"; - public int SupporterLevel + public int SupportLevel { set { diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index ce02e61d82..c1fe430bdd 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -157,7 +157,7 @@ namespace osu.Game.Overlays.Profile.Header openUserExternally.Link = $@"https://osu.ppy.sh/users/{user?.Id ?? 0}"; userFlag.Country = user?.Country; userCountryText.Text = user?.Country?.FullName ?? "Alien"; - supporterTag.SupporterLevel = user?.SupportLevel ?? 0; + supporterTag.SupportLevel = user?.SupportLevel ?? 0; titleText.Text = user?.Title ?? string.Empty; titleText.Colour = OsuColour.FromHex(user?.Colour ?? "fff"); diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 75069273a5..47571b673d 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -191,7 +191,7 @@ namespace osu.Game.Users infoContainer.Add(new SupporterIcon { Height = 20f, - SupporterLevel = user.SupportLevel + SupportLevel = user.SupportLevel }); } From f9f6e1f04ae8cfbc3d1851a7746814baf5b189e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 28 Apr 2019 20:13:28 +0900 Subject: [PATCH 534/623] Clamp values to avoid potentially weird element --- .../Overlays/Profile/Header/Components/SupporterIcon.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs index d4ccef8b69..cb12a62702 100644 --- a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; +using osuTK; namespace osu.Game.Overlays.Profile.Header.Components { @@ -23,7 +24,9 @@ namespace osu.Game.Overlays.Profile.Header.Components { set { - if (value == 0) + int count = MathHelper.Clamp(value, 0, 3); + + if (count == 0) { content.Hide(); } @@ -31,7 +34,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { content.Show(); iconContainer.Clear(); - for (int i = 0; i < value; i++) + for (int i = 0; i < count; i++) { iconContainer.Add(new SpriteIcon { From 5d50316ae7565979ecb40604c74c7da0649b632c Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 30 Apr 2019 18:12:27 +0300 Subject: [PATCH 535/623] Add xmldoc --- osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs index 34e8d858f6..1b62dbdacf 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs @@ -13,6 +13,9 @@ namespace osu.Game.Rulesets.Mods { void ApplyToScoreProcessor(ScoreProcessor scoreProcessor); + /// + /// Adjusts a rank value passed by and returns it. + /// ScoreRank AdjustRank(ScoreRank rank, double accuracy); } } From 665558c297984e2a05590337440ee81bea864784 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 30 Apr 2019 18:44:06 +0300 Subject: [PATCH 536/623] Remove unnecessary comment --- osu.Game/Rulesets/Mods/ModHidden.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index c55c45c9fa..cda919c894 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -36,11 +36,10 @@ namespace osu.Game.Rulesets.Mods public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { - // Default value of ScoreProcessor's Rank in Hidden Mod should bes SS+ + // Default value of public interface IApplicableToScoreProcessor : IApplicableMod { + /// + /// Provide a to a mod. Called once on initialisation of a play instance. + /// void ApplyToScoreProcessor(ScoreProcessor scoreProcessor); /// - /// Adjusts a rank value passed by and returns it. + /// Called every time a rank calculation is requested. Allows mods to adjust the final rank. /// ScoreRank AdjustRank(ScoreRank rank, double accuracy); } From c6992bd6f9d89e2670592d4e9264bd3dfc645ee7 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 2 May 2019 03:37:15 -0700 Subject: [PATCH 547/623] Switch hidden/expanded centre profile header information --- osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index c1ad2011f8..c2f1d5f2ad 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -134,8 +134,8 @@ namespace osu.Game.Overlays.Profile.Header DetailsVisible.BindValueChanged(visible => { - hiddenDetailContainer.Alpha = visible.NewValue ? 1 : 0; - expandedDetailContainer.Alpha = visible.NewValue ? 0 : 1; + hiddenDetailContainer.Alpha = visible.NewValue ? 0 : 1; + expandedDetailContainer.Alpha = visible.NewValue ? 1 : 0; }, true); User.BindValueChanged(user => updateDisplay(user.NewValue)); From ac31a9640a6f3f8df8f14edd2229f2d936b43dd0 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 2 May 2019 03:47:20 -0700 Subject: [PATCH 548/623] Add number sign to centre info rankings --- osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index c2f1d5f2ad..0642ef94df 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -143,8 +143,8 @@ namespace osu.Game.Overlays.Profile.Header private void updateDisplay(User user) { - hiddenDetailGlobal.Content = user?.Statistics?.Ranks.Global?.ToString("#,##0") ?? "-"; - hiddenDetailCountry.Content = user?.Statistics?.Ranks.Country?.ToString("#,##0") ?? "-"; + hiddenDetailGlobal.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-"; + hiddenDetailCountry.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-"; } } } From 6bdaca1e3bf0a3ce4411a5d53f9ae64e80c8aec1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 May 2019 10:05:45 +0900 Subject: [PATCH 549/623] Fix mod equality checks not working as intended --- osu.Game/Online/API/Requests/Responses/APIMod.cs | 2 ++ osu.Game/Rulesets/Mods/IMod.cs | 3 ++- osu.Game/Rulesets/Mods/Mod.cs | 2 ++ osu.Game/Scoring/ScoreInfo.cs | 2 ++ osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs | 2 +- 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIMod.cs b/osu.Game/Online/API/Requests/Responses/APIMod.cs index d7dda07b33..b9da4f49ee 100644 --- a/osu.Game/Online/API/Requests/Responses/APIMod.cs +++ b/osu.Game/Online/API/Requests/Responses/APIMod.cs @@ -8,5 +8,7 @@ namespace osu.Game.Online.API.Requests.Responses public class APIMod : IMod { public string Acronym { get; set; } + + public bool Equals(IMod other) => Acronym == other?.Acronym; } } diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index 448ad0eb30..a5e19f293c 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -1,11 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using Newtonsoft.Json; namespace osu.Game.Rulesets.Mods { - public interface IMod + public interface IMod : IEquatable { /// /// The shortened name of this mod. diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index d2d0a5bb26..023d37497a 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -70,5 +70,7 @@ namespace osu.Game.Rulesets.Mods /// Creates a copy of this initialised to a default state. /// public virtual Mod CreateCopy() => (Mod)Activator.CreateInstance(GetType()); + + public bool Equals(IMod other) => GetType() == other?.GetType(); } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index d36f963016..8bdc30ac94 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -177,6 +177,8 @@ namespace osu.Game.Scoring protected class DeserializedMod : IMod { public string Acronym { get; set; } + + public bool Equals(IMod other) => Acronym == other?.Acronym; } public override string ToString() => $"{User} playing {Beatmap}"; diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index 9692dc513d..88c6fc5e2e 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Multi.Play if (ruleset.Value.ID != playlistItem.Ruleset.ID) throw new InvalidOperationException("Current Ruleset does not match PlaylistItem's Ruleset"); - if (!playlistItem.RequiredMods.All(m => Mods.Value.Contains(m))) + if (!playlistItem.RequiredMods.All(m => Mods.Value.Any(m.Equals))) throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods"); var req = new CreateRoomScoreRequest(roomId.Value ?? 0, playlistItem.ID); From 009eaa647a6acb5bf2d6b34bc02ce8223df891e3 Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Fri, 3 May 2019 16:38:15 +0800 Subject: [PATCH 550/623] fixes to FooterButtonRandom --- osu.Game/Screens/Select/FooterButtonRandom.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index 7466e1f243..61e95de7fa 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Select public FooterButtonRandom() { - textContainer.Add(secondaryText = new OsuSpriteText + TextContainer.Add(secondaryText = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -43,12 +43,12 @@ namespace osu.Game.Screens.Select { if (secondaryActive) { - spriteText.FadeOut(120, Easing.InQuad); + SpriteText.FadeOut(120, Easing.InQuad); secondaryText.FadeIn(120, Easing.InQuad); } else { - spriteText.FadeIn(120, Easing.InQuad); + SpriteText.FadeIn(120, Easing.InQuad); secondaryText.FadeOut(120, Easing.InQuad); } } From 31341bfeb181a5f2c3a71db7ae7010ed6115f839 Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Fri, 3 May 2019 16:51:33 +0800 Subject: [PATCH 551/623] use SongSelect's SelectedMods property to ensure it exists --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 7fb49e6dc6..db77323b3d 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -224,7 +224,7 @@ namespace osu.Game.Screens.Select if (Footer != null) { - Footer.AddButton(new FooterButtonMods(selectedMods), @"mods", colours.Yellow, ModSelect, Key.F1); + Footer.AddButton(new FooterButtonMods(SelectedMods), @"mods", colours.Yellow, ModSelect, Key.F1); Footer.AddButton(new FooterButtonRandom(), @"random", colours.Green, triggerRandom, Key.F2); Footer.AddButton(new FooterButton(), @"options", colours.Blue, BeatmapOptions, Key.F3); From 0fa02718786a0eefa063cce18e9e5351f509ab59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 May 2019 11:01:12 +0900 Subject: [PATCH 552/623] Add animation when collapsing or expanding the profile details section --- .../Profile/Header/CentreHeaderContainer.cs | 6 ++-- .../Profile/Header/DetailHeaderContainer.cs | 33 +++++++++++++++++-- osu.Game/Overlays/Profile/ProfileHeader.cs | 2 +- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index 0642ef94df..b441775393 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -134,9 +134,9 @@ namespace osu.Game.Overlays.Profile.Header DetailsVisible.BindValueChanged(visible => { - hiddenDetailContainer.Alpha = visible.NewValue ? 0 : 1; - expandedDetailContainer.Alpha = visible.NewValue ? 1 : 0; - }, true); + hiddenDetailContainer.FadeTo(visible.NewValue ? 0 : 1, 200, Easing.OutQuint); + expandedDetailContainer.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); + }); User.BindValueChanged(user => updateDisplay(user.NewValue)); } diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index 8fcf2711dd..de710c5fcd 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -26,14 +26,40 @@ namespace osu.Game.Overlays.Profile.Header private OverlinedInfoContainer ppInfo; private OverlinedInfoContainer detailGlobalRank; private OverlinedInfoContainer detailCountryRank; + private FillFlowContainer fillFlow; private RankGraph rankGraph; public readonly Bindable User = new Bindable(); + private bool expanded = true; + + public bool Expanded + { + set + { + if (expanded == value) return; + + expanded = value; + + if (fillFlow == null) return; + + fillFlow.ClearTransforms(); + + if (expanded) + fillFlow.AutoSizeAxes = Axes.Y; + else + { + fillFlow.AutoSizeAxes = Axes.None; + fillFlow.ResizeHeightTo(0, 200, Easing.OutQuint); + } + } + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { AutoSizeAxes = Axes.Y; + User.ValueChanged += e => updateDisplay(e.NewValue); InternalChildren = new Drawable[] @@ -43,10 +69,13 @@ namespace osu.Game.Overlays.Profile.Header RelativeSizeAxes = Axes.Both, Colour = colours.CommunityUserGrayGreenDarkest, }, - new FillFlowContainer + fillFlow = new FillFlowContainer { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + AutoSizeAxes = expanded ? Axes.Y : Axes.None, + AutoSizeDuration = 200, + AutoSizeEasing = Easing.OutQuint, + Masking = true, Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 20), diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index f5233cf70c..2d8c47b11a 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -117,7 +117,7 @@ namespace osu.Game.Overlays.Profile infoTabControl.AddItem("Info"); infoTabControl.AddItem("Modding"); - centreHeaderContainer.DetailsVisible.BindValueChanged(visible => detailHeaderContainer.Alpha = visible.NewValue ? 1 : 0, true); + centreHeaderContainer.DetailsVisible.BindValueChanged(visible => detailHeaderContainer.Expanded = visible.NewValue, true); User.ValueChanged += e => updateDisplay(e.NewValue); } From b9a39fb7880b3dfa818a0e3e59f89343b2241d51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Apr 2019 21:52:49 +0900 Subject: [PATCH 553/623] Fix instant pausing when game becomes inactive Resolves #4685. --- .../Screens/Play/HUD/HoldForMenuButton.cs | 24 +++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index c0ee5e6142..6f2738e4a1 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -70,6 +70,11 @@ namespace osu.Game.Screens.Play.HUD return base.OnMouseMove(e); } + public bool GameInactive + { + set => button.GameInactive = value; + } + protected override void Update() { base.Update(); @@ -184,6 +189,25 @@ namespace osu.Game.Screens.Play.HUD base.OnHoverLost(e); } + private bool gameInactive; + + public bool GameInactive + { + get => gameInactive; + set + { + if (gameInactive == value) + return; + + gameInactive = value; + + if (gameInactive) + BeginConfirm(); + else + AbortConfirm(); + } + } + public bool OnPressed(GlobalAction action) { switch (action) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 81fa348d72..516caf8744 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -393,8 +393,8 @@ namespace osu.Game.Screens.Play base.Update(); // eagerly pause when we lose window focus (if we are locally playing). - if (PauseOnFocusLost && !Game.IsActive.Value) - Pause(); + if (PauseOnFocusLost) + HUDOverlay.HoldToQuit.GameInactive = !Game.IsActive.Value; } public void Pause() From 3e3f12f27722c1dc2b2473a39be52dd48f64f45a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 May 2019 16:50:36 +0900 Subject: [PATCH 554/623] Use more local bindables --- .../Visual/Gameplay/TestCasePause.cs | 5 ++- .../Screens/Play/HUD/HoldForMenuButton.cs | 37 ++++++++++++------- osu.Game/Screens/Play/Player.cs | 11 ------ 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs index a52e84ed62..b9c7fd14bd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs @@ -196,9 +196,10 @@ namespace osu.Game.Tests.Visual.Gameplay public bool PauseOverlayVisible => PauseOverlay.State == Visibility.Visible; - public PausePlayer() + protected override void LoadComplete() { - PauseOnFocusLost = false; + base.LoadComplete(); + HUDOverlay.HoldToQuit.PauseOnFocusLost = false; } } } diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 6f2738e4a1..41591e98c0 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -70,9 +71,9 @@ namespace osu.Game.Screens.Play.HUD return base.OnMouseMove(e); } - public bool GameInactive + public bool PauseOnFocusLost { - set => button.GameInactive = value; + set => button.PauseOnFocusLost = value; } protected override void Update() @@ -98,8 +99,10 @@ namespace osu.Game.Screens.Play.HUD public Action HoverGained; public Action HoverLost; + private readonly IBindable gameActive = new Bindable(true); + [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, Framework.Game game) { Size = new Vector2(60); @@ -140,6 +143,9 @@ namespace osu.Game.Screens.Play.HUD }; bind(); + + gameActive.BindValueChanged(_ => updateActive()); + gameActive.BindTo(game.IsActive); } private void bind() @@ -189,25 +195,30 @@ namespace osu.Game.Screens.Play.HUD base.OnHoverLost(e); } - private bool gameInactive; + private bool pauseOnFocusLost; - public bool GameInactive + public bool PauseOnFocusLost { - get => gameInactive; set { - if (gameInactive == value) + if (pauseOnFocusLost == value) return; - gameInactive = value; - - if (gameInactive) - BeginConfirm(); - else - AbortConfirm(); + pauseOnFocusLost = value; + updateActive(); } } + private void updateActive() + { + if (!pauseOnFocusLost) return; + + if (gameActive.Value) + AbortConfirm(); + else + BeginConfirm(); + } + public bool OnPressed(GlobalAction action) { switch (action) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 516caf8744..fd9ddec314 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -44,8 +44,6 @@ namespace osu.Game.Screens.Play public bool HasFailed { get; private set; } - public bool PauseOnFocusLost { get; set; } = true; - private Bindable mouseWheelDisabled; private readonly Bindable storyboardReplacesBackground = new Bindable(); @@ -388,15 +386,6 @@ namespace osu.Game.Screens.Play // already resuming && !IsResuming; - protected override void Update() - { - base.Update(); - - // eagerly pause when we lose window focus (if we are locally playing). - if (PauseOnFocusLost) - HUDOverlay.HoldToQuit.GameInactive = !Game.IsActive.Value; - } - public void Pause() { if (!canPause) return; From 7fdc79dd68f32d13456aaa64994881215cf21b41 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 May 2019 20:20:41 +0900 Subject: [PATCH 555/623] Improve skip boundary logic to be closer to expectations Supersedes #4693. Closes #4676. --- osu.Game/Screens/Play/SkipOverlay.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 738877232d..65cf5e51f3 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -38,6 +38,10 @@ namespace osu.Game.Screens.Play public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; protected override bool BlockPositionalInput => false; + /// + /// Displays a skip overlay, giving the user the ability to skip forward. + /// + /// The time at which gameplay begins to appear. public SkipOverlay(double startTime) { this.startTime = startTime; @@ -87,16 +91,21 @@ namespace osu.Game.Screens.Play }; } - private const double skip_required_cutoff = 3000; + /// + /// Duration before gameplay start time required before skip button displays. + /// + private const double skip_buffer = 1000; + private const double fade_time = 300; - private double beginFadeTime => startTime - skip_required_cutoff - fade_time; + private double beginFadeTime => startTime - fade_time; protected override void LoadComplete() { base.LoadComplete(); - if (startTime < skip_required_cutoff) + // skip is not required if there is no extra "empty" time to skip. + if (Clock.CurrentTime > beginFadeTime - skip_buffer) { Alpha = 0; Expire(); @@ -107,7 +116,7 @@ namespace osu.Game.Screens.Play using (BeginAbsoluteSequence(beginFadeTime)) this.FadeOut(fade_time); - button.Action = () => RequestSeek?.Invoke(startTime - skip_required_cutoff - fade_time); + button.Action = () => RequestSeek?.Invoke(beginFadeTime); displayTime = Time.Current; From 8ef6a745f74ed7e3abb3eba77097819435c069d3 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 5 May 2019 22:43:03 -0700 Subject: [PATCH 556/623] Fix slider border not reverting to default color --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 57ea0abdd8..def034877a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.SkinChanged(skin, allowFallback); Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? Body.AccentColour; - Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Body.BorderColour; + Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Color4.White; Ball.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Ball.AccentColour; } From abc163fa36fac13a90d732267484b3eeffc94668 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 7 May 2019 12:07:45 +0900 Subject: [PATCH 557/623] Fix merge --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index a477868ad0..e174a25df3 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Mods shader.GetUniform("flashlightPos").UpdateValue(ref flashlightPosition); shader.GetUniform("flashlightSize").UpdateValue(ref flashlightSize); - shader.GetUniform("flashlightDim").UpdateValue(ref FlashlightDim); + shader.GetUniform("flashlightDim").UpdateValue(ref flashlightDim); Texture.WhitePixel.DrawQuad(screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); From f26a1cff6cc7b1666e450d190d2c54e57a860531 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 May 2019 12:14:57 +0900 Subject: [PATCH 558/623] Fix beatmap import crashing during room creation --- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 6271693a6a..a69b3d87ae 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -206,7 +206,7 @@ namespace osu.Game.Screens.Multi.Match if (Beatmap.Value != beatmapManager.DefaultBeatmap) return; - if (Beatmap.Value == null) + if (CurrentItem.Value == null) return; // Try to retrieve the corresponding local beatmap From 3661ddbff61c92dae456656974f5253a02681c59 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 May 2019 13:02:19 +0900 Subject: [PATCH 559/623] Fix player loader testcase potentially failing --- .../Visual/Gameplay/TestCasePlayerLoader.cs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs index 15c38e6d18..4ec0299c9a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -34,20 +35,20 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestLoadContinuation() { - AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player(false, false)))); + Player player = null; + SlowLoadPlayer slowPlayer = null; + + AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => player = new Player(false, false)))); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); + AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); AddStep("load slow dummy beatmap", () => { - SlowLoadPlayer slow = null; - - stack.Push(loader = new PlayerLoader(() => slow = new SlowLoadPlayer(false, false))); - - Scheduler.AddDelayed(() => slow.Ready = true, 5000); + stack.Push(loader = new PlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); + Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000); }); - AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); + AddUntilStep("wait for player to be current", () => slowPlayer.IsCurrentScreen()); } [Test] @@ -113,7 +114,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected class SlowLoadPlayer : Player { - public bool Ready; + public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(false); public SlowLoadPlayer(bool allowPause = true, bool showResults = true) : base(allowPause, showResults) @@ -123,8 +124,8 @@ namespace osu.Game.Tests.Visual.Gameplay [BackgroundDependencyLoader] private void load() { - while (!Ready) - Thread.Sleep(1); + if (!AllowLoad.Wait(TimeSpan.FromSeconds(10))) + throw new TimeoutException(); } } } From 13b9b04bb804c06f62be86de4a2b54f0aec2d930 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 May 2019 13:23:09 +0900 Subject: [PATCH 560/623] Apply more cases/fix some existing ones --- osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs | 1 + osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs | 1 + osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs | 6 ++++++ osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs | 5 ----- osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs | 2 ++ osu.Game/Graphics/OsuFont.cs | 1 - .../Online/API/Requests/Responses/APILegacyScoreInfo.cs | 1 - osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs | 1 + .../Overlays/Profile/Header/Components/SupporterIcon.cs | 1 + osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs | 2 ++ osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs | 1 + osu.Game/Overlays/Profile/ProfileHeader.cs | 4 ++-- osu.Game/Rulesets/Mods/ModHidden.cs | 2 ++ osu.Game/Rulesets/UI/Playfield.cs | 1 + osu.Game/Users/UserStatistics.cs | 5 +++++ 15 files changed, 25 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 0d0ca6506c..99b22b2d56 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -32,6 +32,7 @@ namespace osu.Game.Rulesets.Catch.Scoring { case HitResult.Miss: return hpDrainRate; + default: return 10.2 - hpDrainRate; // Award less HP as drain rate is increased } diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs index 48c2eb547b..b9c6e3a7f7 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Mania.Judgements { case HitResult.Miss: return 0; + default: return 0.040; } diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs index 3727966390..0e4c811945 100644 --- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs @@ -36,16 +36,22 @@ namespace osu.Game.Rulesets.Mania.Judgements { case HitResult.Miss: return -0.125; + case HitResult.Meh: return 0.005; + case HitResult.Ok: return 0.010; + case HitResult.Good: return 0.035; + case HitResult.Great: return 0.055; + case HitResult.Perfect: return 0.065; + default: return 0; } diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 7a39263edf..5caf08fb1e 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -85,11 +85,6 @@ namespace osu.Game.Rulesets.Mania.Scoring protected override double HealthAdjustmentFactorFor(JudgementResult result) => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier; - - - - - public override HitWindows CreateHitWindows() => new ManiaHitWindows(); } } diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs index 1531b64cc6..7a5b98864c 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs @@ -34,10 +34,12 @@ namespace osu.Game.Rulesets.Osu.Judgements { case HitResult.Miss: return -0.02; + case HitResult.Meh: case HitResult.Good: case HitResult.Great: return 0.01; + default: return 0; } diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 121f5b6f81..22250d4a56 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -43,7 +43,6 @@ namespace osu.Game.Graphics case Typeface.Exo: return "Exo2.0"; - case Typeface.Venera: return "Venera"; } diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index 358c571719..3060300077 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -70,7 +70,6 @@ namespace osu.Game.Online.API.Requests.Responses { foreach (var kvp in value) { - switch (kvp.Key) { case @"count_geki": diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index f97fecb913..52336dcd30 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -107,6 +107,7 @@ namespace osu.Game.Overlays.Profile.Header topLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: embolden); string websiteWithoutProtcol = user.Website; + if (!string.IsNullOrEmpty(websiteWithoutProtcol)) { if (Uri.TryCreate(websiteWithoutProtcol, UriKind.Absolute, out var uri)) diff --git a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs index cb12a62702..97454d7327 100644 --- a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs @@ -34,6 +34,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { content.Show(); iconContainer.Clear(); + for (int i = 0; i < count; i++) { iconContainer.Add(new SpriteIcon diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs index 1e214b2d0c..25d04195b2 100644 --- a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -68,9 +68,11 @@ namespace osu.Game.Overlays.Profile.Header { var badges = user.Badges; badgeFlowContainer.Clear(); + if (badges?.Length > 0) { Show(); + for (var index = 0; index < badges.Length; index++) { int displayIndex = index; diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index c1fe430bdd..2ac7f3cc96 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -162,6 +162,7 @@ namespace osu.Game.Overlays.Profile.Header titleText.Colour = OsuColour.FromHex(user?.Colour ?? "fff"); userStats.Clear(); + if (user?.Statistics != null) { userStats.Add(new UserStatsLine("Ranked Score", user.Statistics.RankedScore.ToString("#,##0"))); diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index c742b8f0d7..2d8c47b11a 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -135,7 +135,6 @@ namespace osu.Game.Overlays.Profile } private class ProfileHeaderTitle : ScreenTitle - { public ProfileHeaderTitle() { @@ -145,8 +144,9 @@ namespace osu.Game.Overlays.Profile [BackgroundDependencyLoader] private void load(OsuColour colours) - + { AccentColour = colours.CommunityUserGreen; + } } } } diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index ea1c56623f..0934992f55 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -46,8 +46,10 @@ namespace osu.Game.Rulesets.Mods { case ScoreRank.X: return ScoreRank.XH; + case ScoreRank.S: return ScoreRank.SH; + default: return rank; } diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index a073ad246b..f2e7f51b52 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -67,6 +67,7 @@ namespace osu.Game.Rulesets.UI private void load() { Cursor = CreateCursor(); + if (Cursor != null) { // initial showing of the cursor will be handed by MenuCursorContainer (via DrawableRuleset's IProvideCursor implementation). diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 752534a80d..7afbef01c5 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -85,14 +85,19 @@ namespace osu.Game.Users { case ScoreRank.XH: return SSPlus; + case ScoreRank.X: return SS; + case ScoreRank.SH: return SPlus; + case ScoreRank.S: return S; + case ScoreRank.A: return A; + default: throw new ArgumentException($"API does not return {rank.ToString()}"); } From 652a21b3dcac5a2b6c17f14011df8b31b003d8c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 May 2019 13:31:54 +0900 Subject: [PATCH 561/623] Update r# CLT --- build/build.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.cake b/build/build.cake index de94eb7ab3..1d2588de49 100644 --- a/build/build.cake +++ b/build/build.cake @@ -1,5 +1,5 @@ #addin "nuget:?package=CodeFileSanity&version=0.0.21" -#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.3.4" +#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.1.1" #tool "nuget:?package=NVika.MSBuild&version=1.0.1" var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); From f7a570d67abab39e854b1dbe5f34f6a6571f6fd5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 May 2019 13:34:06 +0900 Subject: [PATCH 562/623] Fix double confirmation --- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 41591e98c0..446df94aca 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -144,10 +144,15 @@ namespace osu.Game.Screens.Play.HUD bind(); - gameActive.BindValueChanged(_ => updateActive()); gameActive.BindTo(game.IsActive); } + protected override void LoadComplete() + { + base.LoadComplete(); + gameActive.BindValueChanged(_ => updateActive(), true); + } + private void bind() { circularProgress.Current.BindTo(Progress); @@ -195,7 +200,7 @@ namespace osu.Game.Screens.Play.HUD base.OnHoverLost(e); } - private bool pauseOnFocusLost; + private bool pauseOnFocusLost = true; public bool PauseOnFocusLost { @@ -205,7 +210,8 @@ namespace osu.Game.Screens.Play.HUD return; pauseOnFocusLost = value; - updateActive(); + if (IsLoaded) + updateActive(); } } From 1cfb7550eee8c295e416d8f5c7749e3ec392e664 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 May 2019 13:42:55 +0900 Subject: [PATCH 563/623] Fix possible nullref --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 5fed2a63e1..9681350ade 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -175,7 +175,7 @@ namespace osu.Desktop.Updater public SquirrelLogger() { - var file = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "SquirrelSetupUpdater.log"); + var file = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location ?? Directory.GetCurrentDirectory()), "SquirrelSetupUpdater.log"); if (File.Exists(file)) File.Delete(file); path = file; } From 99f2ee0e48244308897b1a7bac23bce0ff08a09d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 May 2019 15:09:03 +0900 Subject: [PATCH 564/623] Fix CI issues --- osu.Game/Online/Leaderboards/DrawableRank.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/Leaderboards/DrawableRank.cs b/osu.Game/Online/Leaderboards/DrawableRank.cs index 5224150181..ce64395dde 100644 --- a/osu.Game/Online/Leaderboards/DrawableRank.cs +++ b/osu.Game/Online/Leaderboards/DrawableRank.cs @@ -44,14 +44,17 @@ namespace osu.Game.Online.Leaderboards private void updateTexture() { string textureName; + switch (Rank) { default: textureName = Rank.GetDescription(); break; + case ScoreRank.SH: textureName = "SPlus"; break; + case ScoreRank.XH: textureName = "SSPlus"; break; From 1d2db85866906bf17e56f93a488d80df49ed5adf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 May 2019 17:23:44 +0900 Subject: [PATCH 565/623] Improve background sprite testcase --- ...stCaseUpdateableBeatmapBackgroundSprite.cs | 67 ++++++++++++++----- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs index 74114b2e53..dc0aff420a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -11,12 +11,15 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; using osu.Game.Tests.Beatmaps.IO; +using osuTK; namespace osu.Game.Tests.Visual.UserInterface { public class TestCaseUpdateableBeatmapBackgroundSprite : OsuTestCase { - private TestUpdateableBeatmapBackgroundSprite backgroundSprite; + private BeatmapSetInfo testBeatmap; + private IAPIProvider api; + private RulesetStore rulesets; [Resolved] private BeatmapManager beatmaps { get; set; } @@ -24,40 +27,68 @@ namespace osu.Game.Tests.Visual.UserInterface [BackgroundDependencyLoader] private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets) { - Bindable beatmapBindable = new Bindable(); + this.api = api; + this.rulesets = rulesets; - var imported = ImportBeatmapTest.LoadOszIntoOsu(osu); + testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu); + } - Child = backgroundSprite = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }; + [Test] + public void TestNullBeatmap() + { + TestUpdateableBeatmapBackgroundSprite background = null; - backgroundSprite.Beatmap.BindTo(beatmapBindable); + AddStep("load null beatmap", () => Child = background = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }); + AddUntilStep("wait for load", () => background.ContentLoaded); + } - var req = new GetBeatmapSetRequest(1); - api.Queue(req); + [Test] + public void TestLocalBeatmap() + { + TestUpdateableBeatmapBackgroundSprite background = null; - AddStep("load null beatmap", () => beatmapBindable.Value = null); - AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1); - AddStep("load imported beatmap", () => beatmapBindable.Value = imported.Beatmaps.First()); - AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1); + AddStep("load local beatmap", () => + { + Child = background = new TestUpdateableBeatmapBackgroundSprite + { + RelativeSizeAxes = Axes.Both, + Beatmap = { Value = testBeatmap.Beatmaps.First() } + }; + }); + AddUntilStep("wait for load", () => background.ContentLoaded); + } + + [Test] + public void TestOnlineBeatmap() + { if (api.IsLoggedIn) { + var req = new GetBeatmapSetRequest(1); + api.Queue(req); + AddUntilStep("wait for api response", () => req.Result != null); - AddStep("load online beatmap", () => beatmapBindable.Value = new BeatmapInfo + + TestUpdateableBeatmapBackgroundSprite background = null; + + AddStep("load online beatmap", () => { - BeatmapSet = req.Result?.ToBeatmapSet(rulesets) + Child = background = new TestUpdateableBeatmapBackgroundSprite + { + RelativeSizeAxes = Axes.Both, + Beatmap = { Value = new BeatmapInfo { BeatmapSet = req.Result?.ToBeatmapSet(rulesets) } } + }; }); - AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1); + + AddUntilStep("wait for load", () => background.ContentLoaded); } else - { AddStep("online (login first)", () => { }); - } } private class TestUpdateableBeatmapBackgroundSprite : UpdateableBeatmapBackgroundSprite { - public int ChildCount => InternalChildren.Count; + public bool ContentLoaded => ((DelayedLoadUnloadWrapper)InternalChildren.LastOrDefault())?.Content?.IsLoaded ?? false; } } } From a00e2b18a9034e5f1736d1b1c99489d8c1c47463 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 May 2019 17:24:05 +0900 Subject: [PATCH 566/623] Fix background unloading/reloading sometimes crashing --- ...stCaseUpdateableBeatmapBackgroundSprite.cs | 55 +++++++++++++++++++ .../UpdateableBeatmapBackgroundSprite.cs | 9 ++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs index dc0aff420a..a4dd7b83e2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . 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 NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Online.API; @@ -86,8 +88,61 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("online (login first)", () => { }); } + [Test] + public void TestUnloadAndReload() + { + var backgrounds = new List(); + ScrollContainer scrollContainer = null; + + AddStep("create backgrounds hierarchy", () => + { + FillFlowContainer backgroundFlow; + + Child = scrollContainer = new ScrollContainer + { + Size = new Vector2(500), + Child = backgroundFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Padding = new MarginPadding { Bottom = 250 } + } + }; + + for (int i = 0; i < 25; i++) + { + var background = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }; + + if (i % 2 == 0) + background.Beatmap.Value = testBeatmap.Beatmaps.First(); + + backgroundFlow.Add(new Container + { + RelativeSizeAxes = Axes.X, + Height = 100, + Masking = true, + Child = background + }); + + backgrounds.Add(background); + } + }); + + var loadedBackgrounds = backgrounds.Where(b => b.ContentLoaded); + + int initialLoadCount = 0; + + AddUntilStep("some loaded", () => (initialLoadCount = loadedBackgrounds.Count()) > 0); + AddStep("scroll to bottom", () => scrollContainer.ScrollToEnd()); + AddUntilStep("some unloaded", () => loadedBackgrounds.Count() < initialLoadCount); + } + private class TestUpdateableBeatmapBackgroundSprite : UpdateableBeatmapBackgroundSprite { + protected override double UnloadDelay => 2000; + public bool ContentLoaded => ((DelayedLoadUnloadWrapper)InternalChildren.LastOrDefault())?.Content?.IsLoaded ?? false; } } diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index ce7811fc0d..bd80919851 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -26,6 +26,11 @@ namespace osu.Game.Beatmaps.Drawables this.beatmapSetCoverType = beatmapSetCoverType; } + /// + /// Delay before the background is unloaded while off-screen. + /// + protected virtual double UnloadDelay => 10000; + private BeatmapInfo lastModel; protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Drawable content, double timeBeforeLoad) @@ -34,13 +39,13 @@ namespace osu.Game.Beatmaps.Drawables { // If DelayedLoadUnloadWrapper is attempting to RELOAD the same content (Beatmap), that means that it was // previously UNLOADED and thus its children have been disposed of, so we need to recreate them here. - if (lastModel == Beatmap.Value && Beatmap.Value != null) + if (lastModel == Beatmap.Value) return CreateDrawable(Beatmap.Value); // If the model has changed since the previous unload (or if there was no load), then we can safely use the given content lastModel = Beatmap.Value; return content; - }, timeBeforeLoad, 10000); + }, timeBeforeLoad, UnloadDelay); } protected override Drawable CreateDrawable(BeatmapInfo model) From ff3c2265967ef633bdf91ba616d95f5bd9f3a60e Mon Sep 17 00:00:00 2001 From: David Zhao Date: Wed, 8 May 2019 13:37:03 +0900 Subject: [PATCH 567/623] Give ZoomableScrollContainer an initial width --- .../Editor/TestCaseZoomableScrollContainer.cs | 60 +++++++++++-------- .../Timeline/ZoomableScrollContainer.cs | 8 +++ 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestCaseZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editor/TestCaseZoomableScrollContainer.cs index e2cf1ef28a..55c978ae06 100644 --- a/osu.Game.Tests/Visual/Editor/TestCaseZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseZoomableScrollContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; +using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -18,38 +19,49 @@ namespace osu.Game.Tests.Visual.Editor { public class TestCaseZoomableScrollContainer : ManualInputManagerTestCase { - private readonly ZoomableScrollContainer scrollContainer; - private readonly Drawable innerBox; + private ZoomableScrollContainer scrollContainer; + private Drawable innerBox; - public TestCaseZoomableScrollContainer() + [SetUpSteps] + public void SetUpSteps() { - Children = new Drawable[] + AddStep("Add new scroll container", () => { - new Container + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = 250, - Width = 0.75f, - Children = new Drawable[] + new Container { - new Box + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = 250, + Width = 0.75f, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(30) - }, - scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both } - } - }, - new MenuCursor() - }; + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(30) + }, + scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both } + } + }, + new MenuCursor() + }; - scrollContainer.Add(innerBox = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(new Color4(0.8f, 0.6f, 0.4f, 1f), new Color4(0.4f, 0.6f, 0.8f, 1f)) + scrollContainer.Add(innerBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(new Color4(0.8f, 0.6f, 0.4f, 1f), new Color4(0.4f, 0.6f, 0.8f, 1f)) + }); }); + AddUntilStep("Scroll container is loaded", () => scrollContainer.LoadState >= LoadState.Loaded); + } + + [Test] + public void TestWidthInitialization() + { + AddAssert("Inner container width was initialized", () => innerBox.DrawWidth > 0); } [Test] diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 9b00a3998d..ee6e29c92e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -102,6 +102,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return true; } + protected override void LoadComplete() + { + base.LoadComplete(); + + // This width only gets updated on the application of a transform, so this needs to be initialized here. + zoomedContent.Width = DrawWidth * currentZoom; + } + private float zoomTarget = 1; private void setZoomTarget(float newZoom, float focusPoint) From bace8296297851ed337ab71afeec50f7350a0169 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 May 2019 18:42:26 +0900 Subject: [PATCH 568/623] Add ability to avoid expand animation in ModDisplay --- osu.Game/Screens/Play/HUD/ModDisplay.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 878d2b7c38..eeed74ae94 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -24,6 +24,8 @@ namespace osu.Game.Screens.Play.HUD public bool DisplayUnrankedText = true; + public bool AllowExpand = true; + private readonly Bindable> current = new Bindable>(); public Bindable> Current @@ -87,7 +89,9 @@ namespace osu.Game.Screens.Play.HUD protected override void LoadComplete() { base.LoadComplete(); + appearTransform(); + iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); } private void appearTransform() @@ -97,14 +101,17 @@ namespace osu.Game.Screens.Play.HUD else unrankedText.Hide(); - iconsContainer.FinishTransforms(); - iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); expand(); + using (iconsContainer.BeginDelayedSequence(1200)) contract(); } - private void expand() => iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); + private void expand() + { + if (AllowExpand) + iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); + } private void contract() => iconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint); From 9a9ac05cd915c74b909bd3ad5372fa8a354db953 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 May 2019 18:42:54 +0900 Subject: [PATCH 569/623] Fix post-merge issues --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ae1343099c..d43ca33ee3 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -223,7 +223,7 @@ namespace osu.Game.Screens.Select if (Footer != null) { - Footer.AddButton(new FooterButtonMods(SelectedMods), @"mods", colours.Yellow, ModSelect, Key.F1); + Footer.AddButton(new FooterButtonMods(mods), @"mods", colours.Yellow, ModSelect, Key.F1); Footer.AddButton(new FooterButtonRandom(), @"random", colours.Green, triggerRandom, Key.F2); Footer.AddButton(new FooterButton(), @"options", colours.Blue, BeatmapOptions, Key.F3); From 8906eb874ae7a015a5932b858b6ee586a9bd8267 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 May 2019 18:43:06 +0900 Subject: [PATCH 570/623] Fix CI issues --- osu.Game/Screens/Select/Footer.cs | 1 + osu.Game/Screens/Select/FooterButtonMods.cs | 7 ++++--- osu.Game/Screens/Select/FooterButtonRandom.cs | 1 - 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index c991e5c350..4466fb418c 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -56,6 +56,7 @@ namespace osu.Game.Screens.Select private readonly List overlays = new List(); + /// THe button to be added. /// Text on the button. /// Colour of the button. /// Hotkey of the button. diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 0a5b8c0929..639804b3bd 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Select { public class FooterButtonMods : FooterButton { - public FooterButtonMods(Bindable> mods) + public FooterButtonMods(Bindable> mods) { FooterModDisplay modDisplay; @@ -21,7 +21,8 @@ namespace osu.Game.Screens.Select { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Child = modDisplay = new FooterModDisplay { + Child = modDisplay = new FooterModDisplay + { DisplayUnrankedText = false, Scale = new Vector2(0.8f) }, diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index 61e95de7fa..a725bfc103 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics.Sprites; -using System; namespace osu.Game.Screens.Select { From b33372ca629e46f4d399c9d35fe360bb9090843c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 May 2019 18:43:15 +0900 Subject: [PATCH 571/623] Don't expand mods in button on hover --- osu.Game/Screens/Select/FooterButtonMods.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 639804b3bd..3de668bbf0 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; @@ -37,6 +37,11 @@ namespace osu.Game.Screens.Select private class FooterModDisplay : ModDisplay { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false; + + public FooterModDisplay() + { + AllowExpand = false; + } } } } From 772eb460fbd477fac46d5948bc133e0bc947b433 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 May 2019 19:03:26 +0900 Subject: [PATCH 572/623] Move button definitions to their respective classes --- osu.Game/Screens/Select/Footer.cs | 57 +++++++------------ osu.Game/Screens/Select/FooterButtonMods.cs | 11 ++++ osu.Game/Screens/Select/FooterButtonRandom.cs | 11 ++++ osu.Game/Screens/Select/SongSelect.cs | 6 +- 4 files changed, 46 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 4466fb418c..b5afc7b0ff 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -30,54 +30,39 @@ namespace osu.Game.Screens.Select private readonly FillFlowContainer buttons; - /// Button to be added. - /// Text on the button. - /// Colour of the button. + private readonly List overlays = new List(); + + /// THe button to be added. + /// The to be toggled by this button. /// Hotkey of the button. - /// Action the button does. - /// - /// Higher depth to be put on the left, and lower to be put on the right. - /// Notice this is different to ! - /// - public void AddButton(FooterButton button, string text, Color4 colour, Action action, Key? hotkey = null, float depth = 0) + public void AddButton(FooterButton button, OverlayContainer overlay, Key? hotkey = null) + { + overlays.Add(overlay); + AddButton(button, () => showOverlay(overlay), hotkey); + } + + /// Button to be added. + /// Action the button does. + /// Hotkey of the button. + public void AddButton(FooterButton button, Action action, Key? hotkey = null) { - button.Text = text; - button.Depth = depth; - button.SelectedColour = colour; - button.DeselectedColour = colour.Opacity(0.5f); button.Hotkey = hotkey; button.Hovered = updateModeLight; button.HoverLost = updateModeLight; button.Action = action; buttons.Add(button); - buttons.SetLayoutPosition(button, -depth); } - private readonly List overlays = new List(); - - /// THe button to be added. - /// Text on the button. - /// Colour of the button. - /// Hotkey of the button. - /// The to be toggled by this button. - /// - /// Higher depth to be put on the left, and lower to be put on the right. - /// Notice this is different to ! - /// - public void AddButton(FooterButton button, string text, Color4 colour, OverlayContainer overlay, Key? hotkey = null, float depth = 0) + private void showOverlay(OverlayContainer overlay) { - overlays.Add(overlay); - AddButton(button, text, colour, () => + foreach (var o in overlays) { - foreach (var o in overlays) - { - if (o == overlay) - o.ToggleVisibility(); - else - o.Hide(); - } - }, hotkey, depth); + if (o == overlay) + o.ToggleVisibility(); + else + o.Hide(); + } } private void updateModeLight() => modeLight.FadeColour(buttons.FirstOrDefault(b => b.IsHovered)?.SelectedColour ?? Color4.Transparent, TRANSITION_LENGTH, Easing.OutQuint); diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 3de668bbf0..4343ee6cdb 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -7,6 +7,9 @@ using osu.Framework.Graphics.Containers; using osu.Game.Screens.Play.HUD; using osu.Game.Rulesets.Mods; using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics; using osuTK; namespace osu.Game.Screens.Select @@ -34,6 +37,14 @@ namespace osu.Game.Screens.Select modDisplay.Current = mods; } + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Yellow; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"mods"; + } + private class FooterModDisplay : ModDisplay { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false; diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index a725bfc103..06f2e1ee43 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -1,9 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; namespace osu.Game.Screens.Select @@ -24,6 +27,14 @@ namespace osu.Game.Screens.Select }); } + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Green; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"random"; + } + protected override bool OnKeyDown(KeyDownEvent e) { secondaryActive = e.ShiftPressed; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index d43ca33ee3..c747fdee60 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -223,9 +223,9 @@ namespace osu.Game.Screens.Select if (Footer != null) { - Footer.AddButton(new FooterButtonMods(mods), @"mods", colours.Yellow, ModSelect, Key.F1); - Footer.AddButton(new FooterButtonRandom(), @"random", colours.Green, triggerRandom, Key.F2); - Footer.AddButton(new FooterButton(), @"options", colours.Blue, BeatmapOptions, Key.F3); + Footer.AddButton(new FooterButtonMods(mods), ModSelect, Key.F1); + Footer.AddButton(new FooterButtonRandom(), triggerRandom, Key.F2); + Footer.AddButton(new FooterButtonOptions(), BeatmapOptions, Key.F3); BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null, Key.Number1); From 39efa2d173de774cf130dded0bf4f5febdbb4666 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 May 2019 19:05:00 +0900 Subject: [PATCH 573/623] Fix possible cross-thread config cache access --- osu.Game/Rulesets/RulesetConfigCache.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index 4ac866fc90..9a5a4d4acd 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; +using System.Collections.Concurrent; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets /// public class RulesetConfigCache : Component { - private readonly Dictionary configCache = new Dictionary(); + private readonly ConcurrentDictionary configCache = new ConcurrentDictionary(); private readonly SettingsStore settingsStore; public RulesetConfigCache(SettingsStore settingsStore) @@ -34,10 +34,7 @@ namespace osu.Game.Rulesets if (ruleset.RulesetInfo.ID == null) throw new InvalidOperationException("The provided ruleset doesn't have a valid id."); - if (configCache.TryGetValue(ruleset.RulesetInfo.ID.Value, out var existing)) - return existing; - - return configCache[ruleset.RulesetInfo.ID.Value] = ruleset.CreateConfig(settingsStore); + return configCache.GetOrAdd(ruleset.RulesetInfo.ID.Value, _ => ruleset.CreateConfig(settingsStore)); } } } From c91b9c60322f2c580c2378e4ad5afbf9c539f967 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 May 2019 19:27:53 +0900 Subject: [PATCH 574/623] Add missing button --- osu.Game/Screens/Select/FooterButtonOptions.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 osu.Game/Screens/Select/FooterButtonOptions.cs diff --git a/osu.Game/Screens/Select/FooterButtonOptions.cs b/osu.Game/Screens/Select/FooterButtonOptions.cs new file mode 100644 index 0000000000..1fbe03216d --- /dev/null +++ b/osu.Game/Screens/Select/FooterButtonOptions.cs @@ -0,0 +1,17 @@ +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Select +{ + public class FooterButtonOptions : FooterButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Blue; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"options"; + } + } +} \ No newline at end of file From 6dea16f36543f640901652e2f2e69020d14c321e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 May 2019 19:29:43 +0900 Subject: [PATCH 575/623] Move action and hotkey specification local --- osu.Game/Screens/Select/Footer.cs | 14 +++++--------- osu.Game/Screens/Select/FooterButtonMods.cs | 2 ++ osu.Game/Screens/Select/FooterButtonOptions.cs | 4 +++- osu.Game/Screens/Select/FooterButtonRandom.cs | 2 ++ osu.Game/Screens/Select/SongSelect.cs | 6 +++--- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index b5afc7b0ff..6ba29751b0 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using osuTK; using osuTK.Graphics; -using osuTK.Input; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -34,22 +33,19 @@ namespace osu.Game.Screens.Select /// THe button to be added. /// The to be toggled by this button. - /// Hotkey of the button. - public void AddButton(FooterButton button, OverlayContainer overlay, Key? hotkey = null) + public void AddButton(FooterButton button, OverlayContainer overlay) { overlays.Add(overlay); - AddButton(button, () => showOverlay(overlay), hotkey); + button.Action = () => showOverlay(overlay); + + AddButton(button); } /// Button to be added. - /// Action the button does. - /// Hotkey of the button. - public void AddButton(FooterButton button, Action action, Key? hotkey = null) + public void AddButton(FooterButton button) { - button.Hotkey = hotkey; button.Hovered = updateModeLight; button.HoverLost = updateModeLight; - button.Action = action; buttons.Add(button); } diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 4343ee6cdb..c96c5022c0 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics; using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Select { @@ -43,6 +44,7 @@ namespace osu.Game.Screens.Select SelectedColour = colours.Yellow; DeselectedColour = SelectedColour.Opacity(0.5f); Text = @"mods"; + Hotkey = Key.F1; } private class FooterModDisplay : ModDisplay diff --git a/osu.Game/Screens/Select/FooterButtonOptions.cs b/osu.Game/Screens/Select/FooterButtonOptions.cs index 1fbe03216d..0e4a32299d 100644 --- a/osu.Game/Screens/Select/FooterButtonOptions.cs +++ b/osu.Game/Screens/Select/FooterButtonOptions.cs @@ -1,6 +1,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics; +using osuTK.Input; namespace osu.Game.Screens.Select { @@ -12,6 +13,7 @@ namespace osu.Game.Screens.Select SelectedColour = colours.Blue; DeselectedColour = SelectedColour.Opacity(0.5f); Text = @"options"; + Hotkey = Key.F3; } } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index 06f2e1ee43..14c9eb2035 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osuTK.Input; namespace osu.Game.Screens.Select { @@ -33,6 +34,7 @@ namespace osu.Game.Screens.Select SelectedColour = colours.Green; DeselectedColour = SelectedColour.Opacity(0.5f); Text = @"random"; + Hotkey = Key.F2; } protected override bool OnKeyDown(KeyDownEvent e) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index c747fdee60..d5301116a9 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -223,9 +223,9 @@ namespace osu.Game.Screens.Select if (Footer != null) { - Footer.AddButton(new FooterButtonMods(mods), ModSelect, Key.F1); - Footer.AddButton(new FooterButtonRandom(), triggerRandom, Key.F2); - Footer.AddButton(new FooterButtonOptions(), BeatmapOptions, Key.F3); + Footer.AddButton(new FooterButtonMods(mods), ModSelect); + Footer.AddButton(new FooterButtonRandom { Action = triggerRandom }); + Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null, Key.Number1); From 4f697e2bd58c4e238c03918c0a2c4eedb9152174 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 May 2019 19:35:20 +0900 Subject: [PATCH 576/623] Add licence header --- osu.Game/Screens/Select/FooterButtonOptions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/FooterButtonOptions.cs b/osu.Game/Screens/Select/FooterButtonOptions.cs index 0e4a32299d..c000d8a8c8 100644 --- a/osu.Game/Screens/Select/FooterButtonOptions.cs +++ b/osu.Game/Screens/Select/FooterButtonOptions.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics; From 83663467ce4ea861622b9a7ad86be18904f03783 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 May 2019 21:08:46 +0900 Subject: [PATCH 577/623] Update dependencies --- osu.Desktop/osu.Desktop.csproj | 6 +++--- osu.Game/osu.Game.csproj | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 66db439c82..aa8848c55f 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,9 +26,9 @@ - - - + + + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index b6b4896658..8f733a5c55 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -11,11 +11,11 @@ - - - - - + + + + + From 17a9f191cd70dde920ee52ba58ada1e81d38127d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 May 2019 23:08:08 +0900 Subject: [PATCH 578/623] Fix incorrect texture name usage for some rank icons --- .../Profile/Header/DetailHeaderContainer.cs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index de710c5fcd..f9ee67002a 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -4,14 +4,12 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Scoring; using osu.Game.Users; @@ -185,8 +183,6 @@ namespace osu.Game.Overlays.Profile.Header private class ScoreRankInfo : CompositeDrawable { - private readonly ScoreRank rank; - private readonly Sprite rankSprite; private readonly OsuSpriteText rankCount; public int RankCount @@ -196,8 +192,6 @@ namespace osu.Game.Overlays.Profile.Header public ScoreRankInfo(ScoreRank rank) { - this.rank = rank; - AutoSizeAxes = Axes.Both; InternalChild = new FillFlowContainer { @@ -206,9 +200,10 @@ namespace osu.Game.Overlays.Profile.Header Direction = FillDirection.Vertical, Children = new Drawable[] { - rankSprite = new Sprite + new DrawableRank(rank) { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + Height = 30, FillMode = FillMode.Fit }, rankCount = new OsuSpriteText @@ -220,12 +215,6 @@ namespace osu.Game.Overlays.Profile.Header } }; } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - rankSprite.Texture = textures.Get($"Grades/{rank.GetDescription()}"); - } } } } From 5128dc32e440d7c3925d1440ef5961d0782c00a4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 May 2019 23:13:09 +0900 Subject: [PATCH 579/623] Remove unnecessary fillmode --- osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index f9ee67002a..e41c90be45 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -204,7 +204,6 @@ namespace osu.Game.Overlays.Profile.Header { RelativeSizeAxes = Axes.X, Height = 30, - FillMode = FillMode.Fit }, rankCount = new OsuSpriteText { From 9b279f324f35c367edf0ad12cb1b22dfe0fef797 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 May 2019 23:38:57 +0900 Subject: [PATCH 580/623] Adjust testcase to avoid random failures --- .../TestCaseUpdateableBeatmapBackgroundSprite.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs index a4dd7b83e2..e9c5bc929d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.UserInterface AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(10), - Padding = new MarginPadding { Bottom = 250 } + Padding = new MarginPadding { Bottom = 550 } } }; @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("some loaded", () => (initialLoadCount = loadedBackgrounds.Count()) > 0); AddStep("scroll to bottom", () => scrollContainer.ScrollToEnd()); - AddUntilStep("some unloaded", () => loadedBackgrounds.Count() < initialLoadCount); + AddUntilStep("all unloaded", () => !loadedBackgrounds.Any()); } private class TestUpdateableBeatmapBackgroundSprite : UpdateableBeatmapBackgroundSprite From 9c01f6793ecb7341d8c3fe42ac20f0ab140e6822 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 May 2019 23:52:44 +0900 Subject: [PATCH 581/623] Remove unused variable --- .../UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs index e9c5bc929d..f57464e7c3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs @@ -132,8 +132,6 @@ namespace osu.Game.Tests.Visual.UserInterface var loadedBackgrounds = backgrounds.Where(b => b.ContentLoaded); - int initialLoadCount = 0; - AddUntilStep("some loaded", () => (initialLoadCount = loadedBackgrounds.Count()) > 0); AddStep("scroll to bottom", () => scrollContainer.ScrollToEnd()); AddUntilStep("all unloaded", () => !loadedBackgrounds.Any()); From 5dbf57046e894a965ebe86725e634cb4b12ee191 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 May 2019 10:38:22 +0900 Subject: [PATCH 582/623] Fix regression from removing unused variable --- .../UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs index f57464e7c3..6185fbd34e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.UserInterface var loadedBackgrounds = backgrounds.Where(b => b.ContentLoaded); - AddUntilStep("some loaded", () => (initialLoadCount = loadedBackgrounds.Count()) > 0); + AddUntilStep("some loaded", () => loadedBackgrounds.Any()); AddStep("scroll to bottom", () => scrollContainer.ScrollToEnd()); AddUntilStep("all unloaded", () => !loadedBackgrounds.Any()); } From 9457a6128eb2a31c4863e52a80a4337b9317020c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 May 2019 10:57:55 +0900 Subject: [PATCH 583/623] Fix game pausing when made inactive while watching a replay --- osu.Game/Screens/Play/HUDOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 3c1b33297a..f8e092c8b1 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -129,6 +129,7 @@ namespace osu.Game.Screens.Play private void replayLoadedValueChanged(ValueChangedEvent e) { PlayerSettingsOverlay.ReplayLoaded = e.NewValue; + HoldToQuit.PauseOnFocusLost = !e.NewValue; if (e.NewValue) { From 5b1ae1210ad01387a7ddf7996e17bfad1a455d95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 May 2019 11:31:40 +0900 Subject: [PATCH 584/623] Add more asserts to pause test in an attempt to track down intermittent test failures --- osu.Game.Tests/Visual/Gameplay/TestCasePause.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs index b9c7fd14bd..14be10b65c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs @@ -157,6 +157,8 @@ namespace osu.Game.Tests.Visual.Gameplay private void confirmPaused() { confirmClockRunning(false); + AddAssert("player not exited", () => Player.IsCurrentScreen()); + AddAssert("player not failed", () => !Player.HasFailed); AddAssert("pause overlay shown", () => Player.PauseOverlayVisible); } From d625f3c104df120ffeed7ed8f9a1203fb2504e90 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 9 May 2019 13:16:56 +0900 Subject: [PATCH 585/623] Private method --- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index ee6e29c92e..eb78e827f0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -92,6 +92,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } + private void updateZoomedContentWidth() => zoomedContent.Width = DrawWidth * currentZoom; + protected override bool OnScroll(ScrollEvent e) { if (e.IsPrecise) @@ -107,7 +109,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline base.LoadComplete(); // This width only gets updated on the application of a transform, so this needs to be initialized here. - zoomedContent.Width = DrawWidth * currentZoom; + updateZoomedContentWidth(); } private float zoomTarget = 1; @@ -171,7 +173,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline d.currentZoom = newZoom; - d.zoomedContent.Width = d.DrawWidth * d.currentZoom; + d.updateZoomedContentWidth(); // Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area. // TODO: Make sure draw size gets invalidated properly on the framework side, and remove this once it is. d.Invalidate(Invalidation.DrawSize); From c69d813745ae55b66548d82e291215a1024ebe9f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 9 May 2019 13:32:18 +0900 Subject: [PATCH 586/623] Fix bindable potentially being set from background thread --- osu.Game/Online/API/APIAccess.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index d62b53088a..70cd21b2db 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -352,10 +352,12 @@ namespace osu.Game.Online.API public void Logout() { flushQueue(); + password = null; authentication.Clear(); - LocalUser.Value = createGuestUser(); State = APIState.Offline; + + Schedule(() => LocalUser.Value = createGuestUser()); } private static User createGuestUser() => new GuestUser(); From 3fed165b749221af2c019885168030b1b4654f13 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 9 May 2019 13:33:18 +0900 Subject: [PATCH 587/623] Cleanup some schedules --- osu.Game/Online/API/APIAccess.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 70cd21b2db..08bb9472c1 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -77,13 +77,13 @@ namespace osu.Game.Online.API /// public void Register(IOnlineComponent component) { - Scheduler.Add(delegate { components.Add(component); }); + Schedule(() => components.Add(component)); component.APIStateChanged(this, state); } public void Unregister(IOnlineComponent component) { - Scheduler.Add(delegate { components.Remove(component); }); + Schedule(() => components.Remove(component)); } public string AccessToken => authentication.RequestAccessToken(); @@ -274,7 +274,7 @@ namespace osu.Game.Online.API state = value; log.Add($@"We just went {state}!"); - Scheduler.Add(delegate + Schedule(() => { components.ForEach(c => c.APIStateChanged(this, state)); OnStateChange?.Invoke(oldState, state); From 35624a5d1cd5969cf12770bda40dab3a5343c9c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 9 May 2019 13:42:04 +0900 Subject: [PATCH 588/623] Invert scheduling order --- osu.Game/Online/API/APIAccess.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 08bb9472c1..594bc1e3ca 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -355,9 +355,11 @@ namespace osu.Game.Online.API password = null; authentication.Clear(); - State = APIState.Offline; + // Scheduled prior to state change such that the state changed event is invoked with the correct user present Schedule(() => LocalUser.Value = createGuestUser()); + + State = APIState.Offline; } private static User createGuestUser() => new GuestUser(); From 5c6b4d923f04d63eaaf7a7c0978ba0bbf95cf977 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 9 May 2019 13:53:53 +0900 Subject: [PATCH 589/623] Reorder methods --- .../Timeline/ZoomableScrollContainer.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index eb78e827f0..829437d599 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -92,7 +92,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - private void updateZoomedContentWidth() => zoomedContent.Width = DrawWidth * currentZoom; + protected override void LoadComplete() + { + base.LoadComplete(); + + // This width only gets updated on the application of a transform, so this needs to be initialized here. + updateZoomedContentWidth(); + } protected override bool OnScroll(ScrollEvent e) { @@ -104,13 +110,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return true; } - protected override void LoadComplete() - { - base.LoadComplete(); - - // This width only gets updated on the application of a transform, so this needs to be initialized here. - updateZoomedContentWidth(); - } + private void updateZoomedContentWidth() => zoomedContent.Width = DrawWidth * currentZoom; private float zoomTarget = 1; From c8f9354327eb25020241c07f456fbfab204d8884 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 May 2019 15:13:35 +0900 Subject: [PATCH 590/623] Remove incorrect deletion include This was causing deletions of scores without considering that scores are now managed by a ScoreManager (and have their own data dependencies). --- osu.Game/Beatmaps/BeatmapStore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index f4b7b1d74f..a2279fdb14 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -65,7 +65,6 @@ namespace osu.Game.Beatmaps protected override IQueryable AddIncludesForDeletion(IQueryable query) => base.AddIncludesForDeletion(query) .Include(s => s.Metadata) - .Include(s => s.Beatmaps).ThenInclude(b => b.Scores) .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata); From 6ebd13c73389660b9a05cc053403021ec1fd03ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 May 2019 15:15:02 +0900 Subject: [PATCH 591/623] Allow Delete and Undelete operations to run silently when needed --- osu.Game/Database/ArchiveModelManager.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index ba348c4090..54dbae9ddc 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -385,7 +385,7 @@ namespace osu.Game.Database /// Delete multiple items. /// This will post notifications tracking progress. /// - public void Delete(List items) + public void Delete(List items, bool silent = false) { if (items.Count == 0) return; @@ -396,7 +396,8 @@ namespace osu.Game.Database State = ProgressNotificationState.Active, }; - PostNotification?.Invoke(notification); + if (!silent) + PostNotification?.Invoke(notification); int i = 0; @@ -423,7 +424,7 @@ namespace osu.Game.Database /// Restore multiple items that were previously deleted. /// This will post notifications tracking progress. /// - public void Undelete(List items) + public void Undelete(List items, bool silent = false) { if (!items.Any()) return; @@ -434,7 +435,8 @@ namespace osu.Game.Database State = ProgressNotificationState.Active, }; - PostNotification?.Invoke(notification); + if (!silent) + PostNotification?.Invoke(notification); int i = 0; From 24e64c13335cc5f8ac0fcf6282a7ddd297651a86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 May 2019 15:15:28 +0900 Subject: [PATCH 592/623] Add proper co-dependent beatmap/score deletion via events --- osu.Game/OsuGameBase.cs | 17 ++++++++++++++++- osu.Game/Scoring/ScoreManager.cs | 8 ++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 44776bb2a8..66552c43e0 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -155,8 +155,23 @@ namespace osu.Game dependencies.Cache(RulesetStore = new RulesetStore(contextFactory)); dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage)); + + // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Host.Storage, contextFactory, Host)); dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap)); - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, BeatmapManager, Host.Storage, contextFactory, Host)); + + // this should likely be moved to ArchiveModelManager when another case appers where it is necessary + // to have inter-dependent model managers. this could be obtained with an IHasForeign interface to + // allow lookups to be done on the child (ScoreManager in this case) to perform the cascading delete. + List getBeatmapScores(BeatmapSetInfo set) + { + var beatmapIds = BeatmapManager.QueryBeatmaps(b => b.BeatmapSetInfoID == set.ID).Select(b => b.ID).ToList(); + return ScoreManager.QueryScores(s => beatmapIds.Contains(s.Beatmap.ID)).ToList(); + } + + BeatmapManager.ItemRemoved += i => ScoreManager.Delete(getBeatmapScores(i), true); + BeatmapManager.ItemAdded += (i, existing) => ScoreManager.Undelete(getBeatmapScores(i), true); + dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 7a527bfc69..6b737dc734 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -25,9 +25,9 @@ namespace osu.Game.Scoring protected override string ImportFromStablePath => "Replays"; private readonly RulesetStore rulesets; - private readonly BeatmapManager beatmaps; + private readonly Func beatmaps; - public ScoreManager(RulesetStore rulesets, BeatmapManager beatmaps, Storage storage, IDatabaseContextFactory contextFactory, IIpcHost importHost = null) + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IDatabaseContextFactory contextFactory, IIpcHost importHost = null) : base(storage, contextFactory, new ScoreStore(contextFactory, storage), importHost) { this.rulesets = rulesets; @@ -43,7 +43,7 @@ namespace osu.Game.Scoring { try { - return new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream).ScoreInfo; + return new DatabasedLegacyScoreParser(rulesets, beatmaps()).Parse(stream).ScoreInfo; } catch (LegacyScoreParser.BeatmapNotFoundException e) { @@ -53,7 +53,7 @@ namespace osu.Game.Scoring } } - public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps, Files.Store); + public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store); public List GetAllUsableScores() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList(); From 39fb5712f1a331720014ee5f8a62ca2261ba4741 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 9 May 2019 15:31:37 +0900 Subject: [PATCH 593/623] Only combo-incrementing results add to result count --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 0ca92a8861..6beb393367 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -314,11 +314,11 @@ namespace osu.Game.Rulesets.Scoring JudgedHits++; - if (result.Type != HitResult.None) - scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1; - if (result.Judgement.AffectsCombo) { + if (result.Type != HitResult.None) + scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1; + switch (result.Type) { case HitResult.None: From b0e34d86d5016cb069a388dfe187decd0502a890 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 9 May 2019 16:16:20 +0900 Subject: [PATCH 594/623] Subtract a result from count if its been reverted --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 6beb393367..2944b784ad 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -360,6 +360,12 @@ namespace osu.Game.Rulesets.Scoring JudgedHits--; + if (result.Judgement.AffectsCombo) + { + if (result.Type != HitResult.None) + scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) - 1; + } + if (result.Judgement.IsBonus) { if (result.IsHit) From 66594b7a1b39ce1ec12049e2458a466030ffb5bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 May 2019 16:36:47 +0900 Subject: [PATCH 595/623] Pass GameplayStartTime to FrameStabilityContainer to allow bypassing prior to start --- .../TestCaseFrameStabilityContainer.cs | 20 +++++++++++++++++++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- .../Rulesets/UI/FrameStabilityContainer.cs | 8 +++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs index 5cd01fe9a8..584fbe5729 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs @@ -73,6 +73,26 @@ namespace osu.Game.Tests.Visual.Gameplay checkFrameCount(2); } + [Test] + public void TestInitialSeekWithGameplayStart() + { + seekManualTo(1000); + createStabilityContainer(30000); + + confirmSeek(1000); + checkFrameCount(0); + + seekManualTo(10000); + confirmSeek(10000); + + checkFrameCount(1); + + seekManualTo(130000); + confirmSeek(130000); + + checkFrameCount(6002); + } + [Test] public void TestInitialSeek() { diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 77d1e60b87..75526ae50a 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.UI { InternalChildren = new Drawable[] { - frameStabilityContainer = new FrameStabilityContainer + frameStabilityContainer = new FrameStabilityContainer(GameplayStartTime) { Child = KeyBindingInputManager .WithChild(CreatePlayfieldAdjustmentContainer() diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index ad15bcf057..16a5ca4387 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -17,10 +17,14 @@ namespace osu.Game.Rulesets.UI /// public class FrameStabilityContainer : Container, IHasReplayHandler { - public FrameStabilityContainer() + private readonly double gameplayStartTime; + + public FrameStabilityContainer(double gameplayStartTime = double.MinValue) { RelativeSizeAxes = Axes.Both; gameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); + + this.gameplayStartTime = gameplayStartTime; } private readonly ManualClock manualClock; @@ -116,6 +120,8 @@ namespace osu.Game.Rulesets.UI firstConsumption = false; } + else if (manualClock.CurrentTime < gameplayStartTime) + manualClock.CurrentTime = newProposedTime = Math.Min(gameplayStartTime, newProposedTime); else if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) { newProposedTime = newProposedTime > manualClock.CurrentTime From 3bcfc86b9c92c0640d29fda24537bf47c0b26076 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 May 2019 16:37:34 +0900 Subject: [PATCH 596/623] Allow custom MaxCatchUpFrames to be specified Also adjusts the default to allow for smoother seeking. --- .../Visual/Gameplay/TestCaseFrameStabilityContainer.cs | 6 +++++- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 9 ++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs index 584fbe5729..7d6430a2cc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs @@ -103,7 +103,11 @@ namespace osu.Game.Tests.Visual.Gameplay checkFrameCount(0); } - private void createStabilityContainer() => AddStep("create container", () => mainContainer.Child = new FrameStabilityContainer().WithChild(consumer = new ClockConsumingChild())); + private const int max_frames_catchup = 50; + + private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () => + mainContainer.Child = new FrameStabilityContainer(gameplayStartTime) { MaxCatchUpFrames = max_frames_catchup } + .WithChild(consumer = new ClockConsumingChild())); private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time); diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 16a5ca4387..9f2bf33628 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -19,6 +19,11 @@ namespace osu.Game.Rulesets.UI { private readonly double gameplayStartTime; + /// + /// The number of frames (per parent frame) which can be run in an attempt to catch-up to real-time. + /// + public int MaxCatchUpFrames { get; set; } = 5; + public FrameStabilityContainer(double gameplayStartTime = double.MinValue) { RelativeSizeAxes = Axes.Both; @@ -68,8 +73,6 @@ namespace osu.Game.Rulesets.UI private bool isAttached => ReplayInputHandler != null; - private const int max_catch_up_updates_per_frame = 50; - private const double sixty_frame_time = 1000.0 / 60; private bool firstConsumption = true; @@ -81,7 +84,7 @@ namespace osu.Game.Rulesets.UI int loops = 0; - while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame) + while (validState && requireMoreUpdateLoops && loops++ < MaxCatchUpFrames) { updateClock(); From 59420721285e2e9b52398be20f786ed87165bd2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 May 2019 18:05:28 +0900 Subject: [PATCH 597/623] Add a tween when clicking to seek --- osu.Game/Screens/Play/SongProgressBar.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs index 2e7d452fe7..8ee2c0aa74 100644 --- a/osu.Game/Screens/Play/SongProgressBar.cs +++ b/osu.Game/Screens/Play/SongProgressBar.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.MathUtils; namespace osu.Game.Screens.Play { @@ -107,9 +108,17 @@ namespace osu.Game.Screens.Play protected override void UpdateValue(float value) { - var xFill = value * UsableWidth; - fill.Width = xFill; - handleBase.X = xFill; + // handled in update + } + + protected override void Update() + { + base.Update(); + + float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, Time.Elapsed / 40); + + fill.Width = newX; + handleBase.X = newX; } protected override void OnUserChange(double value) => OnSeek?.Invoke(value); From 9248e6290cbe4a2276b7b4678799a73b67e2c235 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 May 2019 18:06:11 +0900 Subject: [PATCH 598/623] Use FrameStabilityClock to denote current position on song progress --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 7 +++++++ osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 11 ++++++----- osu.Game/Screens/Play/HUDOverlay.cs | 17 +++++++++++++---- osu.Game/Screens/Play/SongProgress.cs | 13 +++++++++---- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 77d1e60b87..76790e2b46 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -59,6 +59,8 @@ namespace osu.Game.Rulesets.UI /// public Container Overlays { get; private set; } + public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock; + /// /// Invoked when a has been applied by a . /// @@ -334,6 +336,11 @@ namespace osu.Game.Rulesets.UI /// public readonly BindableBool IsPaused = new BindableBool(); + /// + /// The frame-stable clock which is being used for playfield display. + /// + public abstract GameplayClock FrameStableClock { get; } + /// ~ /// The associated ruleset. /// diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index ad15bcf057..733a495248 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -20,7 +20,8 @@ namespace osu.Game.Rulesets.UI public FrameStabilityContainer() { RelativeSizeAxes = Axes.Both; - gameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); + + GameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); } private readonly ManualClock manualClock; @@ -28,7 +29,7 @@ namespace osu.Game.Rulesets.UI private readonly FramedClock framedClock; [Cached] - private GameplayClock gameplayClock; + public GameplayClock GameplayClock { get; } private IFrameBasedClock parentGameplayClock; @@ -38,7 +39,7 @@ namespace osu.Game.Rulesets.UI if (clock != null) { parentGameplayClock = clock; - gameplayClock.IsPaused.BindTo(clock.IsPaused); + GameplayClock.IsPaused.BindTo(clock.IsPaused); } } @@ -73,7 +74,7 @@ namespace osu.Game.Rulesets.UI public override bool UpdateSubTree() { requireMoreUpdateLoops = true; - validState = !gameplayClock.IsPaused.Value; + validState = !GameplayClock.IsPaused.Value; int loops = 0; @@ -160,7 +161,7 @@ namespace osu.Game.Rulesets.UI if (parentGameplayClock == null) parentGameplayClock = Clock; - Clock = gameplayClock; + Clock = GameplayClock; ProcessCustomClock = false; } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index f8e092c8b1..ee229ab93e 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -35,6 +35,10 @@ namespace osu.Game.Screens.Play public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; + private readonly ScoreProcessor scoreProcessor; + private readonly DrawableRuleset drawableRuleset; + private readonly IReadOnlyList mods; + private Bindable showHud; private readonly Container visibilityContainer; private readonly BindableBool replayLoaded = new BindableBool(); @@ -45,6 +49,10 @@ namespace osu.Game.Screens.Play public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) { + this.scoreProcessor = scoreProcessor; + this.drawableRuleset = drawableRuleset; + this.mods = mods; + RelativeSizeAxes = Axes.Both; Children = new Drawable[] @@ -89,20 +97,21 @@ namespace osu.Game.Screens.Play } } }; + } + [BackgroundDependencyLoader(true)] + private void load(OsuConfigManager config, NotificationOverlay notificationOverlay) + { BindProcessor(scoreProcessor); BindDrawableRuleset(drawableRuleset); Progress.Objects = drawableRuleset.Objects; Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value; Progress.RequestSeek = time => RequestSeek(time); + Progress.ReferenceClock = drawableRuleset.FrameStableClock; ModDisplay.Current.Value = mods; - } - [BackgroundDependencyLoader(true)] - private void load(OsuConfigManager config, NotificationOverlay notificationOverlay) - { showHud = config.GetBindable(OsuSetting.ShowInterface); showHud.ValueChanged += visible => visibilityContainer.FadeTo(visible.NewValue ? 1 : 0, duration); showHud.TriggerChange(); diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 94b25e04a3..d478454f00 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -10,6 +10,7 @@ using osu.Game.Graphics; using osu.Framework.Allocation; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Timing; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI; @@ -55,7 +56,9 @@ namespace osu.Game.Screens.Play private readonly BindableBool replayLoaded = new BindableBool(); - private GameplayClock gameplayClock; + public IClock ReferenceClock; + + private IClock gameplayClock; [BackgroundDependencyLoader(true)] private void load(OsuColour colours, GameplayClock clock) @@ -154,10 +157,12 @@ namespace osu.Game.Screens.Play if (objects == null) return; - double position = gameplayClock?.CurrentTime ?? Time.Current; - double progress = Math.Min(1, (position - firstHitTime) / (lastHitTime - firstHitTime)); + double gameplayTime = gameplayClock?.CurrentTime ?? Time.Current; + double frameStableTime = ReferenceClock?.CurrentTime ?? gameplayTime; - bar.CurrentTime = position; + double progress = Math.Min(1, (frameStableTime - firstHitTime) / (lastHitTime - firstHitTime)); + + bar.CurrentTime = gameplayTime; graph.Progress = (int)(graph.ColumnCount * progress); } } From 9e0af723cca8acca9bb4fad33e60da8ed5cadae8 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 9 May 2019 18:56:19 +0900 Subject: [PATCH 599/623] Split out affectscombo change --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 2944b784ad..4adc29853d 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -314,11 +314,11 @@ namespace osu.Game.Rulesets.Scoring JudgedHits++; + if (result.Type != HitResult.None) + scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1; + if (result.Judgement.AffectsCombo) { - if (result.Type != HitResult.None) - scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1; - switch (result.Type) { case HitResult.None: From 5c096cbc910172aab8948576912acab0da033593 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Thu, 9 May 2019 18:59:00 +0900 Subject: [PATCH 600/623] Revert mirroring condition too --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 4adc29853d..cf42a70640 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -360,11 +360,8 @@ namespace osu.Game.Rulesets.Scoring JudgedHits--; - if (result.Judgement.AffectsCombo) - { - if (result.Type != HitResult.None) - scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) - 1; - } + if (result.Type != HitResult.None) + scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) - 1; if (result.Judgement.IsBonus) { From 7c105fd99fea6b415156faf88721872ea16a3fcd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 May 2019 15:39:25 +0900 Subject: [PATCH 601/623] Fix testcase players pausing on window unfocus --- .../TestCaseSliderInput.cs | 2 ++ .../TestCaseBackgroundScreenBeatmap.cs | 2 +- .../Visual/Gameplay/TestCaseAutoplay.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestCasePause.cs | 8 +------- .../Visual/Gameplay/TestCasePlayerLoader.cs | 16 ++-------------- .../Visual/Gameplay/TestCaseReplay.cs | 2 ++ osu.Game/Screens/Play/HUDOverlay.cs | 4 +--- osu.Game/Screens/Play/Player.cs | 7 +++++++ osu.Game/Tests/Visual/AllPlayersTestCase.cs | 2 +- osu.Game/Tests/Visual/PlayerTestCase.cs | 2 +- osu.Game/Tests/Visual/TestPlayer.cs | 17 +++++++++++++++++ 11 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 osu.Game/Tests/Visual/TestPlayer.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs index 76bd9ef758..9a32cf42b0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs @@ -353,6 +353,8 @@ namespace osu.Game.Rulesets.Osu.Tests { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + protected override bool PauseOnFocusLost => false; + public ScoreAccessibleReplayPlayer(Score score) : base(score, false, false) { diff --git a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs index 81fab5b4b3..55e8a810fd 100644 --- a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs @@ -328,7 +328,7 @@ namespace osu.Game.Tests.Visual.Background public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR); } - private class TestPlayer : Player + private class TestPlayer : Visual.TestPlayer { protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs index 624e5f08bd..e5dc092c73 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0)); } - private class ScoreAccessiblePlayer : Player + private class ScoreAccessiblePlayer : TestPlayer { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new HUDOverlay HUDOverlay => base.HUDOverlay; diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs index 14be10b65c..61790467e2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs @@ -186,7 +186,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer(); - protected class PausePlayer : Player + protected class PausePlayer : TestPlayer { public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; @@ -197,12 +197,6 @@ namespace osu.Game.Tests.Visual.Gameplay public bool FailOverlayVisible => FailOverlay.State == Visibility.Visible; public bool PauseOverlayVisible => PauseOverlay.State == Visibility.Visible; - - protected override void LoadComplete() - { - base.LoadComplete(); - HUDOverlay.HoldToQuit.PauseOnFocusLost = false; - } } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs index 15c38e6d18..b8b8f2e4e2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs @@ -1,12 +1,10 @@ // Copyright (c) ppy Pty Ltd . 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 System.Threading; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Rulesets.Mods; @@ -34,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestLoadContinuation() { - AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player(false, false)))); + AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new TestPlayer(false, false)))); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); @@ -101,17 +99,7 @@ namespace osu.Game.Tests.Visual.Gameplay public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; } - private class TestPlayer : Player - { - public new Bindable> Mods => base.Mods; - - public TestPlayer() - : base(false, false) - { - } - } - - protected class SlowLoadPlayer : Player + protected class SlowLoadPlayer : TestPlayer { public bool Ready; diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs index 263070ab21..a302c978d2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs @@ -33,6 +33,8 @@ namespace osu.Game.Tests.Visual.Gameplay public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new HUDOverlay HUDOverlay => base.HUDOverlay; + protected override bool PauseOnFocusLost => false; + public ScoreAccessibleReplayPlayer(Score score) : base(score) { diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index f8e092c8b1..361123d08b 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -122,14 +122,12 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); - replayLoaded.ValueChanged += replayLoadedValueChanged; - replayLoaded.TriggerChange(); + replayLoaded.BindValueChanged(replayLoadedValueChanged, true); } private void replayLoadedValueChanged(ValueChangedEvent e) { PlayerSettingsOverlay.ReplayLoaded = e.NewValue; - HoldToQuit.PauseOnFocusLost = !e.NewValue; if (e.NewValue) { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index fd9ddec314..5cc6f09383 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -40,6 +40,11 @@ namespace osu.Game.Screens.Play public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; + /// + /// Whether gameplay should pause when the game window focus is lost. + /// + protected virtual bool PauseOnFocusLost => true; + public Action RestartRequested; public bool HasFailed { get; private set; } @@ -167,6 +172,8 @@ namespace osu.Game.Screens.Play } }; + DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true); + // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); diff --git a/osu.Game/Tests/Visual/AllPlayersTestCase.cs b/osu.Game/Tests/Visual/AllPlayersTestCase.cs index 40aa90f2d1..dc3ef1a85b 100644 --- a/osu.Game/Tests/Visual/AllPlayersTestCase.cs +++ b/osu.Game/Tests/Visual/AllPlayersTestCase.cs @@ -76,6 +76,6 @@ namespace osu.Game.Tests.Visual return Player; } - protected virtual Player CreatePlayer(Ruleset ruleset) => new Player(false, false); + protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); } } diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index c1960eefeb..67d5696020 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -56,6 +56,6 @@ namespace osu.Game.Tests.Visual LoadScreen(Player); } - protected virtual Player CreatePlayer(Ruleset ruleset) => new Player(false, false); + protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); } } diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs new file mode 100644 index 0000000000..b93a1466e0 --- /dev/null +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + public class TestPlayer : Player + { + protected override bool PauseOnFocusLost => false; + + public TestPlayer(bool allowPause = true, bool showResults = true) + : base(allowPause, showResults) + { + } + } +} From 6a957ad27ff7fe280eb145ded18e38e3dcecdc1c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 May 2019 15:51:12 +0900 Subject: [PATCH 602/623] Fix pause triggered when already paused --- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 9 +++++++-- osu.Game/Screens/Play/HUDOverlay.cs | 2 ++ osu.Game/Screens/Play/Player.cs | 6 +++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 446df94aca..91c14591b1 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -25,6 +25,8 @@ namespace osu.Game.Screens.Play.HUD { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + public readonly Bindable IsPaused = new Bindable(); + private readonly Button button; public Action Action @@ -51,7 +53,8 @@ namespace osu.Game.Screens.Play.HUD button = new Button { HoverGained = () => text.FadeIn(500, Easing.OutQuint), - HoverLost = () => text.FadeOut(500, Easing.OutQuint) + HoverLost = () => text.FadeOut(500, Easing.OutQuint), + IsPaused = { BindTarget = IsPaused } } }; AutoSizeAxes = Axes.Both; @@ -94,6 +97,8 @@ namespace osu.Game.Screens.Play.HUD private CircularProgress circularProgress; private Circle overlayCircle; + public readonly Bindable IsPaused = new Bindable(); + protected override bool AllowMultipleFires => true; public Action HoverGained; @@ -217,7 +222,7 @@ namespace osu.Game.Screens.Play.HUD private void updateActive() { - if (!pauseOnFocusLost) return; + if (!pauseOnFocusLost || IsPaused.Value) return; if (gameActive.Value) AbortConfirm(); diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 361123d08b..43733ef252 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -35,6 +35,8 @@ namespace osu.Game.Screens.Play public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; + public readonly IBindable IsPaused = new Bindable(); + private Bindable showHud; private readonly Container visibilityContainer; private readonly BindableBool replayLoaded = new BindableBool(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 5cc6f09383..92f53d3f9e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -137,7 +137,11 @@ namespace osu.Game.Screens.Play DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, Mods.Value) { - HoldToQuit = { Action = performUserRequestedExit }, + HoldToQuit = + { + Action = performUserRequestedExit, + IsPaused = { BindTarget = GameplayClockContainer.IsPaused } + }, PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, KeyCounter = { Visible = { BindTarget = DrawableRuleset.HasReplayLoaded } }, RequestSeek = GameplayClockContainer.Seek, From d25d39b3154e8bfea6c0b9aa7af31ae9a16b6df6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 May 2019 16:31:09 +0900 Subject: [PATCH 603/623] Add cancellation to storyboard/hitobject loading --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 15 ++++++++++++--- .../Storyboards/Drawables/DrawableStoryboard.cs | 8 +++++++- .../Drawables/DrawableStoryboardLayer.cs | 6 +++++- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 77d1e60b87..7f13c8e469 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects.Drawables; using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using osu.Framework.Bindables; using osu.Framework.Graphics.Cursor; using osu.Framework.Input; @@ -140,7 +141,7 @@ namespace osu.Game.Rulesets.UI public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer(); [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, CancellationToken cancellationToken) { InternalChildren = new Drawable[] { @@ -163,16 +164,24 @@ namespace osu.Game.Rulesets.UI applyRulesetMods(mods, config); - loadObjects(); + loadObjects(cancellationToken); } /// /// Creates and adds drawable representations of hit objects to the play field. /// - private void loadObjects() + private void loadObjects(CancellationToken cancellationToken) { foreach (TObject h in Beatmap.HitObjects) + { + if (cancellationToken.IsCancellationRequested) + break; + addHitObject(h); + } + + if (cancellationToken.IsCancellationRequested) + return; Playfield.PostProcess(); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 1182cacfc1..6af9d4d7f9 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -57,7 +58,7 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader(true)] - private void load(FileStore fileStore, GameplayClock clock) + private void load(FileStore fileStore, GameplayClock clock, CancellationToken cancellationToken) { if (clock != null) Clock = clock; @@ -65,7 +66,12 @@ namespace osu.Game.Storyboards.Drawables dependencies.Cache(new TextureStore(new TextureLoaderStore(fileStore.Store), false, scaleAdjust: 1)); foreach (var layer in Storyboard.Layers) + { + if (cancellationToken.IsCancellationRequested) + break; + Add(layer.CreateDrawable()); + } } private void updateLayerVisibility() diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs index 106ebfaf5d..e82b0df4f4 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -24,10 +25,13 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load() + private void load(CancellationToken cancellationToken) { foreach (var element in Layer.Elements) { + if (cancellationToken.IsCancellationRequested) + break; + if (element.IsDrawable) AddInternal(element.CreateDrawable()); } From f6dfcc4dcf80673156ceecb3a757a9d0026eaeb9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 May 2019 16:31:22 +0900 Subject: [PATCH 604/623] Remove unnecessary Storyboard disposal --- osu.Game/Storyboards/Storyboard.cs | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 0cc753ff7e..3d988c5fe3 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -5,11 +5,10 @@ using osu.Game.Beatmaps; using osu.Game.Storyboards.Drawables; using System.Collections.Generic; using System.Linq; -using System; namespace osu.Game.Storyboards { - public class Storyboard : IDisposable + public class Storyboard { private readonly Dictionary layers = new Dictionary(); public IEnumerable Layers => layers.Values; @@ -56,30 +55,5 @@ namespace osu.Game.Storyboards drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f); return drawable; } - - #region Disposal - - ~Storyboard() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private bool isDisposed; - - protected virtual void Dispose(bool isDisposing) - { - if (isDisposed) - return; - - isDisposed = true; - } - - #endregion } } From fdf67aaa11a3cc26d4b1343890a73ec11dfaeed3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 May 2019 17:18:39 +0900 Subject: [PATCH 605/623] Clamp values --- osu.Game/Screens/Play/SongProgressBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs index 8ee2c0aa74..7e94dadf49 100644 --- a/osu.Game/Screens/Play/SongProgressBar.cs +++ b/osu.Game/Screens/Play/SongProgressBar.cs @@ -115,7 +115,7 @@ namespace osu.Game.Screens.Play { base.Update(); - float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, Time.Elapsed / 40); + float newX = (float)MathHelper.Clamp(Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, Time.Elapsed / 40), 0, UsableWidth); fill.Width = newX; handleBase.X = newX; From ad4b4f34226beb2d5333814b798de63272a3f582 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 May 2019 17:42:45 +0900 Subject: [PATCH 606/623] Use nullable cancellation tokens --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 11 ++++------- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 5 ++--- .../Storyboards/Drawables/DrawableStoryboardLayer.cs | 5 ++--- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 1215cfb626..37f53d552c 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.UI public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer(); [BackgroundDependencyLoader] - private void load(OsuConfigManager config, CancellationToken cancellationToken) + private void load(OsuConfigManager config, CancellationToken? cancellationToken) { InternalChildren = new Drawable[] { @@ -170,18 +170,15 @@ namespace osu.Game.Rulesets.UI /// /// Creates and adds drawable representations of hit objects to the play field. /// - private void loadObjects(CancellationToken cancellationToken) + private void loadObjects(CancellationToken? cancellationToken) { foreach (TObject h in Beatmap.HitObjects) { - if (cancellationToken.IsCancellationRequested) - break; - + cancellationToken?.ThrowIfCancellationRequested(); addHitObject(h); } - if (cancellationToken.IsCancellationRequested) - return; + cancellationToken?.ThrowIfCancellationRequested(); Playfield.PostProcess(); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 6af9d4d7f9..2b27a56844 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -58,7 +58,7 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader(true)] - private void load(FileStore fileStore, GameplayClock clock, CancellationToken cancellationToken) + private void load(FileStore fileStore, GameplayClock clock, CancellationToken? cancellationToken) { if (clock != null) Clock = clock; @@ -67,8 +67,7 @@ namespace osu.Game.Storyboards.Drawables foreach (var layer in Storyboard.Layers) { - if (cancellationToken.IsCancellationRequested) - break; + cancellationToken?.ThrowIfCancellationRequested(); Add(layer.CreateDrawable()); } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs index e82b0df4f4..fd2d441f34 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs @@ -25,12 +25,11 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(CancellationToken cancellationToken) + private void load(CancellationToken? cancellationToken) { foreach (var element in Layer.Elements) { - if (cancellationToken.IsCancellationRequested) - break; + cancellationToken?.ThrowIfCancellationRequested(); if (element.IsDrawable) AddInternal(element.CreateDrawable()); From d2479acbf25f12d9adf3c61dd86d492db6b6aea0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 May 2019 18:04:58 +0900 Subject: [PATCH 607/623] Fix incorrect value being clamped --- osu.Game/Screens/Play/SongProgressBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs index 7e94dadf49..dd7b5826d5 100644 --- a/osu.Game/Screens/Play/SongProgressBar.cs +++ b/osu.Game/Screens/Play/SongProgressBar.cs @@ -115,7 +115,7 @@ namespace osu.Game.Screens.Play { base.Update(); - float newX = (float)MathHelper.Clamp(Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, Time.Elapsed / 40), 0, UsableWidth); + float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, MathHelper.Clamp(Time.Elapsed / 40, 0, 1)); fill.Width = newX; handleBase.X = newX; From 97796a85781aa5192d0317c177026ef00cab9c56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 May 2019 18:10:07 +0900 Subject: [PATCH 608/623] Attempt to fix failing tests by delaying starting of the gameplay clock --- osu.Game.Tests/Visual/Gameplay/TestCasePause.cs | 15 +++++++++++++++ osu.Game/Tests/Visual/PlayerTestCase.cs | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs index 14be10b65c..81ef41ceec 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Rulesets; @@ -31,6 +32,14 @@ namespace osu.Game.Tests.Visual.Gameplay base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }); } + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + AddStep("resume player", () => Player.GameplayClockContainer.Start()); + confirmClockRunning(true); + } + [Test] public void TestPauseResume() { @@ -203,6 +212,12 @@ namespace osu.Game.Tests.Visual.Gameplay base.LoadComplete(); HUDOverlay.HoldToQuit.PauseOnFocusLost = false; } + + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + GameplayClockContainer.Stop(); + } } } } diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index c1960eefeb..85eacc7e09 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Linq; @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual } [SetUpSteps] - public void SetUpSteps() + public virtual void SetUpSteps() { AddStep(ruleset.RulesetInfo.Name, loadPlayer); AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); From 3352252e00f82cce8c25ce074c8cb00616bc685a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 May 2019 18:21:07 +0900 Subject: [PATCH 609/623] Fix testcase regression --- .../Visual/Gameplay/TestCasePlayerLoader.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs index b8b8f2e4e2..511af971dc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . 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 System.Threading; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Rulesets.Mods; @@ -99,7 +101,17 @@ namespace osu.Game.Tests.Visual.Gameplay public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; } - protected class SlowLoadPlayer : TestPlayer + private class TestPlayer : Visual.TestPlayer + { + public new Bindable> Mods => base.Mods; + + public TestPlayer(bool allowPause = true, bool showResults = true) + : base(allowPause, showResults) + { + } + } + + protected class SlowLoadPlayer : Visual.TestPlayer { public bool Ready; From 3fe61c4a7ee96dddac374bfa244502c983d05a97 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 May 2019 18:48:39 +0900 Subject: [PATCH 610/623] Trim whitespace --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 6a2169592d..1cc56fff8b 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.UI /// The number of frames (per parent frame) which can be run in an attempt to catch-up to real-time. /// public int MaxCatchUpFrames { get; set; } = 5; - + [Cached] public GameplayClock GameplayClock { get; } From 01eb1a34a915fcc871b3b013467b7d12da60a01d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 May 2019 22:15:33 +0900 Subject: [PATCH 611/623] Remove unused variable --- osu.Game/Screens/Play/HUDOverlay.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 43733ef252..361123d08b 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -35,8 +35,6 @@ namespace osu.Game.Screens.Play public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; - public readonly IBindable IsPaused = new Bindable(); - private Bindable showHud; private readonly Container visibilityContainer; private readonly BindableBool replayLoaded = new BindableBool(); From 6cf1ca288f578874c84adf1c3468851d9d1b6f11 Mon Sep 17 00:00:00 2001 From: Paul Teng Date: Sat, 11 May 2019 19:13:48 -0400 Subject: [PATCH 612/623] Do not try to join the Add-channel button --- osu.Game/Online/Chat/ChannelManager.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 8c6422afe3..1165c77977 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -84,7 +84,11 @@ namespace osu.Game.Online.Chat ?? new Channel(user); } - private void currentChannelChanged(ValueChangedEvent e) => JoinChannel(e.NewValue); + private void currentChannelChanged(ValueChangedEvent e) + { + if (e.NewValue?.Name != "+") + JoinChannel(e.NewValue); + } /// /// Ensure we run post actions in sequence, once at a time. From 3971a495491ac5e0317b7c3b5d462c5271a56a89 Mon Sep 17 00:00:00 2001 From: Paul Teng Date: Sat, 11 May 2019 19:16:15 -0400 Subject: [PATCH 613/623] Ignore Add-channel button --- osu.Game/Overlays/ChatOverlay.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 221fd35576..6d18d3dae8 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -198,6 +198,11 @@ namespace osu.Game.Overlays channelSelectionOverlay.State = Visibility.Visible; return; } + + if (e.NewValue.Name == "+") + { + return; + } textbox.Current.Disabled = e.NewValue.ReadOnly; From c508b8ed6b1a8f3df9a59f904b73c3d1e5abd07a Mon Sep 17 00:00:00 2001 From: Paul Teng Date: Sat, 11 May 2019 19:21:12 -0400 Subject: [PATCH 614/623] Trim whitespace --- osu.Game/Overlays/ChatOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 6d18d3dae8..fa1c289007 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -198,7 +198,7 @@ namespace osu.Game.Overlays channelSelectionOverlay.State = Visibility.Visible; return; } - + if (e.NewValue.Name == "+") { return; From 29cec54b3ceaace918c38f0e47e2fbb38d0aa0ee Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 11 May 2019 20:08:45 -0700 Subject: [PATCH 615/623] Fix beatmap carousel overlapping beatmap info wedge --- osu.Game/Screens/Select/SongSelect.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index d5301116a9..edc0474aa3 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -89,8 +89,6 @@ namespace osu.Game.Screens.Select protected SongSelect() { - const float carousel_width = 640; - AddRangeInternal(new Drawable[] { new ParallaxContainer @@ -103,7 +101,8 @@ namespace osu.Game.Screens.Select new WedgeBackground { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = carousel_width * 0.76f }, + Padding = new MarginPadding { Right = -150 }, + Size = new Vector2(wedged_container_size.X, 1), } } }, @@ -144,8 +143,8 @@ namespace osu.Game.Screens.Select Carousel = new BeatmapCarousel { Masking = false, - RelativeSizeAxes = Axes.Y, - Size = new Vector2(carousel_width, 1), + RelativeSizeAxes = Axes.Both, + Size = new Vector2(wedged_container_size.X, 1), Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, SelectionChanged = updateSelectedBeatmap, From 175daac16ae0b9bdd5989f0f23ff115a057a4b2b Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 11 May 2019 21:26:42 -0700 Subject: [PATCH 616/623] Close beatmap options when suspending --- osu.Game/Screens/Select/SongSelect.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index d5301116a9..e8512748b3 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -505,6 +505,8 @@ namespace osu.Game.Screens.Select { ModSelect.Hide(); + BeatmapOptions.Hide(); + this.ScaleTo(1.1f, 250, Easing.InSine); this.FadeOut(250); From 790700d8bf8a711d956206cf19dfc149e2f5a68b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 12 May 2019 15:40:58 +0900 Subject: [PATCH 617/623] Use more correct calculation --- osu.Game/Screens/Select/SongSelect.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index edc0474aa3..b2924da908 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -39,6 +39,7 @@ namespace osu.Game.Screens.Select public abstract class SongSelect : OsuScreen { private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245); + protected const float BACKGROUND_BLUR = 20; private const float left_area_padding = 20; @@ -144,7 +145,7 @@ namespace osu.Game.Screens.Select { Masking = false, RelativeSizeAxes = Axes.Both, - Size = new Vector2(wedged_container_size.X, 1), + Size = new Vector2(1 - wedged_container_size.X, 1), Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, SelectionChanged = updateSelectedBeatmap, From 038e49701ec34a239a935072749f3bc466fcff65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 12 May 2019 16:25:25 +0900 Subject: [PATCH 618/623] Move conditional to within BreakPeriod --- osu.Game/Beatmaps/Timing/BreakPeriod.cs | 7 +++++++ osu.Game/Screens/Play/Player.cs | 3 +-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index 7cff54a058..856a5fefd4 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -29,5 +29,12 @@ namespace osu.Game.Beatmaps.Timing /// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap. /// public bool HasEffect => Duration >= MIN_BREAK_DURATION; + + /// + /// Whether this break contains a specified time. + /// + /// The time to check in milliseconds. + /// Whether the time falls within this . + public bool Contains(double time) => time >= StartTime && time <= EndTime; } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4d3bec668b..60054f38fa 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -416,8 +416,7 @@ namespace osu.Game.Screens.Play // breaks and time-based conditions may allow instant resume. double time = GameplayClockContainer.GameplayClock.CurrentTime; - if (Beatmap.Value.Beatmap.Breaks.Any(b => time >= b.StartTime && time <= b.EndTime) || - time < Beatmap.Value.Beatmap.HitObjects.First().StartTime) + if (Beatmap.Value.Beatmap.Breaks.Any(b => b.Contains(time)) || time < Beatmap.Value.Beatmap.HitObjects.First().StartTime) completeResume(); else DrawableRuleset.RequestResume(completeResume); From b8446fb67f07a9c8753947c41a1fa2940b322cb0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 12 May 2019 18:51:31 +0900 Subject: [PATCH 619/623] Fix fallbacks for SliderTrackOverride and SliderBall too --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 96cacd669d..b4c0aacb4d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -160,9 +160,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.SkinChanged(skin, allowFallback); - Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? Body.AccentColour; + Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour; Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Color4.White; - Ball.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Ball.AccentColour; + Ball.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? AccentColour; } protected override void CheckForResult(bool userTriggered, double timeOffset) From 496a9dd41dc74460463ad3341541874f97f938a7 Mon Sep 17 00:00:00 2001 From: Paul Teng Date: Sun, 12 May 2019 06:02:21 -0400 Subject: [PATCH 620/623] Create separate type for Join-Channel button --- .../Chat/Tabs/ChannelSelectorTabChannel.cs | 15 +++++++++++++++ osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabChannel.cs diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabChannel.cs b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabChannel.cs new file mode 100644 index 0000000000..39c570b239 --- /dev/null +++ b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabChannel.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Online.Chat; + +namespace osu.Game.Overlays.Chat.Tabs +{ + public class ChannelSelectorTabChannel : Channel + { + public ChannelSelectorTabChannel() + { + Name = "+"; + } + } +} diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index 67d9356b76..1d0dd3f192 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Chat.Tabs Margin = new MarginPadding(10), }); - AddTabItem(selectorTab = new ChannelSelectorTabItem(new Channel { Name = "+" })); + AddTabItem(selectorTab = new ChannelSelectorTabItem(new ChannelSelectorTabChannel())); ChannelSelectorActive.BindTo(selectorTab.Active); } From d53fb9a5c8b58d54bcedc82c5be5ca3fb62f33b8 Mon Sep 17 00:00:00 2001 From: Paul Teng Date: Sun, 12 May 2019 06:11:16 -0400 Subject: [PATCH 621/623] Check against type instead of channel name --- osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs | 2 +- osu.Game/Online/Chat/ChannelManager.cs | 3 ++- osu.Game/Overlays/ChatOverlay.cs | 6 ++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs b/osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs index fdc3d5394f..85488e36e7 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("remove all channels", () => { var first = channelTabControl.Items.First(); - if (first.Name == "+") + if (first is ChannelSelectorTabChannel) return true; channelTabControl.RemoveChannel(first); diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 1165c77977..bf624ccbe7 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Chat.Tabs; using osu.Game.Users; namespace osu.Game.Online.Chat @@ -86,7 +87,7 @@ namespace osu.Game.Online.Chat private void currentChannelChanged(ValueChangedEvent e) { - if (e.NewValue?.Name != "+") + if (!(e.NewValue is ChannelSelectorTabChannel)) JoinChannel(e.NewValue); } diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index fa1c289007..a418fb1e78 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -199,10 +199,8 @@ namespace osu.Game.Overlays return; } - if (e.NewValue.Name == "+") - { + if (e.NewValue is ChannelSelectorTabChannel) return; - } textbox.Current.Disabled = e.NewValue.ReadOnly; @@ -273,7 +271,7 @@ namespace osu.Game.Overlays private void selectTab(int index) { var channel = channelTabControl.Items.Skip(index).FirstOrDefault(); - if (channel != null && channel.Name != "+") + if (channel != null && !(channel is ChannelSelectorTabChannel)) channelTabControl.Current.Value = channel; } From 8957ad5a7e9f0da1342ee194279b6953b3a62919 Mon Sep 17 00:00:00 2001 From: Paul Teng Date: Sun, 12 May 2019 06:26:03 -0400 Subject: [PATCH 622/623] Instantiate channel in tab item --- osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs | 4 ++-- osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs index c26ecfd86f..9f22ab6923 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs @@ -13,8 +13,8 @@ namespace osu.Game.Overlays.Chat.Tabs public override bool IsSwitchable => false; - public ChannelSelectorTabItem(Channel value) - : base(value) + public ChannelSelectorTabItem() + : base(new ChannelSelectorTabChannel()) { Depth = float.MaxValue; Width = 45; diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index 1d0dd3f192..fafcb0a72d 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Chat.Tabs Margin = new MarginPadding(10), }); - AddTabItem(selectorTab = new ChannelSelectorTabItem(new ChannelSelectorTabChannel())); + AddTabItem(selectorTab = new ChannelSelectorTabItem()); ChannelSelectorActive.BindTo(selectorTab.Active); } From 7adaa092630f7f41ea268c0a161d326abc5d1564 Mon Sep 17 00:00:00 2001 From: Paul Teng Date: Sun, 12 May 2019 06:31:11 -0400 Subject: [PATCH 623/623] Move tab channel class into tab item class --- .../Visual/Online/TestCaseChannelTabControl.cs | 2 +- osu.Game/Online/Chat/ChannelManager.cs | 2 +- .../Chat/Tabs/ChannelSelectorTabChannel.cs | 15 --------------- .../Overlays/Chat/Tabs/ChannelSelectorTabItem.cs | 8 ++++++++ osu.Game/Overlays/ChatOverlay.cs | 4 ++-- 5 files changed, 12 insertions(+), 19 deletions(-) delete mode 100644 osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabChannel.cs diff --git a/osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs b/osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs index 85488e36e7..356ede0d57 100644 --- a/osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs +++ b/osu.Game.Tests/Visual/Online/TestCaseChannelTabControl.cs @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("remove all channels", () => { var first = channelTabControl.Items.First(); - if (first is ChannelSelectorTabChannel) + if (first is ChannelSelectorTabItem.ChannelSelectorTabChannel) return true; channelTabControl.RemoveChannel(first); diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index bf624ccbe7..2efc9f4968 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -87,7 +87,7 @@ namespace osu.Game.Online.Chat private void currentChannelChanged(ValueChangedEvent e) { - if (!(e.NewValue is ChannelSelectorTabChannel)) + if (!(e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel)) JoinChannel(e.NewValue); } diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabChannel.cs b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabChannel.cs deleted file mode 100644 index 39c570b239..0000000000 --- a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabChannel.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Online.Chat; - -namespace osu.Game.Overlays.Chat.Tabs -{ - public class ChannelSelectorTabChannel : Channel - { - public ChannelSelectorTabChannel() - { - Name = "+"; - } - } -} diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs index 9f22ab6923..7386bffb1a 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs @@ -31,5 +31,13 @@ namespace osu.Game.Overlays.Chat.Tabs BackgroundInactive = colour.Gray2; BackgroundActive = colour.Gray3; } + + public class ChannelSelectorTabChannel : Channel + { + public ChannelSelectorTabChannel() + { + Name = "+"; + } + } } } diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index a418fb1e78..eb95fabe02 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -199,7 +199,7 @@ namespace osu.Game.Overlays return; } - if (e.NewValue is ChannelSelectorTabChannel) + if (e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel) return; textbox.Current.Disabled = e.NewValue.ReadOnly; @@ -271,7 +271,7 @@ namespace osu.Game.Overlays private void selectTab(int index) { var channel = channelTabControl.Items.Skip(index).FirstOrDefault(); - if (channel != null && !(channel is ChannelSelectorTabChannel)) + if (channel != null && !(channel is ChannelSelectorTabItem.ChannelSelectorTabChannel)) channelTabControl.Current.Value = channel; }