diff --git a/osu.Android.props b/osu.Android.props
index e5a1ec2f4e..25bde037db 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -54,6 +54,6 @@
-
+
diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
index 267e6d12c7..0c754412e5 100644
--- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
@@ -36,7 +36,11 @@ namespace osu.Game.Rulesets.Catch.Objects
}
}
- public double EndTime => StartTime + Duration;
+ public double EndTime
+ {
+ get => StartTime + Duration;
+ set => Duration = value - StartTime;
+ }
public double Duration { get; set; }
}
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index a4ed966abb..b014b32305 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -110,7 +110,11 @@ namespace osu.Game.Rulesets.Catch.Objects
}
}
- public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
+ public double EndTime
+ {
+ get => StartTime + this.SpanCount() * Path.Distance / Velocity;
+ set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
+ }
public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH;
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index bdba813eed..86d3d2b2b0 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -15,7 +15,11 @@ namespace osu.Game.Rulesets.Mania.Objects
///
public class HoldNote : ManiaHitObject, IHasEndTime
{
- public double EndTime => StartTime + Duration;
+ public double EndTime
+ {
+ get => StartTime + Duration;
+ set => Duration = value - StartTime;
+ }
private double duration;
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index fe65ab78d1..95fb6d9d48 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -18,7 +18,12 @@ namespace osu.Game.Rulesets.Osu.Objects
{
public class Slider : OsuHitObject, IHasCurve
{
- public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
+ public double EndTime
+ {
+ get => StartTime + this.SpanCount() * Path.Distance / Velocity;
+ set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
+ }
+
public double Duration => EndTime - StartTime;
private readonly Cached endPositionCache = new Cached();
@@ -81,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Objects
set
{
repeatCount = value;
- endPositionCache.Invalidate();
+ updateNestedPositions();
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
index 8956ca9c19..aacd78f176 100644
--- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
@@ -18,7 +18,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
///
private const float base_distance = 100;
- public double EndTime => StartTime + Duration;
+ public double EndTime
+ {
+ get => StartTime + Duration;
+ set => Duration = value - StartTime;
+ }
public double Duration { get; set; }
diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs
index e60984596d..2f06066a16 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs
@@ -11,7 +11,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
{
public class Swell : TaikoHitObject, IHasEndTime
{
- public double EndTime => StartTime + Duration;
+ public double EndTime
+ {
+ get => StartTime + Duration;
+ set => Duration = value - StartTime;
+ }
public double Duration { get; set; }
diff --git a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs
index 5a4e76d586..3cb5909ba9 100644
--- a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs
+++ b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs
@@ -3,6 +3,8 @@
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
@@ -23,15 +25,31 @@ namespace osu.Game.Tests.Editor
[Cached(typeof(IBeatSnapProvider))]
private readonly EditorBeatmap editorBeatmap;
+ protected override Container Content { get; }
+
public TestSceneHitObjectComposerDistanceSnapping()
{
- editorBeatmap = new EditorBeatmap(new OsuBeatmap(), BeatDivisor);
+ base.Content.Add(new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ editorBeatmap = new EditorBeatmap(new OsuBeatmap()),
+ Content = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ },
+ });
}
[SetUp]
public void Setup() => Schedule(() =>
{
- Child = composer = new TestHitObjectComposer();
+ Children = new Drawable[]
+ {
+ composer = new TestHitObjectComposer()
+ };
BeatDivisor.Value = 1;
diff --git a/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs b/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs
index d7f709dc03..a6e8622b6f 100644
--- a/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs
+++ b/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs
@@ -18,12 +18,21 @@ namespace osu.Game.Tests.ScrollAlgorithms
}
[Test]
- public void TestDisplayStartTime()
+ public void TestPointDisplayStartTime()
{
- Assert.AreEqual(-8000, algorithm.GetDisplayStartTime(2000, 10000));
- Assert.AreEqual(-3000, algorithm.GetDisplayStartTime(2000, 5000));
- Assert.AreEqual(2000, algorithm.GetDisplayStartTime(7000, 5000));
- Assert.AreEqual(7000, algorithm.GetDisplayStartTime(17000, 10000));
+ Assert.AreEqual(-8000, algorithm.GetDisplayStartTime(2000, 0, 10000, 1));
+ Assert.AreEqual(-3000, algorithm.GetDisplayStartTime(2000, 0, 5000, 1));
+ Assert.AreEqual(2000, algorithm.GetDisplayStartTime(7000, 0, 5000, 1));
+ Assert.AreEqual(7000, algorithm.GetDisplayStartTime(17000, 0, 10000, 1));
+ }
+
+ [Test]
+ public void TestObjectDisplayStartTime()
+ {
+ Assert.AreEqual(900, algorithm.GetDisplayStartTime(2000, 50, 1000, 500)); // 2000 - (1 + 50 / 500) * 1000
+ Assert.AreEqual(8900, algorithm.GetDisplayStartTime(10000, 50, 1000, 500)); // 10000 - (1 + 50 / 500) * 1000
+ Assert.AreEqual(13500, algorithm.GetDisplayStartTime(15000, 250, 1000, 500)); // 15000 - (1 + 250 / 500) * 1000
+ Assert.AreEqual(19000, algorithm.GetDisplayStartTime(25000, 100, 5000, 500)); // 25000 - (1 + 100 / 500) * 5000
}
[Test]
diff --git a/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs b/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs
index 106aa88be3..1429d22c1a 100644
--- a/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs
+++ b/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs
@@ -27,11 +27,22 @@ namespace osu.Game.Tests.ScrollAlgorithms
}
[Test]
- public void TestDisplayStartTime()
+ public void TestPointDisplayStartTime()
{
- Assert.AreEqual(1000, algorithm.GetDisplayStartTime(2000, 1000)); // Like constant
- Assert.AreEqual(10000, algorithm.GetDisplayStartTime(10500, 1000)); // 10500 - (1000 * 0.5)
- Assert.AreEqual(20000, algorithm.GetDisplayStartTime(22000, 1000)); // 23000 - (1000 / 0.5)
+ Assert.AreEqual(1000, algorithm.GetDisplayStartTime(2000, 0, 1000, 1)); // Like constant
+ Assert.AreEqual(10000, algorithm.GetDisplayStartTime(10500, 0, 1000, 1)); // 10500 - (1000 * 0.5)
+ Assert.AreEqual(20000, algorithm.GetDisplayStartTime(22000, 0, 1000, 1)); // 23000 - (1000 / 0.5)
+ }
+
+ [Test]
+ public void TestObjectDisplayStartTime()
+ {
+ Assert.AreEqual(900, algorithm.GetDisplayStartTime(2000, 50, 1000, 500)); // 2000 - (1 + 50 / 500) * 1000 / 1
+ Assert.AreEqual(9450, algorithm.GetDisplayStartTime(10000, 50, 1000, 500)); // 10000 - (1 + 50 / 500) * 1000 / 2
+ Assert.AreEqual(14250, algorithm.GetDisplayStartTime(15000, 250, 1000, 500)); // 15000 - (1 + 250 / 500) * 1000 / 2
+ Assert.AreEqual(16500, algorithm.GetDisplayStartTime(18000, 250, 2000, 500)); // 18000 - (1 + 250 / 500) * 2000 / 2
+ Assert.AreEqual(17800, algorithm.GetDisplayStartTime(20000, 50, 1000, 500)); // 20000 - (1 + 50 / 500) * 1000 / 0.5
+ Assert.AreEqual(19800, algorithm.GetDisplayStartTime(22000, 50, 1000, 500)); // 22000 - (1 + 50 / 500) * 1000 / 0.5
}
[Test]
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs
index 3c75fd5310..4d8f877575 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs
@@ -1,6 +1,8 @@
// 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.Graphics;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
@@ -10,6 +12,17 @@ namespace osu.Game.Tests.Visual.Editor
[TestFixture]
public class TestSceneTimelineBlueprintContainer : TimelineTestScene
{
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(TimelineHitObjectBlueprint),
+ };
+
public override Drawable CreateTestComponent() => new TimelineBlueprintContainer();
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ Clock.Seek(10000);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs
index adfed9a299..ae09a7fa47 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs
@@ -27,7 +27,12 @@ namespace osu.Game.Tests.Visual.Editor
};
[Cached(typeof(EditorBeatmap))]
- private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ private readonly EditorBeatmap editorBeatmap;
+
+ public TestSceneTimingScreen()
+ {
+ editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ }
[BackgroundDependencyLoader]
private void load()
diff --git a/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs
index b5e526d3c2..7081eb3af5 100644
--- a/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs
+++ b/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs
@@ -13,7 +13,6 @@ using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK;
@@ -38,13 +37,16 @@ namespace osu.Game.Tests.Visual.Editor
{
Beatmap.Value = new WaveformTestBeatmap(audio);
- var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap, BeatDivisor);
+ var playable = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
+
+ var editorBeatmap = new EditorBeatmap(playable);
Dependencies.Cache(editorBeatmap);
Dependencies.CacheAs(editorBeatmap);
AddRange(new Drawable[]
{
+ editorBeatmap,
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
index 8629522dc2..d03716db2e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
@@ -11,6 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading;
+using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@@ -19,6 +20,7 @@ using osu.Game.Rulesets.Timing;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Gameplay
{
@@ -30,7 +32,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached(typeof(IReadOnlyList))]
private IReadOnlyList mods { get; set; } = Array.Empty();
- private const int spawn_interval = 5000;
+ private const int time_range = 5000;
+ private const int spawn_rate = time_range / 10;
private readonly ScrollingTestContainer[] scrollContainers = new ScrollingTestContainer[4];
private readonly TestPlayfield[] playfields = new TestPlayfield[4];
@@ -50,13 +53,13 @@ namespace osu.Game.Tests.Visual.Gameplay
{
RelativeSizeAxes = Axes.Both,
Child = playfields[0] = new TestPlayfield(),
- TimeRange = spawn_interval
+ TimeRange = time_range
},
scrollContainers[1] = new ScrollingTestContainer(ScrollingDirection.Down)
{
RelativeSizeAxes = Axes.Both,
Child = playfields[1] = new TestPlayfield(),
- TimeRange = spawn_interval
+ TimeRange = time_range
},
},
new Drawable[]
@@ -65,13 +68,13 @@ namespace osu.Game.Tests.Visual.Gameplay
{
RelativeSizeAxes = Axes.Both,
Child = playfields[2] = new TestPlayfield(),
- TimeRange = spawn_interval
+ TimeRange = time_range
},
scrollContainers[3] = new ScrollingTestContainer(ScrollingDirection.Right)
{
RelativeSizeAxes = Axes.Both,
Child = playfields[3] = new TestPlayfield(),
- TimeRange = spawn_interval
+ TimeRange = time_range
}
}
}
@@ -84,31 +87,55 @@ namespace osu.Game.Tests.Visual.Gameplay
{
scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0)));
- for (int i = 0; i <= spawn_interval; i += 1000)
+ for (int i = spawn_rate / 2; i <= time_range; i += spawn_rate)
addHitObject(Time.Current + i);
hitObjectSpawnDelegate?.Cancel();
- hitObjectSpawnDelegate = Scheduler.AddDelayed(() => addHitObject(Time.Current + spawn_interval), 1000, true);
+ hitObjectSpawnDelegate = Scheduler.AddDelayed(() => addHitObject(Time.Current + time_range), spawn_rate, true);
}
+ private IList testControlPoints => new List
+ {
+ new MultiplierControlPoint(time_range) { DifficultyPoint = { SpeedMultiplier = 1.25 } },
+ new MultiplierControlPoint(1.5 * time_range) { DifficultyPoint = { SpeedMultiplier = 1 } },
+ new MultiplierControlPoint(2 * time_range) { DifficultyPoint = { SpeedMultiplier = 1.5 } }
+ };
+
[Test]
public void TestScrollAlgorithms()
{
- AddStep("Constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
- AddStep("Overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
- AddStep("Sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
+ AddStep("constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
+ AddStep("overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
+ AddStep("sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
- AddSliderStep("Time range", 100, 10000, spawn_interval, v => scrollContainers.Where(c => c != null).ForEach(c => c.TimeRange = v));
- AddStep("Add control point", () => addControlPoint(Time.Current + spawn_interval));
+ AddSliderStep("time range", 100, 10000, time_range, v => scrollContainers.Where(c => c != null).ForEach(c => c.TimeRange = v));
+
+ AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
}
[Test]
- public void TestScrollLifetime()
+ public void TestConstantScrollLifetime()
{
- AddStep("Set constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
+ AddStep("set constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
// scroll container time range must be less than the rate of spawning hitobjects
// otherwise the hitobjects will spawn already partly visible on screen and look wrong
- AddStep("Set time range", () => scrollContainers.ForEach(c => c.TimeRange = spawn_interval / 2.0));
+ AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
+ }
+
+ [Test]
+ public void TestSequentialScrollLifetime()
+ {
+ AddStep("set sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
+ AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
+ AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
+ }
+
+ [Test]
+ public void TestOverlappingScrollLifetime()
+ {
+ AddStep("set overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
+ AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
+ AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
}
private void addHitObject(double time)
@@ -122,28 +149,27 @@ namespace osu.Game.Tests.Visual.Gameplay
});
}
- private void addControlPoint(double time)
+ private TestDrawableControlPoint createDrawablePoint(TestPlayfield playfield, double t)
{
- scrollContainers.ForEach(c =>
+ var obj = new TestDrawableControlPoint(playfield.Direction, t);
+ setAnchor(obj, playfield);
+ return obj;
+ }
+
+ private void addControlPoints(IList controlPoints, double sequenceStartTime)
+ {
+ controlPoints.ForEach(point => point.StartTime += sequenceStartTime);
+
+ scrollContainers.ForEach(container =>
{
- c.ControlPoints.Add(new MultiplierControlPoint(time) { DifficultyPoint = { SpeedMultiplier = 3 } });
- c.ControlPoints.Add(new MultiplierControlPoint(time + 2000) { DifficultyPoint = { SpeedMultiplier = 2 } });
- c.ControlPoints.Add(new MultiplierControlPoint(time + 3000) { DifficultyPoint = { SpeedMultiplier = 1 } });
+ container.ControlPoints.AddRange(controlPoints);
});
- playfields.ForEach(p =>
+ foreach (var playfield in playfields)
{
- TestDrawableControlPoint createDrawablePoint(double t)
- {
- var obj = new TestDrawableControlPoint(p.Direction, t);
- setAnchor(obj, p);
- return obj;
- }
-
- p.Add(createDrawablePoint(time));
- p.Add(createDrawablePoint(time + 2000));
- p.Add(createDrawablePoint(time + 3000));
- });
+ foreach (var controlPoint in controlPoints)
+ playfield.Add(createDrawablePoint(playfield, controlPoint.StartTime));
+ }
}
private void setAnchor(DrawableHitObject obj, TestPlayfield playfield)
@@ -236,7 +262,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AutoSizeAxes = Axes.Both;
- AddInternal(new Box { Size = new Vector2(75) });
+ AddInternal(new Box
+ {
+ Size = new Vector2(75),
+ Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1)
+ });
}
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
index eb4dc909df..fe14a1ff0a 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
@@ -4,11 +4,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Online.Multiplayer;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Catch;
+using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Multi;
using osu.Game.Screens.Multi.Lounge.Components;
using osu.Game.Users;
@@ -27,11 +32,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Cached(Type = typeof(IRoomManager))]
private TestRoomManager roomManager = new TestRoomManager();
+ private RoomsContainer container;
+
[BackgroundDependencyLoader]
private void load()
{
- RoomsContainer container;
-
Child = container = new RoomsContainer
{
Anchor = Anchor.Centre,
@@ -39,24 +44,21 @@ namespace osu.Game.Tests.Visual.Multiplayer
Width = 0.5f,
JoinRequested = joinRequested
};
+ }
+
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
AddStep("clear rooms", () => roomManager.Rooms.Clear());
+ }
- AddStep("add rooms", () =>
- {
- for (int i = 0; i < 3; i++)
- {
- roomManager.Rooms.Add(new Room
- {
- RoomID = { Value = i },
- Name = { Value = $"Room {i}" },
- Host = { Value = new User { Username = "Host" } },
- EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }
- });
- }
- });
+ [Test]
+ public void TestBasicListChanges()
+ {
+ addRooms(3);
- AddAssert("has 2 rooms", () => container.Rooms.Count == 3);
+ AddAssert("has 3 rooms", () => container.Rooms.Count == 3);
AddStep("remove first room", () => roomManager.Rooms.Remove(roomManager.Rooms.FirstOrDefault()));
AddAssert("has 2 rooms", () => container.Rooms.Count == 2);
AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0));
@@ -68,6 +70,70 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("first room joined", () => roomManager.Rooms.First().Status.Value is JoinedRoomStatus);
}
+ [Test]
+ public void TestStringFiltering()
+ {
+ addRooms(4);
+
+ AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
+
+ AddStep("filter one room", () => container.Filter(new FilterCriteria { SearchString = "1" }));
+
+ AddUntilStep("1 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 1);
+
+ AddStep("remove filter", () => container.Filter(null));
+
+ AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
+ }
+
+ [Test]
+ public void TestRulesetFiltering()
+ {
+ addRooms(2, new OsuRuleset().RulesetInfo);
+ addRooms(3, new CatchRuleset().RulesetInfo);
+
+ AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5);
+
+ AddStep("filter osu! rooms", () => container.Filter(new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo }));
+
+ AddUntilStep("2 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2);
+
+ AddStep("filter catch rooms", () => container.Filter(new FilterCriteria { Ruleset = new CatchRuleset().RulesetInfo }));
+
+ AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3);
+ }
+
+ private void addRooms(int count, RulesetInfo ruleset = null)
+ {
+ AddStep("add rooms", () =>
+ {
+ for (int i = 0; i < count; i++)
+ {
+ var room = new Room
+ {
+ RoomID = { Value = i },
+ Name = { Value = $"Room {i}" },
+ Host = { Value = new User { Username = "Host" } },
+ EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }
+ };
+
+ if (ruleset != null)
+ {
+ room.Playlist.Add(new PlaylistItem
+ {
+ Ruleset = ruleset,
+ Beatmap = new BeatmapInfo
+ {
+ Metadata = new BeatmapMetadata()
+ }
+ });
+ }
+
+ roomManager.Rooms.Add(room);
+ }
+ });
+ }
+
private void joinRequested(Room room) => room.Status.Value = new JoinedRoomStatus();
private class TestRoomManager : IRoomManager
diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs
index 9c526c4f81..2a43ba3f99 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs
@@ -10,6 +10,8 @@ using osu.Framework.Graphics;
using osu.Game.Overlays.Comments;
using osu.Game.Overlays;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Game.Users;
namespace osu.Game.Tests.Visual.Online
{
@@ -37,23 +39,29 @@ namespace osu.Game.Tests.Visual.Online
public TestSceneCommentsContainer()
{
BasicScrollContainer scroll;
- CommentsContainer comments;
+ TestCommentsContainer comments;
Add(scroll = new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
- Child = comments = new CommentsContainer()
+ Child = comments = new TestCommentsContainer()
});
AddStep("Big Black comments", () => comments.ShowComments(CommentableType.Beatmapset, 41823));
AddStep("Airman comments", () => comments.ShowComments(CommentableType.Beatmapset, 24313));
AddStep("Lazer build comments", () => comments.ShowComments(CommentableType.Build, 4772));
AddStep("News comments", () => comments.ShowComments(CommentableType.NewsPost, 715));
+ AddStep("Trigger user change", comments.User.TriggerChange);
AddStep("Idle state", () =>
{
scroll.Clear();
- scroll.Add(comments = new CommentsContainer());
+ scroll.Add(comments = new TestCommentsContainer());
});
}
+
+ private class TestCommentsContainer : CommentsContainer
+ {
+ public new Bindable User => base.User;
+ }
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs
new file mode 100644
index 0000000000..7476b52b49
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs
@@ -0,0 +1,66 @@
+// 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.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Overlays.Music;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ public class TestScenePlaylistOverlay : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(PlaylistOverlay),
+ typeof(Playlist)
+ };
+
+ private readonly BindableList beatmapSets = new BindableList();
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ PlaylistOverlay overlay;
+
+ Child = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(300, 500),
+ Child = overlay = new PlaylistOverlay
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.X,
+ State = { Value = Visibility.Visible }
+ }
+ };
+
+ beatmapSets.Clear();
+
+ for (int i = 0; i < 100; i++)
+ {
+ beatmapSets.Add(new BeatmapSetInfo
+ {
+ Metadata = new BeatmapMetadata
+ {
+ // Create random metadata, then we can check if sorting works based on these
+ Artist = "Some Artist " + RNG.Next(0, 9),
+ Title = $"Some Song {i + 1}",
+ AuthorString = "Some Guy " + RNG.Next(0, 9),
+ },
+ DateAdded = DateTimeOffset.UtcNow,
+ });
+ }
+
+ overlay.BeatmapSets.BindTo(beatmapSets);
+ });
+ }
+}
diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs
index b7d7bb1ee1..df6394ed34 100644
--- a/osu.Game.Tests/WaveformTestBeatmap.cs
+++ b/osu.Game.Tests/WaveformTestBeatmap.cs
@@ -11,6 +11,8 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.IO.Archives;
+using osu.Game.Rulesets.Catch;
+using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests
@@ -20,11 +22,18 @@ namespace osu.Game.Tests
///
public class WaveformTestBeatmap : WorkingBeatmap
{
+ private readonly Beatmap beatmap;
private readonly ITrackStore trackStore;
public WaveformTestBeatmap(AudioManager audioManager)
- : base(new BeatmapInfo(), audioManager)
+ : this(audioManager, new WaveformBeatmap())
{
+ }
+
+ public WaveformTestBeatmap(AudioManager audioManager, Beatmap beatmap)
+ : base(beatmap.BeatmapInfo, audioManager)
+ {
+ this.beatmap = beatmap;
trackStore = audioManager.GetTrackStore(getZipReader());
}
@@ -34,11 +43,11 @@ namespace osu.Game.Tests
trackStore?.Dispose();
}
- private Stream getStream() => TestResources.GetTestBeatmapStream();
+ private static Stream getStream() => TestResources.GetTestBeatmapStream();
- private ZipArchiveReader getZipReader() => new ZipArchiveReader(getStream());
+ private static ZipArchiveReader getZipReader() => new ZipArchiveReader(getStream());
- protected override IBeatmap GetBeatmap() => createTestBeatmap();
+ protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => null;
@@ -57,10 +66,16 @@ namespace osu.Game.Tests
}
}
- private Beatmap createTestBeatmap()
+ private class WaveformBeatmap : TestBeatmap
{
- using (var reader = getZipReader())
+ public WaveformBeatmap()
+ : base(new CatchRuleset().RulesetInfo)
{
+ }
+
+ protected override Beatmap CreateBeatmap()
+ {
+ using (var reader = getZipReader())
using (var beatmapStream = reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu"))))
using (var beatmapReader = new LineBufferedReader(beatmapStream))
return Decoder.GetDecoder(beatmapReader).Decode(beatmapReader);
diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs
index f859dccc80..a3788e4582 100644
--- a/osu.Game/Configuration/SettingSourceAttribute.cs
+++ b/osu.Game/Configuration/SettingSourceAttribute.cs
@@ -45,7 +45,8 @@ namespace osu.Game.Configuration
yield return new SettingsSlider
{
LabelText = attr.Label,
- Bindable = bNumber
+ Bindable = bNumber,
+ KeyboardStep = 0.1f,
};
break;
@@ -54,7 +55,8 @@ namespace osu.Game.Configuration
yield return new SettingsSlider
{
LabelText = attr.Label,
- Bindable = bNumber
+ Bindable = bNumber,
+ KeyboardStep = 0.1f,
};
break;
diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs
index 8761b88b1e..33a6be0d7a 100644
--- a/osu.Game/Overlays/Comments/CommentsContainer.cs
+++ b/osu.Game/Overlays/Comments/CommentsContainer.cs
@@ -12,6 +12,7 @@ using osu.Game.Online.API.Requests.Responses;
using System.Threading;
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Game.Users;
namespace osu.Game.Overlays.Comments
{
@@ -23,6 +24,8 @@ namespace osu.Game.Overlays.Comments
public readonly Bindable Sort = new Bindable();
public readonly BindableBool ShowDeleted = new BindableBool();
+ protected readonly Bindable User = new Bindable();
+
[Resolved]
private IAPIProvider api { get; set; }
@@ -109,10 +112,13 @@ namespace osu.Game.Overlays.Comments
}
}
});
+
+ User.BindTo(api.LocalUser);
}
protected override void LoadComplete()
{
+ User.BindValueChanged(_ => refetchComments());
Sort.BindValueChanged(_ => refetchComments(), true);
base.LoadComplete();
}
@@ -148,7 +154,7 @@ namespace osu.Game.Overlays.Comments
loadCancellation?.Cancel();
request = new GetCommentsRequest(type, id.Value, Sort.Value, currentPage++);
request.Success += onSuccess;
- api.Queue(request);
+ api.PerformAsync(request);
}
private void clearComments()
diff --git a/osu.Game/Overlays/Music/Playlist.cs b/osu.Game/Overlays/Music/Playlist.cs
new file mode 100644
index 0000000000..1ba568443d
--- /dev/null
+++ b/osu.Game/Overlays/Music/Playlist.cs
@@ -0,0 +1,52 @@
+// 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.Linq;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics.Containers;
+using osuTK;
+
+namespace osu.Game.Overlays.Music
+{
+ public class Playlist : RearrangeableListContainer
+ {
+ public Action RequestSelection;
+
+ public readonly Bindable SelectedSet = new Bindable();
+
+ ///
+ /// Whether any item is currently being dragged. Used to hide other items' drag handles.
+ ///
+ private readonly BindableBool playlistDragActive = new BindableBool();
+
+ public new MarginPadding Padding
+ {
+ get => base.Padding;
+ set => base.Padding = value;
+ }
+
+ public void Filter(string searchTerm) => ((SearchContainer>)ListContainer).SearchTerm = searchTerm;
+
+ public BeatmapSetInfo FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter);
+
+ protected override RearrangeableListItem CreateDrawable(BeatmapSetInfo item) => new PlaylistItem(item)
+ {
+ SelectedSet = { BindTarget = SelectedSet },
+ PlaylistDragActive = { BindTarget = playlistDragActive },
+ RequestSelection = set => RequestSelection?.Invoke(set)
+ };
+
+ protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer();
+
+ protected override FillFlowContainer> CreateListFillFlowContainer() => new SearchContainer>
+ {
+ Spacing = new Vector2(0, 3),
+ LayoutDuration = 200,
+ LayoutEasing = Easing.OutQuint,
+ };
+ }
+}
diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs
index d40f391982..0569261867 100644
--- a/osu.Game/Overlays/Music/PlaylistItem.cs
+++ b/osu.Game/Overlays/Music/PlaylistItem.cs
@@ -4,8 +4,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using osuTK.Graphics;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
@@ -15,64 +15,38 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Overlays.Music
{
- public class PlaylistItem : Container, IFilterable, IDraggable
+ public class PlaylistItem : RearrangeableListItem, IFilterable
{
private const float fade_duration = 100;
- private Color4 hoverColour;
- private Color4 artistColour;
+ public BindableBool PlaylistDragActive = new BindableBool();
- private SpriteIcon handle;
+ public readonly Bindable SelectedSet = new Bindable();
+
+ public Action RequestSelection;
+
+ private PlaylistItemHandle handle;
private TextFlowContainer text;
private IEnumerable titleSprites;
private ILocalisedBindableString titleBind;
private ILocalisedBindableString artistBind;
- public readonly BeatmapSetInfo BeatmapSetInfo;
+ private Color4 hoverColour;
+ private Color4 artistColour;
- public Action OnSelect;
-
- public bool IsDraggable { get; private set; }
-
- protected override bool OnMouseDown(MouseDownEvent e)
+ public PlaylistItem(BeatmapSetInfo item)
+ : base(item)
{
- IsDraggable = handle.IsHovered;
- return base.OnMouseDown(e);
- }
-
- protected override void OnMouseUp(MouseUpEvent e)
- {
- IsDraggable = false;
- base.OnMouseUp(e);
- }
-
- private bool selected;
-
- public bool Selected
- {
- get => selected;
- set
- {
- if (value == selected) return;
-
- selected = value;
-
- FinishTransforms(true);
- foreach (Drawable s in titleSprites)
- s.FadeColour(Selected ? hoverColour : Color4.White, fade_duration);
- }
- }
-
- public PlaylistItem(BeatmapSetInfo setInfo)
- {
- BeatmapSetInfo = setInfo;
-
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
- Padding = new MarginPadding { Top = 3, Bottom = 3 };
+
+ Padding = new MarginPadding { Left = 5 };
+
+ FilterTerms = item.Metadata.SearchableTerms;
}
[BackgroundDependencyLoader]
@@ -81,30 +55,55 @@ namespace osu.Game.Overlays.Music
hoverColour = colours.Yellow;
artistColour = colours.Gray9;
- var metadata = BeatmapSetInfo.Metadata;
- FilterTerms = metadata.SearchableTerms;
-
- Children = new Drawable[]
+ InternalChild = new GridContainer
{
- handle = new PlaylistItemHandle
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Content = new[]
{
- Colour = colours.Gray5
- },
- text = new OsuTextFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Padding = new MarginPadding { Left = 20 },
- ContentIndent = 10f,
+ new Drawable[]
+ {
+ handle = new PlaylistItemHandle
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Size = new Vector2(12),
+ Colour = colours.Gray5,
+ AlwaysPresent = true,
+ Alpha = 0
+ },
+ text = new OsuTextFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Padding = new MarginPadding { Left = 5 },
+ },
+ }
},
+ ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
+ RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
};
- titleBind = localisation.GetLocalisedString(new LocalisedString((metadata.TitleUnicode, metadata.Title)));
- artistBind = localisation.GetLocalisedString(new LocalisedString((metadata.ArtistUnicode, metadata.Artist)));
+ titleBind = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.TitleUnicode, Model.Metadata.Title)));
+ artistBind = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.ArtistUnicode, Model.Metadata.Artist)));
artistBind.BindValueChanged(_ => recreateText(), true);
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ SelectedSet.BindValueChanged(set =>
+ {
+ if (set.OldValue != Model && set.NewValue != Model)
+ return;
+
+ foreach (Drawable s in titleSprites)
+ s.FadeColour(set.NewValue == Model ? hoverColour : Color4.White, fade_duration);
+ }, true);
+ }
+
private void recreateText()
{
text.Clear();
@@ -120,25 +119,38 @@ namespace osu.Game.Overlays.Music
});
}
- protected override bool OnHover(HoverEvent e)
- {
- handle.FadeIn(fade_duration);
-
- return base.OnHover(e);
- }
-
- protected override void OnHoverLost(HoverLostEvent e)
- {
- handle.FadeOut(fade_duration);
- }
-
protected override bool OnClick(ClickEvent e)
{
- OnSelect?.Invoke(BeatmapSetInfo);
+ RequestSelection?.Invoke(Model);
return true;
}
- public IEnumerable FilterTerms { get; private set; }
+ protected override bool OnDragStart(DragStartEvent e)
+ {
+ if (!base.OnDragStart(e))
+ return false;
+
+ PlaylistDragActive.Value = true;
+ return true;
+ }
+
+ protected override void OnDragEnd(DragEndEvent e)
+ {
+ PlaylistDragActive.Value = false;
+ base.OnDragEnd(e);
+ }
+
+ protected override bool IsDraggableAt(Vector2 screenSpacePos) => handle.HandlingDrag;
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ handle.UpdateHoverState(IsDragged || !PlaylistDragActive.Value);
+ return base.OnHover(e);
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e) => handle.UpdateHoverState(false);
+
+ public IEnumerable FilterTerms { get; }
private bool matching = true;
@@ -159,25 +171,41 @@ namespace osu.Game.Overlays.Music
private class PlaylistItemHandle : SpriteIcon
{
+ public bool HandlingDrag { get; private set; }
+ private bool isHovering;
+
public PlaylistItemHandle()
{
- Anchor = Anchor.CentreLeft;
- Origin = Anchor.CentreLeft;
- Size = new Vector2(12);
Icon = FontAwesome.Solid.Bars;
- Alpha = 0f;
- Margin = new MarginPadding { Left = 5 };
}
- public override bool HandlePositionalInput => IsPresent;
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ base.OnMouseDown(e);
+
+ HandlingDrag = true;
+ UpdateHoverState(isHovering);
+
+ return false;
+ }
+
+ protected override void OnMouseUp(MouseUpEvent e)
+ {
+ base.OnMouseUp(e);
+
+ HandlingDrag = false;
+ UpdateHoverState(isHovering);
+ }
+
+ public void UpdateHoverState(bool hovering)
+ {
+ isHovering = hovering;
+
+ if (isHovering || HandlingDrag)
+ this.FadeIn(fade_duration);
+ else
+ this.FadeOut(fade_duration);
+ }
}
}
-
- public interface IDraggable : IDrawable
- {
- ///
- /// Whether this can be dragged in its current state.
- ///
- bool IsDraggable { get; }
- }
}
diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs
deleted file mode 100644
index 7bdcab6dff..0000000000
--- a/osu.Game/Overlays/Music/PlaylistList.cs
+++ /dev/null
@@ -1,268 +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 osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osu.Framework.Extensions.IEnumerableExtensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Input.Events;
-using osu.Game.Beatmaps;
-using osu.Game.Graphics.Containers;
-using osuTK;
-
-namespace osu.Game.Overlays.Music
-{
- public class PlaylistList : CompositeDrawable
- {
- public Action Selected;
-
- private readonly ItemsScrollContainer items;
-
- public PlaylistList()
- {
- InternalChild = items = new ItemsScrollContainer
- {
- RelativeSizeAxes = Axes.Both,
- Selected = set => Selected?.Invoke(set),
- };
- }
-
- public new MarginPadding Padding
- {
- get => base.Padding;
- set => base.Padding = value;
- }
-
- public BeatmapSetInfo FirstVisibleSet => items.FirstVisibleSet;
-
- public void Filter(string searchTerm) => items.SearchTerm = searchTerm;
-
- private class ItemsScrollContainer : OsuScrollContainer
- {
- public Action Selected;
-
- private readonly SearchContainer search;
- private readonly FillFlowContainer items;
-
- private readonly IBindable beatmapBacking = new Bindable();
-
- private IBindableList beatmaps;
-
- [Resolved]
- private MusicController musicController { get; set; }
-
- public ItemsScrollContainer()
- {
- Children = new Drawable[]
- {
- search = new SearchContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Children = new Drawable[]
- {
- items = new ItemSearchContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- },
- }
- }
- };
- }
-
- [BackgroundDependencyLoader]
- private void load(IBindable beatmap)
- {
- beatmaps = musicController.BeatmapSets.GetBoundCopy();
- beatmaps.ItemsAdded += i => i.ForEach(addBeatmapSet);
- beatmaps.ItemsRemoved += i => i.ForEach(removeBeatmapSet);
- beatmaps.ForEach(addBeatmapSet);
-
- beatmapBacking.BindTo(beatmap);
- beatmapBacking.ValueChanged += _ => Scheduler.AddOnce(updateSelectedSet);
- }
-
- private void addBeatmapSet(BeatmapSetInfo obj)
- {
- if (obj == draggedItem?.BeatmapSetInfo) return;
-
- Schedule(() => items.Insert(items.Count - 1, new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) }));
- }
-
- private void removeBeatmapSet(BeatmapSetInfo obj)
- {
- if (obj == draggedItem?.BeatmapSetInfo) return;
-
- Schedule(() =>
- {
- var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == obj.ID);
- if (itemToRemove != null)
- items.Remove(itemToRemove);
- });
- }
-
- private void updateSelectedSet()
- {
- foreach (PlaylistItem s in items.Children)
- {
- s.Selected = s.BeatmapSetInfo.ID == beatmapBacking.Value.BeatmapSetInfo?.ID;
- if (s.Selected)
- ScrollIntoView(s);
- }
- }
-
- public string SearchTerm
- {
- get => search.SearchTerm;
- set => search.SearchTerm = value;
- }
-
- public BeatmapSetInfo FirstVisibleSet => items.FirstOrDefault(i => i.MatchingFilter)?.BeatmapSetInfo;
-
- private Vector2 nativeDragPosition;
- private PlaylistItem draggedItem;
-
- private int? dragDestination;
-
- protected override bool OnDragStart(DragStartEvent e)
- {
- nativeDragPosition = e.ScreenSpaceMousePosition;
- draggedItem = items.FirstOrDefault(d => d.IsDraggable);
- return draggedItem != null || base.OnDragStart(e);
- }
-
- protected override void OnDrag(DragEvent e)
- {
- nativeDragPosition = e.ScreenSpaceMousePosition;
-
- if (draggedItem == null)
- base.OnDrag(e);
- }
-
- protected override void OnDragEnd(DragEndEvent e)
- {
- nativeDragPosition = e.ScreenSpaceMousePosition;
-
- if (draggedItem == null)
- {
- base.OnDragEnd(e);
- return;
- }
-
- if (dragDestination != null)
- musicController.ChangeBeatmapSetPosition(draggedItem.BeatmapSetInfo, dragDestination.Value);
-
- draggedItem = null;
- dragDestination = null;
- }
-
- protected override void Update()
- {
- base.Update();
-
- if (draggedItem == null)
- return;
-
- updateScrollPosition();
- updateDragPosition();
- }
-
- private void updateScrollPosition()
- {
- const float start_offset = 10;
- const double max_power = 50;
- const double exp_base = 1.05;
-
- var localPos = ToLocalSpace(nativeDragPosition);
-
- if (localPos.Y < start_offset)
- {
- if (Current <= 0)
- return;
-
- var power = Math.Min(max_power, Math.Abs(start_offset - localPos.Y));
- ScrollBy(-(float)Math.Pow(exp_base, power));
- }
- else if (localPos.Y > DrawHeight - start_offset)
- {
- if (IsScrolledToEnd())
- return;
-
- var power = Math.Min(max_power, Math.Abs(DrawHeight - start_offset - localPos.Y));
- ScrollBy((float)Math.Pow(exp_base, power));
- }
- }
-
- private void updateDragPosition()
- {
- var itemsPos = items.ToLocalSpace(nativeDragPosition);
-
- int srcIndex = (int)items.GetLayoutPosition(draggedItem);
-
- // Find the last item with position < mouse position. Note we can't directly use
- // the item positions as they are being transformed
- float heightAccumulator = 0;
- int dstIndex = 0;
-
- for (; dstIndex < items.Count; dstIndex++)
- {
- // Using BoundingBox here takes care of scale, paddings, etc...
- heightAccumulator += items[dstIndex].BoundingBox.Height;
- if (heightAccumulator > itemsPos.Y)
- break;
- }
-
- dstIndex = Math.Clamp(dstIndex, 0, items.Count - 1);
-
- if (srcIndex == dstIndex)
- return;
-
- if (srcIndex < dstIndex)
- {
- for (int i = srcIndex + 1; i <= dstIndex; i++)
- items.SetLayoutPosition(items[i], i - 1);
- }
- else
- {
- for (int i = dstIndex; i < srcIndex; i++)
- items.SetLayoutPosition(items[i], i + 1);
- }
-
- items.SetLayoutPosition(draggedItem, dstIndex);
- dragDestination = dstIndex;
- }
-
- private class ItemSearchContainer : FillFlowContainer, IHasFilterableChildren
- {
- public IEnumerable FilterTerms => Array.Empty();
-
- public bool MatchingFilter
- {
- set
- {
- if (value)
- InvalidateLayout();
- }
- }
-
- public bool FilteringActive
- {
- set { }
- }
-
- public IEnumerable FilterableChildren => Children;
-
- public ItemSearchContainer()
- {
- LayoutDuration = 200;
- LayoutEasing = Easing.OutQuint;
- }
- }
- }
- }
-}
diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs
index b89a577282..7c391e27f9 100644
--- a/osu.Game/Overlays/Music/PlaylistOverlay.cs
+++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs
@@ -21,11 +21,15 @@ namespace osu.Game.Overlays.Music
private const float transition_duration = 600;
private const float playlist_height = 510;
+ public IBindableList BeatmapSets => beatmapSets;
+
+ private readonly BindableList beatmapSets = new BindableList();
+
private readonly Bindable beatmap = new Bindable();
private BeatmapManager beatmaps;
private FilterControl filter;
- private PlaylistList list;
+ private Playlist list;
[BackgroundDependencyLoader]
private void load(OsuColour colours, Bindable beatmap, BeatmapManager beatmaps)
@@ -53,11 +57,11 @@ namespace osu.Game.Overlays.Music
Colour = colours.Gray3,
RelativeSizeAxes = Axes.Both,
},
- list = new PlaylistList
+ list = new Playlist
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 },
- Selected = itemSelected,
+ RequestSelection = itemSelected
},
filter = new FilterControl
{
@@ -70,6 +74,8 @@ namespace osu.Game.Overlays.Music
},
};
+ list.Items.BindTo(beatmapSets);
+
filter.Search.OnCommit = (sender, newText) =>
{
BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault();
@@ -80,6 +86,8 @@ namespace osu.Game.Overlays.Music
beatmap.Value.Track.Restart();
}
};
+
+ beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo, true);
}
protected override void PopIn()
diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs
index 042e95c6d7..dfcf99d30c 100644
--- a/osu.Game/Overlays/NowPlayingOverlay.cs
+++ b/osu.Game/Overlays/NowPlayingOverlay.cs
@@ -183,6 +183,7 @@ namespace osu.Game.Overlays
}
};
+ playlist.BeatmapSets.BindTo(musicController.BeatmapSets);
playlist.State.ValueChanged += s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint);
}
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index 5ac1d4ecc4..6c81d70190 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Framework.Logging;
-using osu.Framework.Threading;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@@ -50,8 +49,6 @@ namespace osu.Game.Rulesets.Edit
[Resolved]
private IBeatSnapProvider beatSnapProvider { get; set; }
- private IBeatmapProcessor beatmapProcessor;
-
private DrawableEditRulesetWrapper drawableRulesetWrapper;
private ComposeBlueprintContainer blueprintContainer;
private Container distanceSnapGridContainer;
@@ -71,8 +68,6 @@ namespace osu.Game.Rulesets.Edit
[BackgroundDependencyLoader]
private void load(IFrameBasedClock framedClock)
{
- beatmapProcessor = Ruleset.CreateBeatmapProcessor(EditorBeatmap.PlayableBeatmap);
-
EditorBeatmap.HitObjectAdded += addHitObject;
EditorBeatmap.HitObjectRemoved += removeHitObject;
EditorBeatmap.StartTimeChanged += UpdateHitObject;
@@ -179,7 +174,7 @@ namespace osu.Game.Rulesets.Edit
{
base.Update();
- if (EditorClock.CurrentTime != lastGridUpdateTime && blueprintContainer.CurrentTool != null)
+ if (EditorClock.CurrentTime != lastGridUpdateTime && !(blueprintContainer.CurrentTool is SelectTool))
showGridFor(Enumerable.Empty());
}
@@ -240,19 +235,6 @@ namespace osu.Game.Rulesets.Edit
lastGridUpdateTime = EditorClock.CurrentTime;
}
- private ScheduledDelegate scheduledUpdate;
-
- public override void UpdateHitObject(HitObject hitObject)
- {
- scheduledUpdate?.Cancel();
- scheduledUpdate = Schedule(() =>
- {
- beatmapProcessor?.PreProcess();
- hitObject?.ApplyDefaults(EditorBeatmap.ControlPointInfo, EditorBeatmap.BeatmapInfo.BaseDifficulty);
- beatmapProcessor?.PostProcess();
- });
- }
-
private void addHitObject(HitObject hitObject) => UpdateHitObject(hitObject);
private void removeHitObject(HitObject hitObject) => UpdateHitObject(null);
@@ -309,6 +291,8 @@ namespace osu.Game.Rulesets.Edit
public override float GetSnappedDistanceFromDistance(double referenceTime, float distance)
=> DurationToDistance(referenceTime, beatSnapProvider.SnapTime(DistanceToDuration(referenceTime, distance), referenceTime));
+ public override void UpdateHitObject(HitObject hitObject) => EditorBeatmap.UpdateHitObject(hitObject);
+
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
@@ -344,7 +328,7 @@ namespace osu.Game.Rulesets.Edit
/// Creates the applicable for a selection.
///
/// The selection.
- /// The for .
+ /// The for . If empty, a grid is returned for the current point in time.
[CanBeNull]
protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable selectedHitObjects) => null;
diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs
index 8d523022d6..53cdf457c4 100644
--- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs
@@ -26,7 +26,12 @@ namespace osu.Game.Rulesets.Objects.Legacy
public List> NodeSamples { get; set; }
public int RepeatCount { get; set; }
- public double EndTime => StartTime + this.SpanCount() * Distance / Velocity;
+ public double EndTime
+ {
+ get => StartTime + this.SpanCount() * Distance / Velocity;
+ set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
+ }
+
public double Duration => EndTime - StartTime;
public double Velocity = 1;
diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs
index 516f1002a4..bc7103c60d 100644
--- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs
+++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs
@@ -1,6 +1,8 @@
// 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;
+
namespace osu.Game.Rulesets.Objects.Types
{
///
@@ -11,7 +13,8 @@ namespace osu.Game.Rulesets.Objects.Types
///
/// The time at which the HitObject ends.
///
- double EndTime { get; }
+ [JsonIgnore]
+ double EndTime { get; set; }
///
/// The duration of the HitObject.
diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs
index b22752e902..256b1f3963 100644
--- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs
+++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Objects.Types
///
/// The amount of times the HitObject repeats.
///
- int RepeatCount { get; }
+ int RepeatCount { get; set; }
///
/// The samples to be played when each node of the is hit.
diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs
index 75ea3efdf2..0d4283e319 100644
--- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs
@@ -5,7 +5,11 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
{
public class ConstantScrollAlgorithm : IScrollAlgorithm
{
- public double GetDisplayStartTime(double time, double timeRange) => time - timeRange;
+ public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
+ {
+ var adjustedTime = TimeAt(-offset, originTime, timeRange, scrollLength);
+ return adjustedTime - timeRange;
+ }
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
{
diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs
index 5f053975c7..c394a05bcc 100644
--- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs
@@ -6,15 +6,33 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
public interface IScrollAlgorithm
{
///
- /// Given a point in time, computes the time at which it enters the time range.
+ /// Given a point in time associated with an object's origin
+ /// and the spatial distance between the edge and the origin of the object along the scrolling axis,
+ /// computes the time at which the object initially enters the time range.
///
- ///
- /// E.g. For a constant time range of 5000ms, the time at which t=7000ms enters the time range is 2000ms.
- ///
- /// The point in time.
+ ///
+ /// Let's assume the following parameters:
+ ///
+ /// - = 7000ms,
+ /// - = 100px,
+ /// - = 5000ms,
+ /// - = 1000px
+ ///
+ /// and a constant scrolling rate.
+ /// To arrive at the end of the scrolling container, the object's origin has to cover
+ /// 1000 + 100 = 1100px
+ /// so that the edge starts at the end of the scrolling container.
+ /// One scroll length of 1000px covers 5000ms of time, so the time required to cover 1100px is equal to
+ /// 5000 * (1100 / 1000) = 5500ms,
+ /// and therefore the object should start being visible at
+ /// 7000 - 5500 = 1500ms.
+ ///
+ /// The time point at which the object origin should enter the time range.
+ /// The spatial distance between the object's edge and its origin along the scrolling axis.
/// The amount of visible time.
- /// The time at which enters .
- double GetDisplayStartTime(double time, double timeRange);
+ /// The absolute spatial length through .
+ /// The time at which the object should enter the time range.
+ double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength);
///
/// Computes the spatial length within a start and end time.
diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs
index fe22a86fad..7b827e0c63 100644
--- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs
@@ -20,11 +20,12 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
searchPoint = new MultiplierControlPoint();
}
- public double GetDisplayStartTime(double time, double timeRange)
+ public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
{
+ var controlPoint = controlPointAt(originTime);
// The total amount of time that the hitobject will remain visible within the timeRange, which decreases as the speed multiplier increases
- double visibleDuration = timeRange / controlPointAt(time).Multiplier;
- return time - visibleDuration;
+ double visibleDuration = (scrollLength + offset) * timeRange / controlPoint.Multiplier / scrollLength;
+ return originTime - visibleDuration;
}
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs
index 3c9a205412..41f9ebdb82 100644
--- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs
@@ -20,7 +20,11 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
positionCache = new Dictionary();
}
- public double GetDisplayStartTime(double time, double timeRange) => time - timeRange - 1000;
+ public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
+ {
+ double adjustedTime = TimeAt(-offset, originTime, timeRange, scrollLength);
+ return adjustedTime - timeRange - 1000;
+ }
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
{
diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
index 04b4374fc4..83a7f7289f 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
@@ -133,8 +133,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
break;
}
- var adjustedStartTime = scrollingInfo.Algorithm.TimeAt(-originAdjustment, hitObject.HitObject.StartTime, timeRange.Value, scrollLength);
- return scrollingInfo.Algorithm.GetDisplayStartTime(adjustedStartTime, timeRange.Value);
+ return scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength);
}
// Cant use AddOnce() since the delegate is re-constructed every invocation
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
index a33040f400..0475e68e42 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
@@ -179,11 +179,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
[Resolved]
private IBeatSnapProvider beatSnapProvider { get; set; }
- public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time)
- {
- var targetTime = (position.X / Content.DrawWidth) * track.Length;
- return (position, beatSnapProvider.SnapTime(targetTime));
- }
+ public double GetTimeFromScreenSpacePosition(Vector2 position)
+ => getTimeFromPosition(Content.ToLocalSpace(position));
+
+ public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) =>
+ (position, beatSnapProvider.SnapTime(getTimeFromPosition(position)));
+
+ private double getTimeFromPosition(Vector2 localPosition) =>
+ (localPosition.X / Content.DrawWidth) * track.Length;
public float GetBeatSnapDistanceAt(double referenceTime) => throw new NotImplementedException();
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs
index 3b9cb1df24..9f3d776e5c 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs
@@ -49,20 +49,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
protected override void OnDrag(DragEvent e)
{
- if (timeline != null)
- {
- var timelineQuad = timeline.ScreenSpaceDrawQuad;
- var mouseX = e.ScreenSpaceMousePosition.X;
-
- // scroll if in a drag and dragging outside visible extents
- if (mouseX > timelineQuad.TopRight.X)
- timeline.ScrollBy((float)((mouseX - timelineQuad.TopRight.X) / 10 * Clock.ElapsedFrameTime));
- else if (mouseX < timelineQuad.TopLeft.X)
- timeline.ScrollBy((float)((mouseX - timelineQuad.TopLeft.X) / 10 * Clock.ElapsedFrameTime));
- }
+ handleScrollViaDrag(e);
base.OnDrag(e);
- lastDragEvent = e;
}
protected override void OnDragEnd(DragEndEvent e)
@@ -74,7 +63,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
protected override void Update()
{
// trigger every frame so drags continue to update selection while playback is scrolling the timeline.
- if (IsDragged)
+ if (lastDragEvent != null)
OnDrag(lastDragEvent);
base.Update();
@@ -82,10 +71,33 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
protected override SelectionHandler CreateSelectionHandler() => new TimelineSelectionHandler();
- protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => new TimelineHitObjectBlueprint(hitObject);
+ protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => new TimelineHitObjectBlueprint(hitObject)
+ {
+ OnDragHandled = handleScrollViaDrag
+ };
protected override DragBox CreateDragBox(Action performSelect) => new TimelineDragBox(performSelect);
+ private void handleScrollViaDrag(DragEvent e)
+ {
+ lastDragEvent = e;
+
+ if (lastDragEvent == null)
+ return;
+
+ if (timeline != null)
+ {
+ var timelineQuad = timeline.ScreenSpaceDrawQuad;
+ var mouseX = e.ScreenSpaceMousePosition.X;
+
+ // scroll if in a drag and dragging outside visible extents
+ if (mouseX > timelineQuad.TopRight.X)
+ timeline.ScrollBy((float)((mouseX - timelineQuad.TopRight.X) / 10 * Clock.ElapsedFrameTime));
+ else if (mouseX < timelineQuad.TopLeft.X)
+ timeline.ScrollBy((float)((mouseX - timelineQuad.TopLeft.X) / 10 * Clock.ElapsedFrameTime));
+ }
+ }
+
internal class TimelineSelectionHandler : SelectionHandler
{
// for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs
index 2ed5471444..8f12c2f0ed 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs
@@ -1,12 +1,18 @@
// 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 JetBrains.Annotations;
+using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Events;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -19,17 +25,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
private readonly Circle circle;
- private readonly Container extensionBar;
-
[UsedImplicitly]
private readonly Bindable startTime;
- public const float THICKNESS = 3;
+ public Action OnDragHandled;
+
+ private readonly DragBar dragBar;
+
+ private readonly List shadowComponents = new List();
+
+ private const float thickness = 5;
+
+ private const float shadow_radius = 5;
private const float circle_size = 16;
- public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || circle.ReceivePositionalInputAt(screenSpacePos);
-
public TimelineHitObjectBlueprint(HitObject hitObject)
: base(hitObject)
{
@@ -44,26 +54,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
- if (hitObject is IHasEndTime)
- {
- AddInternal(extensionBar = new Container
- {
- CornerRadius = 2,
- Masking = true,
- Size = new Vector2(1, THICKNESS),
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- RelativePositionAxes = Axes.X,
- RelativeSizeAxes = Axes.X,
- Colour = Color4.Black,
- Child = new Box
- {
- RelativeSizeAxes = Axes.Both,
- }
- });
- }
-
- AddInternal(circle = new Circle
+ circle = new Circle
{
Size = new Vector2(circle_size),
Anchor = Anchor.CentreLeft,
@@ -71,9 +62,65 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
RelativePositionAxes = Axes.X,
AlwaysPresent = true,
Colour = Color4.White,
- BorderColour = Color4.Black,
- BorderThickness = THICKNESS,
- });
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Radius = shadow_radius,
+ Colour = Color4.Black
+ },
+ };
+
+ shadowComponents.Add(circle);
+
+ if (hitObject is IHasEndTime)
+ {
+ DragBar dragBarUnderlay;
+ Container extensionBar;
+
+ AddRangeInternal(new Drawable[]
+ {
+ extensionBar = new Container
+ {
+ Masking = true,
+ Size = new Vector2(1, thickness),
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ RelativePositionAxes = Axes.X,
+ RelativeSizeAxes = Axes.X,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Radius = shadow_radius,
+ Colour = Color4.Black
+ },
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ },
+ circle,
+ // only used for drawing the shadow
+ dragBarUnderlay = new DragBar(null),
+ // cover up the shadow on the join
+ new Box
+ {
+ Height = thickness,
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ RelativeSizeAxes = Axes.X,
+ },
+ dragBar = new DragBar(hitObject) { OnDragHandled = e => OnDragHandled?.Invoke(e) },
+ });
+
+ shadowComponents.Add(dragBarUnderlay);
+ shadowComponents.Add(extensionBar);
+ }
+ else
+ {
+ AddInternal(circle);
+ }
+
+ updateShadows();
}
protected override void Update()
@@ -84,18 +131,46 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
Width = (float)(HitObject.GetEndTime() - HitObject.StartTime);
}
+ protected override bool ShouldBeConsideredForInput(Drawable child) => true;
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
+ base.ReceivePositionalInputAt(screenSpacePos) ||
+ circle.ReceivePositionalInputAt(screenSpacePos) ||
+ dragBar?.ReceivePositionalInputAt(screenSpacePos) == true;
+
protected override void OnSelected()
{
- circle.BorderColour = Color4.Orange;
- if (extensionBar != null)
- extensionBar.Colour = Color4.Orange;
+ updateShadows();
+ }
+
+ private void updateShadows()
+ {
+ foreach (var s in shadowComponents)
+ {
+ if (State == SelectionState.Selected)
+ {
+ s.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Radius = shadow_radius / 2,
+ Colour = Color4.Orange,
+ };
+ }
+ else
+ {
+ s.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Radius = shadow_radius,
+ Colour = State == SelectionState.Selected ? Color4.Orange : Color4.Black
+ };
+ }
+ }
}
protected override void OnDeselected()
{
- circle.BorderColour = Color4.Black;
- if (extensionBar != null)
- extensionBar.Colour = Color4.Black;
+ updateShadows();
}
public override Quad SelectionQuad
@@ -103,14 +178,130 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
get
{
// correctly include the circle in the selection quad region, as it is usually outside the blueprint itself.
- var circleQuad = circle.ScreenSpaceDrawQuad;
- var actualQuad = ScreenSpaceDrawQuad;
+ var leftQuad = circle.ScreenSpaceDrawQuad;
+ var rightQuad = dragBar?.ScreenSpaceDrawQuad ?? ScreenSpaceDrawQuad;
- return new Quad(circleQuad.TopLeft, Vector2.ComponentMax(actualQuad.TopRight, circleQuad.TopRight),
- circleQuad.BottomLeft, Vector2.ComponentMax(actualQuad.BottomRight, circleQuad.BottomRight));
+ return new Quad(leftQuad.TopLeft, Vector2.ComponentMax(rightQuad.TopRight, leftQuad.TopRight),
+ leftQuad.BottomLeft, Vector2.ComponentMax(rightQuad.BottomRight, leftQuad.BottomRight));
}
}
public override Vector2 SelectionPoint => ScreenSpaceDrawQuad.TopLeft;
+
+ public class DragBar : Container
+ {
+ private readonly HitObject hitObject;
+
+ [Resolved]
+ private Timeline timeline { get; set; }
+
+ public Action OnDragHandled;
+
+ public override bool HandlePositionalInput => hitObject != null;
+
+ public DragBar(HitObject hitObject)
+ {
+ this.hitObject = hitObject;
+
+ CornerRadius = 2;
+ Masking = true;
+ Size = new Vector2(5, 1);
+ Anchor = Anchor.CentreRight;
+ Origin = Anchor.Centre;
+ RelativePositionAxes = Axes.X;
+ RelativeSizeAxes = Axes.Y;
+
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ };
+ }
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ updateState();
+
+ return base.OnHover(e);
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ updateState();
+ base.OnHoverLost(e);
+ }
+
+ private bool hasMouseDown;
+
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ hasMouseDown = true;
+ updateState();
+ return true;
+ }
+
+ protected override void OnMouseUp(MouseUpEvent e)
+ {
+ hasMouseDown = false;
+ updateState();
+ base.OnMouseUp(e);
+ }
+
+ private void updateState()
+ {
+ Colour = IsHovered || hasMouseDown ? Color4.OrangeRed : Color4.White;
+ }
+
+ protected override bool OnDragStart(DragStartEvent e) => true;
+
+ [Resolved]
+ private EditorBeatmap beatmap { get; set; }
+
+ [Resolved]
+ private IBeatSnapProvider beatSnapProvider { get; set; }
+
+ protected override void OnDrag(DragEvent e)
+ {
+ base.OnDrag(e);
+
+ OnDragHandled?.Invoke(e);
+
+ var time = timeline.GetTimeFromScreenSpacePosition(e.ScreenSpaceMousePosition);
+
+ switch (hitObject)
+ {
+ case IHasRepeats repeatHitObject:
+ // find the number of repeats which can fit in the requested time.
+ var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1);
+ var proposedCount = Math.Max(0, (int)((time - hitObject.StartTime) / lengthOfOneRepeat) - 1);
+
+ if (proposedCount == repeatHitObject.RepeatCount)
+ return;
+
+ repeatHitObject.RepeatCount = proposedCount;
+ break;
+
+ case IHasEndTime endTimeHitObject:
+ var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time));
+
+ if (endTimeHitObject.EndTime == snappedTime)
+ return;
+
+ endTimeHitObject.EndTime = snappedTime;
+ break;
+ }
+
+ beatmap.UpdateHitObject(hitObject);
+ }
+
+ protected override void OnDragEnd(DragEndEvent e)
+ {
+ base.OnDragEnd(e);
+
+ OnDragHandled?.Invoke(null);
+ }
+ }
}
}
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 8c7270d3a2..3bec4b8f6f 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -80,15 +80,15 @@ namespace osu.Game.Screens.Edit
clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false };
clock.ChangeSource(sourceClock);
- playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
- editorBeatmap = new EditorBeatmap(playableBeatmap, beatDivisor);
-
dependencies.CacheAs(clock);
dependencies.CacheAs(clock);
// todo: remove caching of this and consume via editorBeatmap?
dependencies.Cache(beatDivisor);
+ playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
+ AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap));
+
dependencies.CacheAs(editorBeatmap);
EditorMenuBar menuBar;
@@ -104,7 +104,7 @@ namespace osu.Game.Screens.Edit
fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit));
- InternalChild = new OsuContextMenuContainer
+ AddInternal(new OsuContextMenuContainer
{
RelativeSizeAxes = Axes.Both,
Children = new[]
@@ -189,7 +189,7 @@ namespace osu.Game.Screens.Edit
}
},
}
- };
+ });
menuBar.Mode.ValueChanged += onModeChanged;
diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs
index 6edd62fa67..cacb539891 100644
--- a/osu.Game/Screens/Edit/EditorBeatmap.cs
+++ b/osu.Game/Screens/Edit/EditorBeatmap.cs
@@ -4,7 +4,10 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Timing;
@@ -13,7 +16,7 @@ using osu.Game.Rulesets.Objects;
namespace osu.Game.Screens.Edit
{
- public class EditorBeatmap : IBeatmap, IBeatSnapProvider
+ public class EditorBeatmap : Component, IBeatmap, IBeatSnapProvider
{
///
/// Invoked when a is added to this .
@@ -34,19 +37,40 @@ namespace osu.Game.Screens.Edit
public readonly IBeatmap PlayableBeatmap;
- private readonly BindableBeatDivisor beatDivisor;
+ [Resolved]
+ private BindableBeatDivisor beatDivisor { get; set; }
+
+ private readonly IBeatmapProcessor beatmapProcessor;
private readonly Dictionary> startTimeBindables = new Dictionary>();
- public EditorBeatmap(IBeatmap playableBeatmap, BindableBeatDivisor beatDivisor = null)
+ public EditorBeatmap(IBeatmap playableBeatmap)
{
PlayableBeatmap = playableBeatmap;
- this.beatDivisor = beatDivisor;
+
+ beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset?.CreateInstance().CreateBeatmapProcessor(PlayableBeatmap);
foreach (var obj in HitObjects)
trackStartTime(obj);
}
+ private ScheduledDelegate scheduledUpdate;
+
+ ///
+ /// Updates a , invoking and re-processing the beatmap.
+ ///
+ /// The to update.
+ public void UpdateHitObject(HitObject hitObject)
+ {
+ scheduledUpdate?.Cancel();
+ scheduledUpdate = Scheduler.AddDelayed(() =>
+ {
+ beatmapProcessor?.PreProcess();
+ hitObject?.ApplyDefaults(ControlPointInfo, BeatmapInfo.BaseDifficulty);
+ beatmapProcessor?.PostProcess();
+ }, 0);
+ }
+
public BeatmapInfo BeatmapInfo
{
get => PlayableBeatmap.BeatmapInfo;
diff --git a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs
index f6cbe300f3..d45dac1ae6 100644
--- a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs
+++ b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs
@@ -75,8 +75,13 @@ namespace osu.Game.Screens.Multi.Lounge.Components
{
matchingFilter = value;
- if (IsLoaded)
- this.FadeTo(MatchingFilter ? 1 : 0, 200);
+ if (!IsLoaded)
+ return;
+
+ if (matchingFilter)
+ this.FadeIn(200);
+ else
+ Hide();
}
}
diff --git a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs
index 29d41132a7..9f93afec9d 100644
--- a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs
+++ b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs
@@ -7,6 +7,7 @@ using osu.Framework.Bindables;
using osu.Framework.Threading;
using osu.Game.Graphics;
using osu.Game.Overlays.SearchableList;
+using osu.Game.Rulesets;
using osuTK.Graphics;
namespace osu.Game.Screens.Multi.Lounge.Components
@@ -22,6 +23,9 @@ namespace osu.Game.Screens.Multi.Lounge.Components
[Resolved(CanBeNull = true)]
private Bindable filter { get; set; }
+ [Resolved]
+ private IBindable ruleset { get; set; }
+
public FilterControl()
{
DisplayStyleControl.Hide();
@@ -38,6 +42,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components
{
base.LoadComplete();
+ ruleset.BindValueChanged(_ => updateFilter());
Search.Current.BindValueChanged(_ => scheduleUpdateFilter());
Tabs.Current.BindValueChanged(_ => updateFilter(), true);
}
@@ -58,7 +63,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components
{
SearchString = Search.Current.Value ?? string.Empty,
PrimaryFilter = Tabs.Current.Value,
- SecondaryFilter = DisplayStyleControl.Dropdown.Current.Value
+ SecondaryFilter = DisplayStyleControl.Dropdown.Current.Value,
+ Ruleset = ruleset.Value
};
}
}
diff --git a/osu.Game/Screens/Multi/Lounge/Components/FilterCriteria.cs b/osu.Game/Screens/Multi/Lounge/Components/FilterCriteria.cs
index 666bc44a8d..26d445e151 100644
--- a/osu.Game/Screens/Multi/Lounge/Components/FilterCriteria.cs
+++ b/osu.Game/Screens/Multi/Lounge/Components/FilterCriteria.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Rulesets;
+
namespace osu.Game.Screens.Multi.Lounge.Components
{
public class FilterCriteria
@@ -8,5 +10,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components
public string SearchString;
public PrimaryFilter PrimaryFilter;
public SecondaryFilter SecondaryFilter;
+ public RulesetInfo Ruleset;
}
}
diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs
index 607b081653..063957d816 100644
--- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs
+++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs
@@ -47,22 +47,15 @@ namespace osu.Game.Screens.Multi.Lounge.Components
};
}
- [BackgroundDependencyLoader]
- private void load()
- {
- rooms.BindTo(roomManager.Rooms);
-
- rooms.ItemsAdded += addRooms;
- rooms.ItemsRemoved += removeRooms;
-
- roomManager.RoomsUpdated += updateSorting;
-
- addRooms(rooms);
- }
-
protected override void LoadComplete()
{
- filter?.BindValueChanged(f => Filter(f.NewValue), true);
+ rooms.ItemsAdded += addRooms;
+ rooms.ItemsRemoved += removeRooms;
+ roomManager.RoomsUpdated += updateSorting;
+
+ rooms.BindTo(roomManager.Rooms);
+
+ filter?.BindValueChanged(criteria => Filter(criteria.NewValue));
}
public void Filter(FilterCriteria criteria)
@@ -74,7 +67,11 @@ namespace osu.Game.Screens.Multi.Lounge.Components
else
{
bool matchingFilter = true;
- matchingFilter &= r.FilterTerms.Any(term => term.IndexOf(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase) >= 0);
+
+ matchingFilter &= r.Room.Playlist.Count == 0 || r.Room.Playlist.Any(i => i.Ruleset.Equals(criteria.Ruleset));
+
+ if (!string.IsNullOrEmpty(criteria.SearchString))
+ matchingFilter &= r.FilterTerms.Any(term => term.IndexOf(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase) >= 0);
switch (criteria.SecondaryFilter)
{
@@ -94,8 +91,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components
foreach (var r in rooms)
roomFlow.Add(new DrawableRoom(r) { Action = () => selectRoom(r) });
- if (filter != null)
- Filter(filter.Value);
+ Filter(filter?.Value);
}
private void removeRooms(IEnumerable rooms)
diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs
index 0a48f761cf..3709b85fcb 100644
--- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs
+++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs
@@ -91,6 +91,22 @@ namespace osu.Game.Screens.Multi.Lounge
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
+
+ onReturning();
+ }
+
+ public override void OnResuming(IScreen last)
+ {
+ base.OnResuming(last);
+
+ if (currentRoom.Value?.RoomID.Value == null)
+ currentRoom.Value = new Room();
+
+ onReturning();
+ }
+
+ private void onReturning()
+ {
Filter.Search.HoldFocus = true;
}
@@ -106,14 +122,6 @@ namespace osu.Game.Screens.Multi.Lounge
Filter.Search.HoldFocus = false;
}
- public override void OnResuming(IScreen last)
- {
- base.OnResuming(last);
-
- if (currentRoom.Value?.RoomID.Value == null)
- currentRoom.Value = new Room();
- }
-
private void joinRequested(Room room)
{
processingOverlay.Show();
diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs
index 9d6a459d14..2277157134 100644
--- a/osu.Game/Screens/Multi/Multiplayer.cs
+++ b/osu.Game/Screens/Multi/Multiplayer.cs
@@ -32,6 +32,8 @@ namespace osu.Game.Screens.Multi
{
public override bool CursorVisible => (screenStack.CurrentScreen as IMultiplayerSubScreen)?.CursorVisible ?? true;
+ // this is required due to PlayerLoader eventually being pushed to the main stack
+ // while leases may be taken out by a subscreen.
public override bool DisallowExternalBeatmapRulesetChanges => true;
private readonly MultiplayerWaveContainer waves;
@@ -96,7 +98,7 @@ namespace osu.Game.Screens.Multi
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = Header.HEIGHT },
- Child = screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }
+ Child = screenStack = new MultiplayerSubScreenStack { RelativeSizeAxes = Axes.Both }
},
new Header(screenStack),
createButton = new HeaderButton
@@ -277,11 +279,7 @@ namespace osu.Game.Screens.Multi
private void updateTrack(ValueChangedEvent _ = null)
{
- bool isMatch = screenStack.CurrentScreen is MatchSubScreen;
-
- Beatmap.Disabled = isMatch;
-
- if (isMatch)
+ if (screenStack.CurrentScreen is MatchSubScreen)
{
var track = Beatmap.Value?.Track;
diff --git a/osu.Game/Screens/Multi/MultiplayerSubScreenStack.cs b/osu.Game/Screens/Multi/MultiplayerSubScreenStack.cs
new file mode 100644
index 0000000000..3b0ed0dba1
--- /dev/null
+++ b/osu.Game/Screens/Multi/MultiplayerSubScreenStack.cs
@@ -0,0 +1,24 @@
+// 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.Screens;
+
+namespace osu.Game.Screens.Multi
+{
+ public class MultiplayerSubScreenStack : OsuScreenStack
+ {
+ protected override void ScreenChanged(IScreen prev, IScreen next)
+ {
+ base.ScreenChanged(prev, next);
+
+ // because this is a screen stack within a screen stack, let's manually handle disabled changes to simplify things.
+ var osuScreen = ((OsuScreen)next);
+
+ bool disallowChanges = osuScreen.DisallowExternalBeatmapRulesetChanges;
+
+ osuScreen.Beatmap.Disabled = disallowChanges;
+ osuScreen.Ruleset.Disabled = disallowChanges;
+ osuScreen.Mods.Disabled = disallowChanges;
+ }
+ }
+}
diff --git a/osu.Game/Screens/OsuScreenStack.cs b/osu.Game/Screens/OsuScreenStack.cs
index a05933ef0e..e2a0414df7 100644
--- a/osu.Game/Screens/OsuScreenStack.cs
+++ b/osu.Game/Screens/OsuScreenStack.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Screens
};
ScreenPushed += screenPushed;
- ScreenExited += screenExited;
+ ScreenExited += ScreenChanged;
}
private void screenPushed(IScreen prev, IScreen next)
@@ -42,10 +42,10 @@ namespace osu.Game.Screens
// create dependencies synchronously to ensure leases are in a sane state.
((OsuScreen)next).CreateLeasedDependencies((prev as OsuScreen)?.Dependencies ?? Dependencies);
- setParallax(next);
+ ScreenChanged(prev, next);
}
- private void screenExited(IScreen prev, IScreen next)
+ protected virtual void ScreenChanged(IScreen prev, IScreen next)
{
setParallax(next);
}
diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs
index a78477c771..6ba4157797 100644
--- a/osu.Game/Screens/Select/MatchSongSelect.cs
+++ b/osu.Game/Screens/Select/MatchSongSelect.cs
@@ -65,20 +65,7 @@ namespace osu.Game.Screens.Select
Mods.Value = CurrentItem.Value.RequiredMods?.ToArray() ?? Array.Empty();
}
- Beatmap.Disabled = true;
- Ruleset.Disabled = true;
- Mods.Disabled = true;
-
return false;
}
-
- public override void OnEntering(IScreen last)
- {
- base.OnEntering(last);
-
- Beatmap.Disabled = false;
- Ruleset.Disabled = false;
- Mods.Disabled = false;
- }
}
}
diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs
index d6f92ba086..96e3c037a3 100644
--- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs
+++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Tests.Beatmaps
{
public TestBeatmap(RulesetInfo ruleset)
{
- var baseBeatmap = createTestBeatmap();
+ var baseBeatmap = CreateBeatmap();
BeatmapInfo = baseBeatmap.BeatmapInfo;
ControlPointInfo = baseBeatmap.ControlPointInfo;
@@ -37,6 +37,8 @@ namespace osu.Game.Tests.Beatmaps
};
}
+ protected virtual Beatmap CreateBeatmap() => createTestBeatmap();
+
private static Beatmap createTestBeatmap()
{
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data)))
diff --git a/osu.Game/Tests/Visual/ScrollingTestContainer.cs b/osu.Game/Tests/Visual/ScrollingTestContainer.cs
index 161ebe7030..18326a78ad 100644
--- a/osu.Game/Tests/Visual/ScrollingTestContainer.cs
+++ b/osu.Game/Tests/Visual/ScrollingTestContainer.cs
@@ -86,8 +86,8 @@ namespace osu.Game.Tests.Visual
}
}
- public double GetDisplayStartTime(double time, double timeRange)
- => implementation.GetDisplayStartTime(time, timeRange);
+ public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
+ => implementation.GetDisplayStartTime(originTime, offset, timeRange, scrollLength);
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
=> implementation.GetLength(startTime, endTime, timeRange, scrollLength);
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index ce58be52ee..21c9eab4c6 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -23,7 +23,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 6ab3c0f2d2..3ed25360c5 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -74,7 +74,7 @@
-
+
@@ -82,7 +82,7 @@
-
+