1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 08:07:24 +08:00
This commit is contained in:
smoogipoo 2020-06-17 22:29:00 +09:00
parent 5e74985eda
commit 725b2e540b
5 changed files with 205 additions and 48 deletions

View File

@ -3,6 +3,7 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -101,6 +102,39 @@ namespace osu.Game.Tests.Visual.Ranking
AddWaitStep("wait for transition", 10); AddWaitStep("wait for transition", 10);
} }
[Test]
public void TestSceneTrackingScorePanel()
{
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A };
addPanelStep(score, PanelState.Contracted);
AddStep("enable tracking", () =>
{
panel.Anchor = Anchor.CentreLeft;
panel.Origin = Anchor.CentreLeft;
panel.Tracking = true;
Add(panel.CreateTrackingComponent().With(d =>
{
d.Anchor = Anchor.Centre;
d.Origin = Anchor.Centre;
}));
});
assertTracking(true);
AddStep("expand panel", () => panel.State = PanelState.Expanded);
AddWaitStep("wait for transition", 2);
assertTracking(true);
AddStep("stop tracking", () => panel.Tracking = false);
assertTracking(false);
AddStep("start tracking", () => panel.Tracking = true);
assertTracking(true);
}
private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () => private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () =>
{ {
Child = panel = new ScorePanel(score) Child = panel = new ScorePanel(score)
@ -110,5 +144,9 @@ namespace osu.Game.Tests.Visual.Ranking
State = state State = state
}; };
}); });
private void assertTracking(bool tracking) => AddAssert($"{(tracking ? "is" : "is not")} tracking", () =>
Precision.AlmostEquals(panel.ScreenSpaceDrawQuad.TopLeft, panel.CreateTrackingComponent().ScreenSpaceDrawQuad.TopLeft) == tracking
&& Precision.AlmostEquals(panel.ScreenSpaceDrawQuad.BottomRight, panel.CreateTrackingComponent().ScreenSpaceDrawQuad.BottomRight) == tracking);
} }
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
@ -10,6 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Utils;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API; using osu.Game.Online.API;
@ -44,6 +46,9 @@ namespace osu.Game.Screens.Ranking
[Resolved] [Resolved]
private IAPIProvider api { get; set; } private IAPIProvider api { get; set; }
private Container<ScorePanel> scorePanelContainer;
private ResultsScrollContainer scrollContainer;
private Container expandedPanelProxyContainer;
private Drawable bottomPanel; private Drawable bottomPanel;
private ScorePanelList panels; private ScorePanelList panels;
@ -58,6 +63,13 @@ namespace osu.Game.Screens.Ranking
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
scorePanelContainer = new Container<ScorePanel>
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
};
FillFlowContainer buttons; FillFlowContainer buttons;
InternalChild = new GridContainer InternalChild = new GridContainer
@ -67,26 +79,35 @@ namespace osu.Game.Screens.Ranking
{ {
new Drawable[] new Drawable[]
{ {
new ResultsScrollContainer new Container
{ {
Child = new FillFlowContainer RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{ {
RelativeSizeAxes = Axes.X, scorePanelContainer,
AutoSizeAxes = Axes.Y, scrollContainer = new ResultsScrollContainer
Children = new Drawable[]
{ {
panels = new ScorePanelList Child = new FillFlowContainer
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = screen_height, AutoSizeAxes = Axes.Y,
SelectedScore = { BindTarget = SelectedScore } Children = new Drawable[]
}, {
new StatisticsPanel(Score) panels = new ScorePanelList(scorePanelContainer)
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = screen_height, Height = screen_height,
SelectedScore = { BindTarget = SelectedScore }
},
new StatisticsPanel(Score)
{
RelativeSizeAxes = Axes.X,
Height = screen_height,
}
}
} }
} },
expandedPanelProxyContainer = new Container { RelativeSizeAxes = Axes.Both }
} }
} }
}, },
@ -173,6 +194,21 @@ namespace osu.Game.Screens.Ranking
/// <returns>An <see cref="APIRequest"/> responsible for the fetch operation. This will be queued and performed automatically.</returns> /// <returns>An <see cref="APIRequest"/> responsible for the fetch operation. This will be queued and performed automatically.</returns>
protected virtual APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback) => null; protected virtual APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback) => null;
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
ScorePanel expandedPanel = scorePanelContainer.Single(p => p.State == PanelState.Expanded);
expandedPanel.Tracking = false;
expandedPanel.Anchor = Anchor.Centre;
expandedPanel.Origin = Anchor.Centre;
scorePanelContainer.X = (float)Interpolation.Lerp(0, -DrawWidth / 2 + ScorePanel.EXPANDED_WIDTH / 2f, Math.Clamp(scrollContainer.Current / (screen_height * 0.8f), 0, 1));
if (expandedPanelProxyContainer.Count == 0)
expandedPanelProxyContainer.Add(expandedPanel.CreateProxy());
}
public override void OnEntering(IScreen last) public override void OnEntering(IScreen last)
{ {
base.OnEntering(last); base.OnEntering(last);
@ -205,22 +241,21 @@ namespace osu.Game.Screens.Ranking
{ {
// If the user is scrolling via mouse drag, follow the mouse 1:1. // If the user is scrolling via mouse drag, follow the mouse 1:1.
base.OnUserScroll(value, false, distanceDecay); base.OnUserScroll(value, false, distanceDecay);
return;
}
float direction = Math.Sign(value - Target);
float target = Target + direction * screen_height;
if (target <= -screen_height / 2 || target >= ScrollableExtent + screen_height / 2)
{
// If the user is already at either extent and scrolling in the clamped direction, we want to follow the default scroll exactly so that the bounces aren't too harsh.
base.OnUserScroll(value, true, distanceDecay);
} }
else else
{ {
float direction = Math.Sign(value - Target); // Otherwise, scroll one screen in the target direction.
float target = Target + direction * screen_height; base.OnUserScroll(target, true, distanceDecay);
if (target <= -screen_height / 2 || target >= ScrollableExtent + screen_height / 2)
{
// If the user is already at either extent and scrolling in the clamped direction, we want to follow the default scroll exactly so that the bounces aren't too harsh.
base.OnUserScroll(value, true, distanceDecay);
}
else
{
// Otherwise, scroll one screen in the target direction.
base.OnUserScroll(target, true, distanceDecay);
}
} }
} }
} }

