mirror of
https://github.com/ppy/osu.git
synced 2025-01-30 21:32:57 +08:00
Merge branch 'master' into autoplay-pause-support
This commit is contained in:
commit
14570b6fb1
@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature Request
|
|
||||||
about: Propose a feature you would like to see in the game!
|
|
||||||
---
|
|
||||||
**Describe the new feature:**
|
|
||||||
|
|
||||||
**Proposal designs of the feature:**
|
|
9
.github/ISSUE_TEMPLATE/config.yml
vendored
9
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,5 +1,12 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
|
- name: Suggestions or feature request
|
||||||
|
url: https://github.com/ppy/osu/discussions/categories/ideas
|
||||||
|
about: Got something you think should change or be added? Search for or start a new discussion!
|
||||||
|
- name: Help
|
||||||
|
url: https://github.com/ppy/osu/discussions/categories/q-a
|
||||||
|
about: osu! not working as you'd expect? Not sure it's a bug? Check the Q&A section!
|
||||||
- name: osu!stable issues
|
- name: osu!stable issues
|
||||||
url: https://github.com/ppy/osu-stable-issues
|
url: https://github.com/ppy/osu-stable-issues
|
||||||
about: For issues regarding osu!stable (not osu!lazer), open them here.
|
about: For osu!stable bugs (not osu!lazer), check out the dedicated repository. Note that we only accept serious bug reports.
|
||||||
|
|
||||||
|
@ -52,6 +52,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.524.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.528.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
// 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.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
@ -10,5 +18,22 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
public class TestSceneCatchPlayerLegacySkin : LegacySkinPlayerTestScene
|
public class TestSceneCatchPlayerLegacySkin : LegacySkinPlayerTestScene
|
||||||
{
|
{
|
||||||
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLegacyHUDComboCounterHidden([Values] bool withModifiedSkin)
|
||||||
|
{
|
||||||
|
if (withModifiedSkin)
|
||||||
|
{
|
||||||
|
AddStep("change component scale", () => Player.ChildrenOfType<LegacyScoreCounter>().First().Scale = new Vector2(2f));
|
||||||
|
AddStep("update target", () => Player.ChildrenOfType<SkinnableTargetContainer>().ForEach(LegacySkin.UpdateDrawableTarget));
|
||||||
|
AddStep("exit player", () => Player.Exit());
|
||||||
|
CreateTest(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddAssert("legacy HUD combo counter hidden", () =>
|
||||||
|
{
|
||||||
|
return Player.ChildrenOfType<LegacyComboCounter>().All(c => c.ChildrenOfType<Container>().Single().Alpha == 0f);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,28 +32,28 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[TestCase(true, false)]
|
[TestCase(true, false)]
|
||||||
[TestCase(false, true)]
|
[TestCase(false, true)]
|
||||||
[TestCase(false, false)]
|
[TestCase(false, false)]
|
||||||
public override void TestBeatmapComboColours(bool userHasCustomColours, bool useBeatmapSkin)
|
public void TestBeatmapComboColours(bool userHasCustomColours, bool useBeatmapSkin)
|
||||||
{
|
{
|
||||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, true));
|
||||||
base.TestBeatmapComboColours(userHasCustomColours, useBeatmapSkin);
|
ConfigureTest(useBeatmapSkin, true, userHasCustomColours);
|
||||||
AddAssert("is beatmap skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
|
AddAssert("is beatmap skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
public override void TestBeatmapComboColoursOverride(bool useBeatmapSkin)
|
public void TestBeatmapComboColoursOverride(bool useBeatmapSkin)
|
||||||
{
|
{
|
||||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, true));
|
||||||
base.TestBeatmapComboColoursOverride(useBeatmapSkin);
|
ConfigureTest(useBeatmapSkin, false, true);
|
||||||
AddAssert("is user custom skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
AddAssert("is user custom skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
public override void TestBeatmapComboColoursOverrideWithDefaultColours(bool useBeatmapSkin)
|
public void TestBeatmapComboColoursOverrideWithDefaultColours(bool useBeatmapSkin)
|
||||||
{
|
{
|
||||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, true));
|
||||||
base.TestBeatmapComboColoursOverrideWithDefaultColours(useBeatmapSkin);
|
ConfigureTest(useBeatmapSkin, false, false);
|
||||||
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
|
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,10 +61,10 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[TestCase(false, true)]
|
[TestCase(false, true)]
|
||||||
[TestCase(true, false)]
|
[TestCase(true, false)]
|
||||||
[TestCase(false, false)]
|
[TestCase(false, false)]
|
||||||
public override void TestBeatmapNoComboColours(bool useBeatmapSkin, bool useBeatmapColour)
|
public void TestBeatmapNoComboColours(bool useBeatmapSkin, bool useBeatmapColour)
|
||||||
{
|
{
|
||||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, false);
|
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, false));
|
||||||
base.TestBeatmapNoComboColours(useBeatmapSkin, useBeatmapColour);
|
ConfigureTest(useBeatmapSkin, useBeatmapColour, false);
|
||||||
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
|
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,10 +72,10 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[TestCase(false, true)]
|
[TestCase(false, true)]
|
||||||
[TestCase(true, false)]
|
[TestCase(true, false)]
|
||||||
[TestCase(false, false)]
|
[TestCase(false, false)]
|
||||||
public override void TestBeatmapNoComboColoursSkinOverride(bool useBeatmapSkin, bool useBeatmapColour)
|
public void TestBeatmapNoComboColoursSkinOverride(bool useBeatmapSkin, bool useBeatmapColour)
|
||||||
{
|
{
|
||||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, false);
|
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, false));
|
||||||
base.TestBeatmapNoComboColoursSkinOverride(useBeatmapSkin, useBeatmapColour);
|
ConfigureTest(useBeatmapSkin, useBeatmapColour, true);
|
||||||
AddAssert("is custom user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
AddAssert("is custom user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
public void TestBeatmapHyperDashColours(bool useBeatmapSkin)
|
public void TestBeatmapHyperDashColours(bool useBeatmapSkin)
|
||||||
{
|
{
|
||||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, true));
|
||||||
ConfigureTest(useBeatmapSkin, true, true);
|
ConfigureTest(useBeatmapSkin, true, true);
|
||||||
AddAssert("is custom hyper dash colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashColour == TestBeatmapSkin.HYPER_DASH_COLOUR);
|
AddAssert("is custom hyper dash colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashColour == TestBeatmapSkin.HYPER_DASH_COLOUR);
|
||||||
AddAssert("is custom hyper dash after image colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashAfterImageColour == TestBeatmapSkin.HYPER_DASH_AFTER_IMAGE_COLOUR);
|
AddAssert("is custom hyper dash after image colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashAfterImageColour == TestBeatmapSkin.HYPER_DASH_AFTER_IMAGE_COLOUR);
|
||||||
@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
public void TestBeatmapHyperDashColoursOverride(bool useBeatmapSkin)
|
public void TestBeatmapHyperDashColoursOverride(bool useBeatmapSkin)
|
||||||
{
|
{
|
||||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, true));
|
||||||
ConfigureTest(useBeatmapSkin, false, true);
|
ConfigureTest(useBeatmapSkin, false, true);
|
||||||
AddAssert("is custom hyper dash colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashColour == TestSkin.HYPER_DASH_COLOUR);
|
AddAssert("is custom hyper dash colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashColour == TestSkin.HYPER_DASH_COLOUR);
|
||||||
AddAssert("is custom hyper dash after image colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashAfterImageColour == TestSkin.HYPER_DASH_AFTER_IMAGE_COLOUR);
|
AddAssert("is custom hyper dash after image colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashAfterImageColour == TestSkin.HYPER_DASH_AFTER_IMAGE_COLOUR);
|
||||||
|
@ -161,13 +161,13 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
case HitResult.LargeTickHit:
|
case HitResult.LargeTickHit:
|
||||||
return "large droplet";
|
return "Large droplet";
|
||||||
|
|
||||||
case HitResult.SmallTickHit:
|
case HitResult.SmallTickHit:
|
||||||
return "small droplet";
|
return "Small droplet";
|
||||||
|
|
||||||
case HitResult.LargeBonus:
|
case HitResult.LargeBonus:
|
||||||
return "banana";
|
return "Banana";
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.GetDisplayNameForHitResult(result);
|
return base.GetDisplayNameForHitResult(result);
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
// 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.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -22,59 +24,68 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
|
|
||||||
public override Drawable GetDrawableComponent(ISkinComponent component)
|
public override Drawable GetDrawableComponent(ISkinComponent component)
|
||||||
{
|
{
|
||||||
if (component is HUDSkinComponent hudComponent)
|
if (component is SkinnableTargetComponent targetComponent)
|
||||||
{
|
{
|
||||||
switch (hudComponent.Component)
|
switch (targetComponent.Target)
|
||||||
{
|
{
|
||||||
case HUDSkinComponents.ComboCounter:
|
case SkinnableTarget.MainHUDComponents:
|
||||||
// catch may provide its own combo counter; hide the default.
|
var components = Source.GetDrawableComponent(component) as SkinnableTargetComponentsContainer;
|
||||||
return providesComboCounter ? Drawable.Empty() : null;
|
|
||||||
|
if (providesComboCounter && components != null)
|
||||||
|
{
|
||||||
|
// catch may provide its own combo counter; hide the default.
|
||||||
|
// todo: this should be done in an elegant way per ruleset, defining which HUD skin components should be displayed.
|
||||||
|
foreach (var legacyComboCounter in components.OfType<LegacyComboCounter>())
|
||||||
|
legacyComboCounter.HiddenByRulesetImplementation = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return components;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(component is CatchSkinComponent catchSkinComponent))
|
if (component is CatchSkinComponent catchSkinComponent)
|
||||||
return null;
|
|
||||||
|
|
||||||
switch (catchSkinComponent.Component)
|
|
||||||
{
|
{
|
||||||
case CatchSkinComponents.Fruit:
|
switch (catchSkinComponent.Component)
|
||||||
if (GetTexture("fruit-pear") != null)
|
{
|
||||||
return new LegacyFruitPiece();
|
case CatchSkinComponents.Fruit:
|
||||||
|
if (GetTexture("fruit-pear") != null)
|
||||||
|
return new LegacyFruitPiece();
|
||||||
|
|
||||||
break;
|
return null;
|
||||||
|
|
||||||
case CatchSkinComponents.Banana:
|
case CatchSkinComponents.Banana:
|
||||||
if (GetTexture("fruit-bananas") != null)
|
if (GetTexture("fruit-bananas") != null)
|
||||||
return new LegacyBananaPiece();
|
return new LegacyBananaPiece();
|
||||||
|
|
||||||
break;
|
return null;
|
||||||
|
|
||||||
case CatchSkinComponents.Droplet:
|
case CatchSkinComponents.Droplet:
|
||||||
if (GetTexture("fruit-drop") != null)
|
if (GetTexture("fruit-drop") != null)
|
||||||
return new LegacyDropletPiece();
|
return new LegacyDropletPiece();
|
||||||
|
|
||||||
break;
|
return null;
|
||||||
|
|
||||||
case CatchSkinComponents.CatcherIdle:
|
case CatchSkinComponents.CatcherIdle:
|
||||||
return this.GetAnimation("fruit-catcher-idle", true, true, true) ??
|
return this.GetAnimation("fruit-catcher-idle", true, true, true) ??
|
||||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||||
|
|
||||||
case CatchSkinComponents.CatcherFail:
|
case CatchSkinComponents.CatcherFail:
|
||||||
return this.GetAnimation("fruit-catcher-fail", true, true, true) ??
|
return this.GetAnimation("fruit-catcher-fail", true, true, true) ??
|
||||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||||
|
|
||||||
case CatchSkinComponents.CatcherKiai:
|
case CatchSkinComponents.CatcherKiai:
|
||||||
return this.GetAnimation("fruit-catcher-kiai", true, true, true) ??
|
return this.GetAnimation("fruit-catcher-kiai", true, true, true) ??
|
||||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||||
|
|
||||||
case CatchSkinComponents.CatchComboCounter:
|
case CatchSkinComponents.CatchComboCounter:
|
||||||
if (providesComboCounter)
|
if (providesComboCounter)
|
||||||
return new LegacyCatchComboCounter(Source);
|
return new LegacyCatchComboCounter(Source);
|
||||||
|
|
||||||
break;
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return Source.GetDrawableComponent(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
public class TestSceneManiaHitObjectSamples : HitObjectSampleTest
|
public class TestSceneManiaHitObjectSamples : HitObjectSampleTest
|
||||||
{
|
{
|
||||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
protected override IResourceStore<byte[]> Resources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneManiaHitObjectSamples)));
|
protected override IResourceStore<byte[]> RulesetResources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneManiaHitObjectSamples)));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests that when a normal sample bank is used, the normal hitsound will be looked up.
|
/// Tests that when a normal sample bank is used, the normal hitsound will be looked up.
|
||||||
|
@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return Source.GetDrawableComponent(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable getResult(HitResult result)
|
private Drawable getResult(HitResult result)
|
||||||
|
@ -30,28 +30,28 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
[TestCase(true, false)]
|
[TestCase(true, false)]
|
||||||
[TestCase(false, true)]
|
[TestCase(false, true)]
|
||||||
[TestCase(false, false)]
|
[TestCase(false, false)]
|
||||||
public override void TestBeatmapComboColours(bool userHasCustomColours, bool useBeatmapSkin)
|
public void TestBeatmapComboColours(bool userHasCustomColours, bool useBeatmapSkin)
|
||||||
{
|
{
|
||||||
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true);
|
PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, true));
|
||||||
base.TestBeatmapComboColours(userHasCustomColours, useBeatmapSkin);
|
ConfigureTest(useBeatmapSkin, true, userHasCustomColours);
|
||||||
AddAssert("is beatmap skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
|
AddAssert("is beatmap skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
public override void TestBeatmapComboColoursOverride(bool useBeatmapSkin)
|
public void TestBeatmapComboColoursOverride(bool useBeatmapSkin)
|
||||||
{
|
{
|
||||||
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true);
|
PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, true));
|
||||||
base.TestBeatmapComboColoursOverride(useBeatmapSkin);
|
ConfigureTest(useBeatmapSkin, false, true);
|
||||||
AddAssert("is user custom skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
AddAssert("is user custom skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
public override void TestBeatmapComboColoursOverrideWithDefaultColours(bool useBeatmapSkin)
|
public void TestBeatmapComboColoursOverrideWithDefaultColours(bool useBeatmapSkin)
|
||||||
{
|
{
|
||||||
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true);
|
PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, true));
|
||||||
base.TestBeatmapComboColoursOverrideWithDefaultColours(useBeatmapSkin);
|
ConfigureTest(useBeatmapSkin, false, false);
|
||||||
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
|
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,10 +59,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
[TestCase(false, true)]
|
[TestCase(false, true)]
|
||||||
[TestCase(true, false)]
|
[TestCase(true, false)]
|
||||||
[TestCase(false, false)]
|
[TestCase(false, false)]
|
||||||
public override void TestBeatmapNoComboColours(bool useBeatmapSkin, bool useBeatmapColour)
|
public void TestBeatmapNoComboColours(bool useBeatmapSkin, bool useBeatmapColour)
|
||||||
{
|
{
|
||||||
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, false);
|
PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, false));
|
||||||
base.TestBeatmapNoComboColours(useBeatmapSkin, useBeatmapColour);
|
ConfigureTest(useBeatmapSkin, useBeatmapColour, false);
|
||||||
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
|
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,10 +70,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
[TestCase(false, true)]
|
[TestCase(false, true)]
|
||||||
[TestCase(true, false)]
|
[TestCase(true, false)]
|
||||||
[TestCase(false, false)]
|
[TestCase(false, false)]
|
||||||
public override void TestBeatmapNoComboColoursSkinOverride(bool useBeatmapSkin, bool useBeatmapColour)
|
public void TestBeatmapNoComboColoursSkinOverride(bool useBeatmapSkin, bool useBeatmapColour)
|
||||||
{
|
{
|
||||||
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, false);
|
PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, false));
|
||||||
base.TestBeatmapNoComboColoursSkinOverride(useBeatmapSkin, useBeatmapColour);
|
ConfigureTest(useBeatmapSkin, useBeatmapColour, true);
|
||||||
AddAssert("is custom user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
AddAssert("is custom user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,13 +7,14 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking, IHasMainCirclePiece
|
public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, IHasMainCirclePiece
|
||||||
{
|
{
|
||||||
public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject;
|
public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject;
|
||||||
|
|
||||||
@ -111,7 +112,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateSnakingPosition(Vector2 start, Vector2 end) =>
|
protected override void OnApply()
|
||||||
Position = HitObject.RepeatIndex % 2 == 0 ? end : start;
|
{
|
||||||
|
base.OnApply();
|
||||||
|
|
||||||
|
if (Slider != null)
|
||||||
|
Position = Slider.CurvePositionAt(HitObject.RepeatIndex % 2 == 0 ? 1 : 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,90 +34,90 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
public override Drawable GetDrawableComponent(ISkinComponent component)
|
public override Drawable GetDrawableComponent(ISkinComponent component)
|
||||||
{
|
{
|
||||||
if (!(component is OsuSkinComponent osuComponent))
|
if (component is OsuSkinComponent osuComponent)
|
||||||
return null;
|
|
||||||
|
|
||||||
switch (osuComponent.Component)
|
|
||||||
{
|
{
|
||||||
case OsuSkinComponents.FollowPoint:
|
switch (osuComponent.Component)
|
||||||
return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false);
|
{
|
||||||
|
case OsuSkinComponents.FollowPoint:
|
||||||
|
return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false);
|
||||||
|
|
||||||
case OsuSkinComponents.SliderFollowCircle:
|
case OsuSkinComponents.SliderFollowCircle:
|
||||||
var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
|
var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
|
||||||
if (followCircle != null)
|
if (followCircle != null)
|
||||||
// follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x
|
// follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x
|
||||||
followCircle.Scale *= 0.5f;
|
followCircle.Scale *= 0.5f;
|
||||||
return followCircle;
|
return followCircle;
|
||||||
|
|
||||||
case OsuSkinComponents.SliderBall:
|
case OsuSkinComponents.SliderBall:
|
||||||
var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "");
|
var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "");
|
||||||
|
|
||||||
// todo: slider ball has a custom frame delay based on velocity
|
// todo: slider ball has a custom frame delay based on velocity
|
||||||
// Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME);
|
// Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME);
|
||||||
|
|
||||||
if (sliderBallContent != null)
|
if (sliderBallContent != null)
|
||||||
return new LegacySliderBall(sliderBallContent);
|
return new LegacySliderBall(sliderBallContent);
|
||||||
|
|
||||||
return null;
|
|
||||||
|
|
||||||
case OsuSkinComponents.SliderBody:
|
|
||||||
if (hasHitCircle.Value)
|
|
||||||
return new LegacySliderBody();
|
|
||||||
|
|
||||||
return null;
|
|
||||||
|
|
||||||
case OsuSkinComponents.SliderTailHitCircle:
|
|
||||||
if (hasHitCircle.Value)
|
|
||||||
return new LegacyMainCirclePiece("sliderendcircle", false);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
|
|
||||||
case OsuSkinComponents.SliderHeadHitCircle:
|
|
||||||
if (hasHitCircle.Value)
|
|
||||||
return new LegacyMainCirclePiece("sliderstartcircle");
|
|
||||||
|
|
||||||
return null;
|
|
||||||
|
|
||||||
case OsuSkinComponents.HitCircle:
|
|
||||||
if (hasHitCircle.Value)
|
|
||||||
return new LegacyMainCirclePiece();
|
|
||||||
|
|
||||||
return null;
|
|
||||||
|
|
||||||
case OsuSkinComponents.Cursor:
|
|
||||||
if (Source.GetTexture("cursor") != null)
|
|
||||||
return new LegacyCursor();
|
|
||||||
|
|
||||||
return null;
|
|
||||||
|
|
||||||
case OsuSkinComponents.CursorTrail:
|
|
||||||
if (Source.GetTexture("cursortrail") != null)
|
|
||||||
return new LegacyCursorTrail();
|
|
||||||
|
|
||||||
return null;
|
|
||||||
|
|
||||||
case OsuSkinComponents.HitCircleText:
|
|
||||||
if (!this.HasFont(LegacyFont.HitCircle))
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new LegacySpriteText(LegacyFont.HitCircle)
|
case OsuSkinComponents.SliderBody:
|
||||||
{
|
if (hasHitCircle.Value)
|
||||||
// stable applies a blanket 0.8x scale to hitcircle fonts
|
return new LegacySliderBody();
|
||||||
Scale = new Vector2(0.8f),
|
|
||||||
};
|
|
||||||
|
|
||||||
case OsuSkinComponents.SpinnerBody:
|
return null;
|
||||||
bool hasBackground = Source.GetTexture("spinner-background") != null;
|
|
||||||
|
|
||||||
if (Source.GetTexture("spinner-top") != null && !hasBackground)
|
case OsuSkinComponents.SliderTailHitCircle:
|
||||||
return new LegacyNewStyleSpinner();
|
if (hasHitCircle.Value)
|
||||||
else if (hasBackground)
|
return new LegacyMainCirclePiece("sliderendcircle", false);
|
||||||
return new LegacyOldStyleSpinner();
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
case OsuSkinComponents.SliderHeadHitCircle:
|
||||||
|
if (hasHitCircle.Value)
|
||||||
|
return new LegacyMainCirclePiece("sliderstartcircle");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
case OsuSkinComponents.HitCircle:
|
||||||
|
if (hasHitCircle.Value)
|
||||||
|
return new LegacyMainCirclePiece();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
case OsuSkinComponents.Cursor:
|
||||||
|
if (Source.GetTexture("cursor") != null)
|
||||||
|
return new LegacyCursor();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
case OsuSkinComponents.CursorTrail:
|
||||||
|
if (Source.GetTexture("cursortrail") != null)
|
||||||
|
return new LegacyCursorTrail();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
case OsuSkinComponents.HitCircleText:
|
||||||
|
if (!this.HasFont(LegacyFont.HitCircle))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new LegacySpriteText(LegacyFont.HitCircle)
|
||||||
|
{
|
||||||
|
// stable applies a blanket 0.8x scale to hitcircle fonts
|
||||||
|
Scale = new Vector2(0.8f),
|
||||||
|
};
|
||||||
|
|
||||||
|
case OsuSkinComponents.SpinnerBody:
|
||||||
|
bool hasBackground = Source.GetTexture("spinner-background") != null;
|
||||||
|
|
||||||
|
if (Source.GetTexture("spinner-top") != null && !hasBackground)
|
||||||
|
return new LegacyNewStyleSpinner();
|
||||||
|
else if (hasBackground)
|
||||||
|
return new LegacyOldStyleSpinner();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return Source.GetDrawableComponent(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
{
|
{
|
||||||
protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset();
|
||||||
|
|
||||||
protected override IResourceStore<byte[]> Resources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneTaikoHitObjectSamples)));
|
protected override IResourceStore<byte[]> RulesetResources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneTaikoHitObjectSamples)));
|
||||||
|
|
||||||
[TestCase("taiko-normal-hitnormal")]
|
[TestCase("taiko-normal-hitnormal")]
|
||||||
[TestCase("normal-hitnormal")]
|
[TestCase("normal-hitnormal")]
|
||||||
|
@ -79,8 +79,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
// Old osu! used hit sounding to determine various hit type information
|
// Old osu! used hit sounding to determine various hit type information
|
||||||
IList<HitSampleInfo> samples = obj.Samples;
|
IList<HitSampleInfo> samples = obj.Samples;
|
||||||
|
|
||||||
bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
|
|
||||||
|
|
||||||
switch (obj)
|
switch (obj)
|
||||||
{
|
{
|
||||||
case IHasDistance distanceData:
|
case IHasDistance distanceData:
|
||||||
@ -94,15 +92,11 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
|
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
|
||||||
{
|
{
|
||||||
IList<HitSampleInfo> currentSamples = allSamples[i];
|
IList<HitSampleInfo> currentSamples = allSamples[i];
|
||||||
bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
|
|
||||||
strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
|
|
||||||
|
|
||||||
yield return new Hit
|
yield return new Hit
|
||||||
{
|
{
|
||||||
StartTime = j,
|
StartTime = j,
|
||||||
Type = isRim ? HitType.Rim : HitType.Centre,
|
|
||||||
Samples = currentSamples,
|
Samples = currentSamples,
|
||||||
IsStrong = strong
|
|
||||||
};
|
};
|
||||||
|
|
||||||
i = (i + 1) % allSamples.Count;
|
i = (i + 1) % allSamples.Count;
|
||||||
@ -117,7 +111,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
{
|
{
|
||||||
StartTime = obj.StartTime,
|
StartTime = obj.StartTime,
|
||||||
Samples = obj.Samples,
|
Samples = obj.Samples,
|
||||||
IsStrong = strong,
|
|
||||||
Duration = taikoDuration,
|
Duration = taikoDuration,
|
||||||
TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
|
TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
|
||||||
};
|
};
|
||||||
@ -143,16 +136,10 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
bool isRimDefinition(HitSampleInfo s) => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE;
|
|
||||||
|
|
||||||
bool isRim = samples.Any(isRimDefinition);
|
|
||||||
|
|
||||||
yield return new Hit
|
yield return new Hit
|
||||||
{
|
{
|
||||||
StartTime = obj.StartTime,
|
StartTime = obj.StartTime,
|
||||||
Type = isRim ? HitType.Rim : HitType.Centre,
|
|
||||||
Samples = samples,
|
Samples = samples,
|
||||||
IsStrong = strong
|
|
||||||
};
|
};
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -69,7 +69,11 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
{
|
{
|
||||||
EditorBeatmap.PerformOnSelection(h =>
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
{
|
{
|
||||||
if (h is Hit taikoHit) taikoHit.Type = state ? HitType.Rim : HitType.Centre;
|
if (h is Hit taikoHit)
|
||||||
|
{
|
||||||
|
taikoHit.Type = state ? HitType.Rim : HitType.Centre;
|
||||||
|
EditorBeatmap.Update(h);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,13 +17,25 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
public HitType Type
|
public HitType Type
|
||||||
{
|
{
|
||||||
get => TypeBindable.Value;
|
get => TypeBindable.Value;
|
||||||
set
|
set => TypeBindable.Value = value;
|
||||||
{
|
|
||||||
TypeBindable.Value = value;
|
|
||||||
updateSamplesFromType();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Hit()
|
||||||
|
{
|
||||||
|
TypeBindable.BindValueChanged(_ => updateSamplesFromType());
|
||||||
|
SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTypeFromSamples()
|
||||||
|
{
|
||||||
|
Type = getRimSamples().Any() ? HitType.Rim : HitType.Centre;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an array of any samples which would cause this object to be a "rim" type hit.
|
||||||
|
/// </summary>
|
||||||
|
private HitSampleInfo[] getRimSamples() => Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray();
|
||||||
|
|
||||||
private void updateSamplesFromType()
|
private void updateSamplesFromType()
|
||||||
{
|
{
|
||||||
var rimSamples = getRimSamples();
|
var rimSamples = getRimSamples();
|
||||||
@ -42,11 +54,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns an array of any samples which would cause this object to be a "rim" type hit.
|
|
||||||
/// </summary>
|
|
||||||
private HitSampleInfo[] getRimSamples() => Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray();
|
|
||||||
|
|
||||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
|
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
|
||||||
|
|
||||||
public class StrongNestedHit : StrongNestedHitObject
|
public class StrongNestedHit : StrongNestedHitObject
|
||||||
|
@ -33,14 +33,21 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
public bool IsStrong
|
public bool IsStrong
|
||||||
{
|
{
|
||||||
get => IsStrongBindable.Value;
|
get => IsStrongBindable.Value;
|
||||||
set
|
set => IsStrongBindable.Value = value;
|
||||||
{
|
|
||||||
IsStrongBindable.Value = value;
|
|
||||||
updateSamplesFromStrong();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSamplesFromStrong()
|
protected TaikoStrongableHitObject()
|
||||||
|
{
|
||||||
|
IsStrongBindable.BindValueChanged(_ => updateSamplesFromType());
|
||||||
|
SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTypeFromSamples()
|
||||||
|
{
|
||||||
|
IsStrong = getStrongSamples().Any();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSamplesFromType()
|
||||||
{
|
{
|
||||||
var strongSamples = getStrongSamples();
|
var strongSamples = getStrongSamples();
|
||||||
|
|
||||||
|
@ -38,98 +38,98 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
return Drawable.Empty().With(d => d.Expire());
|
return Drawable.Empty().With(d => d.Expire());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(component is TaikoSkinComponent taikoComponent))
|
if (component is TaikoSkinComponent taikoComponent)
|
||||||
return null;
|
|
||||||
|
|
||||||
switch (taikoComponent.Component)
|
|
||||||
{
|
{
|
||||||
case TaikoSkinComponents.DrumRollBody:
|
switch (taikoComponent.Component)
|
||||||
if (GetTexture("taiko-roll-middle") != null)
|
{
|
||||||
return new LegacyDrumRoll();
|
case TaikoSkinComponents.DrumRollBody:
|
||||||
|
if (GetTexture("taiko-roll-middle") != null)
|
||||||
|
return new LegacyDrumRoll();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case TaikoSkinComponents.InputDrum:
|
case TaikoSkinComponents.InputDrum:
|
||||||
if (GetTexture("taiko-bar-left") != null)
|
if (GetTexture("taiko-bar-left") != null)
|
||||||
return new LegacyInputDrum();
|
return new LegacyInputDrum();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case TaikoSkinComponents.CentreHit:
|
case TaikoSkinComponents.CentreHit:
|
||||||
case TaikoSkinComponents.RimHit:
|
case TaikoSkinComponents.RimHit:
|
||||||
|
|
||||||
if (GetTexture("taikohitcircle") != null)
|
if (GetTexture("taikohitcircle") != null)
|
||||||
return new LegacyHit(taikoComponent.Component);
|
return new LegacyHit(taikoComponent.Component);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case TaikoSkinComponents.DrumRollTick:
|
case TaikoSkinComponents.DrumRollTick:
|
||||||
return this.GetAnimation("sliderscorepoint", false, false);
|
return this.GetAnimation("sliderscorepoint", false, false);
|
||||||
|
|
||||||
case TaikoSkinComponents.HitTarget:
|
case TaikoSkinComponents.HitTarget:
|
||||||
if (GetTexture("taikobigcircle") != null)
|
if (GetTexture("taikobigcircle") != null)
|
||||||
return new TaikoLegacyHitTarget();
|
return new TaikoLegacyHitTarget();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case TaikoSkinComponents.PlayfieldBackgroundRight:
|
case TaikoSkinComponents.PlayfieldBackgroundRight:
|
||||||
if (GetTexture("taiko-bar-right") != null)
|
if (GetTexture("taiko-bar-right") != null)
|
||||||
return new TaikoLegacyPlayfieldBackgroundRight();
|
return new TaikoLegacyPlayfieldBackgroundRight();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case TaikoSkinComponents.PlayfieldBackgroundLeft:
|
case TaikoSkinComponents.PlayfieldBackgroundLeft:
|
||||||
// This is displayed inside LegacyInputDrum. It is required to be there for layout purposes (can be seen on legacy skins).
|
// This is displayed inside LegacyInputDrum. It is required to be there for layout purposes (can be seen on legacy skins).
|
||||||
if (GetTexture("taiko-bar-right") != null)
|
if (GetTexture("taiko-bar-right") != null)
|
||||||
return Drawable.Empty();
|
return Drawable.Empty();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case TaikoSkinComponents.BarLine:
|
case TaikoSkinComponents.BarLine:
|
||||||
if (GetTexture("taiko-barline") != null)
|
if (GetTexture("taiko-barline") != null)
|
||||||
return new LegacyBarLine();
|
return new LegacyBarLine();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case TaikoSkinComponents.TaikoExplosionMiss:
|
case TaikoSkinComponents.TaikoExplosionMiss:
|
||||||
|
|
||||||
var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false);
|
var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false);
|
||||||
if (missSprite != null)
|
if (missSprite != null)
|
||||||
return new LegacyHitExplosion(missSprite);
|
return new LegacyHitExplosion(missSprite);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case TaikoSkinComponents.TaikoExplosionOk:
|
case TaikoSkinComponents.TaikoExplosionOk:
|
||||||
case TaikoSkinComponents.TaikoExplosionGreat:
|
case TaikoSkinComponents.TaikoExplosionGreat:
|
||||||
|
|
||||||
var hitName = getHitName(taikoComponent.Component);
|
var hitName = getHitName(taikoComponent.Component);
|
||||||
var hitSprite = this.GetAnimation(hitName, true, false);
|
var hitSprite = this.GetAnimation(hitName, true, false);
|
||||||
|
|
||||||
if (hitSprite != null)
|
if (hitSprite != null)
|
||||||
{
|
{
|
||||||
var strongHitSprite = this.GetAnimation($"{hitName}k", true, false);
|
var strongHitSprite = this.GetAnimation($"{hitName}k", true, false);
|
||||||
|
|
||||||
return new LegacyHitExplosion(hitSprite, strongHitSprite);
|
return new LegacyHitExplosion(hitSprite, strongHitSprite);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case TaikoSkinComponents.TaikoExplosionKiai:
|
case TaikoSkinComponents.TaikoExplosionKiai:
|
||||||
// suppress the default kiai explosion if the skin brings its own sprites.
|
// suppress the default kiai explosion if the skin brings its own sprites.
|
||||||
// the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield.
|
// the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield.
|
||||||
if (hasExplosion.Value)
|
if (hasExplosion.Value)
|
||||||
return Drawable.Empty().With(d => d.Expire());
|
return Drawable.Empty().With(d => d.Expire());
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case TaikoSkinComponents.Scroller:
|
case TaikoSkinComponents.Scroller:
|
||||||
if (GetTexture("taiko-slider") != null)
|
if (GetTexture("taiko-slider") != null)
|
||||||
return new LegacyTaikoScroller();
|
return new LegacyTaikoScroller();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case TaikoSkinComponents.Mascot:
|
case TaikoSkinComponents.Mascot:
|
||||||
return new DrawableTaikoMascot();
|
return new DrawableTaikoMascot();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Source.GetDrawableComponent(component);
|
return Source.GetDrawableComponent(component);
|
||||||
|
@ -169,6 +169,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
|
|
||||||
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||||
|
|
||||||
|
protected override ISkin GetSkin() => throw new NotImplementedException();
|
||||||
|
|
||||||
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
|
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Tests.Collections.IO
|
|||||||
{
|
{
|
||||||
var osu = LoadOsuIntoHost(host);
|
var osu = LoadOsuIntoHost(host);
|
||||||
|
|
||||||
await osu.CollectionManager.Import(new MemoryStream());
|
await importCollectionsFromStream(osu, new MemoryStream());
|
||||||
|
|
||||||
Assert.That(osu.CollectionManager.Collections.Count, Is.Zero);
|
Assert.That(osu.CollectionManager.Collections.Count, Is.Zero);
|
||||||
}
|
}
|
||||||
@ -43,7 +43,7 @@ namespace osu.Game.Tests.Collections.IO
|
|||||||
{
|
{
|
||||||
var osu = LoadOsuIntoHost(host);
|
var osu = LoadOsuIntoHost(host);
|
||||||
|
|
||||||
await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
|
await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db"));
|
||||||
|
|
||||||
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
|
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ namespace osu.Game.Tests.Collections.IO
|
|||||||
{
|
{
|
||||||
var osu = LoadOsuIntoHost(host, true);
|
var osu = LoadOsuIntoHost(host, true);
|
||||||
|
|
||||||
await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
|
await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db"));
|
||||||
|
|
||||||
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
|
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ namespace osu.Game.Tests.Collections.IO
|
|||||||
|
|
||||||
ms.Seek(0, SeekOrigin.Begin);
|
ms.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
await osu.CollectionManager.Import(ms);
|
await importCollectionsFromStream(osu, ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.That(host.UpdateThread.Running, Is.True);
|
Assert.That(host.UpdateThread.Running, Is.True);
|
||||||
@ -134,7 +134,7 @@ namespace osu.Game.Tests.Collections.IO
|
|||||||
{
|
{
|
||||||
var osu = LoadOsuIntoHost(host, true);
|
var osu = LoadOsuIntoHost(host, true);
|
||||||
|
|
||||||
await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
|
await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db"));
|
||||||
|
|
||||||
// Move first beatmap from second collection into the first.
|
// Move first beatmap from second collection into the first.
|
||||||
osu.CollectionManager.Collections[0].Beatmaps.Add(osu.CollectionManager.Collections[1].Beatmaps[0]);
|
osu.CollectionManager.Collections[0].Beatmaps.Add(osu.CollectionManager.Collections[1].Beatmaps[0]);
|
||||||
@ -169,5 +169,12 @@ namespace osu.Game.Tests.Collections.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task importCollectionsFromStream(TestOsuGameBase osu, Stream stream)
|
||||||
|
{
|
||||||
|
// intentionally spin this up on a separate task to avoid disposal deadlocks.
|
||||||
|
// see https://github.com/EventStore/EventStore/issues/1179
|
||||||
|
await Task.Run(() => osu.CollectionManager.Import(stream).Wait());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
@ -70,15 +72,46 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
AddAssert("Lifetime is changed", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == 1000);
|
AddAssert("Lifetime is changed", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLifetimeUpdatedOnDefaultApplied()
|
||||||
|
{
|
||||||
|
TestLifetimeEntry entry = null;
|
||||||
|
AddStep("Create entry", () => entry = new TestLifetimeEntry(new HitObject()) { LifetimeStart = 1 });
|
||||||
|
AddStep("ApplyDefaults", () => entry.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()));
|
||||||
|
AddAssert("Lifetime is updated", () => entry.LifetimeStart == -TestLifetimeEntry.INITIAL_LIFETIME_OFFSET);
|
||||||
|
|
||||||
|
TestDrawableHitObject dho = null;
|
||||||
|
AddStep("Create DHO", () =>
|
||||||
|
{
|
||||||
|
dho = new TestDrawableHitObject(null);
|
||||||
|
dho.Apply(entry);
|
||||||
|
Child = dho;
|
||||||
|
dho.SetLifetimeStartOnApply = true;
|
||||||
|
});
|
||||||
|
AddStep("ApplyDefaults", () => entry.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()));
|
||||||
|
AddAssert("Lifetime is correct", () => dho.LifetimeStart == TestDrawableHitObject.LIFETIME_ON_APPLY && entry.LifetimeStart == TestDrawableHitObject.LIFETIME_ON_APPLY);
|
||||||
|
}
|
||||||
|
|
||||||
private class TestDrawableHitObject : DrawableHitObject
|
private class TestDrawableHitObject : DrawableHitObject
|
||||||
{
|
{
|
||||||
public const double INITIAL_LIFETIME_OFFSET = 100;
|
public const double INITIAL_LIFETIME_OFFSET = 100;
|
||||||
|
public const double LIFETIME_ON_APPLY = 222;
|
||||||
protected override double InitialLifetimeOffset => INITIAL_LIFETIME_OFFSET;
|
protected override double InitialLifetimeOffset => INITIAL_LIFETIME_OFFSET;
|
||||||
|
|
||||||
|
public bool SetLifetimeStartOnApply;
|
||||||
|
|
||||||
public TestDrawableHitObject(HitObject hitObject)
|
public TestDrawableHitObject(HitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnApply()
|
||||||
|
{
|
||||||
|
base.OnApply();
|
||||||
|
|
||||||
|
if (SetLifetimeStartOnApply)
|
||||||
|
LifetimeStart = LIFETIME_ON_APPLY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestLifetimeEntry : HitObjectLifetimeEntry
|
private class TestLifetimeEntry : HitObjectLifetimeEntry
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
public class TestSceneHitObjectSamples : HitObjectSampleTest
|
public class TestSceneHitObjectSamples : HitObjectSampleTest
|
||||||
{
|
{
|
||||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||||
protected override IResourceStore<byte[]> Resources => TestResources.GetStore();
|
protected override IResourceStore<byte[]> RulesetResources => TestResources.GetStore();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests that a hitobject which provides no custom sample set retrieves samples from the user skin.
|
/// Tests that a hitobject which provides no custom sample set retrieves samples from the user skin.
|
||||||
|
@ -219,6 +219,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
|
|
||||||
public AudioManager AudioManager => Audio;
|
public AudioManager AudioManager => Audio;
|
||||||
public IResourceStore<byte[]> Files => null;
|
public IResourceStore<byte[]> Files => null;
|
||||||
|
public new IResourceStore<byte[]> Resources => base.Resources;
|
||||||
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null;
|
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -44,7 +45,7 @@ namespace osu.Game.Tests.Online
|
|||||||
private void load(AudioManager audio, GameHost host)
|
private void load(AudioManager audio, GameHost host)
|
||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
Dependencies.CacheAs<BeatmapManager>(beatmaps = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, host, Beatmap.Default));
|
Dependencies.CacheAs<BeatmapManager>(beatmaps = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default));
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
@ -160,8 +161,8 @@ namespace osu.Game.Tests.Online
|
|||||||
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize)
|
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize)
|
||||||
=> new TestDownloadRequest(set);
|
=> new TestDownloadRequest(set);
|
||||||
|
|
||||||
public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, GameHost host = null, WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false)
|
public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore<byte[]> resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false)
|
||||||
: base(storage, contextFactory, rulesets, api, audioManager, host, defaultBeatmap, performOnlineLookups)
|
: base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap, performOnlineLookups)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||||
Dependencies.Cache(new OsuConfigManager(LocalStorage));
|
Dependencies.Cache(new OsuConfigManager(LocalStorage));
|
||||||
|
|
||||||
manager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
manager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
||||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Collections
|
|||||||
private void load(GameHost host)
|
private void load(GameHost host)
|
||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default));
|
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default));
|
||||||
|
|
||||||
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
||||||
|
|
||||||
|
@ -24,13 +24,13 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
protected override bool EditorComponentsReady => Editor.ChildrenOfType<SetupScreen>().SingleOrDefault()?.IsLoaded == true;
|
protected override bool EditorComponentsReady => Editor.ChildrenOfType<SetupScreen>().SingleOrDefault()?.IsLoaded == true;
|
||||||
|
|
||||||
|
protected override bool IsolateSavingFromDatabase => false;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmapManager { get; set; }
|
private BeatmapManager beatmapManager { get; set; }
|
||||||
|
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
{
|
{
|
||||||
AddStep("set dummy", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null));
|
|
||||||
|
|
||||||
base.SetUpSteps();
|
base.SetUpSteps();
|
||||||
|
|
||||||
// if we save a beatmap with a hash collision, things fall over.
|
// if we save a beatmap with a hash collision, things fall over.
|
||||||
@ -38,6 +38,12 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString());
|
AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadEditor()
|
||||||
|
{
|
||||||
|
Beatmap.Value = new DummyWorkingBeatmap(Audio, null);
|
||||||
|
base.LoadEditor();
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCreateNewBeatmap()
|
public void TestCreateNewBeatmap()
|
||||||
{
|
{
|
||||||
|
130
osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
Normal file
130
osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Lists;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public class TestSceneBeatmapSkinFallbacks : OsuPlayerTestScene
|
||||||
|
{
|
||||||
|
private ISkin currentBeatmapSkin;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private SkinManager skinManager { get; set; }
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||||
|
|
||||||
|
[Cached(typeof(HealthProcessor))]
|
||||||
|
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
|
||||||
|
|
||||||
|
protected override bool HasCustomSteps => true;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEmptyLegacyBeatmapSkinFallsBack()
|
||||||
|
{
|
||||||
|
CreateSkinTest(SkinInfo.Default, () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null));
|
||||||
|
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func<ISkin> getBeatmapSkin)
|
||||||
|
{
|
||||||
|
CreateTest(() =>
|
||||||
|
{
|
||||||
|
AddStep("setup skins", () =>
|
||||||
|
{
|
||||||
|
skinManager.CurrentSkinInfo.Value = gameCurrentSkin;
|
||||||
|
currentBeatmapSkin = getBeatmapSkin();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool AssertComponentsFromExpectedSource(SkinnableTarget target, ISkin expectedSource)
|
||||||
|
{
|
||||||
|
var actualComponentsContainer = Player.ChildrenOfType<SkinnableTargetContainer>().First(s => s.Target == target)
|
||||||
|
.ChildrenOfType<SkinnableTargetComponentsContainer>().SingleOrDefault();
|
||||||
|
|
||||||
|
if (actualComponentsContainer == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var actualInfo = actualComponentsContainer.CreateSkinnableInfo();
|
||||||
|
|
||||||
|
var expectedComponentsContainer = (SkinnableTargetComponentsContainer)expectedSource.GetDrawableComponent(new SkinnableTargetComponent(target));
|
||||||
|
if (expectedComponentsContainer == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var expectedComponentsAdjustmentContainer = new Container
|
||||||
|
{
|
||||||
|
Position = actualComponentsContainer.Parent.ToSpaceOfOtherDrawable(actualComponentsContainer.DrawPosition, Content),
|
||||||
|
Size = actualComponentsContainer.DrawSize,
|
||||||
|
Child = expectedComponentsContainer,
|
||||||
|
};
|
||||||
|
|
||||||
|
Add(expectedComponentsAdjustmentContainer);
|
||||||
|
expectedComponentsAdjustmentContainer.UpdateSubTree();
|
||||||
|
var expectedInfo = expectedComponentsContainer.CreateSkinnableInfo();
|
||||||
|
Remove(expectedComponentsAdjustmentContainer);
|
||||||
|
|
||||||
|
return almostEqual(actualInfo, expectedInfo);
|
||||||
|
|
||||||
|
static bool almostEqual(SkinnableInfo info, SkinnableInfo other) =>
|
||||||
|
other != null
|
||||||
|
&& info.Type == other.Type
|
||||||
|
&& info.Anchor == other.Anchor
|
||||||
|
&& info.Origin == other.Origin
|
||||||
|
&& Precision.AlmostEquals(info.Position, other.Position)
|
||||||
|
&& Precision.AlmostEquals(info.Scale, other.Scale)
|
||||||
|
&& Precision.AlmostEquals(info.Rotation, other.Rotation)
|
||||||
|
&& info.Children.SequenceEqual(other.Children, new FuncEqualityComparer<SkinnableInfo>(almostEqual));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||||
|
=> new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, Audio, currentBeatmapSkin);
|
||||||
|
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new TestOsuRuleset();
|
||||||
|
|
||||||
|
private class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
|
||||||
|
{
|
||||||
|
private readonly ISkin beatmapSkin;
|
||||||
|
|
||||||
|
public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, ISkin beatmapSkin)
|
||||||
|
: base(beatmap, storyboard, referenceClock, audio)
|
||||||
|
{
|
||||||
|
this.beatmapSkin = beatmapSkin;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ISkin GetSkin() => beatmapSkin;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestOsuRuleset : OsuRuleset
|
||||||
|
{
|
||||||
|
public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new TestOsuLegacySkinTransformer(source);
|
||||||
|
|
||||||
|
private class TestOsuLegacySkinTransformer : OsuLegacySkinTransformer
|
||||||
|
{
|
||||||
|
public TestOsuLegacySkinTransformer(ISkinSource source)
|
||||||
|
: base(source)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -88,13 +88,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
beforeLoadAction?.Invoke();
|
beforeLoadAction?.Invoke();
|
||||||
|
|
||||||
|
prepareBeatmap();
|
||||||
|
|
||||||
|
LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareBeatmap()
|
||||||
|
{
|
||||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||||
Beatmap.Value.BeatmapInfo.EpilepsyWarning = epilepsyWarning;
|
Beatmap.Value.BeatmapInfo.EpilepsyWarning = epilepsyWarning;
|
||||||
|
|
||||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>())
|
foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>())
|
||||||
mod.ApplyToTrack(Beatmap.Value.Track);
|
mod.ApplyToTrack(Beatmap.Value.Track);
|
||||||
|
|
||||||
LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -178,10 +183,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddStep("load slow dummy beatmap", () =>
|
AddStep("load slow dummy beatmap", () =>
|
||||||
{
|
{
|
||||||
LoadScreen(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
|
prepareBeatmap();
|
||||||
Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000);
|
slowPlayer = new SlowLoadPlayer(false, false);
|
||||||
|
LoadScreen(loader = new TestPlayerLoader(() => slowPlayer));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddStep("schedule slow load", () => Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000));
|
||||||
|
|
||||||
AddUntilStep("wait for player to be current", () => slowPlayer.IsCurrentScreen());
|
AddUntilStep("wait for player to be current", () => slowPlayer.IsCurrentScreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
CreateTest(null);
|
CreateTest(null);
|
||||||
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||||
AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space));
|
AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space));
|
||||||
AddAssert("score shown", () => Player.IsScoreShown);
|
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
|
||||||
|
AddUntilStep("time less than storyboard duration", () => Player.GameplayClockContainer.GameplayClock.CurrentTime < currentStoryboardDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||||
|
|
||||||
manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait();
|
manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait();
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
|
@ -73,8 +73,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
for (int i = 0; i < users; i++)
|
for (int i = 0; i < users; i++)
|
||||||
spectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
spectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
||||||
|
|
||||||
Client.CurrentMatchPlayingUserIds.Clear();
|
spectatorClient.Schedule(() =>
|
||||||
Client.CurrentMatchPlayingUserIds.AddRange(spectatorClient.PlayingUsers);
|
{
|
||||||
|
Client.CurrentMatchPlayingUserIds.Clear();
|
||||||
|
Client.CurrentMatchPlayingUserIds.AddRange(spectatorClient.PlayingUsers);
|
||||||
|
});
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -91,6 +94,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
||||||
|
AddUntilStep("wait for user population", () => Client.CurrentMatchPlayingUserIds.Count > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||||
|
|
||||||
beatmaps = new List<BeatmapInfo>();
|
beatmaps = new List<BeatmapInfo>();
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
||||||
|
|
||||||
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
|
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
|
||||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
||||||
|
|
||||||
Add(beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker
|
Add(beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker
|
||||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||||
|
|
||||||
var beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = selectedItem } };
|
var beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = selectedItem } };
|
||||||
base.Content.Add(beatmapTracker);
|
base.Content.Add(beatmapTracker);
|
||||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||||
|
|
||||||
var beatmaps = new List<BeatmapInfo>();
|
var beatmaps = new List<BeatmapInfo>();
|
||||||
|
|
||||||
|
43
osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs
Normal file
43
osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Wiki;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Online
|
||||||
|
{
|
||||||
|
public class TestSceneWikiMainPage : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange);
|
||||||
|
|
||||||
|
public TestSceneWikiMainPage()
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = overlayColour.Background5,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new BasicScrollContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(20),
|
||||||
|
Child = new WikiMainPage
|
||||||
|
{
|
||||||
|
Markdown = main_page_markdown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// From https://osu.ppy.sh/api/v2/wiki/en/Main_Page
|
||||||
|
private const string main_page_markdown =
|
||||||
|
"---\nlayout: main_page\n---\n\n<!-- Do not add any empty lines inside this div. -->\n\n<div class=\"wiki-main-page__blurb\">\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n</div>\n\n<div class=\"wiki-main-page__panels\">\n<div class=\"wiki-main-page-panel wiki-main-page-panel--full\">\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n</div>\n</div>\n";
|
||||||
|
}
|
||||||
|
}
|
175
osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs
Normal file
175
osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Markdig.Syntax.Inlines;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Containers.Markdown;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics.Containers.Markdown;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Wiki.Markdown;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Online
|
||||||
|
{
|
||||||
|
public class TestSceneWikiMarkdownContainer : OsuTestScene
|
||||||
|
{
|
||||||
|
private TestMarkdownContainer markdownContainer;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange);
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = overlayColour.Background5,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new BasicScrollContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(20),
|
||||||
|
Child = markdownContainer = new TestMarkdownContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLink()
|
||||||
|
{
|
||||||
|
AddStep("set current path", () => markdownContainer.CurrentPath = "Article_styling_criteria/");
|
||||||
|
|
||||||
|
AddStep("set '/wiki/Main_Page''", () => markdownContainer.Text = "[wiki main page](/wiki/Main_Page)");
|
||||||
|
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Main_Page");
|
||||||
|
|
||||||
|
AddStep("set '../FAQ''", () => markdownContainer.Text = "[FAQ](../FAQ)");
|
||||||
|
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/FAQ");
|
||||||
|
|
||||||
|
AddStep("set './Writing''", () => markdownContainer.Text = "[wiki writing guidline](./Writing)");
|
||||||
|
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/Writing");
|
||||||
|
|
||||||
|
AddStep("set 'Formatting''", () => markdownContainer.Text = "[wiki formatting guidline](Formatting)");
|
||||||
|
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/Formatting");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOutdatedNoticeBox()
|
||||||
|
{
|
||||||
|
AddStep("Add outdated yaml header", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.Text = @"---
|
||||||
|
outdated: true
|
||||||
|
---";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNeedsCleanupNoticeBox()
|
||||||
|
{
|
||||||
|
AddStep("Add needs cleanup yaml header", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.Text = @"---
|
||||||
|
needs_cleanup: true
|
||||||
|
---";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOnlyShowOutdatedNoticeBox()
|
||||||
|
{
|
||||||
|
AddStep("Add outdated and needs cleanup yaml", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.Text = @"---
|
||||||
|
outdated: true
|
||||||
|
needs_cleanup: true
|
||||||
|
---";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAbsoluteImage()
|
||||||
|
{
|
||||||
|
AddStep("Add absolute image", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
|
||||||
|
markdownContainer.Text = "![intro](/wiki/Interface/img/intro-screen.jpg)";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRelativeImage()
|
||||||
|
{
|
||||||
|
AddStep("Add relative image", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
|
||||||
|
markdownContainer.CurrentPath = "Interface/";
|
||||||
|
markdownContainer.Text = "![intro](img/intro-screen.jpg)";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBlockImage()
|
||||||
|
{
|
||||||
|
AddStep("Add paragraph with block image", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
|
||||||
|
markdownContainer.CurrentPath = "Interface/";
|
||||||
|
markdownContainer.Text = @"Line before image
|
||||||
|
|
||||||
|
![play menu](img/play-menu.jpg ""Main Menu in osu!"")
|
||||||
|
|
||||||
|
Line after image";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInlineImage()
|
||||||
|
{
|
||||||
|
AddStep("Add inline image", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
|
||||||
|
markdownContainer.Text = "![osu! mode icon](/wiki/shared/mode/osu.png) osu!";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestMarkdownContainer : WikiMarkdownContainer
|
||||||
|
{
|
||||||
|
public LinkInline Link;
|
||||||
|
|
||||||
|
public new string DocumentUrl
|
||||||
|
{
|
||||||
|
set => base.DocumentUrl = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override MarkdownTextFlowContainer CreateTextFlow() => new TestMarkdownTextFlowContainer
|
||||||
|
{
|
||||||
|
UrlAdded = link => Link = link,
|
||||||
|
};
|
||||||
|
|
||||||
|
private class TestMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer
|
||||||
|
{
|
||||||
|
public Action<LinkInline> UrlAdded;
|
||||||
|
|
||||||
|
protected override void AddLinkText(string text, LinkInline linkInline)
|
||||||
|
{
|
||||||
|
base.AddLinkText(text, linkInline);
|
||||||
|
|
||||||
|
UrlAdded?.Invoke(linkInline);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void AddImage(LinkInline linkInline) => AddDrawable(new WikiMarkdownImage(linkInline));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||||
|
|
||||||
manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait();
|
manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait();
|
||||||
|
|
||||||
|
@ -70,6 +70,7 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
AddStep("click first row", () =>
|
AddStep("click first row", () =>
|
||||||
{
|
{
|
||||||
firstRow = panel.ChildrenOfType<KeyBindingRow>().First();
|
firstRow = panel.ChildrenOfType<KeyBindingRow>().First();
|
||||||
|
|
||||||
InputManager.MoveMouseTo(firstRow);
|
InputManager.MoveMouseTo(firstRow);
|
||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
@ -138,6 +139,64 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleBindingResetButton()
|
||||||
|
{
|
||||||
|
KeyBindingRow settingsKeyBindingRow = null;
|
||||||
|
|
||||||
|
AddStep("click first row", () =>
|
||||||
|
{
|
||||||
|
settingsKeyBindingRow = panel.ChildrenOfType<KeyBindingRow>().First();
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(settingsKeyBindingRow);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.PressKey(Key.P);
|
||||||
|
InputManager.ReleaseKey(Key.P);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha > 0);
|
||||||
|
|
||||||
|
AddStep("click reset button for bindings", () =>
|
||||||
|
{
|
||||||
|
var resetButton = settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First();
|
||||||
|
|
||||||
|
resetButton.Click();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha == 0);
|
||||||
|
|
||||||
|
AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestResetAllBindingsButton()
|
||||||
|
{
|
||||||
|
KeyBindingRow settingsKeyBindingRow = null;
|
||||||
|
|
||||||
|
AddStep("click first row", () =>
|
||||||
|
{
|
||||||
|
settingsKeyBindingRow = panel.ChildrenOfType<KeyBindingRow>().First();
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(settingsKeyBindingRow);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.PressKey(Key.P);
|
||||||
|
InputManager.ReleaseKey(Key.P);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha > 0);
|
||||||
|
|
||||||
|
AddStep("click reset button for bindings", () =>
|
||||||
|
{
|
||||||
|
var resetButton = panel.ChildrenOfType<ResetButton>().First();
|
||||||
|
|
||||||
|
resetButton.Click();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha == 0);
|
||||||
|
|
||||||
|
AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0)));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestClickRowSelectsFirstBinding()
|
public void TestClickRowSelectsFirstBinding()
|
||||||
{
|
{
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Settings
|
namespace osu.Game.Tests.Visual.Settings
|
||||||
{
|
{
|
||||||
@ -37,7 +38,7 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
|
|
||||||
private class TestSettingsTextBox : SettingsTextBox
|
private class TestSettingsTextBox : SettingsTextBox
|
||||||
{
|
{
|
||||||
public new Drawable RestoreDefaultValueButton => this.ChildrenOfType<RestoreDefaultValueButton>().Single();
|
public Drawable RestoreDefaultValueButton => this.ChildrenOfType<RestoreDefaultValueButton<string>>().Single();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -786,9 +786,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkVisibleItemCount(bool diff, int count) =>
|
private void checkVisibleItemCount(bool diff, int count)
|
||||||
AddAssert($"{count} {(diff ? "diffs" : "sets")} visible", () =>
|
{
|
||||||
|
// until step required as we are querying against alive items, which are loaded asynchronously inside DrawableCarouselBeatmapSet.
|
||||||
|
AddUntilStep($"{count} {(diff ? "diffs" : "sets")} visible", () =>
|
||||||
carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count);
|
carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count);
|
||||||
|
}
|
||||||
|
|
||||||
private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null);
|
private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null);
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
private void load(GameHost host)
|
private void load(GameHost host)
|
||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default));
|
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default));
|
||||||
|
|
||||||
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, defaultBeatmap = Beatmap.Default));
|
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default));
|
||||||
|
|
||||||
Dependencies.Cache(music = new MusicController());
|
Dependencies.Cache(music = new MusicController());
|
||||||
|
|
||||||
@ -358,7 +358,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target));
|
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target));
|
||||||
|
|
||||||
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
|
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
|
||||||
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo == target);
|
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.Equals(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -390,7 +390,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddUntilStep("has correct ruleset", () => Ruleset.Value.ID == 0);
|
AddUntilStep("has correct ruleset", () => Ruleset.Value.ID == 0);
|
||||||
|
|
||||||
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
|
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
|
||||||
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo == target);
|
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.Equals(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -781,7 +781,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3);
|
AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3);
|
||||||
|
|
||||||
AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo == groupIcon.Items.First().Beatmap);
|
AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo.Equals(groupIcon.Items.First().Beatmap));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||||
|
|
||||||
dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
|
dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
|
||||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), dependencies.Get<GameHost>(), Beatmap.Default));
|
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory));
|
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory));
|
||||||
|
|
||||||
beatmap = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Beatmaps[0];
|
beatmap = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Beatmaps[0];
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
// 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 NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Dialog;
|
using osu.Game.Overlays.Dialog;
|
||||||
|
|
||||||
@ -11,13 +13,20 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneDialogOverlay : OsuTestScene
|
public class TestSceneDialogOverlay : OsuTestScene
|
||||||
{
|
{
|
||||||
public TestSceneDialogOverlay()
|
private DialogOverlay overlay;
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
{
|
{
|
||||||
DialogOverlay overlay;
|
AddStep("create dialog overlay", () => Child = overlay = new DialogOverlay());
|
||||||
|
}
|
||||||
|
|
||||||
Add(overlay = new DialogOverlay());
|
[Test]
|
||||||
|
public void TestBasic()
|
||||||
|
{
|
||||||
|
TestPopupDialog dialog = null;
|
||||||
|
|
||||||
AddStep("dialog #1", () => overlay.Push(new TestPopupDialog
|
AddStep("dialog #1", () => overlay.Push(dialog = new TestPopupDialog
|
||||||
{
|
{
|
||||||
Icon = FontAwesome.Regular.TrashAlt,
|
Icon = FontAwesome.Regular.TrashAlt,
|
||||||
HeaderText = @"Confirm deletion of",
|
HeaderText = @"Confirm deletion of",
|
||||||
@ -37,7 +46,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
AddStep("dialog #2", () => overlay.Push(new TestPopupDialog
|
AddAssert("first dialog displayed", () => overlay.CurrentDialog == dialog);
|
||||||
|
|
||||||
|
AddStep("dialog #2", () => overlay.Push(dialog = new TestPopupDialog
|
||||||
{
|
{
|
||||||
Icon = FontAwesome.Solid.Cog,
|
Icon = FontAwesome.Solid.Cog,
|
||||||
HeaderText = @"What do you want to do with",
|
HeaderText = @"What do you want to do with",
|
||||||
@ -70,6 +81,42 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
AddAssert("second dialog displayed", () => overlay.CurrentDialog == dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDismissBeforePush()
|
||||||
|
{
|
||||||
|
AddStep("dismissed dialog push", () =>
|
||||||
|
{
|
||||||
|
overlay.Push(new TestPopupDialog
|
||||||
|
{
|
||||||
|
State = { Value = Visibility.Hidden }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("no dialog pushed", () => overlay.CurrentDialog == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDismissBeforePushViaButtonPress()
|
||||||
|
{
|
||||||
|
AddStep("dismissed dialog push", () =>
|
||||||
|
{
|
||||||
|
TestPopupDialog dialog;
|
||||||
|
overlay.Push(dialog = new TestPopupDialog
|
||||||
|
{
|
||||||
|
Buttons = new PopupDialogButton[]
|
||||||
|
{
|
||||||
|
new PopupDialogOkButton { Text = @"OK" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.PerformOkAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("no dialog pushed", () => overlay.CurrentDialog == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestPopupDialog : PopupDialog
|
private class TestPopupDialog : PopupDialog
|
||||||
|
@ -86,6 +86,15 @@ _**italic with underscore, bold with asterisk**_";
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLinkWithTitle()
|
||||||
|
{
|
||||||
|
AddStep("Add Link with title", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.Text = "[wikipedia](https://www.wikipedia.org \"The Free Encyclopedia\")";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestInlineCode()
|
public void TestInlineCode()
|
||||||
{
|
{
|
||||||
|
@ -11,6 +11,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
@ -52,6 +53,8 @@ namespace osu.Game.Tests
|
|||||||
|
|
||||||
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
|
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
|
||||||
|
|
||||||
|
protected override ISkin GetSkin() => null;
|
||||||
|
|
||||||
public override Stream GetStream(string storagePath) => null;
|
public override Stream GetStream(string storagePath) => null;
|
||||||
|
|
||||||
protected override Track GetBeatmapTrack() => trackStore.Get(firstAudioFile);
|
protected override Track GetBeatmapTrack() => trackStore.Get(firstAudioFile);
|
||||||
|
@ -72,6 +72,7 @@ namespace osu.Game.Beatmaps
|
|||||||
private readonly RulesetStore rulesets;
|
private readonly RulesetStore rulesets;
|
||||||
private readonly BeatmapStore beatmaps;
|
private readonly BeatmapStore beatmaps;
|
||||||
private readonly AudioManager audioManager;
|
private readonly AudioManager audioManager;
|
||||||
|
private readonly IResourceStore<byte[]> resources;
|
||||||
private readonly LargeTextureStore largeTextureStore;
|
private readonly LargeTextureStore largeTextureStore;
|
||||||
private readonly ITrackStore trackStore;
|
private readonly ITrackStore trackStore;
|
||||||
|
|
||||||
@ -81,12 +82,13 @@ namespace osu.Game.Beatmaps
|
|||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
private readonly BeatmapOnlineLookupQueue onlineLookupQueue;
|
private readonly BeatmapOnlineLookupQueue onlineLookupQueue;
|
||||||
|
|
||||||
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, GameHost host = null,
|
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore<byte[]> resources, GameHost host = null,
|
||||||
WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false)
|
WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false)
|
||||||
: base(storage, contextFactory, api, new BeatmapStore(contextFactory), host)
|
: base(storage, contextFactory, api, new BeatmapStore(contextFactory), host)
|
||||||
{
|
{
|
||||||
this.rulesets = rulesets;
|
this.rulesets = rulesets;
|
||||||
this.audioManager = audioManager;
|
this.audioManager = audioManager;
|
||||||
|
this.resources = resources;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
|
|
||||||
DefaultBeatmap = defaultBeatmap;
|
DefaultBeatmap = defaultBeatmap;
|
||||||
@ -240,7 +242,7 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <param name="info">The <see cref="BeatmapInfo"/> to save the content against. The file referenced by <see cref="BeatmapInfo.Path"/> will be replaced.</param>
|
/// <param name="info">The <see cref="BeatmapInfo"/> to save the content against. The file referenced by <see cref="BeatmapInfo.Path"/> will be replaced.</param>
|
||||||
/// <param name="beatmapContent">The <see cref="IBeatmap"/> content to write.</param>
|
/// <param name="beatmapContent">The <see cref="IBeatmap"/> content to write.</param>
|
||||||
/// <param name="beatmapSkin">The beatmap <see cref="ISkin"/> content to write, null if to be omitted.</param>
|
/// <param name="beatmapSkin">The beatmap <see cref="ISkin"/> content to write, null if to be omitted.</param>
|
||||||
public void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null)
|
public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null)
|
||||||
{
|
{
|
||||||
var setInfo = info.BeatmapSet;
|
var setInfo = info.BeatmapSet;
|
||||||
|
|
||||||
@ -280,23 +282,17 @@ namespace osu.Game.Beatmaps
|
|||||||
/// Retrieve a <see cref="WorkingBeatmap"/> instance for the provided <see cref="BeatmapInfo"/>
|
/// Retrieve a <see cref="WorkingBeatmap"/> instance for the provided <see cref="BeatmapInfo"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="beatmapInfo">The beatmap to lookup.</param>
|
/// <param name="beatmapInfo">The beatmap to lookup.</param>
|
||||||
/// <param name="previous">The currently loaded <see cref="WorkingBeatmap"/>. Allows for optimisation where elements are shared with the new beatmap. May be returned if beatmapInfo requested matches</param>
|
|
||||||
/// <returns>A <see cref="WorkingBeatmap"/> instance correlating to the provided <see cref="BeatmapInfo"/>.</returns>
|
/// <returns>A <see cref="WorkingBeatmap"/> instance correlating to the provided <see cref="BeatmapInfo"/>.</returns>
|
||||||
public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null)
|
public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo)
|
||||||
{
|
{
|
||||||
if (beatmapInfo?.ID > 0 && previous != null && previous.BeatmapInfo?.ID == beatmapInfo.ID)
|
// if there are no files, presume the full beatmap info has not yet been fetched from the database.
|
||||||
return previous;
|
if (beatmapInfo?.BeatmapSet?.Files.Count == 0)
|
||||||
|
|
||||||
if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
|
|
||||||
return DefaultBeatmap;
|
|
||||||
|
|
||||||
if (beatmapInfo.BeatmapSet.Files == null)
|
|
||||||
{
|
{
|
||||||
var info = beatmapInfo;
|
int lookupId = beatmapInfo.ID;
|
||||||
beatmapInfo = QueryBeatmap(b => b.ID == info.ID);
|
beatmapInfo = QueryBeatmap(b => b.ID == lookupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (beatmapInfo == null)
|
if (beatmapInfo?.BeatmapSet == null)
|
||||||
return DefaultBeatmap;
|
return DefaultBeatmap;
|
||||||
|
|
||||||
lock (workingCache)
|
lock (workingCache)
|
||||||
@ -506,6 +502,7 @@ namespace osu.Game.Beatmaps
|
|||||||
ITrackStore IBeatmapResourceProvider.Tracks => trackStore;
|
ITrackStore IBeatmapResourceProvider.Tracks => trackStore;
|
||||||
AudioManager IStorageResourceProvider.AudioManager => audioManager;
|
AudioManager IStorageResourceProvider.AudioManager => audioManager;
|
||||||
IResourceStore<byte[]> IStorageResourceProvider.Files => Files.Store;
|
IResourceStore<byte[]> IStorageResourceProvider.Files => Files.Store;
|
||||||
|
IResourceStore<byte[]> IStorageResourceProvider.Resources => resources;
|
||||||
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host?.CreateTextureLoaderStore(underlyingStore);
|
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host?.CreateTextureLoaderStore(underlyingStore);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -526,6 +523,7 @@ namespace osu.Game.Beatmaps
|
|||||||
protected override IBeatmap GetBeatmap() => beatmap;
|
protected override IBeatmap GetBeatmap() => beatmap;
|
||||||
protected override Texture GetBackground() => null;
|
protected override Texture GetBackground() => null;
|
||||||
protected override Track GetBeatmapTrack() => null;
|
protected override Track GetBeatmapTrack() => null;
|
||||||
|
protected override ISkin GetSkin() => null;
|
||||||
public override Stream GetStream(string storagePath) => null;
|
public override Stream GetStream(string storagePath) => null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
|
|
||||||
@ -31,6 +32,9 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public List<BeatmapInfo> Beatmaps { get; set; }
|
public List<BeatmapInfo> Beatmaps { get; set; }
|
||||||
|
|
||||||
|
[NotNull]
|
||||||
|
public List<BeatmapSetFileInfo> Files { get; set; } = new List<BeatmapSetFileInfo>();
|
||||||
|
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public BeatmapSetOnlineInfo OnlineInfo { get; set; }
|
public BeatmapSetOnlineInfo OnlineInfo { get; set; }
|
||||||
|
|
||||||
@ -57,16 +61,14 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public string Hash { get; set; }
|
public string Hash { get; set; }
|
||||||
|
|
||||||
public string StoryboardFile => Files?.Find(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename;
|
public string StoryboardFile => Files.Find(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null.
|
/// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null.
|
||||||
/// The path returned is relative to the user file storage.
|
/// The path returned is relative to the user file storage.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filename">The name of the file to get the storage path of.</param>
|
/// <param name="filename">The name of the file to get the storage path of.</param>
|
||||||
public string GetPathForFile(string filename) => Files?.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath;
|
public string GetPathForFile(string filename) => Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath;
|
||||||
|
|
||||||
public List<BeatmapSetFileInfo> Files { get; set; }
|
|
||||||
|
|
||||||
public override string ToString() => Metadata?.ToString() ?? base.ToString();
|
public override string ToString() => Metadata?.ToString() ?? base.ToString();
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ using osu.Game.Rulesets.Difficulty;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
{
|
{
|
||||||
@ -49,6 +50,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
protected override Track GetBeatmapTrack() => GetVirtualTrack();
|
protected override Track GetBeatmapTrack() => GetVirtualTrack();
|
||||||
|
|
||||||
|
protected override ISkin GetSkin() => null;
|
||||||
|
|
||||||
public override Stream GetStream(string storagePath) => null;
|
public override Stream GetStream(string storagePath) => null;
|
||||||
|
|
||||||
private class DummyRulesetInfo : RulesetInfo
|
private class DummyRulesetInfo : RulesetInfo
|
||||||
|
@ -324,7 +324,7 @@ namespace osu.Game.Beatmaps
|
|||||||
public bool SkinLoaded => skin.IsResultAvailable;
|
public bool SkinLoaded => skin.IsResultAvailable;
|
||||||
public ISkin Skin => skin.Value;
|
public ISkin Skin => skin.Value;
|
||||||
|
|
||||||
protected virtual ISkin GetSkin() => new DefaultSkin(null);
|
protected abstract ISkin GetSkin();
|
||||||
private readonly RecyclableLazy<ISkin> skin;
|
private readonly RecyclableLazy<ISkin> skin;
|
||||||
|
|
||||||
public abstract Stream GetStream(string storagePath);
|
public abstract Stream GetStream(string storagePath);
|
||||||
|
@ -58,8 +58,13 @@ namespace osu.Game.Collections
|
|||||||
|
|
||||||
if (storage.Exists(database_name))
|
if (storage.Exists(database_name))
|
||||||
{
|
{
|
||||||
|
List<BeatmapCollection> beatmapCollections;
|
||||||
|
|
||||||
using (var stream = storage.GetStream(database_name))
|
using (var stream = storage.GetStream(database_name))
|
||||||
importCollections(readCollections(stream));
|
beatmapCollections = readCollections(stream);
|
||||||
|
|
||||||
|
// intentionally fire-and-forget async.
|
||||||
|
importCollections(beatmapCollections);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,11 +6,13 @@ using Markdig.Extensions.AutoIdentifiers;
|
|||||||
using Markdig.Extensions.Tables;
|
using Markdig.Extensions.Tables;
|
||||||
using Markdig.Extensions.Yaml;
|
using Markdig.Extensions.Yaml;
|
||||||
using Markdig.Syntax;
|
using Markdig.Syntax;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Containers.Markdown;
|
using osu.Framework.Graphics.Containers.Markdown;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Containers.Markdown
|
namespace osu.Game.Graphics.Containers.Markdown
|
||||||
{
|
{
|
||||||
@ -21,6 +23,16 @@ namespace osu.Game.Graphics.Containers.Markdown
|
|||||||
LineSpacing = 21;
|
LineSpacing = 21;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
|
{
|
||||||
|
var api = parent.Get<IAPIProvider>();
|
||||||
|
|
||||||
|
// needs to be set before the base BDL call executes to avoid invalidating any already populated markdown content.
|
||||||
|
DocumentUrl = api.WebsiteRootUrl;
|
||||||
|
|
||||||
|
return base.CreateChildDependencies(parent);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void AddMarkdownComponent(IMarkdownObject markdownObject, FillFlowContainer container, int level)
|
protected override void AddMarkdownComponent(IMarkdownObject markdownObject, FillFlowContainer container, int level)
|
||||||
{
|
{
|
||||||
switch (markdownObject)
|
switch (markdownObject)
|
||||||
|
@ -1,48 +1,63 @@
|
|||||||
// 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.Collections.Generic;
|
||||||
using Markdig.Syntax.Inlines;
|
using Markdig.Syntax.Inlines;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers.Markdown;
|
using osu.Framework.Graphics.Containers.Markdown;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Game.Online.Chat;
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Containers.Markdown
|
namespace osu.Game.Graphics.Containers.Markdown
|
||||||
{
|
{
|
||||||
public class OsuMarkdownLinkText : MarkdownLinkText
|
public class OsuMarkdownLinkText : MarkdownLinkText
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved(canBeNull: true)]
|
||||||
private OverlayColourProvider colourProvider { get; set; }
|
private OsuGame game { get; set; }
|
||||||
|
|
||||||
private SpriteText spriteText;
|
private readonly string text;
|
||||||
|
private readonly string title;
|
||||||
|
|
||||||
public OsuMarkdownLinkText(string text, LinkInline linkInline)
|
public OsuMarkdownLinkText(string text, LinkInline linkInline)
|
||||||
: base(text, linkInline)
|
: base(text, linkInline)
|
||||||
{
|
{
|
||||||
|
this.text = text;
|
||||||
|
title = linkInline.Title;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
spriteText.Colour = colourProvider.Light2;
|
var textDrawable = CreateSpriteText().With(t => t.Text = text);
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
textDrawable,
|
||||||
|
new OsuMarkdownLinkCompiler(new[] { textDrawable })
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Action = OnLinkPressed,
|
||||||
|
TooltipText = title ?? Url,
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SpriteText CreateSpriteText()
|
protected override void OnLinkPressed() => game?.HandleLink(Url);
|
||||||
{
|
|
||||||
return spriteText = base.CreateSpriteText();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
private class OsuMarkdownLinkCompiler : DrawableLinkCompiler
|
||||||
{
|
{
|
||||||
spriteText.Colour = colourProvider.Light1;
|
public OsuMarkdownLinkCompiler(IEnumerable<Drawable> parts)
|
||||||
return base.OnHover(e);
|
: base(parts)
|
||||||
}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
[BackgroundDependencyLoader]
|
||||||
{
|
private void load(OverlayColourProvider colourProvider)
|
||||||
spriteText.Colour = colourProvider.Light2;
|
{
|
||||||
base.OnHoverLost(e);
|
IdleColour = colourProvider.Light2;
|
||||||
|
HoverColour = colourProvider.Light1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,11 @@ namespace osu.Game.IO
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
IResourceStore<byte[]> Files { get; }
|
IResourceStore<byte[]> Files { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Access game-wide resources.
|
||||||
|
/// </summary>
|
||||||
|
IResourceStore<byte[]> Resources { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a texture loader store based on an underlying data store.
|
/// Create a texture loader store based on an underlying data store.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
38
osu.Game/Input/OsuUserInputManager.cs
Normal file
38
osu.Game/Input/OsuUserInputManager.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// 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 osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Input
|
||||||
|
{
|
||||||
|
public class OsuUserInputManager : UserInputManager
|
||||||
|
{
|
||||||
|
internal OsuUserInputManager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MouseButtonEventManager CreateButtonEventManagerFor(MouseButton button)
|
||||||
|
{
|
||||||
|
switch (button)
|
||||||
|
{
|
||||||
|
case MouseButton.Right:
|
||||||
|
return new RightMouseManager(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.CreateButtonEventManagerFor(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RightMouseManager : MouseButtonEventManager
|
||||||
|
{
|
||||||
|
public RightMouseManager(MouseButton button)
|
||||||
|
: base(button)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool EnableDrag => true; // allow right-mouse dragging for absolute scroll in scroll containers.
|
||||||
|
public override bool EnableClick => false;
|
||||||
|
public override bool ChangeFocusOnClick => false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -235,6 +235,9 @@ namespace osu.Game.Online.Spectator
|
|||||||
{
|
{
|
||||||
Debug.Assert(ThreadSafety.IsUpdateThread);
|
Debug.Assert(ThreadSafety.IsUpdateThread);
|
||||||
|
|
||||||
|
if (!IsPlaying)
|
||||||
|
return;
|
||||||
|
|
||||||
if (frame is IConvertibleReplayFrame convertible)
|
if (frame is IConvertibleReplayFrame convertible)
|
||||||
pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap));
|
pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap));
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -41,7 +40,6 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
using osuTK.Input;
|
|
||||||
using RuntimeInfo = osu.Framework.RuntimeInfo;
|
using RuntimeInfo = osu.Framework.RuntimeInfo;
|
||||||
|
|
||||||
namespace osu.Game
|
namespace osu.Game
|
||||||
@ -51,78 +49,19 @@ namespace osu.Game
|
|||||||
/// Unlike <see cref="OsuGame"/>, this class will not load any kind of UI, allowing it to be used
|
/// Unlike <see cref="OsuGame"/>, this class will not load any kind of UI, allowing it to be used
|
||||||
/// for provide dependencies to test cases without interfering with them.
|
/// for provide dependencies to test cases without interfering with them.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class OsuGameBase : Framework.Game, ICanAcceptFiles
|
public partial class OsuGameBase : Framework.Game, ICanAcceptFiles
|
||||||
{
|
{
|
||||||
public const string CLIENT_STREAM_NAME = "lazer";
|
public const string CLIENT_STREAM_NAME = @"lazer";
|
||||||
|
|
||||||
public const int SAMPLE_CONCURRENCY = 6;
|
public const int SAMPLE_CONCURRENCY = 6;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects.
|
||||||
|
/// </summary>
|
||||||
|
internal const double GLOBAL_TRACK_VOLUME_ADJUST = 0.5;
|
||||||
|
|
||||||
public bool UseDevelopmentServer { get; }
|
public bool UseDevelopmentServer { get; }
|
||||||
|
|
||||||
protected OsuConfigManager LocalConfig;
|
|
||||||
|
|
||||||
protected SessionStatics SessionStatics { get; private set; }
|
|
||||||
|
|
||||||
protected BeatmapManager BeatmapManager;
|
|
||||||
|
|
||||||
protected ScoreManager ScoreManager;
|
|
||||||
|
|
||||||
protected BeatmapDifficultyCache DifficultyCache;
|
|
||||||
|
|
||||||
protected UserLookupCache UserCache;
|
|
||||||
|
|
||||||
protected SkinManager SkinManager;
|
|
||||||
|
|
||||||
protected RulesetStore RulesetStore;
|
|
||||||
|
|
||||||
protected FileStore FileStore;
|
|
||||||
|
|
||||||
protected KeyBindingStore KeyBindingStore;
|
|
||||||
|
|
||||||
protected SettingsStore SettingsStore;
|
|
||||||
|
|
||||||
protected RulesetConfigCache RulesetConfigCache;
|
|
||||||
|
|
||||||
protected IAPIProvider API;
|
|
||||||
|
|
||||||
private SpectatorClient spectatorClient;
|
|
||||||
private MultiplayerClient multiplayerClient;
|
|
||||||
|
|
||||||
protected MenuCursorContainer MenuCursorContainer;
|
|
||||||
|
|
||||||
protected MusicController MusicController;
|
|
||||||
|
|
||||||
private Container content;
|
|
||||||
|
|
||||||
protected override Container<Drawable> Content => content;
|
|
||||||
|
|
||||||
protected Storage Storage { get; set; }
|
|
||||||
|
|
||||||
[Cached]
|
|
||||||
[Cached(typeof(IBindable<RulesetInfo>))]
|
|
||||||
protected readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The current mod selection for the local user.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// If a mod select overlay is present, mod instances set to this value are not guaranteed to remain as the provided instance and will be overwritten by a copy.
|
|
||||||
/// In such a case, changes to settings of a mod will *not* propagate after a mod is added to this collection.
|
|
||||||
/// As such, all settings should be finalised before adding a mod to this collection.
|
|
||||||
/// </remarks>
|
|
||||||
[Cached]
|
|
||||||
[Cached(typeof(IBindable<IReadOnlyList<Mod>>))]
|
|
||||||
protected readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Mods available for the current <see cref="Ruleset"/>.
|
|
||||||
/// </summary>
|
|
||||||
public readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> AvailableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
|
|
||||||
|
|
||||||
protected Bindable<WorkingBeatmap> Beatmap { get; private set; } // cached via load() method
|
|
||||||
|
|
||||||
private Bindable<bool> fpsDisplayVisible;
|
|
||||||
|
|
||||||
public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version();
|
public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -144,30 +83,83 @@ namespace osu.Game
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected OsuConfigManager LocalConfig { get; private set; }
|
||||||
|
|
||||||
|
protected SessionStatics SessionStatics { get; private set; }
|
||||||
|
|
||||||
|
protected BeatmapManager BeatmapManager { get; private set; }
|
||||||
|
|
||||||
|
protected ScoreManager ScoreManager { get; private set; }
|
||||||
|
|
||||||
|
protected SkinManager SkinManager { get; private set; }
|
||||||
|
|
||||||
|
protected RulesetStore RulesetStore { get; private set; }
|
||||||
|
|
||||||
|
protected KeyBindingStore KeyBindingStore { get; private set; }
|
||||||
|
|
||||||
|
protected MenuCursorContainer MenuCursorContainer { get; private set; }
|
||||||
|
|
||||||
|
protected MusicController MusicController { get; private set; }
|
||||||
|
|
||||||
|
protected IAPIProvider API { get; set; }
|
||||||
|
|
||||||
|
protected Storage Storage { get; set; }
|
||||||
|
|
||||||
|
protected Bindable<WorkingBeatmap> Beatmap { get; private set; } // cached via load() method
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
[Cached(typeof(IBindable<RulesetInfo>))]
|
||||||
|
protected readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current mod selection for the local user.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If a mod select overlay is present, mod instances set to this value are not guaranteed to remain as the provided instance and will be overwritten by a copy.
|
||||||
|
/// In such a case, changes to settings of a mod will *not* propagate after a mod is added to this collection.
|
||||||
|
/// As such, all settings should be finalised before adding a mod to this collection.
|
||||||
|
/// </remarks>
|
||||||
|
[Cached]
|
||||||
|
[Cached(typeof(IBindable<IReadOnlyList<Mod>>))]
|
||||||
|
protected readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mods available for the current <see cref="Ruleset"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> AvailableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
|
||||||
|
|
||||||
|
private BeatmapDifficultyCache difficultyCache;
|
||||||
|
|
||||||
|
private UserLookupCache userCache;
|
||||||
|
|
||||||
|
private FileStore fileStore;
|
||||||
|
|
||||||
|
private SettingsStore settingsStore;
|
||||||
|
|
||||||
|
private RulesetConfigCache rulesetConfigCache;
|
||||||
|
|
||||||
|
private SpectatorClient spectatorClient;
|
||||||
|
|
||||||
|
private MultiplayerClient multiplayerClient;
|
||||||
|
|
||||||
|
private DatabaseContextFactory contextFactory;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => content;
|
||||||
|
|
||||||
|
private Container content;
|
||||||
|
|
||||||
|
private DependencyContainer dependencies;
|
||||||
|
|
||||||
|
private Bindable<bool> fpsDisplayVisible;
|
||||||
|
|
||||||
|
private readonly BindableNumber<double> globalTrackVolumeAdjust = new BindableNumber<double>(GLOBAL_TRACK_VOLUME_ADJUST);
|
||||||
|
|
||||||
public OsuGameBase()
|
public OsuGameBase()
|
||||||
{
|
{
|
||||||
UseDevelopmentServer = DebugUtils.IsDebugBuild;
|
UseDevelopmentServer = DebugUtils.IsDebugBuild;
|
||||||
Name = @"osu!lazer";
|
Name = @"osu!lazer";
|
||||||
}
|
}
|
||||||
|
|
||||||
private DependencyContainer dependencies;
|
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
|
||||||
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
|
||||||
|
|
||||||
private DatabaseContextFactory contextFactory;
|
|
||||||
|
|
||||||
protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager();
|
|
||||||
|
|
||||||
protected virtual BatteryInfo CreateBatteryInfo() => null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects.
|
|
||||||
/// </summary>
|
|
||||||
internal const double GLOBAL_TRACK_VOLUME_ADJUST = 0.5;
|
|
||||||
|
|
||||||
private readonly BindableNumber<double> globalTrackVolumeAdjust = new BindableNumber<double>(GLOBAL_TRACK_VOLUME_ADJUST);
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
@ -217,7 +209,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
runMigrations();
|
runMigrations();
|
||||||
|
|
||||||
dependencies.Cache(SkinManager = new SkinManager(Storage, contextFactory, Host, Audio, new NamespacedResourceStore<byte[]>(Resources, "Skins/Legacy")));
|
dependencies.Cache(SkinManager = new SkinManager(Storage, contextFactory, Host, Resources, Audio));
|
||||||
dependencies.CacheAs<ISkinSource>(SkinManager);
|
dependencies.CacheAs<ISkinSource>(SkinManager);
|
||||||
|
|
||||||
// needs to be done here rather than inside SkinManager to ensure thread safety of CurrentSkinInfo.
|
// needs to be done here rather than inside SkinManager to ensure thread safety of CurrentSkinInfo.
|
||||||
@ -246,11 +238,11 @@ namespace osu.Game
|
|||||||
var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures);
|
var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures);
|
||||||
|
|
||||||
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory, Storage));
|
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory, Storage));
|
||||||
dependencies.Cache(FileStore = new FileStore(contextFactory, Storage));
|
dependencies.Cache(fileStore = new FileStore(contextFactory, Storage));
|
||||||
|
|
||||||
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
||||||
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => DifficultyCache, LocalConfig));
|
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => difficultyCache, LocalConfig));
|
||||||
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap, true));
|
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, true));
|
||||||
|
|
||||||
// this should likely be moved to ArchiveModelManager when another case appers where it is necessary
|
// this should likely be moved to ArchiveModelManager when another case appers where it is necessary
|
||||||
// to have inter-dependent model managers. this could be obtained with an IHasForeign<T> interface to
|
// to have inter-dependent model managers. this could be obtained with an IHasForeign<T> interface to
|
||||||
@ -273,19 +265,19 @@ namespace osu.Game
|
|||||||
ScoreManager.Undelete(getBeatmapScores(item), true);
|
ScoreManager.Undelete(getBeatmapScores(item), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
dependencies.Cache(DifficultyCache = new BeatmapDifficultyCache());
|
dependencies.Cache(difficultyCache = new BeatmapDifficultyCache());
|
||||||
AddInternal(DifficultyCache);
|
AddInternal(difficultyCache);
|
||||||
|
|
||||||
dependencies.Cache(UserCache = new UserLookupCache());
|
dependencies.Cache(userCache = new UserLookupCache());
|
||||||
AddInternal(UserCache);
|
AddInternal(userCache);
|
||||||
|
|
||||||
var scorePerformanceManager = new ScorePerformanceCache();
|
var scorePerformanceManager = new ScorePerformanceCache();
|
||||||
dependencies.Cache(scorePerformanceManager);
|
dependencies.Cache(scorePerformanceManager);
|
||||||
AddInternal(scorePerformanceManager);
|
AddInternal(scorePerformanceManager);
|
||||||
|
|
||||||
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
|
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
|
||||||
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
|
dependencies.Cache(settingsStore = new SettingsStore(contextFactory));
|
||||||
dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore));
|
dependencies.Cache(rulesetConfigCache = new RulesetConfigCache(settingsStore));
|
||||||
|
|
||||||
var powerStatus = CreateBatteryInfo();
|
var powerStatus = CreateBatteryInfo();
|
||||||
if (powerStatus != null)
|
if (powerStatus != null)
|
||||||
@ -308,7 +300,7 @@ namespace osu.Game
|
|||||||
dependencies.CacheAs<IBindable<WorkingBeatmap>>(Beatmap);
|
dependencies.CacheAs<IBindable<WorkingBeatmap>>(Beatmap);
|
||||||
dependencies.CacheAs(Beatmap);
|
dependencies.CacheAs(Beatmap);
|
||||||
|
|
||||||
FileStore.Cleanup();
|
fileStore.Cleanup();
|
||||||
|
|
||||||
// add api components to hierarchy.
|
// add api components to hierarchy.
|
||||||
if (API is APIAccess apiAccess)
|
if (API is APIAccess apiAccess)
|
||||||
@ -316,7 +308,7 @@ namespace osu.Game
|
|||||||
AddInternal(spectatorClient);
|
AddInternal(spectatorClient);
|
||||||
AddInternal(multiplayerClient);
|
AddInternal(multiplayerClient);
|
||||||
|
|
||||||
AddInternal(RulesetConfigCache);
|
AddInternal(rulesetConfigCache);
|
||||||
|
|
||||||
GlobalActionContainer globalBindings;
|
GlobalActionContainer globalBindings;
|
||||||
|
|
||||||
@ -344,24 +336,6 @@ namespace osu.Game
|
|||||||
Ruleset.BindValueChanged(onRulesetChanged);
|
Ruleset.BindValueChanged(onRulesetChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> r)
|
|
||||||
{
|
|
||||||
var dict = new Dictionary<ModType, IReadOnlyList<Mod>>();
|
|
||||||
|
|
||||||
if (r.NewValue?.Available == true)
|
|
||||||
{
|
|
||||||
foreach (ModType type in Enum.GetValues(typeof(ModType)))
|
|
||||||
dict[type] = r.NewValue.CreateInstance().GetModsFor(type).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!SelectedMods.Disabled)
|
|
||||||
SelectedMods.Value = Array.Empty<Mod>();
|
|
||||||
|
|
||||||
AvailableMods.Value = dict;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual Container CreateScalingContainer() => new DrawSizePreservingFillContainer();
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -375,28 +349,8 @@ namespace osu.Game
|
|||||||
FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None;
|
FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runMigrations()
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
||||||
{
|
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||||
try
|
|
||||||
{
|
|
||||||
using (var db = contextFactory.GetForWrite(false))
|
|
||||||
db.Context.Migrate();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.Error(e.InnerException ?? e, "Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database);
|
|
||||||
|
|
||||||
// if we failed, let's delete the database and start fresh.
|
|
||||||
// todo: we probably want a better (non-destructive) migrations/recovery process at a later point than this.
|
|
||||||
contextFactory.ResetDatabase();
|
|
||||||
|
|
||||||
Logger.Log("Database purged successfully.", LoggingTarget.Database);
|
|
||||||
|
|
||||||
// only run once more, then hard bail.
|
|
||||||
using (var db = contextFactory.GetForWrite(false))
|
|
||||||
db.Context.Migrate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetHost(GameHost host)
|
public override void SetHost(GameHost host)
|
||||||
{
|
{
|
||||||
@ -422,51 +376,59 @@ namespace osu.Game
|
|||||||
Scheduler.AddDelayed(GracefullyExit, 2000);
|
Scheduler.AddDelayed(GracefullyExit, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Migrate(string path)
|
||||||
|
{
|
||||||
|
contextFactory.FlushConnections();
|
||||||
|
(Storage as OsuStorage)?.Migrate(Host.GetStorage(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager();
|
||||||
|
|
||||||
|
protected virtual BatteryInfo CreateBatteryInfo() => null;
|
||||||
|
|
||||||
|
protected virtual Container CreateScalingContainer() => new DrawSizePreservingFillContainer();
|
||||||
|
|
||||||
protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage);
|
protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage);
|
||||||
|
|
||||||
private readonly List<ICanAcceptFiles> fileImporters = new List<ICanAcceptFiles>();
|
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> r)
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Register a global handler for file imports. Most recently registered will have precedence.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="handler">The handler to register.</param>
|
|
||||||
public void RegisterImportHandler(ICanAcceptFiles handler) => fileImporters.Insert(0, handler);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unregister a global handler for file imports.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="handler">The previously registered handler.</param>
|
|
||||||
public void UnregisterImportHandler(ICanAcceptFiles handler) => fileImporters.Remove(handler);
|
|
||||||
|
|
||||||
public async Task Import(params string[] paths)
|
|
||||||
{
|
{
|
||||||
if (paths.Length == 0)
|
var dict = new Dictionary<ModType, IReadOnlyList<Mod>>();
|
||||||
return;
|
|
||||||
|
|
||||||
var filesPerExtension = paths.GroupBy(p => Path.GetExtension(p).ToLowerInvariant());
|
if (r.NewValue?.Available == true)
|
||||||
|
|
||||||
foreach (var groups in filesPerExtension)
|
|
||||||
{
|
{
|
||||||
foreach (var importer in fileImporters)
|
foreach (ModType type in Enum.GetValues(typeof(ModType)))
|
||||||
{
|
dict[type] = r.NewValue.CreateInstance().GetModsFor(type).ToList();
|
||||||
if (importer.HandledExtensions.Contains(groups.Key))
|
}
|
||||||
await importer.Import(groups.ToArray()).ConfigureAwait(false);
|
|
||||||
}
|
if (!SelectedMods.Disabled)
|
||||||
|
SelectedMods.Value = Array.Empty<Mod>();
|
||||||
|
|
||||||
|
AvailableMods.Value = dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runMigrations()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var db = contextFactory.GetForWrite(false))
|
||||||
|
db.Context.Migrate();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error(e.InnerException ?? e, "Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database);
|
||||||
|
|
||||||
|
// if we failed, let's delete the database and start fresh.
|
||||||
|
// todo: we probably want a better (non-destructive) migrations/recovery process at a later point than this.
|
||||||
|
contextFactory.ResetDatabase();
|
||||||
|
|
||||||
|
Logger.Log("Database purged successfully.", LoggingTarget.Database);
|
||||||
|
|
||||||
|
// only run once more, then hard bail.
|
||||||
|
using (var db = contextFactory.GetForWrite(false))
|
||||||
|
db.Context.Migrate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual async Task Import(params ImportTask[] tasks)
|
|
||||||
{
|
|
||||||
var tasksPerExtension = tasks.GroupBy(t => Path.GetExtension(t.Path).ToLowerInvariant());
|
|
||||||
await Task.WhenAll(tasksPerExtension.Select(taskGroup =>
|
|
||||||
{
|
|
||||||
var importer = fileImporters.FirstOrDefault(i => i.HandledExtensions.Contains(taskGroup.Key));
|
|
||||||
return importer?.Import(taskGroup.ToArray()) ?? Task.CompletedTask;
|
|
||||||
})).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<string> HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions);
|
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
@ -477,37 +439,5 @@ namespace osu.Game
|
|||||||
|
|
||||||
contextFactory.FlushConnections();
|
contextFactory.FlushConnections();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class OsuUserInputManager : UserInputManager
|
|
||||||
{
|
|
||||||
protected override MouseButtonEventManager CreateButtonEventManagerFor(MouseButton button)
|
|
||||||
{
|
|
||||||
switch (button)
|
|
||||||
{
|
|
||||||
case MouseButton.Right:
|
|
||||||
return new RightMouseManager(button);
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.CreateButtonEventManagerFor(button);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class RightMouseManager : MouseButtonEventManager
|
|
||||||
{
|
|
||||||
public RightMouseManager(MouseButton button)
|
|
||||||
: base(button)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool EnableDrag => true; // allow right-mouse dragging for absolute scroll in scroll containers.
|
|
||||||
public override bool EnableClick => false;
|
|
||||||
public override bool ChangeFocusOnClick => false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Migrate(string path)
|
|
||||||
{
|
|
||||||
contextFactory.FlushConnections();
|
|
||||||
(Storage as OsuStorage)?.Migrate(Host.GetStorage(path));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
57
osu.Game/OsuGameBase_Importing.cs
Normal file
57
osu.Game/OsuGameBase_Importing.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// 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 System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Game.Database;
|
||||||
|
|
||||||
|
namespace osu.Game
|
||||||
|
{
|
||||||
|
public partial class OsuGameBase
|
||||||
|
{
|
||||||
|
private readonly List<ICanAcceptFiles> fileImporters = new List<ICanAcceptFiles>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register a global handler for file imports. Most recently registered will have precedence.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">The handler to register.</param>
|
||||||
|
public void RegisterImportHandler(ICanAcceptFiles handler) => fileImporters.Insert(0, handler);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregister a global handler for file imports.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">The previously registered handler.</param>
|
||||||
|
public void UnregisterImportHandler(ICanAcceptFiles handler) => fileImporters.Remove(handler);
|
||||||
|
|
||||||
|
public async Task Import(params string[] paths)
|
||||||
|
{
|
||||||
|
if (paths.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var filesPerExtension = paths.GroupBy(p => Path.GetExtension(p).ToLowerInvariant());
|
||||||
|
|
||||||
|
foreach (var groups in filesPerExtension)
|
||||||
|
{
|
||||||
|
foreach (var importer in fileImporters)
|
||||||
|
{
|
||||||
|
if (importer.HandledExtensions.Contains(groups.Key))
|
||||||
|
await importer.Import(groups.ToArray()).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task Import(params ImportTask[] tasks)
|
||||||
|
{
|
||||||
|
var tasksPerExtension = tasks.GroupBy(t => Path.GetExtension(t.Path).ToLowerInvariant());
|
||||||
|
await Task.WhenAll(tasksPerExtension.Select(taskGroup =>
|
||||||
|
{
|
||||||
|
var importer = fileImporters.FirstOrDefault(i => i.HandledExtensions.Contains(taskGroup.Key));
|
||||||
|
return importer?.Import(taskGroup.ToArray()) ?? Task.CompletedTask;
|
||||||
|
})).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions);
|
||||||
|
}
|
||||||
|
}
|
@ -95,6 +95,10 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We always want dialogs to show their appear animation, so we request they start hidden.
|
||||||
|
// Normally this would not be required, but is here due to the manual Show() call that occurs before LoadComplete().
|
||||||
|
protected override bool StartHidden => true;
|
||||||
|
|
||||||
protected PopupDialog()
|
protected PopupDialog()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
@ -205,8 +209,17 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// It's important we start in a visible state so our state fires on hide, even before load.
|
||||||
|
// This is used by the DialogOverlay to know when the dialog was dismissed.
|
||||||
|
Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Programmatically clicks the first <see cref="PopupDialogOkButton"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void PerformOkAction() => Buttons.OfType<PopupDialogOkButton>().First().Click();
|
||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
{
|
{
|
||||||
if (e.Repeat) return false;
|
if (e.Repeat) return false;
|
||||||
|
@ -35,15 +35,16 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
public void Push(PopupDialog dialog)
|
public void Push(PopupDialog dialog)
|
||||||
{
|
{
|
||||||
if (dialog == CurrentDialog) return;
|
if (dialog == CurrentDialog || dialog.State.Value != Visibility.Visible) return;
|
||||||
|
|
||||||
|
// if any existing dialog is being displayed, dismiss it before showing a new one.
|
||||||
CurrentDialog?.Hide();
|
CurrentDialog?.Hide();
|
||||||
|
|
||||||
CurrentDialog = dialog;
|
CurrentDialog = dialog;
|
||||||
|
CurrentDialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue);
|
||||||
|
|
||||||
dialogContainer.Add(CurrentDialog);
|
dialogContainer.Add(CurrentDialog);
|
||||||
|
|
||||||
CurrentDialog.Show();
|
|
||||||
CurrentDialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue);
|
|
||||||
Show();
|
Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System.Collections.Generic;
|
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.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -46,12 +47,19 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Container content;
|
||||||
|
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||||
|
content.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
public bool FilteringActive { get; set; }
|
public bool FilteringActive { get; set; }
|
||||||
|
|
||||||
private OsuSpriteText text;
|
private OsuSpriteText text;
|
||||||
private FillFlowContainer cancelAndClearButtons;
|
private FillFlowContainer cancelAndClearButtons;
|
||||||
private FillFlowContainer<KeyButton> buttons;
|
private FillFlowContainer<KeyButton> buttons;
|
||||||
|
|
||||||
|
private Bindable<bool> isDefault { get; } = new BindableBool(true);
|
||||||
|
|
||||||
public IEnumerable<string> FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text.ToString());
|
public IEnumerable<string> FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text.ToString());
|
||||||
|
|
||||||
public KeyBindingRow(object action, IEnumerable<Framework.Input.Bindings.KeyBinding> bindings)
|
public KeyBindingRow(object action, IEnumerable<Framework.Input.Bindings.KeyBinding> bindings)
|
||||||
@ -61,9 +69,6 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
Masking = true;
|
|
||||||
CornerRadius = padding;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
@ -72,51 +77,72 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
EdgeEffect = new EdgeEffectParameters
|
RelativeSizeAxes = Axes.X;
|
||||||
{
|
AutoSizeAxes = Axes.Y;
|
||||||
Radius = 2,
|
Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS };
|
||||||
Colour = colours.YellowDark.Opacity(0),
|
|
||||||
Type = EdgeEffectType.Shadow,
|
|
||||||
Hollow = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
new RestoreDefaultValueButton<bool>
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
Current = isDefault,
|
||||||
Colour = Color4.Black,
|
Action = RestoreDefaults,
|
||||||
Alpha = 0.6f,
|
|
||||||
},
|
|
||||||
text = new OsuSpriteText
|
|
||||||
{
|
|
||||||
Text = action.GetDescription(),
|
|
||||||
Margin = new MarginPadding(padding),
|
|
||||||
},
|
|
||||||
buttons = new FillFlowContainer<KeyButton>
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Anchor = Anchor.TopRight,
|
|
||||||
Origin = Anchor.TopRight
|
|
||||||
},
|
|
||||||
cancelAndClearButtons = new FillFlowContainer
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Padding = new MarginPadding(padding) { Top = height + padding * 2 },
|
|
||||||
Anchor = Anchor.TopRight,
|
|
||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
Alpha = 0,
|
},
|
||||||
Spacing = new Vector2(5),
|
content = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = padding,
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Radius = 2,
|
||||||
|
Colour = colours.YellowDark.Opacity(0),
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Hollow = true,
|
||||||
|
},
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new CancelButton { Action = finalise },
|
new Box
|
||||||
new ClearButton { Action = clear },
|
{
|
||||||
},
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.Black,
|
||||||
|
Alpha = 0.6f,
|
||||||
|
},
|
||||||
|
text = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = action.GetDescription(),
|
||||||
|
Margin = new MarginPadding(padding),
|
||||||
|
},
|
||||||
|
buttons = new FillFlowContainer<KeyButton>
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight
|
||||||
|
},
|
||||||
|
cancelAndClearButtons = new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(padding) { Top = height + padding * 2 },
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
Alpha = 0,
|
||||||
|
Spacing = new Vector2(5),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new CancelButton { Action = finalise },
|
||||||
|
new ClearButton { Action = clear },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var b in bindings)
|
foreach (var b in bindings)
|
||||||
buttons.Add(new KeyButton(b));
|
buttons.Add(new KeyButton(b));
|
||||||
|
|
||||||
|
updateIsDefaultValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RestoreDefaults()
|
public void RestoreDefaults()
|
||||||
@ -129,18 +155,20 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
button.UpdateKeyCombination(d);
|
button.UpdateKeyCombination(d);
|
||||||
store.Update(button.KeyBinding);
|
store.Update(button.KeyBinding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isDefault.Value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
FadeEdgeEffectTo(1, transition_time, Easing.OutQuint);
|
content.FadeEdgeEffectTo(1, transition_time, Easing.OutQuint);
|
||||||
|
|
||||||
return base.OnHover(e);
|
return base.OnHover(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
{
|
{
|
||||||
FadeEdgeEffectTo(0, transition_time, Easing.OutQuint);
|
content.FadeEdgeEffectTo(0, transition_time, Easing.OutQuint);
|
||||||
|
|
||||||
base.OnHoverLost(e);
|
base.OnHoverLost(e);
|
||||||
}
|
}
|
||||||
@ -288,6 +316,8 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
{
|
{
|
||||||
store.Update(bindTarget.KeyBinding);
|
store.Update(bindTarget.KeyBinding);
|
||||||
|
|
||||||
|
updateIsDefaultValue();
|
||||||
|
|
||||||
bindTarget.IsBinding = false;
|
bindTarget.IsBinding = false;
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
@ -305,8 +335,8 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
|
|
||||||
protected override void OnFocus(FocusEvent e)
|
protected override void OnFocus(FocusEvent e)
|
||||||
{
|
{
|
||||||
AutoSizeDuration = 500;
|
content.AutoSizeDuration = 500;
|
||||||
AutoSizeEasing = Easing.OutQuint;
|
content.AutoSizeEasing = Easing.OutQuint;
|
||||||
|
|
||||||
cancelAndClearButtons.FadeIn(300, Easing.OutQuint);
|
cancelAndClearButtons.FadeIn(300, Easing.OutQuint);
|
||||||
cancelAndClearButtons.BypassAutoSizeAxes &= ~Axes.Y;
|
cancelAndClearButtons.BypassAutoSizeAxes &= ~Axes.Y;
|
||||||
@ -331,6 +361,11 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
if (bindTarget != null) bindTarget.IsBinding = true;
|
if (bindTarget != null) bindTarget.IsBinding = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateIsDefaultValue()
|
||||||
|
{
|
||||||
|
isDefault.Value = bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults);
|
||||||
|
}
|
||||||
|
|
||||||
private class CancelButton : TriangleButton
|
private class CancelButton : TriangleButton
|
||||||
{
|
{
|
||||||
public CancelButton()
|
public CancelButton()
|
||||||
@ -379,9 +414,6 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
|
|
||||||
Margin = new MarginPadding(padding);
|
Margin = new MarginPadding(padding);
|
||||||
|
|
||||||
// todo: use this in a meaningful way
|
|
||||||
// var isDefault = keyBinding.Action is Enum;
|
|
||||||
|
|
||||||
Masking = true;
|
Masking = true;
|
||||||
CornerRadius = padding;
|
CornerRadius = padding;
|
||||||
|
|
||||||
|
@ -61,8 +61,11 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
{
|
{
|
||||||
Text = "Reset all bindings in section";
|
Text = "Reset all bindings in section";
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Margin = new MarginPadding { Top = 5 };
|
Width = 0.5f;
|
||||||
Height = 20;
|
Anchor = Anchor.TopCentre;
|
||||||
|
Origin = Anchor.TopCentre;
|
||||||
|
Margin = new MarginPadding { Top = 15 };
|
||||||
|
Height = 30;
|
||||||
|
|
||||||
Content.CornerRadius = 5;
|
Content.CornerRadius = 5;
|
||||||
}
|
}
|
||||||
|
@ -252,7 +252,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
if (playable != null)
|
if (playable != null)
|
||||||
{
|
{
|
||||||
changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value));
|
changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First()));
|
||||||
restartTrack();
|
restartTrack();
|
||||||
return PreviousTrackResult.Previous;
|
return PreviousTrackResult.Previous;
|
||||||
}
|
}
|
||||||
@ -283,7 +283,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
if (playable != null)
|
if (playable != null)
|
||||||
{
|
{
|
||||||
changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value));
|
changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First()));
|
||||||
restartTrack();
|
restartTrack();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,13 @@
|
|||||||
// 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API;
|
|
||||||
using osu.Game.Online.API.Requests;
|
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -20,26 +19,16 @@ namespace osu.Game.Overlays.News.Displays
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ArticleListing : CompositeDrawable
|
public class ArticleListing : CompositeDrawable
|
||||||
{
|
{
|
||||||
public Action<APINewsSidebar> SidebarMetadataUpdated;
|
private readonly Action fetchMorePosts;
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private IAPIProvider api { get; set; }
|
|
||||||
|
|
||||||
private FillFlowContainer content;
|
private FillFlowContainer content;
|
||||||
private ShowMoreButton showMore;
|
private ShowMoreButton showMore;
|
||||||
|
|
||||||
private GetNewsRequest request;
|
private CancellationTokenSource cancellationToken;
|
||||||
private Cursor lastCursor;
|
|
||||||
|
|
||||||
private readonly int? year;
|
public ArticleListing(Action fetchMorePosts)
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Instantiate a listing for the specified year.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="year">The year to load articles from. If null, will show the most recent articles.</param>
|
|
||||||
public ArticleListing(int? year = null)
|
|
||||||
{
|
{
|
||||||
this.year = year;
|
this.fetchMorePosts = fetchMorePosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -47,6 +36,7 @@ namespace osu.Game.Overlays.News.Displays
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
Padding = new MarginPadding
|
Padding = new MarginPadding
|
||||||
{
|
{
|
||||||
Vertical = 20,
|
Vertical = 20,
|
||||||
@ -75,53 +65,25 @@ namespace osu.Game.Overlays.News.Displays
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
Margin = new MarginPadding
|
Margin = new MarginPadding { Top = 15 },
|
||||||
{
|
Action = fetchMorePosts,
|
||||||
Top = 15
|
|
||||||
},
|
|
||||||
Action = performFetch,
|
|
||||||
Alpha = 0
|
Alpha = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
performFetch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void performFetch()
|
public void AddPosts(IEnumerable<APINewsPost> posts, bool morePostsAvailable) => Schedule(() =>
|
||||||
{
|
LoadComponentsAsync(posts.Select(p => new NewsCard(p)).ToList(), loaded =>
|
||||||
request?.Cancel();
|
|
||||||
|
|
||||||
request = new GetNewsRequest(year, lastCursor);
|
|
||||||
request.Success += response => Schedule(() => onSuccess(response));
|
|
||||||
api.PerformAsync(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CancellationTokenSource cancellationToken;
|
|
||||||
|
|
||||||
private void onSuccess(GetNewsResponse response)
|
|
||||||
{
|
|
||||||
cancellationToken?.Cancel();
|
|
||||||
|
|
||||||
// only needs to be updated on the initial load, as the content won't change during pagination.
|
|
||||||
if (lastCursor == null)
|
|
||||||
SidebarMetadataUpdated?.Invoke(response.SidebarMetadata);
|
|
||||||
|
|
||||||
// store cursor for next pagination request.
|
|
||||||
lastCursor = response.Cursor;
|
|
||||||
|
|
||||||
LoadComponentsAsync(response.NewsPosts.Select(p => new NewsCard(p)).ToList(), loaded =>
|
|
||||||
{
|
{
|
||||||
content.AddRange(loaded);
|
content.AddRange(loaded);
|
||||||
|
|
||||||
showMore.IsLoading = false;
|
showMore.IsLoading = false;
|
||||||
showMore.Alpha = response.Cursor != null ? 1 : 0;
|
showMore.Alpha = morePostsAvailable ? 1 : 0;
|
||||||
}, (cancellationToken = new CancellationTokenSource()).Token);
|
}, (cancellationToken = new CancellationTokenSource()).Token)
|
||||||
}
|
);
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
request?.Cancel();
|
|
||||||
cancellationToken?.Cancel();
|
cancellationToken?.Cancel();
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using System.Threading;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Overlays.News;
|
using osu.Game.Overlays.News;
|
||||||
using osu.Game.Overlays.News.Displays;
|
using osu.Game.Overlays.News.Displays;
|
||||||
using osu.Game.Overlays.News.Sidebar;
|
using osu.Game.Overlays.News.Sidebar;
|
||||||
@ -14,13 +15,21 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
public class NewsOverlay : OnlineOverlay<NewsHeader>
|
public class NewsOverlay : OnlineOverlay<NewsHeader>
|
||||||
{
|
{
|
||||||
private readonly Bindable<string> article = new Bindable<string>(null);
|
private readonly Bindable<string> article = new Bindable<string>();
|
||||||
|
|
||||||
private readonly Container sidebarContainer;
|
private readonly Container sidebarContainer;
|
||||||
private readonly NewsSidebar sidebar;
|
private readonly NewsSidebar sidebar;
|
||||||
|
|
||||||
private readonly Container content;
|
private readonly Container content;
|
||||||
|
|
||||||
|
private GetNewsRequest request;
|
||||||
|
|
||||||
|
private Cursor lastCursor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The year currently being displayed. If null, the main listing is being displayed.
|
||||||
|
/// </summary>
|
||||||
|
private int? displayedYear;
|
||||||
|
|
||||||
private CancellationTokenSource cancellationToken;
|
private CancellationTokenSource cancellationToken;
|
||||||
|
|
||||||
private bool displayUpdateRequired = true;
|
private bool displayUpdateRequired = true;
|
||||||
@ -65,7 +74,13 @@ namespace osu.Game.Overlays
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
// should not be run until first pop-in to avoid requesting data before user views.
|
// should not be run until first pop-in to avoid requesting data before user views.
|
||||||
article.BindValueChanged(onArticleChanged);
|
article.BindValueChanged(a =>
|
||||||
|
{
|
||||||
|
if (a.NewValue == null)
|
||||||
|
loadListing();
|
||||||
|
else
|
||||||
|
loadArticle(a.NewValue);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override NewsHeader CreateHeader() => new NewsHeader { ShowFrontPage = ShowFrontPage };
|
protected override NewsHeader CreateHeader() => new NewsHeader { ShowFrontPage = ShowFrontPage };
|
||||||
@ -95,7 +110,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
public void ShowYear(int year)
|
public void ShowYear(int year)
|
||||||
{
|
{
|
||||||
loadFrontPage(year);
|
loadListing(year);
|
||||||
Show();
|
Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +123,11 @@ namespace osu.Game.Overlays
|
|||||||
protected void LoadDisplay(Drawable display)
|
protected void LoadDisplay(Drawable display)
|
||||||
{
|
{
|
||||||
ScrollFlow.ScrollToStart();
|
ScrollFlow.ScrollToStart();
|
||||||
LoadComponentAsync(display, loaded => content.Child = loaded, (cancellationToken = new CancellationTokenSource()).Token);
|
LoadComponentAsync(display, loaded =>
|
||||||
|
{
|
||||||
|
content.Child = loaded;
|
||||||
|
Loading.Hide();
|
||||||
|
}, (cancellationToken = new CancellationTokenSource()).Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
@ -118,48 +137,65 @@ namespace osu.Game.Overlays
|
|||||||
sidebarContainer.Y = Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0));
|
sidebarContainer.Y = Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onArticleChanged(ValueChangedEvent<string> article)
|
private void loadListing(int? year = null)
|
||||||
{
|
{
|
||||||
if (article.NewValue == null)
|
|
||||||
loadFrontPage();
|
|
||||||
else
|
|
||||||
loadArticle(article.NewValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadFrontPage(int? year = null)
|
|
||||||
{
|
|
||||||
beginLoading();
|
|
||||||
|
|
||||||
Header.SetFrontPage();
|
Header.SetFrontPage();
|
||||||
|
|
||||||
var page = new ArticleListing(year);
|
displayedYear = year;
|
||||||
page.SidebarMetadataUpdated += metadata => Schedule(() =>
|
lastCursor = null;
|
||||||
|
|
||||||
|
beginLoading(true);
|
||||||
|
|
||||||
|
request = new GetNewsRequest(displayedYear);
|
||||||
|
request.Success += response => Schedule(() =>
|
||||||
{
|
{
|
||||||
sidebar.Metadata.Value = metadata;
|
lastCursor = response.Cursor;
|
||||||
Loading.Hide();
|
sidebar.Metadata.Value = response.SidebarMetadata;
|
||||||
|
|
||||||
|
var listing = new ArticleListing(getMorePosts);
|
||||||
|
listing.AddPosts(response.NewsPosts, response.Cursor != null);
|
||||||
|
LoadDisplay(listing);
|
||||||
});
|
});
|
||||||
LoadDisplay(page);
|
|
||||||
|
API.PerformAsync(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getMorePosts()
|
||||||
|
{
|
||||||
|
beginLoading(false);
|
||||||
|
|
||||||
|
request = new GetNewsRequest(displayedYear, lastCursor);
|
||||||
|
request.Success += response => Schedule(() =>
|
||||||
|
{
|
||||||
|
lastCursor = response.Cursor;
|
||||||
|
if (content.Child is ArticleListing listing)
|
||||||
|
listing.AddPosts(response.NewsPosts, response.Cursor != null);
|
||||||
|
});
|
||||||
|
|
||||||
|
API.PerformAsync(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadArticle(string article)
|
private void loadArticle(string article)
|
||||||
{
|
{
|
||||||
beginLoading();
|
// This is not yet implemented nor called from anywhere.
|
||||||
|
beginLoading(true);
|
||||||
|
|
||||||
Header.SetArticle(article);
|
Header.SetArticle(article);
|
||||||
|
|
||||||
// Temporary, should be handled by ArticleDisplay later
|
|
||||||
LoadDisplay(Empty());
|
LoadDisplay(Empty());
|
||||||
Loading.Hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beginLoading()
|
private void beginLoading(bool showLoadingOverlay)
|
||||||
{
|
{
|
||||||
|
request?.Cancel();
|
||||||
cancellationToken?.Cancel();
|
cancellationToken?.Cancel();
|
||||||
Loading.Show();
|
|
||||||
|
if (showLoadingOverlay)
|
||||||
|
Loading.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
|
request?.Cancel();
|
||||||
cancellationToken?.Cancel();
|
cancellationToken?.Cancel();
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
}
|
}
|
||||||
|
106
osu.Game/Overlays/RestoreDefaultValueButton.cs
Normal file
106
osu.Game/Overlays/RestoreDefaultValueButton.cs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays
|
||||||
|
{
|
||||||
|
public class RestoreDefaultValueButton<T> : OsuButton, IHasTooltip, IHasCurrentValue<T>
|
||||||
|
{
|
||||||
|
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
|
||||||
|
|
||||||
|
private readonly BindableWithCurrent<T> current = new BindableWithCurrent<T>();
|
||||||
|
|
||||||
|
// this is done to ensure a click on this button doesn't trigger focus on a parent element which contains the button.
|
||||||
|
public override bool AcceptsFocus => true;
|
||||||
|
|
||||||
|
public Bindable<T> Current
|
||||||
|
{
|
||||||
|
get => current.Current;
|
||||||
|
set => current.Current = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color4 buttonColour;
|
||||||
|
|
||||||
|
private bool hovering;
|
||||||
|
|
||||||
|
public RestoreDefaultValueButton()
|
||||||
|
{
|
||||||
|
Height = 1;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
Width = SettingsPanel.CONTENT_MARGINS;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colour)
|
||||||
|
{
|
||||||
|
BackgroundColour = colour.Yellow;
|
||||||
|
buttonColour = colour.Yellow;
|
||||||
|
Content.Width = 0.33f;
|
||||||
|
Content.CornerRadius = 3;
|
||||||
|
Content.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Colour = buttonColour.Opacity(0.1f),
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Radius = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
Padding = new MarginPadding { Vertical = 1.5f };
|
||||||
|
Alpha = 0f;
|
||||||
|
|
||||||
|
Action += () =>
|
||||||
|
{
|
||||||
|
if (!current.Disabled) current.SetDefault();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Current.ValueChanged += _ => UpdateState();
|
||||||
|
Current.DisabledChanged += _ => UpdateState();
|
||||||
|
Current.DefaultChanged += _ => UpdateState();
|
||||||
|
|
||||||
|
UpdateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TooltipText => "revert to default";
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
hovering = true;
|
||||||
|
UpdateState();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
hovering = false;
|
||||||
|
UpdateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateState() => Scheduler.AddOnce(updateState);
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
|
{
|
||||||
|
if (current == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.FadeTo(current.IsDefault ? 0f :
|
||||||
|
hovering && !current.Disabled ? 1f : 0.65f, 200, Easing.OutQuint);
|
||||||
|
this.FadeColour(current.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,16 +5,11 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osuTK.Graphics;
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Effects;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -108,7 +103,7 @@ namespace osu.Game.Overlays.Settings
|
|||||||
|
|
||||||
protected SettingsItem()
|
protected SettingsItem()
|
||||||
{
|
{
|
||||||
RestoreDefaultValueButton restoreDefaultButton;
|
RestoreDefaultValueButton<T> restoreDefaultButton;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
@ -116,7 +111,7 @@ namespace osu.Game.Overlays.Settings
|
|||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
restoreDefaultButton = new RestoreDefaultValueButton(),
|
restoreDefaultButton = new RestoreDefaultValueButton<T>(),
|
||||||
FlowContent = new FillFlowContainer
|
FlowContent = new FillFlowContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
@ -137,7 +132,7 @@ namespace osu.Game.Overlays.Settings
|
|||||||
controlWithCurrent.Current.DisabledChanged += _ => updateDisabled();
|
controlWithCurrent.Current.DisabledChanged += _ => updateDisabled();
|
||||||
|
|
||||||
if (ShowsDefaultIndicator)
|
if (ShowsDefaultIndicator)
|
||||||
restoreDefaultButton.Bindable = controlWithCurrent.Current;
|
restoreDefaultButton.Current = controlWithCurrent.Current;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,101 +141,5 @@ namespace osu.Game.Overlays.Settings
|
|||||||
if (labelText != null)
|
if (labelText != null)
|
||||||
labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1;
|
labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internal class RestoreDefaultValueButton : Container, IHasTooltip
|
|
||||||
{
|
|
||||||
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
|
|
||||||
|
|
||||||
private Bindable<T> bindable;
|
|
||||||
|
|
||||||
public Bindable<T> Bindable
|
|
||||||
{
|
|
||||||
get => bindable;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
bindable = value;
|
|
||||||
bindable.ValueChanged += _ => UpdateState();
|
|
||||||
bindable.DisabledChanged += _ => UpdateState();
|
|
||||||
bindable.DefaultChanged += _ => UpdateState();
|
|
||||||
UpdateState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Color4 buttonColour;
|
|
||||||
|
|
||||||
private bool hovering;
|
|
||||||
|
|
||||||
public RestoreDefaultValueButton()
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Y;
|
|
||||||
Width = SettingsPanel.CONTENT_MARGINS;
|
|
||||||
Padding = new MarginPadding { Vertical = 1.5f };
|
|
||||||
Alpha = 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuColour colour)
|
|
||||||
{
|
|
||||||
buttonColour = colour.Yellow;
|
|
||||||
|
|
||||||
Child = new Container
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
CornerRadius = 3,
|
|
||||||
Masking = true,
|
|
||||||
Colour = buttonColour,
|
|
||||||
EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Colour = buttonColour.Opacity(0.1f),
|
|
||||||
Type = EdgeEffectType.Glow,
|
|
||||||
Radius = 2,
|
|
||||||
},
|
|
||||||
Width = 0.33f,
|
|
||||||
Child = new Box { RelativeSizeAxes = Axes.Both },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
UpdateState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string TooltipText => "revert to default";
|
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
|
||||||
{
|
|
||||||
if (bindable != null && !bindable.Disabled)
|
|
||||||
bindable.SetDefault();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
|
||||||
{
|
|
||||||
hovering = true;
|
|
||||||
UpdateState();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
|
||||||
{
|
|
||||||
hovering = false;
|
|
||||||
UpdateState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateState() => Scheduler.AddOnce(updateState);
|
|
||||||
|
|
||||||
private void updateState()
|
|
||||||
{
|
|
||||||
if (bindable == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.FadeTo(bindable.IsDefault ? 0f :
|
|
||||||
hovering && !bindable.Disabled ? 1f : 0.65f, 200, Easing.OutQuint);
|
|
||||||
this.FadeColour(bindable.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,6 +10,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -49,8 +50,6 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private readonly bool showSidebar;
|
private readonly bool showSidebar;
|
||||||
|
|
||||||
protected Box Background;
|
|
||||||
|
|
||||||
protected SettingsPanel(bool showSidebar)
|
protected SettingsPanel(bool showSidebar)
|
||||||
{
|
{
|
||||||
this.showSidebar = showSidebar;
|
this.showSidebar = showSidebar;
|
||||||
@ -63,13 +62,13 @@ namespace osu.Game.Overlays
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
InternalChild = ContentContainer = new Container
|
InternalChild = ContentContainer = new NonMaskedContent
|
||||||
{
|
{
|
||||||
Width = WIDTH,
|
Width = WIDTH,
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
Background = new Box
|
new Box
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopRight,
|
Anchor = Anchor.TopRight,
|
||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
@ -165,7 +164,7 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
base.PopOut();
|
base.PopOut();
|
||||||
|
|
||||||
ContentContainer.MoveToX(-WIDTH, TRANSITION_LENGTH, Easing.OutQuint);
|
ContentContainer.MoveToX(-WIDTH + ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint);
|
||||||
|
|
||||||
Sidebar?.MoveToX(-sidebar_width, TRANSITION_LENGTH, Easing.OutQuint);
|
Sidebar?.MoveToX(-sidebar_width, TRANSITION_LENGTH, Easing.OutQuint);
|
||||||
this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint);
|
this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint);
|
||||||
@ -191,6 +190,12 @@ namespace osu.Game.Overlays
|
|||||||
Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 };
|
Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class NonMaskedContent : Container<Drawable>
|
||||||
|
{
|
||||||
|
// masking breaks the pan-out transform with nested sub-settings panels.
|
||||||
|
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
|
||||||
|
}
|
||||||
|
|
||||||
public class SettingsSectionsContainer : SectionsContainer<SettingsSection>
|
public class SettingsSectionsContainer : SectionsContainer<SettingsSection>
|
||||||
{
|
{
|
||||||
public SearchContainer<SettingsSection> SearchContainer;
|
public SearchContainer<SettingsSection> SearchContainer;
|
||||||
|
50
osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs
Normal file
50
osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using Markdig.Extensions.Yaml;
|
||||||
|
using Markdig.Syntax;
|
||||||
|
using Markdig.Syntax.Inlines;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Containers.Markdown;
|
||||||
|
using osu.Game.Graphics.Containers.Markdown;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Wiki.Markdown
|
||||||
|
{
|
||||||
|
public class WikiMarkdownContainer : OsuMarkdownContainer
|
||||||
|
{
|
||||||
|
public string CurrentPath
|
||||||
|
{
|
||||||
|
set => DocumentUrl = $"{DocumentUrl}wiki/{value}";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void AddMarkdownComponent(IMarkdownObject markdownObject, FillFlowContainer container, int level)
|
||||||
|
{
|
||||||
|
switch (markdownObject)
|
||||||
|
{
|
||||||
|
case YamlFrontMatterBlock yamlFrontMatterBlock:
|
||||||
|
container.Add(new WikiNoticeContainer(yamlFrontMatterBlock));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ParagraphBlock paragraphBlock:
|
||||||
|
// Check if paragraph only contains an image
|
||||||
|
if (paragraphBlock.Inline.Count() == 1 && paragraphBlock.Inline.FirstChild is LinkInline { IsImage: true } linkInline)
|
||||||
|
{
|
||||||
|
container.Add(new WikiMarkdownImageBlock(linkInline));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.AddMarkdownComponent(markdownObject, container, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override MarkdownTextFlowContainer CreateTextFlow() => new WikiMarkdownTextFlowContainer();
|
||||||
|
|
||||||
|
private class WikiMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer
|
||||||
|
{
|
||||||
|
protected override void AddImage(LinkInline linkInline) => AddDrawable(new WikiMarkdownImage(linkInline));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs
Normal file
29
osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// 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 Markdig.Syntax.Inlines;
|
||||||
|
using osu.Framework.Graphics.Containers.Markdown;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Wiki.Markdown
|
||||||
|
{
|
||||||
|
public class WikiMarkdownImage : MarkdownImage, IHasTooltip
|
||||||
|
{
|
||||||
|
public string TooltipText { get; }
|
||||||
|
|
||||||
|
public WikiMarkdownImage(LinkInline linkInline)
|
||||||
|
: base(linkInline.Url)
|
||||||
|
{
|
||||||
|
TooltipText = linkInline.Title;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ImageContainer CreateImageContainer(string url)
|
||||||
|
{
|
||||||
|
// The idea is replace "https://website.url/wiki/{path-to-image}" to "https://website.url/wiki/images/{path-to-image}"
|
||||||
|
// "/wiki/images/*" is route to fetch wiki image from osu!web server (see: https://github.com/ppy/osu-web/blob/4205eb66a4da86bdee7835045e4bf28c35456e04/routes/web.php#L289)
|
||||||
|
url = url.Replace("/wiki/", "/wiki/images/");
|
||||||
|
|
||||||
|
return base.CreateImageContainer(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs
Normal file
49
osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// 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 Markdig.Syntax.Inlines;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Containers.Markdown;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Wiki.Markdown
|
||||||
|
{
|
||||||
|
public class WikiMarkdownImageBlock : FillFlowContainer
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private IMarkdownTextComponent parentTextComponent { get; set; }
|
||||||
|
|
||||||
|
private readonly LinkInline linkInline;
|
||||||
|
|
||||||
|
public WikiMarkdownImageBlock(LinkInline linkInline)
|
||||||
|
{
|
||||||
|
this.linkInline = linkInline;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
Direction = FillDirection.Vertical;
|
||||||
|
Spacing = new Vector2(0, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new WikiMarkdownImage(linkInline)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
},
|
||||||
|
parentTextComponent.CreateSpriteText().With(t =>
|
||||||
|
{
|
||||||
|
t.Text = linkInline.Title;
|
||||||
|
t.Anchor = Anchor.TopCentre;
|
||||||
|
t.Origin = Anchor.TopCentre;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs
Normal file
97
osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// 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 Markdig.Extensions.Yaml;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Containers.Markdown;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Wiki.Markdown
|
||||||
|
{
|
||||||
|
public class WikiNoticeContainer : FillFlowContainer
|
||||||
|
{
|
||||||
|
private readonly bool isOutdated;
|
||||||
|
private readonly bool needsCleanup;
|
||||||
|
|
||||||
|
public WikiNoticeContainer(YamlFrontMatterBlock yamlFrontMatterBlock)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
Direction = FillDirection.Vertical;
|
||||||
|
|
||||||
|
foreach (var line in yamlFrontMatterBlock.Lines)
|
||||||
|
{
|
||||||
|
switch (line.ToString())
|
||||||
|
{
|
||||||
|
case "outdated: true":
|
||||||
|
isOutdated = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "needs_cleanup: true":
|
||||||
|
needsCleanup = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
// Reference : https://github.com/ppy/osu-web/blob/master/resources/views/wiki/_notice.blade.php and https://github.com/ppy/osu-web/blob/master/resources/lang/en/wiki.php
|
||||||
|
// TODO : add notice box for fallback translation, legal translation and outdated translation after implement wiki locale in the future.
|
||||||
|
if (isOutdated)
|
||||||
|
{
|
||||||
|
Add(new NoticeBox
|
||||||
|
{
|
||||||
|
Text = "The content on this page is incomplete or outdated. If you are able to help out, please consider updating the article!",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (needsCleanup)
|
||||||
|
{
|
||||||
|
Add(new NoticeBox
|
||||||
|
{
|
||||||
|
Text = "This page does not meet the standards of the osu! wiki and needs to be cleaned up or rewritten. If you are able to help out, please consider updating the article!",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NoticeBox : Container
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private IMarkdownTextFlowComponent parentFlowComponent { get; set; }
|
||||||
|
|
||||||
|
public string Text { get; set; }
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider, OsuColour colour)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
MarkdownTextFlowContainer textFlow;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background4,
|
||||||
|
},
|
||||||
|
textFlow = parentFlowComponent.CreateTextFlow().With(t =>
|
||||||
|
{
|
||||||
|
t.Colour = colour.Orange1;
|
||||||
|
t.Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Vertical = 10,
|
||||||
|
Horizontal = 15,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
textFlow.AddText(Text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
osu.Game/Overlays/Wiki/WikiMainPage.cs
Normal file
104
osu.Game/Overlays/Wiki/WikiMainPage.cs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
// 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 System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using HtmlAgilityPack;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Wiki
|
||||||
|
{
|
||||||
|
public class WikiMainPage : FillFlowContainer
|
||||||
|
{
|
||||||
|
public string Markdown;
|
||||||
|
|
||||||
|
public WikiMainPage()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
var html = new HtmlDocument();
|
||||||
|
html.LoadHtml(Markdown);
|
||||||
|
|
||||||
|
var panels = createPanels(html).ToArray();
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
createBlurb(html),
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
RowDimensions = Enumerable.Repeat(new Dimension(GridSizeMode.AutoSize), panels.Length).ToArray(),
|
||||||
|
Content = panels,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Container createBlurb(HtmlDocument html)
|
||||||
|
{
|
||||||
|
var blurbNode = html.DocumentNode.SelectSingleNode("//div[contains(@class, 'wiki-main-page__blurb')]");
|
||||||
|
|
||||||
|
return new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Vertical = 30,
|
||||||
|
},
|
||||||
|
Child = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = blurbNode.InnerText,
|
||||||
|
Font = OsuFont.GetFont(size: 12),
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<Drawable[]> createPanels(HtmlDocument html)
|
||||||
|
{
|
||||||
|
var panelsNode = html.DocumentNode.SelectNodes("//div[contains(@class, 'wiki-main-page-panel')]").ToArray();
|
||||||
|
|
||||||
|
Debug.Assert(panelsNode.Length > 1);
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
while (i < panelsNode.Length)
|
||||||
|
{
|
||||||
|
var isFullWidth = panelsNode[i].HasClass("wiki-main-page-panel--full");
|
||||||
|
|
||||||
|
if (isFullWidth)
|
||||||
|
{
|
||||||
|
yield return new Drawable[]
|
||||||
|
{
|
||||||
|
new WikiPanelContainer(panelsNode[i++].InnerText, true)
|
||||||
|
{
|
||||||
|
// This is required to fill up the space of "null" drawable below.
|
||||||
|
Width = 2,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yield return new Drawable[]
|
||||||
|
{
|
||||||
|
new WikiPanelContainer(panelsNode[i++].InnerText),
|
||||||
|
i < panelsNode.Length ? new WikiPanelContainer(panelsNode[i++].InnerText) : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
125
osu.Game/Overlays/Wiki/WikiPanelContainer.cs
Normal file
125
osu.Game/Overlays/Wiki/WikiPanelContainer.cs
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Markdig.Syntax;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Containers.Markdown;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers.Markdown;
|
||||||
|
using osu.Game.Overlays.Wiki.Markdown;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Wiki
|
||||||
|
{
|
||||||
|
public class WikiPanelContainer : Container
|
||||||
|
{
|
||||||
|
private WikiPanelMarkdownContainer panelContainer;
|
||||||
|
|
||||||
|
private readonly string text;
|
||||||
|
|
||||||
|
private readonly bool isFullWidth;
|
||||||
|
|
||||||
|
public WikiPanelContainer(string text, bool isFullWidth = false)
|
||||||
|
{
|
||||||
|
this.text = text;
|
||||||
|
this.isFullWidth = isFullWidth;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Padding = new MarginPadding(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 4,
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Colour = Color4.Black.Opacity(25),
|
||||||
|
Offset = new Vector2(0, 1),
|
||||||
|
Radius = 3,
|
||||||
|
},
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
Colour = colourProvider.Background4,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
panelContainer = new WikiPanelMarkdownContainer(isFullWidth)
|
||||||
|
{
|
||||||
|
Text = text,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
Height = Math.Max(panelContainer.Height, Parent.DrawHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class WikiPanelMarkdownContainer : WikiMarkdownContainer
|
||||||
|
{
|
||||||
|
private readonly bool isFullWidth;
|
||||||
|
|
||||||
|
public WikiPanelMarkdownContainer(bool isFullWidth)
|
||||||
|
{
|
||||||
|
this.isFullWidth = isFullWidth;
|
||||||
|
|
||||||
|
LineSpacing = 0;
|
||||||
|
DocumentPadding = new MarginPadding(30);
|
||||||
|
DocumentMargin = new MarginPadding(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SpriteText CreateSpriteText() => base.CreateSpriteText().With(t => t.Font = t.Font.With(weight: FontWeight.Bold));
|
||||||
|
|
||||||
|
public override MarkdownTextFlowContainer CreateTextFlow() => base.CreateTextFlow().With(f => f.TextAnchor = Anchor.TopCentre);
|
||||||
|
|
||||||
|
protected override MarkdownParagraph CreateParagraph(ParagraphBlock paragraphBlock, int level)
|
||||||
|
=> base.CreateParagraph(paragraphBlock, level).With(p => p.Margin = new MarginPadding { Bottom = 10 });
|
||||||
|
|
||||||
|
protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new WikiPanelHeading(headingBlock)
|
||||||
|
{
|
||||||
|
IsFullWidth = isFullWidth,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class WikiPanelHeading : OsuMarkdownHeading
|
||||||
|
{
|
||||||
|
public bool IsFullWidth;
|
||||||
|
|
||||||
|
public WikiPanelHeading(HeadingBlock headingBlock)
|
||||||
|
: base(headingBlock)
|
||||||
|
{
|
||||||
|
Margin = new MarginPadding { Bottom = 40 };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override MarkdownTextFlowContainer CreateTextFlow() => base.CreateTextFlow().With(f =>
|
||||||
|
{
|
||||||
|
f.Anchor = Anchor.TopCentre;
|
||||||
|
f.Origin = Anchor.TopCentre;
|
||||||
|
f.TextAnchor = Anchor.TopCentre;
|
||||||
|
});
|
||||||
|
|
||||||
|
protected override FontWeight GetFontWeightByLevel(int level) => FontWeight.Light;
|
||||||
|
|
||||||
|
protected override float GetFontSizeByLevel(int level) => base.GetFontSizeByLevel(IsFullWidth ? level : 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.TypeExtensions;
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Performance;
|
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
@ -311,6 +310,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked for this <see cref="DrawableHitObject"/> to take on any values from a newly-applied <see cref="HitObject"/>.
|
/// Invoked for this <see cref="DrawableHitObject"/> to take on any values from a newly-applied <see cref="HitObject"/>.
|
||||||
|
/// This is also fired after any changes which occurred via an <see cref="osu.Game.Rulesets.Objects.HitObject.ApplyDefaults"/> call.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void OnApply()
|
protected virtual void OnApply()
|
||||||
{
|
{
|
||||||
@ -318,6 +318,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked for this <see cref="DrawableHitObject"/> to revert any values previously taken on from the currently-applied <see cref="HitObject"/>.
|
/// Invoked for this <see cref="DrawableHitObject"/> to revert any values previously taken on from the currently-applied <see cref="HitObject"/>.
|
||||||
|
/// This is also fired after any changes which occurred via an <see cref="osu.Game.Rulesets.Objects.HitObject.ApplyDefaults"/> call.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void OnFree()
|
protected virtual void OnFree()
|
||||||
{
|
{
|
||||||
@ -443,9 +444,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Apply (generally fade-in) transforms leading into the <see cref="HitObject"/> start time.
|
/// Apply (generally fade-in) transforms leading into the <see cref="HitObject"/> start time.
|
||||||
/// The local drawable hierarchy is recursively delayed to <see cref="LifetimeEntry.LifetimeStart"/> for convenience.
|
/// By default, this will fade in the object from zero with no duration.
|
||||||
///
|
|
||||||
/// By default this will fade in the object from zero with no duration.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is called once before every <see cref="UpdateStateTransforms"/>. This is to ensure a good state in the case
|
/// This is called once before every <see cref="UpdateStateTransforms"/>. This is to ensure a good state in the case
|
||||||
@ -621,17 +620,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
protected internal new ScheduledDelegate Schedule(Action action) => base.Schedule(action);
|
protected internal new ScheduledDelegate Schedule(Action action) => base.Schedule(action);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A safe offset prior to the start time of <see cref="HitObject"/> at which this <see cref="DrawableHitObject"/> may begin displaying contents.
|
/// An offset prior to the start time of <see cref="HitObject"/> at which this <see cref="DrawableHitObject"/> may begin displaying contents.
|
||||||
/// By default, <see cref="DrawableHitObject"/>s are assumed to display their contents within 10 seconds prior to the start time of <see cref="HitObject"/>.
|
/// By default, <see cref="DrawableHitObject"/>s are assumed to display their contents within 10 seconds prior to the start time of <see cref="HitObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is only used as an optimisation to delay the initial update of this <see cref="DrawableHitObject"/> and may be tuned more aggressively if required.
|
/// The initial transformation (<see cref="UpdateInitialTransforms"/>) starts at this offset before the start time of <see cref="HitObject"/>.
|
||||||
/// It is indirectly used to decide the automatic transform offset provided to <see cref="UpdateInitialTransforms"/>.
|
|
||||||
/// A more accurate <see cref="LifetimeEntry.LifetimeStart"/> should be set for further optimisation (in <see cref="LoadComplete"/>, for example).
|
|
||||||
/// <para>
|
|
||||||
/// Only has an effect if this <see cref="DrawableHitObject"/> is not being pooled.
|
|
||||||
/// For pooled <see cref="DrawableHitObject"/>s, use <see cref="HitObjectLifetimeEntry.InitialLifetimeOffset"/> instead.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected virtual double InitialLifetimeOffset => 10000;
|
protected virtual double InitialLifetimeOffset => 10000;
|
||||||
|
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects.Drawables
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An interface that exposes properties required for scrolling hit objects to be properly displayed.
|
|
||||||
/// </summary>
|
|
||||||
internal interface IScrollingHitObject : IDrawable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Time offset before the hit object start time at which this <see cref="IScrollingHitObject"/> becomes visible and the time offset
|
|
||||||
/// after the hit object's end time after which it expires.
|
|
||||||
///
|
|
||||||
/// <para>
|
|
||||||
/// This provides only a default life time range, however classes inheriting from <see cref="IScrollingHitObject"/> should override
|
|
||||||
/// their life times if more tight control is desired.
|
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
|
||||||
BindableDouble LifetimeOffset { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Axes which this <see cref="IScrollingHitObject"/> will scroll through.
|
|
||||||
/// This is set by the container which this scrolls through.
|
|
||||||
/// </summary>
|
|
||||||
Axes ScrollingAxes { set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -35,7 +35,11 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
HitObject = hitObject;
|
HitObject = hitObject;
|
||||||
|
|
||||||
startTimeBindable.BindTo(HitObject.StartTimeBindable);
|
startTimeBindable.BindTo(HitObject.StartTimeBindable);
|
||||||
startTimeBindable.BindValueChanged(onStartTimeChanged, true);
|
startTimeBindable.BindValueChanged(_ => setInitialLifetime(), true);
|
||||||
|
|
||||||
|
// Subscribe to this event before the DrawableHitObject so that the local callback is invoked before the entry is re-applied as a result of DefaultsApplied.
|
||||||
|
// This way, the DrawableHitObject can use OnApply() to overwrite the LifetimeStart that was set inside setInitialLifetime().
|
||||||
|
HitObject.DefaultsApplied += _ => setInitialLifetime();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The lifetime, as set by the hitobject.
|
// The lifetime, as set by the hitobject.
|
||||||
@ -82,15 +86,14 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
/// By default, <see cref="HitObject"/>s are assumed to display their contents within 10 seconds prior to their start time.
|
/// By default, <see cref="HitObject"/>s are assumed to display their contents within 10 seconds prior to their start time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is only used as an optimisation to delay the initial update of the <see cref="HitObject"/> and may be tuned more aggressively if required.
|
/// This is only used as an optimisation to delay the initial application of the <see cref="HitObject"/> to a <see cref="DrawableHitObject"/>.
|
||||||
/// It is indirectly used to decide the automatic transform offset provided to <see cref="DrawableHitObject.UpdateInitialTransforms"/>.
|
/// A more accurate <see cref="LifetimeEntry.LifetimeStart"/> should be set on the hit object application, for further optimisation.
|
||||||
/// A more accurate <see cref="LifetimeEntry.LifetimeStart"/> should be set for further optimisation (in <see cref="DrawableHitObject.LoadComplete"/>, for example).
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected virtual double InitialLifetimeOffset => 10000;
|
protected virtual double InitialLifetimeOffset => 10000;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resets <see cref="LifetimeEntry.LifetimeStart"/> according to the change in start time of the <see cref="HitObject"/>.
|
/// Set <see cref="LifetimeEntry.LifetimeStart"/> using <see cref="InitialLifetimeOffset"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void onStartTimeChanged(ValueChangedEvent<double> startTime) => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset;
|
private void setInitialLifetime() => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,18 +122,20 @@ namespace osu.Game.Rulesets.UI
|
|||||||
var entry = (HitObjectLifetimeEntry)lifetimeEntry;
|
var entry = (HitObjectLifetimeEntry)lifetimeEntry;
|
||||||
Debug.Assert(!aliveDrawableMap.ContainsKey(entry));
|
Debug.Assert(!aliveDrawableMap.ContainsKey(entry));
|
||||||
|
|
||||||
bool isNonPooled = nonPooledDrawableMap.TryGetValue(entry, out var drawable);
|
bool isPooled = !nonPooledDrawableMap.TryGetValue(entry, out var drawable);
|
||||||
drawable ??= pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null);
|
drawable ??= pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null);
|
||||||
if (drawable == null)
|
if (drawable == null)
|
||||||
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
|
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
|
||||||
|
|
||||||
aliveDrawableMap[entry] = drawable;
|
aliveDrawableMap[entry] = drawable;
|
||||||
|
|
||||||
|
if (isPooled)
|
||||||
|
{
|
||||||
|
addDrawable(drawable);
|
||||||
|
HitObjectUsageBegan?.Invoke(entry.HitObject);
|
||||||
|
}
|
||||||
|
|
||||||
OnAdd(drawable);
|
OnAdd(drawable);
|
||||||
|
|
||||||
if (isNonPooled) return;
|
|
||||||
|
|
||||||
addDrawable(drawable);
|
|
||||||
HitObjectUsageBegan?.Invoke(entry.HitObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void entryBecameDead(LifetimeEntry lifetimeEntry)
|
private void entryBecameDead(LifetimeEntry lifetimeEntry)
|
||||||
@ -142,17 +144,18 @@ namespace osu.Game.Rulesets.UI
|
|||||||
Debug.Assert(aliveDrawableMap.ContainsKey(entry));
|
Debug.Assert(aliveDrawableMap.ContainsKey(entry));
|
||||||
|
|
||||||
var drawable = aliveDrawableMap[entry];
|
var drawable = aliveDrawableMap[entry];
|
||||||
bool isNonPooled = nonPooledDrawableMap.ContainsKey(entry);
|
bool isPooled = !nonPooledDrawableMap.ContainsKey(entry);
|
||||||
|
|
||||||
drawable.OnKilled();
|
drawable.OnKilled();
|
||||||
aliveDrawableMap.Remove(entry);
|
aliveDrawableMap.Remove(entry);
|
||||||
|
|
||||||
|
if (isPooled)
|
||||||
|
{
|
||||||
|
removeDrawable(drawable);
|
||||||
|
HitObjectUsageFinished?.Invoke(entry.HitObject);
|
||||||
|
}
|
||||||
|
|
||||||
OnRemove(drawable);
|
OnRemove(drawable);
|
||||||
|
|
||||||
if (isNonPooled) return;
|
|
||||||
|
|
||||||
removeDrawable(drawable);
|
|
||||||
// The hit object is not freed when the DHO was not pooled.
|
|
||||||
HitObjectUsageFinished?.Invoke(entry.HitObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addDrawable(DrawableHitObject drawable)
|
private void addDrawable(DrawableHitObject drawable)
|
||||||
@ -211,21 +214,16 @@ namespace osu.Game.Rulesets.UI
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a <see cref="DrawableHitObject"/> is added to this container.
|
/// Invoked after a <see cref="DrawableHitObject"/> is added to this container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
|
||||||
/// This method is not invoked for nested <see cref="DrawableHitObject"/>s.
|
|
||||||
/// </remarks>
|
|
||||||
protected virtual void OnAdd(DrawableHitObject drawableHitObject)
|
protected virtual void OnAdd(DrawableHitObject drawableHitObject)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(drawableHitObject.LoadState >= LoadState.Ready);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a <see cref="DrawableHitObject"/> is removed from this container.
|
/// Invoked after a <see cref="DrawableHitObject"/> is removed from this container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
|
||||||
/// This method is not invoked for nested <see cref="DrawableHitObject"/>s.
|
|
||||||
/// </remarks>
|
|
||||||
protected virtual void OnRemove(DrawableHitObject drawableHitObject)
|
protected virtual void OnRemove(DrawableHitObject drawableHitObject)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hit objects which require lifetime computation in the next update call.
|
/// A set of top-level <see cref="DrawableHitObject"/>s which have an up-to-date layout.
|
||||||
/// </summary>
|
|
||||||
private readonly HashSet<DrawableHitObject> toComputeLifetime = new HashSet<DrawableHitObject>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A set containing all <see cref="HitObjectContainer.AliveObjects"/> which have an up-to-date layout.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly HashSet<DrawableHitObject> layoutComputed = new HashSet<DrawableHitObject>();
|
private readonly HashSet<DrawableHitObject> layoutComputed = new HashSet<DrawableHitObject>();
|
||||||
|
|
||||||
@ -54,7 +49,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
{
|
{
|
||||||
base.Clear();
|
base.Clear();
|
||||||
|
|
||||||
toComputeLifetime.Clear();
|
|
||||||
layoutComputed.Clear();
|
layoutComputed.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +77,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
|
|
||||||
flipPositionIfRequired(ref position);
|
flipPositionIfRequired(ref position);
|
||||||
|
|
||||||
return scrollingInfo.Algorithm.TimeAt(position, Time.Current, scrollingInfo.TimeRange.Value, getLength());
|
return scrollingInfo.Algorithm.TimeAt(position, Time.Current, scrollingInfo.TimeRange.Value, scrollLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -91,7 +85,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Vector2 ScreenSpacePositionAtTime(double time)
|
public Vector2 ScreenSpacePositionAtTime(double time)
|
||||||
{
|
{
|
||||||
var pos = scrollingInfo.Algorithm.PositionAt(time, Time.Current, scrollingInfo.TimeRange.Value, getLength());
|
var pos = scrollingInfo.Algorithm.PositionAt(time, Time.Current, scrollingInfo.TimeRange.Value, scrollLength);
|
||||||
|
|
||||||
flipPositionIfRequired(ref pos);
|
flipPositionIfRequired(ref pos);
|
||||||
|
|
||||||
@ -106,16 +100,19 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private float getLength()
|
private float scrollLength
|
||||||
{
|
{
|
||||||
switch (scrollingInfo.Direction.Value)
|
get
|
||||||
{
|
{
|
||||||
case ScrollingDirection.Left:
|
switch (scrollingInfo.Direction.Value)
|
||||||
case ScrollingDirection.Right:
|
{
|
||||||
return DrawWidth;
|
case ScrollingDirection.Left:
|
||||||
|
case ScrollingDirection.Right:
|
||||||
|
return DrawWidth;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return DrawHeight;
|
return DrawHeight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,81 +147,40 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnAdd(DrawableHitObject drawableHitObject) => onAddRecursive(drawableHitObject);
|
protected override void OnAdd(DrawableHitObject drawableHitObject)
|
||||||
|
|
||||||
protected override void OnRemove(DrawableHitObject drawableHitObject) => onRemoveRecursive(drawableHitObject);
|
|
||||||
|
|
||||||
private void onAddRecursive(DrawableHitObject hitObject)
|
|
||||||
{
|
{
|
||||||
invalidateHitObject(hitObject);
|
invalidateHitObject(drawableHitObject);
|
||||||
|
drawableHitObject.DefaultsApplied += invalidateHitObject;
|
||||||
hitObject.DefaultsApplied += invalidateHitObject;
|
|
||||||
|
|
||||||
foreach (var nested in hitObject.NestedHitObjects)
|
|
||||||
onAddRecursive(nested);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onRemoveRecursive(DrawableHitObject hitObject)
|
protected override void OnRemove(DrawableHitObject drawableHitObject)
|
||||||
{
|
{
|
||||||
toComputeLifetime.Remove(hitObject);
|
layoutComputed.Remove(drawableHitObject);
|
||||||
layoutComputed.Remove(hitObject);
|
|
||||||
|
|
||||||
hitObject.DefaultsApplied -= invalidateHitObject;
|
drawableHitObject.DefaultsApplied -= invalidateHitObject;
|
||||||
|
|
||||||
foreach (var nested in hitObject.NestedHitObjects)
|
|
||||||
onRemoveRecursive(nested);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Make this <see cref="DrawableHitObject"/> lifetime and layout computed in next update.
|
|
||||||
/// </summary>
|
|
||||||
private void invalidateHitObject(DrawableHitObject hitObject)
|
private void invalidateHitObject(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
// Lifetime computation is delayed until next update because
|
hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject);
|
||||||
// when the hit object is not pooled this container is not loaded here and `scrollLength` cannot be computed.
|
|
||||||
toComputeLifetime.Add(hitObject);
|
|
||||||
layoutComputed.Remove(hitObject);
|
layoutComputed.Remove(hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
private float scrollLength;
|
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
if (!layoutCache.IsValid)
|
if (layoutCache.IsValid) return;
|
||||||
|
|
||||||
|
foreach (var hitObject in Objects)
|
||||||
{
|
{
|
||||||
toComputeLifetime.Clear();
|
if (hitObject.HitObject != null)
|
||||||
|
invalidateHitObject(hitObject);
|
||||||
foreach (var hitObject in Objects)
|
|
||||||
{
|
|
||||||
if (hitObject.HitObject != null)
|
|
||||||
toComputeLifetime.Add(hitObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
layoutComputed.Clear();
|
|
||||||
|
|
||||||
scrollingInfo.Algorithm.Reset();
|
|
||||||
|
|
||||||
switch (direction.Value)
|
|
||||||
{
|
|
||||||
case ScrollingDirection.Up:
|
|
||||||
case ScrollingDirection.Down:
|
|
||||||
scrollLength = DrawSize.Y;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
scrollLength = DrawSize.X;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
layoutCache.Validate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var hitObject in toComputeLifetime)
|
scrollingInfo.Algorithm.Reset();
|
||||||
hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject);
|
|
||||||
|
|
||||||
toComputeLifetime.Clear();
|
layoutCache.Validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateAfterChildrenLife()
|
protected override void UpdateAfterChildrenLife()
|
||||||
|
@ -77,7 +77,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
double offset = result.Time.Value - blueprints.First().Item.StartTime;
|
double offset = result.Time.Value - blueprints.First().Item.StartTime;
|
||||||
|
|
||||||
if (offset != 0)
|
if (offset != 0)
|
||||||
Beatmap.PerformOnSelection(obj => obj.StartTime += offset);
|
{
|
||||||
|
Beatmap.PerformOnSelection(obj =>
|
||||||
|
{
|
||||||
|
obj.StartTime += offset;
|
||||||
|
Beatmap.Update(obj);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -125,6 +125,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
h.Samples.Add(new HitSampleInfo(sampleName));
|
h.Samples.Add(new HitSampleInfo(sampleName));
|
||||||
|
EditorBeatmap.Update(h);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +135,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <param name="sampleName">The name of the hit sample.</param>
|
/// <param name="sampleName">The name of the hit sample.</param>
|
||||||
public void RemoveHitSample(string sampleName)
|
public void RemoveHitSample(string sampleName)
|
||||||
{
|
{
|
||||||
EditorBeatmap.PerformOnSelection(h => h.SamplesBindable.RemoveAll(s => s.Name == sampleName));
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
|
{
|
||||||
|
h.SamplesBindable.RemoveAll(s => s.Name == sampleName);
|
||||||
|
EditorBeatmap.Update(h);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -276,7 +276,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
var timingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(selected.First().StartTime);
|
var timingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(selected.First().StartTime);
|
||||||
double adjustment = timingPoint.BeatLength / EditorBeatmap.BeatDivisor * amount;
|
double adjustment = timingPoint.BeatLength / EditorBeatmap.BeatDivisor * amount;
|
||||||
|
|
||||||
EditorBeatmap.PerformOnSelection(h => h.StartTime += adjustment);
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
|
{
|
||||||
|
h.StartTime += adjustment;
|
||||||
|
EditorBeatmap.Update(h);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,25 +473,23 @@ namespace osu.Game.Screens.Edit
|
|||||||
{
|
{
|
||||||
if (!exitConfirmed)
|
if (!exitConfirmed)
|
||||||
{
|
{
|
||||||
// if the confirm dialog is already showing (or we can't show it, ie. in tests) exit without save.
|
// dialog overlay may not be available in visual tests.
|
||||||
if (dialogOverlay == null || dialogOverlay.CurrentDialog is PromptForSaveDialog)
|
if (dialogOverlay == null)
|
||||||
{
|
{
|
||||||
confirmExit();
|
confirmExit();
|
||||||
return base.OnExiting(next);
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the dialog is already displayed, confirm exit with no save.
|
||||||
|
if (dialogOverlay.CurrentDialog is PromptForSaveDialog saveDialog)
|
||||||
|
{
|
||||||
|
saveDialog.PerformOkAction();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNewBeatmap || HasUnsavedChanges)
|
if (isNewBeatmap || HasUnsavedChanges)
|
||||||
{
|
{
|
||||||
dialogOverlay?.Push(new PromptForSaveDialog(() =>
|
dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave));
|
||||||
{
|
|
||||||
confirmExit();
|
|
||||||
this.Exit();
|
|
||||||
}, () =>
|
|
||||||
{
|
|
||||||
confirmExitWithSave();
|
|
||||||
this.Exit();
|
|
||||||
}));
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -499,15 +497,23 @@ namespace osu.Game.Screens.Edit
|
|||||||
ApplyToBackground(b => b.FadeColour(Color4.White, 500));
|
ApplyToBackground(b => b.FadeColour(Color4.White, 500));
|
||||||
resetTrack();
|
resetTrack();
|
||||||
|
|
||||||
Beatmap.Value = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo);
|
// To update the game-wide beatmap with any changes, perform a re-fetch on exit.
|
||||||
|
// This is required as the editor makes its local changes via EditorBeatmap
|
||||||
|
// (which are not propagated outwards to a potentially cached WorkingBeatmap).
|
||||||
|
var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo);
|
||||||
|
|
||||||
|
if (!(refetchedBeatmap is DummyWorkingBeatmap))
|
||||||
|
Beatmap.Value = refetchedBeatmap;
|
||||||
|
|
||||||
return base.OnExiting(next);
|
return base.OnExiting(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void confirmExitWithSave()
|
private void confirmExitWithSave()
|
||||||
{
|
{
|
||||||
exitConfirmed = true;
|
|
||||||
Save();
|
Save();
|
||||||
|
|
||||||
|
exitConfirmed = true;
|
||||||
|
this.Exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void confirmExit()
|
private void confirmExit()
|
||||||
@ -529,6 +535,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
}
|
}
|
||||||
|
|
||||||
exitConfirmed = true;
|
exitConfirmed = true;
|
||||||
|
this.Exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Bindable<string> clipboard = new Bindable<string>();
|
private readonly Bindable<string> clipboard = new Bindable<string>();
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Audio.Track;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
|
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit
|
namespace osu.Game.Screens.Edit
|
||||||
@ -117,6 +118,8 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||||
|
|
||||||
|
protected override ISkin GetSkin() => throw new NotImplementedException();
|
||||||
|
|
||||||
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
|
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +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.Linq;
|
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -22,6 +19,8 @@ using osu.Game.Screens.Edit;
|
|||||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Menu
|
namespace osu.Game.Screens.Menu
|
||||||
{
|
{
|
||||||
@ -120,7 +119,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
Margin = new MarginPadding { Right = 15, Top = 5 }
|
Margin = new MarginPadding { Right = 15, Top = 5 }
|
||||||
},
|
},
|
||||||
exitConfirmOverlay?.CreateProxy() ?? Drawable.Empty()
|
exitConfirmOverlay?.CreateProxy() ?? Empty()
|
||||||
});
|
});
|
||||||
|
|
||||||
buttons.StateChanged += state =>
|
buttons.StateChanged += state =>
|
||||||
@ -270,15 +269,11 @@ namespace osu.Game.Screens.Menu
|
|||||||
if (!exitConfirmed && dialogOverlay != null)
|
if (!exitConfirmed && dialogOverlay != null)
|
||||||
{
|
{
|
||||||
if (dialogOverlay.CurrentDialog is ConfirmExitDialog exitDialog)
|
if (dialogOverlay.CurrentDialog is ConfirmExitDialog exitDialog)
|
||||||
{
|
exitDialog.PerformOkAction();
|
||||||
exitConfirmed = true;
|
|
||||||
exitDialog.Buttons.First().Click();
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort()));
|
dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort()));
|
||||||
return true;
|
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
buttons.State = ButtonSystemState.Exit;
|
buttons.State = ButtonSystemState.Exit;
|
||||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class LegacyComboCounter : CompositeDrawable, ISkinnableDrawable
|
public class LegacyComboCounter : CompositeDrawable, ISkinnableDrawable
|
||||||
{
|
{
|
||||||
public Bindable<int> Current { get; } = new BindableInt { MinValue = 0, };
|
public Bindable<int> Current { get; } = new BindableInt { MinValue = 0 };
|
||||||
|
|
||||||
private uint scheduledPopOutCurrentId;
|
private uint scheduledPopOutCurrentId;
|
||||||
|
|
||||||
@ -32,9 +32,9 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const double rolling_duration = 20;
|
private const double rolling_duration = 20;
|
||||||
|
|
||||||
private Drawable popOutCount;
|
private readonly Drawable popOutCount;
|
||||||
|
|
||||||
private Drawable displayedCountSpriteText;
|
private readonly Drawable displayedCountSpriteText;
|
||||||
|
|
||||||
private int previousValue;
|
private int previousValue;
|
||||||
|
|
||||||
@ -45,6 +45,20 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private ISkinSource skin { get; set; }
|
private ISkinSource skin { get; set; }
|
||||||
|
|
||||||
|
private readonly Container counterContainer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hides the combo counter internally without affecting its <see cref="SkinnableInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is used for rulesets that provide their own combo counter and don't want this HUD one to be visible,
|
||||||
|
/// without potentially affecting the user's selected skin.
|
||||||
|
/// </remarks>
|
||||||
|
public bool HiddenByRulesetImplementation
|
||||||
|
{
|
||||||
|
set => counterContainer.Alpha = value ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
public LegacyComboCounter()
|
public LegacyComboCounter()
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
@ -55,6 +69,25 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
Margin = new MarginPadding(10);
|
Margin = new MarginPadding(10);
|
||||||
|
|
||||||
Scale = new Vector2(1.2f);
|
Scale = new Vector2(1.2f);
|
||||||
|
|
||||||
|
InternalChild = counterContainer = new Container
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
popOutCount = new LegacySpriteText(LegacyFont.Combo)
|
||||||
|
{
|
||||||
|
Alpha = 0,
|
||||||
|
Margin = new MarginPadding(0.05f),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
},
|
||||||
|
displayedCountSpriteText = new LegacySpriteText(LegacyFont.Combo)
|
||||||
|
{
|
||||||
|
Alpha = 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -82,20 +115,6 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ScoreProcessor scoreProcessor)
|
private void load(ScoreProcessor scoreProcessor)
|
||||||
{
|
{
|
||||||
InternalChildren = new[]
|
|
||||||
{
|
|
||||||
popOutCount = new LegacySpriteText(LegacyFont.Combo)
|
|
||||||
{
|
|
||||||
Alpha = 0,
|
|
||||||
Margin = new MarginPadding(0.05f),
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
},
|
|
||||||
displayedCountSpriteText = new LegacySpriteText(LegacyFont.Combo)
|
|
||||||
{
|
|
||||||
Alpha = 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Current.BindTo(scoreProcessor.Combo);
|
Current.BindTo(scoreProcessor.Combo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,10 +124,12 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
((IHasText)displayedCountSpriteText).Text = formatCount(Current.Value);
|
((IHasText)displayedCountSpriteText).Text = formatCount(Current.Value);
|
||||||
|
|
||||||
|
counterContainer.Anchor = Anchor;
|
||||||
|
counterContainer.Origin = Origin;
|
||||||
displayedCountSpriteText.Anchor = Anchor;
|
displayedCountSpriteText.Anchor = Anchor;
|
||||||
displayedCountSpriteText.Origin = Origin;
|
displayedCountSpriteText.Origin = Origin;
|
||||||
popOutCount.Origin = Origin;
|
|
||||||
popOutCount.Anchor = Anchor;
|
popOutCount.Anchor = Anchor;
|
||||||
|
popOutCount.Origin = Origin;
|
||||||
|
|
||||||
Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true);
|
Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true);
|
||||||
}
|
}
|
||||||
|
@ -520,7 +520,10 @@ namespace osu.Game.Screens.Play
|
|||||||
if (!this.IsCurrentScreen())
|
if (!this.IsCurrentScreen())
|
||||||
{
|
{
|
||||||
ValidForResume = false;
|
ValidForResume = false;
|
||||||
this.MakeCurrent();
|
|
||||||
|
// in the potential case that this instance has already been exited, this is required to avoid a crash.
|
||||||
|
if (this.GetChildScreen() != null)
|
||||||
|
this.MakeCurrent();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private ManageCollectionsDialog manageCollectionsDialog { get; set; }
|
private ManageCollectionsDialog manageCollectionsDialog { get; set; }
|
||||||
|
|
||||||
public IEnumerable<DrawableCarouselItem> DrawableBeatmaps => beatmapContainer?.Children ?? Enumerable.Empty<DrawableCarouselItem>();
|
public IEnumerable<DrawableCarouselItem> DrawableBeatmaps => beatmapContainer?.IsLoaded != true ? Enumerable.Empty<DrawableCarouselItem>() : beatmapContainer.AliveChildren;
|
||||||
|
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
private Container<DrawableCarouselItem> beatmapContainer;
|
private Container<DrawableCarouselItem> beatmapContainer;
|
||||||
|
@ -505,12 +505,13 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\"");
|
Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\"");
|
||||||
|
|
||||||
WorkingBeatmap previous = Beatmap.Value;
|
int? lastSetID = Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID;
|
||||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, previous);
|
|
||||||
|
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap);
|
||||||
|
|
||||||
if (beatmap != null)
|
if (beatmap != null)
|
||||||
{
|
{
|
||||||
if (beatmap.BeatmapSetInfoID == previous?.BeatmapInfo.BeatmapSetInfoID)
|
if (beatmap.BeatmapSetInfoID == lastSetID)
|
||||||
sampleChangeDifficulty.Play();
|
sampleChangeDifficulty.Play();
|
||||||
else
|
else
|
||||||
sampleChangeBeatmap.Play();
|
sampleChangeBeatmap.Play();
|
||||||
|
@ -11,14 +11,14 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
public class DefaultLegacySkin : LegacySkin
|
public class DefaultLegacySkin : LegacySkin
|
||||||
{
|
{
|
||||||
public DefaultLegacySkin(IResourceStore<byte[]> storage, IStorageResourceProvider resources)
|
public DefaultLegacySkin(IStorageResourceProvider resources)
|
||||||
: this(Info, storage, resources)
|
: this(Info, resources)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)]
|
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)]
|
||||||
public DefaultLegacySkin(SkinInfo skin, IResourceStore<byte[]> storage, IStorageResourceProvider resources)
|
public DefaultLegacySkin(SkinInfo skin, IStorageResourceProvider resources)
|
||||||
: base(skin, storage, resources, string.Empty)
|
: base(skin, new NamespacedResourceStore<byte[]>(resources.Resources, "Skins/Legacy"), resources, string.Empty)
|
||||||
{
|
{
|
||||||
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
|
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
|
||||||
Configuration.AddComboColours(
|
Configuration.AddComboColours(
|
||||||
|
@ -59,6 +59,10 @@ namespace osu.Game.Skinning.Editor
|
|||||||
// the selection quad is always upright, so use an AABB rect to make mutating the values easier.
|
// the selection quad is always upright, so use an AABB rect to make mutating the values easier.
|
||||||
var selectionRect = getSelectionQuad().AABBFloat;
|
var selectionRect = getSelectionQuad().AABBFloat;
|
||||||
|
|
||||||
|
// If the selection has no area we cannot scale it
|
||||||
|
if (selectionRect.Area == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
// copy to mutate, as we will need to compare to the original later on.
|
// copy to mutate, as we will need to compare to the original later on.
|
||||||
var adjustedRect = selectionRect;
|
var adjustedRect = selectionRect;
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -23,6 +24,25 @@ namespace osu.Game.Skinning
|
|||||||
Configuration.AllowDefaultComboColoursFallback = false;
|
Configuration.AllowDefaultComboColoursFallback = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Drawable GetDrawableComponent(ISkinComponent component)
|
||||||
|
{
|
||||||
|
if (component is SkinnableTargetComponent targetComponent)
|
||||||
|
{
|
||||||
|
switch (targetComponent.Target)
|
||||||
|
{
|
||||||
|
case SkinnableTarget.MainHUDComponents:
|
||||||
|
// this should exist in LegacySkin instead, but there isn't a fallback skin for LegacySkins yet.
|
||||||
|
// therefore keep the check here until fallback default legacy skin is supported.
|
||||||
|
if (!this.HasFont(LegacyFont.Score))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetDrawableComponent(component);
|
||||||
|
}
|
||||||
|
|
||||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
@ -51,6 +71,6 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static SkinInfo createSkinInfo(BeatmapInfo beatmap) =>
|
private static SkinInfo createSkinInfo(BeatmapInfo beatmap) =>
|
||||||
new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata.Author.ToString() };
|
new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata?.AuthorString };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,7 +333,6 @@ namespace osu.Game.Skinning
|
|||||||
switch (target.Target)
|
switch (target.Target)
|
||||||
{
|
{
|
||||||
case SkinnableTarget.MainHUDComponents:
|
case SkinnableTarget.MainHUDComponents:
|
||||||
|
|
||||||
var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container =>
|
var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container =>
|
||||||
{
|
{
|
||||||
var score = container.OfType<LegacyScoreCounter>().FirstOrDefault();
|
var score = container.OfType<LegacyScoreCounter>().FirstOrDefault();
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.IO.Stores;
|
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
@ -28,16 +27,13 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
public string InstantiationInfo { get; set; }
|
public string InstantiationInfo { get; set; }
|
||||||
|
|
||||||
public virtual Skin CreateInstance(IResourceStore<byte[]> legacyDefaultResources, IStorageResourceProvider resources)
|
public virtual Skin CreateInstance(IStorageResourceProvider resources)
|
||||||
{
|
{
|
||||||
var type = string.IsNullOrEmpty(InstantiationInfo)
|
var type = string.IsNullOrEmpty(InstantiationInfo)
|
||||||
// handle the case of skins imported before InstantiationInfo was added.
|
// handle the case of skins imported before InstantiationInfo was added.
|
||||||
? typeof(LegacySkin)
|
? typeof(LegacySkin)
|
||||||
: Type.GetType(InstantiationInfo).AsNonNull();
|
: Type.GetType(InstantiationInfo).AsNonNull();
|
||||||
|
|
||||||
if (type == typeof(DefaultLegacySkin))
|
|
||||||
return (Skin)Activator.CreateInstance(type, this, legacyDefaultResources, resources);
|
|
||||||
|
|
||||||
return (Skin)Activator.CreateInstance(type, this, resources);
|
return (Skin)Activator.CreateInstance(type, this, resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
private readonly GameHost host;
|
private readonly GameHost host;
|
||||||
|
|
||||||
private readonly IResourceStore<byte[]> legacyDefaultResources;
|
private readonly IResourceStore<byte[]> resources;
|
||||||
|
|
||||||
public readonly Bindable<Skin> CurrentSkin = new Bindable<Skin>(new DefaultSkin(null));
|
public readonly Bindable<Skin> CurrentSkin = new Bindable<Skin>(new DefaultSkin(null));
|
||||||
public readonly Bindable<SkinInfo> CurrentSkinInfo = new Bindable<SkinInfo>(SkinInfo.Default) { Default = SkinInfo.Default };
|
public readonly Bindable<SkinInfo> CurrentSkinInfo = new Bindable<SkinInfo>(SkinInfo.Default) { Default = SkinInfo.Default };
|
||||||
@ -48,13 +48,12 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
protected override string ImportFromStablePath => "Skins";
|
protected override string ImportFromStablePath => "Skins";
|
||||||
|
|
||||||
public SkinManager(Storage storage, DatabaseContextFactory contextFactory, GameHost host, AudioManager audio, IResourceStore<byte[]> legacyDefaultResources)
|
public SkinManager(Storage storage, DatabaseContextFactory contextFactory, GameHost host, IResourceStore<byte[]> resources, AudioManager audio)
|
||||||
: base(storage, contextFactory, new SkinStore(contextFactory, storage), host)
|
: base(storage, contextFactory, new SkinStore(contextFactory, storage), host)
|
||||||
{
|
{
|
||||||
this.audio = audio;
|
this.audio = audio;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
|
this.resources = resources;
|
||||||
this.legacyDefaultResources = legacyDefaultResources;
|
|
||||||
|
|
||||||
CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue);
|
CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue);
|
||||||
CurrentSkin.ValueChanged += skin =>
|
CurrentSkin.ValueChanged += skin =>
|
||||||
@ -152,7 +151,7 @@ namespace osu.Game.Skinning
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="skinInfo">The skin to lookup.</param>
|
/// <param name="skinInfo">The skin to lookup.</param>
|
||||||
/// <returns>A <see cref="Skin"/> instance correlating to the provided <see cref="SkinInfo"/>.</returns>
|
/// <returns>A <see cref="Skin"/> instance correlating to the provided <see cref="SkinInfo"/>.</returns>
|
||||||
public Skin GetSkin(SkinInfo skinInfo) => skinInfo.CreateInstance(legacyDefaultResources, this);
|
public Skin GetSkin(SkinInfo skinInfo) => skinInfo.CreateInstance(this);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ensure that the current skin is in a state it can accept user modifications.
|
/// Ensure that the current skin is in a state it can accept user modifications.
|
||||||
@ -216,6 +215,7 @@ namespace osu.Game.Skinning
|
|||||||
#region IResourceStorageProvider
|
#region IResourceStorageProvider
|
||||||
|
|
||||||
AudioManager IStorageResourceProvider.AudioManager => audio;
|
AudioManager IStorageResourceProvider.AudioManager => audio;
|
||||||
|
IResourceStore<byte[]> IStorageResourceProvider.Resources => resources;
|
||||||
IResourceStore<byte[]> IStorageResourceProvider.Files => Files.Store;
|
IResourceStore<byte[]> IStorageResourceProvider.Files => Files.Store;
|
||||||
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host.CreateTextureLoaderStore(underlyingStore);
|
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host.CreateTextureLoaderStore(underlyingStore);
|
||||||
|
|
||||||
|
@ -57,7 +57,13 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
public DrawableStoryboard(Storyboard storyboard)
|
public DrawableStoryboard(Storyboard storyboard)
|
||||||
{
|
{
|
||||||
Storyboard = storyboard;
|
Storyboard = storyboard;
|
||||||
|
|
||||||
Size = new Vector2(640, 480);
|
Size = new Vector2(640, 480);
|
||||||
|
|
||||||
|
bool onlyHasVideoElements = Storyboard.Layers.SelectMany(l => l.Elements).Any(e => !(e is StoryboardVideo));
|
||||||
|
|
||||||
|
Width = Height * (storyboard.BeatmapInfo.WidescreenStoryboard || onlyHasVideoElements ? 16 / 9f : 4 / 3f);
|
||||||
|
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user