1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 08:52:55 +08:00

Rework score panel tracking to fix visual edge cases

This commit is contained in:
smoogipoo 2020-06-19 17:28:35 +09:00
parent 5530e2a1db
commit ec16b0fc5a
5 changed files with 155 additions and 139 deletions

View File

@ -3,7 +3,6 @@
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;
@ -102,39 +101,6 @@ 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)
@ -144,9 +110,5 @@ 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;
@ -47,6 +48,7 @@ namespace osu.Game.Screens.Ranking
private StatisticsPanel statisticsPanel;
private Drawable bottomPanel;
private ScorePanelList scorePanelList;
private Container<ScorePanel> detachedPanelContainer;
protected ResultsScreen(ScoreInfo score, bool allowRetry = true)
{
@ -89,11 +91,15 @@ namespace osu.Game.Screens.Ranking
SelectedScore = { BindTarget = SelectedScore },
PostExpandAction = () => statisticsPanel.ToggleVisibility()
},
detachedPanelContainer = new Container<ScorePanel>
{
RelativeSizeAxes = Axes.Both
},
statisticsPanel = new StatisticsPanel
{
RelativeSizeAxes = Axes.Both,
Score = { BindTarget = SelectedScore }
}
},
}
}
},
@ -169,7 +175,7 @@ namespace osu.Game.Screens.Ranking
var req = FetchScores(scores => Schedule(() =>
{
foreach (var s in scores)
scorePanelList.AddScore(s);
addScore(s);
}));
if (req != null)
@ -208,42 +214,71 @@ namespace osu.Game.Screens.Ranking
return base.OnExiting(next);
}
private void addScore(ScoreInfo score)
{
var panel = scorePanelList.AddScore(score);
if (detachedPanel != null)
panel.Alpha = 0;
}
private ScorePanel detachedPanel;
private void onStatisticsStateChanged(ValueChangedEvent<Visibility> state)
{
if (state.NewValue == Visibility.Hidden)
if (state.NewValue == Visibility.Visible)
{
Background.FadeTo(0.5f, 150);
// Detach the panel in its original location, and move into the desired location in the local container.
var expandedPanel = scorePanelList.GetPanelForScore(SelectedScore.Value);
var screenSpacePos = expandedPanel.ScreenSpaceDrawQuad.TopLeft;
foreach (var panel in scorePanelList.Panels)
{
if (panel.State == PanelState.Contracted)
panel.FadeIn(150);
else
{
panel.MoveTo(panel.GetTrackingPosition(), 150, Easing.OutQuint).OnComplete(p =>
{
scorePanelList.HandleScroll = true;
p.Tracking = true;
});
}
}
}
else
{
// Detach and move into the local container.
scorePanelList.Detach(expandedPanel);
detachedPanelContainer.Add(expandedPanel);
// Move into its original location in the local container.
var origLocation = detachedPanelContainer.ToLocalSpace(screenSpacePos);
expandedPanel.MoveTo(origLocation);
expandedPanel.MoveToX(origLocation.X);
// Move into the final location.
expandedPanel.MoveToX(StatisticsPanel.SIDE_PADDING, 150, Easing.OutQuint);
// Hide contracted panels.
foreach (var contracted in scorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted))
contracted.FadeOut(150, Easing.OutQuint);
scorePanelList.HandleInput = false;
// Dim background.
Background.FadeTo(0.1f, 150);
foreach (var panel in scorePanelList.Panels)
{
if (panel.State == PanelState.Contracted)
panel.FadeOut(150, Easing.OutQuint);
else
{
scorePanelList.HandleScroll = false;
detachedPanel = expandedPanel;
}
else if (detachedPanel != null)
{
var screenSpacePos = detachedPanel.ScreenSpaceDrawQuad.TopLeft;
panel.Tracking = false;
panel.MoveTo(new Vector2(scorePanelList.CurrentScrollPosition + StatisticsPanel.SIDE_PADDING, panel.GetTrackingPosition().Y), 150, Easing.OutQuint);
}
}
// Remove from the local container and re-attach.
detachedPanelContainer.Remove(detachedPanel);
scorePanelList.Attach(detachedPanel);
// Move into its original location in the attached container.
var origLocation = detachedPanel.Parent.ToLocalSpace(screenSpacePos);
detachedPanel.MoveTo(origLocation);
detachedPanel.MoveToX(origLocation.X);
// Move into the final location.
detachedPanel.MoveToX(0, 150, Easing.OutQuint);
// Show contracted panels.
foreach (var contracted in scorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted))
contracted.FadeIn(150, Easing.OutQuint);
scorePanelList.HandleInput = true;
// Un-dim background.
Background.FadeTo(0.5f, 150);
detachedPanel = null;
}
}
}

