mirror of
https://github.com/ppy/osu.git
synced 2025-01-27 11:12:59 +08:00
Merge branch 'master' into fast-circle
This commit is contained in:
commit
501ea68a21
@ -254,5 +254,7 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
|
|
||||||
return adjustedDifficulty;
|
return adjustedDifficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool EditorShowScrollSpeed => false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,12 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
{
|
{
|
||||||
public new CatchBeatmap Beatmap => (CatchBeatmap)base.Beatmap;
|
public new CatchBeatmap Beatmap => (CatchBeatmap)base.Beatmap;
|
||||||
|
|
||||||
|
private readonly float halfCatcherWidth;
|
||||||
|
|
||||||
public CatchAutoGenerator(IBeatmap beatmap)
|
public CatchAutoGenerator(IBeatmap beatmap)
|
||||||
: base(beatmap)
|
: base(beatmap)
|
||||||
{
|
{
|
||||||
|
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.Difficulty) * 0.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void GenerateFrames()
|
protected override void GenerateFrames()
|
||||||
@ -47,10 +50,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
bool dashRequired = speedRequired > Catcher.BASE_WALK_SPEED;
|
bool dashRequired = speedRequired > Catcher.BASE_WALK_SPEED;
|
||||||
bool impossibleJump = speedRequired > Catcher.BASE_DASH_SPEED;
|
bool impossibleJump = speedRequired > Catcher.BASE_DASH_SPEED;
|
||||||
|
|
||||||
// todo: get correct catcher size, based on difficulty CS.
|
if (lastPosition - halfCatcherWidth < h.EffectiveX && lastPosition + halfCatcherWidth > h.EffectiveX)
|
||||||
const float catcher_width_half = Catcher.BASE_SIZE * 0.3f * 0.5f;
|
|
||||||
|
|
||||||
if (lastPosition - catcher_width_half < h.EffectiveX && lastPosition + catcher_width_half > h.EffectiveX)
|
|
||||||
{
|
{
|
||||||
// we are already in the correct range.
|
// we are already in the correct range.
|
||||||
lastTime = h.StartTime;
|
lastTime = h.StartTime;
|
||||||
|
@ -99,12 +99,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
|
||||||
{
|
|
||||||
Show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnTouchDown(TouchDownEvent e)
|
protected override bool OnTouchDown(TouchDownEvent e)
|
||||||
{
|
{
|
||||||
Show();
|
Show();
|
||||||
@ -172,17 +166,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
updateButton(false);
|
updateButton(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
|
||||||
{
|
|
||||||
updateButton(true);
|
|
||||||
return false; // handled by parent container to show overlay.
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnMouseUp(MouseUpEvent e)
|
|
||||||
{
|
|
||||||
updateButton(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateButton(bool press)
|
private void updateButton(bool press)
|
||||||
{
|
{
|
||||||
if (press == isPressed)
|
if (press == isPressed)
|
||||||
|
@ -359,5 +359,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
return adjustedDifficulty;
|
return adjustedDifficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool EditorShowScrollSpeed => false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -537,7 +537,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
[TestCaseSource(nameof(correct_date_query_examples))]
|
[TestCaseSource(nameof(correct_date_query_examples))]
|
||||||
public void TestValidDateQueries(string dateQuery)
|
public void TestValidDateQueries(string dateQuery)
|
||||||
{
|
{
|
||||||
string query = $"played<{dateQuery} time";
|
string query = $"lastplayed<{dateQuery} time";
|
||||||
var filterCriteria = new FilterCriteria();
|
var filterCriteria = new FilterCriteria();
|
||||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
|
Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
|
||||||
@ -571,7 +571,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestGreaterDateQuery()
|
public void TestGreaterDateQuery()
|
||||||
{
|
{
|
||||||
const string query = "played>50";
|
const string query = "lastplayed>50";
|
||||||
var filterCriteria = new FilterCriteria();
|
var filterCriteria = new FilterCriteria();
|
||||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null);
|
Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null);
|
||||||
@ -584,7 +584,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestLowerDateQuery()
|
public void TestLowerDateQuery()
|
||||||
{
|
{
|
||||||
const string query = "played<50";
|
const string query = "lastplayed<50";
|
||||||
var filterCriteria = new FilterCriteria();
|
var filterCriteria = new FilterCriteria();
|
||||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
Assert.That(filterCriteria.LastPlayed.Max, Is.Null);
|
Assert.That(filterCriteria.LastPlayed.Max, Is.Null);
|
||||||
@ -597,7 +597,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestBothSidesDateQuery()
|
public void TestBothSidesDateQuery()
|
||||||
{
|
{
|
||||||
const string query = "played>3M played<1y6M";
|
const string query = "lastplayed>3M lastplayed<1y6M";
|
||||||
var filterCriteria = new FilterCriteria();
|
var filterCriteria = new FilterCriteria();
|
||||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
Assert.That(filterCriteria.LastPlayed.Min, Is.Not.Null);
|
Assert.That(filterCriteria.LastPlayed.Min, Is.Not.Null);
|
||||||
@ -611,7 +611,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestEqualDateQuery()
|
public void TestEqualDateQuery()
|
||||||
{
|
{
|
||||||
const string query = "played=50";
|
const string query = "lastplayed=50";
|
||||||
var filterCriteria = new FilterCriteria();
|
var filterCriteria = new FilterCriteria();
|
||||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter);
|
Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter);
|
||||||
@ -620,11 +620,34 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestOutOfRangeDateQuery()
|
public void TestOutOfRangeDateQuery()
|
||||||
{
|
{
|
||||||
const string query = "played<10000y";
|
const string query = "lastplayed<10000y";
|
||||||
var filterCriteria = new FilterCriteria();
|
var filterCriteria = new FilterCriteria();
|
||||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
|
Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
|
||||||
Assert.AreEqual(DateTimeOffset.MinValue.AddMilliseconds(1), filterCriteria.LastPlayed.Min);
|
Assert.AreEqual(DateTimeOffset.MinValue.AddMilliseconds(1), filterCriteria.LastPlayed.Min);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly object[] played_query_tests =
|
||||||
|
{
|
||||||
|
new object[] { "0", DateTimeOffset.MinValue, true },
|
||||||
|
new object[] { "0", DateTimeOffset.Now, false },
|
||||||
|
new object[] { "false", DateTimeOffset.MinValue, true },
|
||||||
|
new object[] { "false", DateTimeOffset.Now, false },
|
||||||
|
|
||||||
|
new object[] { "1", DateTimeOffset.MinValue, false },
|
||||||
|
new object[] { "1", DateTimeOffset.Now, true },
|
||||||
|
new object[] { "true", DateTimeOffset.MinValue, false },
|
||||||
|
new object[] { "true", DateTimeOffset.Now, true },
|
||||||
|
};
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCaseSource(nameof(played_query_tests))]
|
||||||
|
public void TestPlayedQuery(string query, DateTimeOffset reference, bool matched)
|
||||||
|
{
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, $"played={query}");
|
||||||
|
Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
|
||||||
|
Assert.AreEqual(matched, filterCriteria.LastPlayed.IsInRange(reference));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,7 +257,7 @@ namespace osu.Game.Tests.Online
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string Target => null;
|
protected override string Target => string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ using osu.Framework.Screens;
|
|||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -48,13 +49,18 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
|
DetachedBeatmapStore detachedBeatmapStore;
|
||||||
|
|
||||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||||
Dependencies.Cache(new OsuConfigManager(LocalStorage));
|
Dependencies.Cache(new OsuConfigManager(LocalStorage));
|
||||||
|
Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore());
|
||||||
Dependencies.Cache(Realm);
|
Dependencies.Cache(Realm);
|
||||||
|
|
||||||
manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||||
|
|
||||||
|
Add(detachedBeatmapStore);
|
||||||
|
|
||||||
Beatmap.SetDefault();
|
Beatmap.SetDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ using Humanizer;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -307,6 +308,46 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
hitObjectNodeHasSampleVolume(0, 1, 10);
|
hitObjectNodeHasSampleVolume(0, 1, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSamplePointSeek()
|
||||||
|
{
|
||||||
|
AddStep("add slider", () =>
|
||||||
|
{
|
||||||
|
EditorBeatmap.Clear();
|
||||||
|
EditorBeatmap.Add(new Slider
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 256),
|
||||||
|
StartTime = 0,
|
||||||
|
Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }),
|
||||||
|
Samples =
|
||||||
|
{
|
||||||
|
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
|
||||||
|
},
|
||||||
|
NodeSamples =
|
||||||
|
{
|
||||||
|
new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) },
|
||||||
|
new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) },
|
||||||
|
},
|
||||||
|
RepeatCount = 1
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
seekSamplePiece(-1);
|
||||||
|
editorTimeIs(0);
|
||||||
|
samplePopoverIsOpen();
|
||||||
|
seekSamplePiece(-1);
|
||||||
|
editorTimeIs(0);
|
||||||
|
samplePopoverIsOpen();
|
||||||
|
seekSamplePiece(1);
|
||||||
|
editorTimeIs(406);
|
||||||
|
seekSamplePiece(1);
|
||||||
|
editorTimeIs(813);
|
||||||
|
seekSamplePiece(1);
|
||||||
|
editorTimeIs(1627);
|
||||||
|
seekSamplePiece(1);
|
||||||
|
editorTimeIs(1627);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHotkeysMultipleSelectionWithSameSampleBank()
|
public void TestHotkeysMultipleSelectionWithSameSampleBank()
|
||||||
{
|
{
|
||||||
@ -548,6 +589,63 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHotkeysUnifySliderSamplesAndNodeSamples()
|
||||||
|
{
|
||||||
|
AddStep("add slider", () =>
|
||||||
|
{
|
||||||
|
EditorBeatmap.Clear();
|
||||||
|
EditorBeatmap.Add(new Slider
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 256),
|
||||||
|
StartTime = 1000,
|
||||||
|
Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }),
|
||||||
|
Samples =
|
||||||
|
{
|
||||||
|
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT),
|
||||||
|
new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_DRUM),
|
||||||
|
},
|
||||||
|
NodeSamples = new List<IList<HitSampleInfo>>
|
||||||
|
{
|
||||||
|
new List<HitSampleInfo>
|
||||||
|
{
|
||||||
|
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_DRUM),
|
||||||
|
new HitSampleInfo(HitSampleInfo.HIT_CLAP, bank: HitSampleInfo.BANK_DRUM),
|
||||||
|
},
|
||||||
|
new List<HitSampleInfo>
|
||||||
|
{
|
||||||
|
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT),
|
||||||
|
new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_SOFT),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
|
|
||||||
|
AddStep("set soft bank", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.LShift);
|
||||||
|
InputManager.Key(Key.E);
|
||||||
|
InputManager.ReleaseKey(Key.LShift);
|
||||||
|
});
|
||||||
|
|
||||||
|
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
|
hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP);
|
||||||
|
hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
|
|
||||||
|
AddStep("unify whistle addition", () => InputManager.Key(Key.W));
|
||||||
|
|
||||||
|
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
|
hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE);
|
||||||
|
hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSelectingObjectDoesNotMutateSamples()
|
public void TestSelectingObjectDoesNotMutateSamples()
|
||||||
{
|
{
|
||||||
@ -569,7 +667,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} sample piece", () =>
|
private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} sample piece", () =>
|
||||||
{
|
{
|
||||||
var samplePiece = this.ChildrenOfType<SamplePointPiece>().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex));
|
var samplePiece = this.ChildrenOfType<SamplePointPiece>().Single(piece => piece is not NodeSamplePointPiece && piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex));
|
||||||
|
|
||||||
InputManager.MoveMouseTo(samplePiece);
|
InputManager.MoveMouseTo(samplePiece);
|
||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
@ -583,6 +681,21 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private void seekSamplePiece(int direction) => AddStep($"seek sample piece {direction}", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.PressKey(Key.ShiftLeft);
|
||||||
|
InputManager.Key(direction < 1 ? Key.Left : Key.Right);
|
||||||
|
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
private void samplePopoverIsOpen() => AddUntilStep("sample popover is open", () =>
|
||||||
|
{
|
||||||
|
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault(o => o.IsPresent);
|
||||||
|
return popover != null;
|
||||||
|
});
|
||||||
|
|
||||||
private void samplePopoverHasNoFocus() => AddUntilStep("sample popover textbox not focused", () =>
|
private void samplePopoverHasNoFocus() => AddUntilStep("sample popover textbox not focused", () =>
|
||||||
{
|
{
|
||||||
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
|
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
|
||||||
@ -727,5 +840,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
|
||||||
return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank);
|
return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private void editorTimeIs(double time) => AddAssert($"editor time is {time}", () => Precision.AlmostEquals(EditorClock.CurrentTimeAccurate, time, 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
() => Is.EqualTo(1));
|
() => Is.EqualTo(1));
|
||||||
|
|
||||||
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo?.Invoke());
|
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo?.Invoke());
|
||||||
AddUntilStep("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
AddUntilStep("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
addStepClickLink("00:00:000 (1)", waitForSeek: false);
|
addStepClickLink("00:00:000 (1)", waitForSeek: false);
|
||||||
AddUntilStep("received 'must be in edit'",
|
AddUntilStep("received 'must be in edit'",
|
||||||
@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddUntilStep("Wait for song select", () =>
|
AddUntilStep("Wait for song select", () =>
|
||||||
Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
||||||
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
||||||
&& songSelect.IsLoaded
|
&& songSelect.BeatmapSetsLoaded
|
||||||
);
|
);
|
||||||
AddStep("Switch ruleset", () => Game.Ruleset.Value = ruleset);
|
AddStep("Switch ruleset", () => Game.Ruleset.Value = ruleset);
|
||||||
AddStep("Open editor for ruleset", () =>
|
AddStep("Open editor for ruleset", () =>
|
||||||
|
@ -96,32 +96,6 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1));
|
AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestCommitPlacementViaGlobalAction()
|
|
||||||
{
|
|
||||||
Playfield playfield = null!;
|
|
||||||
|
|
||||||
AddStep("select slider placement tool", () => InputManager.Key(Key.Number3));
|
|
||||||
AddStep("move mouse to top left of playfield", () =>
|
|
||||||
{
|
|
||||||
playfield = this.ChildrenOfType<Playfield>().Single();
|
|
||||||
var location = (3 * playfield.ScreenSpaceDrawQuad.TopLeft + playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
|
|
||||||
InputManager.MoveMouseTo(location);
|
|
||||||
});
|
|
||||||
AddStep("begin placement", () => InputManager.Click(MouseButton.Left));
|
|
||||||
AddStep("move mouse to bottom right of playfield", () =>
|
|
||||||
{
|
|
||||||
var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
|
|
||||||
InputManager.MoveMouseTo(location);
|
|
||||||
});
|
|
||||||
AddStep("confirm via global action", () =>
|
|
||||||
{
|
|
||||||
globalActionContainer.TriggerPressed(GlobalAction.Select);
|
|
||||||
globalActionContainer.TriggerReleased(GlobalAction.Select);
|
|
||||||
});
|
|
||||||
AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAbortPlacementViaGlobalAction()
|
public void TestAbortPlacementViaGlobalAction()
|
||||||
{
|
{
|
||||||
@ -272,11 +246,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
|
var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
|
||||||
InputManager.MoveMouseTo(location);
|
InputManager.MoveMouseTo(location);
|
||||||
});
|
});
|
||||||
AddStep("confirm via global action", () =>
|
AddStep("confirm via right click", () => InputManager.Click(MouseButton.Right));
|
||||||
{
|
|
||||||
globalActionContainer.TriggerPressed(GlobalAction.Select);
|
|
||||||
globalActionContainer.TriggerReleased(GlobalAction.Select);
|
|
||||||
});
|
|
||||||
AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1));
|
AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1));
|
||||||
|
|
||||||
AddAssert("slider samples have drum bank", () => EditorBeatmap.HitObjects[0].Samples.All(s => s.Bank == HitSampleInfo.BANK_DRUM));
|
AddAssert("slider samples have drum bank", () => EditorBeatmap.HitObjects[0].Samples.All(s => s.Bank == HitSampleInfo.BANK_DRUM));
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Platform;
|
|||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -45,9 +46,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
|
DetachedBeatmapStore detachedBeatmapStore;
|
||||||
|
|
||||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||||
|
Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore());
|
||||||
Dependencies.Cache(Realm);
|
Dependencies.Cache(Realm);
|
||||||
|
|
||||||
|
Add(detachedBeatmapStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
|
@ -19,6 +19,7 @@ using osu.Framework.Testing;
|
|||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
@ -65,9 +66,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
|
DetachedBeatmapStore detachedBeatmapStore;
|
||||||
|
|
||||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default));
|
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default));
|
||||||
|
Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore());
|
||||||
Dependencies.Cache(Realm);
|
Dependencies.Cache(Realm);
|
||||||
|
|
||||||
|
Add(detachedBeatmapStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
|
@ -45,11 +45,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
|
DetachedBeatmapStore detachedBeatmapStore;
|
||||||
|
|
||||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||||
|
Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore());
|
||||||
Dependencies.Cache(Realm);
|
Dependencies.Cache(Realm);
|
||||||
|
|
||||||
importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray()));
|
importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray()));
|
||||||
|
|
||||||
|
Add(detachedBeatmapStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Platform;
|
|||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -33,13 +34,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
|
DetachedBeatmapStore detachedBeatmapStore;
|
||||||
|
|
||||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||||
|
Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore());
|
||||||
Dependencies.Cache(Realm);
|
Dependencies.Cache(Realm);
|
||||||
|
|
||||||
var beatmapSet = TestResources.CreateTestBeatmapSetInfo();
|
var beatmapSet = TestResources.CreateTestBeatmapSetInfo();
|
||||||
|
|
||||||
manager.Import(beatmapSet);
|
manager.Import(beatmapSet);
|
||||||
|
|
||||||
|
Add(detachedBeatmapStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
|
@ -165,16 +165,19 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
[Solo]
|
||||||
public void TestEditorGameplayTestAlwaysUsesOriginalRuleset()
|
public void TestEditorGameplayTestAlwaysUsesOriginalRuleset()
|
||||||
{
|
{
|
||||||
prepareBeatmap();
|
prepareBeatmap();
|
||||||
|
|
||||||
AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo);
|
AddStep("switch ruleset at song select", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo);
|
||||||
|
|
||||||
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
||||||
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
|
||||||
AddStep("test gameplay", () => getEditor().TestGameplay());
|
|
||||||
|
|
||||||
|
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
||||||
|
AddAssert("editor ruleset is osu!", () => Game.Ruleset.Value, () => Is.EqualTo(new OsuRuleset().RulesetInfo));
|
||||||
|
|
||||||
|
AddStep("test gameplay", () => getEditor().TestGameplay());
|
||||||
AddUntilStep("wait for player", () =>
|
AddUntilStep("wait for player", () =>
|
||||||
{
|
{
|
||||||
// notifications may fire at almost any inopportune time and cause annoying test failures.
|
// notifications may fire at almost any inopportune time and cause annoying test failures.
|
||||||
@ -183,8 +186,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
Game.CloseAllOverlays();
|
Game.CloseAllOverlays();
|
||||||
return Game.ScreenStack.CurrentScreen is EditorPlayer editorPlayer && editorPlayer.IsLoaded;
|
return Game.ScreenStack.CurrentScreen is EditorPlayer editorPlayer && editorPlayer.IsLoaded;
|
||||||
});
|
});
|
||||||
|
AddAssert("gameplay ruleset is osu!", () => Game.Ruleset.Value, () => Is.EqualTo(new OsuRuleset().RulesetInfo));
|
||||||
AddAssert("current ruleset is osu!", () => Game.Ruleset.Value.Equals(new OsuRuleset().RulesetInfo));
|
|
||||||
|
|
||||||
AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield()));
|
AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield()));
|
||||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||||
@ -352,7 +354,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
AddUntilStep("wait for song select",
|
AddUntilStep("wait for song select",
|
||||||
() => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
() => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
||||||
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
||||||
&& songSelect.IsLoaded);
|
&& songSelect.BeatmapSetsLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openEditor()
|
private void openEditor()
|
||||||
|
@ -176,6 +176,12 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
private void confirmBeatmapInSongSelect(Func<BeatmapSetInfo> getImport)
|
private void confirmBeatmapInSongSelect(Func<BeatmapSetInfo> getImport)
|
||||||
{
|
{
|
||||||
|
AddUntilStep("wait for carousel loaded", () =>
|
||||||
|
{
|
||||||
|
var songSelect = (Screens.Select.SongSelect)Game.ScreenStack.CurrentScreen;
|
||||||
|
return songSelect.ChildrenOfType<BeatmapCarousel>().SingleOrDefault()?.IsLoaded == true;
|
||||||
|
});
|
||||||
|
|
||||||
AddUntilStep("beatmap in song select", () =>
|
AddUntilStep("beatmap in song select", () =>
|
||||||
{
|
{
|
||||||
var songSelect = (Screens.Select.SongSelect)Game.ScreenStack.CurrentScreen;
|
var songSelect = (Screens.Select.SongSelect)Game.ScreenStack.CurrentScreen;
|
||||||
@ -187,7 +193,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
{
|
{
|
||||||
AddStep("present beatmap", () => Game.PresentBeatmap(getImport()));
|
AddStep("present beatmap", () => Game.PresentBeatmap(getImport()));
|
||||||
|
|
||||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.IsLoaded);
|
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||||
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID, () => Is.EqualTo(getImport().OnlineID));
|
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID, () => Is.EqualTo(getImport().OnlineID));
|
||||||
AddAssert("correct ruleset selected", () => Game.Ruleset.Value, () => Is.EqualTo(getImport().Beatmaps.First().Ruleset));
|
AddAssert("correct ruleset selected", () => Game.Ruleset.Value, () => Is.EqualTo(getImport().Beatmaps.First().Ruleset));
|
||||||
}
|
}
|
||||||
@ -197,7 +203,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
Predicate<BeatmapInfo> pred = b => b.OnlineID == importedID * 1024 + 2;
|
Predicate<BeatmapInfo> pred = b => b.OnlineID == importedID * 1024 + 2;
|
||||||
AddStep("present difficulty", () => Game.PresentBeatmap(getImport(), pred));
|
AddStep("present difficulty", () => Game.PresentBeatmap(getImport(), pred));
|
||||||
|
|
||||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.IsLoaded);
|
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||||
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineID, () => Is.EqualTo(importedID * 1024 + 2));
|
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineID, () => Is.EqualTo(importedID * 1024 + 2));
|
||||||
AddAssert("correct ruleset selected", () => Game.Ruleset.Value.OnlineID, () => Is.EqualTo(expectedRulesetOnlineID ?? getImport().Beatmaps.First().Ruleset.OnlineID));
|
AddAssert("correct ruleset selected", () => Game.Ruleset.Value.OnlineID, () => Is.EqualTo(expectedRulesetOnlineID ?? getImport().Beatmaps.First().Ruleset.OnlineID));
|
||||||
}
|
}
|
||||||
|
@ -1035,9 +1035,11 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestTouchScreenDetectionInGame()
|
public void TestTouchScreenDetectionInGame()
|
||||||
{
|
{
|
||||||
|
BeatmapSetInfo beatmapSet = null;
|
||||||
|
|
||||||
PushAndConfirm(() => new TestPlaySongSelect());
|
PushAndConfirm(() => new TestPlaySongSelect());
|
||||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
AddStep("import beatmap", () => beatmapSet = BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely());
|
||||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
AddUntilStep("wait for selected", () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet));
|
||||||
AddStep("select", () => InputManager.Key(Key.Enter));
|
AddStep("select", () => InputManager.Key(Key.Enter));
|
||||||
|
|
||||||
Player player = null;
|
Player player = null;
|
||||||
|
@ -446,7 +446,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
AddStep("Show overlay with channel 1", () =>
|
AddStep("Show overlay with channel 1", () =>
|
||||||
{
|
{
|
||||||
channelManager.JoinChannel(testChannel1);
|
channelManager.CurrentChannel.Value = channelManager.JoinChannel(testChannel1);
|
||||||
chatOverlay.Show();
|
chatOverlay.Show();
|
||||||
});
|
});
|
||||||
waitForChannel1Visible();
|
waitForChannel1Visible();
|
||||||
@ -462,7 +462,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
AddStep("Show overlay with channel 1", () =>
|
AddStep("Show overlay with channel 1", () =>
|
||||||
{
|
{
|
||||||
channelManager.JoinChannel(testChannel1);
|
channelManager.CurrentChannel.Value = channelManager.JoinChannel(testChannel1);
|
||||||
chatOverlay.Show();
|
chatOverlay.Show();
|
||||||
});
|
});
|
||||||
waitForChannel1Visible();
|
waitForChannel1Visible();
|
||||||
|
@ -17,6 +17,7 @@ using osu.Game.Online.API;
|
|||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays.Comments;
|
using osu.Game.Overlays.Comments;
|
||||||
|
using osu.Game.Overlays.Comments.Buttons;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Online
|
namespace osu.Game.Tests.Visual.Online
|
||||||
{
|
{
|
||||||
@ -58,6 +59,11 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
||||||
AddUntilStep("show more button hidden",
|
AddUntilStep("show more button hidden",
|
||||||
() => commentsContainer.ChildrenOfType<CommentsShowMoreButton>().Single().Alpha == 0);
|
() => commentsContainer.ChildrenOfType<CommentsShowMoreButton>().Single().Alpha == 0);
|
||||||
|
|
||||||
|
if (withPinned)
|
||||||
|
AddAssert("pinned comment replies collapsed", () => commentsContainer.ChildrenOfType<ShowRepliesButton>().First().Expanded.Value, () => Is.False);
|
||||||
|
else
|
||||||
|
AddAssert("first comment replies expanded", () => commentsContainer.ChildrenOfType<ShowRepliesButton>().First().Expanded.Value, () => Is.True);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
@ -302,7 +308,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
bundle.Comments.Add(new Comment
|
bundle.Comments.Add(new Comment
|
||||||
{
|
{
|
||||||
Id = 20,
|
Id = 20,
|
||||||
Message = "Reply to pinned comment",
|
Message = "Reply to pinned comment initially hidden",
|
||||||
LegacyName = "AbandonedUser",
|
LegacyName = "AbandonedUser",
|
||||||
CreatedAt = DateTimeOffset.Now,
|
CreatedAt = DateTimeOffset.Now,
|
||||||
VotesCount = 0,
|
VotesCount = 0,
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
@ -52,11 +53,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
createCarousel(new List<BeatmapSetInfo>());
|
createCarousel(new List<BeatmapSetInfo>());
|
||||||
|
|
||||||
AddStep("filter to ruleset 0", () => carousel.Filter(new FilterCriteria
|
AddStep("filter to ruleset 0", () => carousel.FilterImmediately(new FilterCriteria
|
||||||
{
|
{
|
||||||
Ruleset = rulesets.AvailableRulesets.ElementAt(0),
|
Ruleset = rulesets.AvailableRulesets.ElementAt(0),
|
||||||
AllowConvertedBeatmaps = true,
|
AllowConvertedBeatmaps = true,
|
||||||
}, false));
|
}));
|
||||||
|
|
||||||
AddStep("add mixed ruleset beatmapset", () =>
|
AddStep("add mixed ruleset beatmapset", () =>
|
||||||
{
|
{
|
||||||
@ -78,11 +79,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
&& visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item)!.BeatmapInfo.Ruleset.OnlineID == 0) == 1;
|
&& visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item)!.BeatmapInfo.Ruleset.OnlineID == 0) == 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("filter to ruleset 1", () => carousel.Filter(new FilterCriteria
|
AddStep("filter to ruleset 1", () => carousel.FilterImmediately(new FilterCriteria
|
||||||
{
|
{
|
||||||
Ruleset = rulesets.AvailableRulesets.ElementAt(1),
|
Ruleset = rulesets.AvailableRulesets.ElementAt(1),
|
||||||
AllowConvertedBeatmaps = true,
|
AllowConvertedBeatmaps = true,
|
||||||
}, false));
|
}));
|
||||||
|
|
||||||
AddUntilStep("wait for filtered difficulties", () =>
|
AddUntilStep("wait for filtered difficulties", () =>
|
||||||
{
|
{
|
||||||
@ -93,11 +94,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
&& visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item)!.BeatmapInfo.Ruleset.OnlineID == 1) == 1;
|
&& visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item)!.BeatmapInfo.Ruleset.OnlineID == 1) == 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("filter to ruleset 2", () => carousel.Filter(new FilterCriteria
|
AddStep("filter to ruleset 2", () => carousel.FilterImmediately(new FilterCriteria
|
||||||
{
|
{
|
||||||
Ruleset = rulesets.AvailableRulesets.ElementAt(2),
|
Ruleset = rulesets.AvailableRulesets.ElementAt(2),
|
||||||
AllowConvertedBeatmaps = true,
|
AllowConvertedBeatmaps = true,
|
||||||
}, false));
|
}));
|
||||||
|
|
||||||
AddUntilStep("wait for filtered difficulties", () =>
|
AddUntilStep("wait for filtered difficulties", () =>
|
||||||
{
|
{
|
||||||
@ -344,7 +345,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
// basic filtering
|
// basic filtering
|
||||||
setSelected(1, 1);
|
setSelected(1, 1);
|
||||||
|
|
||||||
AddStep("Filter", () => carousel.Filter(new FilterCriteria { SearchText = carousel.BeatmapSets.ElementAt(2).Metadata.Title }, false));
|
AddStep("Filter", () => carousel.FilterImmediately(new FilterCriteria { SearchText = carousel.BeatmapSets.ElementAt(2).Metadata.Title }));
|
||||||
checkVisibleItemCount(diff: false, count: 1);
|
checkVisibleItemCount(diff: false, count: 1);
|
||||||
checkVisibleItemCount(diff: true, count: 3);
|
checkVisibleItemCount(diff: true, count: 3);
|
||||||
waitForSelection(3, 1);
|
waitForSelection(3, 1);
|
||||||
@ -360,13 +361,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
// test filtering some difficulties (and keeping current beatmap set selected).
|
// test filtering some difficulties (and keeping current beatmap set selected).
|
||||||
|
|
||||||
setSelected(1, 2);
|
setSelected(1, 2);
|
||||||
AddStep("Filter some difficulties", () => carousel.Filter(new FilterCriteria { SearchText = "Normal" }, false));
|
AddStep("Filter some difficulties", () => carousel.FilterImmediately(new FilterCriteria { SearchText = "Normal" }));
|
||||||
waitForSelection(1, 1);
|
waitForSelection(1, 1);
|
||||||
|
|
||||||
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
|
AddStep("Un-filter", () => carousel.FilterImmediately(new FilterCriteria()));
|
||||||
waitForSelection(1, 1);
|
waitForSelection(1, 1);
|
||||||
|
|
||||||
AddStep("Filter all", () => carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false));
|
AddStep("Filter all", () => carousel.FilterImmediately(new FilterCriteria { SearchText = "Dingo" }));
|
||||||
|
|
||||||
checkVisibleItemCount(false, 0);
|
checkVisibleItemCount(false, 0);
|
||||||
checkVisibleItemCount(true, 0);
|
checkVisibleItemCount(true, 0);
|
||||||
@ -378,7 +379,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
advanceSelection(false);
|
advanceSelection(false);
|
||||||
AddAssert("Selection is null", () => currentSelection == null);
|
AddAssert("Selection is null", () => currentSelection == null);
|
||||||
|
|
||||||
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
|
AddStep("Un-filter", () => carousel.FilterImmediately(new FilterCriteria()));
|
||||||
|
|
||||||
AddAssert("Selection is non-null", () => currentSelection != null);
|
AddAssert("Selection is non-null", () => currentSelection != null);
|
||||||
|
|
||||||
@ -399,7 +400,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
setSelected(1, 3);
|
setSelected(1, 3);
|
||||||
|
|
||||||
AddStep("Apply a range filter", () => carousel.Filter(new FilterCriteria
|
AddStep("Apply a range filter", () => carousel.FilterImmediately(new FilterCriteria
|
||||||
{
|
{
|
||||||
SearchText = searchText,
|
SearchText = searchText,
|
||||||
StarDifficulty = new FilterCriteria.OptionalRange<double>
|
StarDifficulty = new FilterCriteria.OptionalRange<double>
|
||||||
@ -408,7 +409,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Max = 5.5,
|
Max = 5.5,
|
||||||
IsLowerInclusive = true
|
IsLowerInclusive = true
|
||||||
}
|
}
|
||||||
}, false));
|
}));
|
||||||
|
|
||||||
// should reselect the buffered selection.
|
// should reselect the buffered selection.
|
||||||
waitForSelection(3, 2);
|
waitForSelection(3, 2);
|
||||||
@ -445,13 +446,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddAssert("ensure repeat", () => selectedSets.Contains(carousel.SelectedBeatmapSet));
|
AddAssert("ensure repeat", () => selectedSets.Contains(carousel.SelectedBeatmapSet));
|
||||||
|
|
||||||
AddStep("Add set with 100 difficulties", () => carousel.UpdateBeatmapSet(TestResources.CreateTestBeatmapSetInfo(100, rulesets.AvailableRulesets.ToArray())));
|
AddStep("Add set with 100 difficulties", () => carousel.UpdateBeatmapSet(TestResources.CreateTestBeatmapSetInfo(100, rulesets.AvailableRulesets.ToArray())));
|
||||||
AddStep("Filter Extra", () => carousel.Filter(new FilterCriteria { SearchText = "Extra 10" }, false));
|
AddStep("Filter Extra", () => carousel.FilterImmediately(new FilterCriteria { SearchText = "Extra 10" }));
|
||||||
checkInvisibleDifficultiesUnselectable();
|
checkInvisibleDifficultiesUnselectable();
|
||||||
checkInvisibleDifficultiesUnselectable();
|
checkInvisibleDifficultiesUnselectable();
|
||||||
checkInvisibleDifficultiesUnselectable();
|
checkInvisibleDifficultiesUnselectable();
|
||||||
checkInvisibleDifficultiesUnselectable();
|
checkInvisibleDifficultiesUnselectable();
|
||||||
checkInvisibleDifficultiesUnselectable();
|
checkInvisibleDifficultiesUnselectable();
|
||||||
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
|
AddStep("Un-filter", () => carousel.FilterImmediately(new FilterCriteria()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -527,7 +528,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
loadBeatmaps(setCount: local_set_count, diffCount: local_diff_count);
|
loadBeatmaps(setCount: local_set_count, diffCount: local_diff_count);
|
||||||
|
|
||||||
AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false));
|
AddStep("Sort by difficulty", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Difficulty }));
|
||||||
|
|
||||||
checkVisibleItemCount(false, local_set_count * local_diff_count);
|
checkVisibleItemCount(false, local_set_count * local_diff_count);
|
||||||
|
|
||||||
@ -566,7 +567,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
loadBeatmaps(sets, () => new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) });
|
loadBeatmaps(sets, () => new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) });
|
||||||
|
|
||||||
AddStep("Set non-empty mode filter", () =>
|
AddStep("Set non-empty mode filter", () =>
|
||||||
carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1) }, false));
|
carousel.FilterImmediately(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1) }));
|
||||||
|
|
||||||
AddAssert("Something is selected", () => carousel.SelectedBeatmapInfo != null);
|
AddAssert("Something is selected", () => carousel.SelectedBeatmapInfo != null);
|
||||||
}
|
}
|
||||||
@ -601,7 +602,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
loadBeatmaps(sets);
|
loadBeatmaps(sets);
|
||||||
|
|
||||||
AddStep("Sort by date submitted", () => carousel.Filter(new FilterCriteria { Sort = SortMode.DateSubmitted }, false));
|
AddStep("Sort by date submitted", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.DateSubmitted }));
|
||||||
checkVisibleItemCount(diff: false, count: 10);
|
checkVisibleItemCount(diff: false, count: 10);
|
||||||
checkVisibleItemCount(diff: true, count: 5);
|
checkVisibleItemCount(diff: true, count: 5);
|
||||||
|
|
||||||
@ -610,11 +611,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddAssert("rest are at start", () => carousel.Items.OfType<DrawableCarouselBeatmapSet>().TakeWhile(i => i.Item is CarouselBeatmapSet s && s.BeatmapSet.DateSubmitted != null).Count(),
|
AddAssert("rest are at start", () => carousel.Items.OfType<DrawableCarouselBeatmapSet>().TakeWhile(i => i.Item is CarouselBeatmapSet s && s.BeatmapSet.DateSubmitted != null).Count(),
|
||||||
() => Is.EqualTo(6));
|
() => Is.EqualTo(6));
|
||||||
|
|
||||||
AddStep("Sort by date submitted and string", () => carousel.Filter(new FilterCriteria
|
AddStep("Sort by date submitted and string", () => carousel.FilterImmediately(new FilterCriteria
|
||||||
{
|
{
|
||||||
Sort = SortMode.DateSubmitted,
|
Sort = SortMode.DateSubmitted,
|
||||||
SearchText = zzz_string
|
SearchText = zzz_string
|
||||||
}, false));
|
}));
|
||||||
checkVisibleItemCount(diff: false, count: 5);
|
checkVisibleItemCount(diff: false, count: 5);
|
||||||
checkVisibleItemCount(diff: true, count: 5);
|
checkVisibleItemCount(diff: true, count: 5);
|
||||||
|
|
||||||
@ -658,10 +659,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
loadBeatmaps(sets);
|
loadBeatmaps(sets);
|
||||||
|
|
||||||
AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false));
|
AddStep("Sort by author", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Author }));
|
||||||
AddAssert($"Check {zzz_uppercase} is last", () => carousel.BeatmapSets.Last().Metadata.Author.Username == zzz_uppercase);
|
AddAssert($"Check {zzz_uppercase} is last", () => carousel.BeatmapSets.Last().Metadata.Author.Username == zzz_uppercase);
|
||||||
AddAssert($"Check {zzz_lowercase} is second last", () => carousel.BeatmapSets.SkipLast(1).Last().Metadata.Author.Username == zzz_lowercase);
|
AddAssert($"Check {zzz_lowercase} is second last", () => carousel.BeatmapSets.SkipLast(1).Last().Metadata.Author.Username == zzz_lowercase);
|
||||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
AddStep("Sort by artist", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Artist }));
|
||||||
AddAssert($"Check {zzz_uppercase} is last", () => carousel.BeatmapSets.Last().Metadata.Artist == zzz_uppercase);
|
AddAssert($"Check {zzz_uppercase} is last", () => carousel.BeatmapSets.Last().Metadata.Artist == zzz_uppercase);
|
||||||
AddAssert($"Check {zzz_lowercase} is second last", () => carousel.BeatmapSets.SkipLast(1).Last().Metadata.Artist == zzz_lowercase);
|
AddAssert($"Check {zzz_lowercase} is second last", () => carousel.BeatmapSets.SkipLast(1).Last().Metadata.Artist == zzz_lowercase);
|
||||||
}
|
}
|
||||||
@ -703,7 +704,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
loadBeatmaps(sets);
|
loadBeatmaps(sets);
|
||||||
|
|
||||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
AddStep("Sort by artist", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Artist }));
|
||||||
AddAssert("Check last item", () =>
|
AddAssert("Check last item", () =>
|
||||||
{
|
{
|
||||||
var lastItem = carousel.BeatmapSets.Last();
|
var lastItem = carousel.BeatmapSets.Last();
|
||||||
@ -746,10 +747,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
loadBeatmaps(sets);
|
loadBeatmaps(sets);
|
||||||
|
|
||||||
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
|
AddStep("Sort by title", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Title }));
|
||||||
AddAssert("Items remain in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
AddAssert("Items remain in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
||||||
|
|
||||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
AddStep("Sort by artist", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Artist }));
|
||||||
AddAssert("Items remain in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
AddAssert("Items remain in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -786,7 +787,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
loadBeatmaps(sets);
|
loadBeatmaps(sets);
|
||||||
|
|
||||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
AddStep("Sort by artist", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Artist }));
|
||||||
|
|
||||||
AddAssert("Items in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
AddAssert("Items in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
||||||
AddStep("Save order", () => originalOrder = carousel.BeatmapSets.Select(s => s.ID).ToArray());
|
AddStep("Save order", () => originalOrder = carousel.BeatmapSets.Select(s => s.ID).ToArray());
|
||||||
@ -796,7 +797,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
||||||
|
|
||||||
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
|
AddStep("Sort by title", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Title }));
|
||||||
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -833,7 +834,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
loadBeatmaps(sets);
|
loadBeatmaps(sets);
|
||||||
|
|
||||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
AddStep("Sort by artist", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Artist }));
|
||||||
|
|
||||||
AddAssert("Items in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
AddAssert("Items in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
||||||
AddStep("Save order", () => originalOrder = carousel.BeatmapSets.Select(s => s.ID).ToArray());
|
AddStep("Save order", () => originalOrder = carousel.BeatmapSets.Select(s => s.ID).ToArray());
|
||||||
@ -858,7 +859,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
||||||
|
|
||||||
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
|
AddStep("Sort by title", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Title }));
|
||||||
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -885,12 +886,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
loadBeatmaps(sets);
|
loadBeatmaps(sets);
|
||||||
|
|
||||||
AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false));
|
AddStep("Sort by difficulty", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Difficulty }));
|
||||||
|
|
||||||
checkVisibleItemCount(false, local_set_count * local_diff_count);
|
checkVisibleItemCount(false, local_set_count * local_diff_count);
|
||||||
checkVisibleItemCount(true, 1);
|
checkVisibleItemCount(true, 1);
|
||||||
|
|
||||||
AddStep("Filter to normal", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Normal" }, false));
|
AddStep("Filter to normal", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Normal" }));
|
||||||
checkVisibleItemCount(false, local_set_count);
|
checkVisibleItemCount(false, local_set_count);
|
||||||
checkVisibleItemCount(true, 1);
|
checkVisibleItemCount(true, 1);
|
||||||
|
|
||||||
@ -901,7 +902,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
.Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Normal", StringComparison.Ordinal)) == local_set_count;
|
.Count(p => ((CarouselBeatmapSet)p.Item)!.Beatmaps.Single().BeatmapInfo.DifficultyName.StartsWith("Normal", StringComparison.Ordinal)) == local_set_count;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("Filter to insane", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Insane" }, false));
|
AddStep("Filter to insane", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Insane" }));
|
||||||
checkVisibleItemCount(false, local_set_count);
|
checkVisibleItemCount(false, local_set_count);
|
||||||
checkVisibleItemCount(true, 1);
|
checkVisibleItemCount(true, 1);
|
||||||
|
|
||||||
@ -1022,7 +1023,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
carousel.UpdateBeatmapSet(testMixed);
|
carousel.UpdateBeatmapSet(testMixed);
|
||||||
});
|
});
|
||||||
AddStep("filter to ruleset 0", () =>
|
AddStep("filter to ruleset 0", () =>
|
||||||
carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false));
|
carousel.FilterImmediately(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }));
|
||||||
AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false));
|
AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false));
|
||||||
AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 0);
|
AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 0);
|
||||||
|
|
||||||
@ -1068,12 +1069,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
AddStep("Toggle non-matching filter", () =>
|
AddStep("Toggle non-matching filter", () =>
|
||||||
{
|
{
|
||||||
carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false);
|
carousel.FilterImmediately(new FilterCriteria { SearchText = Guid.NewGuid().ToString() });
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("Restore no filter", () =>
|
AddStep("Restore no filter", () =>
|
||||||
{
|
{
|
||||||
carousel.Filter(new FilterCriteria(), false);
|
carousel.FilterImmediately(new FilterCriteria());
|
||||||
eagerSelectedIDs.Add(carousel.SelectedBeatmapSet!.ID);
|
eagerSelectedIDs.Add(carousel.SelectedBeatmapSet!.ID);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1097,7 +1098,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
loadBeatmaps(manySets);
|
loadBeatmaps(manySets);
|
||||||
|
|
||||||
AddStep("Sort by difficulty", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty }, false));
|
AddStep("Sort by difficulty", () => carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Difficulty }));
|
||||||
|
|
||||||
advanceSelection(direction: 1, diff: false);
|
advanceSelection(direction: 1, diff: false);
|
||||||
|
|
||||||
@ -1105,12 +1106,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
AddStep("Toggle non-matching filter", () =>
|
AddStep("Toggle non-matching filter", () =>
|
||||||
{
|
{
|
||||||
carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false);
|
carousel.FilterImmediately(new FilterCriteria { SearchText = Guid.NewGuid().ToString() });
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("Restore no filter", () =>
|
AddStep("Restore no filter", () =>
|
||||||
{
|
{
|
||||||
carousel.Filter(new FilterCriteria(), false);
|
carousel.FilterImmediately(new FilterCriteria());
|
||||||
eagerSelectedIDs.Add(carousel.SelectedBeatmapSet!.ID);
|
eagerSelectedIDs.Add(carousel.SelectedBeatmapSet!.ID);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1185,7 +1186,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddStep($"Set ruleset to {rulesetInfo.ShortName}", () =>
|
AddStep($"Set ruleset to {rulesetInfo.ShortName}", () =>
|
||||||
{
|
{
|
||||||
carousel.Filter(new FilterCriteria { Ruleset = rulesetInfo, Sort = SortMode.Title }, false);
|
carousel.FilterImmediately(new FilterCriteria { Ruleset = rulesetInfo, Sort = SortMode.Title });
|
||||||
});
|
});
|
||||||
waitForSelection(i + 1, 1);
|
waitForSelection(i + 1, 1);
|
||||||
}
|
}
|
||||||
@ -1223,12 +1224,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
setSelected(i, 1);
|
setSelected(i, 1);
|
||||||
AddStep("Set ruleset to taiko", () =>
|
AddStep("Set ruleset to taiko", () =>
|
||||||
{
|
{
|
||||||
carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1), Sort = SortMode.Title }, false);
|
carousel.FilterImmediately(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1), Sort = SortMode.Title });
|
||||||
});
|
});
|
||||||
waitForSelection(i - 1, 1);
|
waitForSelection(i - 1, 1);
|
||||||
AddStep("Remove ruleset filter", () =>
|
AddStep("Remove ruleset filter", () =>
|
||||||
{
|
{
|
||||||
carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false);
|
carousel.FilterImmediately(new FilterCriteria { Sort = SortMode.Title });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1268,26 +1269,23 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createCarousel(beatmapSets, c =>
|
createCarousel(beatmapSets, initialCriteria, c =>
|
||||||
{
|
{
|
||||||
carouselAdjust?.Invoke(c);
|
|
||||||
|
|
||||||
carousel.Filter(initialCriteria?.Invoke() ?? new FilterCriteria());
|
|
||||||
carousel.BeatmapSetsChanged = () => changed = true;
|
carousel.BeatmapSetsChanged = () => changed = true;
|
||||||
carousel.BeatmapSets = beatmapSets;
|
carouselAdjust?.Invoke(c);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("Wait for load", () => changed);
|
AddUntilStep("Wait for load", () => changed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createCarousel(List<BeatmapSetInfo> beatmapSets, Action<BeatmapCarousel> carouselAdjust = null, Container target = null)
|
private void createCarousel(List<BeatmapSetInfo> beatmapSets, [CanBeNull] Func<FilterCriteria> initialCriteria = null, Action<BeatmapCarousel> carouselAdjust = null, Container target = null)
|
||||||
{
|
{
|
||||||
AddStep("Create carousel", () =>
|
AddStep("Create carousel", () =>
|
||||||
{
|
{
|
||||||
selectedSets.Clear();
|
selectedSets.Clear();
|
||||||
eagerSelectedIDs.Clear();
|
eagerSelectedIDs.Clear();
|
||||||
|
|
||||||
carousel = new TestBeatmapCarousel
|
carousel = new TestBeatmapCarousel(initialCriteria?.Invoke() ?? new FilterCriteria())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
};
|
};
|
||||||
@ -1389,6 +1387,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
private partial class TestBeatmapCarousel : BeatmapCarousel
|
private partial class TestBeatmapCarousel : BeatmapCarousel
|
||||||
{
|
{
|
||||||
|
public TestBeatmapCarousel(FilterCriteria criteria)
|
||||||
|
: base(criteria)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public bool PendingFilterTask => PendingFilter != null;
|
public bool PendingFilterTask => PendingFilter != null;
|
||||||
|
|
||||||
public IEnumerable<DrawableCarouselItem> Items
|
public IEnumerable<DrawableCarouselItem> Items
|
||||||
@ -1410,6 +1413,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void FilterImmediately(FilterCriteria newCriteria)
|
||||||
|
{
|
||||||
|
Filter(newCriteria);
|
||||||
|
FlushPendingFilterOperations();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,16 +56,20 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
|
DetachedBeatmapStore detachedBeatmapStore;
|
||||||
|
|
||||||
// These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install.
|
// These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install.
|
||||||
// At a point we have isolated interactive test runs enough, this can likely be removed.
|
// At a point we have isolated interactive test runs enough, this can likely be removed.
|
||||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||||
Dependencies.Cache(Realm);
|
Dependencies.Cache(Realm);
|
||||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, defaultBeatmap = Beatmap.Default));
|
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, defaultBeatmap = Beatmap.Default));
|
||||||
|
Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore());
|
||||||
|
|
||||||
Dependencies.Cache(music = new MusicController());
|
Dependencies.Cache(music = new MusicController());
|
||||||
|
|
||||||
// required to get bindables attached
|
// required to get bindables attached
|
||||||
Add(music);
|
Add(music);
|
||||||
|
Add(detachedBeatmapStore);
|
||||||
|
|
||||||
Dependencies.Cache(config = new OsuConfigManager(LocalStorage));
|
Dependencies.Cache(config = new OsuConfigManager(LocalStorage));
|
||||||
}
|
}
|
||||||
@ -242,7 +246,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
createSongSelect();
|
createSongSelect();
|
||||||
|
|
||||||
AddAssert("filter count is 1", () => songSelect?.FilterCount == 1);
|
AddAssert("filter count is 0", () => songSelect?.FilterCount, () => Is.EqualTo(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -362,7 +366,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddStep("return", () => songSelect!.MakeCurrent());
|
AddStep("return", () => songSelect!.MakeCurrent());
|
||||||
AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen());
|
AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen());
|
||||||
AddAssert("filter count is 1", () => songSelect!.FilterCount == 1);
|
AddAssert("filter count is 0", () => songSelect!.FilterCount, () => Is.EqualTo(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -382,7 +386,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddStep("return", () => songSelect!.MakeCurrent());
|
AddStep("return", () => songSelect!.MakeCurrent());
|
||||||
AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen());
|
AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen());
|
||||||
AddAssert("filter count is 2", () => songSelect!.FilterCount == 2);
|
AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -1270,11 +1274,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
// Mod that is guaranteed to never re-filter.
|
// Mod that is guaranteed to never re-filter.
|
||||||
AddStep("add non-filterable mod", () => SelectedMods.Value = new Mod[] { new OsuModCinema() });
|
AddStep("add non-filterable mod", () => SelectedMods.Value = new Mod[] { new OsuModCinema() });
|
||||||
AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1));
|
AddAssert("filter count is 0", () => songSelect!.FilterCount, () => Is.EqualTo(0));
|
||||||
|
|
||||||
// Removing the mod should still not re-filter.
|
// Removing the mod should still not re-filter.
|
||||||
AddStep("remove non-filterable mod", () => SelectedMods.Value = Array.Empty<Mod>());
|
AddStep("remove non-filterable mod", () => SelectedMods.Value = Array.Empty<Mod>());
|
||||||
AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1));
|
AddAssert("filter count is 0", () => songSelect!.FilterCount, () => Is.EqualTo(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -1286,35 +1290,35 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
// Change to mania ruleset.
|
// Change to mania ruleset.
|
||||||
AddStep("filter to mania ruleset", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 3));
|
AddStep("filter to mania ruleset", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 3));
|
||||||
AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(2));
|
AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(1));
|
||||||
|
|
||||||
// Apply a mod, but this should NOT re-filter because there's no search text.
|
// Apply a mod, but this should NOT re-filter because there's no search text.
|
||||||
AddStep("add filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey3() });
|
AddStep("add filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey3() });
|
||||||
AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(2));
|
AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1));
|
||||||
|
|
||||||
// Set search text. Should re-filter.
|
// Set search text. Should re-filter.
|
||||||
AddStep("set search text to match mods", () => songSelect!.FilterControl.CurrentTextSearch.Value = "keys=3");
|
AddStep("set search text to match mods", () => songSelect!.FilterControl.CurrentTextSearch.Value = "keys=3");
|
||||||
AddAssert("filter count is 3", () => songSelect!.FilterCount, () => Is.EqualTo(3));
|
AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(2));
|
||||||
|
|
||||||
// Change filterable mod. Should re-filter.
|
// Change filterable mod. Should re-filter.
|
||||||
AddStep("change new filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey5() });
|
AddStep("change new filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey5() });
|
||||||
AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4));
|
AddAssert("filter count is 3", () => songSelect!.FilterCount, () => Is.EqualTo(3));
|
||||||
|
|
||||||
// Add non-filterable mod. Should NOT re-filter.
|
// Add non-filterable mod. Should NOT re-filter.
|
||||||
AddStep("apply non-filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModNoFail(), new ManiaModKey5() });
|
AddStep("apply non-filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModNoFail(), new ManiaModKey5() });
|
||||||
AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4));
|
AddAssert("filter count is 3", () => songSelect!.FilterCount, () => Is.EqualTo(3));
|
||||||
|
|
||||||
// Remove filterable mod. Should re-filter.
|
// Remove filterable mod. Should re-filter.
|
||||||
AddStep("remove filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModNoFail() });
|
AddStep("remove filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModNoFail() });
|
||||||
AddAssert("filter count is 5", () => songSelect!.FilterCount, () => Is.EqualTo(5));
|
AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4));
|
||||||
|
|
||||||
// Remove non-filterable mod. Should NOT re-filter.
|
// Remove non-filterable mod. Should NOT re-filter.
|
||||||
AddStep("remove filterable mod", () => SelectedMods.Value = Array.Empty<Mod>());
|
AddStep("remove filterable mod", () => SelectedMods.Value = Array.Empty<Mod>());
|
||||||
AddAssert("filter count is 5", () => songSelect!.FilterCount, () => Is.EqualTo(5));
|
AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4));
|
||||||
|
|
||||||
// Add filterable mod. Should re-filter.
|
// Add filterable mod. Should re-filter.
|
||||||
AddStep("add filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey3() });
|
AddStep("add filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey3() });
|
||||||
AddAssert("filter count is 6", () => songSelect!.FilterCount, () => Is.EqualTo(6));
|
AddAssert("filter count is 5", () => songSelect!.FilterCount, () => Is.EqualTo(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void waitForInitialSelection()
|
private void waitForInitialSelection()
|
||||||
@ -1397,8 +1401,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
public Action? StartRequested;
|
public Action? StartRequested;
|
||||||
|
|
||||||
public new Bindable<RulesetInfo> Ruleset => base.Ruleset;
|
|
||||||
|
|
||||||
public new FilterControl FilterControl => base.FilterControl;
|
public new FilterControl FilterControl => base.FilterControl;
|
||||||
|
|
||||||
public WorkingBeatmap CurrentBeatmap => Beatmap.Value;
|
public WorkingBeatmap CurrentBeatmap => Beatmap.Value;
|
||||||
@ -1408,18 +1410,18 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
public new void PresentScore(ScoreInfo score) => base.PresentScore(score);
|
public new void PresentScore(ScoreInfo score) => base.PresentScore(score);
|
||||||
|
|
||||||
|
public int FilterCount;
|
||||||
|
|
||||||
protected override bool OnStart()
|
protected override bool OnStart()
|
||||||
{
|
{
|
||||||
StartRequested?.Invoke();
|
StartRequested?.Invoke();
|
||||||
return base.OnStart();
|
return base.OnStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int FilterCount;
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
protected override void ApplyFilterToCarousel(FilterCriteria criteria)
|
|
||||||
{
|
{
|
||||||
FilterCount++;
|
FilterControl.FilterChanged += _ => FilterCount++;
|
||||||
base.ApplyFilterToCarousel(criteria);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,7 +246,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
private BeatmapCarousel createCarousel()
|
private BeatmapCarousel createCarousel()
|
||||||
{
|
{
|
||||||
return carousel = new BeatmapCarousel
|
return carousel = new BeatmapCarousel(new FilterCriteria())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
BeatmapSets = new List<BeatmapSetInfo>
|
BeatmapSets = new List<BeatmapSetInfo>
|
||||||
|
163
osu.Game/Database/DetachedBeatmapStore.cs
Normal file
163
osu.Game/Database/DetachedBeatmapStore.cs
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using Realms;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
public partial class DetachedBeatmapStore : Component
|
||||||
|
{
|
||||||
|
private readonly ManualResetEventSlim loaded = new ManualResetEventSlim();
|
||||||
|
|
||||||
|
private readonly BindableList<BeatmapSetInfo> detachedBeatmapSets = new BindableList<BeatmapSetInfo>();
|
||||||
|
|
||||||
|
private IDisposable? realmSubscription;
|
||||||
|
|
||||||
|
private readonly Queue<OperationArgs> pendingOperations = new Queue<OperationArgs>();
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private RealmAccess realm { get; set; } = null!;
|
||||||
|
|
||||||
|
public IBindableList<BeatmapSetInfo> GetDetachedBeatmaps(CancellationToken? cancellationToken)
|
||||||
|
{
|
||||||
|
loaded.Wait(cancellationToken ?? CancellationToken.None);
|
||||||
|
return detachedBeatmapSets.GetBoundCopy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
realmSubscription = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>().Where(s => !s.DeletePending && !s.Protected), beatmapSetsChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void beatmapSetsChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes)
|
||||||
|
{
|
||||||
|
if (changes == null)
|
||||||
|
{
|
||||||
|
if (detachedBeatmapSets.Count > 0 && sender.Count == 0)
|
||||||
|
{
|
||||||
|
// Usually we'd reset stuff here, but doing so triggers a silly flow which ends up deadlocking realm.
|
||||||
|
// Additionally, user should not be at song select when realm is blocking all operations in the first place.
|
||||||
|
//
|
||||||
|
// Note that due to the catch-up logic below, once operations are restored we will still be in a roughly
|
||||||
|
// correct state. The only things that this return will change is the carousel will not empty *during* the blocking
|
||||||
|
// operation.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detaching beatmaps takes some time, so let's make sure it doesn't run on the update thread.
|
||||||
|
var frozenSets = sender.Freeze();
|
||||||
|
|
||||||
|
Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
realm.Run(_ =>
|
||||||
|
{
|
||||||
|
var detached = frozenSets.Detach();
|
||||||
|
|
||||||
|
detachedBeatmapSets.Clear();
|
||||||
|
detachedBeatmapSets.AddRange(detached);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
loaded.Set();
|
||||||
|
}
|
||||||
|
}, TaskCreationOptions.LongRunning).FireAndForget();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (int i in changes.DeletedIndices.OrderDescending())
|
||||||
|
{
|
||||||
|
pendingOperations.Enqueue(new OperationArgs
|
||||||
|
{
|
||||||
|
Type = OperationType.Remove,
|
||||||
|
Index = i,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (int i in changes.InsertedIndices)
|
||||||
|
{
|
||||||
|
pendingOperations.Enqueue(new OperationArgs
|
||||||
|
{
|
||||||
|
Type = OperationType.Insert,
|
||||||
|
BeatmapSet = sender[i].Detach(),
|
||||||
|
Index = i,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (int i in changes.NewModifiedIndices)
|
||||||
|
{
|
||||||
|
pendingOperations.Enqueue(new OperationArgs
|
||||||
|
{
|
||||||
|
Type = OperationType.Update,
|
||||||
|
BeatmapSet = sender[i].Detach(),
|
||||||
|
Index = i,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
// We can't start processing operations until we have finished detaching the initial list.
|
||||||
|
if (!loaded.IsSet)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If this ever leads to performance issues, we could dequeue a limited number of operations per update frame.
|
||||||
|
while (pendingOperations.TryDequeue(out var op))
|
||||||
|
{
|
||||||
|
switch (op.Type)
|
||||||
|
{
|
||||||
|
case OperationType.Insert:
|
||||||
|
detachedBeatmapSets.Insert(op.Index, op.BeatmapSet!);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OperationType.Update:
|
||||||
|
detachedBeatmapSets.ReplaceRange(op.Index, 1, new[] { op.BeatmapSet! });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OperationType.Remove:
|
||||||
|
detachedBeatmapSets.RemoveAt(op.Index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
loaded.Set();
|
||||||
|
loaded.Dispose();
|
||||||
|
realmSubscription?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private record OperationArgs
|
||||||
|
{
|
||||||
|
public OperationType Type;
|
||||||
|
public BeatmapSetInfo? BeatmapSet;
|
||||||
|
public int Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum OperationType
|
||||||
|
{
|
||||||
|
Insert,
|
||||||
|
Update,
|
||||||
|
Remove
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -147,6 +147,10 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor),
|
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl),
|
new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl),
|
new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.Left }, GlobalAction.EditorSeekToPreviousHitObject),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.Right }, GlobalAction.EditorSeekToNextHitObject),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.Left }, GlobalAction.EditorSeekToPreviousSamplePoint),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.Right }, GlobalAction.EditorSeekToNextSamplePoint),
|
||||||
};
|
};
|
||||||
|
|
||||||
private static IEnumerable<KeyBinding> editorTestPlayKeyBindings => new[]
|
private static IEnumerable<KeyBinding> editorTestPlayKeyBindings => new[]
|
||||||
@ -456,6 +460,18 @@ namespace osu.Game.Input.Bindings
|
|||||||
|
|
||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestPlayQuickExitToCurrentTime))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestPlayQuickExitToCurrentTime))]
|
||||||
EditorTestPlayQuickExitToCurrentTime,
|
EditorTestPlayQuickExitToCurrentTime,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToPreviousHitObject))]
|
||||||
|
EditorSeekToPreviousHitObject,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToNextHitObject))]
|
||||||
|
EditorSeekToNextHitObject,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToPreviousSamplePoint))]
|
||||||
|
EditorSeekToPreviousSamplePoint,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToNextSamplePoint))]
|
||||||
|
EditorSeekToNextSamplePoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum GlobalActionCategory
|
public enum GlobalActionCategory
|
||||||
|
@ -404,6 +404,26 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString DecreaseModSpeed => new TranslatableString(getKey(@"decrease_mod_speed"), @"Decrease mod speed");
|
public static LocalisableString DecreaseModSpeed => new TranslatableString(getKey(@"decrease_mod_speed"), @"Decrease mod speed");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Seek to previous hit object"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString EditorSeekToPreviousHitObject => new TranslatableString(getKey(@"editor_seek_to_previous_hit_object"), @"Seek to previous hit object");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Seek to next hit object"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString EditorSeekToNextHitObject => new TranslatableString(getKey(@"editor_seek_to_next_hit_object"), @"Seek to next hit object");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Seek to previous sample point"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString EditorSeekToPreviousSamplePoint => new TranslatableString(getKey(@"editor_seek_to_previous_sample_point"), @"Seek to previous sample point");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Seek to next sample point"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString EditorSeekToNextSamplePoint => new TranslatableString(getKey(@"editor_seek_to_next_sample_point"), @"Seek to next sample point");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,7 +159,7 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
private void onTokenChanged(ValueChangedEvent<OAuthToken> e) => config.SetValue(OsuSetting.Token, config.Get<bool>(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty);
|
private void onTokenChanged(ValueChangedEvent<OAuthToken> e) => config.SetValue(OsuSetting.Token, config.Get<bool>(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty);
|
||||||
|
|
||||||
internal new void Schedule(Action action) => base.Schedule(action);
|
void IAPIProvider.Schedule(Action action) => base.Schedule(action);
|
||||||
|
|
||||||
public string AccessToken => authentication.RequestAccessToken();
|
public string AccessToken => authentication.RequestAccessToken();
|
||||||
|
|
||||||
@ -385,7 +385,8 @@ namespace osu.Game.Online.API
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
request.Perform(this);
|
request.AttachAPI(this);
|
||||||
|
request.Perform();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -483,7 +484,8 @@ namespace osu.Game.Online.API
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
req.Perform(this);
|
req.AttachAPI(this);
|
||||||
|
req.Perform();
|
||||||
|
|
||||||
if (req.CompletionState != APIRequestCompletionState.Completed)
|
if (req.CompletionState != APIRequestCompletionState.Completed)
|
||||||
return false;
|
return false;
|
||||||
@ -568,6 +570,8 @@ namespace osu.Game.Online.API
|
|||||||
{
|
{
|
||||||
lock (queue)
|
lock (queue)
|
||||||
{
|
{
|
||||||
|
request.AttachAPI(this);
|
||||||
|
|
||||||
if (state.Value == APIState.Offline)
|
if (state.Value == APIState.Offline)
|
||||||
{
|
{
|
||||||
request.Fail(new WebException(@"User not logged in"));
|
request.Fail(new WebException(@"User not logged in"));
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using osu.Framework.IO.Network;
|
using osu.Framework.IO.Network;
|
||||||
|
|
||||||
@ -34,7 +35,11 @@ namespace osu.Game.Online.API
|
|||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void request_Progress(long current, long total) => API.Schedule(() => Progressed?.Invoke(current, total));
|
private void request_Progress(long current, long total)
|
||||||
|
{
|
||||||
|
Debug.Assert(API != null);
|
||||||
|
API.Schedule(() => Progressed?.Invoke(current, total));
|
||||||
|
}
|
||||||
|
|
||||||
protected void TriggerSuccess(string filename)
|
protected void TriggerSuccess(string filename)
|
||||||
{
|
{
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Extensions.TypeExtensions;
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
using osu.Framework.IO.Network;
|
using osu.Framework.IO.Network;
|
||||||
@ -26,18 +24,17 @@ namespace osu.Game.Online.API
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The deserialised response object. May be null if the request or deserialisation failed.
|
/// The deserialised response object. May be null if the request or deserialisation failed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[CanBeNull]
|
public T? Response { get; private set; }
|
||||||
public T Response { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked on successful completion of an API request.
|
/// Invoked on successful completion of an API request.
|
||||||
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public new event APISuccessHandler<T> Success;
|
public new event APISuccessHandler<T>? Success;
|
||||||
|
|
||||||
protected APIRequest()
|
protected APIRequest()
|
||||||
{
|
{
|
||||||
base.Success += () => Success?.Invoke(Response);
|
base.Success += () => Success?.Invoke(Response!);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PostProcess()
|
protected override void PostProcess()
|
||||||
@ -71,27 +68,28 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
protected virtual WebRequest CreateWebRequest() => new OsuWebRequest(Uri);
|
protected virtual WebRequest CreateWebRequest() => new OsuWebRequest(Uri);
|
||||||
|
|
||||||
protected virtual string Uri => $@"{API.APIEndpointUrl}/api/v2/{Target}";
|
protected virtual string Uri => $@"{API!.APIEndpointUrl}/api/v2/{Target}";
|
||||||
|
|
||||||
protected APIAccess API;
|
protected IAPIProvider? API;
|
||||||
protected WebRequest WebRequest;
|
|
||||||
|
protected WebRequest? WebRequest;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The currently logged in user. Note that this will only be populated during <see cref="Perform"/>.
|
/// The currently logged in user. Note that this will only be populated during <see cref="Perform"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected APIUser User { get; private set; }
|
protected APIUser? User { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked on successful completion of an API request.
|
/// Invoked on successful completion of an API request.
|
||||||
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event APISuccessHandler Success;
|
public event APISuccessHandler? Success;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked on failure to complete an API request.
|
/// Invoked on failure to complete an API request.
|
||||||
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event APIFailureHandler Failure;
|
public event APIFailureHandler? Failure;
|
||||||
|
|
||||||
private readonly object completionStateLock = new object();
|
private readonly object completionStateLock = new object();
|
||||||
|
|
||||||
@ -101,16 +99,29 @@ namespace osu.Game.Online.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public APIRequestCompletionState CompletionState { get; private set; }
|
public APIRequestCompletionState CompletionState { get; private set; }
|
||||||
|
|
||||||
public void Perform(IAPIProvider api)
|
/// <summary>
|
||||||
|
/// Should be called before <see cref="Perform"/> to give API context.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This allows scheduling of operations back to the correct thread (which may be required before <see cref="Perform"/> is called).
|
||||||
|
/// </remarks>
|
||||||
|
public void AttachAPI(IAPIProvider apiAccess)
|
||||||
{
|
{
|
||||||
if (!(api is APIAccess apiAccess))
|
if (API != null && API != apiAccess)
|
||||||
|
throw new InvalidOperationException("Attached API cannot be changed after initial set.");
|
||||||
|
|
||||||
|
API = apiAccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Perform()
|
||||||
|
{
|
||||||
|
if (API == null)
|
||||||
{
|
{
|
||||||
Fail(new NotSupportedException($"A {nameof(APIAccess)} is required to perform requests."));
|
Fail(new NotSupportedException($"A {nameof(APIAccess)} is required to perform requests."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
API = apiAccess;
|
User = API.LocalUser.Value;
|
||||||
User = apiAccess.LocalUser.Value;
|
|
||||||
|
|
||||||
if (isFailing) return;
|
if (isFailing) return;
|
||||||
|
|
||||||
@ -153,6 +164,8 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
internal void TriggerSuccess()
|
internal void TriggerSuccess()
|
||||||
{
|
{
|
||||||
|
Debug.Assert(API != null);
|
||||||
|
|
||||||
lock (completionStateLock)
|
lock (completionStateLock)
|
||||||
{
|
{
|
||||||
if (CompletionState != APIRequestCompletionState.Waiting)
|
if (CompletionState != APIRequestCompletionState.Waiting)
|
||||||
@ -161,14 +174,13 @@ namespace osu.Game.Online.API
|
|||||||
CompletionState = APIRequestCompletionState.Completed;
|
CompletionState = APIRequestCompletionState.Completed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (API == null)
|
API.Schedule(() => Success?.Invoke());
|
||||||
Success?.Invoke();
|
|
||||||
else
|
|
||||||
API.Schedule(() => Success?.Invoke());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void TriggerFailure(Exception e)
|
internal void TriggerFailure(Exception e)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(API != null);
|
||||||
|
|
||||||
lock (completionStateLock)
|
lock (completionStateLock)
|
||||||
{
|
{
|
||||||
if (CompletionState != APIRequestCompletionState.Waiting)
|
if (CompletionState != APIRequestCompletionState.Waiting)
|
||||||
@ -177,10 +189,7 @@ namespace osu.Game.Online.API
|
|||||||
CompletionState = APIRequestCompletionState.Failed;
|
CompletionState = APIRequestCompletionState.Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (API == null)
|
API.Schedule(() => Failure?.Invoke(e));
|
||||||
Failure?.Invoke(e);
|
|
||||||
else
|
|
||||||
API.Schedule(() => Failure?.Invoke(e));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled"));
|
public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled"));
|
||||||
@ -197,7 +206,7 @@ namespace osu.Game.Online.API
|
|||||||
// in the case of a cancellation we don't care about whether there's an error in the response.
|
// in the case of a cancellation we don't care about whether there's an error in the response.
|
||||||
if (!(e is OperationCanceledException))
|
if (!(e is OperationCanceledException))
|
||||||
{
|
{
|
||||||
string responseString = WebRequest?.GetResponseString();
|
string? responseString = WebRequest?.GetResponseString();
|
||||||
|
|
||||||
// naive check whether there's an error in the response to avoid unnecessary JSON deserialisation.
|
// naive check whether there's an error in the response to avoid unnecessary JSON deserialisation.
|
||||||
if (!string.IsNullOrEmpty(responseString) && responseString.Contains(@"""error"""))
|
if (!string.IsNullOrEmpty(responseString) && responseString.Contains(@"""error"""))
|
||||||
@ -235,7 +244,7 @@ namespace osu.Game.Online.API
|
|||||||
private class DisplayableError
|
private class DisplayableError
|
||||||
{
|
{
|
||||||
[JsonProperty("error")]
|
[JsonProperty("error")]
|
||||||
public string ErrorMessage { get; set; }
|
public string ErrorMessage { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +82,8 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
public virtual void Queue(APIRequest request)
|
public virtual void Queue(APIRequest request)
|
||||||
{
|
{
|
||||||
|
request.AttachAPI(this);
|
||||||
|
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
if (HandleRequest?.Invoke(request) != true)
|
if (HandleRequest?.Invoke(request) != true)
|
||||||
@ -98,10 +100,17 @@ namespace osu.Game.Online.API
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Perform(APIRequest request) => HandleRequest?.Invoke(request);
|
void IAPIProvider.Schedule(Action action) => base.Schedule(action);
|
||||||
|
|
||||||
|
public void Perform(APIRequest request)
|
||||||
|
{
|
||||||
|
request.AttachAPI(this);
|
||||||
|
HandleRequest?.Invoke(request);
|
||||||
|
}
|
||||||
|
|
||||||
public Task PerformAsync(APIRequest request)
|
public Task PerformAsync(APIRequest request)
|
||||||
{
|
{
|
||||||
|
request.AttachAPI(this);
|
||||||
HandleRequest?.Invoke(request);
|
HandleRequest?.Invoke(request);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@ -155,6 +164,8 @@ namespace osu.Game.Online.API
|
|||||||
state.Value = APIState.Connecting;
|
state.Value = APIState.Connecting;
|
||||||
LastLoginError = null;
|
LastLoginError = null;
|
||||||
|
|
||||||
|
request.AttachAPI(this);
|
||||||
|
|
||||||
// if no handler installed / handler can't handle verification, just assume that the server would verify for simplicity.
|
// if no handler installed / handler can't handle verification, just assume that the server would verify for simplicity.
|
||||||
if (HandleRequest?.Invoke(request) != true)
|
if (HandleRequest?.Invoke(request) != true)
|
||||||
onSuccessfulLogin();
|
onSuccessfulLogin();
|
||||||
|
@ -134,6 +134,11 @@ namespace osu.Game.Online.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void UpdateStatistics(UserStatistics newStatistics);
|
void UpdateStatistics(UserStatistics newStatistics);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Schedule a callback to run on the update thread.
|
||||||
|
/// </summary>
|
||||||
|
internal void Schedule(Action action);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a new <see cref="IHubClientConnector"/>. May be null if not supported.
|
/// Constructs a new <see cref="IHubClientConnector"/>. May be null if not supported.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -23,6 +23,6 @@ namespace osu.Game.Online.API.Requests
|
|||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string Target => $@"chat/channels/{channel.Id}/users/{User.Id}";
|
protected override string Target => $@"chat/channels/{channel.Id}/users/{User!.Id}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,6 @@ namespace osu.Game.Online.API.Requests
|
|||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string Target => $@"chat/channels/{channel.Id}/users/{User.Id}";
|
protected override string Target => $@"chat/channels/{channel.Id}/users/{User!.Id}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ namespace osu.Game.Online.Chat
|
|||||||
|
|
||||||
fetchReq.Success += updates =>
|
fetchReq.Success += updates =>
|
||||||
{
|
{
|
||||||
if (updates?.Presence != null)
|
if (updates.Presence != null)
|
||||||
{
|
{
|
||||||
foreach (var channel in updates.Presence)
|
foreach (var channel in updates.Presence)
|
||||||
joinChannel(channel);
|
joinChannel(channel);
|
||||||
|
@ -27,6 +27,6 @@ namespace osu.Game.Online.Rooms
|
|||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string Target => $@"rooms/{Room.RoomID.Value}/users/{User.Id}";
|
protected override string Target => $@"rooms/{Room.RoomID.Value}/users/{User!.Id}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,6 @@ namespace osu.Game.Online.Rooms
|
|||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string Target => $"rooms/{room.RoomID.Value}/users/{User.Id}";
|
protected override string Target => $"rooms/{room.RoomID.Value}/users/{User!.Id}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1141,6 +1141,7 @@ namespace osu.Game
|
|||||||
loadComponentSingleFile(new MedalOverlay(), topMostOverlayContent.Add);
|
loadComponentSingleFile(new MedalOverlay(), topMostOverlayContent.Add);
|
||||||
|
|
||||||
loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add);
|
loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add);
|
||||||
|
loadComponentSingleFile(new DetachedBeatmapStore(), Add, true);
|
||||||
|
|
||||||
Add(difficultyRecommender);
|
Add(difficultyRecommender);
|
||||||
Add(externalLinkOpener = new ExternalLinkOpener());
|
Add(externalLinkOpener = new ExternalLinkOpener());
|
||||||
|
@ -692,7 +692,7 @@ namespace osu.Game
|
|||||||
if (Interlocked.Decrement(ref allowableExceptions) < 0)
|
if (Interlocked.Decrement(ref allowableExceptions) < 0)
|
||||||
{
|
{
|
||||||
Logger.Log("Too many unhandled exceptions, crashing out.");
|
Logger.Log("Too many unhandled exceptions, crashing out.");
|
||||||
RulesetStore.TryDisableCustomRulesetsCausing(ex);
|
RulesetStore?.TryDisableCustomRulesetsCausing(ex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Comments
|
|||||||
|
|
||||||
public readonly BindableList<DrawableComment> Replies = new BindableList<DrawableComment>();
|
public readonly BindableList<DrawableComment> Replies = new BindableList<DrawableComment>();
|
||||||
|
|
||||||
private readonly BindableBool childrenExpanded = new BindableBool(true);
|
private readonly BindableBool childrenExpanded;
|
||||||
|
|
||||||
private int currentPage;
|
private int currentPage;
|
||||||
|
|
||||||
@ -92,6 +92,8 @@ namespace osu.Game.Overlays.Comments
|
|||||||
{
|
{
|
||||||
Comment = comment;
|
Comment = comment;
|
||||||
Meta = meta;
|
Meta = meta;
|
||||||
|
|
||||||
|
childrenExpanded = new BindableBool(!comment.Pinned);
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
|
@ -115,7 +115,7 @@ namespace osu.Game.Overlays
|
|||||||
seekDelegate?.Cancel();
|
seekDelegate?.Cancel();
|
||||||
seekDelegate = Schedule(() =>
|
seekDelegate = Schedule(() =>
|
||||||
{
|
{
|
||||||
if (beatmap.Disabled || !AllowTrackControl.Value)
|
if (!AllowTrackControl.Value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
CurrentTrack.Seek(position);
|
CurrentTrack.Seek(position);
|
||||||
|
@ -23,15 +23,15 @@ namespace osu.Game.Overlays.Volume
|
|||||||
{
|
{
|
||||||
case GlobalAction.DecreaseVolume:
|
case GlobalAction.DecreaseVolume:
|
||||||
case GlobalAction.IncreaseVolume:
|
case GlobalAction.IncreaseVolume:
|
||||||
ActionRequested?.Invoke(e.Action);
|
return ActionRequested?.Invoke(e.Action) == true;
|
||||||
return true;
|
|
||||||
|
|
||||||
case GlobalAction.ToggleMute:
|
case GlobalAction.ToggleMute:
|
||||||
case GlobalAction.NextVolumeMeter:
|
case GlobalAction.NextVolumeMeter:
|
||||||
case GlobalAction.PreviousVolumeMeter:
|
case GlobalAction.PreviousVolumeMeter:
|
||||||
if (!e.Repeat)
|
if (!e.Repeat)
|
||||||
ActionRequested?.Invoke(e.Action);
|
return ActionRequested?.Invoke(e.Action) == true;
|
||||||
return true;
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -110,14 +110,18 @@ namespace osu.Game.Overlays
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case GlobalAction.NextVolumeMeter:
|
case GlobalAction.NextVolumeMeter:
|
||||||
if (State.Value == Visibility.Visible)
|
if (State.Value != Visibility.Visible)
|
||||||
volumeMeters.SelectNext();
|
return false;
|
||||||
|
|
||||||
|
volumeMeters.SelectNext();
|
||||||
Show();
|
Show();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case GlobalAction.PreviousVolumeMeter:
|
case GlobalAction.PreviousVolumeMeter:
|
||||||
if (State.Value == Visibility.Visible)
|
if (State.Value != Visibility.Visible)
|
||||||
volumeMeters.SelectPrevious();
|
return false;
|
||||||
|
|
||||||
|
volumeMeters.SelectPrevious();
|
||||||
Show();
|
Show();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -125,10 +125,6 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
case GlobalAction.Select:
|
|
||||||
EndPlacement(true);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case GlobalAction.Back:
|
case GlobalAction.Back:
|
||||||
EndPlacement(false);
|
EndPlacement(false);
|
||||||
return true;
|
return true;
|
||||||
|
@ -401,5 +401,10 @@ namespace osu.Game.Rulesets
|
|||||||
new DifficultySection(),
|
new DifficultySection(),
|
||||||
new ColoursSection(),
|
new ColoursSection(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Can be overridden to avoid showing scroll speed changes in the editor.
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool EditorShowScrollSpeed => true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,8 +64,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
MaxValue = time_span_max
|
MaxValue = time_span_max
|
||||||
};
|
};
|
||||||
|
|
||||||
ScrollVisualisationMethod IDrawableScrollingRuleset.VisualisationMethod => VisualisationMethod;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the player can change <see cref="TimeRange"/>.
|
/// Whether the player can change <see cref="TimeRange"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Configuration;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.UI.Scrolling
|
namespace osu.Game.Rulesets.UI.Scrolling
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -10,8 +8,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IDrawableScrollingRuleset
|
public interface IDrawableScrollingRuleset
|
||||||
{
|
{
|
||||||
ScrollVisualisationMethod VisualisationMethod { get; }
|
|
||||||
|
|
||||||
IScrollingInfo ScrollingInfo { get; }
|
IScrollingInfo ScrollingInfo { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,125 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Cursor;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Localisation;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Extensions;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|
||||||
{
|
|
||||||
public partial class EffectPointVisualisation : CompositeDrawable, IControlPointVisualisation
|
|
||||||
{
|
|
||||||
private readonly EffectControlPoint effect;
|
|
||||||
private Bindable<bool> kiai = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private EditorBeatmap beatmap { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private OsuColour colours { get; set; } = null!;
|
|
||||||
|
|
||||||
public EffectPointVisualisation(EffectControlPoint point)
|
|
||||||
{
|
|
||||||
RelativePositionAxes = Axes.Both;
|
|
||||||
RelativeSizeAxes = Axes.Y;
|
|
||||||
|
|
||||||
effect = point;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
kiai = effect.KiaiModeBindable.GetBoundCopy();
|
|
||||||
kiai.BindValueChanged(_ => refreshDisplay(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private EffectControlPoint? nextControlPoint;
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
// Due to the limitations of ControlPointInfo, it's impossible to know via event flow when the next kiai point has changed.
|
|
||||||
// This is due to the fact that an EffectPoint can be added to an existing group. We would need to bind to ItemAdded on *every*
|
|
||||||
// future group to track this.
|
|
||||||
//
|
|
||||||
// I foresee this being a potential performance issue on beatmaps with many control points, so let's limit how often we check
|
|
||||||
// for changes. ControlPointInfo needs a refactor to make this flow better, but it should do for now.
|
|
||||||
Scheduler.AddDelayed(() =>
|
|
||||||
{
|
|
||||||
EffectControlPoint? next = null;
|
|
||||||
|
|
||||||
for (int i = 0; i < beatmap.ControlPointInfo.EffectPoints.Count; i++)
|
|
||||||
{
|
|
||||||
var point = beatmap.ControlPointInfo.EffectPoints[i];
|
|
||||||
|
|
||||||
if (point.Time > effect.Time)
|
|
||||||
{
|
|
||||||
next = point;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ReferenceEquals(nextControlPoint, next))
|
|
||||||
{
|
|
||||||
nextControlPoint = next;
|
|
||||||
refreshDisplay();
|
|
||||||
}
|
|
||||||
}, 100, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshDisplay()
|
|
||||||
{
|
|
||||||
ClearInternal();
|
|
||||||
|
|
||||||
AddInternal(new ControlPointVisualisation(effect));
|
|
||||||
|
|
||||||
if (!kiai.Value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// handle kiai duration
|
|
||||||
// eventually this will be simpler when we have control points with durations.
|
|
||||||
if (nextControlPoint != null)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
Origin = Anchor.TopLeft;
|
|
||||||
|
|
||||||
Width = (float)(nextControlPoint.Time - effect.Time);
|
|
||||||
|
|
||||||
AddInternal(new KiaiVisualisation(effect.Time, nextControlPoint.Time)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Anchor = Anchor.BottomLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Height = 0.4f,
|
|
||||||
Depth = float.MaxValue,
|
|
||||||
Colour = colours.Purple1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private partial class KiaiVisualisation : FastCircle, IHasTooltip
|
|
||||||
{
|
|
||||||
private readonly double startTime;
|
|
||||||
private readonly double endTime;
|
|
||||||
|
|
||||||
public KiaiVisualisation(double startTime, double endTime)
|
|
||||||
{
|
|
||||||
this.startTime = startTime;
|
|
||||||
this.endTime = endTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LocalisableString TooltipText => $"{startTime.ToEditorFormattedString()} - {endTime.ToEditorFormattedString()} kiai time";
|
|
||||||
}
|
|
||||||
|
|
||||||
// kiai sections display duration, so are required to be visualised.
|
|
||||||
public bool IsVisuallyRedundant(ControlPoint other) => other is EffectControlPoint otherEffect && effect.KiaiMode == otherEffect.KiaiMode;
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -15,6 +16,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
|
|
||||||
private readonly IBindableList<ControlPoint> controlPoints = new BindableList<ControlPoint>();
|
private readonly IBindableList<ControlPoint> controlPoints = new BindableList<ControlPoint>();
|
||||||
|
|
||||||
|
private bool showScrollSpeed;
|
||||||
|
|
||||||
public GroupVisualisation(ControlPointGroup group)
|
public GroupVisualisation(ControlPointGroup group)
|
||||||
{
|
{
|
||||||
RelativePositionAxes = Axes.X;
|
RelativePositionAxes = Axes.X;
|
||||||
@ -24,8 +27,13 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
|
|
||||||
Group = group;
|
Group = group;
|
||||||
X = (float)group.Time;
|
X = (float)group.Time;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(EditorBeatmap beatmap)
|
||||||
|
{
|
||||||
|
showScrollSpeed = beatmap.BeatmapInfo.Ruleset.CreateInstance().EditorShowScrollSpeed;
|
||||||
|
|
||||||
// Run in constructor so IsRedundant calls can work correctly.
|
|
||||||
controlPoints.BindTo(Group.ControlPoints);
|
controlPoints.BindTo(Group.ControlPoints);
|
||||||
controlPoints.BindCollectionChanged((_, _) =>
|
controlPoints.BindCollectionChanged((_, _) =>
|
||||||
{
|
{
|
||||||
@ -47,8 +55,15 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EffectControlPoint effect:
|
case EffectControlPoint:
|
||||||
AddInternal(new EffectPointVisualisation(effect));
|
if (!showScrollSpeed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AddInternal(new ControlPointVisualisation(point)
|
||||||
|
{
|
||||||
|
// importantly, override the x position being set since we do that above.
|
||||||
|
X = 0,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Pooling;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The part of the timeline that displays kiai sections in the song.
|
||||||
|
/// </summary>
|
||||||
|
public partial class KiaiPart : TimelinePart
|
||||||
|
{
|
||||||
|
private DrawablePool<KiaiVisualisation> pool = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AddInternal(pool = new DrawablePool<KiaiVisualisation>(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadBeatmap(EditorBeatmap beatmap)
|
||||||
|
{
|
||||||
|
base.LoadBeatmap(beatmap);
|
||||||
|
EditorBeatmap.ControlPointInfo.ControlPointsChanged += updateParts;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
updateParts();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateParts() => Scheduler.AddOnce(() =>
|
||||||
|
{
|
||||||
|
Clear(disposeChildren: false);
|
||||||
|
|
||||||
|
double? startTime = null;
|
||||||
|
|
||||||
|
foreach (var effectPoint in EditorBeatmap.ControlPointInfo.EffectPoints)
|
||||||
|
{
|
||||||
|
if (startTime.HasValue)
|
||||||
|
{
|
||||||
|
if (effectPoint.KiaiMode)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var section = new KiaiSection
|
||||||
|
{
|
||||||
|
StartTime = startTime.Value,
|
||||||
|
EndTime = effectPoint.Time
|
||||||
|
};
|
||||||
|
|
||||||
|
Add(pool.Get(v => v.Section = section));
|
||||||
|
|
||||||
|
startTime = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!effectPoint.KiaiMode)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
startTime = effectPoint.Time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// last effect point has kiai enabled, kiai should last until the end of the map
|
||||||
|
if (startTime.HasValue)
|
||||||
|
{
|
||||||
|
Add(pool.Get(v => v.Section = new KiaiSection
|
||||||
|
{
|
||||||
|
StartTime = startTime.Value,
|
||||||
|
EndTime = Content.RelativeChildSize.X
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private partial class KiaiVisualisation : PoolableDrawable, IHasTooltip
|
||||||
|
{
|
||||||
|
private KiaiSection section;
|
||||||
|
|
||||||
|
public KiaiSection Section
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
section = value;
|
||||||
|
|
||||||
|
X = (float)value.StartTime;
|
||||||
|
Width = (float)value.Duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
RelativePositionAxes = Axes.X;
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Anchor = Anchor.CentreLeft;
|
||||||
|
Origin = Anchor.CentreLeft;
|
||||||
|
Height = 0.2f;
|
||||||
|
AddInternal(new Circle
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colours.Purple1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalisableString TooltipText => $"{section.StartTime.ToEditorFormattedString()} - {section.EndTime.ToEditorFormattedString()} kiai time";
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly struct KiaiSection
|
||||||
|
{
|
||||||
|
public double StartTime { get; init; }
|
||||||
|
public double EndTime { get; init; }
|
||||||
|
public double Duration => EndTime - StartTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -65,6 +65,12 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
|
new KiaiPart
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
new ControlPointPart
|
new ControlPointPart
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
|
@ -229,7 +229,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
EditorBeatmap.PerformOnSelection(h =>
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
{
|
{
|
||||||
if (h.Samples.All(s => s.Bank == bankName))
|
if (hasRelevantBank(h))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
h.Samples = h.Samples.Select(s => s.With(newBank: bankName)).ToList();
|
h.Samples = h.Samples.Select(s => s.With(newBank: bankName)).ToList();
|
||||||
@ -269,10 +269,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
EditorBeatmap.PerformOnSelection(h =>
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
{
|
{
|
||||||
// Make sure there isn't already an existing sample
|
// Make sure there isn't already an existing sample
|
||||||
if (h.Samples.Any(s => s.Name == sampleName))
|
if (h.Samples.All(s => s.Name != sampleName))
|
||||||
return;
|
h.Samples.Add(h.CreateHitSampleInfo(sampleName));
|
||||||
|
|
||||||
h.Samples.Add(h.CreateHitSampleInfo(sampleName));
|
|
||||||
|
|
||||||
if (h is IHasRepeats hasRepeats)
|
if (h is IHasRepeats hasRepeats)
|
||||||
{
|
{
|
||||||
|
@ -22,6 +22,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
NodeIndex = nodeIndex;
|
NodeIndex = nodeIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override double GetTime()
|
||||||
|
{
|
||||||
|
var hasRepeats = (IHasRepeats)HitObject;
|
||||||
|
return HitObject.StartTime + hasRepeats.Duration * NodeIndex / hasRepeats.SpanCount();
|
||||||
|
}
|
||||||
|
|
||||||
protected override IList<HitSampleInfo> GetSamples()
|
protected override IList<HitSampleInfo> GetSamples()
|
||||||
{
|
{
|
||||||
var hasRepeats = (IHasRepeats)HitObject;
|
var hasRepeats = (IHasRepeats)HitObject;
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -33,6 +34,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
{
|
{
|
||||||
public readonly HitObject HitObject;
|
public readonly HitObject HitObject;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorClock? editorClock { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Editor? editor { get; set; }
|
||||||
|
|
||||||
public SamplePointPiece(HitObject hitObject)
|
public SamplePointPiece(HitObject hitObject)
|
||||||
{
|
{
|
||||||
HitObject = hitObject;
|
HitObject = hitObject;
|
||||||
@ -43,11 +50,32 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
protected override Color4 GetRepresentingColour(OsuColour colours) => AlternativeColor ? colours.Pink2 : colours.Pink1;
|
protected override Color4 GetRepresentingColour(OsuColour colours) => AlternativeColor ? colours.Pink2 : colours.Pink1;
|
||||||
|
|
||||||
|
protected virtual double GetTime() => HitObject is IHasRepeats r ? HitObject.StartTime + r.Duration / r.SpanCount() / 2 : HitObject.StartTime;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
HitObject.DefaultsApplied += _ => updateText();
|
HitObject.DefaultsApplied += _ => updateText();
|
||||||
updateText();
|
updateText();
|
||||||
|
|
||||||
|
if (editor != null)
|
||||||
|
editor.ShowSampleEditPopoverRequested += onShowSampleEditPopoverRequested;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (editor != null)
|
||||||
|
editor.ShowSampleEditPopoverRequested -= onShowSampleEditPopoverRequested;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onShowSampleEditPopoverRequested(double time)
|
||||||
|
{
|
||||||
|
if (!Precision.AlmostEquals(time, GetTime())) return;
|
||||||
|
|
||||||
|
editorClock?.SeekSmoothlyTo(GetTime());
|
||||||
|
this.ShowPopover();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
|
@ -44,6 +44,7 @@ using osu.Game.Overlays.OSD;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Screens.Edit.Components.Menus;
|
using osu.Game.Screens.Edit.Components.Menus;
|
||||||
using osu.Game.Screens.Edit.Compose;
|
using osu.Game.Screens.Edit.Compose;
|
||||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
@ -224,6 +225,9 @@ namespace osu.Game.Screens.Edit
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public Bindable<bool> ComposerFocusMode { get; } = new Bindable<bool>();
|
public Bindable<bool> ComposerFocusMode { get; } = new Bindable<bool>();
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
public event Action<double> ShowSampleEditPopoverRequested;
|
||||||
|
|
||||||
public Editor(EditorLoader loader = null)
|
public Editor(EditorLoader loader = null)
|
||||||
{
|
{
|
||||||
this.loader = loader;
|
this.loader = loader;
|
||||||
@ -713,6 +717,26 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
{
|
{
|
||||||
|
// Repeatable actions
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case GlobalAction.EditorSeekToPreviousHitObject:
|
||||||
|
seekHitObject(-1);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case GlobalAction.EditorSeekToNextHitObject:
|
||||||
|
seekHitObject(1);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case GlobalAction.EditorSeekToPreviousSamplePoint:
|
||||||
|
seekSamplePoint(-1);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case GlobalAction.EditorSeekToNextSamplePoint:
|
||||||
|
seekSamplePoint(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (e.Repeat)
|
if (e.Repeat)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -750,10 +774,9 @@ namespace osu.Game.Screens.Edit
|
|||||||
case GlobalAction.EditorTestGameplay:
|
case GlobalAction.EditorTestGameplay:
|
||||||
bottomBar.TestGameplayButton.TriggerClick();
|
bottomBar.TestGameplayButton.TriggerClick();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||||
@ -1077,6 +1100,66 @@ namespace osu.Game.Screens.Edit
|
|||||||
clock.Seek(found.Time);
|
clock.Seek(found.Time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void seekHitObject(int direction)
|
||||||
|
{
|
||||||
|
var found = direction < 1
|
||||||
|
? editorBeatmap.HitObjects.LastOrDefault(p => p.StartTime < clock.CurrentTimeAccurate)
|
||||||
|
: editorBeatmap.HitObjects.FirstOrDefault(p => p.StartTime > clock.CurrentTimeAccurate);
|
||||||
|
|
||||||
|
if (found != null)
|
||||||
|
clock.SeekSmoothlyTo(found.StartTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seekSamplePoint(int direction)
|
||||||
|
{
|
||||||
|
double currentTime = clock.CurrentTimeAccurate;
|
||||||
|
|
||||||
|
// Check if we are currently inside a hit object with node samples, if so seek to the next node sample point
|
||||||
|
var current = direction < 1
|
||||||
|
? editorBeatmap.HitObjects.LastOrDefault(p => p is IHasRepeats r && p.StartTime < currentTime && r.EndTime >= currentTime)
|
||||||
|
: editorBeatmap.HitObjects.LastOrDefault(p => p is IHasRepeats r && p.StartTime <= currentTime && r.EndTime > currentTime);
|
||||||
|
|
||||||
|
if (current != null)
|
||||||
|
{
|
||||||
|
// Find the next node sample point
|
||||||
|
var r = (IHasRepeats)current;
|
||||||
|
double[] nodeSamplePointTimes = new double[r.RepeatCount + 3];
|
||||||
|
|
||||||
|
nodeSamplePointTimes[0] = current.StartTime;
|
||||||
|
// The sample point for the main samples is sandwiched between the head and the first repeat
|
||||||
|
nodeSamplePointTimes[1] = current.StartTime + r.Duration / r.SpanCount() / 2;
|
||||||
|
|
||||||
|
for (int i = 0; i < r.SpanCount(); i++)
|
||||||
|
{
|
||||||
|
nodeSamplePointTimes[i + 2] = current.StartTime + r.Duration * (i + 1) / r.SpanCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
double found = direction < 1
|
||||||
|
? nodeSamplePointTimes.Last(p => p < currentTime)
|
||||||
|
: nodeSamplePointTimes.First(p => p > currentTime);
|
||||||
|
|
||||||
|
clock.SeekSmoothlyTo(found);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (direction < 1)
|
||||||
|
{
|
||||||
|
current = editorBeatmap.HitObjects.LastOrDefault(p => p.StartTime < currentTime);
|
||||||
|
if (current != null)
|
||||||
|
clock.SeekSmoothlyTo(current is IHasRepeats r ? r.EndTime : current.StartTime);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
current = editorBeatmap.HitObjects.FirstOrDefault(p => p.StartTime > currentTime);
|
||||||
|
if (current != null)
|
||||||
|
clock.SeekSmoothlyTo(current.StartTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the sample edit popover at the current time
|
||||||
|
ShowSampleEditPopoverRequested?.Invoke(clock.CurrentTimeAccurate);
|
||||||
|
}
|
||||||
|
|
||||||
private void seek(UIEvent e, int direction)
|
private void seek(UIEvent e, int direction)
|
||||||
{
|
{
|
||||||
double amount = e.ShiftPressed ? 4 : 1;
|
double amount = e.ShiftPressed ? 4 : 1;
|
||||||
|
@ -5,9 +5,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Configuration;
|
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Timing
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
{
|
{
|
||||||
@ -38,8 +36,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
kiai.Current.BindValueChanged(_ => saveChanges());
|
kiai.Current.BindValueChanged(_ => saveChanges());
|
||||||
scrollSpeedSlider.Current.BindValueChanged(_ => saveChanges());
|
scrollSpeedSlider.Current.BindValueChanged(_ => saveChanges());
|
||||||
|
|
||||||
var drawableRuleset = Beatmap.BeatmapInfo.Ruleset.CreateInstance().CreateDrawableRulesetWith(Beatmap.PlayableBeatmap);
|
if (!Beatmap.BeatmapInfo.Ruleset.CreateInstance().EditorShowScrollSpeed)
|
||||||
if (drawableRuleset is not IDrawableScrollingRuleset scrollingRuleset || scrollingRuleset.VisualisationMethod == ScrollVisualisationMethod.Constant)
|
|
||||||
scrollSpeedSlider.Hide();
|
scrollSpeedSlider.Hide();
|
||||||
|
|
||||||
void saveChanges()
|
void saveChanges()
|
||||||
|
@ -15,6 +15,10 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes
|
|||||||
|
|
||||||
private AttributeText kiaiModeBubble = null!;
|
private AttributeText kiaiModeBubble = null!;
|
||||||
private AttributeText text = null!;
|
private AttributeText text = null!;
|
||||||
|
private AttributeProgressBar progressBar = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
protected EditorBeatmap Beatmap { get; private set; } = null!;
|
||||||
|
|
||||||
public EffectRowAttribute(EffectControlPoint effect)
|
public EffectRowAttribute(EffectControlPoint effect)
|
||||||
: base(effect, "effect")
|
: base(effect, "effect")
|
||||||
@ -28,7 +32,7 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes
|
|||||||
{
|
{
|
||||||
Content.AddRange(new Drawable[]
|
Content.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
new AttributeProgressBar(Point)
|
progressBar = new AttributeProgressBar(Point)
|
||||||
{
|
{
|
||||||
Current = scrollSpeed,
|
Current = scrollSpeed,
|
||||||
},
|
},
|
||||||
@ -36,6 +40,12 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes
|
|||||||
kiaiModeBubble = new AttributeText(Point) { Text = "kiai" },
|
kiaiModeBubble = new AttributeText(Point) { Text = "kiai" },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!Beatmap.BeatmapInfo.Ruleset.CreateInstance().EditorShowScrollSpeed)
|
||||||
|
{
|
||||||
|
text.Hide();
|
||||||
|
progressBar.Hide();
|
||||||
|
}
|
||||||
|
|
||||||
kiaiMode.BindValueChanged(enabled => kiaiModeBubble.FadeTo(enabled.NewValue ? 1 : 0), true);
|
kiaiMode.BindValueChanged(enabled => kiaiModeBubble.FadeTo(enabled.NewValue ? 1 : 0), true);
|
||||||
scrollSpeed.BindValueChanged(_ => updateText(), true);
|
scrollSpeed.BindValueChanged(_ => updateText(), true);
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,6 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
public override bool? AllowGlobalTrackControl => true;
|
public override bool? AllowGlobalTrackControl => true;
|
||||||
|
|
||||||
private Screen songSelect;
|
|
||||||
|
|
||||||
private MenuSideFlashes sideFlashes;
|
private MenuSideFlashes sideFlashes;
|
||||||
|
|
||||||
protected ButtonSystem Buttons;
|
protected ButtonSystem Buttons;
|
||||||
@ -220,26 +218,11 @@ namespace osu.Game.Screens.Menu
|
|||||||
Buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility();
|
Buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility();
|
||||||
|
|
||||||
reappearSampleSwoosh = audio.Samples.Get(@"Menu/reappear-swoosh");
|
reappearSampleSwoosh = audio.Samples.Get(@"Menu/reappear-swoosh");
|
||||||
|
|
||||||
preloadSongSelect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReturnToOsuLogo() => Buttons.State = ButtonSystemState.Initial;
|
public void ReturnToOsuLogo() => Buttons.State = ButtonSystemState.Initial;
|
||||||
|
|
||||||
private void preloadSongSelect()
|
private void loadSoloSongSelect() => this.Push(new PlaySongSelect());
|
||||||
{
|
|
||||||
if (songSelect == null)
|
|
||||||
LoadComponentAsync(songSelect = new PlaySongSelect());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadSoloSongSelect() => this.Push(consumeSongSelect());
|
|
||||||
|
|
||||||
private Screen consumeSongSelect()
|
|
||||||
{
|
|
||||||
var s = songSelect;
|
|
||||||
songSelect = null;
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnEntering(ScreenTransitionEvent e)
|
public override void OnEntering(ScreenTransitionEvent e)
|
||||||
{
|
{
|
||||||
@ -373,9 +356,6 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
ApplyToBackground(b => (b as BackgroundScreenDefault)?.Next());
|
ApplyToBackground(b => (b as BackgroundScreenDefault)?.Next());
|
||||||
|
|
||||||
// we may have consumed our preloaded instance, so let's make another.
|
|
||||||
preloadSongSelect();
|
|
||||||
|
|
||||||
musicController.EnsurePlayingSomething();
|
musicController.EnsurePlayingSomething();
|
||||||
|
|
||||||
// Cycle tip on resuming
|
// Cycle tip on resuming
|
||||||
|
@ -108,7 +108,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
if (client.IsNotNull())
|
if (client.IsNotNull())
|
||||||
|
{
|
||||||
client.RoomUpdated -= onRoomUpdated;
|
client.RoomUpdated -= onRoomUpdated;
|
||||||
|
client.GameplayAborted -= onGameplayAborted;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
@ -21,6 +23,7 @@ using osu.Framework.Utils;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Screens.Select.Carousel;
|
using osu.Game.Screens.Select.Carousel;
|
||||||
@ -76,8 +79,6 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
private CarouselBeatmapSet? selectedBeatmapSet;
|
private CarouselBeatmapSet? selectedBeatmapSet;
|
||||||
|
|
||||||
private List<BeatmapSetInfo> originalBeatmapSetsDetached = new List<BeatmapSetInfo>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raised when the <see cref="SelectedBeatmapInfo"/> is changed.
|
/// Raised when the <see cref="SelectedBeatmapInfo"/> is changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -109,28 +110,38 @@ namespace osu.Game.Screens.Select
|
|||||||
[Cached]
|
[Cached]
|
||||||
protected readonly CarouselScrollContainer Scroll;
|
protected readonly CarouselScrollContainer Scroll;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private RealmAccess realm { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private DetachedBeatmapStore? detachedBeatmapStore { get; set; }
|
||||||
|
|
||||||
|
private IBindableList<BeatmapSetInfo>? detachedBeatmapSets;
|
||||||
|
|
||||||
private readonly NoResultsPlaceholder noResultsPlaceholder;
|
private readonly NoResultsPlaceholder noResultsPlaceholder;
|
||||||
|
|
||||||
private IEnumerable<CarouselBeatmapSet> beatmapSets => root.Items.OfType<CarouselBeatmapSet>();
|
private IEnumerable<CarouselBeatmapSet> beatmapSets => root.Items.OfType<CarouselBeatmapSet>();
|
||||||
|
|
||||||
// todo: only used for testing, maybe remove.
|
internal IEnumerable<BeatmapSetInfo> BeatmapSets
|
||||||
private bool loadedTestBeatmaps;
|
|
||||||
|
|
||||||
public IEnumerable<BeatmapSetInfo> BeatmapSets
|
|
||||||
{
|
{
|
||||||
get => beatmapSets.Select(g => g.BeatmapSet);
|
get => beatmapSets.Select(g => g.BeatmapSet);
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
loadedTestBeatmaps = true;
|
if (LoadState != LoadState.NotLoaded)
|
||||||
Schedule(() => loadBeatmapSets(value));
|
throw new InvalidOperationException("If not using a realm source, beatmap sets must be set before load.");
|
||||||
|
|
||||||
|
detachedBeatmapSets = new BindableList<BeatmapSetInfo>(value);
|
||||||
|
Schedule(loadNewRoot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadBeatmapSets(IEnumerable<BeatmapSetInfo> beatmapSets)
|
private void loadNewRoot()
|
||||||
{
|
{
|
||||||
originalBeatmapSetsDetached = beatmapSets.Detach();
|
// Ensure no changes are made to the list while we are initialising items.
|
||||||
|
// We'll catch up on changes via subscriptions anyway.
|
||||||
|
BeatmapSetInfo[] loadableSets = detachedBeatmapSets!.ToArray();
|
||||||
|
|
||||||
if (selectedBeatmapSet != null && !originalBeatmapSetsDetached.Contains(selectedBeatmapSet.BeatmapSet))
|
if (selectedBeatmapSet != null && !loadableSets.Contains(selectedBeatmapSet.BeatmapSet))
|
||||||
selectedBeatmapSet = null;
|
selectedBeatmapSet = null;
|
||||||
|
|
||||||
var selectedBeatmapBefore = selectedBeatmap?.BeatmapInfo;
|
var selectedBeatmapBefore = selectedBeatmap?.BeatmapInfo;
|
||||||
@ -139,7 +150,7 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
if (beatmapsSplitOut)
|
if (beatmapsSplitOut)
|
||||||
{
|
{
|
||||||
var carouselBeatmapSets = originalBeatmapSetsDetached.SelectMany(s => s.Beatmaps).Select(b =>
|
var carouselBeatmapSets = loadableSets.SelectMany(s => s.Beatmaps).Select(b =>
|
||||||
{
|
{
|
||||||
return createCarouselSet(new BeatmapSetInfo(new[] { b })
|
return createCarouselSet(new BeatmapSetInfo(new[] { b })
|
||||||
{
|
{
|
||||||
@ -153,25 +164,18 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var carouselBeatmapSets = originalBeatmapSetsDetached.Select(createCarouselSet).OfType<CarouselBeatmapSet>();
|
var carouselBeatmapSets = loadableSets.Select(createCarouselSet).OfType<CarouselBeatmapSet>();
|
||||||
|
|
||||||
newRoot.AddItems(carouselBeatmapSets);
|
newRoot.AddItems(carouselBeatmapSets);
|
||||||
}
|
}
|
||||||
|
|
||||||
root = newRoot;
|
root = newRoot;
|
||||||
|
root.Filter(activeCriteria);
|
||||||
|
|
||||||
Scroll.Clear(false);
|
Scroll.Clear(false);
|
||||||
itemsCache.Invalidate();
|
itemsCache.Invalidate();
|
||||||
ScrollToSelected();
|
ScrollToSelected();
|
||||||
|
|
||||||
applyActiveCriteria(false);
|
|
||||||
|
|
||||||
if (loadedTestBeatmaps)
|
|
||||||
{
|
|
||||||
invalidateAfterChange();
|
|
||||||
BeatmapSetsLoaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore selection
|
// Restore selection
|
||||||
if (selectedBeatmapBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedBeatmapBefore.BeatmapSet!.ID, out var newSelectionCandidates))
|
if (selectedBeatmapBefore != null && newRoot.BeatmapSetsByID.TryGetValue(selectedBeatmapBefore.BeatmapSet!.ID, out var newSelectionCandidates))
|
||||||
{
|
{
|
||||||
@ -180,6 +184,12 @@ namespace osu.Game.Screens.Select
|
|||||||
if (found != null)
|
if (found != null)
|
||||||
found.State.Value = CarouselItemState.Selected;
|
found.State.Value = CarouselItemState.Selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
invalidateAfterChange();
|
||||||
|
BeatmapSetsLoaded = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<CarouselItem> visibleItems = new List<CarouselItem>();
|
private readonly List<CarouselItem> visibleItems = new List<CarouselItem>();
|
||||||
@ -195,7 +205,6 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
private CarouselRoot root;
|
private CarouselRoot root;
|
||||||
|
|
||||||
private IDisposable? subscriptionSets;
|
|
||||||
private IDisposable? subscriptionBeatmaps;
|
private IDisposable? subscriptionBeatmaps;
|
||||||
|
|
||||||
private readonly DrawablePool<DrawableCarouselBeatmapSet> setPool = new DrawablePool<DrawableCarouselBeatmapSet>(100);
|
private readonly DrawablePool<DrawableCarouselBeatmapSet> setPool = new DrawablePool<DrawableCarouselBeatmapSet>(100);
|
||||||
@ -205,7 +214,7 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
private int visibleSetsCount;
|
private int visibleSetsCount;
|
||||||
|
|
||||||
public BeatmapCarousel()
|
public BeatmapCarousel(FilterCriteria initialCriterial)
|
||||||
{
|
{
|
||||||
root = new CarouselRoot(this);
|
root = new CarouselRoot(this);
|
||||||
InternalChild = new Container
|
InternalChild = new Container
|
||||||
@ -227,10 +236,12 @@ namespace osu.Game.Screens.Select
|
|||||||
noResultsPlaceholder = new NoResultsPlaceholder()
|
noResultsPlaceholder = new NoResultsPlaceholder()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
activeCriteria = initialCriterial;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config, AudioManager audio)
|
private void load(OsuConfigManager config, AudioManager audio, CancellationToken? cancellationToken)
|
||||||
{
|
{
|
||||||
spinSample = audio.Samples.Get("SongSelect/random-spin");
|
spinSample = audio.Samples.Get("SongSelect/random-spin");
|
||||||
randomSelectSample = audio.Samples.Get(@"SongSelect/select-random");
|
randomSelectSample = audio.Samples.Get(@"SongSelect/select-random");
|
||||||
@ -241,97 +252,61 @@ namespace osu.Game.Screens.Select
|
|||||||
RightClickScrollingEnabled.ValueChanged += enabled => Scroll.RightMouseScrollbar = enabled.NewValue;
|
RightClickScrollingEnabled.ValueChanged += enabled => Scroll.RightMouseScrollbar = enabled.NewValue;
|
||||||
RightClickScrollingEnabled.TriggerChange();
|
RightClickScrollingEnabled.TriggerChange();
|
||||||
|
|
||||||
if (!loadedTestBeatmaps)
|
if (detachedBeatmapStore != null && detachedBeatmapSets == null)
|
||||||
{
|
{
|
||||||
// This is performing an unnecessary second lookup on realm (in addition to the subscription), but for performance reasons
|
// This is performing an unnecessary second lookup on realm (in addition to the subscription), but for performance reasons
|
||||||
// we require it to be separate: the subscription's initial callback (with `ChangeSet` of `null`) will run on the update
|
// we require it to be separate: the subscription's initial callback (with `ChangeSet` of `null`) will run on the update
|
||||||
// thread. If we attempt to detach beatmaps in this callback the game will fall over (it takes time).
|
// thread. If we attempt to detach beatmaps in this callback the game will fall over (it takes time).
|
||||||
realm.Run(r => loadBeatmapSets(getBeatmapSets(r)));
|
detachedBeatmapSets = detachedBeatmapStore.GetDetachedBeatmaps(cancellationToken);
|
||||||
|
detachedBeatmapSets.BindCollectionChanged(beatmapSetsChanged);
|
||||||
|
loadNewRoot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private RealmAccess realm { get; set; } = null!;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Track GUIDs of all sets in realm to allow handling deletions.
|
|
||||||
/// </summary>
|
|
||||||
private readonly List<Guid> realmBeatmapSets = new List<Guid>();
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
subscriptionSets = realm.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged);
|
|
||||||
subscriptionBeatmaps = realm.RegisterForNotifications(r => r.All<BeatmapInfo>().Where(b => !b.Hidden), beatmapsChanged);
|
subscriptionBeatmaps = realm.RegisterForNotifications(r => r.All<BeatmapInfo>().Where(b => !b.Hidden), beatmapsChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly HashSet<Guid> setsRequiringUpdate = new HashSet<Guid>();
|
private readonly HashSet<BeatmapSetInfo> setsRequiringUpdate = new HashSet<BeatmapSetInfo>();
|
||||||
private readonly HashSet<Guid> setsRequiringRemoval = new HashSet<Guid>();
|
private readonly HashSet<BeatmapSetInfo> setsRequiringRemoval = new HashSet<BeatmapSetInfo>();
|
||||||
|
|
||||||
private void beatmapSetsChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes)
|
private void beatmapSetsChanged(object? beatmaps, NotifyCollectionChangedEventArgs changed)
|
||||||
{
|
{
|
||||||
// If loading test beatmaps, avoid overwriting with realm subscription callbacks.
|
IEnumerable<BeatmapSetInfo>? newBeatmapSets = changed.NewItems?.Cast<BeatmapSetInfo>();
|
||||||
if (loadedTestBeatmaps)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (changes == null)
|
switch (changed.Action)
|
||||||
{
|
{
|
||||||
realmBeatmapSets.Clear();
|
case NotifyCollectionChangedAction.Add:
|
||||||
realmBeatmapSets.AddRange(sender.Select(r => r.ID));
|
HashSet<Guid> newBeatmapSetIDs = newBeatmapSets!.Select(s => s.ID).ToHashSet();
|
||||||
|
|
||||||
if (originalBeatmapSetsDetached.Count > 0 && sender.Count == 0)
|
setsRequiringRemoval.RemoveWhere(s => newBeatmapSetIDs.Contains(s.ID));
|
||||||
{
|
setsRequiringUpdate.AddRange(newBeatmapSets!);
|
||||||
// Usually we'd reset stuff here, but doing so triggers a silly flow which ends up deadlocking realm.
|
break;
|
||||||
// Additionally, user should not be at song select when realm is blocking all operations in the first place.
|
|
||||||
//
|
|
||||||
// Note that due to the catch-up logic below, once operations are restored we will still be in a roughly
|
|
||||||
// correct state. The only things that this return will change is the carousel will not empty *during* the blocking
|
|
||||||
// operation.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do a full two-way check for missing (or incorrectly present) beatmaps.
|
case NotifyCollectionChangedAction.Remove:
|
||||||
// Let's assume that the worst that can happen is deletions or additions.
|
IEnumerable<BeatmapSetInfo> oldBeatmapSets = changed.OldItems!.Cast<BeatmapSetInfo>();
|
||||||
setsRequiringRemoval.Clear();
|
HashSet<Guid> oldBeatmapSetIDs = oldBeatmapSets.Select(s => s.ID).ToHashSet();
|
||||||
setsRequiringUpdate.Clear();
|
|
||||||
|
|
||||||
foreach (Guid id in realmBeatmapSets)
|
setsRequiringUpdate.RemoveWhere(s => oldBeatmapSetIDs.Contains(s.ID));
|
||||||
{
|
setsRequiringRemoval.AddRange(oldBeatmapSets);
|
||||||
if (!root.BeatmapSetsByID.ContainsKey(id))
|
break;
|
||||||
setsRequiringUpdate.Add(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Guid id in root.BeatmapSetsByID.Keys)
|
case NotifyCollectionChangedAction.Replace:
|
||||||
{
|
setsRequiringUpdate.AddRange(newBeatmapSets!);
|
||||||
if (!realmBeatmapSets.Contains(id))
|
break;
|
||||||
setsRequiringRemoval.Add(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (int i in changes.DeletedIndices.OrderDescending())
|
|
||||||
{
|
|
||||||
Guid id = realmBeatmapSets[i];
|
|
||||||
|
|
||||||
setsRequiringRemoval.Add(id);
|
case NotifyCollectionChangedAction.Move:
|
||||||
setsRequiringUpdate.Remove(id);
|
setsRequiringUpdate.AddRange(newBeatmapSets!);
|
||||||
|
break;
|
||||||
|
|
||||||
realmBeatmapSets.RemoveAt(i);
|
case NotifyCollectionChangedAction.Reset:
|
||||||
}
|
setsRequiringRemoval.Clear();
|
||||||
|
setsRequiringUpdate.Clear();
|
||||||
foreach (int i in changes.InsertedIndices)
|
loadNewRoot();
|
||||||
{
|
break;
|
||||||
Guid id = sender[i].ID;
|
|
||||||
|
|
||||||
setsRequiringRemoval.Remove(id);
|
|
||||||
setsRequiringUpdate.Add(id);
|
|
||||||
|
|
||||||
realmBeatmapSets.Insert(i, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (int i in changes.NewModifiedIndices)
|
|
||||||
setsRequiringUpdate.Add(sender[i].ID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Scheduler.AddOnce(processBeatmapChanges);
|
Scheduler.AddOnce(processBeatmapChanges);
|
||||||
@ -345,9 +320,9 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (var set in setsRequiringRemoval) removeBeatmapSet(set);
|
foreach (var set in setsRequiringRemoval) removeBeatmapSet(set.ID);
|
||||||
|
|
||||||
foreach (var set in setsRequiringUpdate) updateBeatmapSet(fetchFromID(set)!);
|
foreach (var set in setsRequiringUpdate) updateBeatmapSet(set);
|
||||||
|
|
||||||
if (setsRequiringRemoval.Count > 0 && SelectedBeatmapInfo != null)
|
if (setsRequiringRemoval.Count > 0 && SelectedBeatmapInfo != null)
|
||||||
{
|
{
|
||||||
@ -365,7 +340,7 @@ namespace osu.Game.Screens.Select
|
|||||||
// This relies on the full update operation being in a single transaction, so please don't change that.
|
// This relies on the full update operation being in a single transaction, so please don't change that.
|
||||||
foreach (var set in setsRequiringUpdate)
|
foreach (var set in setsRequiringUpdate)
|
||||||
{
|
{
|
||||||
foreach (var beatmapInfo in fetchFromID(set)!.Beatmaps)
|
foreach (var beatmapInfo in set.Beatmaps)
|
||||||
{
|
{
|
||||||
if (!((IBeatmapMetadataInfo)beatmapInfo.Metadata).Equals(SelectedBeatmapInfo.Metadata)) continue;
|
if (!((IBeatmapMetadataInfo)beatmapInfo.Metadata).Equals(SelectedBeatmapInfo.Metadata)) continue;
|
||||||
|
|
||||||
@ -380,7 +355,7 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
// If a direct selection couldn't be made, it's feasible that the difficulty name (or beatmap metadata) changed.
|
// If a direct selection couldn't be made, it's feasible that the difficulty name (or beatmap metadata) changed.
|
||||||
// Let's attempt to follow set-level selection anyway.
|
// Let's attempt to follow set-level selection anyway.
|
||||||
SelectBeatmap(fetchFromID(setsRequiringUpdate.First())!.Beatmaps.First());
|
SelectBeatmap(setsRequiringUpdate.First().Beatmaps.First());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -425,8 +400,6 @@ namespace osu.Game.Screens.Select
|
|||||||
invalidateAfterChange();
|
invalidateAfterChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IQueryable<BeatmapSetInfo> getBeatmapSets(Realm realm) => realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending && !s.Protected);
|
|
||||||
|
|
||||||
public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() =>
|
public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() =>
|
||||||
{
|
{
|
||||||
removeBeatmapSet(beatmapSet.ID);
|
removeBeatmapSet(beatmapSet.ID);
|
||||||
@ -438,8 +411,6 @@ namespace osu.Game.Screens.Select
|
|||||||
if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSets))
|
if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSets))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
originalBeatmapSetsDetached.RemoveAll(set => set.ID == beatmapSetID);
|
|
||||||
|
|
||||||
foreach (var set in existingSets)
|
foreach (var set in existingSets)
|
||||||
{
|
{
|
||||||
foreach (var beatmap in set.Beatmaps)
|
foreach (var beatmap in set.Beatmaps)
|
||||||
@ -450,24 +421,14 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet)
|
public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() =>
|
||||||
{
|
{
|
||||||
beatmapSet = beatmapSet.Detach();
|
updateBeatmapSet(beatmapSet);
|
||||||
|
invalidateAfterChange();
|
||||||
Schedule(() =>
|
});
|
||||||
{
|
|
||||||
updateBeatmapSet(beatmapSet);
|
|
||||||
invalidateAfterChange();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBeatmapSet(BeatmapSetInfo beatmapSet)
|
private void updateBeatmapSet(BeatmapSetInfo beatmapSet)
|
||||||
{
|
{
|
||||||
beatmapSet = beatmapSet.Detach();
|
|
||||||
|
|
||||||
originalBeatmapSetsDetached.RemoveAll(set => set.ID == beatmapSet.ID);
|
|
||||||
originalBeatmapSetsDetached.Add(beatmapSet);
|
|
||||||
|
|
||||||
var newSets = new List<CarouselBeatmapSet>();
|
var newSets = new List<CarouselBeatmapSet>();
|
||||||
|
|
||||||
if (beatmapsSplitOut)
|
if (beatmapsSplitOut)
|
||||||
@ -696,7 +657,7 @@ namespace osu.Game.Screens.Select
|
|||||||
item.State.Value = CarouselItemState.Selected;
|
item.State.Value = CarouselItemState.Selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FilterCriteria activeCriteria = new FilterCriteria();
|
private FilterCriteria activeCriteria;
|
||||||
|
|
||||||
protected ScheduledDelegate? PendingFilter;
|
protected ScheduledDelegate? PendingFilter;
|
||||||
|
|
||||||
@ -733,12 +694,12 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Filter(FilterCriteria? newCriteria, bool debounce = true)
|
public void Filter(FilterCriteria? newCriteria)
|
||||||
{
|
{
|
||||||
if (newCriteria != null)
|
if (newCriteria != null)
|
||||||
activeCriteria = newCriteria;
|
activeCriteria = newCriteria;
|
||||||
|
|
||||||
applyActiveCriteria(debounce);
|
applyActiveCriteria(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool beatmapsSplitOut;
|
private bool beatmapsSplitOut;
|
||||||
@ -766,7 +727,7 @@ namespace osu.Game.Screens.Select
|
|||||||
if (activeCriteria.SplitOutDifficulties != beatmapsSplitOut)
|
if (activeCriteria.SplitOutDifficulties != beatmapsSplitOut)
|
||||||
{
|
{
|
||||||
beatmapsSplitOut = activeCriteria.SplitOutDifficulties;
|
beatmapsSplitOut = activeCriteria.SplitOutDifficulties;
|
||||||
loadBeatmapSets(originalBeatmapSetsDetached);
|
loadNewRoot();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1315,7 +1276,6 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
subscriptionSets?.Dispose();
|
|
||||||
subscriptionBeatmaps?.Dispose();
|
subscriptionBeatmaps?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,10 +62,31 @@ namespace osu.Game.Screens.Select
|
|||||||
case "length":
|
case "length":
|
||||||
return tryUpdateLengthRange(criteria, op, value);
|
return tryUpdateLengthRange(criteria, op, value);
|
||||||
|
|
||||||
case "played":
|
|
||||||
case "lastplayed":
|
case "lastplayed":
|
||||||
return tryUpdateDateAgoRange(ref criteria.LastPlayed, op, value);
|
return tryUpdateDateAgoRange(ref criteria.LastPlayed, op, value);
|
||||||
|
|
||||||
|
case "played":
|
||||||
|
if (!tryParseBool(value, out bool played))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Unplayed beatmaps are filtered on DateTimeOffset.MinValue.
|
||||||
|
|
||||||
|
if (played)
|
||||||
|
{
|
||||||
|
criteria.LastPlayed.Min = DateTimeOffset.MinValue;
|
||||||
|
criteria.LastPlayed.Max = DateTimeOffset.MaxValue;
|
||||||
|
criteria.LastPlayed.IsLowerInclusive = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
criteria.LastPlayed.Min = DateTimeOffset.MinValue;
|
||||||
|
criteria.LastPlayed.Max = DateTimeOffset.MinValue;
|
||||||
|
criteria.LastPlayed.IsLowerInclusive = true;
|
||||||
|
criteria.LastPlayed.IsUpperInclusive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
case "divisor":
|
case "divisor":
|
||||||
return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt);
|
return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt);
|
||||||
|
|
||||||
@ -133,6 +154,23 @@ namespace osu.Game.Screens.Select
|
|||||||
private static bool tryParseInt(string value, out int result) =>
|
private static bool tryParseInt(string value, out int result) =>
|
||||||
int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result);
|
int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result);
|
||||||
|
|
||||||
|
private static bool tryParseBool(string value, out bool result)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case "1":
|
||||||
|
result = true;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "0":
|
||||||
|
result = false;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return bool.TryParse(value, out result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool tryParseEnum<TEnum>(string value, out TEnum result) where TEnum : struct
|
private static bool tryParseEnum<TEnum>(string value, out TEnum result) where TEnum : struct
|
||||||
{
|
{
|
||||||
// First try an exact match.
|
// First try an exact match.
|
||||||
|
@ -162,20 +162,6 @@ namespace osu.Game.Screens.Select
|
|||||||
ApplyToBackground(applyBlurToBackground);
|
ApplyToBackground(applyBlurToBackground);
|
||||||
});
|
});
|
||||||
|
|
||||||
LoadComponentAsync(Carousel = new BeatmapCarousel
|
|
||||||
{
|
|
||||||
AllowSelection = false, // delay any selection until our bindables are ready to make a good choice.
|
|
||||||
Anchor = Anchor.CentreRight,
|
|
||||||
Origin = Anchor.CentreRight,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
BleedTop = FilterControl.HEIGHT,
|
|
||||||
BleedBottom = Select.Footer.HEIGHT,
|
|
||||||
SelectionChanged = updateSelectedBeatmap,
|
|
||||||
BeatmapSetsChanged = carouselBeatmapsLoaded,
|
|
||||||
FilterApplied = () => Scheduler.AddOnce(updateVisibleBeatmapCount),
|
|
||||||
GetRecommendedBeatmap = s => recommender?.GetRecommendedBeatmap(s),
|
|
||||||
}, c => carouselContainer.Child = c);
|
|
||||||
|
|
||||||
// initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter).
|
// initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter).
|
||||||
transferRulesetValue();
|
transferRulesetValue();
|
||||||
|
|
||||||
@ -227,7 +213,6 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = FilterControl.HEIGHT,
|
Height = FilterControl.HEIGHT,
|
||||||
FilterChanged = ApplyFilterToCarousel,
|
|
||||||
},
|
},
|
||||||
new GridContainer // used for max width implementation
|
new GridContainer // used for max width implementation
|
||||||
{
|
{
|
||||||
@ -328,6 +313,23 @@ namespace osu.Game.Screens.Select
|
|||||||
modSpeedHotkeyHandler = new ModSpeedHotkeyHandler(),
|
modSpeedHotkeyHandler = new ModSpeedHotkeyHandler(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Important to load this after the filter control is loaded (so we have initial filter criteria prepared).
|
||||||
|
LoadComponentAsync(Carousel = new BeatmapCarousel(FilterControl.CreateCriteria())
|
||||||
|
{
|
||||||
|
AllowSelection = false, // delay any selection until our bindables are ready to make a good choice.
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
BleedTop = FilterControl.HEIGHT,
|
||||||
|
BleedBottom = Select.Footer.HEIGHT,
|
||||||
|
SelectionChanged = updateSelectedBeatmap,
|
||||||
|
BeatmapSetsChanged = carouselBeatmapsLoaded,
|
||||||
|
FilterApplied = () => Scheduler.AddOnce(updateVisibleBeatmapCount),
|
||||||
|
GetRecommendedBeatmap = s => recommender?.GetRecommendedBeatmap(s),
|
||||||
|
}, c => carouselContainer.Child = c);
|
||||||
|
|
||||||
|
FilterControl.FilterChanged = Carousel.Filter;
|
||||||
|
|
||||||
if (ShowSongSelectFooter)
|
if (ShowSongSelectFooter)
|
||||||
{
|
{
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
@ -401,14 +403,6 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay();
|
protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay();
|
||||||
|
|
||||||
protected virtual void ApplyFilterToCarousel(FilterCriteria criteria)
|
|
||||||
{
|
|
||||||
// if not the current screen, we want to get carousel in a good presentation state before displaying (resume or enter).
|
|
||||||
bool shouldDebounce = this.IsCurrentScreen();
|
|
||||||
|
|
||||||
Carousel.Filter(criteria, shouldDebounce);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DependencyContainer dependencies = null!;
|
private DependencyContainer dependencies = null!;
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
@ -434,7 +428,8 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
// Forced refetch is important here to guarantee correct invalidation across all difficulties.
|
// Forced refetch is important here to guarantee correct invalidation across all difficulties.
|
||||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo ?? beatmapInfoNoDebounce, true);
|
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo ?? beatmapInfoNoDebounce, true);
|
||||||
this.Push(new EditorLoader());
|
|
||||||
|
FinaliseSelection(customStartAction: () => this.Push(new EditorLoader()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -992,7 +987,8 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
// if we have a pending filter operation, we want to run it now.
|
// if we have a pending filter operation, we want to run it now.
|
||||||
// it could change selection (ie. if the ruleset has been changed).
|
// it could change selection (ie. if the ruleset has been changed).
|
||||||
Carousel.FlushPendingFilterOperations();
|
if (IsLoaded)
|
||||||
|
Carousel.FlushPendingFilterOperations();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Tests
|
|||||||
|
|
||||||
fetchReq.Success += updates =>
|
fetchReq.Success += updates =>
|
||||||
{
|
{
|
||||||
if (updates?.Presence != null)
|
if (updates.Presence != null)
|
||||||
{
|
{
|
||||||
foreach (var channel in updates.Presence)
|
foreach (var channel in updates.Presence)
|
||||||
handleChannelJoined(channel);
|
handleChannelJoined(channel);
|
||||||
|
Loading…
Reference in New Issue
Block a user