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

Update ProfileHeader to the new design

This commit is contained in:
jorolf 2018-12-22 21:50:25 +01:00
parent 15ae0cd70a
commit 2fe80d5568
10 changed files with 480 additions and 635 deletions

View File

@ -1,62 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<Type> 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()));
}
}
}

View File

@ -28,7 +28,6 @@ namespace osu.Game.Tests.Visual
typeof(UserProfileOverlay),
typeof(RankGraph),
typeof(LineGraph),
typeof(BadgeContainer),
typeof(SectionsContainer<>),
typeof(SupporterIcon)
};

View File

@ -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()
{

View File

@ -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<Path>
{
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));
}

View File

@ -1,20 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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}";
}
}

View File

@ -1,50 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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}");
}
}
}

View File

@ -1,198 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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;
}
}
}

View File

@ -0,0 +1,149 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<string>
{
private readonly Box bar;
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value) return;
accentColour = value;
bar.Colour = value;
foreach (TabItem<string> 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<string> CreateDropdown() => null;
protected override TabItem<string> CreateTabItem(string value) => new ProfileHeaderTabItem(value)
{
AccentColour = AccentColour
};
private class ProfileHeaderTabItem : TabItem<string>
{
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";
}
}
}
}

View File

@ -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<int, int>[] ranks;
private int dayIndex;
public Bindable<User> User = new Bindable<User>();
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<int> 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);
}
}
}

View File

@ -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<ScoreRank, ScoreRankInfo> scoreRankInfos = new Dictionary<ScoreRank, ScoreRankInfo>();
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<string>
{
private readonly Box bar;
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value) return;
accentColour = value;
bar.Colour = value;
foreach (TabItem<string> 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<string> CreateDropdown() => null;
protected override TabItem<string> CreateTabItem(string value) => new CoverInfoTabItem(value)
{
AccentColour = AccentColour
};
private class CoverInfoTabItem : TabItem<string>
{
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;
}
}
}