mirror of
https://github.com/osukey/osukey.git
synced 2025-08-04 23:24:04 +09:00
Merge branch 'master' into sharpen
This commit is contained in:
144
osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs
Normal file
144
osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs
Normal file
@ -0,0 +1,144 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets;
|
||||
using osuTK;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK.Graphics;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
public class LeaderboardModSelector : CompositeDrawable
|
||||
{
|
||||
public readonly BindableList<Mod> SelectedMods = new BindableList<Mod>();
|
||||
public readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
|
||||
|
||||
private readonly FillFlowContainer<ModButton> modsContainer;
|
||||
|
||||
public LeaderboardModSelector()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
InternalChild = modsContainer = new FillFlowContainer<ModButton>
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Full,
|
||||
Spacing = new Vector2(4),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
Ruleset.BindValueChanged(onRulesetChanged, true);
|
||||
}
|
||||
|
||||
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> ruleset)
|
||||
{
|
||||
SelectedMods.Clear();
|
||||
modsContainer.Clear();
|
||||
|
||||
if (ruleset.NewValue == null)
|
||||
return;
|
||||
|
||||
modsContainer.Add(new ModButton(new ModNoMod()));
|
||||
modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllMods().Where(m => m.Ranked).Select(m => new ModButton(m)));
|
||||
|
||||
modsContainer.ForEach(button => button.OnSelectionChanged = selectionChanged);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateHighlighted();
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
base.OnHoverLost(e);
|
||||
updateHighlighted();
|
||||
}
|
||||
|
||||
private void selectionChanged(Mod mod, bool selected)
|
||||
{
|
||||
if (selected)
|
||||
SelectedMods.Add(mod);
|
||||
else
|
||||
SelectedMods.Remove(mod);
|
||||
|
||||
updateHighlighted();
|
||||
}
|
||||
|
||||
private void updateHighlighted()
|
||||
{
|
||||
if (SelectedMods.Any())
|
||||
return;
|
||||
|
||||
modsContainer.Children.Where(button => !button.IsHovered).ForEach(button => button.Highlighted.Value = !IsHovered);
|
||||
}
|
||||
|
||||
public void DeselectAll() => modsContainer.ForEach(mod => mod.Selected.Value = false);
|
||||
|
||||
private class ModButton : ModIcon
|
||||
{
|
||||
private const int duration = 200;
|
||||
|
||||
public readonly BindableBool Highlighted = new BindableBool();
|
||||
public Action<Mod, bool> OnSelectionChanged;
|
||||
|
||||
public ModButton(Mod mod)
|
||||
: base(mod)
|
||||
{
|
||||
Scale = new Vector2(0.4f);
|
||||
Add(new HoverClickSounds());
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Highlighted.BindValueChanged(highlighted =>
|
||||
{
|
||||
if (Selected.Value)
|
||||
return;
|
||||
|
||||
this.FadeColour(highlighted.NewValue ? Color4.White : Color4.DimGray, duration, Easing.OutQuint);
|
||||
}, true);
|
||||
|
||||
Selected.BindValueChanged(selected =>
|
||||
{
|
||||
OnSelectionChanged?.Invoke(Mod, selected.NewValue);
|
||||
Highlighted.TriggerChange();
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
Selected.Toggle();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
Highlighted.Value = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
base.OnHoverLost(e);
|
||||
Highlighted.Value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs
Normal file
46
osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
{
|
||||
public class NoScoresPlaceholder : Container
|
||||
{
|
||||
private readonly SpriteText text;
|
||||
|
||||
public NoScoresPlaceholder()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Child = text = new OsuSpriteText();
|
||||
}
|
||||
|
||||
public override void Show() => this.FadeIn(200, Easing.OutQuint);
|
||||
|
||||
public override void Hide() => this.FadeOut(200, Easing.OutQuint);
|
||||
|
||||
public void ShowWithScope(BeatmapLeaderboardScope scope)
|
||||
{
|
||||
Show();
|
||||
|
||||
switch (scope)
|
||||
{
|
||||
default:
|
||||
text.Text = @"No scores have been set yet. Maybe you can be the first!";
|
||||
break;
|
||||
|
||||
case BeatmapLeaderboardScope.Friend:
|
||||
text.Text = @"None of your friends have set a score on this map yet.";
|
||||
break;
|
||||
|
||||
case BeatmapLeaderboardScope.Country:
|
||||
text.Text = @"No one from your country has set a score on this map yet.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
{
|
||||
public class NotSupporterPlaceholder : Container
|
||||
{
|
||||
public NotSupporterPlaceholder()
|
||||
{
|
||||
LinkFlowContainer text;
|
||||
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = @"You need to be an osu!supporter to access the friend and country rankings!",
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
||||
},
|
||||
text = new LinkFlowContainer(t => t.Font = t.Font.With(size: 12))
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Direction = FillDirection.Horizontal,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
text.AddText("Click ");
|
||||
text.AddLink("here", "/home/support");
|
||||
text.AddText(" to see all the fancy features that you can get!");
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,10 @@ using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
{
|
||||
@ -20,32 +24,25 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
{
|
||||
private const int spacing = 15;
|
||||
|
||||
public readonly Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
|
||||
private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
|
||||
private readonly Bindable<BeatmapLeaderboardScope> scope = new Bindable<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Global);
|
||||
private readonly Bindable<User> user = new Bindable<User>();
|
||||
|
||||
private readonly Box background;
|
||||
private readonly ScoreTable scoreTable;
|
||||
private readonly FillFlowContainer topScoresContainer;
|
||||
private readonly LoadingAnimation loadingAnimation;
|
||||
private readonly DimmedLoadingLayer loading;
|
||||
private readonly LeaderboardModSelector modSelector;
|
||||
private readonly NoScoresPlaceholder noScoresPlaceholder;
|
||||
private readonly FillFlowContainer content;
|
||||
private readonly NotSupporterPlaceholder notSupporterPlaceholder;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
private GetScoresRequest getScoresRequest;
|
||||
|
||||
private BeatmapInfo beatmap;
|
||||
|
||||
public BeatmapInfo Beatmap
|
||||
{
|
||||
get => beatmap;
|
||||
set
|
||||
{
|
||||
if (beatmap == value)
|
||||
return;
|
||||
|
||||
beatmap = value;
|
||||
|
||||
getScores(beatmap);
|
||||
}
|
||||
}
|
||||
|
||||
protected APILegacyScores Scores
|
||||
{
|
||||
set => Schedule(() =>
|
||||
@ -82,7 +79,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new FillFlowContainer
|
||||
content = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
@ -90,29 +87,88 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 0.95f,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, spacing),
|
||||
Margin = new MarginPadding { Vertical = spacing },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
topScoresContainer = new FillFlowContainer
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 5),
|
||||
Spacing = new Vector2(0, spacing),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new LeaderboardScopeSelector
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Current = { BindTarget = scope }
|
||||
},
|
||||
modSelector = new LeaderboardModSelector
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Ruleset = { BindTarget = ruleset }
|
||||
}
|
||||
}
|
||||
},
|
||||
scoreTable = new ScoreTable
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Margin = new MarginPadding { Vertical = spacing },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
noScoresPlaceholder = new NoScoresPlaceholder
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
Margin = new MarginPadding { Vertical = 10 }
|
||||
},
|
||||
notSupporterPlaceholder = new NotSupporterPlaceholder
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Alpha = 0,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, spacing),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
topScoresContainer = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 5),
|
||||
},
|
||||
scoreTable = new ScoreTable
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 10,
|
||||
Child = loading = new DimmedLoadingLayer(iconScale: 0.8f)
|
||||
{
|
||||
Alpha = 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
loadingAnimation = new LoadingAnimation
|
||||
{
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding(20),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -120,26 +176,88 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
background.Colour = colours.Gray2;
|
||||
|
||||
user.BindTo(api.LocalUser);
|
||||
}
|
||||
|
||||
private void getScores(BeatmapInfo beatmap)
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
scope.BindValueChanged(_ => getScores());
|
||||
ruleset.BindValueChanged(_ => getScores());
|
||||
|
||||
modSelector.SelectedMods.ItemsAdded += _ => getScores();
|
||||
modSelector.SelectedMods.ItemsRemoved += _ => getScores();
|
||||
|
||||
Beatmap.BindValueChanged(onBeatmapChanged);
|
||||
user.BindValueChanged(onUserChanged, true);
|
||||
}
|
||||
|
||||
private void onBeatmapChanged(ValueChangedEvent<BeatmapInfo> beatmap)
|
||||
{
|
||||
var beatmapRuleset = beatmap.NewValue?.Ruleset;
|
||||
|
||||
if (ruleset.Value?.Equals(beatmapRuleset) ?? false)
|
||||
{
|
||||
modSelector.DeselectAll();
|
||||
ruleset.TriggerChange();
|
||||
}
|
||||
else
|
||||
ruleset.Value = beatmapRuleset;
|
||||
|
||||
scope.Value = BeatmapLeaderboardScope.Global;
|
||||
}
|
||||
|
||||
private void onUserChanged(ValueChangedEvent<User> user)
|
||||
{
|
||||
if (modSelector.SelectedMods.Any())
|
||||
modSelector.DeselectAll();
|
||||
else
|
||||
getScores();
|
||||
|
||||
modSelector.FadeTo(userIsSupporter ? 1 : 0);
|
||||
}
|
||||
|
||||
private void getScores()
|
||||
{
|
||||
getScoresRequest?.Cancel();
|
||||
getScoresRequest = null;
|
||||
|
||||
Scores = null;
|
||||
noScoresPlaceholder.Hide();
|
||||
|
||||
if (beatmap?.OnlineBeatmapID.HasValue != true || beatmap.Status <= BeatmapSetOnlineStatus.Pending)
|
||||
if (Beatmap.Value?.OnlineBeatmapID.HasValue != true || Beatmap.Value.Status <= BeatmapSetOnlineStatus.Pending)
|
||||
{
|
||||
Scores = null;
|
||||
content.Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
loadingAnimation.Show();
|
||||
getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset);
|
||||
if (scope.Value != BeatmapLeaderboardScope.Global && !userIsSupporter)
|
||||
{
|
||||
Scores = null;
|
||||
notSupporterPlaceholder.Show();
|
||||
loading.Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
notSupporterPlaceholder.Hide();
|
||||
|
||||
content.Show();
|
||||
loading.Show();
|
||||
|
||||
getScoresRequest = new GetScoresRequest(Beatmap.Value, Beatmap.Value.Ruleset, scope.Value, modSelector.SelectedMods);
|
||||
getScoresRequest.Success += scores =>
|
||||
{
|
||||
loadingAnimation.Hide();
|
||||
loading.Hide();
|
||||
Scores = scores;
|
||||
|
||||
if (!scores.Scores.Any())
|
||||
noScoresPlaceholder.ShowWithScope(scope.Value);
|
||||
};
|
||||
|
||||
api.Queue(getScoresRequest);
|
||||
}
|
||||
|
||||
private bool userIsSupporter => api.IsLoggedIn && api.LocalUser.Value.IsSupporter;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user