1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 21:43:04 +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 osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
@ -101,6 +102,39 @@ namespace osu.Game.Tests.Visual.Ranking
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", () =>
{
Child = panel = new ScorePanel(score)
@ -110,5 +144,9 @@ namespace osu.Game.Tests.Visual.Ranking
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.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
@ -10,6 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Screens;
using osu.Framework.Utils;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
@ -44,6 +46,9 @@ namespace osu.Game.Screens.Ranking
[Resolved]
private IAPIProvider api { get; set; }
private Container<ScorePanel> scorePanelContainer;
private ResultsScrollContainer scrollContainer;
private Container expandedPanelProxyContainer;
private Drawable bottomPanel;
private ScorePanelList panels;
@ -58,6 +63,13 @@ namespace osu.Game.Screens.Ranking
[BackgroundDependencyLoader]
private void load()
{
scorePanelContainer = new Container<ScorePanel>
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
};
FillFlowContainer buttons;
InternalChild = new GridContainer
@ -67,26 +79,35 @@ namespace osu.Game.Screens.Ranking
{
new Drawable[]
{
new ResultsScrollContainer
new Container
{
Child = new FillFlowContainer
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
scorePanelContainer,
scrollContainer = new ResultsScrollContainer
{
panels = new ScorePanelList
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
Height = screen_height,
SelectedScore = { BindTarget = SelectedScore }
},
new StatisticsPanel(Score)
{
RelativeSizeAxes = Axes.X,
Height = screen_height,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
panels = new ScorePanelList(scorePanelContainer)
{
RelativeSizeAxes = Axes.X,
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>
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)
{
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.
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
{
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
{
// Otherwise, scroll one screen in the target direction.
base.OnUserScroll(target, true, distanceDecay);
}
// 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()
{
topLayerContent?.FadeOut(content_fade_duration).Expire();
@ -248,5 +282,31 @@ namespace osu.Game.Screens.Ranking
=> base.ReceivePositionalInputAt(screenSpacePos)
|| topLayerContainer.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.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -27,11 +28,20 @@ namespace osu.Game.Screens.Ranking
public readonly Bindable<ScoreInfo> SelectedScore = new Bindable<ScoreInfo>();
private readonly Container<ScorePanel> panels;
private readonly Flow flow;
private readonly Scroll scroll;
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;
@ -47,6 +57,18 @@ namespace osu.Game.Screens.Ranking
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()
@ -62,10 +84,9 @@ namespace osu.Game.Screens.Ranking
/// <param name="score">The <see cref="ScoreInfo"/> to add.</param>
public void AddScore(ScoreInfo score)
{
flow.Add(new ScorePanel(score)
var panel = new ScorePanel(score)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Tracking = true
}.With(p =>
{
p.StateChanged += s =>
@ -73,6 +94,13 @@ namespace osu.Game.Screens.Ranking
if (s == PanelState.Expanded)
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)
@ -99,14 +127,15 @@ namespace osu.Game.Screens.Ranking
private void selectedScoreChanged(ValueChangedEvent<ScoreInfo> score)
{
// 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;
p.Margin = new MarginPadding();
t.Panel.State = PanelState.Contracted;
t.Margin = new MarginPadding();
}
// 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.
scroll.HandleScroll = () => expandedPanel?.IsHovered != true;
@ -114,9 +143,11 @@ namespace osu.Game.Screens.Ranking
if (expandedPanel == null)
return;
Debug.Assert(expandedTrackingComponent != null);
// Expand the new panel.
expandedTrackingComponent.Margin = new MarginPadding { Horizontal = expanded_panel_spacing };
expandedPanel.State = PanelState.Expanded;
expandedPanel.Margin = new MarginPadding { Horizontal = expanded_panel_spacing };
// Scroll to the new panel. This is done manually since we need:
// 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 };
}
private class Flow : FillFlowContainer<ScorePanel>
private class Flow : FillFlowContainer<ScorePanel.TrackingComponent>
{
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>()
.OrderByDescending(s => s.Score.TotalScore)
.ThenBy(s => s.Score.OnlineScoreID);
private IEnumerable<ScorePanel.TrackingComponent> applySorting(IEnumerable<Drawable> drawables) => drawables.OfType<ScorePanel.TrackingComponent>()
.OrderByDescending(s => s.Panel.Score.TotalScore)
.ThenBy(s => s.Panel.Score.OnlineScoreID);
}
private class Scroll : OsuScrollContainer

View File

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