View File

@ -182,6 +182,40 @@ namespace osu.Game.Screens.Ranking
} }
} }
private bool tracking;
private Vector2 lastNonTrackingPosition;
/// <summary>
/// Whether this <see cref="ScorePanel"/> should track the position of the tracking component created via <see cref="CreateTrackingComponent"/>.
/// </summary>
public bool Tracking
{
get => tracking;
set
{
if (tracking == value)
return;
tracking = value;
if (tracking)
lastNonTrackingPosition = Position;
else
Position = lastNonTrackingPosition;
}
}
protected override void Update()
{
base.Update();
if (Tracking && trackingComponent != null)
{
Vector2 topLeftPos = Parent.ToLocalSpace(trackingComponent.ScreenSpaceDrawQuad.TopLeft);
Position = topLeftPos - AnchorPosition + OriginPosition;
}
}
private void updateState() private void updateState()
{ {
topLayerContent?.FadeOut(content_fade_duration).Expire(); topLayerContent?.FadeOut(content_fade_duration).Expire();
@ -248,5 +282,31 @@ namespace osu.Game.Screens.Ranking
=> base.ReceivePositionalInputAt(screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos)
|| topLayerContainer.ReceivePositionalInputAt(screenSpacePos) || topLayerContainer.ReceivePositionalInputAt(screenSpacePos)
|| middleLayerContainer.ReceivePositionalInputAt(screenSpacePos); || middleLayerContainer.ReceivePositionalInputAt(screenSpacePos);
private TrackingComponent trackingComponent;
public TrackingComponent CreateTrackingComponent() => trackingComponent ??= new TrackingComponent(this);
public class TrackingComponent : Drawable
{
public readonly ScorePanel Panel;
public TrackingComponent(ScorePanel panel)
{
Panel = panel;
}
protected override void Update()
{
base.Update();
Size = Panel.DrawSize;
}
// In ScorePanelList, score panels are added _before_ the flow, but this means that input will be blocked by the scroll container.
// So by forwarding input events, we remove the need to consider the order in which input is handled.
protected override bool OnClick(ClickEvent e) => Panel.TriggerEvent(e);
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Panel.ReceivePositionalInputAt(screenSpacePos);
}
} }
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -27,11 +28,20 @@ namespace osu.Game.Screens.Ranking
public readonly Bindable<ScoreInfo> SelectedScore = new Bindable<ScoreInfo>(); public readonly Bindable<ScoreInfo> SelectedScore = new Bindable<ScoreInfo>();
private readonly Container<ScorePanel> panels;
private readonly Flow flow; private readonly Flow flow;
private readonly Scroll scroll; private readonly Scroll scroll;
private ScorePanel expandedPanel; private ScorePanel expandedPanel;
public ScorePanelList() /// <summary>
/// Creates a new <see cref="ScorePanelList"/>.
/// </summary>
/// <param name="panelTarget">The target container in which <see cref="ScorePanel"/>s should reside.
/// <see cref="ScorePanel"/>s are set to track by default, but this allows
/// This should be placed _before_ the <see cref="ScorePanelList"/> in the hierarchy.
/// <remarks></remarks>
/// </param>
public ScorePanelList(Container<ScorePanel> panelTarget = null)
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -47,6 +57,18 @@ namespace osu.Game.Screens.Ranking
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
} }
}; };
if (panelTarget == null)
{
// To prevent 1-frame sizing issues, the panel container is added _before_ the scroll + flow containers
AddInternal(panels = new Container<ScorePanel>
{
RelativeSizeAxes = Axes.Both,
Depth = 1
});
}
else
panels = panelTarget;
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -62,10 +84,9 @@ namespace osu.Game.Screens.Ranking
/// <param name="score">The <see cref="ScoreInfo"/> to add.</param> /// <param name="score">The <see cref="ScoreInfo"/> to add.</param>
public void AddScore(ScoreInfo score) public void AddScore(ScoreInfo score)
{ {
flow.Add(new ScorePanel(score) var panel = new ScorePanel(score)
{ {
Anchor = Anchor.Centre, Tracking = true
Origin = Anchor.Centre,
}.With(p => }.With(p =>
{ {
p.StateChanged += s => p.StateChanged += s =>
@ -73,6 +94,13 @@ namespace osu.Game.Screens.Ranking
if (s == PanelState.Expanded) if (s == PanelState.Expanded)
SelectedScore.Value = p.Score; SelectedScore.Value = p.Score;
}; };
});
panels.Add(panel);
flow.Add(panel.CreateTrackingComponent().With(d =>
{
d.Anchor = Anchor.Centre;
d.Origin = Anchor.Centre;
})); }));
if (SelectedScore.Value == score) if (SelectedScore.Value == score)
@ -99,14 +127,15 @@ namespace osu.Game.Screens.Ranking
private void selectedScoreChanged(ValueChangedEvent<ScoreInfo> score) private void selectedScoreChanged(ValueChangedEvent<ScoreInfo> score)
{ {
// Contract the old panel. // Contract the old panel.
foreach (var p in flow.Where(p => p.Score == score.OldValue)) foreach (var t in flow.Where(t => t.Panel.Score == score.OldValue))
{ {
p.State = PanelState.Contracted; t.Panel.State = PanelState.Contracted;
p.Margin = new MarginPadding(); t.Margin = new MarginPadding();
} }
// Find the panel corresponding to the new score. // Find the panel corresponding to the new score.
expandedPanel = flow.SingleOrDefault(p => p.Score == score.NewValue); var expandedTrackingComponent = flow.SingleOrDefault(t => t.Panel.Score == score.NewValue);
expandedPanel = expandedTrackingComponent?.Panel;
// handle horizontal scroll only when not hovering the expanded panel. // handle horizontal scroll only when not hovering the expanded panel.
scroll.HandleScroll = () => expandedPanel?.IsHovered != true; scroll.HandleScroll = () => expandedPanel?.IsHovered != true;
@ -114,9 +143,11 @@ namespace osu.Game.Screens.Ranking
if (expandedPanel == null) if (expandedPanel == null)
return; return;
Debug.Assert(expandedTrackingComponent != null);
// Expand the new panel. // Expand the new panel.
expandedTrackingComponent.Margin = new MarginPadding { Horizontal = expanded_panel_spacing };
expandedPanel.State = PanelState.Expanded; expandedPanel.State = PanelState.Expanded;
expandedPanel.Margin = new MarginPadding { Horizontal = expanded_panel_spacing };
// Scroll to the new panel. This is done manually since we need: // Scroll to the new panel. This is done manually since we need:
// 1) To scroll after the scroll container's visible range is updated. // 1) To scroll after the scroll container's visible range is updated.
@ -145,15 +176,15 @@ namespace osu.Game.Screens.Ranking
flow.Padding = new MarginPadding { Horizontal = offset }; flow.Padding = new MarginPadding { Horizontal = offset };
} }
private class Flow : FillFlowContainer<ScorePanel> private class Flow : FillFlowContainer<ScorePanel.TrackingComponent>
{ {
public override IEnumerable<Drawable> FlowingChildren => applySorting(AliveInternalChildren); public override IEnumerable<Drawable> FlowingChildren => applySorting(AliveInternalChildren);
public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Score != score).Count(); public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count();
private IEnumerable<ScorePanel> applySorting(IEnumerable<Drawable> drawables) => drawables.OfType<ScorePanel>() private IEnumerable<ScorePanel.TrackingComponent> applySorting(IEnumerable<Drawable> drawables) => drawables.OfType<ScorePanel.TrackingComponent>()
.OrderByDescending(s => s.Score.TotalScore) .OrderByDescending(s => s.Panel.Score.TotalScore)
.ThenBy(s => s.Score.OnlineScoreID); .ThenBy(s => s.Panel.Score.OnlineScoreID);
} }
private class Scroll : OsuScrollContainer private class Scroll : OsuScrollContainer

View File

@ -26,13 +26,6 @@ namespace osu.Game.Screens.Ranking.Statistics
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#333") Colour = Color4Extensions.FromHex("#333")
}, },
new ScorePanel(score) // Todo: Temporary
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
State = PanelState.Expanded,
X = 30
},
new Container new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,