mirror of
https://github.com/osukey/osukey.git
synced 2025-05-20 21:17:32 +09:00
Rework score panel tracking to fix visual edge cases
This commit is contained in:
parent
5530e2a1db
commit
ec16b0fc5a
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
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;
|
||||||
@ -102,39 +101,6 @@ 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)
|
||||||
@ -144,9 +110,5 @@ 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
@ -47,6 +48,7 @@ namespace osu.Game.Screens.Ranking
|
|||||||
private StatisticsPanel statisticsPanel;
|
private StatisticsPanel statisticsPanel;
|
||||||
private Drawable bottomPanel;
|
private Drawable bottomPanel;
|
||||||
private ScorePanelList scorePanelList;
|
private ScorePanelList scorePanelList;
|
||||||
|
private Container<ScorePanel> detachedPanelContainer;
|
||||||
|
|
||||||
protected ResultsScreen(ScoreInfo score, bool allowRetry = true)
|
protected ResultsScreen(ScoreInfo score, bool allowRetry = true)
|
||||||
{
|
{
|
||||||
@ -89,11 +91,15 @@ namespace osu.Game.Screens.Ranking
|
|||||||
SelectedScore = { BindTarget = SelectedScore },
|
SelectedScore = { BindTarget = SelectedScore },
|
||||||
PostExpandAction = () => statisticsPanel.ToggleVisibility()
|
PostExpandAction = () => statisticsPanel.ToggleVisibility()
|
||||||
},
|
},
|
||||||
|
detachedPanelContainer = new Container<ScorePanel>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
statisticsPanel = new StatisticsPanel
|
statisticsPanel = new StatisticsPanel
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Score = { BindTarget = SelectedScore }
|
Score = { BindTarget = SelectedScore }
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -169,7 +175,7 @@ namespace osu.Game.Screens.Ranking
|
|||||||
var req = FetchScores(scores => Schedule(() =>
|
var req = FetchScores(scores => Schedule(() =>
|
||||||
{
|
{
|
||||||
foreach (var s in scores)
|
foreach (var s in scores)
|
||||||
scorePanelList.AddScore(s);
|
addScore(s);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (req != null)
|
if (req != null)
|
||||||
@ -208,42 +214,71 @@ namespace osu.Game.Screens.Ranking
|
|||||||
return base.OnExiting(next);
|
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)
|
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)
|
// Detach and move into the local container.
|
||||||
{
|
scorePanelList.Detach(expandedPanel);
|
||||||
if (panel.State == PanelState.Contracted)
|
detachedPanelContainer.Add(expandedPanel);
|
||||||
panel.FadeIn(150);
|
|
||||||
else
|
// Move into its original location in the local container.
|
||||||
{
|
var origLocation = detachedPanelContainer.ToLocalSpace(screenSpacePos);
|
||||||
panel.MoveTo(panel.GetTrackingPosition(), 150, Easing.OutQuint).OnComplete(p =>
|
expandedPanel.MoveTo(origLocation);
|
||||||
{
|
expandedPanel.MoveToX(origLocation.X);
|
||||||
scorePanelList.HandleScroll = true;
|
|
||||||
p.Tracking = true;
|
// 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))
|
||||||
else
|
contracted.FadeOut(150, Easing.OutQuint);
|
||||||
{
|
scorePanelList.HandleInput = false;
|
||||||
|
|
||||||
|
// Dim background.
|
||||||
Background.FadeTo(0.1f, 150);
|
Background.FadeTo(0.1f, 150);
|
||||||
|
|
||||||
foreach (var panel in scorePanelList.Panels)
|
detachedPanel = expandedPanel;
|
||||||
{
|
}
|
||||||
if (panel.State == PanelState.Contracted)
|
else if (detachedPanel != null)
|
||||||
panel.FadeOut(150, Easing.OutQuint);
|
{
|
||||||
else
|
var screenSpacePos = detachedPanel.ScreenSpaceDrawQuad.TopLeft;
|
||||||
{
|
|
||||||
scorePanelList.HandleScroll = false;
|
|
||||||
|
|
||||||
panel.Tracking = false;
|
// Remove from the local container and re-attach.
|
||||||
panel.MoveTo(new Vector2(scorePanelList.CurrentScrollPosition + StatisticsPanel.SIDE_PADDING, panel.GetTrackingPosition().Y), 150, Easing.OutQuint);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,11 +78,6 @@ namespace osu.Game.Screens.Ranking
|
|||||||
public event Action<PanelState> StateChanged;
|
public event Action<PanelState> StateChanged;
|
||||||
public Action PostExpandAction;
|
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>
|
/// <summary>
|
||||||
/// Whether this <see cref="ScorePanel"/> can enter into an <see cref="PanelState.Expanded"/> state.
|
/// Whether this <see cref="ScorePanel"/> can enter into an <see cref="PanelState.Expanded"/> state.
|
||||||
/// </summary>
|
/// </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()
|
private void updateState()
|
||||||
{
|
{
|
||||||
topLayerContent?.FadeOut(content_fade_duration).Expire();
|
topLayerContent?.FadeOut(content_fade_duration).Expire();
|
||||||
@ -269,8 +250,8 @@ namespace osu.Game.Screens.Ranking
|
|||||||
{
|
{
|
||||||
base.Size = value;
|
base.Size = value;
|
||||||
|
|
||||||
if (trackingComponent != null)
|
if (trackingContainer != null)
|
||||||
trackingComponent.Size = value;
|
trackingContainer.Size = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,26 +274,14 @@ namespace osu.Game.Screens.Ranking
|
|||||||
|| topLayerContainer.ReceivePositionalInputAt(screenSpacePos)
|
|| topLayerContainer.ReceivePositionalInputAt(screenSpacePos)
|
||||||
|| middleLayerContainer.ReceivePositionalInputAt(screenSpacePos);
|
|| middleLayerContainer.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
private TrackingComponent trackingComponent;
|
private ScorePanelTrackingContainer trackingContainer;
|
||||||
|
|
||||||
public TrackingComponent CreateTrackingComponent() => trackingComponent ??= new TrackingComponent(this);
|
public ScorePanelTrackingContainer CreateTrackingContainer()
|
||||||
|
|
||||||
public class TrackingComponent : Drawable
|
|
||||||
{
|
{
|
||||||
public readonly ScorePanel Panel;
|
if (trackingContainer != null)
|
||||||
|
throw new InvalidOperationException("A score panel container has already been created.");
|
||||||
|
|
||||||
public TrackingComponent(ScorePanel panel)
|
return trackingContainer = new ScorePanelTrackingContainer(this);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,6 @@ namespace osu.Game.Screens.Ranking
|
|||||||
|
|
||||||
public float CurrentScrollPosition => scroll.Current;
|
public float CurrentScrollPosition => scroll.Current;
|
||||||
|
|
||||||
public IReadOnlyList<ScorePanel> Panels => panels;
|
|
||||||
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;
|
||||||
@ -49,10 +46,9 @@ namespace osu.Game.Screens.Ranking
|
|||||||
InternalChild = scroll = new Scroll
|
InternalChild = scroll = new Scroll
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
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[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
panels = new Container<ScorePanel> { RelativeSizeAxes = Axes.Both },
|
|
||||||
flow = new Flow
|
flow = new Flow
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -72,34 +68,14 @@ namespace osu.Game.Screens.Ranking
|
|||||||
SelectedScore.BindValueChanged(selectedScoreChanged, true);
|
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>
|
/// <summary>
|
||||||
/// Adds a <see cref="ScoreInfo"/> to this list.
|
/// Adds a <see cref="ScoreInfo"/> to this list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <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 ScorePanel AddScore(ScoreInfo score)
|
||||||
{
|
{
|
||||||
var panel = new ScorePanel(score)
|
var panel = new ScorePanel(score)
|
||||||
{
|
{
|
||||||
Tracking = true,
|
|
||||||
PostExpandAction = () => PostExpandAction?.Invoke()
|
PostExpandAction = () => PostExpandAction?.Invoke()
|
||||||
}.With(p =>
|
}.With(p =>
|
||||||
{
|
{
|
||||||
@ -110,8 +86,7 @@ namespace osu.Game.Screens.Ranking
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
panels.Add(panel);
|
flow.Add(panel.CreateTrackingContainer().With(d =>
|
||||||
flow.Add(panel.CreateTrackingComponent().With(d =>
|
|
||||||
{
|
{
|
||||||
d.Anchor = Anchor.Centre;
|
d.Anchor = Anchor.Centre;
|
||||||
d.Origin = 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;
|
scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return panel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -187,15 +164,53 @@ namespace osu.Game.Screens.Ranking
|
|||||||
flow.Padding = new MarginPadding { Horizontal = offset };
|
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 override IEnumerable<Drawable> FlowingChildren => applySorting(AliveInternalChildren);
|
||||||
|
|
||||||
public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count();
|
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>()
|
private IEnumerable<ScorePanelTrackingContainer> applySorting(IEnumerable<Drawable> drawables) => drawables.OfType<ScorePanelTrackingContainer>()
|
||||||
.OrderByDescending(s => s.Panel.Score.TotalScore)
|
.OrderByDescending(s => s.Panel.Score.TotalScore)
|
||||||
.ThenBy(s => s.Panel.Score.OnlineScoreID);
|
.ThenBy(s => s.Panel.Score.OnlineScoreID);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Scroll : OsuScrollContainer
|
private class Scroll : OsuScrollContainer
|
||||||
|
35
osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs
Normal file
35
osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user