diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index 513c473830..640e895b6c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; +using osu.Game.Configuration; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Profile; @@ -19,6 +20,9 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + [Resolved] + private OsuConfigManager configManager { get; set; } = null!; + private ProfileHeader header = null!; [SetUpSteps] @@ -33,6 +37,22 @@ namespace osu.Game.Tests.Visual.Online AddStep("Show example user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER, new OsuRuleset().RulesetInfo)); } + [Test] + public void TestProfileCoverExpanded() + { + AddStep("Set cover to expanded", () => configManager.SetValue(OsuSetting.ProfileCoverExpanded, true)); + AddStep("Show example user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER, new OsuRuleset().RulesetInfo)); + AddUntilStep("Cover is expanded", () => header.ChildrenOfType().Single().Height, () => Is.GreaterThan(0)); + } + + [Test] + public void TestProfileCoverCollapsed() + { + AddStep("Set cover to collapsed", () => configManager.SetValue(OsuSetting.ProfileCoverExpanded, false)); + AddStep("Show example user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER, new OsuRuleset().RulesetInfo)); + AddUntilStep("Cover is collapsed", () => header.ChildrenOfType().Single().Height, () => Is.EqualTo(0)); + } + [Test] public void TestOnlineState() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index fc8c2c0b6e..9aaa616c04 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"Somebody", Id = 1, - CountryCode = CountryCode.Unknown, + CountryCode = CountryCode.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", JoinDate = DateTimeOffset.Now.AddDays(-1), LastVisit = DateTimeOffset.Now, @@ -143,7 +143,8 @@ namespace osu.Game.Tests.Visual.Online { Available = 10, Total = 50 - } + }, + SupportLevel = 2, }; } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 6cbb677a64..507d5e611c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -58,6 +58,8 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.BeatmapListingCardSize, BeatmapCardSize.Normal); + SetDefault(OsuSetting.ProfileCoverExpanded, true); + SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full); // Online settings @@ -375,5 +377,6 @@ namespace osu.Game.Configuration LastProcessedMetadataId, SafeAreaConsiderations, ComboColourNormalisationAmount, + ProfileCoverExpanded, } } diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs similarity index 68% rename from osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs rename to osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs index e7a83c95d8..9171d5de7d 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; @@ -15,11 +14,11 @@ using osuTK; namespace osu.Game.Overlays.Profile.Header.Components { - public partial class ExpandDetailsButton : ProfileHeaderButton + public partial class ToggleCoverButton : ProfileHeaderButton { - public readonly BindableBool DetailsVisible = new BindableBool(); + public readonly BindableBool CoverExpanded = new BindableBool(true); - public override LocalisableString TooltipText => DetailsVisible.Value ? CommonStrings.ButtonsCollapse : CommonStrings.ButtonsExpand; + public override LocalisableString TooltipText => CoverExpanded.Value ? UsersStrings.ShowCoverTo0 : UsersStrings.ShowCoverTo1; private SpriteIcon icon = null!; private Sample? sampleOpen; @@ -27,12 +26,12 @@ namespace osu.Game.Overlays.Profile.Header.Components protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverClickSounds(); - public ExpandDetailsButton() + public ToggleCoverButton() { Action = () => { - DetailsVisible.Toggle(); - (DetailsVisible.Value ? sampleOpen : sampleClose)?.Play(); + CoverExpanded.Toggle(); + (CoverExpanded.Value ? sampleOpen : sampleClose)?.Play(); }; } @@ -40,19 +39,21 @@ namespace osu.Game.Overlays.Profile.Header.Components private void load(OverlayColourProvider colourProvider, AudioManager audio) { IdleColour = colourProvider.Background2; - HoverColour = colourProvider.Background2.Lighten(0.2f); + HoverColour = colourProvider.Background1; sampleOpen = audio.Samples.Get(@"UI/dropdown-open"); sampleClose = audio.Samples.Get(@"UI/dropdown-close"); + AutoSizeAxes = Axes.None; + Size = new Vector2(30); Child = icon = new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(20, 12) + Size = new Vector2(10.5f, 12) }; - DetailsVisible.BindValueChanged(visible => updateState(visible.NewValue), true); + CoverExpanded.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/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 1fe39cb570..652ebcec26 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -7,13 +7,16 @@ 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.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users; using osu.Game.Users.Drawables; using osuTK; @@ -21,13 +24,15 @@ namespace osu.Game.Overlays.Profile.Header { public partial class TopHeaderContainer : CompositeDrawable { - private const float avatar_size = 110; + private const float content_height = 65; + private const float vertical_padding = 10; public readonly Bindable User = new Bindable(); [Resolved] private IAPIProvider api { get; set; } = null!; + private UserCoverBackground cover = null!; private SupporterIcon supporterTag = null!; private UpdateableAvatar avatar = null!; private OsuSpriteText usernameText = null!; @@ -36,11 +41,19 @@ namespace osu.Game.Overlays.Profile.Header private UpdateableFlag userFlag = null!; private OsuSpriteText userCountryText = null!; private GroupBadgeFlow groupBadgeFlow = null!; + private ToggleCoverButton coverToggle = null!; + + private Bindable coverExpanded = null!; + + private FillFlowContainer flow = null!; [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, OsuConfigManager configManager) { - Height = 150; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + coverExpanded = configManager.GetBindable(OsuSetting.ProfileCoverExpanded); InternalChildren = new Drawable[] { @@ -51,124 +64,147 @@ namespace osu.Game.Overlays.Profile.Header }, 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, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, Children = new Drawable[] { - avatar = new UpdateableAvatar(isInteractive: false, showGuestOnNull: false) + cover = new ProfileCoverBackground { - Size = new Vector2(avatar_size), - Masking = true, - CornerRadius = avatar_size * 0.25f, + RelativeSizeAxes = Axes.X, }, - new OsuContextMenuContainer + new Container { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Child = new Container + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Padding = new MarginPadding { Left = 10 }, - Children = new Drawable[] + flow = new FillFlowContainer { - new FillFlowContainer + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - Children = new Drawable[] - { - usernameText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) - }, - openUserExternally = new ExternalLinkButton - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - groupBadgeFlow = new GroupBadgeFlow - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - } - } - }, - titleText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 18, weight: FontWeight.Regular) - }, - } + Left = UserProfileOverlay.CONTENT_X_MARGIN, + Vertical = vertical_padding }, - new FillFlowContainer + Height = content_height + 2 * vertical_padding, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] + avatar = new UpdateableAvatar(isInteractive: false, showGuestOnNull: false) { - supporterTag = new SupporterIcon + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Masking = true, + EdgeEffect = new EdgeEffectParameters { - Height = 20, - Margin = new MarginPadding { Top = 5 } - }, - new Box - { - RelativeSizeAxes = Axes.X, - Height = 1.5f, - Margin = new MarginPadding { Top = 10 }, - Colour = colourProvider.Light1, - }, - new FillFlowContainer + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0, 1), + Radius = 3, + Colour = Colour4.Black.Opacity(0.25f), + } + }, + new OsuContextMenuContainer + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Child = new FillFlowContainer { AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 5 }, - Direction = FillDirection.Horizontal, + Direction = FillDirection.Vertical, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Children = new Drawable[] { - userFlag = new UpdateableFlag + new FillFlowContainer { - Size = new Vector2(28, 20), - ShowPlaceholderOnUnknown = false, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + usernameText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) + }, + supporterTag = new SupporterIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 15, + }, + openUserExternally = new ExternalLinkButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + groupBadgeFlow = new GroupBadgeFlow + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + } }, - userCountryText = new OsuSpriteText + titleText = new OsuSpriteText { - Font = OsuFont.GetFont(size: 17.5f, weight: FontWeight.Regular), - Margin = new MarginPadding { Left = 10 }, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Colour = colourProvider.Light1, - } + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), + Margin = new MarginPadding { Bottom = 5 } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + userFlag = new UpdateableFlag + { + Size = new Vector2(28, 20), + ShowPlaceholderOnUnknown = false, + }, + userCountryText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 14f, weight: FontWeight.Regular), + Margin = new MarginPadding { Left = 5 }, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + } + } + }, } }, - } + }, } + }, + coverToggle = new ToggleCoverButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding { Right = 10 }, + CoverExpanded = { BindTarget = coverExpanded } } - } - } - } + }, + }, + }, }, }; + } - User.BindValueChanged(user => updateUser(user.NewValue)); + protected override void LoadComplete() + { + base.LoadComplete(); + + User.BindValueChanged(user => updateUser(user.NewValue), true); + coverExpanded.BindValueChanged(_ => updateCoverState(), true); + FinishTransforms(true); } private void updateUser(UserProfileData? data) { var user = data?.User; + cover.User = user; avatar.User = user; usernameText.Text = user?.Username ?? string.Empty; openUserExternally.Link = $@"{api.WebsiteRootUrl}/users/{user?.Id ?? 0}"; @@ -179,5 +215,27 @@ namespace osu.Game.Overlays.Profile.Header titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff"); groupBadgeFlow.User.Value = user; } + + private void updateCoverState() + { + const float transition_duration = 500; + + bool expanded = coverToggle.CoverExpanded.Value; + + cover.ResizeHeightTo(expanded ? 250 : 0, transition_duration, Easing.OutQuint); + avatar.ResizeTo(new Vector2(expanded ? 120 : content_height), transition_duration, Easing.OutQuint); + avatar.TransformTo(nameof(avatar.CornerRadius), expanded ? 40f : 20f, transition_duration, Easing.OutQuint); + flow.TransformTo(nameof(flow.Spacing), new Vector2(expanded ? 20f : 10f), transition_duration, Easing.OutQuint); + } + + private partial class ProfileCoverBackground : UserCoverBackground + { + protected override double LoadDelay => 0; + + public ProfileCoverBackground() + { + Masking = true; + } + } } } diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index c559a1c102..67b723b7e6 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -3,23 +3,17 @@ using System.Diagnostics; 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.Framework.Localisation; using osu.Game.Overlays.Profile.Header; using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Resources.Localisation.Web; -using osu.Game.Users; namespace osu.Game.Overlays.Profile { public partial class ProfileHeader : TabControlOverlayHeader { - private UserCoverBackground coverContainer = null!; - public Bindable User = new Bindable(); private CentreHeaderContainer centreHeaderContainer; @@ -29,8 +23,6 @@ namespace osu.Game.Overlays.Profile { ContentSidePadding = UserProfileOverlay.CONTENT_X_MARGIN; - User.ValueChanged += e => updateDisplay(e.NewValue); - TabControl.AddItem(LayoutStrings.HeaderUsersShow); // todo: pending implementation. @@ -41,25 +33,7 @@ namespace osu.Game.Overlays.Profile Debug.Assert(detailHeaderContainer != null); } - protected override Drawable CreateBackground() => - new Container - { - RelativeSizeAxes = Axes.X, - Height = 150, - Masking = true, - Children = new Drawable[] - { - coverContainer = new ProfileCoverBackground - { - RelativeSizeAxes = Axes.Both, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("222").Opacity(0.8f), Color4Extensions.FromHex("222").Opacity(0.2f)) - }, - } - }; + protected override Drawable CreateBackground() => Empty(); protected override Drawable CreateContent() => new FillFlowContainer { @@ -103,8 +77,6 @@ namespace osu.Game.Overlays.Profile User = { BindTarget = User } }; - private void updateDisplay(UserProfileData? user) => coverContainer.User = user?.User; - private partial class ProfileHeaderTitle : OverlayTitle { public ProfileHeaderTitle() @@ -113,10 +85,5 @@ namespace osu.Game.Overlays.Profile IconTexture = "Icons/Hexacons/profile"; } } - - private partial class ProfileCoverBackground : UserCoverBackground - { - protected override double LoadDelay => 0; - } } }