View File

@ -78,11 +78,6 @@ namespace osu.Game.Screens.Ranking
public event Action<PanelState> StateChanged;
public Action PostExpandAction;
/// <summary>
/// Whether this <see cref="ScorePanel"/> should track the position of the tracking component created via <see cref="CreateTrackingComponent"/>.
/// </summary>
public bool Tracking;
/// <summary>
/// Whether this <see cref="ScorePanel"/> can enter into an <see cref="PanelState.Expanded"/> state.
/// </summary>
@ -194,20 +189,6 @@ namespace osu.Game.Screens.Ranking
}
}
protected override void Update()
{
base.Update();
if (Tracking && trackingComponent != null)
Position = GetTrackingPosition();
}
public Vector2 GetTrackingPosition()
{
Vector2 topLeftPos = Parent.ToLocalSpace(trackingComponent.ScreenSpaceDrawQuad.TopLeft);
return topLeftPos - AnchorPosition + OriginPosition;
}
private void updateState()
{
topLayerContent?.FadeOut(content_fade_duration).Expire();
@ -269,8 +250,8 @@ namespace osu.Game.Screens.Ranking
{
base.Size = value;
if (trackingComponent != null)
trackingComponent.Size = value;
if (trackingContainer != null)
trackingContainer.Size = value;
}
}
@ -293,26 +274,14 @@ namespace osu.Game.Screens.Ranking
|| topLayerContainer.ReceivePositionalInputAt(screenSpacePos)
|| middleLayerContainer.ReceivePositionalInputAt(screenSpacePos);
private TrackingComponent trackingComponent;
private ScorePanelTrackingContainer trackingContainer;
public TrackingComponent CreateTrackingComponent() => trackingComponent ??= new TrackingComponent(this);
public class TrackingComponent : Drawable
public ScorePanelTrackingContainer CreateTrackingContainer()
{
public readonly ScorePanel Panel;
if (trackingContainer != null)
throw new InvalidOperationException("A score panel container has already been created.");
public TrackingComponent(ScorePanel panel)
{
Panel = panel;
}
// 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);
public override bool IsPresent => Panel.IsPresent;
return trackingContainer = new ScorePanelTrackingContainer(this);
}
}
}

View File

