From d2629561469e4dfd0efba561b757d4da10aac481 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 28 Apr 2021 18:27:40 +0900 Subject: [PATCH 01/65] Always use LifetimeEntry to manage hit objects in HitObjectContainer Previously, non-pooled DHOs were immediately added as children of the HOC when Add is called. Also, non-pooled DHOs were always attached to the HOC as children. New behavior is that non-pooled DHOs are only added after CheckChildLifetime, and only attached to the HOC while the DHOs are alive. - LifetimeManagementContainer inheritance of HOC is removed, as it is now all DHOs are "unmanaged" (previously `AddInternal(false)`). - The signature of `Clear` is changed, and it is now always not disposing the children immediately. --- .../Edit/ManiaBeatSnapGrid.cs | 2 +- .../HitObjectApplicationTestScene.cs | 2 +- .../Pooling/PoolableDrawableWithLifetime.cs | 2 +- osu.Game/Rulesets/UI/HitObjectContainer.cs | 66 +++++++++---------- .../Scrolling/ScrollingHitObjectContainer.cs | 4 +- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index afc08dcc96..9d1f5429a1 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Edit foreach (var line in grid.Objects.OfType()) availableLines.Push(line); - grid.Clear(false); + grid.Clear(); } if (selectionTimeRange == null) diff --git a/osu.Game.Rulesets.Taiko.Tests/HitObjectApplicationTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/HitObjectApplicationTestScene.cs index a1d000386f..ac01508081 100644 --- a/osu.Game.Rulesets.Taiko.Tests/HitObjectApplicationTestScene.cs +++ b/osu.Game.Rulesets.Taiko.Tests/HitObjectApplicationTestScene.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Tests [SetUpSteps] public void SetUp() - => AddStep("clear SHOC", () => hitObjectContainer.Clear(false)); + => AddStep("clear SHOC", () => hitObjectContainer.Clear()); protected void AddHitObject(DrawableHitObject hitObject) => AddStep("add to SHOC", () => hitObjectContainer.Add(hitObject)); diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index 93e476be76..31f1768044 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Objects.Pooling /// /// The entry holding essential state of this . /// - protected TEntry? Entry { get; private set; } + public TEntry? Entry { get; private set; } /// /// Whether is applied to this . diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 11312a46df..3d74cdedd6 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -17,7 +17,7 @@ using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.UI { - public class HitObjectContainer : LifetimeManagementContainer, IHitObjectContainer + public class HitObjectContainer : CompositeDrawable, IHitObjectContainer { public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); @@ -62,6 +62,7 @@ namespace osu.Game.Rulesets.UI private readonly Dictionary startTimeMap = new Dictionary(); private readonly Dictionary drawableMap = new Dictionary(); private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); + private readonly Dictionary nonPooledDrawableMap = new Dictionary(); [Resolved(CanBeNull = true)] private IPooledHitObjectProvider pooledObjectProvider { get; set; } @@ -72,6 +73,7 @@ namespace osu.Game.Rulesets.UI lifetimeManager.EntryBecameAlive += entryBecameAlive; lifetimeManager.EntryBecameDead += entryBecameDead; + lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary; } protected override void LoadAsyncComplete() @@ -86,7 +88,13 @@ namespace osu.Game.Rulesets.UI public void Add(HitObjectLifetimeEntry entry) => lifetimeManager.AddEntry(entry); - public bool Remove(HitObjectLifetimeEntry entry) => lifetimeManager.RemoveEntry(entry); + public bool Remove(HitObjectLifetimeEntry entry) + { + if (!lifetimeManager.RemoveEntry(entry)) return false; + // It has to be done here because non-pooled entry may be removed by specifying its entry. + nonPooledDrawableMap.Remove(entry); + return true; + } private void entryBecameAlive(LifetimeEntry entry) => addDrawable((HitObjectLifetimeEntry)entry); @@ -96,7 +104,8 @@ namespace osu.Game.Rulesets.UI { Debug.Assert(!drawableMap.ContainsKey(entry)); - var drawable = pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null); + nonPooledDrawableMap.TryGetValue(entry, out var drawable); + drawable ??= pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null); if (drawable == null) throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}."); @@ -104,7 +113,7 @@ namespace osu.Game.Rulesets.UI drawable.OnRevertResult += onRevertResult; bindStartTime(drawable); - AddInternal(drawableMap[entry] = drawable, false); + AddInternal(drawableMap[entry] = drawable); OnAdd(drawable); HitObjectUsageBegan?.Invoke(entry.HitObject); @@ -127,50 +136,42 @@ namespace osu.Game.Rulesets.UI unbindStartTime(drawable); RemoveInternal(drawable); - HitObjectUsageFinished?.Invoke(entry.HitObject); + // The hit object is not freed when the DHO was not pooled. + if (!nonPooledDrawableMap.ContainsKey(entry)) + HitObjectUsageFinished?.Invoke(entry.HitObject); } #endregion #region Non-pooling support - public virtual void Add(DrawableHitObject hitObject) + public virtual void Add(DrawableHitObject drawable) { - bindStartTime(hitObject); + if (drawable.Entry == null) + throw new InvalidOperationException($"May not add a {nameof(DrawableHitObject)} without {nameof(HitObject)} associated"); - hitObject.OnNewResult += onNewResult; - hitObject.OnRevertResult += onRevertResult; - - AddInternal(hitObject); - OnAdd(hitObject); + nonPooledDrawableMap.Add(drawable.Entry, drawable); + Add(drawable.Entry); } - public virtual bool Remove(DrawableHitObject hitObject) + public virtual bool Remove(DrawableHitObject drawable) { - OnRemove(hitObject); - if (!RemoveInternal(hitObject)) + if (drawable.Entry == null) return false; - hitObject.OnNewResult -= onNewResult; - hitObject.OnRevertResult -= onRevertResult; - - unbindStartTime(hitObject); - - return true; + return Remove(drawable.Entry); } public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject); - protected override void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e) + private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) { - if (!(e.Child is DrawableHitObject hitObject)) - return; + if (nonPooledDrawableMap.TryGetValue((HitObjectLifetimeEntry)entry, out var drawable)) + OnChildLifetimeBoundaryCrossed(new LifetimeBoundaryCrossedEvent(drawable, kind, direction)); + } - if ((e.Kind == LifetimeBoundaryKind.End && e.Direction == LifetimeBoundaryCrossingDirection.Forward) - || (e.Kind == LifetimeBoundaryKind.Start && e.Direction == LifetimeBoundaryCrossingDirection.Backward)) - { - hitObject.OnKilled(); - } + protected virtual void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e) + { } #endregion @@ -195,12 +196,11 @@ namespace osu.Game.Rulesets.UI { } - public virtual void Clear(bool disposeChildren = true) + public virtual void Clear() { lifetimeManager.ClearEntries(); - - ClearInternal(disposeChildren); - unbindAllStartTimes(); + nonPooledDrawableMap.Clear(); + Debug.Assert(InternalChildren.Count == 0 && startTimeMap.Count == 0 && drawableMap.Count == 0, "All hit objects should have been removed"); } protected override bool CheckChildrenLife() diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 289578f3d8..a9eaf3da68 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -50,9 +50,9 @@ namespace osu.Game.Rulesets.UI.Scrolling timeRange.ValueChanged += _ => layoutCache.Invalidate(); } - public override void Clear(bool disposeChildren = true) + public override void Clear() { - base.Clear(disposeChildren); + base.Clear(); toComputeLifetime.Clear(); layoutComputed.Clear(); From f55aa016bec4acd26e5525754a50661398da52b5 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 26 Apr 2021 15:53:07 +0900 Subject: [PATCH 02/65] Adopt HitObjectContainer change in a test Non-pooled objects are attached as children only while alive --- osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs index f2bfccb6de..8f3d3f1276 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs @@ -4,6 +4,7 @@ using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Testing; +using osu.Framework.Timing; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -19,7 +20,14 @@ namespace osu.Game.Tests.Gameplay [SetUp] public void Setup() => Schedule(() => { - Child = container = new HitObjectContainer(); + Child = container = new HitObjectContainer + { + Clock = new FramedClock(new ManualClock + { + // Make sure hit objects with `StartTime == 0` are alive + CurrentTime = -1 + }) + }; }); [Test] From 799d2a3300dce4b833a2ca7e9e176d1079aa6038 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 21 Apr 2021 13:00:46 +0900 Subject: [PATCH 03/65] Replace failed mania test (pooling not accounted) with a more robust test Also fix null reference in Playfield --- .../TestSceneDrawableNote.cs | 67 +++++++++++++++++++ .../TestSceneHoldNoteInput.cs | 15 +---- osu.Game/Rulesets/UI/Playfield.cs | 7 +- 3 files changed, 73 insertions(+), 16 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneDrawableNote.cs diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableNote.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableNote.cs new file mode 100644 index 0000000000..4a6c59e297 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableNote.cs @@ -0,0 +1,67 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestSceneDrawableManiaHitObject : OsuTestScene + { + private readonly ManualClock clock = new ManualClock(); + + private Column column; + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = new ScrollingTestContainer(ScrollingDirection.Down) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + TimeRange = 2000, + Clock = new FramedClock(clock), + Child = column = new Column(0) + { + Action = { Value = ManiaAction.Key1 }, + Height = 0.85f, + AccentColour = Color4.Gray + }, + }; + }); + + [Test] + public void TestHoldNoteHeadVisibility() + { + DrawableHoldNote note = null; + AddStep("Add hold note", () => + { + var h = new HoldNote + { + StartTime = 0, + Duration = 1000 + }; + h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + column.Add(note = new DrawableHoldNote(h)); + }); + AddStep("Hold key", () => + { + clock.CurrentTime = 0; + note.OnPressed(ManiaAction.Key1); + }); + AddStep("progress time", () => clock.CurrentTime = 500); + AddAssert("head is visible", () => note.Head.Alpha == 1); + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 668487f673..387c5f4195 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -4,14 +4,11 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Screens; -using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Objects; @@ -411,17 +408,7 @@ namespace osu.Game.Rulesets.Mania.Tests judgementResults = new List(); }); - AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); - AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); - - AddUntilStep("wait for head", () => currentPlayer.GameplayClockContainer.GameplayClock.CurrentTime >= time_head); - AddAssert("head is visible", - () => currentPlayer.ChildrenOfType() - .Single(note => note.HitObject == beatmap.HitObjects[0]) - .Head - .Alpha == 1); - - AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); + AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor?.HasCompleted.Value == true); } private class ScoreAccessibleReplayPlayer : ReplayPlayer diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 17d3cf01a4..b154288dba 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -354,8 +354,11 @@ namespace osu.Game.Rulesets.UI // If this is the first time this DHO is being used, then apply the DHO mods. // This is done before Apply() so that the state is updated once when the hitobject is applied. - foreach (var m in mods.OfType()) - m.ApplyToDrawableHitObjects(dho.Yield()); + if (mods != null) + { + foreach (var m in mods.OfType()) + m.ApplyToDrawableHitObjects(dho.Yield()); + } } if (!lifetimeEntryMap.TryGetValue(hitObject, out var entry)) From 971ca398260275c52fb16be82bfb38993777c504 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 18 Apr 2021 21:46:30 +0900 Subject: [PATCH 04/65] Fix failing taiko tests Non-pooled DHO is now not eagerly loaded --- .../TestSceneFlyingHits.cs | 20 +++++++++---------- .../TestSceneHits.cs | 8 ++++++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs index 63854e7ead..5738be05d7 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs @@ -20,20 +20,19 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestCase(HitType.Rim)] public void TestFlyingHits(HitType hitType) { - DrawableFlyingHit flyingHit = null; - AddStep("add flying hit", () => { addFlyingHit(hitType); - - // flying hits all land in one common scrolling container (and stay there for rewind purposes), - // so we need to manually get the latest one. - flyingHit = this.ChildrenOfType() - .OrderByDescending(h => h.HitObject.StartTime) - .FirstOrDefault(); }); - AddAssert("hit type is correct", () => flyingHit.HitObject.Type == hitType); + AddAssert("hit type is correct", () => + { + // flying hits all land in one common scrolling container (and stay there for rewind purposes), + // so we need to manually get the latest one. + return this.ChildrenOfType() + .OrderByDescending(h => h.HitObject.StartTime) + .FirstOrDefault()?.HitObject.Type == hitType; + }); } private void addFlyingHit(HitType hitType) @@ -42,7 +41,8 @@ namespace osu.Game.Rulesets.Taiko.Tests DrawableDrumRollTick h; DrawableRuleset.Playfield.Add(h = new DrawableDrumRollTick(tick) { JudgementType = hitType }); - ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Great }); + h.OnLoadComplete += _ => + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Great }); } } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index 87c936d386..06acdad3d6 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -129,8 +129,12 @@ namespace osu.Game.Rulesets.Taiko.Tests DrawableRuleset.Playfield.Add(h); - ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(hit, new TaikoJudgement()) { Type = hitResult }); - ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), new JudgementResult(hit.NestedHitObjects.Single(), new TaikoStrongJudgement()) { Type = HitResult.Great }); + h.OnLoadComplete += _ => + { + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(hit, new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), + new JudgementResult(hit.NestedHitObjects.Single(), new TaikoStrongJudgement()) { Type = HitResult.Great }); + }; } private void addMissJudgement() From b88e5a31ea838406b25dfb2e57e992fbb14f89f8 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 28 Apr 2021 12:55:04 +0900 Subject: [PATCH 05/65] Add failing test showing lifetime not recomputed with pooled objects --- .../Gameplay/TestSceneDrawableScrollingRuleset.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 9931ee4a45..75a5eec6f7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -90,6 +90,20 @@ namespace osu.Game.Tests.Visual.Gameplay assertChildPosition(5); } + [TestCase("pooled")] + [TestCase("non-pooled")] + public void TestLifetimeRecomputedWhenTimeRangeChanges(string pooled) + { + var beatmap = createBeatmap(_ => pooled == "pooled" ? new TestPooledHitObject() : new TestHitObject()); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); + createTest(beatmap); + + assertDead(3); + + AddStep("increase time range", () => drawableRuleset.TimeRange.Value = 3 * time_range); + assertPosition(3, 1); + } + [Test] public void TestRelativeBeatLengthScaleSingleTimingPoint() { From 5aa522b1c28cb500ac1985595234b8cd0acacb69 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 28 Apr 2021 20:23:52 +0900 Subject: [PATCH 06/65] Completely delegate DHO lifetime to Entry lifetime A downside is lifetime update is not caught by LifetimeManagementContainer if used. --- .../Gameplay/TestSceneDrawableHitObject.cs | 13 ++++---- .../Pooling/PoolableDrawableWithLifetime.cs | 32 ++++++------------- 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs index 2e3f192f1b..0bec02c488 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs @@ -45,15 +45,16 @@ namespace osu.Game.Tests.Gameplay AddStep("Create DHO", () => { dho = new TestDrawableHitObject(null); - dho.Apply(entry = new TestLifetimeEntry(new HitObject()) - { - LifetimeStart = 0, - LifetimeEnd = 1000, - }); + dho.Apply(entry = new TestLifetimeEntry(new HitObject())); Child = dho; }); - AddStep("KeepAlive = true", () => entry.KeepAlive = true); + AddStep("KeepAlive = true", () => + { + entry.LifetimeStart = 0; + entry.LifetimeEnd = 1000; + entry.KeepAlive = true; + }); AddAssert("Lifetime is overriden", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == double.MaxValue); AddStep("Set LifetimeStart", () => dho.LifetimeStart = 500); diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index 31f1768044..e94b6dca9d 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -28,14 +28,20 @@ namespace osu.Game.Rulesets.Objects.Pooling public override double LifetimeStart { - get => base.LifetimeStart; - set => setLifetime(value, LifetimeEnd); + get => Entry?.LifetimeStart ?? double.MinValue; + set + { + if (Entry != null) Entry.LifetimeStart = value; + } } public override double LifetimeEnd { - get => base.LifetimeEnd; - set => setLifetime(LifetimeStart, value); + get => Entry?.LifetimeEnd ?? double.MaxValue; + set + { + if (Entry != null) Entry.LifetimeEnd = value; + } } public override bool RemoveWhenNotAlive => false; @@ -64,11 +70,8 @@ namespace osu.Game.Rulesets.Objects.Pooling if (HasEntryApplied) free(); - setLifetime(entry.LifetimeStart, entry.LifetimeEnd); Entry = entry; - OnApply(entry); - HasEntryApplied = true; } @@ -95,27 +98,12 @@ namespace osu.Game.Rulesets.Objects.Pooling { } - private void setLifetime(double start, double end) - { - base.LifetimeStart = start; - base.LifetimeEnd = end; - - if (Entry != null) - { - Entry.LifetimeStart = start; - Entry.LifetimeEnd = end; - } - } - private void free() { Debug.Assert(Entry != null && HasEntryApplied); OnFree(Entry); - Entry = null; - setLifetime(double.MaxValue, double.MaxValue); - HasEntryApplied = false; } } From 1d023dcedbb64fcac9d4956c73de22407e51b035 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 26 Apr 2021 19:53:38 +0900 Subject: [PATCH 07/65] Fix mania editor null reference --- .../Edit/Blueprints/ManiaSelectionBlueprint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index 384f49d9b2..8f8f45c0dd 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -18,6 +18,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IScrollingInfo scrollingInfo { get; set; } + // Overriding the base because this method is called right after `Column` is changed and `DrawableObject` is not yet loaded and Parent is not set. + public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Parent.ToLocalSpace(screenSpacePosition) - Position; + protected ManiaSelectionBlueprint(DrawableHitObject drawableObject) : base(drawableObject) { From 4cc94efb06258cc7cf5c445e6318f3ac5cf6d852 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 28 Apr 2021 20:38:44 +0900 Subject: [PATCH 08/65] Fix failing mania test --- .../Editor/TestSceneNotePlacementBlueprint.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs index 36c34a8fb9..a162c5ec44 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs @@ -15,7 +15,6 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; -using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Mania.Tests.Editor @@ -35,7 +34,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Test] public void TestPlaceBeforeCurrentTimeDownwards() { - AddStep("move mouse before current time", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single().ScreenSpaceDrawQuad.BottomLeft - new Vector2(0, 10))); + AddStep("move mouse before current time", () => + { + var column = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(column.ScreenSpacePositionAtTime(-100)); + }); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -45,7 +48,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Test] public void TestPlaceAfterCurrentTimeDownwards() { - AddStep("move mouse after current time", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("move mouse after current time", () => + { + var column = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(column.ScreenSpacePositionAtTime(100)); + }); AddStep("click", () => InputManager.Click(MouseButton.Left)); From c83c8040573ad0cdd3c650487b1aa9a28d756d71 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 29 Apr 2021 14:42:41 +0900 Subject: [PATCH 09/65] Expose lifetime entries from HOC --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 24 +++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 3d74cdedd6..1d32313f2b 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -19,6 +19,16 @@ namespace osu.Game.Rulesets.UI { public class HitObjectContainer : CompositeDrawable, IHitObjectContainer { + /// + /// All entries in this including dead entries. + /// + public IEnumerable Entries => allEntries; + + /// + /// All alive entries and s used by the entries. + /// + public IEnumerable<(HitObjectLifetimeEntry Entry, DrawableHitObject Drawable)> AliveEntries => drawableMap.Select(x => (x.Key, x.Value)); + public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); public IEnumerable AliveObjects => AliveInternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); @@ -60,10 +70,13 @@ namespace osu.Game.Rulesets.UI internal double FutureLifetimeExtension { get; set; } private readonly Dictionary startTimeMap = new Dictionary(); + private readonly Dictionary drawableMap = new Dictionary(); - private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); private readonly Dictionary nonPooledDrawableMap = new Dictionary(); + private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); + private readonly HashSet allEntries = new HashSet(); + [Resolved(CanBeNull = true)] private IPooledHitObjectProvider pooledObjectProvider { get; set; } @@ -86,13 +99,18 @@ namespace osu.Game.Rulesets.UI #region Pooling support - public void Add(HitObjectLifetimeEntry entry) => lifetimeManager.AddEntry(entry); + public void Add(HitObjectLifetimeEntry entry) + { + allEntries.Add(entry); + lifetimeManager.AddEntry(entry); + } public bool Remove(HitObjectLifetimeEntry entry) { if (!lifetimeManager.RemoveEntry(entry)) return false; - // It has to be done here because non-pooled entry may be removed by specifying its entry. + // The entry has to be removed from the non-pooled map here because non-pooled entry may be removed by specifying its entry. nonPooledDrawableMap.Remove(entry); + allEntries.Remove(entry); return true; } From 632bb70e0f7d051c1ee82502122ce5946227cd95 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 29 Apr 2021 15:04:32 +0900 Subject: [PATCH 10/65] Use entry to calculate lifetime in ScrollingHOC DHOs cannot be used to calculate lifetime, it is not created before the entry became alive. --- .../Scrolling/ScrollingHitObjectContainer.cs | 60 ++++++++----------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index a9eaf3da68..915bab9a51 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -5,7 +5,9 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; using osu.Framework.Layout; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -17,16 +19,18 @@ namespace osu.Game.Rulesets.UI.Scrolling private readonly IBindable timeRange = new BindableDouble(); private readonly IBindable direction = new Bindable(); - /// - /// Hit objects which require lifetime computation in the next update call. - /// - private readonly HashSet toComputeLifetime = new HashSet(); - /// /// A set containing all which have an up-to-date layout. /// private readonly HashSet layoutComputed = new HashSet(); + /// + /// A conservative estimate of maximum bounding box of a + /// with respect to the start time position of the hit object. + /// It is used to calculate when the object appears inbound. + /// + protected virtual RectangleF GetDrawRectangle(HitObjectLifetimeEntry entry) => new RectangleF().Inflate(100); + [Resolved] private IScrollingInfo scrollingInfo { get; set; } @@ -54,7 +58,6 @@ namespace osu.Game.Rulesets.UI.Scrolling { base.Clear(); - toComputeLifetime.Clear(); layoutComputed.Clear(); } @@ -166,7 +169,6 @@ namespace osu.Game.Rulesets.UI.Scrolling private void onRemoveRecursive(DrawableHitObject hitObject) { - toComputeLifetime.Remove(hitObject); layoutComputed.Remove(hitObject); hitObject.DefaultsApplied -= invalidateHitObject; @@ -175,14 +177,11 @@ namespace osu.Game.Rulesets.UI.Scrolling onRemoveRecursive(nested); } - /// - /// Make this lifetime and layout computed in next update. - /// private void invalidateHitObject(DrawableHitObject hitObject) { - // Lifetime computation is delayed until next update because - // when the hit object is not pooled this container is not loaded here and `scrollLength` cannot be computed. - toComputeLifetime.Add(hitObject); + if (hitObject.ParentHitObject == null) + updateLifetime(hitObject.Entry); + layoutComputed.Remove(hitObject); } @@ -194,13 +193,8 @@ namespace osu.Game.Rulesets.UI.Scrolling if (!layoutCache.IsValid) { - toComputeLifetime.Clear(); - - foreach (var hitObject in Objects) - { - if (hitObject.HitObject != null) - toComputeLifetime.Add(hitObject); - } + foreach (var entry in Entries) + updateLifetime(entry); layoutComputed.Clear(); @@ -220,11 +214,6 @@ namespace osu.Game.Rulesets.UI.Scrolling layoutCache.Validate(); } - - foreach (var hitObject in toComputeLifetime) - hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject); - - toComputeLifetime.Clear(); } protected override void UpdateAfterChildrenLife() @@ -247,32 +236,31 @@ namespace osu.Game.Rulesets.UI.Scrolling } } - private double computeOriginAdjustedLifetimeStart(DrawableHitObject hitObject) + private void updateLifetime(HitObjectLifetimeEntry entry) { - float originAdjustment = 0.0f; + var rectangle = GetDrawRectangle(entry); + float startOffset = 0; - // calculate the dimension of the part of the hitobject that should already be visible - // when the hitobject origin first appears inside the scrolling container switch (direction.Value) { - case ScrollingDirection.Up: - originAdjustment = hitObject.OriginPosition.Y; + case ScrollingDirection.Right: + startOffset = rectangle.Right; break; case ScrollingDirection.Down: - originAdjustment = hitObject.DrawHeight - hitObject.OriginPosition.Y; + startOffset = rectangle.Bottom; break; case ScrollingDirection.Left: - originAdjustment = hitObject.OriginPosition.X; + startOffset = -rectangle.Left; break; - case ScrollingDirection.Right: - originAdjustment = hitObject.DrawWidth - hitObject.OriginPosition.X; + case ScrollingDirection.Up: + startOffset = -rectangle.Top; break; } - return scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength); + entry.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(entry.HitObject.StartTime, startOffset, timeRange.Value, scrollLength); } private void updateLayoutRecursive(DrawableHitObject hitObject) From 73dfb04df8ba3b45ab9fbdd5bb1934d55f61defc Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 29 Apr 2021 15:17:30 +0900 Subject: [PATCH 11/65] Fix uninitialized scrollLength value is used --- .../Scrolling/ScrollingHitObjectContainer.cs | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 915bab9a51..538d4d1d11 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -185,8 +185,6 @@ namespace osu.Game.Rulesets.UI.Scrolling layoutComputed.Remove(hitObject); } - private float scrollLength; - protected override void Update() { base.Update(); @@ -199,29 +197,16 @@ namespace osu.Game.Rulesets.UI.Scrolling layoutComputed.Clear(); scrollingInfo.Algorithm.Reset(); - - switch (direction.Value) - { - case ScrollingDirection.Up: - case ScrollingDirection.Down: - scrollLength = DrawSize.Y; - break; - - default: - scrollLength = DrawSize.X; - break; - } - layoutCache.Validate(); } } + // We need to calculate hit object positions (including nested hit objects) as soon as possible after lifetimes + // to prevent hit objects displayed in a wrong position for one frame. protected override void UpdateAfterChildrenLife() { base.UpdateAfterChildrenLife(); - // We need to calculate hit object positions (including nested hit objects) as soon as possible after lifetimes - // to prevent hit objects displayed in a wrong position for one frame. // Only AliveObjects need to be considered for layout (reduces overhead in the case of scroll speed changes). foreach (var obj in AliveObjects) { @@ -260,7 +245,7 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } - entry.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(entry.HitObject.StartTime, startOffset, timeRange.Value, scrollLength); + entry.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(entry.HitObject.StartTime, startOffset, timeRange.Value, getLength()); } private void updateLayoutRecursive(DrawableHitObject hitObject) @@ -271,12 +256,12 @@ namespace osu.Game.Rulesets.UI.Scrolling { case ScrollingDirection.Up: case ScrollingDirection.Down: - hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength); + hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, getLength()); break; case ScrollingDirection.Left: case ScrollingDirection.Right: - hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength); + hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, getLength()); break; } } @@ -295,19 +280,19 @@ namespace osu.Game.Rulesets.UI.Scrolling switch (direction.Value) { case ScrollingDirection.Up: - hitObject.Y = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); + hitObject.Y = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, getLength()); break; case ScrollingDirection.Down: - hitObject.Y = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); + hitObject.Y = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, getLength()); break; case ScrollingDirection.Left: - hitObject.X = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); + hitObject.X = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, getLength()); break; case ScrollingDirection.Right: - hitObject.X = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); + hitObject.X = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, getLength()); break; } } From fd8e552a8bb12d2aa854adef2ab0d66ed7d5ea9b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 29 Apr 2021 19:36:52 +0900 Subject: [PATCH 12/65] Fix filename not matching class name --- ...estSceneDrawableNote.cs => TestSceneDrawableManiaHitObject.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Rulesets.Mania.Tests/{TestSceneDrawableNote.cs => TestSceneDrawableManiaHitObject.cs} (100%) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableNote.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs similarity index 100% rename from osu.Game.Rulesets.Mania.Tests/TestSceneDrawableNote.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs From 36438175a0f61efc7c990ad8cf0f127cd09078bc Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 4 May 2021 16:04:58 +0900 Subject: [PATCH 13/65] Throw an exception if try to modify lifetime of PoolableDrawableWithLifetime without lifetime --- .../Pooling/PoolableDrawableWithLifetime.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index e94b6dca9d..d8565f4b30 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -3,6 +3,7 @@ #nullable enable +using System; using System.Diagnostics; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Pooling; @@ -31,7 +32,12 @@ namespace osu.Game.Rulesets.Objects.Pooling get => Entry?.LifetimeStart ?? double.MinValue; set { - if (Entry != null) Entry.LifetimeStart = value; + if (LifetimeStart == value) return; + + if (Entry == null) + throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime)} when entry is not set"); + + Entry.LifetimeStart = value; } } @@ -40,7 +46,12 @@ namespace osu.Game.Rulesets.Objects.Pooling get => Entry?.LifetimeEnd ?? double.MaxValue; set { - if (Entry != null) Entry.LifetimeEnd = value; + if (LifetimeEnd == value) return; + + if (Entry == null) + throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime)} when entry is not set"); + + Entry.LifetimeEnd = value; } } From 913fc8c3bc9eb4c37a0ff66bdccb5e90754dcf8a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 4 May 2021 16:44:48 +0900 Subject: [PATCH 14/65] Revert the change of not adding non-pooled DHO to HOC until alive --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 78 +++++++++++++--------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 1d32313f2b..dcf350cbd4 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.UI /// /// All alive entries and s used by the entries. /// - public IEnumerable<(HitObjectLifetimeEntry Entry, DrawableHitObject Drawable)> AliveEntries => drawableMap.Select(x => (x.Key, x.Value)); + public IEnumerable<(HitObjectLifetimeEntry Entry, DrawableHitObject Drawable)> AliveEntries => aliveDrawableMap.Select(x => (x.Key, x.Value)); public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.UI private readonly Dictionary startTimeMap = new Dictionary(); - private readonly Dictionary drawableMap = new Dictionary(); + private readonly Dictionary aliveDrawableMap = new Dictionary(); private readonly Dictionary nonPooledDrawableMap = new Dictionary(); private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); @@ -108,55 +108,70 @@ namespace osu.Game.Rulesets.UI public bool Remove(HitObjectLifetimeEntry entry) { if (!lifetimeManager.RemoveEntry(entry)) return false; - // The entry has to be removed from the non-pooled map here because non-pooled entry may be removed by specifying its entry. - nonPooledDrawableMap.Remove(entry); + + // This logic is not in `Remove(DrawableHitObject)` because a non-pooled drawable may be removed by specifying its entry. + if (nonPooledDrawableMap.Remove(entry, out var drawable)) + removeDrawable(drawable); + allEntries.Remove(entry); return true; } - private void entryBecameAlive(LifetimeEntry entry) => addDrawable((HitObjectLifetimeEntry)entry); - - private void entryBecameDead(LifetimeEntry entry) => removeDrawable((HitObjectLifetimeEntry)entry); - - private void addDrawable(HitObjectLifetimeEntry entry) + private void entryBecameAlive(LifetimeEntry lifetimeEntry) { - Debug.Assert(!drawableMap.ContainsKey(entry)); + var entry = (HitObjectLifetimeEntry)lifetimeEntry; + Debug.Assert(!aliveDrawableMap.ContainsKey(entry)); - nonPooledDrawableMap.TryGetValue(entry, out var drawable); + bool isNonPooled = nonPooledDrawableMap.TryGetValue(entry, out var drawable); drawable ??= pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null); if (drawable == null) throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}."); + aliveDrawableMap[entry] = drawable; + OnAdd(drawable); + + if (isNonPooled) return; + + addDrawable(drawable); + HitObjectUsageBegan?.Invoke(entry.HitObject); + } + + private void entryBecameDead(LifetimeEntry lifetimeEntry) + { + var entry = (HitObjectLifetimeEntry)lifetimeEntry; + Debug.Assert(aliveDrawableMap.ContainsKey(entry)); + + var drawable = aliveDrawableMap[entry]; + bool isNonPooled = nonPooledDrawableMap.ContainsKey(entry); + + drawable.OnKilled(); + aliveDrawableMap.Remove(entry); + OnRemove(drawable); + + if (isNonPooled) return; + + removeDrawable(drawable); + // The hit object is not freed when the DHO was not pooled. + HitObjectUsageFinished?.Invoke(entry.HitObject); + } + + private void addDrawable(DrawableHitObject drawable) + { drawable.OnNewResult += onNewResult; drawable.OnRevertResult += onRevertResult; bindStartTime(drawable); - AddInternal(drawableMap[entry] = drawable); - OnAdd(drawable); - - HitObjectUsageBegan?.Invoke(entry.HitObject); + AddInternal(drawable); } - private void removeDrawable(HitObjectLifetimeEntry entry) + private void removeDrawable(DrawableHitObject drawable) { - Debug.Assert(drawableMap.ContainsKey(entry)); - - var drawable = drawableMap[entry]; - - // OnKilled can potentially change the hitobject's result, so it needs to run first before unbinding. - drawable.OnKilled(); drawable.OnNewResult -= onNewResult; drawable.OnRevertResult -= onRevertResult; - drawableMap.Remove(entry); - - OnRemove(drawable); unbindStartTime(drawable); - RemoveInternal(drawable); - // The hit object is not freed when the DHO was not pooled. - if (!nonPooledDrawableMap.ContainsKey(entry)) - HitObjectUsageFinished?.Invoke(entry.HitObject); + RemoveInternal(drawable); } #endregion @@ -169,6 +184,7 @@ namespace osu.Game.Rulesets.UI throw new InvalidOperationException($"May not add a {nameof(DrawableHitObject)} without {nameof(HitObject)} associated"); nonPooledDrawableMap.Add(drawable.Entry, drawable); + addDrawable(drawable); Add(drawable.Entry); } @@ -217,8 +233,10 @@ namespace osu.Game.Rulesets.UI public virtual void Clear() { lifetimeManager.ClearEntries(); + foreach (var drawable in nonPooledDrawableMap.Values) + removeDrawable(drawable); nonPooledDrawableMap.Clear(); - Debug.Assert(InternalChildren.Count == 0 && startTimeMap.Count == 0 && drawableMap.Count == 0, "All hit objects should have been removed"); + Debug.Assert(InternalChildren.Count == 0 && startTimeMap.Count == 0 && aliveDrawableMap.Count == 0, "All hit objects should have been removed"); } protected override bool CheckChildrenLife() From 39bccc50489cf502c367891dc0a4071cec4c1dfc Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 4 May 2021 16:45:24 +0900 Subject: [PATCH 15/65] Revert "Adopt HitObjectContainer change in a test" This reverts commit f55aa016 --- osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs index 8f3d3f1276..f2bfccb6de 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs @@ -4,7 +4,6 @@ using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Testing; -using osu.Framework.Timing; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -20,14 +19,7 @@ namespace osu.Game.Tests.Gameplay [SetUp] public void Setup() => Schedule(() => { - Child = container = new HitObjectContainer - { - Clock = new FramedClock(new ManualClock - { - // Make sure hit objects with `StartTime == 0` are alive - CurrentTime = -1 - }) - }; + Child = container = new HitObjectContainer(); }); [Test] From 787bfd6bd08a4afaea8e937ab327445199572948 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 4 May 2021 16:45:39 +0900 Subject: [PATCH 16/65] Revert "Fix failing taiko tests" This reverts commit 971ca398 --- .../TestSceneFlyingHits.cs | 16 ++++++++-------- osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs | 8 ++------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs index 5738be05d7..63854e7ead 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs @@ -20,19 +20,20 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestCase(HitType.Rim)] public void TestFlyingHits(HitType hitType) { + DrawableFlyingHit flyingHit = null; + AddStep("add flying hit", () => { addFlyingHit(hitType); - }); - AddAssert("hit type is correct", () => - { // flying hits all land in one common scrolling container (and stay there for rewind purposes), // so we need to manually get the latest one. - return this.ChildrenOfType() - .OrderByDescending(h => h.HitObject.StartTime) - .FirstOrDefault()?.HitObject.Type == hitType; + flyingHit = this.ChildrenOfType() + .OrderByDescending(h => h.HitObject.StartTime) + .FirstOrDefault(); }); + + AddAssert("hit type is correct", () => flyingHit.HitObject.Type == hitType); } private void addFlyingHit(HitType hitType) @@ -41,8 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Tests DrawableDrumRollTick h; DrawableRuleset.Playfield.Add(h = new DrawableDrumRollTick(tick) { JudgementType = hitType }); - h.OnLoadComplete += _ => - ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Great }); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Great }); } } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index 06acdad3d6..87c936d386 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -129,12 +129,8 @@ namespace osu.Game.Rulesets.Taiko.Tests DrawableRuleset.Playfield.Add(h); - h.OnLoadComplete += _ => - { - ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(hit, new TaikoJudgement()) { Type = hitResult }); - ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), - new JudgementResult(hit.NestedHitObjects.Single(), new TaikoStrongJudgement()) { Type = HitResult.Great }); - }; + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(hit, new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), new JudgementResult(hit.NestedHitObjects.Single(), new TaikoStrongJudgement()) { Type = HitResult.Great }); } private void addMissJudgement() From 4a93e27e8394ed5d347ca06989951319afb6e6c4 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 4 May 2021 16:46:30 +0900 Subject: [PATCH 17/65] Revert "Fix mania editor null reference" This reverts commit 1d023dce --- .../Edit/Blueprints/ManiaSelectionBlueprint.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index 8f8f45c0dd..384f49d9b2 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -18,9 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IScrollingInfo scrollingInfo { get; set; } - // Overriding the base because this method is called right after `Column` is changed and `DrawableObject` is not yet loaded and Parent is not set. - public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Parent.ToLocalSpace(screenSpacePosition) - Position; - protected ManiaSelectionBlueprint(DrawableHitObject drawableObject) : base(drawableObject) { From aa42cf2fc055eeee7a01188fbf61ef3735651dd3 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 4 May 2021 16:56:48 +0900 Subject: [PATCH 18/65] Fix setting lifetime during KeepAlive is ignored --- .../Pooling/PoolableDrawableWithLifetime.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index d8565f4b30..ed0430012a 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -32,12 +32,11 @@ namespace osu.Game.Rulesets.Objects.Pooling get => Entry?.LifetimeStart ?? double.MinValue; set { - if (LifetimeStart == value) return; - - if (Entry == null) + if (Entry == null && LifetimeStart != value) throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime)} when entry is not set"); - Entry.LifetimeStart = value; + if (Entry != null) + Entry.LifetimeStart = value; } } @@ -46,12 +45,11 @@ namespace osu.Game.Rulesets.Objects.Pooling get => Entry?.LifetimeEnd ?? double.MaxValue; set { - if (LifetimeEnd == value) return; - - if (Entry == null) + if (Entry == null && LifetimeEnd != value) throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime)} when entry is not set"); - Entry.LifetimeEnd = value; + if (Entry != null) + Entry.LifetimeEnd = value; } } From 7ca3e13712b974ce4cee0b431602ddf2fa654eed Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 10 May 2021 07:43:01 +0300 Subject: [PATCH 19/65] Implement basic years panel --- .../Visual/Online/TestSceneNewsYearsPanel.cs | 34 ++++++ osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 108 ++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs create mode 100644 osu.Game/Overlays/News/Sidebar/YearsPanel.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs new file mode 100644 index 0000000000..75975f04f8 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Overlays.News.Sidebar; +using osu.Framework.Allocation; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneNewsYearsPanel : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + private readonly YearsPanel panel; + + public TestSceneNewsYearsPanel() + { + Add(panel = new YearsPanel() + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + AddStep("Load years", () => panel.Years = new[] { 1000, 2000, 3000, 4000 }); + AddStep("Load different years", () => panel.Years = new[] { 1001, 2001, 3001, 4001, 5001, 6001, 7001, 8001 }); + } + } +} diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs new file mode 100644 index 0000000000..d71c7ba48e --- /dev/null +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -0,0 +1,108 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osuTK; +using System.Linq; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using System.Collections.Generic; +using osu.Game.Graphics; +using osu.Framework.Bindables; +using System.Collections.Specialized; + +namespace osu.Game.Overlays.News.Sidebar +{ + public class YearsPanel : CompositeDrawable + { + public int[] Years + { + set + { + years.Clear(); + years.AddRange(value); + } + } + + private readonly BindableList years = new BindableList(); + + private FillFlowContainer flow; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Width = 160; + AutoSizeAxes = Axes.Y; + Masking = true; + CornerRadius = 6; + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background3 + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(5), + Child = flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(5) + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + years.BindCollectionChanged((u, v) => + { + switch (v.Action) + { + case NotifyCollectionChangedAction.Add: + flow.Children = years.Select(y => new YearButton(y)).ToArray(); + break; + } + }, true); + } + + private class YearButton : OsuHoverContainer + { + protected override IEnumerable EffectTargets => new[] { text }; + + private readonly int year; + private readonly OsuSpriteText text; + + public YearButton(int year) + { + this.year = year; + + Size = new Vector2(33.75f, 15); + Child = text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 12), + Text = year.ToString() + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + IdleColour = colourProvider.Light2; + HoverColour = colourProvider.Light1; + Action = () => { }; // TODO + } + } + } +} From 7971a2ef485bf8e80104a2032c378da069acdb7f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 10 May 2021 08:47:00 +0300 Subject: [PATCH 20/65] Implement MonthPanel component --- .../Visual/Online/TestSceneNewsMonthPanel.cs | 80 +++++++++ osu.Game/Overlays/News/Sidebar/MonthPanel.cs | 155 ++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs create mode 100644 osu.Game/Overlays/News/Sidebar/MonthPanel.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs new file mode 100644 index 0000000000..75e02b66e1 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.News.Sidebar; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneNewsMonthPanel : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + [Test] + public void CreateClosedMonthPanel() + { + AddStep("Create", () => Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background2, + }, + new MonthPanel(DateTime.Now, posts), + } + }); + } + + [Test] + public void CreateOpenMonthPanel() + { + AddStep("Create", () => Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background2, + }, + new MonthPanel(DateTime.Now, posts) + { + IsOpen = { Value = true } + }, + } + }); + } + + private static APINewsPost[] posts => new[] + { + new APINewsPost + { + Title = "Short title" + }, + new APINewsPost + { + Title = "Oh boy that's a long post title I wonder if it will break anything" + }, + new APINewsPost + { + Title = "Medium title, nothing to see here" + } + }; + } +} diff --git a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs new file mode 100644 index 0000000000..1dd1c561ab --- /dev/null +++ b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs @@ -0,0 +1,155 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Graphics.Containers; +using osuTK; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; +using System.Linq; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Overlays.News.Sidebar +{ + public class MonthPanel : CompositeDrawable + { + public readonly BindableBool IsOpen = new BindableBool(); + + private readonly FillFlowContainer postsFlow; + + public MonthPanel(DateTime date, APINewsPost[] posts) + { + Width = 160; + AutoSizeDuration = 250; + AutoSizeEasing = Easing.OutQuint; + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + new DropdownButton(date) + { + IsOpen = { BindTarget = IsOpen } + }, + postsFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = posts.Select(p => new PostButton(p)).ToArray() + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + IsOpen.BindValueChanged(open => + { + ClearTransforms(); + + if (open.NewValue) + { + AutoSizeAxes = Axes.Y; + postsFlow.FadeIn(250, Easing.OutQuint); + } + else + { + AutoSizeAxes = Axes.None; + this.ResizeHeightTo(15, 250, Easing.OutQuint); + + postsFlow.FadeOut(250, Easing.OutQuint); + } + }, true); + + // First state change should be instant. + FinishTransforms(); + postsFlow.FinishTransforms(); + } + + private class DropdownButton : OsuHoverContainer + { + public readonly BindableBool IsOpen = new BindableBool(); + + protected override IEnumerable EffectTargets => null; + + private readonly SpriteIcon icon; + + public DropdownButton(DateTime date) + { + Size = new Vector2(160, 15); + Action = IsOpen.Toggle; + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Text = date.ToString("MMM yyyy") + }, + icon = new SpriteIcon + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(10), + Icon = FontAwesome.Solid.ChevronDown + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + IsOpen.BindValueChanged(open => + { + icon.Scale = new Vector2(1, open.NewValue ? -1 : 1); + }, true); + } + } + + private class PostButton : OsuHoverContainer + { + protected override IEnumerable EffectTargets => new[] { text }; + + private readonly APINewsPost post; + private readonly TextFlowContainer text; + + public PostButton(APINewsPost post) + { + this.post = post; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Child = text = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = post.Title + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + IdleColour = colourProvider.Light2; + HoverColour = colourProvider.Light1; + Action = () => { }; // TODO + } + } + } +} From 4b972249320ad7832619a999679e1a6558a9e7bf Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 10 May 2021 09:53:52 +0300 Subject: [PATCH 21/65] Implement NewsSideBar component --- .../Visual/Online/TestSceneNewsSideBar.cs | 114 ++++++++++++++++++ .../Online/API/Requests/GetNewsResponse.cs | 3 + .../API/Requests/Responses/APINewsSidebar.cs | 20 +++ osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 114 ++++++++++++++++++ 4 files changed, 251 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs create mode 100644 osu.Game/Online/API/Requests/Responses/APINewsSidebar.cs create mode 100644 osu.Game/Overlays/News/Sidebar/NewsSideBar.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs new file mode 100644 index 0000000000..931341f837 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs @@ -0,0 +1,114 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.News.Sidebar; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneNewsSideBar : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + private NewsSideBar sidebar; + + [Test] + public void TestCreateEmpty() + { + createSidebar(null); + } + + [Test] + public void TestCreateWithData() + { + createSidebar(metadata); + } + + [Test] + public void TestDataChange() + { + createSidebar(null); + AddStep("Add data", () => + { + if (sidebar != null) + sidebar.Metadata.Value = metadata; + }); + } + + private void createSidebar(APINewsSidebar metadata) => AddStep("Create", () => Child = sidebar = new NewsSideBar + { + Metadata = { Value = metadata } + }); + + private static APINewsSidebar metadata = new APINewsSidebar + { + CurrentYear = 2021, + Years = new[] + { + 2021, + 2020, + 2019, + 2018, + 2017, + 2016, + 2015, + 2014, + 2013 + }, + NewsPosts = new List + { + new APINewsPost + { + Title = "(Mar) Short title", + PublishedAt = new DateTime(2021, 3, 1) + }, + new APINewsPost + { + Title = "(Mar) Oh boy that's a long post title I wonder if it will break anything", + PublishedAt = new DateTime(2021, 3, 1) + }, + new APINewsPost + { + Title = "(Mar) Medium title, nothing to see here", + PublishedAt = new DateTime(2021, 3, 1) + }, + new APINewsPost + { + Title = "(Feb) Short title", + PublishedAt = new DateTime(2021, 2, 1) + }, + new APINewsPost + { + Title = "(Feb) Oh boy that's a long post title I wonder if it will break anything", + PublishedAt = new DateTime(2021, 2, 1) + }, + new APINewsPost + { + Title = "(Feb) Medium title, nothing to see here", + PublishedAt = new DateTime(2021, 2, 1) + }, + new APINewsPost + { + Title = "Short title", + PublishedAt = new DateTime(2021, 1, 1) + }, + new APINewsPost + { + Title = "Oh boy that's a long post title I wonder if it will break anything", + PublishedAt = new DateTime(2021, 1, 1) + }, + new APINewsPost + { + Title = "Medium title, nothing to see here", + PublishedAt = new DateTime(2021, 1, 1) + } + } + }; + } +} diff --git a/osu.Game/Online/API/Requests/GetNewsResponse.cs b/osu.Game/Online/API/Requests/GetNewsResponse.cs index 835289a51d..98f76d105c 100644 --- a/osu.Game/Online/API/Requests/GetNewsResponse.cs +++ b/osu.Game/Online/API/Requests/GetNewsResponse.cs @@ -11,5 +11,8 @@ namespace osu.Game.Online.API.Requests { [JsonProperty("news_posts")] public IEnumerable NewsPosts; + + [JsonProperty("news_sidebar")] + public APINewsSidebar SidebarMetadata; } } diff --git a/osu.Game/Online/API/Requests/Responses/APINewsSidebar.cs b/osu.Game/Online/API/Requests/Responses/APINewsSidebar.cs new file mode 100644 index 0000000000..b8d6469a1d --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APINewsSidebar.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APINewsSidebar + { + [JsonProperty("current_year")] + public int CurrentYear { get; set; } + + [JsonProperty("news_posts")] + public IEnumerable NewsPosts { get; set; } + + [JsonProperty("years")] + public int[] Years { get; set; } + } +} diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs new file mode 100644 index 0000000000..34c995cab6 --- /dev/null +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -0,0 +1,114 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Graphics.Shapes; +using osuTK; +using System.Collections.Generic; +using System; + +namespace osu.Game.Overlays.News.Sidebar +{ + public class NewsSideBar : CompositeDrawable + { + public readonly Bindable Metadata = new Bindable(); + + private YearsPanel yearsPanel; + private FillFlowContainer monthsFlow; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + RelativeSizeAxes = Axes.Y; + Width = 250; + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4 + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Top = 20, + Left = 50, + Right = 30 + }, + Child = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + yearsPanel = new YearsPanel(), + monthsFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10) + } + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Metadata.BindValueChanged(metadata => + { + monthsFlow.Clear(); + + if (metadata.NewValue == null) + { + yearsPanel.Hide(); + return; + } + + yearsPanel.Years = metadata.NewValue.Years; + yearsPanel.Show(); + + if (metadata.NewValue != null) + { + var dict = new Dictionary>(); + + foreach (var p in metadata.NewValue.NewsPosts) + { + var month = p.PublishedAt.Month; + + if (dict.ContainsKey(month)) + dict[month].Add(p); + else + { + dict.Add(month, new List(new[] { p })); + } + } + + bool isFirst = true; + + foreach (var keyValuePair in dict) + { + monthsFlow.Add(new MonthPanel(new DateTime(metadata.NewValue.CurrentYear, keyValuePair.Key, 1), keyValuePair.Value.ToArray()) + { + IsOpen = { Value = isFirst } + }); + + isFirst = false; + } + } + }, true); + } + } +} From 0d243be457f514eab35edd8a3499f51fd0f573c7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 10 May 2021 10:07:43 +0300 Subject: [PATCH 22/65] CI fixes --- osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs | 2 +- osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs | 2 +- osu.Game/Overlays/News/Sidebar/MonthPanel.cs | 3 --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 3 --- 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs index 931341f837..96161c28ed 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Online Metadata = { Value = metadata } }); - private static APINewsSidebar metadata = new APINewsSidebar + private static readonly APINewsSidebar metadata = new APINewsSidebar { CurrentYear = 2021, Years = new[] diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs index 75975f04f8..446cd06eea 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Online public TestSceneNewsYearsPanel() { - Add(panel = new YearsPanel() + Add(panel = new YearsPanel { Anchor = Anchor.Centre, Origin = Anchor.Centre diff --git a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs index 1dd1c561ab..8b405eae10 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs @@ -125,13 +125,10 @@ namespace osu.Game.Overlays.News.Sidebar { protected override IEnumerable EffectTargets => new[] { text }; - private readonly APINewsPost post; private readonly TextFlowContainer text; public PostButton(APINewsPost post) { - this.post = post; - RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index d71c7ba48e..046e1804bd 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -79,13 +79,10 @@ namespace osu.Game.Overlays.News.Sidebar { protected override IEnumerable EffectTargets => new[] { text }; - private readonly int year; private readonly OsuSpriteText text; public YearButton(int year) { - this.year = year; - Size = new Vector2(33.75f, 15); Child = text = new OsuSpriteText { From 220eef035181e3d08b0fde4430b966b3f69c3cb7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 10 May 2021 17:00:18 +0300 Subject: [PATCH 23/65] Remove overcomplicated date logic in MonthPanel --- .../Visual/Online/TestSceneNewsMonthPanel.cs | 10 ++++++---- osu.Game/Overlays/News/Sidebar/MonthPanel.cs | 6 +++--- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 3 +-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs index 75e02b66e1..ee7fb8b407 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -33,7 +34,7 @@ namespace osu.Game.Tests.Visual.Online RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background2, }, - new MonthPanel(DateTime.Now, posts), + new MonthPanel(posts), } }); } @@ -53,7 +54,7 @@ namespace osu.Game.Tests.Visual.Online RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background2, }, - new MonthPanel(DateTime.Now, posts) + new MonthPanel(posts) { IsOpen = { Value = true } }, @@ -61,11 +62,12 @@ namespace osu.Game.Tests.Visual.Online }); } - private static APINewsPost[] posts => new[] + private static List posts => new List { new APINewsPost { - Title = "Short title" + Title = "Short title", + PublishedAt = DateTimeOffset.Now }, new APINewsPost { diff --git a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs index 8b405eae10..5f2acd63d1 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.News.Sidebar private readonly FillFlowContainer postsFlow; - public MonthPanel(DateTime date, APINewsPost[] posts) + public MonthPanel(List posts) { Width = 160; AutoSizeDuration = 250; @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.News.Sidebar Spacing = new Vector2(0, 5), Children = new Drawable[] { - new DropdownButton(date) + new DropdownButton(posts[0].PublishedAt) { IsOpen = { BindTarget = IsOpen } }, @@ -87,7 +87,7 @@ namespace osu.Game.Overlays.News.Sidebar private readonly SpriteIcon icon; - public DropdownButton(DateTime date) + public DropdownButton(DateTimeOffset date) { Size = new Vector2(160, 15); Action = IsOpen.Toggle; diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 34c995cab6..9a2fdf2d97 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -9,7 +9,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Framework.Graphics.Shapes; using osuTK; using System.Collections.Generic; -using System; namespace osu.Game.Overlays.News.Sidebar { @@ -100,7 +99,7 @@ namespace osu.Game.Overlays.News.Sidebar foreach (var keyValuePair in dict) { - monthsFlow.Add(new MonthPanel(new DateTime(metadata.NewValue.CurrentYear, keyValuePair.Key, 1), keyValuePair.Value.ToArray()) + monthsFlow.Add(new MonthPanel(keyValuePair.Value) { IsOpen = { Value = isFirst } }); From 69f01e82db6cf14052f6fe05d063619042449b86 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 14:42:56 +0300 Subject: [PATCH 24/65] Add bottom padding for NewsSideBar content --- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 9a2fdf2d97..4444cc79b8 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { - Top = 20, + Vertical = 20, Left = 50, Right = 30 }, From 711e7ba860cd0e3e2113fa09ed53b79c6e6a07e3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 14:43:23 +0300 Subject: [PATCH 25/65] Apply suggestions for MonthPanel --- osu.Game/Overlays/News/Sidebar/MonthPanel.cs | 37 ++++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs index 5f2acd63d1..4d7a5f18aa 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs @@ -19,6 +19,9 @@ namespace osu.Game.Overlays.News.Sidebar { public class MonthPanel : CompositeDrawable { + private const int header_height = 15; + private const int animation_duration = 250; + public readonly BindableBool IsOpen = new BindableBool(); private readonly FillFlowContainer postsFlow; @@ -26,8 +29,7 @@ namespace osu.Game.Overlays.News.Sidebar public MonthPanel(List posts) { Width = 160; - AutoSizeDuration = 250; - AutoSizeEasing = Easing.OutQuint; + Masking = true; InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -63,33 +65,46 @@ namespace osu.Game.Overlays.News.Sidebar if (open.NewValue) { AutoSizeAxes = Axes.Y; - postsFlow.FadeIn(250, Easing.OutQuint); + postsFlow.FadeIn(animation_duration, Easing.OutQuint); } else { AutoSizeAxes = Axes.None; - this.ResizeHeightTo(15, 250, Easing.OutQuint); + this.ResizeHeightTo(header_height, animation_duration, Easing.OutQuint); - postsFlow.FadeOut(250, Easing.OutQuint); + postsFlow.FadeOut(animation_duration, Easing.OutQuint); } }, true); // First state change should be instant. - FinishTransforms(); - postsFlow.FinishTransforms(); + FinishTransforms(true); } - private class DropdownButton : OsuHoverContainer + private bool shouldUpdateAutosize = true; + + // Workaround to allow the dropdown to be opened immediately since FinishTransforms doesn't work for AutosizeDuration. + protected override void UpdateAfterAutoSize() + { + base.UpdateAfterAutoSize(); + + if (shouldUpdateAutosize) + { + AutoSizeDuration = animation_duration; + AutoSizeEasing = Easing.OutQuint; + + shouldUpdateAutosize = false; + } + } + + private class DropdownButton : OsuClickableContainer { public readonly BindableBool IsOpen = new BindableBool(); - protected override IEnumerable EffectTargets => null; - private readonly SpriteIcon icon; public DropdownButton(DateTimeOffset date) { - Size = new Vector2(160, 15); + Size = new Vector2(160, header_height); Action = IsOpen.Toggle; Children = new Drawable[] { From e736240a064020b4f7b767acfb278ca15d02f3c7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 15:12:04 +0300 Subject: [PATCH 26/65] Use lookup instead of dictionary to distribute posts --- osu.Game/Overlays/News/Sidebar/MonthPanel.cs | 4 +-- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 29 +++++-------------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs index 4d7a5f18aa..5460ccce16 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthPanel.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.News.Sidebar private readonly FillFlowContainer postsFlow; - public MonthPanel(List posts) + public MonthPanel(IEnumerable posts) { Width = 160; Masking = true; @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.News.Sidebar Spacing = new Vector2(0, 5), Children = new Drawable[] { - new DropdownButton(posts[0].PublishedAt) + new DropdownButton(posts.ElementAt(0).PublishedAt) { IsOpen = { BindTarget = IsOpen } }, diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 4444cc79b8..4d2d3949bd 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics; using osu.Game.Online.API.Requests.Responses; using osu.Framework.Graphics.Shapes; using osuTK; -using System.Collections.Generic; +using System.Linq; namespace osu.Game.Overlays.News.Sidebar { @@ -81,30 +81,17 @@ namespace osu.Game.Overlays.News.Sidebar if (metadata.NewValue != null) { - var dict = new Dictionary>(); + var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); - foreach (var p in metadata.NewValue.NewsPosts) + var keys = lookup.Select(kvp => kvp.Key); + var sortedKeys = keys.OrderByDescending(k => k).ToList(); + + for (int i = 0; i < sortedKeys.Count; i++) { - var month = p.PublishedAt.Month; - - if (dict.ContainsKey(month)) - dict[month].Add(p); - else + monthsFlow.Add(new MonthPanel(lookup[sortedKeys[i]]) { - dict.Add(month, new List(new[] { p })); - } - } - - bool isFirst = true; - - foreach (var keyValuePair in dict) - { - monthsFlow.Add(new MonthPanel(keyValuePair.Value) - { - IsOpen = { Value = isFirst } + IsOpen = { Value = i == 0 } }); - - isFirst = false; } } }, true); From 9603712aa1a21b930179533f788e36448c9ec8a1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 15:33:27 +0300 Subject: [PATCH 27/65] Cache metadata in NewsSideBar --- .../Visual/Online/TestSceneNewsYearsPanel.cs | 32 ++++++++++++++----- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 13 ++------ osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 29 +++++++---------- 3 files changed, 38 insertions(+), 36 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs index 446cd06eea..031a98dd8b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs @@ -5,6 +5,9 @@ using osu.Framework.Graphics; using osu.Game.Overlays.News.Sidebar; using osu.Framework.Allocation; using osu.Game.Overlays; +using osu.Framework.Bindables; +using osu.Game.Online.API.Requests.Responses; +using NUnit.Framework; namespace osu.Game.Tests.Visual.Online { @@ -13,22 +16,35 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - private readonly YearsPanel panel; + [Cached] + private readonly Bindable metadataBindable = new Bindable(); - public TestSceneNewsYearsPanel() + private YearsPanel panel; + + [SetUp] + public void SetUp() { - Add(panel = new YearsPanel + Child = panel = new YearsPanel { Anchor = Anchor.Centre, Origin = Anchor.Centre - }); + }; } - protected override void LoadComplete() + [Test] + public void TestMetadata() { - base.LoadComplete(); - AddStep("Load years", () => panel.Years = new[] { 1000, 2000, 3000, 4000 }); - AddStep("Load different years", () => panel.Years = new[] { 1001, 2001, 3001, 4001, 5001, 6001, 7001, 8001 }); + AddStep("Change metadata to null", () => metadataBindable.Value = null); + AddAssert("Panel is hidden", () => panel.IsPresent == false); + AddStep("Change metadata", () => metadataBindable.Value = metadata); + AddAssert("Panel is visible", () => panel.IsPresent == true); + AddStep("Change metadata to null", () => metadataBindable.Value = null); + AddAssert("Panel is hidden", () => panel.IsPresent == false); } + + private static readonly APINewsSidebar metadata = new APINewsSidebar + { + Years = new[] { 1001, 2001, 3001, 4001, 5001, 6001, 7001, 8001, 9001 } + }; } } diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 4d2d3949bd..75726c5fed 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -14,9 +14,9 @@ namespace osu.Game.Overlays.News.Sidebar { public class NewsSideBar : CompositeDrawable { + [Cached] public readonly Bindable Metadata = new Bindable(); - private YearsPanel yearsPanel; private FillFlowContainer monthsFlow; [BackgroundDependencyLoader] @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.News.Sidebar Spacing = new Vector2(0, 20), Children = new Drawable[] { - yearsPanel = new YearsPanel(), + new YearsPanel(), monthsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -70,15 +70,6 @@ namespace osu.Game.Overlays.News.Sidebar { monthsFlow.Clear(); - if (metadata.NewValue == null) - { - yearsPanel.Hide(); - return; - } - - yearsPanel.Years = metadata.NewValue.Years; - yearsPanel.Show(); - if (metadata.NewValue != null) { var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 046e1804bd..23dd8d8a5e 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -12,28 +12,21 @@ using osu.Game.Graphics.Sprites; using System.Collections.Generic; using osu.Game.Graphics; using osu.Framework.Bindables; -using System.Collections.Specialized; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.News.Sidebar { public class YearsPanel : CompositeDrawable { - public int[] Years - { - set - { - years.Clear(); - years.AddRange(value); - } - } - - private readonly BindableList years = new BindableList(); + private readonly Bindable metadata = new Bindable(); private FillFlowContainer flow; [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, Bindable metadata) { + this.metadata.BindTo(metadata); + Width = 160; AutoSizeAxes = Axes.Y; Masking = true; @@ -64,14 +57,16 @@ namespace osu.Game.Overlays.News.Sidebar { base.LoadComplete(); - years.BindCollectionChanged((u, v) => + metadata.BindValueChanged(m => { - switch (v.Action) + if (m.NewValue == null) { - case NotifyCollectionChangedAction.Add: - flow.Children = years.Select(y => new YearButton(y)).ToArray(); - break; + Hide(); + return; } + + flow.Children = m.NewValue.Years.Select(y => new YearButton(y)).ToArray(); + Show(); }, true); } From 0a9c3c9413e89efaf183793fc781e3bcc7f4a5bc Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 15:39:50 +0300 Subject: [PATCH 28/65] Move metadata change logic to it's own method --- .../Visual/Online/TestSceneNewsYearsPanel.cs | 2 +- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 43 +++++++++++-------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs index 031a98dd8b..014a9ac7ed 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestMetadata() + public void TestVisibility() { AddStep("Change metadata to null", () => metadataBindable.Value = null); AddAssert("Panel is hidden", () => panel.IsPresent == false); diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 75726c5fed..d90b73aa82 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -66,26 +66,35 @@ namespace osu.Game.Overlays.News.Sidebar { base.LoadComplete(); - Metadata.BindValueChanged(metadata => + Metadata.BindValueChanged(onMetadataChanged, true); + } + + private void onMetadataChanged(ValueChangedEvent metadata) + { + monthsFlow.Clear(); + + if (metadata.NewValue == null) + return; + + var allPosts = metadata.NewValue.NewsPosts; + + if (!allPosts?.Any() ?? false) + return; + + var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); + + var keys = lookup.Select(kvp => kvp.Key); + var sortedKeys = keys.OrderByDescending(k => k).ToList(); + + for (int i = 0; i < sortedKeys.Count; i++) { - monthsFlow.Clear(); + var posts = lookup[sortedKeys[i]]; - if (metadata.NewValue != null) + monthsFlow.Add(new MonthPanel(posts) { - var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); - - var keys = lookup.Select(kvp => kvp.Key); - var sortedKeys = keys.OrderByDescending(k => k).ToList(); - - for (int i = 0; i < sortedKeys.Count; i++) - { - monthsFlow.Add(new MonthPanel(lookup[sortedKeys[i]]) - { - IsOpen = { Value = i == 0 } - }); - } - } - }, true); + IsOpen = { Value = i == 0 } + }); + } } } } From 705aad262aa8c2e4ea798a79306d2df820693a61 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 15:42:40 +0300 Subject: [PATCH 29/65] Rename MonthPanel to MonthDropdown --- ...SceneNewsMonthPanel.cs => TestSceneNewsMonthDropdown.cs} | 6 +++--- .../News/Sidebar/{MonthPanel.cs => MonthDropdown.cs} | 4 ++-- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) rename osu.Game.Tests/Visual/Online/{TestSceneNewsMonthPanel.cs => TestSceneNewsMonthDropdown.cs} (94%) rename osu.Game/Overlays/News/Sidebar/{MonthPanel.cs => MonthDropdown.cs} (97%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs similarity index 94% rename from osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs rename to osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs index ee7fb8b407..c51e299f78 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs @@ -14,7 +14,7 @@ using osu.Game.Overlays.News.Sidebar; namespace osu.Game.Tests.Visual.Online { - public class TestSceneNewsMonthPanel : OsuTestScene + public class TestSceneNewsMonthDropdown : OsuTestScene { [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Online RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background2, }, - new MonthPanel(posts), + new MonthDropdown(posts), } }); } @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Online RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background2, }, - new MonthPanel(posts) + new MonthDropdown(posts) { IsOpen = { Value = true } }, diff --git a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs similarity index 97% rename from osu.Game/Overlays/News/Sidebar/MonthPanel.cs rename to osu.Game/Overlays/News/Sidebar/MonthDropdown.cs index 5460ccce16..87a72a3eaf 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs @@ -17,7 +17,7 @@ using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.News.Sidebar { - public class MonthPanel : CompositeDrawable + public class MonthDropdown : CompositeDrawable { private const int header_height = 15; private const int animation_duration = 250; @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.News.Sidebar private readonly FillFlowContainer postsFlow; - public MonthPanel(IEnumerable posts) + public MonthDropdown(IEnumerable posts) { Width = 160; Masking = true; diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index d90b73aa82..1518d61d59 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.News.Sidebar [Cached] public readonly Bindable Metadata = new Bindable(); - private FillFlowContainer monthsFlow; + private FillFlowContainer monthsFlow; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -50,7 +50,7 @@ namespace osu.Game.Overlays.News.Sidebar Children = new Drawable[] { new YearsPanel(), - monthsFlow = new FillFlowContainer + monthsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, @@ -90,7 +90,7 @@ namespace osu.Game.Overlays.News.Sidebar { var posts = lookup[sortedKeys[i]]; - monthsFlow.Add(new MonthPanel(posts) + monthsFlow.Add(new MonthDropdown(posts) { IsOpen = { Value = i == 0 } }); From 01f5c77dac4b5705e78ba6ec10b2d864ea92073f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 15:56:50 +0300 Subject: [PATCH 30/65] Add better comments explaining empty actions --- osu.Game/Overlays/News/Sidebar/MonthDropdown.cs | 2 +- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs index 87a72a3eaf..ff50bfe4c2 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs @@ -160,7 +160,7 @@ namespace osu.Game.Overlays.News.Sidebar { IdleColour = colourProvider.Light2; HoverColour = colourProvider.Light1; - Action = () => { }; // TODO + Action = () => { }; // Avoid button being disabled since there's no proper action assigned. } } } diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 23dd8d8a5e..c06a8424f6 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -93,7 +93,7 @@ namespace osu.Game.Overlays.News.Sidebar { IdleColour = colourProvider.Light2; HoverColour = colourProvider.Light1; - Action = () => { }; // TODO + Action = () => { }; // Avoid button being disabled since there's no proper action assigned. } } } From 208224cc0deb0ae2d79c155f04073577332f322f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 16:08:09 +0300 Subject: [PATCH 31/65] CI fixes --- osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs | 6 +++--- osu.Game/Overlays/News/Sidebar/MonthDropdown.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs index 014a9ac7ed..39bb97fe98 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs @@ -35,11 +35,11 @@ namespace osu.Game.Tests.Visual.Online public void TestVisibility() { AddStep("Change metadata to null", () => metadataBindable.Value = null); - AddAssert("Panel is hidden", () => panel.IsPresent == false); + AddAssert("Panel is hidden", () => !panel.IsPresent); AddStep("Change metadata", () => metadataBindable.Value = metadata); - AddAssert("Panel is visible", () => panel.IsPresent == true); + AddAssert("Panel is visible", () => panel.IsPresent); AddStep("Change metadata to null", () => metadataBindable.Value = null); - AddAssert("Panel is hidden", () => panel.IsPresent == false); + AddAssert("Panel is hidden", () => !panel.IsPresent); } private static readonly APINewsSidebar metadata = new APINewsSidebar diff --git a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs index ff50bfe4c2..35092acdc1 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs @@ -160,7 +160,7 @@ namespace osu.Game.Overlays.News.Sidebar { IdleColour = colourProvider.Light2; HoverColour = colourProvider.Light1; - Action = () => { }; // Avoid button being disabled since there's no proper action assigned. + Action = () => { }; // Avoid button being disabled since there's no proper action assigned. } } } From 1c0b0996cf43249e4127238f39965133d65c9d4f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 16:42:18 +0300 Subject: [PATCH 32/65] Rename DropdownButton to DropdownHeader --- osu.Game/Overlays/News/Sidebar/MonthDropdown.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs index 35092acdc1..cc06f48544 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.News.Sidebar Spacing = new Vector2(0, 5), Children = new Drawable[] { - new DropdownButton(posts.ElementAt(0).PublishedAt) + new DropdownHeader(posts.ElementAt(0).PublishedAt) { IsOpen = { BindTarget = IsOpen } }, @@ -96,15 +96,16 @@ namespace osu.Game.Overlays.News.Sidebar } } - private class DropdownButton : OsuClickableContainer + private class DropdownHeader : OsuClickableContainer { public readonly BindableBool IsOpen = new BindableBool(); private readonly SpriteIcon icon; - public DropdownButton(DateTimeOffset date) + public DropdownHeader(DateTimeOffset date) { - Size = new Vector2(160, header_height); + RelativeSizeAxes = Axes.X; + Height = header_height; Action = IsOpen.Toggle; Children = new Drawable[] { From c2ba16f977ba8e3fb04d72f56bb81cd647def6d2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 16:51:59 +0300 Subject: [PATCH 33/65] Use relative sizing for MonthDropdown --- .../Online/TestSceneNewsMonthDropdown.cs | 55 ++++++++----------- .../Overlays/News/Sidebar/MonthDropdown.cs | 2 +- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 8 +-- 3 files changed, 27 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs index c51e299f78..f03b7d3f58 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs @@ -22,46 +22,35 @@ namespace osu.Game.Tests.Visual.Online [Test] public void CreateClosedMonthPanel() { - AddStep("Create", () => Child = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background2, - }, - new MonthDropdown(posts), - } - }); + create(false); } [Test] public void CreateOpenMonthPanel() { - AddStep("Create", () => Child = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background2, - }, - new MonthDropdown(posts) - { - IsOpen = { Value = true } - }, - } - }); + create(true); } + private void create(bool isOpen) => AddStep("Create", () => Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Y, + Width = 160, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background2, + }, + new MonthDropdown(posts) + { + IsOpen = { Value = isOpen } + } + } + }); + private static List posts => new List { new APINewsPost diff --git a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs index cc06f48544..11c0ce863f 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.News.Sidebar public MonthDropdown(IEnumerable posts) { - Width = 160; + RelativeSizeAxes = Axes.X; Masking = true; InternalChild = new FillFlowContainer { diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 1518d61d59..7de7e4dd71 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -42,17 +42,17 @@ namespace osu.Game.Overlays.News.Sidebar }, Child = new FillFlowContainer { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Spacing = new Vector2(0, 20), Children = new Drawable[] { new YearsPanel(), monthsFlow = new FillFlowContainer { - AutoSizeAxes = Axes.Both, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 10) } From b79a0237a3b57104df4dfee944ad1d2033af49ce Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 16:54:19 +0300 Subject: [PATCH 34/65] Fix TestSceneNewsYearsPanel error --- .../Visual/Online/TestSceneNewsYearsPanel.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs index 39bb97fe98..3199b83a7d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs @@ -22,24 +22,21 @@ namespace osu.Game.Tests.Visual.Online private YearsPanel panel; [SetUp] - public void SetUp() + public void SetUp() => Schedule(() => Child = panel = new YearsPanel { - Child = panel = new YearsPanel - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }; - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); [Test] public void TestVisibility() { AddStep("Change metadata to null", () => metadataBindable.Value = null); - AddAssert("Panel is hidden", () => !panel.IsPresent); + AddUntilStep("Panel is hidden", () => panel?.Alpha == 0); AddStep("Change metadata", () => metadataBindable.Value = metadata); - AddAssert("Panel is visible", () => panel.IsPresent); + AddUntilStep("Panel is visible", () => panel?.Alpha == 1); AddStep("Change metadata to null", () => metadataBindable.Value = null); - AddAssert("Panel is hidden", () => !panel.IsPresent); + AddUntilStep("Panel is hidden", () => panel?.Alpha == 0); } private static readonly APINewsSidebar metadata = new APINewsSidebar From 822d99e69f92f1ef07b04f242219210eaf0c9971 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 20:42:13 +0300 Subject: [PATCH 35/65] Remove pointless test scenes --- .../Online/TestSceneNewsMonthDropdown.cs | 71 ------------------- .../Visual/Online/TestSceneNewsSideBar.cs | 31 +++----- .../Visual/Online/TestSceneNewsYearsPanel.cs | 47 ------------ 3 files changed, 9 insertions(+), 140 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs delete mode 100644 osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs deleted file mode 100644 index f03b7d3f58..0000000000 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsMonthDropdown.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Overlays; -using osu.Game.Overlays.News.Sidebar; - -namespace osu.Game.Tests.Visual.Online -{ - public class TestSceneNewsMonthDropdown : OsuTestScene - { - [Cached] - private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - - [Test] - public void CreateClosedMonthPanel() - { - create(false); - } - - [Test] - public void CreateOpenMonthPanel() - { - create(true); - } - - private void create(bool isOpen) => AddStep("Create", () => Child = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Y, - Width = 160, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background2, - }, - new MonthDropdown(posts) - { - IsOpen = { Value = isOpen } - } - } - }); - - private static List posts => new List - { - new APINewsPost - { - Title = "Short title", - PublishedAt = DateTimeOffset.Now - }, - new APINewsPost - { - Title = "Oh boy that's a long post title I wonder if it will break anything" - }, - new APINewsPost - { - Title = "Medium title, nothing to see here" - } - }; - } -} diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs index 96161c28ed..8b09a3d176 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Testing; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.News.Sidebar; @@ -18,33 +20,18 @@ namespace osu.Game.Tests.Visual.Online private NewsSideBar sidebar; - [Test] - public void TestCreateEmpty() - { - createSidebar(null); - } + [SetUp] + public void SetUp() => Schedule(() => Child = sidebar = new NewsSideBar()); [Test] - public void TestCreateWithData() + public void TestMetadataChange() { - createSidebar(metadata); + AddUntilStep("Years panel is hidden", () => yearsPanel?.Alpha == 0); + AddStep("Add data", () => sidebar.Metadata.Value = metadata); + AddUntilStep("Years panel is visible", () => yearsPanel?.Alpha == 1); } - [Test] - public void TestDataChange() - { - createSidebar(null); - AddStep("Add data", () => - { - if (sidebar != null) - sidebar.Metadata.Value = metadata; - }); - } - - private void createSidebar(APINewsSidebar metadata) => AddStep("Create", () => Child = sidebar = new NewsSideBar - { - Metadata = { Value = metadata } - }); + private YearsPanel yearsPanel => sidebar.ChildrenOfType().FirstOrDefault(); private static readonly APINewsSidebar metadata = new APINewsSidebar { diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs deleted file mode 100644 index 3199b83a7d..0000000000 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsYearsPanel.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics; -using osu.Game.Overlays.News.Sidebar; -using osu.Framework.Allocation; -using osu.Game.Overlays; -using osu.Framework.Bindables; -using osu.Game.Online.API.Requests.Responses; -using NUnit.Framework; - -namespace osu.Game.Tests.Visual.Online -{ - public class TestSceneNewsYearsPanel : OsuTestScene - { - [Cached] - private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - - [Cached] - private readonly Bindable metadataBindable = new Bindable(); - - private YearsPanel panel; - - [SetUp] - public void SetUp() => Schedule(() => Child = panel = new YearsPanel - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }); - - [Test] - public void TestVisibility() - { - AddStep("Change metadata to null", () => metadataBindable.Value = null); - AddUntilStep("Panel is hidden", () => panel?.Alpha == 0); - AddStep("Change metadata", () => metadataBindable.Value = metadata); - AddUntilStep("Panel is visible", () => panel?.Alpha == 1); - AddStep("Change metadata to null", () => metadataBindable.Value = null); - AddUntilStep("Panel is hidden", () => panel?.Alpha == 0); - } - - private static readonly APINewsSidebar metadata = new APINewsSidebar - { - Years = new[] { 1001, 2001, 3001, 4001, 5001, 6001, 7001, 8001, 9001 } - }; - } -} From b0297c6324cff8807952194154271261f4155b4a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 20:52:11 +0300 Subject: [PATCH 36/65] Fix incorrect no posts handling and add corresponding test --- .../Visual/Online/TestSceneNewsSideBar.cs | 28 ++++++++++++++++++- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 2 +- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs index 8b09a3d176..b5405a979e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs @@ -24,13 +24,20 @@ namespace osu.Game.Tests.Visual.Online public void SetUp() => Schedule(() => Child = sidebar = new NewsSideBar()); [Test] - public void TestMetadataChange() + public void TestYearsPanelVisibility() { AddUntilStep("Years panel is hidden", () => yearsPanel?.Alpha == 0); AddStep("Add data", () => sidebar.Metadata.Value = metadata); AddUntilStep("Years panel is visible", () => yearsPanel?.Alpha == 1); } + [Test] + public void TestMetadataWithNoPosts() + { + AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts); + AddUntilStep("No dropdowns were created", () => !sidebar.ChildrenOfType().Any()); + } + private YearsPanel yearsPanel => sidebar.ChildrenOfType().FirstOrDefault(); private static readonly APINewsSidebar metadata = new APINewsSidebar @@ -97,5 +104,24 @@ namespace osu.Game.Tests.Visual.Online } } }; + + private static readonly APINewsSidebar metadata_with_no_posts = new APINewsSidebar + { + CurrentYear = 2022, + Years = new[] + { + 2022, + 2021, + 2020, + 2019, + 2018, + 2017, + 2016, + 2015, + 2014, + 2013 + }, + NewsPosts = Array.Empty() + }; } } diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 7de7e4dd71..baa1826185 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -78,7 +78,7 @@ namespace osu.Game.Overlays.News.Sidebar var allPosts = metadata.NewValue.NewsPosts; - if (!allPosts?.Any() ?? false) + if (!allPosts?.Any() ?? true) return; var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); From f4801c08ff9693c8e97a5252b96a38fe6f288760 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 22:34:01 +0300 Subject: [PATCH 37/65] Refactor MonthDropdown to ensure all the posts are within a given month --- osu.Game/Overlays/News/Sidebar/MonthDropdown.cs | 11 ++++++++--- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 7 +++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs index 11c0ce863f..91412d9527 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs @@ -14,6 +14,7 @@ using System.Linq; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; +using System.Diagnostics; namespace osu.Game.Overlays.News.Sidebar { @@ -26,8 +27,10 @@ namespace osu.Game.Overlays.News.Sidebar private readonly FillFlowContainer postsFlow; - public MonthDropdown(IEnumerable posts) + public MonthDropdown(int month, int year, IEnumerable posts) { + Debug.Assert(posts.All(p => p.PublishedAt.Month == month && p.PublishedAt.Year == year)); + RelativeSizeAxes = Axes.X; Masking = true; InternalChild = new FillFlowContainer @@ -38,7 +41,7 @@ namespace osu.Game.Overlays.News.Sidebar Spacing = new Vector2(0, 5), Children = new Drawable[] { - new DropdownHeader(posts.ElementAt(0).PublishedAt) + new DropdownHeader(month, year) { IsOpen = { BindTarget = IsOpen } }, @@ -102,8 +105,10 @@ namespace osu.Game.Overlays.News.Sidebar private readonly SpriteIcon icon; - public DropdownHeader(DateTimeOffset date) + public DropdownHeader(int month, int year) { + var date = new DateTime(year, month, 1); + RelativeSizeAxes = Axes.X; Height = header_height; Action = IsOpen.Toggle; diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index baa1826185..3851dea83a 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -86,11 +86,14 @@ namespace osu.Game.Overlays.News.Sidebar var keys = lookup.Select(kvp => kvp.Key); var sortedKeys = keys.OrderByDescending(k => k).ToList(); + var year = metadata.NewValue.CurrentYear; + for (int i = 0; i < sortedKeys.Count; i++) { - var posts = lookup[sortedKeys[i]]; + var month = sortedKeys[i]; + var posts = lookup[month]; - monthsFlow.Add(new MonthDropdown(posts) + monthsFlow.Add(new MonthDropdown(month, year, posts) { IsOpen = { Value = i == 0 } }); From 20a6903a40e1a59a253714780718192e1f195837 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 May 2021 23:43:01 +0300 Subject: [PATCH 38/65] Use GridContainer to distribute buttons in YearsPanel --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 93 +++++++++++++++++--- 1 file changed, 79 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index c06a8424f6..2a4e2024d2 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -5,14 +5,13 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osuTK; -using System.Linq; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using System.Collections.Generic; using osu.Game.Graphics; using osu.Framework.Bindables; using osu.Game.Online.API.Requests.Responses; +using System; namespace osu.Game.Overlays.News.Sidebar { @@ -20,15 +19,15 @@ namespace osu.Game.Overlays.News.Sidebar { private readonly Bindable metadata = new Bindable(); - private FillFlowContainer flow; + private Container gridPlaceholder; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, Bindable metadata) { this.metadata.BindTo(metadata); - Width = 160; AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; Masking = true; CornerRadius = 6; InternalChildren = new Drawable[] @@ -38,17 +37,11 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background3 }, - new Container + gridPlaceholder = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(5), - Child = flow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(5) - } + Padding = new MarginPadding(5) } }; } @@ -65,7 +58,7 @@ namespace osu.Game.Overlays.News.Sidebar return; } - flow.Children = m.NewValue.Years.Select(y => new YearButton(y)).ToArray(); + gridPlaceholder.Child = new YearsGridContainer(m.NewValue.Years); Show(); }, true); } @@ -78,7 +71,8 @@ namespace osu.Game.Overlays.News.Sidebar public YearButton(int year) { - Size = new Vector2(33.75f, 15); + RelativeSizeAxes = Axes.X; + Height = 15; Child = text = new OsuSpriteText { Anchor = Anchor.Centre, @@ -96,5 +90,76 @@ namespace osu.Game.Overlays.News.Sidebar Action = () => { }; // Avoid button being disabled since there's no proper action assigned. } } + + private class YearsGridContainer : GridContainer + { + private const int column_count = 4; + private const float spacing = 5f; + + private readonly int rowCount; + private readonly int[] years; + + public YearsGridContainer(int[] years) + { + this.years = years; + rowCount = (int)Math.Ceiling((float)years.Length / column_count); + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + RowDimensions = getRowDimensions(); + ColumnDimensions = getColumnDimensions(); + Content = createContent(); + } + + private Dimension[] getRowDimensions() + { + var rowDimensions = new Dimension[rowCount]; + for (int i = 0; i < rowCount; i++) + rowDimensions[i] = new Dimension(GridSizeMode.AutoSize); + + return rowDimensions; + } + + private Dimension[] getColumnDimensions() + { + var columnDimensions = new Dimension[column_count]; + for (int i = 0; i < column_count; i++) + columnDimensions[i] = new Dimension(GridSizeMode.Relative, size: 1f / column_count); + + return columnDimensions; + } + + private Drawable[][] createContent() + { + var buttons = new Drawable[rowCount][]; + + for (int i = 0; i < rowCount; i++) + { + buttons[i] = new Drawable[column_count]; + + for (int j = 0; j < column_count; j++) + { + var index = i * column_count + j; + buttons[i][j] = index >= years.Length + ? Empty() + : new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Top = i == 0 ? 0 : spacing / 2, + Bottom = i == rowCount - 1 ? 0 : spacing / 2, + Left = j == 0 ? 0 : spacing / 2, + Right = j == column_count - 1 ? 0 : spacing / 2 + }, + Child = new YearButton(years[index]) + }; + } + } + + return buttons; + } + } } } From 315a2b8314a044689eb607dfdced7bc24fc1f4c9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 12 May 2021 20:50:20 +0300 Subject: [PATCH 39/65] Refactor MonthDropdown to decouple autosized logic --- .../Overlays/News/Sidebar/MonthDropdown.cs | 122 ++++++++++-------- 1 file changed, 69 insertions(+), 53 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs index 91412d9527..d6d7527a6c 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs @@ -20,85 +20,37 @@ namespace osu.Game.Overlays.News.Sidebar { public class MonthDropdown : CompositeDrawable { - private const int header_height = 15; private const int animation_duration = 250; public readonly BindableBool IsOpen = new BindableBool(); - private readonly FillFlowContainer postsFlow; - public MonthDropdown(int month, int year, IEnumerable posts) { Debug.Assert(posts.All(p => p.PublishedAt.Month == month && p.PublishedAt.Year == year)); RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; Masking = true; InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), Children = new Drawable[] { new DropdownHeader(month, year) { IsOpen = { BindTarget = IsOpen } }, - postsFlow = new FillFlowContainer + new PostsContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), + IsOpen = { BindTarget = IsOpen }, Children = posts.Select(p => new PostButton(p)).ToArray() } } }; } - protected override void LoadComplete() - { - base.LoadComplete(); - - IsOpen.BindValueChanged(open => - { - ClearTransforms(); - - if (open.NewValue) - { - AutoSizeAxes = Axes.Y; - postsFlow.FadeIn(animation_duration, Easing.OutQuint); - } - else - { - AutoSizeAxes = Axes.None; - this.ResizeHeightTo(header_height, animation_duration, Easing.OutQuint); - - postsFlow.FadeOut(animation_duration, Easing.OutQuint); - } - }, true); - - // First state change should be instant. - FinishTransforms(true); - } - - private bool shouldUpdateAutosize = true; - - // Workaround to allow the dropdown to be opened immediately since FinishTransforms doesn't work for AutosizeDuration. - protected override void UpdateAfterAutoSize() - { - base.UpdateAfterAutoSize(); - - if (shouldUpdateAutosize) - { - AutoSizeDuration = animation_duration; - AutoSizeEasing = Easing.OutQuint; - - shouldUpdateAutosize = false; - } - } - private class DropdownHeader : OsuClickableContainer { public readonly BindableBool IsOpen = new BindableBool(); @@ -110,7 +62,7 @@ namespace osu.Game.Overlays.News.Sidebar var date = new DateTime(year, month, 1); RelativeSizeAxes = Axes.X; - Height = header_height; + Height = 15; Action = IsOpen.Toggle; Children = new Drawable[] { @@ -152,7 +104,6 @@ namespace osu.Game.Overlays.News.Sidebar { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Child = text = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12)) { RelativeSizeAxes = Axes.X, @@ -169,5 +120,70 @@ namespace osu.Game.Overlays.News.Sidebar Action = () => { }; // Avoid button being disabled since there's no proper action assigned. } } + + private class PostsContainer : Container + { + public readonly BindableBool IsOpen = new BindableBool(); + + protected override Container Content => content; + + private readonly FillFlowContainer content; + + public PostsContainer() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + InternalChild = content = new FillFlowContainer + { + Margin = new MarginPadding { Top = 5 }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5) + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + IsOpen.BindValueChanged(open => + { + ClearTransforms(); + + if (open.NewValue) + { + AutoSizeAxes = Axes.Y; + content.FadeIn(animation_duration, Easing.OutQuint); + } + else + { + AutoSizeAxes = Axes.None; + this.ResizeHeightTo(0, animation_duration, Easing.OutQuint); + + content.FadeOut(animation_duration, Easing.OutQuint); + } + }, true); + + // First state change should be instant. + FinishTransforms(true); + } + + private bool shouldUpdateAutosize = true; + + // Workaround to allow the dropdown to be opened immediately since FinishTransforms doesn't work for AutosizeDuration. + protected override void UpdateAfterAutoSize() + { + base.UpdateAfterAutoSize(); + + if (shouldUpdateAutosize) + { + AutoSizeDuration = animation_duration; + AutoSizeEasing = Easing.OutQuint; + + shouldUpdateAutosize = false; + } + } + } } } From 38c0ba2d1034b756f5d6e8b1f007afee44c9e648 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 13 May 2021 16:16:19 +0300 Subject: [PATCH 40/65] Implement current year highlight in YearsPanel --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 39 +++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 2a4e2024d2..2d405e832b 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics; using osu.Framework.Bindables; using osu.Game.Online.API.Requests.Responses; using System; +using osuTK.Graphics; namespace osu.Game.Overlays.News.Sidebar { @@ -58,7 +59,7 @@ namespace osu.Game.Overlays.News.Sidebar return; } - gridPlaceholder.Child = new YearsGridContainer(m.NewValue.Years); + gridPlaceholder.Child = new YearsGridContainer(m.NewValue.Years, m.NewValue.CurrentYear); Show(); }, true); } @@ -68,16 +69,19 @@ namespace osu.Game.Overlays.News.Sidebar protected override IEnumerable EffectTargets => new[] { text }; private readonly OsuSpriteText text; + private readonly bool isCurrent; - public YearButton(int year) + public YearButton(int year, bool isCurrent) { + this.isCurrent = isCurrent; + RelativeSizeAxes = Axes.X; Height = 15; Child = text = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 12), + Font = OsuFont.GetFont(size: 12, weight: isCurrent ? FontWeight.SemiBold : FontWeight.Medium), Text = year.ToString() }; } @@ -85,8 +89,8 @@ namespace osu.Game.Overlays.News.Sidebar [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - IdleColour = colourProvider.Light2; - HoverColour = colourProvider.Light1; + IdleColour = isCurrent ? Color4.White : colourProvider.Light2; + HoverColour = isCurrent ? Color4.White : colourProvider.Light1; Action = () => { }; // Avoid button being disabled since there's no proper action assigned. } } @@ -97,18 +101,16 @@ namespace osu.Game.Overlays.News.Sidebar private const float spacing = 5f; private readonly int rowCount; - private readonly int[] years; - public YearsGridContainer(int[] years) + public YearsGridContainer(int[] years, int currentYear) { - this.years = years; rowCount = (int)Math.Ceiling((float)years.Length / column_count); RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; RowDimensions = getRowDimensions(); ColumnDimensions = getColumnDimensions(); - Content = createContent(); + Content = createContent(years, currentYear); } private Dimension[] getRowDimensions() @@ -129,7 +131,7 @@ namespace osu.Game.Overlays.News.Sidebar return columnDimensions; } - private Drawable[][] createContent() + private Drawable[][] createContent(int[] years, int currentYear) { var buttons = new Drawable[rowCount][]; @@ -140,9 +142,17 @@ namespace osu.Game.Overlays.News.Sidebar for (int j = 0; j < column_count; j++) { var index = i * column_count + j; - buttons[i][j] = index >= years.Length - ? Empty() - : new Container + + if (index >= years.Length) + { + buttons[i][j] = Empty(); + } + else + { + var year = years[index]; + var isCurrent = year == currentYear; + + buttons[i][j] = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -153,8 +163,9 @@ namespace osu.Game.Overlays.News.Sidebar Left = j == 0 ? 0 : spacing / 2, Right = j == column_count - 1 ? 0 : spacing / 2 }, - Child = new YearButton(years[index]) + Child = new YearButton(year, isCurrent) }; + } } } From e479db91864dd9a097aa90407940fd9b3639004b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 15 May 2021 19:14:02 +0300 Subject: [PATCH 41/65] Clear transforms in PostsContainer for all children --- osu.Game/Overlays/News/Sidebar/MonthDropdown.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs index d6d7527a6c..85a06c8227 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs @@ -149,7 +149,7 @@ namespace osu.Game.Overlays.News.Sidebar IsOpen.BindValueChanged(open => { - ClearTransforms(); + ClearTransforms(true); if (open.NewValue) { From 3e1b1c6c3ebb539c8bc9d8cbb3b5c709aab2b0fc Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 15 May 2021 19:14:58 +0300 Subject: [PATCH 42/65] Improve statement readability --- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 3851dea83a..837c661cbd 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -78,7 +78,7 @@ namespace osu.Game.Overlays.News.Sidebar var allPosts = metadata.NewValue.NewsPosts; - if (!allPosts?.Any() ?? true) + if (allPosts?.Any() != true) return; var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); From 50e2b5a32761768102975a76511d099bc51180d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 16:00:36 +0900 Subject: [PATCH 43/65] SideBar -> Sidebar --- osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs | 6 +++--- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs index b5405a979e..e376d9b1ed 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs @@ -13,15 +13,15 @@ using osu.Game.Overlays.News.Sidebar; namespace osu.Game.Tests.Visual.Online { - public class TestSceneNewsSideBar : OsuTestScene + public class TestSceneNewsSidebar : OsuTestScene { [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - private NewsSideBar sidebar; + private NewsSidebar sidebar; [SetUp] - public void SetUp() => Schedule(() => Child = sidebar = new NewsSideBar()); + public void SetUp() => Schedule(() => Child = sidebar = new NewsSidebar()); [Test] public void TestYearsPanelVisibility() diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index 837c661cbd..bad334cb2f 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -12,7 +12,7 @@ using System.Linq; namespace osu.Game.Overlays.News.Sidebar { - public class NewsSideBar : CompositeDrawable + public class NewsSidebar : CompositeDrawable { [Cached] public readonly Bindable Metadata = new Bindable(); From 22561cda1956e51f3747952f19fb1f59c9ebc9db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 16:02:21 +0900 Subject: [PATCH 44/65] MonthDropdown -> MonthSection --- osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs | 2 +- .../News/Sidebar/{MonthDropdown.cs => MonthSection.cs} | 4 ++-- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game/Overlays/News/Sidebar/{MonthDropdown.cs => MonthSection.cs} (97%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs index e376d9b1ed..5e8cd397bc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Online public void TestMetadataWithNoPosts() { AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts); - AddUntilStep("No dropdowns were created", () => !sidebar.ChildrenOfType().Any()); + AddUntilStep("No dropdowns were created", () => !sidebar.ChildrenOfType().Any()); } private YearsPanel yearsPanel => sidebar.ChildrenOfType().FirstOrDefault(); diff --git a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs similarity index 97% rename from osu.Game/Overlays/News/Sidebar/MonthDropdown.cs rename to osu.Game/Overlays/News/Sidebar/MonthSection.cs index 85a06c8227..80c408bda5 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthDropdown.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -18,13 +18,13 @@ using System.Diagnostics; namespace osu.Game.Overlays.News.Sidebar { - public class MonthDropdown : CompositeDrawable + public class MonthSection : CompositeDrawable { private const int animation_duration = 250; public readonly BindableBool IsOpen = new BindableBool(); - public MonthDropdown(int month, int year, IEnumerable posts) + public MonthSection(int month, int year, IEnumerable posts) { Debug.Assert(posts.All(p => p.PublishedAt.Month == month && p.PublishedAt.Year == year)); diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs index bad334cb2f..b8d283b7e2 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.News.Sidebar [Cached] public readonly Bindable Metadata = new Bindable(); - private FillFlowContainer monthsFlow; + private FillFlowContainer monthsFlow; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.News.Sidebar Children = new Drawable[] { new YearsPanel(), - monthsFlow = new FillFlowContainer + monthsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, @@ -93,7 +93,7 @@ namespace osu.Game.Overlays.News.Sidebar var month = sortedKeys[i]; var posts = lookup[month]; - monthsFlow.Add(new MonthDropdown(month, year, posts) + monthsFlow.Add(new MonthSection(month, year, posts) { IsOpen = { Value = i == 0 } }); From 032f60819d0836fdd24a6fe715b08a3835e454c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 16:11:46 +0900 Subject: [PATCH 45/65] Rename content container --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 2d405e832b..932494f740 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.News.Sidebar { private readonly Bindable metadata = new Bindable(); - private Container gridPlaceholder; + private Container gridContent; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, Bindable metadata) @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background3 }, - gridPlaceholder = new Container + gridContent = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.News.Sidebar return; } - gridPlaceholder.Child = new YearsGridContainer(m.NewValue.Years, m.NewValue.CurrentYear); + gridContent.Child = new YearsGridContainer(m.NewValue.Years, m.NewValue.CurrentYear); Show(); }, true); } @@ -77,6 +77,7 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.X; Height = 15; + Child = text = new OsuSpriteText { Anchor = Anchor.Centre, From ae1e62288d57d1477155f439f4d2b094edd68447 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 16:16:50 +0900 Subject: [PATCH 46/65] Reorder tests to not have the first test show nothing --- .../Visual/Online/TestSceneNewsSideBar.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs index 5e8cd397bc..706de2b310 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs @@ -23,6 +23,13 @@ namespace osu.Game.Tests.Visual.Online [SetUp] public void SetUp() => Schedule(() => Child = sidebar = new NewsSidebar()); + [Test] + public void TestMetadataWithNoPosts() + { + AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts); + AddUntilStep("No dropdowns were created", () => !sidebar.ChildrenOfType().Any()); + } + [Test] public void TestYearsPanelVisibility() { @@ -31,13 +38,6 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("Years panel is visible", () => yearsPanel?.Alpha == 1); } - [Test] - public void TestMetadataWithNoPosts() - { - AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts); - AddUntilStep("No dropdowns were created", () => !sidebar.ChildrenOfType().Any()); - } - private YearsPanel yearsPanel => sidebar.ChildrenOfType().FirstOrDefault(); private static readonly APINewsSidebar metadata = new APINewsSidebar From abeeda5d04ddb2922629fba6fea2812ca5335c27 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 16:24:43 +0900 Subject: [PATCH 47/65] Rewrite `YearsPanel` to not be insanely long --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 49 ++++---------------- 1 file changed, 9 insertions(+), 40 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 932494f740..4573ae530f 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -1,17 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using System.Collections.Generic; -using osu.Game.Graphics; -using osu.Framework.Bindables; using osu.Game.Online.API.Requests.Responses; -using System; using osuTK.Graphics; namespace osu.Game.Overlays.News.Sidebar @@ -77,6 +77,7 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.X; Height = 15; + Padding = new MarginPadding { Vertical = 2.5f }; Child = text = new OsuSpriteText { @@ -99,39 +100,19 @@ namespace osu.Game.Overlays.News.Sidebar private class YearsGridContainer : GridContainer { private const int column_count = 4; - private const float spacing = 5f; private readonly int rowCount; public YearsGridContainer(int[] years, int currentYear) { - rowCount = (int)Math.Ceiling((float)years.Length / column_count); + rowCount = (years.Length + column_count - 1) / column_count; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - RowDimensions = getRowDimensions(); - ColumnDimensions = getColumnDimensions(); + RowDimensions = Enumerable.Range(0, rowCount).Select(_ => new Dimension(GridSizeMode.AutoSize)).ToArray(); Content = createContent(years, currentYear); } - private Dimension[] getRowDimensions() - { - var rowDimensions = new Dimension[rowCount]; - for (int i = 0; i < rowCount; i++) - rowDimensions[i] = new Dimension(GridSizeMode.AutoSize); - - return rowDimensions; - } - - private Dimension[] getColumnDimensions() - { - var columnDimensions = new Dimension[column_count]; - for (int i = 0; i < column_count; i++) - columnDimensions[i] = new Dimension(GridSizeMode.Relative, size: 1f / column_count); - - return columnDimensions; - } - private Drawable[][] createContent(int[] years, int currentYear) { var buttons = new Drawable[rowCount][]; @@ -153,19 +134,7 @@ namespace osu.Game.Overlays.News.Sidebar var year = years[index]; var isCurrent = year == currentYear; - buttons[i][j] = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding - { - Top = i == 0 ? 0 : spacing / 2, - Bottom = i == rowCount - 1 ? 0 : spacing / 2, - Left = j == 0 ? 0 : spacing / 2, - Right = j == column_count - 1 ? 0 : spacing / 2 - }, - Child = new YearButton(year, isCurrent) - }; + buttons[i][j] = new YearButton(year, isCurrent); } } } From e754d2e59028940ff1a2e8b06cf66e88d1187299 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 17 May 2021 10:54:45 +0300 Subject: [PATCH 48/65] Simplify YearButton --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 4573ae530f..2528d51331 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -66,9 +65,6 @@ namespace osu.Game.Overlays.News.Sidebar private class YearButton : OsuHoverContainer { - protected override IEnumerable EffectTargets => new[] { text }; - - private readonly OsuSpriteText text; private readonly bool isCurrent; public YearButton(int year, bool isCurrent) @@ -79,7 +75,7 @@ namespace osu.Game.Overlays.News.Sidebar Height = 15; Padding = new MarginPadding { Vertical = 2.5f }; - Child = text = new OsuSpriteText + Child = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 5059bfaef99e987293d6b3f3d3f5b7629dcab9d9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 17 May 2021 11:17:02 +0300 Subject: [PATCH 49/65] Use FillFlowContainer in YearsPanel --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 70 +++++--------------- 1 file changed, 18 insertions(+), 52 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 2528d51331..331a7e10e1 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -11,6 +10,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; +using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.News.Sidebar @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.News.Sidebar { private readonly Bindable metadata = new Bindable(); - private Container gridContent; + private FillFlowContainer yearsFlow; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, Bindable metadata) @@ -37,11 +37,17 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background3 }, - gridContent = new Container + new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(5) + Padding = new MarginPadding(5), + Child = yearsFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 5) + } } }; } @@ -52,13 +58,19 @@ namespace osu.Game.Overlays.News.Sidebar metadata.BindValueChanged(m => { + yearsFlow.Clear(); + if (m.NewValue == null) { Hide(); return; } - gridContent.Child = new YearsGridContainer(m.NewValue.Years, m.NewValue.CurrentYear); + var currentYear = m.NewValue.CurrentYear; + + foreach (var y in m.NewValue.Years) + yearsFlow.Add(new YearButton(y, y == currentYear)); + Show(); }, true); } @@ -72,8 +84,8 @@ namespace osu.Game.Overlays.News.Sidebar this.isCurrent = isCurrent; RelativeSizeAxes = Axes.X; + Width = 0.25f; Height = 15; - Padding = new MarginPadding { Vertical = 2.5f }; Child = new OsuSpriteText { @@ -92,51 +104,5 @@ namespace osu.Game.Overlays.News.Sidebar Action = () => { }; // Avoid button being disabled since there's no proper action assigned. } } - - private class YearsGridContainer : GridContainer - { - private const int column_count = 4; - - private readonly int rowCount; - - public YearsGridContainer(int[] years, int currentYear) - { - rowCount = (years.Length + column_count - 1) / column_count; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - RowDimensions = Enumerable.Range(0, rowCount).Select(_ => new Dimension(GridSizeMode.AutoSize)).ToArray(); - Content = createContent(years, currentYear); - } - - private Drawable[][] createContent(int[] years, int currentYear) - { - var buttons = new Drawable[rowCount][]; - - for (int i = 0; i < rowCount; i++) - { - buttons[i] = new Drawable[column_count]; - - for (int j = 0; j < column_count; j++) - { - var index = i * column_count + j; - - if (index >= years.Length) - { - buttons[i][j] = Empty(); - } - else - { - var year = years[index]; - var isCurrent = year == currentYear; - - buttons[i][j] = new YearButton(year, isCurrent); - } - } - } - - return buttons; - } - } } } From c0cfbd11ddabc1c4544b3b54872e4e4c3458ce14 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 17 May 2021 11:20:31 +0300 Subject: [PATCH 50/65] Add tooltip and action for PostButton in MonthSection --- osu.Game/Overlays/News/Sidebar/MonthSection.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index 80c408bda5..20c4d2e83e 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using System.Diagnostics; +using osu.Framework.Platform; namespace osu.Game.Overlays.News.Sidebar { @@ -99,9 +100,12 @@ namespace osu.Game.Overlays.News.Sidebar protected override IEnumerable EffectTargets => new[] { text }; private readonly TextFlowContainer text; + private readonly APINewsPost post; public PostButton(APINewsPost post) { + this.post = post; + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Child = text = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12)) @@ -113,11 +117,13 @@ namespace osu.Game.Overlays.News.Sidebar } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, GameHost host) { IdleColour = colourProvider.Light2; HoverColour = colourProvider.Light1; - Action = () => { }; // Avoid button being disabled since there's no proper action assigned. + + TooltipText = "view in browser"; + Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug); } } From 586c5c7365b3d0d0291447fb57b316b1e01b098e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 17 May 2021 11:36:53 +0300 Subject: [PATCH 51/65] Emulate year changes in the test scene --- .../Visual/Online/TestSceneNewsSideBar.cs | 47 +++++++++++++------ osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 5 +- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs index 706de2b310..376c270689 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs @@ -10,6 +10,7 @@ using osu.Framework.Testing; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.News.Sidebar; +using static osu.Game.Overlays.News.Sidebar.YearsPanel; namespace osu.Game.Tests.Visual.Online { @@ -18,10 +19,10 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - private NewsSidebar sidebar; + private TestNewsSidebar sidebar; [SetUp] - public void SetUp() => Schedule(() => Child = sidebar = new NewsSidebar()); + public void SetUp() => Schedule(() => Child = sidebar = new TestNewsSidebar { YearChanged = onYearChanged }); [Test] public void TestMetadataWithNoPosts() @@ -34,15 +35,17 @@ namespace osu.Game.Tests.Visual.Online public void TestYearsPanelVisibility() { AddUntilStep("Years panel is hidden", () => yearsPanel?.Alpha == 0); - AddStep("Add data", () => sidebar.Metadata.Value = metadata); + AddStep("Add data", () => sidebar.Metadata.Value = getMetadata(2021)); AddUntilStep("Years panel is visible", () => yearsPanel?.Alpha == 1); } + private void onYearChanged(int year) => sidebar.Metadata.Value = getMetadata(year); + private YearsPanel yearsPanel => sidebar.ChildrenOfType().FirstOrDefault(); - private static readonly APINewsSidebar metadata = new APINewsSidebar + private APINewsSidebar getMetadata(int year) => new APINewsSidebar { - CurrentYear = 2021, + CurrentYear = year, Years = new[] { 2021, @@ -60,47 +63,47 @@ namespace osu.Game.Tests.Visual.Online new APINewsPost { Title = "(Mar) Short title", - PublishedAt = new DateTime(2021, 3, 1) + PublishedAt = new DateTime(year, 3, 1) }, new APINewsPost { Title = "(Mar) Oh boy that's a long post title I wonder if it will break anything", - PublishedAt = new DateTime(2021, 3, 1) + PublishedAt = new DateTime(year, 3, 1) }, new APINewsPost { Title = "(Mar) Medium title, nothing to see here", - PublishedAt = new DateTime(2021, 3, 1) + PublishedAt = new DateTime(year, 3, 1) }, new APINewsPost { Title = "(Feb) Short title", - PublishedAt = new DateTime(2021, 2, 1) + PublishedAt = new DateTime(year, 2, 1) }, new APINewsPost { Title = "(Feb) Oh boy that's a long post title I wonder if it will break anything", - PublishedAt = new DateTime(2021, 2, 1) + PublishedAt = new DateTime(year, 2, 1) }, new APINewsPost { Title = "(Feb) Medium title, nothing to see here", - PublishedAt = new DateTime(2021, 2, 1) + PublishedAt = new DateTime(year, 2, 1) }, new APINewsPost { Title = "Short title", - PublishedAt = new DateTime(2021, 1, 1) + PublishedAt = new DateTime(year, 1, 1) }, new APINewsPost { Title = "Oh boy that's a long post title I wonder if it will break anything", - PublishedAt = new DateTime(2021, 1, 1) + PublishedAt = new DateTime(year, 1, 1) }, new APINewsPost { Title = "Medium title, nothing to see here", - PublishedAt = new DateTime(2021, 1, 1) + PublishedAt = new DateTime(year, 1, 1) } } }; @@ -123,5 +126,21 @@ namespace osu.Game.Tests.Visual.Online }, NewsPosts = Array.Empty() }; + + private class TestNewsSidebar : NewsSidebar + { + public Action YearChanged; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Metadata.BindValueChanged(m => + { + foreach (var b in this.ChildrenOfType()) + b.Action = () => YearChanged?.Invoke(b.Year); + }, true); + } + } } } diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 331a7e10e1..ffdb5cf22e 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -75,12 +75,15 @@ namespace osu.Game.Overlays.News.Sidebar }, true); } - private class YearButton : OsuHoverContainer + public class YearButton : OsuHoverContainer { + public int Year { get; } + private readonly bool isCurrent; public YearButton(int year, bool isCurrent) { + Year = year; this.isCurrent = isCurrent; RelativeSizeAxes = Axes.X; From 01090de1fd45bfd0d1a1c1b743847d089b521e93 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 17 May 2021 11:55:55 +0300 Subject: [PATCH 52/65] Fix filenames does not match contained type --- .../Visual/Online/TestSceneNewsSidebar.cs | 146 ++++++++++++++++++ osu.Game/Overlays/News/Sidebar/NewsSidebar.cs | 103 ++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs create mode 100644 osu.Game/Overlays/News/Sidebar/NewsSidebar.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs new file mode 100644 index 0000000000..376c270689 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs @@ -0,0 +1,146 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Testing; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.News.Sidebar; +using static osu.Game.Overlays.News.Sidebar.YearsPanel; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneNewsSidebar : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + private TestNewsSidebar sidebar; + + [SetUp] + public void SetUp() => Schedule(() => Child = sidebar = new TestNewsSidebar { YearChanged = onYearChanged }); + + [Test] + public void TestMetadataWithNoPosts() + { + AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts); + AddUntilStep("No dropdowns were created", () => !sidebar.ChildrenOfType().Any()); + } + + [Test] + public void TestYearsPanelVisibility() + { + AddUntilStep("Years panel is hidden", () => yearsPanel?.Alpha == 0); + AddStep("Add data", () => sidebar.Metadata.Value = getMetadata(2021)); + AddUntilStep("Years panel is visible", () => yearsPanel?.Alpha == 1); + } + + private void onYearChanged(int year) => sidebar.Metadata.Value = getMetadata(year); + + private YearsPanel yearsPanel => sidebar.ChildrenOfType().FirstOrDefault(); + + private APINewsSidebar getMetadata(int year) => new APINewsSidebar + { + CurrentYear = year, + Years = new[] + { + 2021, + 2020, + 2019, + 2018, + 2017, + 2016, + 2015, + 2014, + 2013 + }, + NewsPosts = new List + { + new APINewsPost + { + Title = "(Mar) Short title", + PublishedAt = new DateTime(year, 3, 1) + }, + new APINewsPost + { + Title = "(Mar) Oh boy that's a long post title I wonder if it will break anything", + PublishedAt = new DateTime(year, 3, 1) + }, + new APINewsPost + { + Title = "(Mar) Medium title, nothing to see here", + PublishedAt = new DateTime(year, 3, 1) + }, + new APINewsPost + { + Title = "(Feb) Short title", + PublishedAt = new DateTime(year, 2, 1) + }, + new APINewsPost + { + Title = "(Feb) Oh boy that's a long post title I wonder if it will break anything", + PublishedAt = new DateTime(year, 2, 1) + }, + new APINewsPost + { + Title = "(Feb) Medium title, nothing to see here", + PublishedAt = new DateTime(year, 2, 1) + }, + new APINewsPost + { + Title = "Short title", + PublishedAt = new DateTime(year, 1, 1) + }, + new APINewsPost + { + Title = "Oh boy that's a long post title I wonder if it will break anything", + PublishedAt = new DateTime(year, 1, 1) + }, + new APINewsPost + { + Title = "Medium title, nothing to see here", + PublishedAt = new DateTime(year, 1, 1) + } + } + }; + + private static readonly APINewsSidebar metadata_with_no_posts = new APINewsSidebar + { + CurrentYear = 2022, + Years = new[] + { + 2022, + 2021, + 2020, + 2019, + 2018, + 2017, + 2016, + 2015, + 2014, + 2013 + }, + NewsPosts = Array.Empty() + }; + + private class TestNewsSidebar : NewsSidebar + { + public Action YearChanged; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Metadata.BindValueChanged(m => + { + foreach (var b in this.ChildrenOfType()) + b.Action = () => YearChanged?.Invoke(b.Year); + }, true); + } + } + } +} diff --git a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs new file mode 100644 index 0000000000..b8d283b7e2 --- /dev/null +++ b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs @@ -0,0 +1,103 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Graphics.Shapes; +using osuTK; +using System.Linq; + +namespace osu.Game.Overlays.News.Sidebar +{ + public class NewsSidebar : CompositeDrawable + { + [Cached] + public readonly Bindable Metadata = new Bindable(); + + private FillFlowContainer monthsFlow; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + RelativeSizeAxes = Axes.Y; + Width = 250; + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4 + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Vertical = 20, + Left = 50, + Right = 30 + }, + Child = new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + new YearsPanel(), + monthsFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10) + } + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Metadata.BindValueChanged(onMetadataChanged, true); + } + + private void onMetadataChanged(ValueChangedEvent metadata) + { + monthsFlow.Clear(); + + if (metadata.NewValue == null) + return; + + var allPosts = metadata.NewValue.NewsPosts; + + if (allPosts?.Any() != true) + return; + + var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); + + var keys = lookup.Select(kvp => kvp.Key); + var sortedKeys = keys.OrderByDescending(k => k).ToList(); + + var year = metadata.NewValue.CurrentYear; + + for (int i = 0; i < sortedKeys.Count; i++) + { + var month = sortedKeys[i]; + var posts = lookup[month]; + + monthsFlow.Add(new MonthSection(month, year, posts) + { + IsOpen = { Value = i == 0 } + }); + } + } + } +} From fc6e65b7dbaf9826f3a8a2832fade1a8521fb645 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 17 May 2021 12:02:06 +0300 Subject: [PATCH 53/65] Delete TestSceneNewsSideBar.cs --- .../Visual/Online/TestSceneNewsSideBar.cs | 146 ------------------ 1 file changed, 146 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs deleted file mode 100644 index 376c270689..0000000000 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSideBar.cs +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Testing; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Overlays; -using osu.Game.Overlays.News.Sidebar; -using static osu.Game.Overlays.News.Sidebar.YearsPanel; - -namespace osu.Game.Tests.Visual.Online -{ - public class TestSceneNewsSidebar : OsuTestScene - { - [Cached] - private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - - private TestNewsSidebar sidebar; - - [SetUp] - public void SetUp() => Schedule(() => Child = sidebar = new TestNewsSidebar { YearChanged = onYearChanged }); - - [Test] - public void TestMetadataWithNoPosts() - { - AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts); - AddUntilStep("No dropdowns were created", () => !sidebar.ChildrenOfType().Any()); - } - - [Test] - public void TestYearsPanelVisibility() - { - AddUntilStep("Years panel is hidden", () => yearsPanel?.Alpha == 0); - AddStep("Add data", () => sidebar.Metadata.Value = getMetadata(2021)); - AddUntilStep("Years panel is visible", () => yearsPanel?.Alpha == 1); - } - - private void onYearChanged(int year) => sidebar.Metadata.Value = getMetadata(year); - - private YearsPanel yearsPanel => sidebar.ChildrenOfType().FirstOrDefault(); - - private APINewsSidebar getMetadata(int year) => new APINewsSidebar - { - CurrentYear = year, - Years = new[] - { - 2021, - 2020, - 2019, - 2018, - 2017, - 2016, - 2015, - 2014, - 2013 - }, - NewsPosts = new List - { - new APINewsPost - { - Title = "(Mar) Short title", - PublishedAt = new DateTime(year, 3, 1) - }, - new APINewsPost - { - Title = "(Mar) Oh boy that's a long post title I wonder if it will break anything", - PublishedAt = new DateTime(year, 3, 1) - }, - new APINewsPost - { - Title = "(Mar) Medium title, nothing to see here", - PublishedAt = new DateTime(year, 3, 1) - }, - new APINewsPost - { - Title = "(Feb) Short title", - PublishedAt = new DateTime(year, 2, 1) - }, - new APINewsPost - { - Title = "(Feb) Oh boy that's a long post title I wonder if it will break anything", - PublishedAt = new DateTime(year, 2, 1) - }, - new APINewsPost - { - Title = "(Feb) Medium title, nothing to see here", - PublishedAt = new DateTime(year, 2, 1) - }, - new APINewsPost - { - Title = "Short title", - PublishedAt = new DateTime(year, 1, 1) - }, - new APINewsPost - { - Title = "Oh boy that's a long post title I wonder if it will break anything", - PublishedAt = new DateTime(year, 1, 1) - }, - new APINewsPost - { - Title = "Medium title, nothing to see here", - PublishedAt = new DateTime(year, 1, 1) - } - } - }; - - private static readonly APINewsSidebar metadata_with_no_posts = new APINewsSidebar - { - CurrentYear = 2022, - Years = new[] - { - 2022, - 2021, - 2020, - 2019, - 2018, - 2017, - 2016, - 2015, - 2014, - 2013 - }, - NewsPosts = Array.Empty() - }; - - private class TestNewsSidebar : NewsSidebar - { - public Action YearChanged; - - protected override void LoadComplete() - { - base.LoadComplete(); - - Metadata.BindValueChanged(m => - { - foreach (var b in this.ChildrenOfType()) - b.Action = () => YearChanged?.Invoke(b.Year); - }, true); - } - } - } -} From 555e3e2db30ac722b57a893799e7b8e63ab54550 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 17 May 2021 12:02:33 +0300 Subject: [PATCH 54/65] Delete NewsSideBar.cs --- osu.Game/Overlays/News/Sidebar/NewsSideBar.cs | 103 ------------------ 1 file changed, 103 deletions(-) delete mode 100644 osu.Game/Overlays/News/Sidebar/NewsSideBar.cs diff --git a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs b/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs deleted file mode 100644 index b8d283b7e2..0000000000 --- a/osu.Game/Overlays/News/Sidebar/NewsSideBar.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics; -using osu.Game.Online.API.Requests.Responses; -using osu.Framework.Graphics.Shapes; -using osuTK; -using System.Linq; - -namespace osu.Game.Overlays.News.Sidebar -{ - public class NewsSidebar : CompositeDrawable - { - [Cached] - public readonly Bindable Metadata = new Bindable(); - - private FillFlowContainer monthsFlow; - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - RelativeSizeAxes = Axes.Y; - Width = 250; - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4 - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Vertical = 20, - Left = 50, - Right = 30 - }, - Child = new FillFlowContainer - { - Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0, 20), - Children = new Drawable[] - { - new YearsPanel(), - monthsFlow = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10) - } - } - } - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Metadata.BindValueChanged(onMetadataChanged, true); - } - - private void onMetadataChanged(ValueChangedEvent metadata) - { - monthsFlow.Clear(); - - if (metadata.NewValue == null) - return; - - var allPosts = metadata.NewValue.NewsPosts; - - if (allPosts?.Any() != true) - return; - - var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month); - - var keys = lookup.Select(kvp => kvp.Key); - var sortedKeys = keys.OrderByDescending(k => k).ToList(); - - var year = metadata.NewValue.CurrentYear; - - for (int i = 0; i < sortedKeys.Count; i++) - { - var month = sortedKeys[i]; - var posts = lookup[month]; - - monthsFlow.Add(new MonthSection(month, year, posts) - { - IsOpen = { Value = i == 0 } - }); - } - } - } -} From 7befcf74ffbb54fc4783d22f9235667db6c0e1c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 May 2021 18:53:09 +0200 Subject: [PATCH 55/65] Split value change callbacks out to separate methods --- .../Overlays/News/Sidebar/MonthSection.cs | 36 ++++++++++--------- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 28 ++++++++------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index 20c4d2e83e..77f09b750d 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -153,28 +153,30 @@ namespace osu.Game.Overlays.News.Sidebar { base.LoadComplete(); - IsOpen.BindValueChanged(open => - { - ClearTransforms(true); - - if (open.NewValue) - { - AutoSizeAxes = Axes.Y; - content.FadeIn(animation_duration, Easing.OutQuint); - } - else - { - AutoSizeAxes = Axes.None; - this.ResizeHeightTo(0, animation_duration, Easing.OutQuint); - - content.FadeOut(animation_duration, Easing.OutQuint); - } - }, true); + IsOpen.BindValueChanged(_ => updateState(), true); // First state change should be instant. FinishTransforms(true); } + private void updateState() + { + ClearTransforms(true); + + if (IsOpen.Value) + { + AutoSizeAxes = Axes.Y; + content.FadeIn(animation_duration, Easing.OutQuint); + } + else + { + AutoSizeAxes = Axes.None; + this.ResizeHeightTo(0, animation_duration, Easing.OutQuint); + + content.FadeOut(animation_duration, Easing.OutQuint); + } + } + private bool shouldUpdateAutosize = true; // Workaround to allow the dropdown to be opened immediately since FinishTransforms doesn't work for AutosizeDuration. diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index ffdb5cf22e..849cdbf659 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -56,23 +56,25 @@ namespace osu.Game.Overlays.News.Sidebar { base.LoadComplete(); - metadata.BindValueChanged(m => + metadata.BindValueChanged(_ => recreateDrawables(), true); + } + + private void recreateDrawables() + { + yearsFlow.Clear(); + + if (metadata.Value == null) { - yearsFlow.Clear(); + Hide(); + return; + } - if (m.NewValue == null) - { - Hide(); - return; - } + var currentYear = metadata.Value.CurrentYear; - var currentYear = m.NewValue.CurrentYear; + foreach (var y in metadata.Value.Years) + yearsFlow.Add(new YearButton(y, y == currentYear)); - foreach (var y in m.NewValue.Years) - yearsFlow.Add(new YearButton(y, y == currentYear)); - - Show(); - }, true); + Show(); } public class YearButton : OsuHoverContainer From d614a47614218d9a6bc1406736ced7e2937d41e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 May 2021 18:54:17 +0200 Subject: [PATCH 56/65] Rename variable to better explain purpose --- osu.Game/Overlays/News/Sidebar/MonthSection.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index 77f09b750d..166da97f93 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -177,19 +177,19 @@ namespace osu.Game.Overlays.News.Sidebar } } - private bool shouldUpdateAutosize = true; + private bool autoSizeTransitionApplied; - // Workaround to allow the dropdown to be opened immediately since FinishTransforms doesn't work for AutosizeDuration. + // Workaround to allow the dropdown to be opened immediately since FinishTransforms doesn't work for AutoSize{Duration,Easing}. protected override void UpdateAfterAutoSize() { base.UpdateAfterAutoSize(); - if (shouldUpdateAutosize) + if (!autoSizeTransitionApplied) { AutoSizeDuration = animation_duration; AutoSizeEasing = Easing.OutQuint; - shouldUpdateAutosize = false; + autoSizeTransitionApplied = true; } } } From 400984457ca08221dbb99178fc6dcba920311a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 May 2021 19:16:30 +0200 Subject: [PATCH 57/65] Fix weird behaviour in test scene Due to a callback set up in another place, clicking away from the 2022 year after launching the test scene would remove the 2022 button (because the callback was returning metadata without it). For simplicity just trim the 2022 year to make sure both test scenes use the same consistent set of years. --- osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs index 376c270689..6cd3bd7d51 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs @@ -110,10 +110,9 @@ namespace osu.Game.Tests.Visual.Online private static readonly APINewsSidebar metadata_with_no_posts = new APINewsSidebar { - CurrentYear = 2022, + CurrentYear = 2021, Years = new[] { - 2022, 2021, 2020, 2019, From 06389c08dc19287fdece6019170de4257365e7d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 May 2021 13:11:22 +0900 Subject: [PATCH 58/65] Add basic test to show data how one would expect it to be displayed --- osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs index 6cd3bd7d51..b000553a7b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs @@ -24,11 +24,18 @@ namespace osu.Game.Tests.Visual.Online [SetUp] public void SetUp() => Schedule(() => Child = sidebar = new TestNewsSidebar { YearChanged = onYearChanged }); + [Test] + public void TestBasic() + { + AddStep("Add metadata", () => sidebar.Metadata.Value = getMetadata(2021)); + AddUntilStep("Month sections exist", () => sidebar.ChildrenOfType().Any()); + } + [Test] public void TestMetadataWithNoPosts() { AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts); - AddUntilStep("No dropdowns were created", () => !sidebar.ChildrenOfType().Any()); + AddUntilStep("No month sections were created", () => !sidebar.ChildrenOfType().Any()); } [Test] @@ -134,7 +141,7 @@ namespace osu.Game.Tests.Visual.Online { base.LoadComplete(); - Metadata.BindValueChanged(m => + Metadata.BindValueChanged(metadata => { foreach (var b in this.ChildrenOfType()) b.Action = () => YearChanged?.Invoke(b.Year); From 2c65b8fa9366fded6ae5f3f2167b768a3017f3d5 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 18 May 2021 19:55:25 +0900 Subject: [PATCH 59/65] Revert "Fix uninitialized scrollLength value is used" This reverts commit 73dfb04d --- .../Scrolling/ScrollingHitObjectContainer.cs | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 538d4d1d11..915bab9a51 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -185,6 +185,8 @@ namespace osu.Game.Rulesets.UI.Scrolling layoutComputed.Remove(hitObject); } + private float scrollLength; + protected override void Update() { base.Update(); @@ -197,16 +199,29 @@ namespace osu.Game.Rulesets.UI.Scrolling layoutComputed.Clear(); scrollingInfo.Algorithm.Reset(); + + switch (direction.Value) + { + case ScrollingDirection.Up: + case ScrollingDirection.Down: + scrollLength = DrawSize.Y; + break; + + default: + scrollLength = DrawSize.X; + break; + } + layoutCache.Validate(); } } - // We need to calculate hit object positions (including nested hit objects) as soon as possible after lifetimes - // to prevent hit objects displayed in a wrong position for one frame. protected override void UpdateAfterChildrenLife() { base.UpdateAfterChildrenLife(); + // We need to calculate hit object positions (including nested hit objects) as soon as possible after lifetimes + // to prevent hit objects displayed in a wrong position for one frame. // Only AliveObjects need to be considered for layout (reduces overhead in the case of scroll speed changes). foreach (var obj in AliveObjects) { @@ -245,7 +260,7 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } - entry.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(entry.HitObject.StartTime, startOffset, timeRange.Value, getLength()); + entry.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(entry.HitObject.StartTime, startOffset, timeRange.Value, scrollLength); } private void updateLayoutRecursive(DrawableHitObject hitObject) @@ -256,12 +271,12 @@ namespace osu.Game.Rulesets.UI.Scrolling { case ScrollingDirection.Up: case ScrollingDirection.Down: - hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, getLength()); + hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength); break; case ScrollingDirection.Left: case ScrollingDirection.Right: - hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, getLength()); + hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength); break; } } @@ -280,19 +295,19 @@ namespace osu.Game.Rulesets.UI.Scrolling switch (direction.Value) { case ScrollingDirection.Up: - hitObject.Y = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, getLength()); + hitObject.Y = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; case ScrollingDirection.Down: - hitObject.Y = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, getLength()); + hitObject.Y = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; case ScrollingDirection.Left: - hitObject.X = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, getLength()); + hitObject.X = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; case ScrollingDirection.Right: - hitObject.X = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, getLength()); + hitObject.X = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; } } From 84a1a86c6329ff60021190f4c070c28707e736c3 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 18 May 2021 19:55:31 +0900 Subject: [PATCH 60/65] Revert "Use entry to calculate lifetime in ScrollingHOC" This reverts commit 632bb70e --- .../Scrolling/ScrollingHitObjectContainer.cs | 60 +++++++++++-------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 915bab9a51..a9eaf3da68 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -5,9 +5,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Primitives; using osu.Framework.Layout; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -19,18 +17,16 @@ namespace osu.Game.Rulesets.UI.Scrolling private readonly IBindable timeRange = new BindableDouble(); private readonly IBindable direction = new Bindable(); + /// + /// Hit objects which require lifetime computation in the next update call. + /// + private readonly HashSet toComputeLifetime = new HashSet(); + /// /// A set containing all which have an up-to-date layout. /// private readonly HashSet layoutComputed = new HashSet(); - /// - /// A conservative estimate of maximum bounding box of a - /// with respect to the start time position of the hit object. - /// It is used to calculate when the object appears inbound. - /// - protected virtual RectangleF GetDrawRectangle(HitObjectLifetimeEntry entry) => new RectangleF().Inflate(100); - [Resolved] private IScrollingInfo scrollingInfo { get; set; } @@ -58,6 +54,7 @@ namespace osu.Game.Rulesets.UI.Scrolling { base.Clear(); + toComputeLifetime.Clear(); layoutComputed.Clear(); } @@ -169,6 +166,7 @@ namespace osu.Game.Rulesets.UI.Scrolling private void onRemoveRecursive(DrawableHitObject hitObject) { + toComputeLifetime.Remove(hitObject); layoutComputed.Remove(hitObject); hitObject.DefaultsApplied -= invalidateHitObject; @@ -177,11 +175,14 @@ namespace osu.Game.Rulesets.UI.Scrolling onRemoveRecursive(nested); } + /// + /// Make this lifetime and layout computed in next update. + /// private void invalidateHitObject(DrawableHitObject hitObject) { - if (hitObject.ParentHitObject == null) - updateLifetime(hitObject.Entry); - + // Lifetime computation is delayed until next update because + // when the hit object is not pooled this container is not loaded here and `scrollLength` cannot be computed. + toComputeLifetime.Add(hitObject); layoutComputed.Remove(hitObject); } @@ -193,8 +194,13 @@ namespace osu.Game.Rulesets.UI.Scrolling if (!layoutCache.IsValid) { - foreach (var entry in Entries) - updateLifetime(entry); + toComputeLifetime.Clear(); + + foreach (var hitObject in Objects) + { + if (hitObject.HitObject != null) + toComputeLifetime.Add(hitObject); + } layoutComputed.Clear(); @@ -214,6 +220,11 @@ namespace osu.Game.Rulesets.UI.Scrolling layoutCache.Validate(); } + + foreach (var hitObject in toComputeLifetime) + hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject); + + toComputeLifetime.Clear(); } protected override void UpdateAfterChildrenLife() @@ -236,31 +247,32 @@ namespace osu.Game.Rulesets.UI.Scrolling } } - private void updateLifetime(HitObjectLifetimeEntry entry) + private double computeOriginAdjustedLifetimeStart(DrawableHitObject hitObject) { - var rectangle = GetDrawRectangle(entry); - float startOffset = 0; + float originAdjustment = 0.0f; + // calculate the dimension of the part of the hitobject that should already be visible + // when the hitobject origin first appears inside the scrolling container switch (direction.Value) { - case ScrollingDirection.Right: - startOffset = rectangle.Right; + case ScrollingDirection.Up: + originAdjustment = hitObject.OriginPosition.Y; break; case ScrollingDirection.Down: - startOffset = rectangle.Bottom; + originAdjustment = hitObject.DrawHeight - hitObject.OriginPosition.Y; break; case ScrollingDirection.Left: - startOffset = -rectangle.Left; + originAdjustment = hitObject.OriginPosition.X; break; - case ScrollingDirection.Up: - startOffset = -rectangle.Top; + case ScrollingDirection.Right: + originAdjustment = hitObject.DrawWidth - hitObject.OriginPosition.X; break; } - entry.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(entry.HitObject.StartTime, startOffset, timeRange.Value, scrollLength); + return scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength); } private void updateLayoutRecursive(DrawableHitObject hitObject) From ee9fe3c4bef77f8c7e4c3d67882fa741e1d86932 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 18 May 2021 19:55:44 +0900 Subject: [PATCH 61/65] Revert "Add failing test showing lifetime not recomputed with pooled objects" This reverts commit b88e5a31 --- .../Gameplay/TestSceneDrawableScrollingRuleset.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 75a5eec6f7..9931ee4a45 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -90,20 +90,6 @@ namespace osu.Game.Tests.Visual.Gameplay assertChildPosition(5); } - [TestCase("pooled")] - [TestCase("non-pooled")] - public void TestLifetimeRecomputedWhenTimeRangeChanges(string pooled) - { - var beatmap = createBeatmap(_ => pooled == "pooled" ? new TestPooledHitObject() : new TestHitObject()); - beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); - createTest(beatmap); - - assertDead(3); - - AddStep("increase time range", () => drawableRuleset.TimeRange.Value = 3 * time_range); - assertPosition(3, 1); - } - [Test] public void TestRelativeBeatLengthScaleSingleTimingPoint() { From d70d37b7f46ec340113e0e531ac38f0975eccf63 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 18 May 2021 22:30:36 +0300 Subject: [PATCH 62/65] Remove convoluted autosize logic in MonthSection --- .../Overlays/News/Sidebar/MonthSection.cs | 25 +++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index 166da97f93..2fc88b3909 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -139,24 +139,23 @@ namespace osu.Game.Overlays.News.Sidebar { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + AutoSizeDuration = animation_duration; + AutoSizeEasing = Easing.Out; InternalChild = content = new FillFlowContainer { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5) + Spacing = new Vector2(0, 5), + Alpha = 0 }; } protected override void LoadComplete() { base.LoadComplete(); - IsOpen.BindValueChanged(_ => updateState(), true); - - // First state change should be instant. - FinishTransforms(true); } private void updateState() @@ -176,22 +175,6 @@ namespace osu.Game.Overlays.News.Sidebar content.FadeOut(animation_duration, Easing.OutQuint); } } - - private bool autoSizeTransitionApplied; - - // Workaround to allow the dropdown to be opened immediately since FinishTransforms doesn't work for AutoSize{Duration,Easing}. - protected override void UpdateAfterAutoSize() - { - base.UpdateAfterAutoSize(); - - if (!autoSizeTransitionApplied) - { - AutoSizeDuration = animation_duration; - AutoSizeEasing = Easing.OutQuint; - - autoSizeTransitionApplied = true; - } - } } } } From e8bc2cac5bc058bdae888dcd0bc67e9d84b6fc47 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 19 May 2021 13:36:39 +0900 Subject: [PATCH 63/65] Fix test not being marked as headless --- .../Editing/TestSceneHitObjectContainerEventBuffer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs b/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs index 5233cbc0be..592971dbaf 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs @@ -4,6 +4,7 @@ using System; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -12,6 +13,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.Editing { + [HeadlessTest] public class TestSceneHitObjectContainerEventBuffer : OsuTestScene { private readonly TestHitObject testObj = new TestHitObject(); From 539e5179fe8ef9d9cd148882a063595295872815 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 15:45:24 +0900 Subject: [PATCH 64/65] Tidy up content and bind event code --- .../Overlays/News/Sidebar/MonthSection.cs | 22 +++++++++---------- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 4 ++-- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index 2fc88b3909..9fc4f49bd3 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -117,10 +117,10 @@ namespace osu.Game.Overlays.News.Sidebar } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, GameHost host) + private void load(OverlayColourProvider overlayColours, GameHost host) { - IdleColour = colourProvider.Light2; - HoverColour = colourProvider.Light1; + IdleColour = overlayColours.Light2; + HoverColour = overlayColours.Light1; TooltipText = "view in browser"; Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug); @@ -131,9 +131,7 @@ namespace osu.Game.Overlays.News.Sidebar { public readonly BindableBool IsOpen = new BindableBool(); - protected override Container Content => content; - - private readonly FillFlowContainer content; + protected override Container Content { get; } public PostsContainer() { @@ -141,7 +139,7 @@ namespace osu.Game.Overlays.News.Sidebar AutoSizeAxes = Axes.Y; AutoSizeDuration = animation_duration; AutoSizeEasing = Easing.Out; - InternalChild = content = new FillFlowContainer + InternalChild = Content = new FillFlowContainer { Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, @@ -155,24 +153,24 @@ namespace osu.Game.Overlays.News.Sidebar protected override void LoadComplete() { base.LoadComplete(); - IsOpen.BindValueChanged(_ => updateState(), true); + IsOpen.BindValueChanged(updateState, true); } - private void updateState() + private void updateState(ValueChangedEvent isOpen) { ClearTransforms(true); - if (IsOpen.Value) + if (isOpen.NewValue) { AutoSizeAxes = Axes.Y; - content.FadeIn(animation_duration, Easing.OutQuint); + Content.FadeIn(animation_duration, Easing.OutQuint); } else { AutoSizeAxes = Axes.None; this.ResizeHeightTo(0, animation_duration, Easing.OutQuint); - content.FadeOut(animation_duration, Easing.OutQuint); + Content.FadeOut(animation_duration, Easing.OutQuint); } } } diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 849cdbf659..b6bbdbb6d4 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.News.Sidebar private FillFlowContainer yearsFlow; [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, Bindable metadata) + private void load(OverlayColourProvider overlayColours, Bindable metadata) { this.metadata.BindTo(metadata); @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.News.Sidebar new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background3 + Colour = overlayColours.Background3 }, new Container { From 19a07b01073f821f4c02131f7834474396ea8d49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 May 2021 15:48:31 +0900 Subject: [PATCH 65/65] IsOpen -> Expanded --- .../Overlays/News/Sidebar/MonthSection.cs | 21 ++++++++++--------- osu.Game/Overlays/News/Sidebar/NewsSidebar.cs | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index 9fc4f49bd3..b300a755f9 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.News.Sidebar { private const int animation_duration = 250; - public readonly BindableBool IsOpen = new BindableBool(); + public readonly BindableBool Expanded = new BindableBool(); public MonthSection(int month, int year, IEnumerable posts) { @@ -32,6 +32,7 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Masking = true; + InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -41,11 +42,11 @@ namespace osu.Game.Overlays.News.Sidebar { new DropdownHeader(month, year) { - IsOpen = { BindTarget = IsOpen } + Expanded = { BindTarget = Expanded } }, new PostsContainer { - IsOpen = { BindTarget = IsOpen }, + Expanded = { BindTarget = Expanded }, Children = posts.Select(p => new PostButton(p)).ToArray() } } @@ -54,7 +55,7 @@ namespace osu.Game.Overlays.News.Sidebar private class DropdownHeader : OsuClickableContainer { - public readonly BindableBool IsOpen = new BindableBool(); + public readonly BindableBool Expanded = new BindableBool(); private readonly SpriteIcon icon; @@ -64,7 +65,7 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.X; Height = 15; - Action = IsOpen.Toggle; + Action = Expanded.Toggle; Children = new Drawable[] { new OsuSpriteText @@ -88,7 +89,7 @@ namespace osu.Game.Overlays.News.Sidebar { base.LoadComplete(); - IsOpen.BindValueChanged(open => + Expanded.BindValueChanged(open => { icon.Scale = new Vector2(1, open.NewValue ? -1 : 1); }, true); @@ -129,7 +130,7 @@ namespace osu.Game.Overlays.News.Sidebar private class PostsContainer : Container { - public readonly BindableBool IsOpen = new BindableBool(); + public readonly BindableBool Expanded = new BindableBool(); protected override Container Content { get; } @@ -153,14 +154,14 @@ namespace osu.Game.Overlays.News.Sidebar protected override void LoadComplete() { base.LoadComplete(); - IsOpen.BindValueChanged(updateState, true); + Expanded.BindValueChanged(updateState, true); } - private void updateState(ValueChangedEvent isOpen) + private void updateState(ValueChangedEvent expanded) { ClearTransforms(true); - if (isOpen.NewValue) + if (expanded.NewValue) { AutoSizeAxes = Axes.Y; Content.FadeIn(animation_duration, Easing.OutQuint); diff --git a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs index b8d283b7e2..d14ad90ef4 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.News.Sidebar monthsFlow.Add(new MonthSection(month, year, posts) { - IsOpen = { Value = i == 0 } + Expanded = { Value = i == 0 } }); } }