mirror of
https://github.com/ppy/osu.git
synced 2025-01-11 20:33:21 +08:00
Merge remote-tracking branch 'upstream/master' into LNFixHotfix
This commit is contained in:
commit
2b85ab6888
@ -49,6 +49,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
AddStep("Create tests", () =>
|
AddStep("Create tests", () =>
|
||||||
{
|
{
|
||||||
|
InputTrigger triggerLeft;
|
||||||
|
InputTrigger triggerRight;
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
osuInputManager = new OsuInputManager(new OsuRuleset().RulesetInfo)
|
osuInputManager = new OsuInputManager(new OsuRuleset().RulesetInfo)
|
||||||
@ -59,29 +62,39 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
leftKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.LeftButton))
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.CentreRight,
|
|
||||||
Depth = float.MinValue,
|
|
||||||
X = -100,
|
|
||||||
},
|
|
||||||
rightKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.RightButton))
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Depth = float.MinValue,
|
|
||||||
X = 100,
|
|
||||||
},
|
|
||||||
new OsuCursorContainer
|
new OsuCursorContainer
|
||||||
{
|
{
|
||||||
Depth = float.MinValue,
|
Depth = float.MinValue,
|
||||||
|
},
|
||||||
|
triggerLeft = new TestActionKeyCounterTrigger(OsuAction.LeftButton)
|
||||||
|
{
|
||||||
|
Depth = float.MinValue
|
||||||
|
},
|
||||||
|
triggerRight = new TestActionKeyCounterTrigger(OsuAction.RightButton)
|
||||||
|
{
|
||||||
|
Depth = float.MinValue
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
new TouchVisualiser(),
|
new TouchVisualiser(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mainContent.AddRange(new[]
|
||||||
|
{
|
||||||
|
leftKeyCounter = new DefaultKeyCounter(triggerLeft)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
X = -100,
|
||||||
|
},
|
||||||
|
rightKeyCounter = new DefaultKeyCounter(triggerRight)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
X = 100,
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ using osu.Game.Extensions;
|
|||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using Realms;
|
using Realms;
|
||||||
using SharpCompress.Archives;
|
using SharpCompress.Archives;
|
||||||
@ -416,6 +417,53 @@ namespace osu.Game.Tests.Database
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestImport_ThenModifyMapWithScore_ThenImport()
|
||||||
|
{
|
||||||
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
|
{
|
||||||
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
|
await createScoreForBeatmap(realm.Realm, imported.Beatmaps.First());
|
||||||
|
|
||||||
|
// imitate making local changes via editor
|
||||||
|
// ReSharper disable once MethodHasAsyncOverload
|
||||||
|
realm.Write(_ =>
|
||||||
|
{
|
||||||
|
BeatmapInfo beatmap = imported.Beatmaps.First();
|
||||||
|
beatmap.Hash = "new_hash";
|
||||||
|
beatmap.ResetOnlineInfo();
|
||||||
|
});
|
||||||
|
|
||||||
|
// for now, making changes to a beatmap doesn't remove the backlink from the score to the beatmap.
|
||||||
|
// the logic of ensuring that scores match the beatmap is upheld via comparing the hash in usages (see: https://github.com/ppy/osu/pull/22539).
|
||||||
|
// TODO: revisit when fixing https://github.com/ppy/osu/issues/24069.
|
||||||
|
Assert.That(imported.Beatmaps.First().Scores.Any());
|
||||||
|
|
||||||
|
var importedSecondTime = await importer.Import(new ImportTask(temp));
|
||||||
|
|
||||||
|
EnsureLoaded(realm.Realm);
|
||||||
|
|
||||||
|
// check the newly "imported" beatmap is not the original.
|
||||||
|
Assert.NotNull(importedSecondTime);
|
||||||
|
Debug.Assert(importedSecondTime != null);
|
||||||
|
Assert.That(imported.ID != importedSecondTime.ID);
|
||||||
|
|
||||||
|
var importedFirstTimeBeatmap = imported.Beatmaps.First();
|
||||||
|
var importedSecondTimeBeatmap = importedSecondTime.PerformRead(s => s.Beatmaps.First());
|
||||||
|
|
||||||
|
Assert.That(importedFirstTimeBeatmap.ID != importedSecondTimeBeatmap.ID);
|
||||||
|
Assert.That(importedFirstTimeBeatmap.Hash != importedSecondTimeBeatmap.Hash);
|
||||||
|
Assert.That(!importedFirstTimeBeatmap.Scores.Any());
|
||||||
|
Assert.That(importedSecondTimeBeatmap.Scores.Count() == 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportThenImportWithChangedFile()
|
public void TestImportThenImportWithChangedFile()
|
||||||
{
|
{
|
||||||
@ -1074,18 +1122,16 @@ namespace osu.Game.Tests.Database
|
|||||||
Assert.IsTrue(realm.All<BeatmapSetInfo>().First(_ => true).DeletePending);
|
Assert.IsTrue(realm.All<BeatmapSetInfo>().First(_ => true).DeletePending);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Task createScoreForBeatmap(Realm realm, BeatmapInfo beatmap)
|
private static Task createScoreForBeatmap(Realm realm, BeatmapInfo beatmap) =>
|
||||||
|
realm.WriteAsync(() =>
|
||||||
{
|
{
|
||||||
// TODO: reimplement when we have score support in realm.
|
realm.Add(new ScoreInfo
|
||||||
// return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
|
{
|
||||||
// {
|
OnlineID = 2,
|
||||||
// OnlineID = 2,
|
BeatmapInfo = beatmap,
|
||||||
// Beatmap = beatmap,
|
BeatmapHash = beatmap.Hash
|
||||||
// BeatmapInfoID = beatmap.ID
|
});
|
||||||
// }, new ImportScoreTest.TestArchiveReader());
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkBeatmapSetCount(Realm realm, int expected, bool includeDeletePending = false)
|
private static void checkBeatmapSetCount(Realm realm, int expected, bool includeDeletePending = false)
|
||||||
{
|
{
|
||||||
|
@ -347,6 +347,73 @@ namespace osu.Game.Tests.Database
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDanglingScoreTransferred()
|
||||||
|
{
|
||||||
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
|
{
|
||||||
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
|
using var rulesets = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
|
using var __ = getBeatmapArchive(out string pathOriginal);
|
||||||
|
using var _ = getBeatmapArchive(out string pathOnlineCopy);
|
||||||
|
|
||||||
|
var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
|
||||||
|
|
||||||
|
Assert.That(importBeforeUpdate, Is.Not.Null);
|
||||||
|
Debug.Assert(importBeforeUpdate != null);
|
||||||
|
|
||||||
|
string scoreTargetBeatmapHash = string.Empty;
|
||||||
|
|
||||||
|
// set a score on the beatmap
|
||||||
|
importBeforeUpdate.PerformWrite(s =>
|
||||||
|
{
|
||||||
|
var beatmapInfo = s.Beatmaps.First();
|
||||||
|
|
||||||
|
scoreTargetBeatmapHash = beatmapInfo.Hash;
|
||||||
|
|
||||||
|
s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All<RulesetInfo>().First(), new RealmUser()));
|
||||||
|
});
|
||||||
|
|
||||||
|
// locally modify beatmap
|
||||||
|
const string new_beatmap_hash = "new_hash";
|
||||||
|
importBeforeUpdate.PerformWrite(s =>
|
||||||
|
{
|
||||||
|
var beatmapInfo = s.Beatmaps.First(b => b.Hash == scoreTargetBeatmapHash);
|
||||||
|
|
||||||
|
beatmapInfo.Hash = new_beatmap_hash;
|
||||||
|
beatmapInfo.ResetOnlineInfo();
|
||||||
|
});
|
||||||
|
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
|
// for now, making changes to a beatmap doesn't remove the backlink from the score to the beatmap.
|
||||||
|
// the logic of ensuring that scores match the beatmap is upheld via comparing the hash in usages (https://github.com/ppy/osu/pull/22539).
|
||||||
|
// TODO: revisit when fixing https://github.com/ppy/osu/issues/24069.
|
||||||
|
checkCount<ScoreInfo>(realm, 1);
|
||||||
|
|
||||||
|
// reimport the original beatmap before local modifications
|
||||||
|
var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOnlineCopy), importBeforeUpdate.Value);
|
||||||
|
|
||||||
|
Assert.That(importAfterUpdate, Is.Not.Null);
|
||||||
|
Debug.Assert(importAfterUpdate != null);
|
||||||
|
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
|
// both original and locally modified versions present
|
||||||
|
checkCount<BeatmapInfo>(realm, count_beatmaps + 1);
|
||||||
|
checkCount<BeatmapMetadata>(realm, count_beatmaps + 1);
|
||||||
|
checkCount<BeatmapSetInfo>(realm, 2);
|
||||||
|
|
||||||
|
// score is preserved
|
||||||
|
checkCount<ScoreInfo>(realm, 1);
|
||||||
|
|
||||||
|
// score is transferred to new beatmap
|
||||||
|
Assert.That(importBeforeUpdate.Value.Beatmaps.First(b => b.Hash == new_beatmap_hash).Scores, Has.Count.EqualTo(0));
|
||||||
|
Assert.That(importAfterUpdate.Value.Beatmaps.First(b => b.Hash == scoreTargetBeatmapHash).Scores, Has.Count.EqualTo(1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestScoreLostOnModification()
|
public void TestScoreLostOnModification()
|
||||||
{
|
{
|
||||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
Artist = "The Artist",
|
Artist = "The Artist",
|
||||||
ArtistUnicode = "check unicode too",
|
ArtistUnicode = "check unicode too",
|
||||||
Title = "Title goes here",
|
Title = "Title goes here",
|
||||||
TitleUnicode = "Title goes here",
|
TitleUnicode = "TitleUnicode goes here",
|
||||||
Author = { Username = "The Author" },
|
Author = { Username = "The Author" },
|
||||||
Source = "unit tests",
|
Source = "unit tests",
|
||||||
Tags = "look for tags too",
|
Tags = "look for tags too",
|
||||||
@ -159,6 +159,34 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
|
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCase("\"artist\"", false)]
|
||||||
|
[TestCase("\"arti\"", true)]
|
||||||
|
[TestCase("\"artist title author\"", true)]
|
||||||
|
[TestCase("\"artist\" \"title\" \"author\"", false)]
|
||||||
|
[TestCase("\"an artist\"", true)]
|
||||||
|
[TestCase("\"tags too\"", false)]
|
||||||
|
[TestCase("\"tags to\"", true)]
|
||||||
|
[TestCase("\"version\"", false)]
|
||||||
|
[TestCase("\"an auteur\"", true)]
|
||||||
|
[TestCase("\"Artist\"!", true)]
|
||||||
|
[TestCase("\"The Artist\"!", false)]
|
||||||
|
[TestCase("\"the artist\"!", false)]
|
||||||
|
[TestCase("\"\\\"", true)] // nasty case, covers properly escaping user input in underlying regex.
|
||||||
|
public void TestCriteriaMatchingExactTerms(string terms, bool filtered)
|
||||||
|
{
|
||||||
|
var exampleBeatmapInfo = getExampleBeatmap();
|
||||||
|
var criteria = new FilterCriteria
|
||||||
|
{
|
||||||
|
Ruleset = new RulesetInfo { OnlineID = 6 },
|
||||||
|
AllowConvertedBeatmaps = true,
|
||||||
|
SearchText = terms
|
||||||
|
};
|
||||||
|
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
|
||||||
|
carouselItem.Filter(criteria);
|
||||||
|
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[TestCase("", false)]
|
[TestCase("", false)]
|
||||||
[TestCase("The", false)]
|
[TestCase("The", false)]
|
||||||
@ -179,6 +207,27 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
|
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCase("", false)]
|
||||||
|
[TestCase("Goes", false)]
|
||||||
|
[TestCase("GOES", false)]
|
||||||
|
[TestCase("goes", false)]
|
||||||
|
[TestCase("title goes", false)]
|
||||||
|
[TestCase("title goes AND then something else", true)]
|
||||||
|
[TestCase("titleunicode", false)]
|
||||||
|
[TestCase("unknown", true)]
|
||||||
|
public void TestCriteriaMatchingTitle(string titleName, bool filtered)
|
||||||
|
{
|
||||||
|
var exampleBeatmapInfo = getExampleBeatmap();
|
||||||
|
var criteria = new FilterCriteria
|
||||||
|
{
|
||||||
|
Title = new FilterCriteria.OptionalTextFilter { SearchTerm = titleName }
|
||||||
|
};
|
||||||
|
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
|
||||||
|
carouselItem.Filter(criteria);
|
||||||
|
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[TestCase("", false)]
|
[TestCase("", false)]
|
||||||
[TestCase("The", false)]
|
[TestCase("The", false)]
|
||||||
@ -188,6 +237,9 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
[TestCase("the artist AND then something else", true)]
|
[TestCase("the artist AND then something else", true)]
|
||||||
[TestCase("unicode too", false)]
|
[TestCase("unicode too", false)]
|
||||||
[TestCase("unknown", true)]
|
[TestCase("unknown", true)]
|
||||||
|
[TestCase("\"Artist\"!", true)]
|
||||||
|
[TestCase("\"The Artist\"!", false)]
|
||||||
|
[TestCase("\"the artist\"!", false)]
|
||||||
public void TestCriteriaMatchingArtist(string artistName, bool filtered)
|
public void TestCriteriaMatchingArtist(string artistName, bool filtered)
|
||||||
{
|
{
|
||||||
var exampleBeatmapInfo = getExampleBeatmap();
|
var exampleBeatmapInfo = getExampleBeatmap();
|
||||||
|
@ -23,6 +23,63 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
Assert.AreEqual(4, filterCriteria.SearchTerms.Length);
|
Assert.AreEqual(4, filterCriteria.SearchTerms.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApplyQueriesBareWordsWithExactMatch()
|
||||||
|
{
|
||||||
|
const string query = "looking for \"a beatmap\"! like \"this\"";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual("looking for \"a beatmap\"! like \"this\"", filterCriteria.SearchText);
|
||||||
|
Assert.AreEqual(5, filterCriteria.SearchTerms.Length);
|
||||||
|
|
||||||
|
Assert.That(filterCriteria.SearchTerms[0].SearchTerm, Is.EqualTo("a beatmap"));
|
||||||
|
Assert.That(filterCriteria.SearchTerms[0].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.FullPhrase));
|
||||||
|
|
||||||
|
Assert.That(filterCriteria.SearchTerms[1].SearchTerm, Is.EqualTo("this"));
|
||||||
|
Assert.That(filterCriteria.SearchTerms[1].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.IsolatedPhrase));
|
||||||
|
|
||||||
|
Assert.That(filterCriteria.SearchTerms[2].SearchTerm, Is.EqualTo("looking"));
|
||||||
|
Assert.That(filterCriteria.SearchTerms[2].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring));
|
||||||
|
|
||||||
|
Assert.That(filterCriteria.SearchTerms[3].SearchTerm, Is.EqualTo("for"));
|
||||||
|
Assert.That(filterCriteria.SearchTerms[3].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring));
|
||||||
|
|
||||||
|
Assert.That(filterCriteria.SearchTerms[4].SearchTerm, Is.EqualTo("like"));
|
||||||
|
Assert.That(filterCriteria.SearchTerms[4].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApplyFullPhraseQueryWithExclamationPointInTerm()
|
||||||
|
{
|
||||||
|
const string query = "looking for \"circles!\"!";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual("looking for \"circles!\"!", filterCriteria.SearchText);
|
||||||
|
Assert.AreEqual(3, filterCriteria.SearchTerms.Length);
|
||||||
|
|
||||||
|
Assert.That(filterCriteria.SearchTerms[0].SearchTerm, Is.EqualTo("circles!"));
|
||||||
|
Assert.That(filterCriteria.SearchTerms[0].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.FullPhrase));
|
||||||
|
|
||||||
|
Assert.That(filterCriteria.SearchTerms[1].SearchTerm, Is.EqualTo("looking"));
|
||||||
|
Assert.That(filterCriteria.SearchTerms[1].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring));
|
||||||
|
|
||||||
|
Assert.That(filterCriteria.SearchTerms[2].SearchTerm, Is.EqualTo("for"));
|
||||||
|
Assert.That(filterCriteria.SearchTerms[2].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApplyBrokenFullPhraseQuery()
|
||||||
|
{
|
||||||
|
const string query = "\"!";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual("\"!", filterCriteria.SearchText);
|
||||||
|
Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
|
||||||
|
|
||||||
|
Assert.That(filterCriteria.SearchTerms[0].SearchTerm, Is.EqualTo("!"));
|
||||||
|
Assert.That(filterCriteria.SearchTerms[0].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.IsolatedPhrase));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The following tests have been written a bit strangely (they don't check exact
|
* The following tests have been written a bit strangely (they don't check exact
|
||||||
* bound equality with what the filter says).
|
* bound equality with what the filter says).
|
||||||
@ -226,6 +283,18 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
Assert.AreEqual("my_fav", filterCriteria.Creator.SearchTerm);
|
Assert.AreEqual("my_fav", filterCriteria.Creator.SearchTerm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApplyTitleQueries()
|
||||||
|
{
|
||||||
|
const string query = "find me songs with title=\"a certain title\" please";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual("find me songs with please", filterCriteria.SearchText.Trim());
|
||||||
|
Assert.AreEqual(5, filterCriteria.SearchTerms.Length);
|
||||||
|
Assert.AreEqual("a certain title", filterCriteria.Title.SearchTerm);
|
||||||
|
Assert.That(filterCriteria.Title.MatchMode, Is.EqualTo(FilterCriteria.MatchMode.IsolatedPhrase));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestApplyArtistQueries()
|
public void TestApplyArtistQueries()
|
||||||
{
|
{
|
||||||
@ -235,6 +304,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
Assert.AreEqual("find me songs by please", filterCriteria.SearchText.Trim());
|
Assert.AreEqual("find me songs by please", filterCriteria.SearchText.Trim());
|
||||||
Assert.AreEqual(5, filterCriteria.SearchTerms.Length);
|
Assert.AreEqual(5, filterCriteria.SearchTerms.Length);
|
||||||
Assert.AreEqual("singer", filterCriteria.Artist.SearchTerm);
|
Assert.AreEqual("singer", filterCriteria.Artist.SearchTerm);
|
||||||
|
Assert.That(filterCriteria.Artist.MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -246,6 +316,19 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
Assert.AreEqual("really like yes", filterCriteria.SearchText.Trim());
|
Assert.AreEqual("really like yes", filterCriteria.SearchText.Trim());
|
||||||
Assert.AreEqual(3, filterCriteria.SearchTerms.Length);
|
Assert.AreEqual(3, filterCriteria.SearchTerms.Length);
|
||||||
Assert.AreEqual("name with space", filterCriteria.Artist.SearchTerm);
|
Assert.AreEqual("name with space", filterCriteria.Artist.SearchTerm);
|
||||||
|
Assert.That(filterCriteria.Artist.MatchMode, Is.EqualTo(FilterCriteria.MatchMode.IsolatedPhrase));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApplyArtistQueriesWithSpacesFullPhrase()
|
||||||
|
{
|
||||||
|
const string query = "artist=\"The Only One\"!";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.That(filterCriteria.SearchText.Trim(), Is.Empty);
|
||||||
|
Assert.AreEqual(0, filterCriteria.SearchTerms.Length);
|
||||||
|
Assert.AreEqual("The Only One", filterCriteria.Artist.SearchTerm);
|
||||||
|
Assert.That(filterCriteria.Artist.MatchMode, Is.EqualTo(FilterCriteria.MatchMode.FullPhrase));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
Binary file not shown.
@ -51,6 +51,8 @@ namespace osu.Game.Tests.Skins
|
|||||||
"Archives/modified-default-20230117.osk",
|
"Archives/modified-default-20230117.osk",
|
||||||
// Covers player avatar and flag.
|
// Covers player avatar and flag.
|
||||||
"Archives/modified-argon-20230305.osk",
|
"Archives/modified-argon-20230305.osk",
|
||||||
|
// Covers key counters
|
||||||
|
"Archives/modified-argon-pro-20230618.osk"
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -101,6 +101,64 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 100);
|
AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRotateHotkeys()
|
||||||
|
{
|
||||||
|
HitCircle[] addedObjects = null;
|
||||||
|
|
||||||
|
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 100 },
|
||||||
|
new HitCircle { StartTime = 200, Position = new Vector2(100) },
|
||||||
|
new HitCircle { StartTime = 300, Position = new Vector2(200) },
|
||||||
|
new HitCircle { StartTime = 400, Position = new Vector2(300) },
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects));
|
||||||
|
|
||||||
|
AddStep("rotate clockwise", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Key(Key.Period);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
AddAssert("objects rotated clockwise", () => addedObjects[0].Position == new Vector2(300, 0));
|
||||||
|
|
||||||
|
AddStep("rotate counterclockwise", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Key(Key.Comma);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
AddAssert("objects reverted to original position", () => addedObjects[0].Position == new Vector2(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGlobalFlipHotkeys()
|
||||||
|
{
|
||||||
|
HitCircle addedObject = null;
|
||||||
|
|
||||||
|
AddStep("add hitobjects", () => EditorBeatmap.Add(addedObject = new HitCircle { StartTime = 100 }));
|
||||||
|
|
||||||
|
AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
|
||||||
|
|
||||||
|
AddStep("flip horizontally across playfield", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Key(Key.H);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
AddAssert("objects flipped horizontally", () => addedObject.Position == new Vector2(OsuPlayfield.BASE_SIZE.X, 0));
|
||||||
|
|
||||||
|
AddStep("flip vertically across playfield", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Key(Key.J);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
AddAssert("objects flipped vertically", () => addedObject.Position == OsuPlayfield.BASE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBasicSelect()
|
public void TestBasicSelect()
|
||||||
{
|
{
|
||||||
|
@ -72,9 +72,13 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null);
|
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null);
|
||||||
AddStep("confirm", () => InputManager.Key(Key.Number1));
|
AddStep("confirm", () => InputManager.Key(Key.Number1));
|
||||||
|
|
||||||
AddAssert($"difficulty {i} is deleted", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Select(b => b.ID), () => Does.Not.Contain(deletedDifficultyID));
|
AddAssert($"difficulty {i} is unattached from set",
|
||||||
AddAssert("count decreased by one", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Count, () => Is.EqualTo(countBeforeDeletion - 1));
|
() => Beatmap.Value.BeatmapSetInfo.Beatmaps.Select(b => b.ID), () => Does.Not.Contain(deletedDifficultyID));
|
||||||
|
AddAssert("beatmap set difficulty count decreased by one",
|
||||||
|
() => Beatmap.Value.BeatmapSetInfo.Beatmaps.Count, () => Is.EqualTo(countBeforeDeletion - 1));
|
||||||
AddAssert("set hash changed", () => Beatmap.Value.BeatmapSetInfo.Hash, () => Is.Not.EqualTo(beatmapSetHashBefore));
|
AddAssert("set hash changed", () => Beatmap.Value.BeatmapSetInfo.Hash, () => Is.Not.EqualTo(beatmapSetHashBefore));
|
||||||
|
AddAssert($"difficulty {i} is deleted from realm",
|
||||||
|
() => Realm.Run(r => r.Find<BeatmapInfo>(deletedDifficultyID)), () => Is.Null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,14 +35,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
|
var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
|
||||||
|
|
||||||
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||||
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Counters.Any(kc => kc.CountPresses.Value > 2));
|
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.InputCountController.Triggers.Any(kc => kc.ActivationCount.Value > 2));
|
||||||
|
|
||||||
seekTo(referenceBeatmap.Breaks[0].StartTime);
|
seekTo(referenceBeatmap.Breaks[0].StartTime);
|
||||||
AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting.Value);
|
AddAssert("keys not counting", () => !Player.HUDOverlay.InputCountController.IsCounting.Value);
|
||||||
AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType<BreakInfo>().Single().AccuracyDisplay.Current.Value == 1);
|
AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType<BreakInfo>().Single().AccuracyDisplay.Current.Value == 1);
|
||||||
|
|
||||||
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
|
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
|
||||||
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Counters.All(kc => kc.CountPresses.Value == 0));
|
AddUntilStep("key counter reset", () => Player.HUDOverlay.InputCountController.Triggers.All(kc => kc.ActivationCount.Value == 0));
|
||||||
|
|
||||||
seekTo(referenceBeatmap.HitObjects[^1].GetEndTime());
|
seekTo(referenceBeatmap.HitObjects[^1].GetEndTime());
|
||||||
AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true);
|
AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true);
|
||||||
|
@ -18,6 +18,7 @@ using osu.Game.Rulesets.Osu;
|
|||||||
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Storyboards;
|
using osu.Game.Storyboards;
|
||||||
|
|
||||||
@ -77,7 +78,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
(typeof(ScoreProcessor), actualComponentsContainer.Dependencies.Get<ScoreProcessor>()),
|
(typeof(ScoreProcessor), actualComponentsContainer.Dependencies.Get<ScoreProcessor>()),
|
||||||
(typeof(HealthProcessor), actualComponentsContainer.Dependencies.Get<HealthProcessor>()),
|
(typeof(HealthProcessor), actualComponentsContainer.Dependencies.Get<HealthProcessor>()),
|
||||||
(typeof(GameplayState), actualComponentsContainer.Dependencies.Get<GameplayState>()),
|
(typeof(GameplayState), actualComponentsContainer.Dependencies.Get<GameplayState>()),
|
||||||
(typeof(IGameplayClock), actualComponentsContainer.Dependencies.Get<IGameplayClock>())
|
(typeof(IGameplayClock), actualComponentsContainer.Dependencies.Get<IGameplayClock>()),
|
||||||
|
(typeof(InputCountController), actualComponentsContainer.Dependencies.Get<InputCountController>())
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
public partial class TestSceneClicksPerSecondCalculator : OsuTestScene
|
public partial class TestSceneClicksPerSecondCalculator : OsuTestScene
|
||||||
{
|
{
|
||||||
private ClicksPerSecondCalculator calculator = null!;
|
private ClicksPerSecondController controller = null!;
|
||||||
|
|
||||||
private TestGameplayClock manualGameplayClock = null!;
|
private TestGameplayClock manualGameplayClock = null!;
|
||||||
|
|
||||||
@ -34,11 +34,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
CachedDependencies = new (Type, object)[] { (typeof(IGameplayClock), manualGameplayClock) },
|
CachedDependencies = new (Type, object)[] { (typeof(IGameplayClock), manualGameplayClock) },
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
calculator = new ClicksPerSecondCalculator(),
|
controller = new ClicksPerSecondController(),
|
||||||
new DependencyProvidingContainer
|
new DependencyProvidingContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondCalculator), calculator) },
|
CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondController), controller) },
|
||||||
Child = new ClicksPerSecondCounter
|
Child = new ClicksPerSecondCounter
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
checkClicksPerSecondValue(6);
|
checkClicksPerSecondValue(6);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkClicksPerSecondValue(int i) => AddAssert("clicks/s is correct", () => calculator.Value, () => Is.EqualTo(i));
|
private void checkClicksPerSecondValue(int i) => AddAssert("clicks/s is correct", () => controller.Value, () => Is.EqualTo(i));
|
||||||
|
|
||||||
private void seekClockImmediately(double time) => manualGameplayClock.CurrentTime = time;
|
private void seekClockImmediately(double time) => manualGameplayClock.CurrentTime = time;
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
foreach (double timestamp in inputs)
|
foreach (double timestamp in inputs)
|
||||||
{
|
{
|
||||||
seekClockImmediately(timestamp);
|
seekClockImmediately(timestamp);
|
||||||
calculator.AddInputTimestamp();
|
controller.AddInputTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
seekClockImmediately(baseTime);
|
seekClockImmediately(baseTime);
|
||||||
|
@ -7,8 +7,8 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -31,11 +31,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||||
addSeekStep(3000);
|
addSeekStep(3000);
|
||||||
AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
|
AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
|
||||||
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Counters.Select(kc => kc.CountPresses.Value).Sum() == 15);
|
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.InputCountController.Triggers.Select(kc => kc.ActivationCount.Value).Sum() == 15);
|
||||||
AddStep("clear results", () => Player.Results.Clear());
|
AddStep("clear results", () => Player.Results.Clear());
|
||||||
addSeekStep(0);
|
addSeekStep(0);
|
||||||
AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
|
AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
|
||||||
AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Counters.All(kc => kc.CountPresses.Value == 0));
|
AddUntilStep("key counters reset", () => Player.HUDOverlay.InputCountController.Triggers.All(kc => kc.ActivationCount.Value == 0));
|
||||||
AddAssert("no results triggered", () => Player.Results.Count == 0);
|
AddAssert("no results triggered", () => Player.Results.Count == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,8 +44,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock());
|
private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock());
|
||||||
|
|
||||||
// best way to check without exposing.
|
// best way to check without exposing.
|
||||||
private Drawable hideTarget => hudOverlay.KeyCounter;
|
private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinComponentsContainer>().First();
|
||||||
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
|
private Drawable keyCounterFlow => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddAssert("showhud is set", () => hudOverlay.ShowHud.Value);
|
AddAssert("showhud is set", () => hudOverlay.ShowHud.Value);
|
||||||
|
|
||||||
AddAssert("hidetarget is visible", () => hideTarget.IsPresent);
|
AddAssert("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
|
||||||
AddAssert("key counter flow is visible", () => keyCounterFlow.IsPresent);
|
AddAssert("key counter flow is visible", () => keyCounterFlow.IsPresent);
|
||||||
AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent);
|
AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent);
|
||||||
}
|
}
|
||||||
@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||||
|
|
||||||
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
|
AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
|
||||||
AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
|
AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
|
||||||
|
|
||||||
// Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above.
|
// Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above.
|
||||||
@ -109,13 +109,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
|
AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
|
||||||
|
|
||||||
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
|
AddUntilStep("wait for fade", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
|
||||||
|
|
||||||
AddStep("trigger momentary show", () => InputManager.PressKey(Key.ControlLeft));
|
AddStep("trigger momentary show", () => InputManager.PressKey(Key.ControlLeft));
|
||||||
AddUntilStep("wait for visible", () => hideTarget.IsPresent);
|
AddUntilStep("wait for visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
|
||||||
|
|
||||||
AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft));
|
AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft));
|
||||||
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
|
AddUntilStep("wait for fade", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -138,16 +138,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("hide key overlay", () =>
|
AddStep("hide key overlay", () =>
|
||||||
{
|
{
|
||||||
localConfig.SetValue(OsuSetting.KeyOverlay, false);
|
localConfig.SetValue(OsuSetting.KeyOverlay, false);
|
||||||
hudOverlay.KeyCounter.AlwaysVisible.Value = false;
|
var kcd = hudOverlay.ChildrenOfType<KeyCounterDisplay>().FirstOrDefault();
|
||||||
|
if (kcd != null)
|
||||||
|
kcd.AlwaysVisible.Value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||||
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
|
AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
|
||||||
AddAssert("key counters hidden", () => !keyCounterFlow.IsPresent);
|
AddUntilStep("key counters hidden", () => !keyCounterFlow.IsPresent);
|
||||||
|
|
||||||
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
|
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
|
||||||
AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent);
|
AddUntilStep("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
|
||||||
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
|
AddUntilStep("key counters still hidden", () => !keyCounterFlow.IsPresent);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -169,7 +171,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||||
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
|
AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
|
||||||
|
|
||||||
AddStep("attempt activate", () =>
|
AddStep("attempt activate", () =>
|
||||||
{
|
{
|
||||||
@ -209,7 +211,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||||
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
|
AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
|
||||||
|
|
||||||
AddStep("attempt seek", () =>
|
AddStep("attempt seek", () =>
|
||||||
{
|
{
|
||||||
@ -234,7 +236,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
createNew();
|
createNew();
|
||||||
|
|
||||||
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
|
|
||||||
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Alpha == 0);
|
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Alpha == 0);
|
||||||
AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().All(c => c.ComponentsLoaded));
|
AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().All(c => c.ComponentsLoaded));
|
||||||
|
|
||||||
@ -253,7 +254,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
createNew();
|
createNew();
|
||||||
|
|
||||||
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
|
|
||||||
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Alpha == 0);
|
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Alpha == 0);
|
||||||
|
|
||||||
AddStep("reload components", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Reload());
|
AddStep("reload components", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Reload());
|
||||||
@ -267,7 +267,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
||||||
|
|
||||||
// Add any key just to display the key counter visually.
|
// Add any key just to display the key counter visually.
|
||||||
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||||
|
|
||||||
scoreProcessor.Combo.Value = 1;
|
scoreProcessor.Combo.Value = 1;
|
||||||
|
|
||||||
@ -275,6 +275,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
Child = hudOverlay;
|
Child = hudOverlay;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
|
||||||
|
AddUntilStep("wait for components present", () => hudOverlay.ChildrenOfType<KeyCounterDisplay>().FirstOrDefault() != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
public partial class TestSceneJudgementCounter : OsuTestScene
|
public partial class TestSceneJudgementCounter : OsuTestScene
|
||||||
{
|
{
|
||||||
private ScoreProcessor scoreProcessor = null!;
|
private ScoreProcessor scoreProcessor = null!;
|
||||||
private JudgementTally judgementTally = null!;
|
private JudgementCountController judgementCountController = null!;
|
||||||
private TestJudgementCounterDisplay counterDisplay = null!;
|
private TestJudgementCounterDisplay counterDisplay = null!;
|
||||||
|
|
||||||
private DependencyProvidingContainer content = null!;
|
private DependencyProvidingContainer content = null!;
|
||||||
@ -47,11 +47,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
CachedDependencies = new (Type, object)[] { (typeof(ScoreProcessor), scoreProcessor), (typeof(Ruleset), ruleset) },
|
CachedDependencies = new (Type, object)[] { (typeof(ScoreProcessor), scoreProcessor), (typeof(Ruleset), ruleset) },
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
judgementTally = new JudgementTally(),
|
judgementCountController = new JudgementCountController(),
|
||||||
content = new DependencyProvidingContainer
|
content = new DependencyProvidingContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
CachedDependencies = new (Type, object)[] { (typeof(JudgementTally), judgementTally) },
|
CachedDependencies = new (Type, object)[] { (typeof(JudgementCountController), judgementCountController) },
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
@ -15,63 +17,66 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public partial class TestSceneKeyCounter : OsuManualInputManagerTestScene
|
public partial class TestSceneKeyCounter : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly InputCountController controller;
|
||||||
|
|
||||||
public TestSceneKeyCounter()
|
public TestSceneKeyCounter()
|
||||||
{
|
{
|
||||||
KeyCounterDisplay defaultDisplay = new DefaultKeyCounterDisplay
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
controller = new InputCountController(),
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(72.7f),
|
||||||
|
Children = new KeyCounterDisplay[]
|
||||||
|
{
|
||||||
|
new DefaultKeyCounterDisplay
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Position = new Vector2(0, 72.7f)
|
},
|
||||||
};
|
new ArgonKeyCounterDisplay
|
||||||
|
|
||||||
KeyCounterDisplay argonDisplay = new ArgonKeyCounterDisplay
|
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Position = new Vector2(0, -72.7f)
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
defaultDisplay.AddRange(new InputTrigger[]
|
var inputTriggers = new InputTrigger[]
|
||||||
{
|
{
|
||||||
new KeyCounterKeyboardTrigger(Key.X),
|
new KeyCounterKeyboardTrigger(Key.X),
|
||||||
new KeyCounterKeyboardTrigger(Key.X),
|
new KeyCounterKeyboardTrigger(Key.X),
|
||||||
new KeyCounterMouseTrigger(MouseButton.Left),
|
new KeyCounterMouseTrigger(MouseButton.Left),
|
||||||
new KeyCounterMouseTrigger(MouseButton.Right),
|
new KeyCounterMouseTrigger(MouseButton.Right),
|
||||||
});
|
};
|
||||||
|
|
||||||
argonDisplay.AddRange(new InputTrigger[]
|
AddRange(inputTriggers);
|
||||||
{
|
controller.AddRange(inputTriggers);
|
||||||
new KeyCounterKeyboardTrigger(Key.X),
|
|
||||||
new KeyCounterKeyboardTrigger(Key.X),
|
|
||||||
new KeyCounterMouseTrigger(MouseButton.Left),
|
|
||||||
new KeyCounterMouseTrigger(MouseButton.Right),
|
|
||||||
});
|
|
||||||
|
|
||||||
var testCounter = (DefaultKeyCounter)defaultDisplay.Counters.First();
|
|
||||||
|
|
||||||
AddStep("Add random", () =>
|
AddStep("Add random", () =>
|
||||||
{
|
{
|
||||||
Key key = (Key)((int)Key.A + RNG.Next(26));
|
Key key = (Key)((int)Key.A + RNG.Next(26));
|
||||||
defaultDisplay.Add(new KeyCounterKeyboardTrigger(key));
|
var trigger = new KeyCounterKeyboardTrigger(key);
|
||||||
argonDisplay.Add(new KeyCounterKeyboardTrigger(key));
|
Add(trigger);
|
||||||
|
controller.Add(trigger);
|
||||||
});
|
});
|
||||||
|
|
||||||
Key testKey = ((KeyCounterKeyboardTrigger)defaultDisplay.Counters.First().Trigger).Key;
|
InputTrigger testTrigger = controller.Triggers.First();
|
||||||
|
Key testKey = ((KeyCounterKeyboardTrigger)testTrigger).Key;
|
||||||
|
|
||||||
addPressKeyStep();
|
addPressKeyStep();
|
||||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 1);
|
AddAssert($"Check {testKey} counter after keypress", () => testTrigger.ActivationCount.Value == 1);
|
||||||
addPressKeyStep();
|
addPressKeyStep();
|
||||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 2);
|
AddAssert($"Check {testKey} counter after keypress", () => testTrigger.ActivationCount.Value == 2);
|
||||||
AddStep("Disable counting", () =>
|
AddStep("Disable counting", () => controller.IsCounting.Value = false);
|
||||||
{
|
|
||||||
argonDisplay.IsCounting.Value = false;
|
|
||||||
defaultDisplay.IsCounting.Value = false;
|
|
||||||
});
|
|
||||||
addPressKeyStep();
|
addPressKeyStep();
|
||||||
AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses.Value == 2);
|
AddAssert($"Check {testKey} count has not changed", () => testTrigger.ActivationCount.Value == 2);
|
||||||
|
|
||||||
Add(defaultDisplay);
|
|
||||||
Add(argonDisplay);
|
|
||||||
|
|
||||||
void addPressKeyStep() => AddStep($"Press {testKey} key", () => InputManager.Key(testKey));
|
void addPressKeyStep() => AddStep($"Press {testKey} key", () => InputManager.Key(testKey));
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
protected override void AddCheckSteps()
|
protected override void AddCheckSteps()
|
||||||
{
|
{
|
||||||
AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0);
|
AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0);
|
||||||
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Counters.Any(kc => kc.CountPresses.Value > 0));
|
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.InputCountController.Triggers.Any(kc => kc.ActivationCount.Value > 0));
|
||||||
AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail);
|
AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Add any key just to display the key counter visually.
|
// Add any key just to display the key counter visually.
|
||||||
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||||
scoreProcessor.Combo.Value = 1;
|
scoreProcessor.Combo.Value = 1;
|
||||||
|
|
||||||
return new Container
|
return new Container
|
||||||
|
@ -19,6 +19,7 @@ using osu.Game.Rulesets.Osu;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Tests.Gameplay;
|
using osu.Game.Tests.Gameplay;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -43,8 +44,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
private IEnumerable<HUDOverlay> hudOverlays => CreatedDrawables.OfType<HUDOverlay>();
|
private IEnumerable<HUDOverlay> hudOverlays => CreatedDrawables.OfType<HUDOverlay>();
|
||||||
|
|
||||||
// best way to check without exposing.
|
// best way to check without exposing.
|
||||||
private Drawable hideTarget => hudOverlay.KeyCounter;
|
private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinComponentsContainer>().First();
|
||||||
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
|
private Drawable keyCounterFlow => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestComboCounterIncrementing()
|
public void TestComboCounterIncrementing()
|
||||||
@ -62,7 +63,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
float? initialAlpha = null;
|
float? initialAlpha = null;
|
||||||
|
|
||||||
createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha);
|
createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha);
|
||||||
AddUntilStep("wait for load", () => hudOverlay.IsAlive);
|
|
||||||
AddAssert("initial alpha was less than 1", () => initialAlpha < 1);
|
AddAssert("initial alpha was less than 1", () => initialAlpha < 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false));
|
AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false));
|
||||||
|
|
||||||
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
|
AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
|
||||||
AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
|
AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
|
||||||
|
|
||||||
// Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above.
|
// Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above.
|
||||||
@ -89,13 +89,16 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
||||||
|
|
||||||
// Add any key just to display the key counter visually.
|
// Add any key just to display the key counter visually.
|
||||||
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||||
|
|
||||||
action?.Invoke(hudOverlay);
|
action?.Invoke(hudOverlay);
|
||||||
|
|
||||||
return hudOverlay;
|
return hudOverlay;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
AddUntilStep("HUD overlay loaded", () => hudOverlay.IsAlive);
|
||||||
|
AddUntilStep("components container loaded",
|
||||||
|
() => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Any(scc => scc.ComponentsLoaded));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
||||||
|
@ -107,6 +107,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestBeatmapDownloadingStates()
|
public void TestBeatmapDownloadingStates()
|
||||||
{
|
{
|
||||||
|
AddStep("set to unknown", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Unknown()));
|
||||||
AddStep("set to no map", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded()));
|
AddStep("set to no map", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded()));
|
||||||
AddStep("set to downloading map", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0)));
|
AddStep("set to downloading map", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0)));
|
||||||
|
|
||||||
@ -382,6 +383,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for list to load", () => participantsList?.IsLoaded == true);
|
AddUntilStep("wait for list to load", () => participantsList?.IsLoaded == true);
|
||||||
|
|
||||||
|
AddStep("set beatmap available", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.LocallyAvailable()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkProgressBarVisibility(bool visible) =>
|
private void checkProgressBarVisibility(bool visible) =>
|
||||||
|
@ -210,7 +210,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("collection filter still selected", () => control.CreateCriteria().CollectionBeatmapMD5Hashes.Any());
|
AddAssert("collection filter still selected", () => control.CreateCriteria().CollectionBeatmapMD5Hashes?.Any() == true);
|
||||||
|
|
||||||
AddAssert("filter request not fired", () => !received);
|
AddAssert("filter request not fired", () => !received);
|
||||||
}
|
}
|
||||||
|
@ -188,7 +188,7 @@ namespace osu.Game.Tournament.Components
|
|||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new DiffPiece(stats),
|
new DiffPiece(stats),
|
||||||
new DiffPiece(("Star Rating", $"{beatmap.StarRating:0.##}{srExtra}"))
|
new DiffPiece(("Star Rating", $"{beatmap.StarRating:0.00}{srExtra}"))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new FillFlowContainer
|
new FillFlowContainer
|
||||||
|
@ -20,6 +20,7 @@ using osu.Game.IO;
|
|||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using Realms;
|
using Realms;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
@ -204,6 +205,15 @@ namespace osu.Game.Beatmaps
|
|||||||
protected override void PostImport(BeatmapSetInfo model, Realm realm, ImportParameters parameters)
|
protected override void PostImport(BeatmapSetInfo model, Realm realm, ImportParameters parameters)
|
||||||
{
|
{
|
||||||
base.PostImport(model, realm, parameters);
|
base.PostImport(model, realm, parameters);
|
||||||
|
|
||||||
|
// Scores are stored separately from beatmaps, and persist even when a beatmap is modified or deleted.
|
||||||
|
// Let's reattach any matching scores that exist in the database, based on hash.
|
||||||
|
foreach (BeatmapInfo beatmap in model.Beatmaps)
|
||||||
|
{
|
||||||
|
foreach (var score in realm.All<ScoreInfo>().Where(score => score.BeatmapHash == beatmap.Hash))
|
||||||
|
score.BeatmapInfo = beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
ProcessBeatmap?.Invoke(model, parameters.Batch ? MetadataLookupScope.LocalCacheFirst : MetadataLookupScope.OnlineFirst);
|
ProcessBeatmap?.Invoke(model, parameters.Batch ? MetadataLookupScope.LocalCacheFirst : MetadataLookupScope.OnlineFirst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,6 +339,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
DeleteFile(setInfo, beatmapInfo.File);
|
DeleteFile(setInfo, beatmapInfo.File);
|
||||||
setInfo.Beatmaps.Remove(beatmapInfo);
|
setInfo.Beatmaps.Remove(beatmapInfo);
|
||||||
|
r.Remove(beatmapInfo.Metadata);
|
||||||
|
r.Remove(beatmapInfo);
|
||||||
|
|
||||||
updateHashAndMarkDirty(setInfo);
|
updateHashAndMarkDirty(setInfo);
|
||||||
workingBeatmapCache.Invalidate(setInfo);
|
workingBeatmapCache.Invalidate(setInfo);
|
||||||
|
@ -188,7 +188,7 @@ namespace osu.Game.Collections
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
X = -OsuScrollContainer.SCROLL_BAR_HEIGHT,
|
X = -OsuScrollContainer.SCROLL_BAR_WIDTH,
|
||||||
Scale = new Vector2(0.65f),
|
Scale = new Vector2(0.65f),
|
||||||
Action = addOrRemove,
|
Action = addOrRemove,
|
||||||
});
|
});
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using AutoMapper.Internal;
|
using AutoMapper.Internal;
|
||||||
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
@ -52,10 +54,23 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
foreach (var beatmap in s.Beatmaps)
|
foreach (var beatmap in s.Beatmaps)
|
||||||
{
|
{
|
||||||
var existing = d.Beatmaps.FirstOrDefault(b => b.ID == beatmap.ID);
|
// Importantly, search all of realm for the beatmap (not just the set's beatmaps).
|
||||||
|
// It may have gotten detached, and if that's the case let's use this opportunity to fix
|
||||||
|
// things up.
|
||||||
|
var existingBeatmap = d.Realm.Find<BeatmapInfo>(beatmap.ID);
|
||||||
|
|
||||||
if (existing != null)
|
if (existingBeatmap != null)
|
||||||
copyChangesToRealm(beatmap, existing);
|
{
|
||||||
|
// As above, reattach if it happens to not be in the set's beatmaps.
|
||||||
|
if (!d.Beatmaps.Contains(existingBeatmap))
|
||||||
|
{
|
||||||
|
Debug.Fail("Beatmaps should never become detached under normal circumstances. If this ever triggers, it should be investigated further.");
|
||||||
|
Logger.Log("WARNING: One of the difficulties in a beatmap was detached from its set. Please save a copy of logs and report this to devs.", LoggingTarget.Database, LogLevel.Important);
|
||||||
|
d.Beatmaps.Add(existingBeatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
copyChangesToRealm(beatmap, existingBeatmap);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var newBeatmap = new BeatmapInfo
|
var newBeatmap = new BeatmapInfo
|
||||||
@ -64,6 +79,7 @@ namespace osu.Game.Database
|
|||||||
BeatmapSet = d,
|
BeatmapSet = d,
|
||||||
Ruleset = d.Realm.Find<RulesetInfo>(beatmap.Ruleset.ShortName)
|
Ruleset = d.Realm.Find<RulesetInfo>(beatmap.Ruleset.ShortName)
|
||||||
};
|
};
|
||||||
|
|
||||||
d.Beatmaps.Add(newBeatmap);
|
d.Beatmaps.Add(newBeatmap);
|
||||||
copyChangesToRealm(beatmap, newBeatmap);
|
copyChangesToRealm(beatmap, newBeatmap);
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
public partial class OsuScrollContainer<T> : ScrollContainer<T> where T : Drawable
|
public partial class OsuScrollContainer<T> : ScrollContainer<T> where T : Drawable
|
||||||
{
|
{
|
||||||
public const float SCROLL_BAR_HEIGHT = 10;
|
public const float SCROLL_BAR_WIDTH = 10;
|
||||||
public const float SCROLL_BAR_PADDING = 3;
|
public const float SCROLL_BAR_PADDING = 3;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -139,6 +139,8 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
private readonly Box box;
|
private readonly Box box;
|
||||||
|
|
||||||
|
protected override float MinimumDimSize => SCROLL_BAR_WIDTH * 3;
|
||||||
|
|
||||||
public OsuScrollbar(Direction scrollDir)
|
public OsuScrollbar(Direction scrollDir)
|
||||||
: base(scrollDir)
|
: base(scrollDir)
|
||||||
{
|
{
|
||||||
@ -147,7 +149,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
CornerRadius = 5;
|
CornerRadius = 5;
|
||||||
|
|
||||||
// needs to be set initially for the ResizeTo to respect minimum size
|
// needs to be set initially for the ResizeTo to respect minimum size
|
||||||
Size = new Vector2(SCROLL_BAR_HEIGHT);
|
Size = new Vector2(SCROLL_BAR_WIDTH);
|
||||||
|
|
||||||
const float margin = 3;
|
const float margin = 3;
|
||||||
|
|
||||||
@ -173,11 +175,10 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
public override void ResizeTo(float val, int duration = 0, Easing easing = Easing.None)
|
public override void ResizeTo(float val, int duration = 0, Easing easing = Easing.None)
|
||||||
{
|
{
|
||||||
Vector2 size = new Vector2(SCROLL_BAR_HEIGHT)
|
this.ResizeTo(new Vector2(SCROLL_BAR_WIDTH)
|
||||||
{
|
{
|
||||||
[(int)ScrollDirection] = val
|
[(int)ScrollDirection] = val
|
||||||
};
|
}, duration, easing);
|
||||||
this.ResizeTo(size, duration, easing);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
@ -24,6 +24,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString SignedIn => new TranslatableString(getKey(@"signed_in"), @"Signed in");
|
public static LocalisableString SignedIn => new TranslatableString(getKey(@"signed_in"), @"Signed in");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Sign out"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString SignOut => new TranslatableString(getKey(@"sign_out"), @"Sign out");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Account"
|
/// "Account"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -63,6 +63,41 @@ Please try changing your audio device to a working setting.");
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ScoreOverlayDisabled(LocalisableString arg0) => new TranslatableString(getKey(@"score_overlay_disabled"), @"The score overlay is currently disabled. You can toggle this by pressing {0}.", arg0);
|
public static LocalisableString ScoreOverlayDisabled(LocalisableString arg0) => new TranslatableString(getKey(@"score_overlay_disabled"), @"The score overlay is currently disabled. You can toggle this by pressing {0}.", arg0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "The URL {0} has an unsupported or dangerous protocol and will not be opened."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString UnsupportedOrDangerousUrlProtocol(string url) => new TranslatableString(getKey(@"unsupported_or_dangerous_url_protocol"), @"The URL {0} has an unsupported or dangerous protocol and will not be opened.", url);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Subsequent messages have been logged. Click to view log files."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString SubsequentMessagesLogged => new TranslatableString(getKey(@"subsequent_messages_logged"), @"Subsequent messages have been logged. Click to view log files.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Disabling tablet support due to error: "{0}""
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString TabletSupportDisabledDueToError(string message) => new TranslatableString(getKey(@"tablet_support_disabled_due_to_error"), @"Disabling tablet support due to error: ""{0}""", message);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Encountered tablet warning, your tablet may not function correctly. Click here for a list of all tablets supported."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString EncounteredTabletWarning => new TranslatableString(getKey(@"encountered_tablet_warning"), @"Encountered tablet warning, your tablet may not function correctly. Click here for a list of all tablets supported.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "This link type is not yet supported!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString LinkTypeNotSupported => new TranslatableString(getKey(@"unsupported_link_type"), @"This link type is not yet supported!");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "You received a private message from '{0}'. Click to read it!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString PrivateMessageReceived(string username) => new TranslatableString(getKey(@"private_message_received"), @"You received a private message from '{0}'. Click to read it!", username);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Your name was mentioned in chat by '{0}'. Click to find out why!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString YourNameWasMentioned(string username) => new TranslatableString(getKey(@"your_name_was_mentioned"), @"Your name was mentioned in chat by '{0}'. Click to find out why!", username);
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -154,7 +155,7 @@ namespace osu.Game.Online.Chat
|
|||||||
: base(message, channel)
|
: base(message, channel)
|
||||||
{
|
{
|
||||||
Icon = FontAwesome.Solid.Envelope;
|
Icon = FontAwesome.Solid.Envelope;
|
||||||
Text = $"You received a private message from '{message.Sender.Username}'. Click to read it!";
|
Text = NotificationsStrings.PrivateMessageReceived(message.Sender.Username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +165,7 @@ namespace osu.Game.Online.Chat
|
|||||||
: base(message, channel)
|
: base(message, channel)
|
||||||
{
|
{
|
||||||
Icon = FontAwesome.Solid.At;
|
Icon = FontAwesome.Solid.At;
|
||||||
Text = $"Your name was mentioned in chat by '{message.Sender.Username}'. Click to find out why!";
|
Text = NotificationsStrings.YourNameWasMentioned(message.Sender.Username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// The availability state of the current beatmap.
|
/// The availability state of the current beatmap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Key(2)]
|
[Key(2)]
|
||||||
public BeatmapAvailability BeatmapAvailability { get; set; } = BeatmapAvailability.LocallyAvailable();
|
public BeatmapAvailability BeatmapAvailability { get; set; } = BeatmapAvailability.Unknown();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Any mods applicable only to the local user.
|
/// Any mods applicable only to the local user.
|
||||||
|
@ -34,6 +34,7 @@ namespace osu.Game.Online.Rooms
|
|||||||
DownloadProgress = downloadProgress;
|
DownloadProgress = downloadProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static BeatmapAvailability Unknown() => new BeatmapAvailability(DownloadState.Unknown);
|
||||||
public static BeatmapAvailability NotDownloaded() => new BeatmapAvailability(DownloadState.NotDownloaded);
|
public static BeatmapAvailability NotDownloaded() => new BeatmapAvailability(DownloadState.NotDownloaded);
|
||||||
public static BeatmapAvailability Downloading(float progress) => new BeatmapAvailability(DownloadState.Downloading, progress);
|
public static BeatmapAvailability Downloading(float progress) => new BeatmapAvailability(DownloadState.Downloading, progress);
|
||||||
public static BeatmapAvailability Importing() => new BeatmapAvailability(DownloadState.Importing);
|
public static BeatmapAvailability Importing() => new BeatmapAvailability(DownloadState.Importing);
|
||||||
|
@ -60,6 +60,15 @@ namespace osu.Game.Online.Rooms
|
|||||||
if (item.NewValue == null)
|
if (item.NewValue == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Initially set to unknown until we have attained a good state.
|
||||||
|
// This has the wanted side effect of forcing a state change when the current playlist
|
||||||
|
// item changes at the server but our local availability doesn't necessarily change
|
||||||
|
// (ie. we have both the previous and next item LocallyAvailable).
|
||||||
|
//
|
||||||
|
// Note that even without this, the server will trigger a state change and things will work.
|
||||||
|
// This is just for safety.
|
||||||
|
availability.Value = BeatmapAvailability.Unknown();
|
||||||
|
|
||||||
downloadTracker?.RemoveAndDisposeImmediately();
|
downloadTracker?.RemoveAndDisposeImmediately();
|
||||||
selectedBeatmap = null;
|
selectedBeatmap = null;
|
||||||
|
|
||||||
@ -115,6 +124,9 @@ namespace osu.Game.Online.Rooms
|
|||||||
switch (downloadTracker.State.Value)
|
switch (downloadTracker.State.Value)
|
||||||
{
|
{
|
||||||
case DownloadState.Unknown:
|
case DownloadState.Unknown:
|
||||||
|
availability.Value = BeatmapAvailability.Unknown();
|
||||||
|
break;
|
||||||
|
|
||||||
case DownloadState.NotDownloaded:
|
case DownloadState.NotDownloaded:
|
||||||
availability.Value = BeatmapAvailability.NotDownloaded();
|
availability.Value = BeatmapAvailability.NotDownloaded();
|
||||||
break;
|
break;
|
||||||
|
@ -435,7 +435,7 @@ namespace osu.Game
|
|||||||
case LinkAction.Spectate:
|
case LinkAction.Spectate:
|
||||||
waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification
|
waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification
|
||||||
{
|
{
|
||||||
Text = @"This link type is not yet supported!",
|
Text = NotificationsStrings.LinkTypeNotSupported,
|
||||||
Icon = FontAwesome.Solid.LifeRing,
|
Icon = FontAwesome.Solid.LifeRing,
|
||||||
}));
|
}));
|
||||||
break;
|
break;
|
||||||
@ -477,7 +477,7 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
Notifications.Post(new SimpleErrorNotification
|
Notifications.Post(new SimpleErrorNotification
|
||||||
{
|
{
|
||||||
Text = $"The URL {url} has an unsupported or dangerous protocol and will not be opened.",
|
Text = NotificationsStrings.UnsupportedOrDangerousUrlProtocol(url),
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -1147,7 +1147,7 @@ namespace osu.Game
|
|||||||
Schedule(() => Notifications.Post(new SimpleNotification
|
Schedule(() => Notifications.Post(new SimpleNotification
|
||||||
{
|
{
|
||||||
Icon = FontAwesome.Solid.EllipsisH,
|
Icon = FontAwesome.Solid.EllipsisH,
|
||||||
Text = "Subsequent messages have been logged. Click to view log files.",
|
Text = NotificationsStrings.SubsequentMessagesLogged,
|
||||||
Activated = () =>
|
Activated = () =>
|
||||||
{
|
{
|
||||||
Storage.GetStorageForDirectory(@"logs").PresentFileExternally(logFile);
|
Storage.GetStorageForDirectory(@"logs").PresentFileExternally(logFile);
|
||||||
@ -1164,7 +1164,9 @@ namespace osu.Game
|
|||||||
private void forwardTabletLogsToNotifications()
|
private void forwardTabletLogsToNotifications()
|
||||||
{
|
{
|
||||||
const string tablet_prefix = @"[Tablet] ";
|
const string tablet_prefix = @"[Tablet] ";
|
||||||
|
|
||||||
bool notifyOnWarning = true;
|
bool notifyOnWarning = true;
|
||||||
|
bool notifyOnError = true;
|
||||||
|
|
||||||
Logger.NewEntry += entry =>
|
Logger.NewEntry += entry =>
|
||||||
{
|
{
|
||||||
@ -1175,11 +1177,16 @@ namespace osu.Game
|
|||||||
|
|
||||||
if (entry.Level == LogLevel.Error)
|
if (entry.Level == LogLevel.Error)
|
||||||
{
|
{
|
||||||
|
if (!notifyOnError)
|
||||||
|
return;
|
||||||
|
|
||||||
|
notifyOnError = false;
|
||||||
|
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
Notifications.Post(new SimpleNotification
|
Notifications.Post(new SimpleNotification
|
||||||
{
|
{
|
||||||
Text = $"Disabling tablet support due to error: \"{message}\"",
|
Text = NotificationsStrings.TabletSupportDisabledDueToError(message),
|
||||||
Icon = FontAwesome.Solid.PenSquare,
|
Icon = FontAwesome.Solid.PenSquare,
|
||||||
IconColour = Colours.RedDark,
|
IconColour = Colours.RedDark,
|
||||||
});
|
});
|
||||||
@ -1196,7 +1203,7 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
Schedule(() => Notifications.Post(new SimpleNotification
|
Schedule(() => Notifications.Post(new SimpleNotification
|
||||||
{
|
{
|
||||||
Text = @"Encountered tablet warning, your tablet may not function correctly. Click here for a list of all tablets supported.",
|
Text = NotificationsStrings.EncounteredTabletWarning,
|
||||||
Icon = FontAwesome.Solid.PenSquare,
|
Icon = FontAwesome.Solid.PenSquare,
|
||||||
IconColour = Colours.YellowDark,
|
IconColour = Colours.YellowDark,
|
||||||
Activated = () =>
|
Activated = () =>
|
||||||
@ -1213,7 +1220,11 @@ namespace osu.Game
|
|||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
ITabletHandler tablet = Host.AvailableInputHandlers.OfType<ITabletHandler>().SingleOrDefault();
|
ITabletHandler tablet = Host.AvailableInputHandlers.OfType<ITabletHandler>().SingleOrDefault();
|
||||||
tablet?.Tablet.BindValueChanged(_ => notifyOnWarning = true, true);
|
tablet?.Tablet.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
notifyOnWarning = true;
|
||||||
|
notifyOnError = true;
|
||||||
|
}, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
|||||||
OnHovered = beatmap =>
|
OnHovered = beatmap =>
|
||||||
{
|
{
|
||||||
showBeatmap(beatmap);
|
showBeatmap(beatmap);
|
||||||
starRating.Text = beatmap.StarRating.ToLocalisableString(@"0.##");
|
starRating.Text = beatmap.StarRating.ToLocalisableString(@"0.00");
|
||||||
starRatingContainer.FadeIn(100);
|
starRatingContainer.FadeIn(100);
|
||||||
},
|
},
|
||||||
OnClicked = beatmap => { Beatmap.Value = beatmap; },
|
OnClicked = beatmap => { Beatmap.Value = beatmap; },
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Login
|
|||||||
[LocalisableDescription(typeof(LoginPanelStrings), nameof(LoginPanelStrings.AppearOffline))]
|
[LocalisableDescription(typeof(LoginPanelStrings), nameof(LoginPanelStrings.AppearOffline))]
|
||||||
AppearOffline,
|
AppearOffline,
|
||||||
|
|
||||||
[LocalisableDescription(typeof(UserVerificationStrings), nameof(UserVerificationStrings.BoxInfoLogoutLink))]
|
[LocalisableDescription(typeof(LoginPanelStrings), nameof(LoginPanelStrings.SignOut))]
|
||||||
SignOut,
|
SignOut,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Overlays
|
|||||||
scrollbarBackground = new Box
|
scrollbarBackground = new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Width = OsuScrollContainer.SCROLL_BAR_HEIGHT,
|
Width = OsuScrollContainer.SCROLL_BAR_WIDTH,
|
||||||
Anchor = Anchor.TopRight,
|
Anchor = Anchor.TopRight,
|
||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
Alpha = 0.5f
|
Alpha = 0.5f
|
||||||
|
@ -335,11 +335,11 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// <returns>The representing <see cref="DrawableHitObject{TObject}"/>.</returns>
|
/// <returns>The representing <see cref="DrawableHitObject{TObject}"/>.</returns>
|
||||||
public abstract DrawableHitObject<TObject> CreateDrawableRepresentation(TObject h);
|
public abstract DrawableHitObject<TObject> CreateDrawableRepresentation(TObject h);
|
||||||
|
|
||||||
public void Attach(KeyCounterDisplay keyCounter) =>
|
public void Attach(InputCountController inputCountController) =>
|
||||||
(KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(keyCounter);
|
(KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(inputCountController);
|
||||||
|
|
||||||
public void Attach(ClicksPerSecondCalculator calculator) =>
|
public void Attach(ClicksPerSecondController controller) =>
|
||||||
(KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(calculator);
|
(KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(controller);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a key conversion input manager. An exception will be thrown if a valid <see cref="RulesetInputManager{T}"/> is not returned.
|
/// Creates a key conversion input manager. An exception will be thrown if a valid <see cref="RulesetInputManager{T}"/> is not returned.
|
||||||
|
21
osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs
Normal file
21
osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// 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.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A target (generally always <see cref="DrawableRuleset"/>) which can attach various skinnable components.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Attach methods will give the target permission to prepare the component into a usable state, usually via
|
||||||
|
/// calling methods on the component (attaching various gameplay devices).
|
||||||
|
/// </remarks>
|
||||||
|
public interface ICanAttachHUDPieces
|
||||||
|
{
|
||||||
|
void Attach(InputCountController inputCountController);
|
||||||
|
void Attach(ClicksPerSecondController controller);
|
||||||
|
}
|
||||||
|
}
|
15
osu.Game/Rulesets/UI/IHasRecordingHandler.cs
Normal file
15
osu.Game/Rulesets/UI/IHasRecordingHandler.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// 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.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Expose the <see cref="ReplayRecorder"/> in a capable <see cref="InputManager"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasRecordingHandler
|
||||||
|
{
|
||||||
|
public ReplayRecorder? Recorder { set; }
|
||||||
|
}
|
||||||
|
}
|
16
osu.Game/Rulesets/UI/IHasReplayHandler.cs
Normal file
16
osu.Game/Rulesets/UI/IHasReplayHandler.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// 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.Input;
|
||||||
|
using osu.Game.Input.Handlers;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Expose the <see cref="ReplayInputHandler"/> in a capable <see cref="InputManager"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasReplayHandler
|
||||||
|
{
|
||||||
|
ReplayInputHandler? ReplayInputHandler { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -160,62 +160,37 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
#region Key Counter Attachment
|
#region Key Counter Attachment
|
||||||
|
|
||||||
public void Attach(KeyCounterDisplay keyCounter)
|
public void Attach(InputCountController inputCountController)
|
||||||
{
|
{
|
||||||
var receptor = new ActionReceptor(keyCounter);
|
var triggers = KeyBindingContainer.DefaultKeyBindings
|
||||||
|
|
||||||
KeyBindingContainer.Add(receptor);
|
|
||||||
|
|
||||||
keyCounter.SetReceptor(receptor);
|
|
||||||
keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings
|
|
||||||
.Select(b => b.GetAction<T>())
|
.Select(b => b.GetAction<T>())
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.OrderBy(action => action)
|
.OrderBy(action => action)
|
||||||
.Select(action => new KeyCounterActionTrigger<T>(action)));
|
.Select(action => new KeyCounterActionTrigger<T>(action))
|
||||||
}
|
.ToArray();
|
||||||
|
|
||||||
private partial class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler<T>
|
KeyBindingContainer.AddRange(triggers);
|
||||||
{
|
inputCountController.AddRange(triggers);
|
||||||
public ActionReceptor(KeyCounterDisplay target)
|
|
||||||
: base(target)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OnPressed(KeyBindingPressEvent<T> e) => Target.Counters.Where(c => c.Trigger is KeyCounterActionTrigger<T>)
|
|
||||||
.Select(c => (KeyCounterActionTrigger<T>)c.Trigger)
|
|
||||||
.Any(c => c.OnPressed(e.Action, Clock.Rate >= 0));
|
|
||||||
|
|
||||||
public void OnReleased(KeyBindingReleaseEvent<T> e)
|
|
||||||
{
|
|
||||||
foreach (var c
|
|
||||||
in Target.Counters.Where(c => c.Trigger is KeyCounterActionTrigger<T>).Select(c => (KeyCounterActionTrigger<T>)c.Trigger))
|
|
||||||
c.OnReleased(e.Action, Clock.Rate >= 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Keys per second Counter Attachment
|
#region Keys per second Counter Attachment
|
||||||
|
|
||||||
public void Attach(ClicksPerSecondCalculator calculator)
|
public void Attach(ClicksPerSecondController controller) => KeyBindingContainer.Add(new ActionListener(controller));
|
||||||
{
|
|
||||||
var listener = new ActionListener(calculator);
|
|
||||||
|
|
||||||
KeyBindingContainer.Add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private partial class ActionListener : Component, IKeyBindingHandler<T>
|
private partial class ActionListener : Component, IKeyBindingHandler<T>
|
||||||
{
|
{
|
||||||
private readonly ClicksPerSecondCalculator calculator;
|
private readonly ClicksPerSecondController controller;
|
||||||
|
|
||||||
public ActionListener(ClicksPerSecondCalculator calculator)
|
public ActionListener(ClicksPerSecondController controller)
|
||||||
{
|
{
|
||||||
this.calculator = calculator;
|
this.controller = controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnPressed(KeyBindingPressEvent<T> e)
|
public bool OnPressed(KeyBindingPressEvent<T> e)
|
||||||
{
|
{
|
||||||
calculator.AddInputTimestamp();
|
controller.AddInputTimestamp();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,29 +222,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Expose the <see cref="ReplayInputHandler"/> in a capable <see cref="InputManager"/>.
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasReplayHandler
|
|
||||||
{
|
|
||||||
ReplayInputHandler ReplayInputHandler { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IHasRecordingHandler
|
|
||||||
{
|
|
||||||
public ReplayRecorder Recorder { set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Supports attaching various HUD pieces.
|
|
||||||
/// Keys will be populated automatically and a receptor will be injected inside.
|
|
||||||
/// </summary>
|
|
||||||
public interface ICanAttachHUDPieces
|
|
||||||
{
|
|
||||||
void Attach(KeyCounterDisplay keyCounter);
|
|
||||||
void Attach(ClicksPerSecondCalculator calculator);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RulesetInputManagerInputState<T> : InputState
|
public class RulesetInputManagerInputState<T> : InputState
|
||||||
where T : struct
|
where T : struct
|
||||||
{
|
{
|
||||||
|
@ -82,6 +82,7 @@ namespace osu.Game.Scoring
|
|||||||
{
|
{
|
||||||
Ruleset = ruleset ?? new RulesetInfo();
|
Ruleset = ruleset ?? new RulesetInfo();
|
||||||
BeatmapInfo = beatmap ?? new BeatmapInfo();
|
BeatmapInfo = beatmap ?? new BeatmapInfo();
|
||||||
|
BeatmapHash = BeatmapInfo.Hash;
|
||||||
RealmUser = realmUser ?? new RealmUser();
|
RealmUser = realmUser ?? new RealmUser();
|
||||||
ID = Guid.NewGuid();
|
ID = Guid.NewGuid();
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -24,13 +22,17 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
private const float button_padding = 5;
|
private const float button_padding = 5;
|
||||||
|
|
||||||
public Func<float, bool> OnRotation;
|
public Func<float, bool>? OnRotation;
|
||||||
public Func<Vector2, Anchor, bool> OnScale;
|
public Func<Vector2, Anchor, bool>? OnScale;
|
||||||
public Func<Direction, bool, bool> OnFlip;
|
public Func<Direction, bool, bool>? OnFlip;
|
||||||
public Func<bool> OnReverse;
|
public Func<bool>? OnReverse;
|
||||||
|
|
||||||
public Action OperationStarted;
|
public Action? OperationStarted;
|
||||||
public Action OperationEnded;
|
public Action? OperationEnded;
|
||||||
|
|
||||||
|
private SelectionBoxButton? reverseButton;
|
||||||
|
private SelectionBoxButton? rotateClockwiseButton;
|
||||||
|
private SelectionBoxButton? rotateCounterClockwiseButton;
|
||||||
|
|
||||||
private bool canReverse;
|
private bool canReverse;
|
||||||
|
|
||||||
@ -134,7 +136,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string text;
|
private string text = string.Empty;
|
||||||
|
|
||||||
public string Text
|
public string Text
|
||||||
{
|
{
|
||||||
@ -150,13 +152,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SelectionBoxDragHandleContainer dragHandles;
|
private SelectionBoxDragHandleContainer dragHandles = null!;
|
||||||
private FillFlowContainer buttons;
|
private FillFlowContainer buttons = null!;
|
||||||
|
|
||||||
private OsuSpriteText selectionDetailsText;
|
private OsuSpriteText? selectionDetailsText;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load() => recreate();
|
private void load() => recreate();
|
||||||
@ -166,19 +168,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
if (e.Repeat || !e.ControlPressed)
|
if (e.Repeat || !e.ControlPressed)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool runOperationFromHotkey(Func<bool> operation)
|
|
||||||
{
|
|
||||||
operationStarted();
|
|
||||||
bool result = operation?.Invoke() ?? false;
|
|
||||||
operationEnded();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (e.Key)
|
switch (e.Key)
|
||||||
{
|
{
|
||||||
case Key.G:
|
case Key.G:
|
||||||
return CanReverse && runOperationFromHotkey(OnReverse);
|
return CanReverse && reverseButton?.TriggerClick() == true;
|
||||||
|
|
||||||
|
case Key.Comma:
|
||||||
|
return CanRotate && rotateCounterClockwiseButton?.TriggerClick() == true;
|
||||||
|
|
||||||
|
case Key.Period:
|
||||||
|
return CanRotate && rotateClockwiseButton?.TriggerClick() == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.OnKeyDown(e);
|
return base.OnKeyDown(e);
|
||||||
@ -256,13 +255,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
if (CanFlipX) addXFlipComponents();
|
if (CanFlipX) addXFlipComponents();
|
||||||
if (CanFlipY) addYFlipComponents();
|
if (CanFlipY) addYFlipComponents();
|
||||||
if (CanRotate) addRotationComponents();
|
if (CanRotate) addRotationComponents();
|
||||||
if (CanReverse) addButton(FontAwesome.Solid.Backward, "Reverse pattern (Ctrl-G)", () => OnReverse?.Invoke());
|
if (CanReverse) reverseButton = addButton(FontAwesome.Solid.Backward, "Reverse pattern (Ctrl-G)", () => OnReverse?.Invoke());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addRotationComponents()
|
private void addRotationComponents()
|
||||||
{
|
{
|
||||||
addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise", () => OnRotation?.Invoke(-90));
|
rotateCounterClockwiseButton = addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise (Ctrl-<)", () => OnRotation?.Invoke(-90));
|
||||||
addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise", () => OnRotation?.Invoke(90));
|
rotateClockwiseButton = addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise (Ctrl->)", () => OnRotation?.Invoke(90));
|
||||||
|
|
||||||
addRotateHandle(Anchor.TopLeft);
|
addRotateHandle(Anchor.TopLeft);
|
||||||
addRotateHandle(Anchor.TopRight);
|
addRotateHandle(Anchor.TopRight);
|
||||||
@ -300,7 +299,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically", () => OnFlip?.Invoke(Direction.Vertical, false));
|
addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically", () => OnFlip?.Invoke(Direction.Vertical, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addButton(IconUsage icon, string tooltip, Action action)
|
private SelectionBoxButton addButton(IconUsage icon, string tooltip, Action action)
|
||||||
{
|
{
|
||||||
var button = new SelectionBoxButton(icon, tooltip)
|
var button = new SelectionBoxButton(icon, tooltip)
|
||||||
{
|
{
|
||||||
@ -310,6 +309,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
button.OperationStarted += operationStarted;
|
button.OperationStarted += operationStarted;
|
||||||
button.OperationEnded += operationEnded;
|
button.OperationEnded += operationEnded;
|
||||||
buttons.Add(button);
|
buttons.Add(button);
|
||||||
|
|
||||||
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addScaleHandle(Anchor anchor)
|
private void addScaleHandle(Anchor anchor)
|
||||||
@ -376,10 +377,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
float leftExcess = thisQuad.TopLeft.X - parentQuad.TopLeft.X;
|
float leftExcess = thisQuad.TopLeft.X - parentQuad.TopLeft.X;
|
||||||
float rightExcess = parentQuad.TopRight.X - thisQuad.TopRight.X;
|
float rightExcess = parentQuad.TopRight.X - thisQuad.TopRight.X;
|
||||||
|
|
||||||
if (topExcess + bottomExcess < buttons.Height + button_padding)
|
float minHeight = buttons.ScreenSpaceDrawQuad.Height;
|
||||||
|
|
||||||
|
if (topExcess < minHeight && bottomExcess < minHeight)
|
||||||
{
|
{
|
||||||
buttons.Anchor = Anchor.BottomCentre;
|
buttons.Anchor = Anchor.BottomCentre;
|
||||||
buttons.Origin = Anchor.BottomCentre;
|
buttons.Origin = Anchor.BottomCentre;
|
||||||
|
buttons.Y = Math.Min(0, ToLocalSpace(Parent.ScreenSpaceDrawQuad.BottomLeft).Y - DrawHeight);
|
||||||
}
|
}
|
||||||
else if (topExcess > bottomExcess)
|
else if (topExcess > bottomExcess)
|
||||||
{
|
{
|
||||||
|
@ -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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -17,11 +15,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
public sealed partial class SelectionBoxButton : SelectionBoxControl, IHasTooltip
|
public sealed partial class SelectionBoxButton : SelectionBoxControl, IHasTooltip
|
||||||
{
|
{
|
||||||
private SpriteIcon icon;
|
private SpriteIcon icon = null!;
|
||||||
|
|
||||||
private readonly IconUsage iconUsage;
|
private readonly IconUsage iconUsage;
|
||||||
|
|
||||||
public Action Action;
|
public Action? Action;
|
||||||
|
|
||||||
public SelectionBoxButton(IconUsage iconUsage, string tooltip)
|
public SelectionBoxButton(IconUsage iconUsage, string tooltip)
|
||||||
{
|
{
|
||||||
@ -49,6 +47,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
|
Circle.FlashColour(Colours.GrayF, 300);
|
||||||
|
|
||||||
TriggerOperationStarted();
|
TriggerOperationStarted();
|
||||||
Action?.Invoke();
|
Action?.Invoke();
|
||||||
TriggerOperationEnded();
|
TriggerOperationEnded();
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
public event Action OperationStarted;
|
public event Action OperationStarted;
|
||||||
public event Action OperationEnded;
|
public event Action OperationEnded;
|
||||||
|
|
||||||
private Circle circle;
|
protected Circle Circle { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the user is currently holding the control with mouse.
|
/// Whether the user is currently holding the control with mouse.
|
||||||
@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
circle = new Circle
|
Circle = new Circle
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -85,9 +85,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
protected virtual void UpdateHoverState()
|
protected virtual void UpdateHoverState()
|
||||||
{
|
{
|
||||||
if (IsHeld)
|
if (IsHeld)
|
||||||
circle.FadeColour(Colours.GrayF, TRANSFORM_DURATION, Easing.OutQuint);
|
Circle.FadeColour(Colours.GrayF, TRANSFORM_DURATION, Easing.OutQuint);
|
||||||
else
|
else
|
||||||
circle.FadeColour(IsHovered ? Colours.Red : Colours.YellowDark, TRANSFORM_DURATION, Easing.OutQuint);
|
Circle.FadeColour(IsHovered ? Colours.Red : Colours.YellowDark, TRANSFORM_DURATION, Easing.OutQuint);
|
||||||
|
|
||||||
this.ScaleTo(IsHeld || IsHovered ? 1.5f : 1, TRANSFORM_DURATION, Easing.OutQuint);
|
this.ScaleTo(IsHeld || IsHovered ? 1.5f : 1, TRANSFORM_DURATION, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
@ -160,13 +160,23 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
if (e.Repeat)
|
if (e.Repeat)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
bool handled;
|
||||||
|
|
||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
case GlobalAction.EditorFlipHorizontally:
|
case GlobalAction.EditorFlipHorizontally:
|
||||||
return HandleFlip(Direction.Horizontal, true);
|
ChangeHandler?.BeginChange();
|
||||||
|
handled = HandleFlip(Direction.Horizontal, true);
|
||||||
|
ChangeHandler?.EndChange();
|
||||||
|
|
||||||
|
return handled;
|
||||||
|
|
||||||
case GlobalAction.EditorFlipVertically:
|
case GlobalAction.EditorFlipVertically:
|
||||||
return HandleFlip(Direction.Vertical, true);
|
ChangeHandler?.BeginChange();
|
||||||
|
handled = HandleFlip(Direction.Vertical, true);
|
||||||
|
ChangeHandler?.EndChange();
|
||||||
|
|
||||||
|
return handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
|||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
scroll.RelativeSizeAxes = Axes.X;
|
scroll.RelativeSizeAxes = Axes.X;
|
||||||
scroll.Height = ParticipantsList.TILE_SIZE + OsuScrollContainer.SCROLL_BAR_HEIGHT + OsuScrollContainer.SCROLL_BAR_PADDING * 2;
|
scroll.Height = ParticipantsList.TILE_SIZE + OsuScrollContainer.SCROLL_BAR_WIDTH + OsuScrollContainer.SCROLL_BAR_PADDING * 2;
|
||||||
|
|
||||||
list.RelativeSizeAxes = Axes.Y;
|
list.RelativeSizeAxes = Axes.Y;
|
||||||
list.AutoSizeAxes = Axes.X;
|
list.AutoSizeAxes = Axes.X;
|
||||||
|
@ -313,17 +313,27 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
|
|
||||||
client.ChangeBeatmapAvailability(availability.NewValue).FireAndForget();
|
client.ChangeBeatmapAvailability(availability.NewValue).FireAndForget();
|
||||||
|
|
||||||
if (availability.NewValue.State != DownloadState.LocallyAvailable)
|
switch (availability.NewValue.State)
|
||||||
{
|
{
|
||||||
// while this flow is handled server-side, this covers the edge case of the local user being in a ready state and then deleting the current beatmap.
|
case DownloadState.LocallyAvailable:
|
||||||
if (client.LocalUser?.State == MultiplayerUserState.Ready)
|
if (client.LocalUser?.State == MultiplayerUserState.Spectating
|
||||||
client.ChangeState(MultiplayerUserState.Idle);
|
|
||||||
}
|
|
||||||
else if (client.LocalUser?.State == MultiplayerUserState.Spectating
|
|
||||||
&& (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing))
|
&& (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing))
|
||||||
{
|
{
|
||||||
onLoadRequested();
|
onLoadRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DownloadState.Unknown:
|
||||||
|
// Don't do anything rash in an unknown state.
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// while this flow is handled server-side, this covers the edge case of the local user being in a ready state and then deleting the current beatmap.
|
||||||
|
if (client.LocalUser?.State == MultiplayerUserState.Ready)
|
||||||
|
client.ChangeState(MultiplayerUserState.Idle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onRoomUpdated()
|
private void onRoomUpdated()
|
||||||
|
@ -154,6 +154,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
this.FadeOut(fade_time);
|
this.FadeOut(fade_time);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case DownloadState.Unknown:
|
||||||
|
text.Text = "checking availability";
|
||||||
|
icon.Icon = FontAwesome.Solid.Question;
|
||||||
|
icon.Colour = colours.Orange0;
|
||||||
|
break;
|
||||||
|
|
||||||
case DownloadState.NotDownloaded:
|
case DownloadState.NotDownloaded:
|
||||||
text.Text = "no map";
|
text.Text = "no map";
|
||||||
icon.Icon = FontAwesome.Solid.MinusCircle;
|
icon.Icon = FontAwesome.Solid.MinusCircle;
|
||||||
|
@ -8,7 +8,7 @@ using osu.Game.Rulesets.UI;
|
|||||||
|
|
||||||
namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
|
namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
|
||||||
{
|
{
|
||||||
public partial class ClicksPerSecondCalculator : Component
|
public partial class ClicksPerSecondController : Component
|
||||||
{
|
{
|
||||||
private readonly List<double> timestamps = new List<double>();
|
private readonly List<double> timestamps = new List<double>();
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
|
|||||||
|
|
||||||
private IGameplayClock clock => frameStableClock ?? gameplayClock;
|
private IGameplayClock clock => frameStableClock ?? gameplayClock;
|
||||||
|
|
||||||
public ClicksPerSecondCalculator()
|
public ClicksPerSecondController()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
|
|||||||
public partial class ClicksPerSecondCounter : RollingCounter<int>, ISerialisableDrawable
|
public partial class ClicksPerSecondCounter : RollingCounter<int>, ISerialisableDrawable
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private ClicksPerSecondCalculator calculator { get; set; } = null!;
|
private ClicksPerSecondController controller { get; set; } = null!;
|
||||||
|
|
||||||
protected override double RollingDuration => 350;
|
protected override double RollingDuration => 350;
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
Current.Value = calculator.Value;
|
Current.Value = controller.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IHasText CreateText() => new TextComponent();
|
protected override IHasText CreateText() => new TextComponent();
|
||||||
|
33
osu.Game/Screens/Play/HUD/InputCountController.cs
Normal file
33
osu.Game/Screens/Play/HUD/InputCountController.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play.HUD
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Keeps track of key press counts for a current play session, exposing bindable counts which can
|
||||||
|
/// be used for display purposes.
|
||||||
|
/// </summary>
|
||||||
|
public partial class InputCountController : Component
|
||||||
|
{
|
||||||
|
public readonly Bindable<bool> IsCounting = new BindableBool(true);
|
||||||
|
|
||||||
|
private readonly BindableList<InputTrigger> triggers = new BindableList<InputTrigger>();
|
||||||
|
|
||||||
|
public IBindableList<InputTrigger> Triggers => triggers;
|
||||||
|
|
||||||
|
public void AddRange(IEnumerable<InputTrigger> triggers) => triggers.ForEach(Add);
|
||||||
|
|
||||||
|
public void Add(InputTrigger trigger)
|
||||||
|
{
|
||||||
|
// Note that these triggers are not added to the hierarchy here. It is presumed they are added externally at a
|
||||||
|
// more correct location (ie. inside a RulesetInputManager).
|
||||||
|
triggers.Add(trigger);
|
||||||
|
trigger.IsCounting.BindTo(IsCounting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
// 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.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play.HUD
|
namespace osu.Game.Screens.Play.HUD
|
||||||
@ -25,13 +26,38 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
public event OnActivateCallback? OnActivate;
|
public event OnActivateCallback? OnActivate;
|
||||||
public event OnDeactivateCallback? OnDeactivate;
|
public event OnDeactivateCallback? OnDeactivate;
|
||||||
|
|
||||||
|
private readonly Bindable<int> activationCount = new BindableInt();
|
||||||
|
private readonly Bindable<bool> isCounting = new BindableBool(true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of times this <see cref="InputTrigger"/> has been activated.
|
||||||
|
/// </summary>
|
||||||
|
public IBindable<int> ActivationCount => activationCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether any activation or deactivation of this <see cref="InputTrigger"/> impacts its <see cref="ActivationCount"/>
|
||||||
|
/// </summary>
|
||||||
|
public IBindable<bool> IsCounting => isCounting;
|
||||||
|
|
||||||
protected InputTrigger(string name)
|
protected InputTrigger(string name)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void Activate(bool forwardPlayback = true) => OnActivate?.Invoke(forwardPlayback);
|
protected void Activate(bool forwardPlayback = true)
|
||||||
|
{
|
||||||
|
if (forwardPlayback && isCounting.Value)
|
||||||
|
activationCount.Value++;
|
||||||
|
|
||||||
protected void Deactivate(bool forwardPlayback = true) => OnDeactivate?.Invoke(forwardPlayback);
|
OnActivate?.Invoke(forwardPlayback);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Deactivate(bool forwardPlayback = true)
|
||||||
|
{
|
||||||
|
if (!forwardPlayback && isCounting.Value)
|
||||||
|
activationCount.Value--;
|
||||||
|
|
||||||
|
OnDeactivate?.Invoke(forwardPlayback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -16,7 +16,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
|||||||
/// Keeps track of judgements for a current play session, exposing bindable counts which can
|
/// Keeps track of judgements for a current play session, exposing bindable counts which can
|
||||||
/// be used for display purposes.
|
/// be used for display purposes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class JudgementTally : CompositeDrawable
|
public partial class JudgementCountController : Component
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private ScoreProcessor scoreProcessor { get; set; } = null!;
|
private ScoreProcessor scoreProcessor { get; set; } = null!;
|
@ -18,9 +18,9 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
|||||||
public BindableBool ShowName = new BindableBool();
|
public BindableBool ShowName = new BindableBool();
|
||||||
public Bindable<FillDirection> Direction = new Bindable<FillDirection>();
|
public Bindable<FillDirection> Direction = new Bindable<FillDirection>();
|
||||||
|
|
||||||
public readonly JudgementTally.JudgementCount Result;
|
public readonly JudgementCountController.JudgementCount Result;
|
||||||
|
|
||||||
public JudgementCounter(JudgementTally.JudgementCount result) => Result = result;
|
public JudgementCounter(JudgementCountController.JudgementCount result) => Result = result;
|
||||||
|
|
||||||
public OsuSpriteText ResultName = null!;
|
public OsuSpriteText ResultName = null!;
|
||||||
private FillFlowContainer flowContainer = null!;
|
private FillFlowContainer flowContainer = null!;
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
|||||||
public BindableBool ShowMaxJudgement { get; set; } = new BindableBool(true);
|
public BindableBool ShowMaxJudgement { get; set; } = new BindableBool(true);
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private JudgementTally tally { get; set; } = null!;
|
private JudgementCountController judgementCountController { get; set; } = null!;
|
||||||
|
|
||||||
protected FillFlowContainer<JudgementCounter> CounterFlow = null!;
|
protected FillFlowContainer<JudgementCounter> CounterFlow = null!;
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
|||||||
AutoSizeAxes = Axes.Both
|
AutoSizeAxes = Axes.Both
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var result in tally.Results)
|
foreach (var result in judgementCountController.Results)
|
||||||
CounterFlow.Add(createCounter(result));
|
CounterFlow.Add(createCounter(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private JudgementCounter createCounter(JudgementTally.JudgementCount info) =>
|
private JudgementCounter createCounter(JudgementCountController.JudgementCount info) =>
|
||||||
new JudgementCounter(info)
|
new JudgementCounter(info)
|
||||||
{
|
{
|
||||||
State = { Value = Visibility.Hidden },
|
State = { Value = Visibility.Hidden },
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// 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.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play.HUD
|
namespace osu.Game.Screens.Play.HUD
|
||||||
@ -17,24 +16,10 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly InputTrigger Trigger;
|
public readonly InputTrigger Trigger;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the actions reported by <see cref="Trigger"/> should be counted.
|
|
||||||
/// </summary>
|
|
||||||
public Bindable<bool> IsCounting { get; } = new BindableBool(true);
|
|
||||||
|
|
||||||
private readonly Bindable<int> countPresses = new BindableInt
|
|
||||||
{
|
|
||||||
MinValue = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current count of registered key presses.
|
/// The current count of registered key presses.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IBindable<int> CountPresses => countPresses;
|
public IBindable<int> CountPresses => Trigger.ActivationCount;
|
||||||
|
|
||||||
private readonly Container content;
|
|
||||||
|
|
||||||
protected override Container<Drawable> Content => content;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this <see cref="KeyCounter"/> is currently in the "activated" state because the associated key is currently pressed.
|
/// Whether this <see cref="KeyCounter"/> is currently in the "activated" state because the associated key is currently pressed.
|
||||||
@ -43,52 +28,26 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
protected KeyCounter(InputTrigger trigger)
|
protected KeyCounter(InputTrigger trigger)
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
Trigger = trigger;
|
||||||
{
|
|
||||||
content = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both
|
|
||||||
},
|
|
||||||
Trigger = trigger,
|
|
||||||
};
|
|
||||||
|
|
||||||
Trigger.OnActivate += Activate;
|
Trigger.OnActivate += Activate;
|
||||||
Trigger.OnDeactivate += Deactivate;
|
Trigger.OnDeactivate += Deactivate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void increment()
|
|
||||||
{
|
|
||||||
if (!IsCounting.Value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
countPresses.Value++;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void decrement()
|
|
||||||
{
|
|
||||||
if (!IsCounting.Value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
countPresses.Value--;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Activate(bool forwardPlayback = true)
|
protected virtual void Activate(bool forwardPlayback = true)
|
||||||
{
|
{
|
||||||
IsActive.Value = true;
|
IsActive.Value = true;
|
||||||
if (forwardPlayback)
|
|
||||||
increment();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Deactivate(bool forwardPlayback = true)
|
protected virtual void Deactivate(bool forwardPlayback = true)
|
||||||
{
|
{
|
||||||
IsActive.Value = false;
|
IsActive.Value = false;
|
||||||
if (!forwardPlayback)
|
|
||||||
decrement();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
Trigger.OnActivate -= Activate;
|
Trigger.OnActivate -= Activate;
|
||||||
Trigger.OnDeactivate -= Deactivate;
|
Trigger.OnDeactivate -= Deactivate;
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play.HUD
|
namespace osu.Game.Screens.Play.HUD
|
||||||
{
|
{
|
||||||
public partial class KeyCounterActionTrigger<T> : InputTrigger
|
public partial class KeyCounterActionTrigger<T> : InputTrigger, IKeyBindingHandler<T>
|
||||||
where T : struct
|
where T : struct
|
||||||
{
|
{
|
||||||
public T Action { get; }
|
public T Action { get; }
|
||||||
@ -16,21 +18,21 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
Action = action;
|
Action = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnPressed(T action, bool forwards)
|
public bool OnPressed(KeyBindingPressEvent<T> e)
|
||||||
{
|
{
|
||||||
if (!EqualityComparer<T>.Default.Equals(action, Action))
|
if (!EqualityComparer<T>.Default.Equals(e.Action, Action))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Activate(forwards);
|
Activate(Clock.Rate >= 0);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnReleased(T action, bool forwards)
|
public void OnReleased(KeyBindingReleaseEvent<T> e)
|
||||||
{
|
{
|
||||||
if (!EqualityComparer<T>.Default.Equals(action, Action))
|
if (!EqualityComparer<T>.Default.Equals(e.Action, Action))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Deactivate(forwards);
|
Deactivate(Clock.Rate >= 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,20 @@
|
|||||||
// 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 System;
|
using System.Collections.Specialized;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osuTK;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play.HUD
|
namespace osu.Game.Screens.Play.HUD
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A flowing display of all gameplay keys. Individual keys can be added using <see cref="InputTrigger"/> implementations.
|
/// A flowing display of all gameplay keys. Individual keys can be added using <see cref="InputTrigger"/> implementations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract partial class KeyCounterDisplay : CompositeDrawable
|
public abstract partial class KeyCounterDisplay : CompositeDrawable, ISerialisableDrawable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the key counter should be visible regardless of the configuration value.
|
/// Whether the key counter should be visible regardless of the configuration value.
|
||||||
@ -26,95 +22,46 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Bindable<bool> AlwaysVisible { get; } = new Bindable<bool>(true);
|
public Bindable<bool> AlwaysVisible { get; } = new Bindable<bool>(true);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="KeyCounter"/>s contained in this <see cref="KeyCounterDisplay"/>.
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<KeyCounter> Counters => KeyFlow;
|
|
||||||
|
|
||||||
protected abstract FillFlowContainer<KeyCounter> KeyFlow { get; }
|
protected abstract FillFlowContainer<KeyCounter> KeyFlow { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the actions reported by all <see cref="InputTrigger"/>s within this <see cref="KeyCounterDisplay"/> should be counted.
|
|
||||||
/// </summary>
|
|
||||||
public Bindable<bool> IsCounting { get; } = new BindableBool(true);
|
|
||||||
|
|
||||||
protected readonly Bindable<bool> ConfigVisibility = new Bindable<bool>();
|
protected readonly Bindable<bool> ConfigVisibility = new Bindable<bool>();
|
||||||
|
|
||||||
|
private readonly IBindableList<InputTrigger> triggers = new BindableList<InputTrigger>();
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private InputCountController controller { get; set; } = null!;
|
||||||
|
|
||||||
protected abstract void UpdateVisibility();
|
protected abstract void UpdateVisibility();
|
||||||
|
|
||||||
private Receptor? receptor;
|
|
||||||
|
|
||||||
public void SetReceptor(Receptor receptor)
|
|
||||||
{
|
|
||||||
if (this.receptor != null)
|
|
||||||
throw new InvalidOperationException("Cannot set a new receptor when one is already active");
|
|
||||||
|
|
||||||
this.receptor = receptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a <see cref="InputTrigger"/> to this display.
|
|
||||||
/// </summary>
|
|
||||||
public void Add(InputTrigger trigger)
|
|
||||||
{
|
|
||||||
var keyCounter = CreateCounter(trigger);
|
|
||||||
|
|
||||||
KeyFlow.Add(keyCounter);
|
|
||||||
|
|
||||||
IsCounting.BindTo(keyCounter.IsCounting);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a range of <see cref="InputTrigger"/> to this display.
|
|
||||||
/// </summary>
|
|
||||||
public void AddRange(IEnumerable<InputTrigger> triggers) => triggers.ForEach(Add);
|
|
||||||
|
|
||||||
protected abstract KeyCounter CreateCounter(InputTrigger trigger);
|
protected abstract KeyCounter CreateCounter(InputTrigger trigger);
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config, DrawableRuleset? drawableRuleset)
|
||||||
{
|
{
|
||||||
config.BindWith(OsuSetting.KeyOverlay, ConfigVisibility);
|
config.BindWith(OsuSetting.KeyOverlay, ConfigVisibility);
|
||||||
|
|
||||||
|
if (drawableRuleset != null)
|
||||||
|
AlwaysVisible.BindTo(drawableRuleset.HasReplayLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
|
triggers.BindTo(controller.Triggers);
|
||||||
|
triggers.BindCollectionChanged(triggersChanged, true);
|
||||||
|
|
||||||
AlwaysVisible.BindValueChanged(_ => UpdateVisibility());
|
AlwaysVisible.BindValueChanged(_ => UpdateVisibility());
|
||||||
ConfigVisibility.BindValueChanged(_ => UpdateVisibility(), true);
|
ConfigVisibility.BindValueChanged(_ => UpdateVisibility(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool HandleNonPositionalInput => receptor == null;
|
private void triggersChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
|
||||||
public override bool HandlePositionalInput => receptor == null;
|
|
||||||
|
|
||||||
public partial class Receptor : Drawable
|
|
||||||
{
|
{
|
||||||
protected readonly KeyCounterDisplay Target;
|
KeyFlow.Clear();
|
||||||
|
foreach (var trigger in controller.Triggers)
|
||||||
public Receptor(KeyCounterDisplay target)
|
KeyFlow.Add(CreateCounter(trigger));
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
Depth = float.MinValue;
|
|
||||||
Target = target;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
public bool UsesFixedAnchor { get; set; }
|
||||||
|
|
||||||
protected override bool Handle(UIEvent e)
|
|
||||||
{
|
|
||||||
switch (e)
|
|
||||||
{
|
|
||||||
case KeyDownEvent:
|
|
||||||
case KeyUpEvent:
|
|
||||||
case MouseDownEvent:
|
|
||||||
case MouseUpEvent:
|
|
||||||
return Target.InternalChildren.Any(c => c.TriggerEvent(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.Handle(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,10 @@ using osu.Framework.Input.Bindings;
|
|||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -26,8 +28,6 @@ using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
|||||||
using osu.Game.Screens.Play.HUD.JudgementCounter;
|
using osu.Game.Screens.Play.HUD.JudgementCounter;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Game.Localisation;
|
|
||||||
using osu.Game.Rulesets;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
{
|
{
|
||||||
@ -54,16 +54,18 @@ namespace osu.Game.Screens.Play
|
|||||||
return child == bottomRightElements;
|
return child == bottomRightElements;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly KeyCounterDisplay KeyCounter;
|
|
||||||
public readonly ModDisplay ModDisplay;
|
public readonly ModDisplay ModDisplay;
|
||||||
public readonly HoldForMenuButton HoldToQuit;
|
public readonly HoldForMenuButton HoldToQuit;
|
||||||
public readonly PlayerSettingsOverlay PlayerSettingsOverlay;
|
public readonly PlayerSettingsOverlay PlayerSettingsOverlay;
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly ClicksPerSecondCalculator clicksPerSecondCalculator;
|
private readonly ClicksPerSecondController clicksPerSecondController;
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly JudgementTally tally;
|
public readonly InputCountController InputCountController;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly JudgementCountController judgementCountController;
|
||||||
|
|
||||||
public Bindable<bool> ShowHealthBar = new Bindable<bool>(true);
|
public Bindable<bool> ShowHealthBar = new Bindable<bool>(true);
|
||||||
|
|
||||||
@ -113,7 +115,9 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
CreateFailingLayer(),
|
CreateFailingLayer(),
|
||||||
//Needs to be initialized before skinnable drawables.
|
//Needs to be initialized before skinnable drawables.
|
||||||
tally = new JudgementTally(),
|
judgementCountController = new JudgementCountController(),
|
||||||
|
clicksPerSecondController = new ClicksPerSecondController(),
|
||||||
|
InputCountController = new InputCountController(),
|
||||||
mainComponents = new HUDComponentsContainer { AlwaysPresent = true, },
|
mainComponents = new HUDComponentsContainer { AlwaysPresent = true, },
|
||||||
rulesetComponents = drawableRuleset != null
|
rulesetComponents = drawableRuleset != null
|
||||||
? new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, }
|
? new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, }
|
||||||
@ -145,7 +149,6 @@ namespace osu.Game.Screens.Play
|
|||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
KeyCounter = CreateKeyCounter(),
|
|
||||||
HoldToQuit = CreateHoldForMenuButton(),
|
HoldToQuit = CreateHoldForMenuButton(),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -156,10 +159,9 @@ namespace osu.Game.Screens.Play
|
|||||||
Padding = new MarginPadding(44), // enough margin to avoid the hit error display
|
Padding = new MarginPadding(44), // enough margin to avoid the hit error display
|
||||||
Spacing = new Vector2(5)
|
Spacing = new Vector2(5)
|
||||||
},
|
},
|
||||||
clicksPerSecondCalculator = new ClicksPerSecondCalculator(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
hideTargets = new List<Drawable> { mainComponents, rulesetComponents, KeyCounter, topRightElements };
|
hideTargets = new List<Drawable> { mainComponents, rulesetComponents, topRightElements };
|
||||||
|
|
||||||
if (!alwaysShowLeaderboard)
|
if (!alwaysShowLeaderboard)
|
||||||
hideTargets.Add(LeaderboardFlow);
|
hideTargets.Add(LeaderboardFlow);
|
||||||
@ -303,13 +305,13 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
PlayerSettingsOverlay.Show();
|
PlayerSettingsOverlay.Show();
|
||||||
ModDisplay.FadeIn(200);
|
ModDisplay.FadeIn(200);
|
||||||
KeyCounter.Margin = new MarginPadding(10) { Bottom = 30 };
|
InputCountController.Margin = new MarginPadding(10) { Bottom = 30 };
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PlayerSettingsOverlay.Hide();
|
PlayerSettingsOverlay.Hide();
|
||||||
ModDisplay.Delay(2000).FadeOut(200);
|
ModDisplay.Delay(2000).FadeOut(200);
|
||||||
KeyCounter.Margin = new MarginPadding(10);
|
InputCountController.Margin = new MarginPadding(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateVisibility();
|
updateVisibility();
|
||||||
@ -319,8 +321,8 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
if (drawableRuleset is ICanAttachHUDPieces attachTarget)
|
if (drawableRuleset is ICanAttachHUDPieces attachTarget)
|
||||||
{
|
{
|
||||||
attachTarget.Attach(KeyCounter);
|
attachTarget.Attach(InputCountController);
|
||||||
attachTarget.Attach(clicksPerSecondCalculator);
|
attachTarget.Attach(clicksPerSecondController);
|
||||||
}
|
}
|
||||||
|
|
||||||
replayLoaded.BindTo(drawableRuleset.HasReplayLoaded);
|
replayLoaded.BindTo(drawableRuleset.HasReplayLoaded);
|
||||||
@ -331,12 +333,6 @@ namespace osu.Game.Screens.Play
|
|||||||
ShowHealth = { BindTarget = ShowHealthBar }
|
ShowHealth = { BindTarget = ShowHealthBar }
|
||||||
};
|
};
|
||||||
|
|
||||||
protected KeyCounterDisplay CreateKeyCounter() => new DefaultKeyCounterDisplay
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
};
|
|
||||||
|
|
||||||
protected HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton
|
protected HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomRight,
|
Anchor = Anchor.BottomRight,
|
||||||
|
@ -378,9 +378,6 @@ namespace osu.Game.Screens.Play
|
|||||||
IsBreakTime.BindTo(breakTracker.IsBreakTime);
|
IsBreakTime.BindTo(breakTracker.IsBreakTime);
|
||||||
IsBreakTime.BindValueChanged(onBreakTimeChanged, true);
|
IsBreakTime.BindValueChanged(onBreakTimeChanged, true);
|
||||||
|
|
||||||
if (Configuration.AutomaticallySkipIntro)
|
|
||||||
skipIntroOverlay.SkipWhenReady();
|
|
||||||
|
|
||||||
loadLeaderboard();
|
loadLeaderboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -432,13 +429,12 @@ namespace osu.Game.Screens.Play
|
|||||||
IsPaused = { BindTarget = GameplayClockContainer.IsPaused },
|
IsPaused = { BindTarget = GameplayClockContainer.IsPaused },
|
||||||
ReplayLoaded = { BindTarget = DrawableRuleset.HasReplayLoaded },
|
ReplayLoaded = { BindTarget = DrawableRuleset.HasReplayLoaded },
|
||||||
},
|
},
|
||||||
KeyCounter =
|
InputCountController =
|
||||||
{
|
{
|
||||||
IsCounting =
|
IsCounting =
|
||||||
{
|
{
|
||||||
Value = false
|
Value = false
|
||||||
},
|
},
|
||||||
AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded },
|
|
||||||
},
|
},
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre
|
Origin = Anchor.Centre
|
||||||
@ -478,7 +474,7 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
updateGameplayState();
|
updateGameplayState();
|
||||||
updatePauseOnFocusLostState();
|
updatePauseOnFocusLostState();
|
||||||
HUDOverlay.KeyCounter.IsCounting.Value = !isBreakTime.NewValue;
|
HUDOverlay.InputCountController.IsCounting.Value = !isBreakTime.NewValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateGameplayState()
|
private void updateGameplayState()
|
||||||
@ -1087,6 +1083,9 @@ namespace osu.Game.Screens.Play
|
|||||||
throw new InvalidOperationException($"{nameof(StartGameplay)} should not be called when the gameplay clock is already running");
|
throw new InvalidOperationException($"{nameof(StartGameplay)} should not be called when the gameplay clock is already running");
|
||||||
|
|
||||||
GameplayClockContainer.Reset(startClock: true);
|
GameplayClockContainer.Reset(startClock: true);
|
||||||
|
|
||||||
|
if (Configuration.AutomaticallySkipIntro)
|
||||||
|
skipIntroOverlay.SkipWhenReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnSuspending(ScreenTransitionEvent e)
|
public override void OnSuspending(ScreenTransitionEvent e)
|
||||||
|
@ -1,7 +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 System;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Screens.Select.Filter;
|
using osu.Game.Screens.Select.Filter;
|
||||||
@ -58,6 +57,8 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
match &= !criteria.Creator.HasFilter || criteria.Creator.Matches(BeatmapInfo.Metadata.Author.Username);
|
match &= !criteria.Creator.HasFilter || criteria.Creator.Matches(BeatmapInfo.Metadata.Author.Username);
|
||||||
match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(BeatmapInfo.Metadata.Artist) ||
|
match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(BeatmapInfo.Metadata.Artist) ||
|
||||||
criteria.Artist.Matches(BeatmapInfo.Metadata.ArtistUnicode);
|
criteria.Artist.Matches(BeatmapInfo.Metadata.ArtistUnicode);
|
||||||
|
match &= !criteria.Title.HasFilter || criteria.Title.Matches(BeatmapInfo.Metadata.Title) ||
|
||||||
|
criteria.Title.Matches(BeatmapInfo.Metadata.TitleUnicode);
|
||||||
|
|
||||||
match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarRating);
|
match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarRating);
|
||||||
|
|
||||||
@ -65,16 +66,16 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
|
|
||||||
if (criteria.SearchTerms.Length > 0)
|
if (criteria.SearchTerms.Length > 0)
|
||||||
{
|
{
|
||||||
var terms = BeatmapInfo.GetSearchableTerms();
|
var searchableTerms = BeatmapInfo.GetSearchableTerms();
|
||||||
|
|
||||||
foreach (string criteriaTerm in criteria.SearchTerms)
|
foreach (FilterCriteria.OptionalTextFilter criteriaTerm in criteria.SearchTerms)
|
||||||
{
|
{
|
||||||
bool any = false;
|
bool any = false;
|
||||||
|
|
||||||
// ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
|
// ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
|
||||||
foreach (string term in terms)
|
foreach (string searchTerm in searchableTerms)
|
||||||
{
|
{
|
||||||
if (!term.Contains(criteriaTerm, StringComparison.InvariantCultureIgnoreCase)) continue;
|
if (!criteriaTerm.Matches(searchTerm)) continue;
|
||||||
|
|
||||||
any = true;
|
any = true;
|
||||||
break;
|
break;
|
||||||
@ -98,7 +99,6 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
if (!match) return false;
|
if (!match) return false;
|
||||||
|
|
||||||
match &= criteria.CollectionBeatmapMD5Hashes?.Contains(BeatmapInfo.MD5Hash) ?? true;
|
match &= criteria.CollectionBeatmapMD5Hashes?.Contains(BeatmapInfo.MD5Hash) ?? true;
|
||||||
|
|
||||||
if (match && criteria.RulesetCriteria != null)
|
if (match && criteria.RulesetCriteria != null)
|
||||||
match &= criteria.RulesetCriteria.Matches(BeatmapInfo);
|
match &= criteria.RulesetCriteria.Matches(BeatmapInfo);
|
||||||
|
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
using System.Text.RegularExpressions;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Collections;
|
using osu.Game.Collections;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -20,7 +19,7 @@ namespace osu.Game.Screens.Select
|
|||||||
public GroupMode Group;
|
public GroupMode Group;
|
||||||
public SortMode Sort;
|
public SortMode Sort;
|
||||||
|
|
||||||
public BeatmapSetInfo SelectedBeatmapSet;
|
public BeatmapSetInfo? SelectedBeatmapSet;
|
||||||
|
|
||||||
public OptionalRange<double> StarDifficulty;
|
public OptionalRange<double> StarDifficulty;
|
||||||
public OptionalRange<float> ApproachRate;
|
public OptionalRange<float> ApproachRate;
|
||||||
@ -33,6 +32,7 @@ namespace osu.Game.Screens.Select
|
|||||||
public OptionalRange<BeatmapOnlineStatus> OnlineStatus;
|
public OptionalRange<BeatmapOnlineStatus> OnlineStatus;
|
||||||
public OptionalTextFilter Creator;
|
public OptionalTextFilter Creator;
|
||||||
public OptionalTextFilter Artist;
|
public OptionalTextFilter Artist;
|
||||||
|
public OptionalTextFilter Title;
|
||||||
|
|
||||||
public OptionalRange<double> UserStarDifficulty = new OptionalRange<double>
|
public OptionalRange<double> UserStarDifficulty = new OptionalRange<double>
|
||||||
{
|
{
|
||||||
@ -40,12 +40,12 @@ namespace osu.Game.Screens.Select
|
|||||||
IsUpperInclusive = true
|
IsUpperInclusive = true
|
||||||
};
|
};
|
||||||
|
|
||||||
public string[] SearchTerms = Array.Empty<string>();
|
public OptionalTextFilter[] SearchTerms = Array.Empty<OptionalTextFilter>();
|
||||||
|
|
||||||
public RulesetInfo Ruleset;
|
public RulesetInfo? Ruleset;
|
||||||
public bool AllowConvertedBeatmaps;
|
public bool AllowConvertedBeatmaps;
|
||||||
|
|
||||||
private string searchText;
|
private string searchText = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="SearchText"/> as a number (if it can be parsed as one).
|
/// <see cref="SearchText"/> as a number (if it can be parsed as one).
|
||||||
@ -58,11 +58,29 @@ namespace osu.Game.Screens.Select
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
searchText = value;
|
searchText = value;
|
||||||
SearchTerms = searchText.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToArray();
|
|
||||||
|
List<OptionalTextFilter> terms = new List<OptionalTextFilter>();
|
||||||
|
|
||||||
|
string remainingText = value;
|
||||||
|
|
||||||
|
// First handle quoted segments to ensure we keep inline spaces in exact matches.
|
||||||
|
foreach (Match quotedSegment in Regex.Matches(searchText, "(\"[^\"]+\"[!]?)"))
|
||||||
|
{
|
||||||
|
terms.Add(new OptionalTextFilter { SearchTerm = quotedSegment.Value });
|
||||||
|
remainingText = remainingText.Replace(quotedSegment.Value, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then handle the rest splitting on any spaces.
|
||||||
|
terms.AddRange(remainingText.Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(s => new OptionalTextFilter
|
||||||
|
{
|
||||||
|
SearchTerm = s
|
||||||
|
}));
|
||||||
|
|
||||||
|
SearchTerms = terms.ToArray();
|
||||||
|
|
||||||
SearchNumber = null;
|
SearchNumber = null;
|
||||||
|
|
||||||
if (SearchTerms.Length == 1 && int.TryParse(SearchTerms[0], out int parsed))
|
if (SearchTerms.Length == 1 && int.TryParse(SearchTerms[0].SearchTerm, out int parsed))
|
||||||
SearchNumber = parsed;
|
SearchNumber = parsed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,11 +88,9 @@ namespace osu.Game.Screens.Select
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hashes from the <see cref="BeatmapCollection"/> to filter to.
|
/// Hashes from the <see cref="BeatmapCollection"/> to filter to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[CanBeNull]
|
public IEnumerable<string>? CollectionBeatmapMD5Hashes { get; set; }
|
||||||
public IEnumerable<string> CollectionBeatmapMD5Hashes { get; set; }
|
|
||||||
|
|
||||||
[CanBeNull]
|
public IRulesetFilterCriteria? RulesetCriteria { get; set; }
|
||||||
public IRulesetFilterCriteria RulesetCriteria { get; set; }
|
|
||||||
|
|
||||||
public struct OptionalRange<T> : IEquatable<OptionalRange<T>>
|
public struct OptionalRange<T> : IEquatable<OptionalRange<T>>
|
||||||
where T : struct
|
where T : struct
|
||||||
@ -124,6 +140,8 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
public bool HasFilter => !string.IsNullOrEmpty(SearchTerm);
|
public bool HasFilter => !string.IsNullOrEmpty(SearchTerm);
|
||||||
|
|
||||||
|
public MatchMode MatchMode { get; private set; }
|
||||||
|
|
||||||
public bool Matches(string value)
|
public bool Matches(string value)
|
||||||
{
|
{
|
||||||
if (!HasFilter)
|
if (!HasFilter)
|
||||||
@ -133,12 +151,67 @@ namespace osu.Game.Screens.Select
|
|||||||
if (string.IsNullOrEmpty(value))
|
if (string.IsNullOrEmpty(value))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
switch (MatchMode)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case MatchMode.Substring:
|
||||||
return value.Contains(SearchTerm, StringComparison.InvariantCultureIgnoreCase);
|
return value.Contains(SearchTerm, StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
|
||||||
|
case MatchMode.IsolatedPhrase:
|
||||||
|
return Regex.IsMatch(value, $@"(^|\s){Regex.Escape(searchTerm)}($|\s)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||||
|
|
||||||
|
case MatchMode.FullPhrase:
|
||||||
|
return CultureInfo.InvariantCulture.CompareInfo.Compare(value, searchTerm, CompareOptions.IgnoreCase) == 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SearchTerm;
|
private string searchTerm;
|
||||||
|
|
||||||
|
public string SearchTerm
|
||||||
|
{
|
||||||
|
get => searchTerm;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
searchTerm = value;
|
||||||
|
|
||||||
|
if (searchTerm.StartsWith('\"'))
|
||||||
|
{
|
||||||
|
// length check ensures that the quote character in the `StartsWith()` check above and the `EndsWith()` check below is not the same character.
|
||||||
|
if (searchTerm.EndsWith("\"!", StringComparison.Ordinal) && searchTerm.Length >= 3)
|
||||||
|
{
|
||||||
|
searchTerm = searchTerm.TrimEnd('!').Trim('\"');
|
||||||
|
MatchMode = MatchMode.FullPhrase;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
searchTerm = searchTerm.Trim('\"');
|
||||||
|
MatchMode = MatchMode.IsolatedPhrase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
MatchMode = MatchMode.Substring;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool Equals(OptionalTextFilter other) => SearchTerm == other.SearchTerm;
|
public bool Equals(OptionalTextFilter other) => SearchTerm == other.SearchTerm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum MatchMode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Match using a simple "contains" substring match.
|
||||||
|
/// </summary>
|
||||||
|
Substring,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Match for the search phrase being isolated by spaces, or at the start or end of the text.
|
||||||
|
/// </summary>
|
||||||
|
IsolatedPhrase,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Match for the search phrase matching the full text in completion.
|
||||||
|
/// </summary>
|
||||||
|
FullPhrase,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Screens.Select
|
|||||||
public static class FilterQueryParser
|
public static class FilterQueryParser
|
||||||
{
|
{
|
||||||
private static readonly Regex query_syntax_regex = new Regex(
|
private static readonly Regex query_syntax_regex = new Regex(
|
||||||
@"\b(?<key>\w+)(?<op>(:|=|(>|<)(:|=)?))(?<value>("".*"")|(\S*))",
|
@"\b(?<key>\w+)(?<op>(:|=|(>|<)(:|=)?))(?<value>("".*""[!]?)|(\S*))",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
internal static void ApplyQueries(FilterCriteria criteria, string query)
|
internal static void ApplyQueries(FilterCriteria criteria, string query)
|
||||||
@ -73,6 +73,9 @@ namespace osu.Game.Screens.Select
|
|||||||
case "artist":
|
case "artist":
|
||||||
return TryUpdateCriteriaText(ref criteria.Artist, op, value);
|
return TryUpdateCriteriaText(ref criteria.Artist, op, value);
|
||||||
|
|
||||||
|
case "title":
|
||||||
|
return TryUpdateCriteriaText(ref criteria.Title, op, value);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return criteria.RulesetCriteria?.TryParseCustomKeywordCriteria(key, op, value) ?? false;
|
return criteria.RulesetCriteria?.TryParseCustomKeywordCriteria(key, op, value) ?? false;
|
||||||
}
|
}
|
||||||
@ -161,7 +164,7 @@ namespace osu.Game.Screens.Select
|
|||||||
switch (op)
|
switch (op)
|
||||||
{
|
{
|
||||||
case Operator.Equal:
|
case Operator.Equal:
|
||||||
textFilter.SearchTerm = value.Trim('"');
|
textFilter.SearchTerm = value;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -12,6 +12,7 @@ using osu.Game.Audio;
|
|||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -113,6 +114,7 @@ namespace osu.Game.Skinning
|
|||||||
var combo = container.OfType<DefaultComboCounter>().FirstOrDefault();
|
var combo = container.OfType<DefaultComboCounter>().FirstOrDefault();
|
||||||
var ppCounter = container.OfType<PerformancePointsCounter>().FirstOrDefault();
|
var ppCounter = container.OfType<PerformancePointsCounter>().FirstOrDefault();
|
||||||
var songProgress = container.OfType<ArgonSongProgress>().FirstOrDefault();
|
var songProgress = container.OfType<ArgonSongProgress>().FirstOrDefault();
|
||||||
|
var keyCounter = container.OfType<ArgonKeyCounterDisplay>().FirstOrDefault();
|
||||||
|
|
||||||
if (score != null)
|
if (score != null)
|
||||||
{
|
{
|
||||||
@ -166,8 +168,20 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
if (songProgress != null)
|
if (songProgress != null)
|
||||||
{
|
{
|
||||||
songProgress.Position = new Vector2(0, -10);
|
const float padding = 10;
|
||||||
|
|
||||||
|
songProgress.Position = new Vector2(0, -padding);
|
||||||
songProgress.Scale = new Vector2(0.9f, 1);
|
songProgress.Scale = new Vector2(0.9f, 1);
|
||||||
|
|
||||||
|
if (keyCounter != null && hitError != null)
|
||||||
|
{
|
||||||
|
// Hard to find this at runtime, so taken from the most expanded state during replay.
|
||||||
|
const float song_progress_offset_height = 36 + padding;
|
||||||
|
|
||||||
|
keyCounter.Anchor = Anchor.BottomRight;
|
||||||
|
keyCounter.Origin = Anchor.BottomRight;
|
||||||
|
keyCounter.Position = new Vector2(-(hitError.Width + padding), -(padding * 2 + song_progress_offset_height));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -179,6 +193,7 @@ namespace osu.Game.Skinning
|
|||||||
new DefaultAccuracyCounter(),
|
new DefaultAccuracyCounter(),
|
||||||
new DefaultHealthDisplay(),
|
new DefaultHealthDisplay(),
|
||||||
new ArgonSongProgress(),
|
new ArgonSongProgress(),
|
||||||
|
new ArgonKeyCounterDisplay(),
|
||||||
new BarHitErrorMeter(),
|
new BarHitErrorMeter(),
|
||||||
new BarHitErrorMeter(),
|
new BarHitErrorMeter(),
|
||||||
new PerformancePointsCounter()
|
new PerformancePointsCounter()
|
||||||
|
@ -22,6 +22,7 @@ using osu.Game.Rulesets.Objects.Types;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
@ -372,12 +373,22 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
|
|
||||||
var hitError = container.OfType<HitErrorMeter>().FirstOrDefault();
|
var hitError = container.OfType<HitErrorMeter>().FirstOrDefault();
|
||||||
|
var keyCounter = container.OfType<DefaultKeyCounterDisplay>().FirstOrDefault();
|
||||||
|
|
||||||
if (hitError != null)
|
if (hitError != null)
|
||||||
{
|
{
|
||||||
hitError.Anchor = Anchor.BottomCentre;
|
hitError.Anchor = Anchor.BottomCentre;
|
||||||
hitError.Origin = Anchor.CentreLeft;
|
hitError.Origin = Anchor.CentreLeft;
|
||||||
hitError.Rotation = -90;
|
hitError.Rotation = -90;
|
||||||
|
|
||||||
|
if (keyCounter != null)
|
||||||
|
{
|
||||||
|
const float padding = 10;
|
||||||
|
|
||||||
|
keyCounter.Anchor = Anchor.BottomRight;
|
||||||
|
keyCounter.Origin = Anchor.BottomRight;
|
||||||
|
keyCounter.Position = new Vector2(-padding, -(padding + hitError.Width));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
@ -389,6 +400,7 @@ namespace osu.Game.Skinning
|
|||||||
new LegacyHealthDisplay(),
|
new LegacyHealthDisplay(),
|
||||||
new LegacySongProgress(),
|
new LegacySongProgress(),
|
||||||
new BarHitErrorMeter(),
|
new BarHitErrorMeter(),
|
||||||
|
new DefaultKeyCounterDisplay()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -90,6 +90,8 @@ namespace osu.Game.Skinning
|
|||||||
var accuracy = container.OfType<DefaultAccuracyCounter>().FirstOrDefault();
|
var accuracy = container.OfType<DefaultAccuracyCounter>().FirstOrDefault();
|
||||||
var combo = container.OfType<DefaultComboCounter>().FirstOrDefault();
|
var combo = container.OfType<DefaultComboCounter>().FirstOrDefault();
|
||||||
var ppCounter = container.OfType<PerformancePointsCounter>().FirstOrDefault();
|
var ppCounter = container.OfType<PerformancePointsCounter>().FirstOrDefault();
|
||||||
|
var songProgress = container.OfType<DefaultSongProgress>().FirstOrDefault();
|
||||||
|
var keyCounter = container.OfType<DefaultKeyCounterDisplay>().FirstOrDefault();
|
||||||
|
|
||||||
if (score != null)
|
if (score != null)
|
||||||
{
|
{
|
||||||
@ -141,6 +143,18 @@ namespace osu.Game.Skinning
|
|||||||
hitError2.Origin = Anchor.CentreLeft;
|
hitError2.Origin = Anchor.CentreLeft;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (songProgress != null && keyCounter != null)
|
||||||
|
{
|
||||||
|
const float padding = 10;
|
||||||
|
|
||||||
|
// Hard to find this at runtime, so taken from the most expanded state during replay.
|
||||||
|
const float song_progress_offset_height = 73;
|
||||||
|
|
||||||
|
keyCounter.Anchor = Anchor.BottomRight;
|
||||||
|
keyCounter.Origin = Anchor.BottomRight;
|
||||||
|
keyCounter.Position = new Vector2(-padding, -(song_progress_offset_height + padding));
|
||||||
|
}
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
@ -150,6 +164,7 @@ namespace osu.Game.Skinning
|
|||||||
new DefaultAccuracyCounter(),
|
new DefaultAccuracyCounter(),
|
||||||
new DefaultHealthDisplay(),
|
new DefaultHealthDisplay(),
|
||||||
new DefaultSongProgress(),
|
new DefaultSongProgress(),
|
||||||
|
new DefaultKeyCounterDisplay(),
|
||||||
new BarHitErrorMeter(),
|
new BarHitErrorMeter(),
|
||||||
new BarHitErrorMeter(),
|
new BarHitErrorMeter(),
|
||||||
new PerformancePointsCounter()
|
new PerformancePointsCounter()
|
||||||
|
Loading…
Reference in New Issue
Block a user