@ -32,9 +32,6 @@ namespace osu.Game.Screens.Ranking
public float CurrentScrollPosition => scroll.Current;
public IReadOnlyList<ScorePanel> Panels => panels;
private readonly Container<ScorePanel> panels;
private readonly Flow flow;
private readonly Scroll scroll;
private ScorePanel expandedPanel;
@ -49,10 +46,9 @@ namespace osu.Game.Screens.Ranking
InternalChild = scroll = new Scroll
{
RelativeSizeAxes = Axes.Both,
HandleScroll = () => HandleScroll && expandedPanel?.IsHovered != true, // handle horizontal scroll only when not hovering the expanded panel.
HandleScroll = () => expandedPanel?.IsHovered != true, // handle horizontal scroll only when not hovering the expanded panel.
Children = new Drawable[]
{
panels = new Container<ScorePanel> { RelativeSizeAxes = Axes.Both },
flow = new Flow
{
Anchor = Anchor.Centre,
@ -72,34 +68,14 @@ namespace osu.Game.Screens.Ranking
SelectedScore.BindValueChanged(selectedScoreChanged, true);
}
private bool handleScroll = true;
public bool HandleScroll
{
get => handleScroll;
set
{
handleScroll = value;
foreach (var p in panels)
p.CanExpand = value;
scroll.ScrollbarVisible = value;
if (!value)
scroll.ScrollTo(CurrentScrollPosition, false);
}
}
/// <summary>
/// Adds a <see cref="ScoreInfo"/> to this list.
/// </summary>
/// <param name="score">The <see cref="ScoreInfo"/> to add.</param>
public void AddScore(ScoreInfo score)
public ScorePanel AddScore(ScoreInfo score)
{
var panel = new ScorePanel(score)
{
Tracking = true,
PostExpandAction = () => PostExpandAction?.Invoke()
}.With(p =>
{
@ -110,8 +86,7 @@ namespace osu.Game.Screens.Ranking
};
});
panels.Add(panel);
flow.Add(panel.CreateTrackingComponent().With(d =>
flow.Add(panel.CreateTrackingContainer().With(d =>
{
d.Anchor = Anchor.Centre;
d.Origin = Anchor.Centre;
@ -132,6 +107,8 @@ namespace osu.Game.Screens.Ranking
scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing;
}
}
return panel;
}
/// <summary>
@ -187,15 +164,53 @@ namespace osu.Game.Screens.Ranking
flow.Padding = new MarginPadding { Horizontal = offset };
}
private class Flow : FillFlowContainer<ScorePanel.TrackingComponent>
private bool handleInput = true;
public bool HandleInput
{
get => handleInput;
set
{
handleInput = value;
scroll.ScrollbarVisible = value;
}
}
public override bool PropagatePositionalInputSubTree => HandleInput && base.PropagatePositionalInputSubTree;
public override bool PropagateNonPositionalInputSubTree => HandleInput && base.PropagateNonPositionalInputSubTree;
public IEnumerable<ScorePanel> GetScorePanels() => flow.Select(t => t.Panel);
public ScorePanel GetPanelForScore(ScoreInfo score) => flow.Single(t => t.Panel.Score == score).Panel;
public void Detach(ScorePanel panel)
{
var container = flow.FirstOrDefault(t => t.Panel == panel);
if (container == null)
throw new InvalidOperationException("Panel is not contained by the score panel list.");
container.Detach();
}
public void Attach(ScorePanel panel)
{
var container = flow.FirstOrDefault(t => t.Panel == panel);
if (container == null)
throw new InvalidOperationException("Panel is not contained by the score panel list.");
container.Attach();
}
private class Flow : FillFlowContainer<ScorePanelTrackingContainer>
{
public override IEnumerable<Drawable> FlowingChildren => applySorting(AliveInternalChildren);
public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count();
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 IEnumerable<ScorePanelTrackingContainer> applySorting(IEnumerable<Drawable> drawables) => drawables.OfType<ScorePanelTrackingContainer>()
.OrderByDescending(s => s.Panel.Score.TotalScore)
.ThenBy(s => s.Panel.Score.OnlineScoreID);
}
private class Scroll : OsuScrollContainer

View File

@ -0,0 +1,35 @@
// 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 System;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Screens.Ranking
{
public class ScorePanelTrackingContainer : CompositeDrawable
{
public readonly ScorePanel Panel;
public ScorePanelTrackingContainer(ScorePanel panel)
{
Panel = panel;
Attach();
}
public void Detach()
{
if (InternalChildren.Count == 0)
throw new InvalidOperationException("Score panel container is not attached.");
RemoveInternal(Panel);
}
public void Attach()
{
if (InternalChildren.Count > 0)
throw new InvalidOperationException("Score panel container is already attached.");
AddInternal(Panel);
}
}
}