mirror of
https://github.com/ppy/osu.git
synced 2025-01-28 06:42:54 +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
|
||||
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
|
||||
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>
|
||||
<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>
|
||||
</Project>
|
||||
|
@ -1,8 +1,16 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
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 osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
@ -10,5 +18,22 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
public class TestSceneCatchPlayerLegacySkin : LegacySkinPlayerTestScene
|
||||
{
|
||||
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(false, true)]
|
||||
[TestCase(false, false)]
|
||||
public override void TestBeatmapComboColours(bool userHasCustomColours, bool useBeatmapSkin)
|
||||
public void TestBeatmapComboColours(bool userHasCustomColours, bool useBeatmapSkin)
|
||||
{
|
||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
||||
base.TestBeatmapComboColours(userHasCustomColours, useBeatmapSkin);
|
||||
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, true));
|
||||
ConfigureTest(useBeatmapSkin, true, userHasCustomColours);
|
||||
AddAssert("is beatmap skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public override void TestBeatmapComboColoursOverride(bool useBeatmapSkin)
|
||||
public void TestBeatmapComboColoursOverride(bool useBeatmapSkin)
|
||||
{
|
||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
||||
base.TestBeatmapComboColoursOverride(useBeatmapSkin);
|
||||
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, true));
|
||||
ConfigureTest(useBeatmapSkin, false, true);
|
||||
AddAssert("is user custom skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public override void TestBeatmapComboColoursOverrideWithDefaultColours(bool useBeatmapSkin)
|
||||
public void TestBeatmapComboColoursOverrideWithDefaultColours(bool useBeatmapSkin)
|
||||
{
|
||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
||||
base.TestBeatmapComboColoursOverrideWithDefaultColours(useBeatmapSkin);
|
||||
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, true));
|
||||
ConfigureTest(useBeatmapSkin, false, false);
|
||||
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(true, false)]
|
||||
[TestCase(false, false)]
|
||||
public override void TestBeatmapNoComboColours(bool useBeatmapSkin, bool useBeatmapColour)
|
||||
public void TestBeatmapNoComboColours(bool useBeatmapSkin, bool useBeatmapColour)
|
||||
{
|
||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, false);
|
||||
base.TestBeatmapNoComboColours(useBeatmapSkin, useBeatmapColour);
|
||||
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, false));
|
||||
ConfigureTest(useBeatmapSkin, useBeatmapColour, false);
|
||||
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(true, false)]
|
||||
[TestCase(false, false)]
|
||||
public override void TestBeatmapNoComboColoursSkinOverride(bool useBeatmapSkin, bool useBeatmapColour)
|
||||
public void TestBeatmapNoComboColoursSkinOverride(bool useBeatmapSkin, bool useBeatmapColour)
|
||||
{
|
||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, false);
|
||||
base.TestBeatmapNoComboColoursSkinOverride(useBeatmapSkin, useBeatmapColour);
|
||||
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, false));
|
||||
ConfigureTest(useBeatmapSkin, useBeatmapColour, true);
|
||||
AddAssert("is custom user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[TestCase(false)]
|
||||
public void TestBeatmapHyperDashColours(bool useBeatmapSkin)
|
||||
{
|
||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
||||
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, true));
|
||||
ConfigureTest(useBeatmapSkin, true, true);
|
||||
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);
|
||||
@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[TestCase(false)]
|
||||
public void TestBeatmapHyperDashColoursOverride(bool useBeatmapSkin)
|
||||
{
|
||||
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
||||
PrepareBeatmap(() => new CatchCustomSkinWorkingBeatmap(audio, true));
|
||||
ConfigureTest(useBeatmapSkin, false, true);
|
||||
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);
|
||||
|
@ -161,13 +161,13 @@ namespace osu.Game.Rulesets.Catch
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.LargeTickHit:
|
||||
return "large droplet";
|
||||
return "Large droplet";
|
||||
|
||||
case HitResult.SmallTickHit:
|
||||
return "small droplet";
|
||||
return "Small droplet";
|
||||
|
||||
case HitResult.LargeBonus:
|
||||
return "banana";
|
||||
return "Banana";
|
||||
}
|
||||
|
||||
return base.GetDisplayNameForHitResult(result);
|
||||
|
@ -1,8 +1,10 @@
|
||||
// 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 osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -22,59 +24,68 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
|
||||
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:
|
||||
// catch may provide its own combo counter; hide the default.
|
||||
return providesComboCounter ? Drawable.Empty() : null;
|
||||
case SkinnableTarget.MainHUDComponents:
|
||||
var components = Source.GetDrawableComponent(component) as SkinnableTargetComponentsContainer;
|
||||
|
||||
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))
|
||||
return null;
|
||||
|
||||
switch (catchSkinComponent.Component)
|
||||
if (component is CatchSkinComponent catchSkinComponent)
|
||||
{
|
||||
case CatchSkinComponents.Fruit:
|
||||
if (GetTexture("fruit-pear") != null)
|
||||
return new LegacyFruitPiece();
|
||||
switch (catchSkinComponent.Component)
|
||||
{
|
||||
case CatchSkinComponents.Fruit:
|
||||
if (GetTexture("fruit-pear") != null)
|
||||
return new LegacyFruitPiece();
|
||||
|
||||
break;
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.Banana:
|
||||
if (GetTexture("fruit-bananas") != null)
|
||||
return new LegacyBananaPiece();
|
||||
case CatchSkinComponents.Banana:
|
||||
if (GetTexture("fruit-bananas") != null)
|
||||
return new LegacyBananaPiece();
|
||||
|
||||
break;
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.Droplet:
|
||||
if (GetTexture("fruit-drop") != null)
|
||||
return new LegacyDropletPiece();
|
||||
case CatchSkinComponents.Droplet:
|
||||
if (GetTexture("fruit-drop") != null)
|
||||
return new LegacyDropletPiece();
|
||||
|
||||
break;
|
||||
return null;
|
||||
|
||||
case CatchSkinComponents.CatcherIdle:
|
||||
return this.GetAnimation("fruit-catcher-idle", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
case CatchSkinComponents.CatcherIdle:
|
||||
return this.GetAnimation("fruit-catcher-idle", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
|
||||
case CatchSkinComponents.CatcherFail:
|
||||
return this.GetAnimation("fruit-catcher-fail", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
case CatchSkinComponents.CatcherFail:
|
||||
return this.GetAnimation("fruit-catcher-fail", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
|
||||
case CatchSkinComponents.CatcherKiai:
|
||||
return this.GetAnimation("fruit-catcher-kiai", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
case CatchSkinComponents.CatcherKiai:
|
||||
return this.GetAnimation("fruit-catcher-kiai", true, true, true) ??
|
||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||
|
||||
case CatchSkinComponents.CatchComboCounter:
|
||||
if (providesComboCounter)
|
||||
return new LegacyCatchComboCounter(Source);
|
||||
case CatchSkinComponents.CatchComboCounter:
|
||||
if (providesComboCounter)
|
||||
return new LegacyCatchComboCounter(Source);
|
||||
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return Source.GetDrawableComponent(component);
|
||||
}
|
||||
|
||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
public class TestSceneManiaHitObjectSamples : HitObjectSampleTest
|
||||
{
|
||||
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>
|
||||
/// 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;
|
||||
}
|
||||
|
||||
return null;
|
||||
return Source.GetDrawableComponent(component);
|
||||
}
|
||||
|
||||
private Drawable getResult(HitResult result)
|
||||
|
@ -30,28 +30,28 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[TestCase(true, false)]
|
||||
[TestCase(false, true)]
|
||||
[TestCase(false, false)]
|
||||
public override void TestBeatmapComboColours(bool userHasCustomColours, bool useBeatmapSkin)
|
||||
public void TestBeatmapComboColours(bool userHasCustomColours, bool useBeatmapSkin)
|
||||
{
|
||||
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true);
|
||||
base.TestBeatmapComboColours(userHasCustomColours, useBeatmapSkin);
|
||||
PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, true));
|
||||
ConfigureTest(useBeatmapSkin, true, userHasCustomColours);
|
||||
AddAssert("is beatmap skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public override void TestBeatmapComboColoursOverride(bool useBeatmapSkin)
|
||||
public void TestBeatmapComboColoursOverride(bool useBeatmapSkin)
|
||||
{
|
||||
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true);
|
||||
base.TestBeatmapComboColoursOverride(useBeatmapSkin);
|
||||
PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, true));
|
||||
ConfigureTest(useBeatmapSkin, false, true);
|
||||
AddAssert("is user custom skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public override void TestBeatmapComboColoursOverrideWithDefaultColours(bool useBeatmapSkin)
|
||||
public void TestBeatmapComboColoursOverrideWithDefaultColours(bool useBeatmapSkin)
|
||||
{
|
||||
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true);
|
||||
base.TestBeatmapComboColoursOverrideWithDefaultColours(useBeatmapSkin);
|
||||
PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, true));
|
||||
ConfigureTest(useBeatmapSkin, false, false);
|
||||
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(true, false)]
|
||||
[TestCase(false, false)]
|
||||
public override void TestBeatmapNoComboColours(bool useBeatmapSkin, bool useBeatmapColour)
|
||||
public void TestBeatmapNoComboColours(bool useBeatmapSkin, bool useBeatmapColour)
|
||||
{
|
||||
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, false);
|
||||
base.TestBeatmapNoComboColours(useBeatmapSkin, useBeatmapColour);
|
||||
PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, false));
|
||||
ConfigureTest(useBeatmapSkin, useBeatmapColour, false);
|
||||
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(true, false)]
|
||||
[TestCase(false, false)]
|
||||
public override void TestBeatmapNoComboColoursSkinOverride(bool useBeatmapSkin, bool useBeatmapColour)
|
||||
public void TestBeatmapNoComboColoursSkinOverride(bool useBeatmapSkin, bool useBeatmapColour)
|
||||
{
|
||||
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, false);
|
||||
base.TestBeatmapNoComboColoursSkinOverride(useBeatmapSkin, useBeatmapColour);
|
||||
PrepareBeatmap(() => new OsuCustomSkinWorkingBeatmap(audio, false));
|
||||
ConfigureTest(useBeatmapSkin, useBeatmapColour, true);
|
||||
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.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
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;
|
||||
|
||||
@ -111,7 +112,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||
}
|
||||
|
||||
public void UpdateSnakingPosition(Vector2 start, Vector2 end) =>
|
||||
Position = HitObject.RepeatIndex % 2 == 0 ? end : start;
|
||||
protected override void OnApply()
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (!(component is OsuSkinComponent osuComponent))
|
||||
return null;
|
||||
|
||||
switch (osuComponent.Component)
|
||||
if (component is OsuSkinComponent osuComponent)
|
||||
{
|
||||
case OsuSkinComponents.FollowPoint:
|
||||
return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false);
|
||||
switch (osuComponent.Component)
|
||||
{
|
||||
case OsuSkinComponents.FollowPoint:
|
||||
return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false);
|
||||
|
||||
case OsuSkinComponents.SliderFollowCircle:
|
||||
var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
|
||||
if (followCircle != null)
|
||||
// follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x
|
||||
followCircle.Scale *= 0.5f;
|
||||
return followCircle;
|
||||
case OsuSkinComponents.SliderFollowCircle:
|
||||
var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
|
||||
if (followCircle != null)
|
||||
// follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x
|
||||
followCircle.Scale *= 0.5f;
|
||||
return followCircle;
|
||||
|
||||
case OsuSkinComponents.SliderBall:
|
||||
var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "");
|
||||
case OsuSkinComponents.SliderBall:
|
||||
var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "");
|
||||
|
||||
// todo: slider ball has a custom frame delay based on velocity
|
||||
// Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME);
|
||||
// todo: slider ball has a custom frame delay based on velocity
|
||||
// Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME);
|
||||
|
||||
if (sliderBallContent != null)
|
||||
return new LegacySliderBall(sliderBallContent);
|
||||
if (sliderBallContent != null)
|
||||
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 new LegacySpriteText(LegacyFont.HitCircle)
|
||||
{
|
||||
// stable applies a blanket 0.8x scale to hitcircle fonts
|
||||
Scale = new Vector2(0.8f),
|
||||
};
|
||||
case OsuSkinComponents.SliderBody:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacySliderBody();
|
||||
|
||||
case OsuSkinComponents.SpinnerBody:
|
||||
bool hasBackground = Source.GetTexture("spinner-background") != null;
|
||||
return null;
|
||||
|
||||
if (Source.GetTexture("spinner-top") != null && !hasBackground)
|
||||
return new LegacyNewStyleSpinner();
|
||||
else if (hasBackground)
|
||||
return new LegacyOldStyleSpinner();
|
||||
case OsuSkinComponents.SliderTailHitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece("sliderendcircle", false);
|
||||
|
||||
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)
|
||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
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("normal-hitnormal")]
|
||||
|
@ -79,8 +79,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
// Old osu! used hit sounding to determine various hit type information
|
||||
IList<HitSampleInfo> samples = obj.Samples;
|
||||
|
||||
bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
|
||||
|
||||
switch (obj)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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
|
||||
{
|
||||
StartTime = j,
|
||||
Type = isRim ? HitType.Rim : HitType.Centre,
|
||||
Samples = currentSamples,
|
||||
IsStrong = strong
|
||||
};
|
||||
|
||||
i = (i + 1) % allSamples.Count;
|
||||
@ -117,7 +111,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
{
|
||||
StartTime = obj.StartTime,
|
||||
Samples = obj.Samples,
|
||||
IsStrong = strong,
|
||||
Duration = taikoDuration,
|
||||
TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
|
||||
};
|
||||
@ -143,16 +136,10 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
|
||||
default:
|
||||
{
|
||||
bool isRimDefinition(HitSampleInfo s) => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE;
|
||||
|
||||
bool isRim = samples.Any(isRimDefinition);
|
||||
|
||||
yield return new Hit
|
||||
{
|
||||
StartTime = obj.StartTime,
|
||||
Type = isRim ? HitType.Rim : HitType.Centre,
|
||||
Samples = samples,
|
||||
IsStrong = strong
|
||||
};
|
||||
|
||||
break;
|
||||
|
@ -69,7 +69,11 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
||||
{
|
||||
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
|
||||
{
|
||||
get => TypeBindable.Value;
|
||||
set
|
||||
{
|
||||
TypeBindable.Value = value;
|
||||
updateSamplesFromType();
|
||||
}
|
||||
set => TypeBindable.Value = value;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
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 };
|
||||
|
||||
public class StrongNestedHit : StrongNestedHitObject
|
||||
|
@ -33,14 +33,21 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
public bool IsStrong
|
||||
{
|
||||
get => IsStrongBindable.Value;
|
||||
set
|
||||
{
|
||||
IsStrongBindable.Value = value;
|
||||
updateSamplesFromStrong();
|
||||
}
|
||||
set => IsStrongBindable.Value = value;
|
||||
}
|
||||
|
||||
private void updateSamplesFromStrong()
|
||||
protected TaikoStrongableHitObject()
|
||||
{
|
||||
IsStrongBindable.BindValueChanged(_ => updateSamplesFromType());
|
||||
SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples());
|
||||
}
|
||||
|
||||
private void updateTypeFromSamples()
|
||||
{
|
||||
IsStrong = getStrongSamples().Any();
|
||||
}
|
||||
|
||||
private void updateSamplesFromType()
|
||||
{
|
||||
var strongSamples = getStrongSamples();
|
||||
|
||||
|
@ -38,98 +38,98 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
return Drawable.Empty().With(d => d.Expire());
|
||||
}
|
||||
|
||||
if (!(component is TaikoSkinComponent taikoComponent))
|
||||
return null;
|
||||
|
||||
switch (taikoComponent.Component)
|
||||
if (component is TaikoSkinComponent taikoComponent)
|
||||
{
|
||||
case TaikoSkinComponents.DrumRollBody:
|
||||
if (GetTexture("taiko-roll-middle") != null)
|
||||
return new LegacyDrumRoll();
|
||||
switch (taikoComponent.Component)
|
||||
{
|
||||
case TaikoSkinComponents.DrumRollBody:
|
||||
if (GetTexture("taiko-roll-middle") != null)
|
||||
return new LegacyDrumRoll();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.InputDrum:
|
||||
if (GetTexture("taiko-bar-left") != null)
|
||||
return new LegacyInputDrum();
|
||||
case TaikoSkinComponents.InputDrum:
|
||||
if (GetTexture("taiko-bar-left") != null)
|
||||
return new LegacyInputDrum();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.CentreHit:
|
||||
case TaikoSkinComponents.RimHit:
|
||||
case TaikoSkinComponents.CentreHit:
|
||||
case TaikoSkinComponents.RimHit:
|
||||
|
||||
if (GetTexture("taikohitcircle") != null)
|
||||
return new LegacyHit(taikoComponent.Component);
|
||||
if (GetTexture("taikohitcircle") != null)
|
||||
return new LegacyHit(taikoComponent.Component);
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.DrumRollTick:
|
||||
return this.GetAnimation("sliderscorepoint", false, false);
|
||||
case TaikoSkinComponents.DrumRollTick:
|
||||
return this.GetAnimation("sliderscorepoint", false, false);
|
||||
|
||||
case TaikoSkinComponents.HitTarget:
|
||||
if (GetTexture("taikobigcircle") != null)
|
||||
return new TaikoLegacyHitTarget();
|
||||
case TaikoSkinComponents.HitTarget:
|
||||
if (GetTexture("taikobigcircle") != null)
|
||||
return new TaikoLegacyHitTarget();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.PlayfieldBackgroundRight:
|
||||
if (GetTexture("taiko-bar-right") != null)
|
||||
return new TaikoLegacyPlayfieldBackgroundRight();
|
||||
case TaikoSkinComponents.PlayfieldBackgroundRight:
|
||||
if (GetTexture("taiko-bar-right") != null)
|
||||
return new TaikoLegacyPlayfieldBackgroundRight();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.PlayfieldBackgroundLeft:
|
||||
// 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)
|
||||
return Drawable.Empty();
|
||||
case TaikoSkinComponents.PlayfieldBackgroundLeft:
|
||||
// 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)
|
||||
return Drawable.Empty();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.BarLine:
|
||||
if (GetTexture("taiko-barline") != null)
|
||||
return new LegacyBarLine();
|
||||
case TaikoSkinComponents.BarLine:
|
||||
if (GetTexture("taiko-barline") != null)
|
||||
return new LegacyBarLine();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.TaikoExplosionMiss:
|
||||
case TaikoSkinComponents.TaikoExplosionMiss:
|
||||
|
||||
var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false);
|
||||
if (missSprite != null)
|
||||
return new LegacyHitExplosion(missSprite);
|
||||
var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false);
|
||||
if (missSprite != null)
|
||||
return new LegacyHitExplosion(missSprite);
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.TaikoExplosionOk:
|
||||
case TaikoSkinComponents.TaikoExplosionGreat:
|
||||
case TaikoSkinComponents.TaikoExplosionOk:
|
||||
case TaikoSkinComponents.TaikoExplosionGreat:
|
||||
|
||||
var hitName = getHitName(taikoComponent.Component);
|
||||
var hitSprite = this.GetAnimation(hitName, true, false);
|
||||
var hitName = getHitName(taikoComponent.Component);
|
||||
var hitSprite = this.GetAnimation(hitName, true, false);
|
||||
|
||||
if (hitSprite != null)
|
||||
{
|
||||
var strongHitSprite = this.GetAnimation($"{hitName}k", true, false);
|
||||
if (hitSprite != null)
|
||||
{
|
||||
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:
|
||||
// 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.
|
||||
if (hasExplosion.Value)
|
||||
return Drawable.Empty().With(d => d.Expire());
|
||||
case TaikoSkinComponents.TaikoExplosionKiai:
|
||||
// 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.
|
||||
if (hasExplosion.Value)
|
||||
return Drawable.Empty().With(d => d.Expire());
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.Scroller:
|
||||
if (GetTexture("taiko-slider") != null)
|
||||
return new LegacyTaikoScroller();
|
||||
case TaikoSkinComponents.Scroller:
|
||||
if (GetTexture("taiko-slider") != null)
|
||||
return new LegacyTaikoScroller();
|
||||
|
||||
return null;
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.Mascot:
|
||||
return new DrawableTaikoMascot();
|
||||
case TaikoSkinComponents.Mascot:
|
||||
return new DrawableTaikoMascot();
|
||||
}
|
||||
}
|
||||
|
||||
return Source.GetDrawableComponent(component);
|
||||
|
@ -169,6 +169,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||
|
||||
protected override ISkin GetSkin() => 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);
|
||||
|
||||
await osu.CollectionManager.Import(new MemoryStream());
|
||||
await importCollectionsFromStream(osu, new MemoryStream());
|
||||
|
||||
Assert.That(osu.CollectionManager.Collections.Count, Is.Zero);
|
||||
}
|
||||
@ -43,7 +43,7 @@ namespace osu.Game.Tests.Collections.IO
|
||||
{
|
||||
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));
|
||||
|
||||
@ -69,7 +69,7 @@ namespace osu.Game.Tests.Collections.IO
|
||||
{
|
||||
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));
|
||||
|
||||
@ -110,7 +110,7 @@ namespace osu.Game.Tests.Collections.IO
|
||||
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
await osu.CollectionManager.Import(ms);
|
||||
await importCollectionsFromStream(osu, ms);
|
||||
}
|
||||
|
||||
Assert.That(host.UpdateThread.Running, Is.True);
|
||||
@ -134,7 +134,7 @@ namespace osu.Game.Tests.Collections.IO
|
||||
{
|
||||
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.
|
||||
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 osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
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);
|
||||
}
|
||||
|
||||
[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
|
||||
{
|
||||
public const double INITIAL_LIFETIME_OFFSET = 100;
|
||||
public const double LIFETIME_ON_APPLY = 222;
|
||||
protected override double InitialLifetimeOffset => INITIAL_LIFETIME_OFFSET;
|
||||
|
||||
public bool SetLifetimeStartOnApply;
|
||||
|
||||
public TestDrawableHitObject(HitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnApply()
|
||||
{
|
||||
base.OnApply();
|
||||
|
||||
if (SetLifetimeStartOnApply)
|
||||
LifetimeStart = LIFETIME_ON_APPLY;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestLifetimeEntry : HitObjectLifetimeEntry
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
public class TestSceneHitObjectSamples : HitObjectSampleTest
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||
protected override IResourceStore<byte[]> Resources => TestResources.GetStore();
|
||||
protected override IResourceStore<byte[]> RulesetResources => TestResources.GetStore();
|
||||
|
||||
/// <summary>
|
||||
/// 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 IResourceStore<byte[]> Files => null;
|
||||
public new IResourceStore<byte[]> Resources => base.Resources;
|
||||
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null;
|
||||
|
||||
#endregion
|
||||
|
@ -12,6 +12,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -44,7 +45,7 @@ namespace osu.Game.Tests.Online
|
||||
private void load(AudioManager audio, GameHost host)
|
||||
{
|
||||
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]
|
||||
@ -160,8 +161,8 @@ namespace osu.Game.Tests.Online
|
||||
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize)
|
||||
=> 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)
|
||||
: base(storage, contextFactory, rulesets, api, audioManager, host, defaultBeatmap, performOnlineLookups)
|
||||
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, resources, host, defaultBeatmap, performOnlineLookups)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
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));
|
||||
|
||||
manager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Collections
|
||||
private void load(GameHost host)
|
||||
{
|
||||
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();
|
||||
|
||||
|
@ -24,13 +24,13 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
protected override bool EditorComponentsReady => Editor.ChildrenOfType<SetupScreen>().SingleOrDefault()?.IsLoaded == true;
|
||||
|
||||
protected override bool IsolateSavingFromDatabase => false;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmapManager { get; set; }
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
AddStep("set dummy", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null));
|
||||
|
||||
base.SetUpSteps();
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
protected override void LoadEditor()
|
||||
{
|
||||
Beatmap.Value = new DummyWorkingBeatmap(Audio, null);
|
||||
base.LoadEditor();
|
||||
}
|
||||
|
||||
[Test]
|
||||
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();
|
||||
|
||||
prepareBeatmap();
|
||||
|
||||
LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)));
|
||||
}
|
||||
|
||||
private void prepareBeatmap()
|
||||
{
|
||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
Beatmap.Value.BeatmapInfo.EpilepsyWarning = epilepsyWarning;
|
||||
|
||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>())
|
||||
mod.ApplyToTrack(Beatmap.Value.Track);
|
||||
|
||||
LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -178,10 +183,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddStep("load slow dummy beatmap", () =>
|
||||
{
|
||||
LoadScreen(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
|
||||
Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000);
|
||||
prepareBeatmap();
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
CreateTest(null);
|
||||
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||
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]
|
||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
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]
|
||||
|
@ -73,8 +73,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
for (int i = 0; i < users; i++)
|
||||
spectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
||||
|
||||
Client.CurrentMatchPlayingUserIds.Clear();
|
||||
Client.CurrentMatchPlayingUserIds.AddRange(spectatorClient.PlayingUsers);
|
||||
spectatorClient.Schedule(() =>
|
||||
{
|
||||
Client.CurrentMatchPlayingUserIds.Clear();
|
||||
Client.CurrentMatchPlayingUserIds.AddRange(spectatorClient.PlayingUsers);
|
||||
});
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -91,6 +94,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
|
||||
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
||||
AddUntilStep("wait for user population", () => Client.CurrentMatchPlayingUserIds.Count > 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
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>();
|
||||
|
||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
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();
|
||||
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
|
||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
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();
|
||||
|
||||
Add(beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker
|
||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
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 } };
|
||||
base.Content.Add(beatmapTracker);
|
||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
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>();
|
||||
|
||||
|
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)
|
||||
{
|
||||
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();
|
||||
|
||||
|
@ -70,6 +70,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
AddStep("click first row", () =>
|
||||
{
|
||||
firstRow = panel.ChildrenOfType<KeyBindingRow>().First();
|
||||
|
||||
InputManager.MoveMouseTo(firstRow);
|
||||
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]
|
||||
public void TestClickRowSelectsFirstBinding()
|
||||
{
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Settings
|
||||
{
|
||||
@ -37,7 +38,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
|
||||
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) =>
|
||||
AddAssert($"{count} {(diff ? "diffs" : "sets")} visible", () =>
|
||||
private void checkVisibleItemCount(bool diff, int count)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null);
|
||||
|
||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
private void load(GameHost host)
|
||||
{
|
||||
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();
|
||||
|
||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
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());
|
||||
|
||||
@ -358,7 +358,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target));
|
||||
|
||||
// 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]
|
||||
@ -390,7 +390,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddUntilStep("has correct ruleset", () => Ruleset.Value.ID == 0);
|
||||
|
||||
// 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]
|
||||
@ -781,7 +781,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
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]
|
||||
|
@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
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));
|
||||
|
||||
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.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
|
||||
@ -11,13 +13,20 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[TestFixture]
|
||||
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,
|
||||
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,
|
||||
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
|
||||
|
@ -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]
|
||||
public void TestInlineCode()
|
||||
{
|
||||
|
@ -11,6 +11,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
@ -52,6 +53,8 @@ namespace osu.Game.Tests
|
||||
|
||||
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
|
||||
|
||||
protected override ISkin GetSkin() => null;
|
||||
|
||||
public override Stream GetStream(string storagePath) => null;
|
||||
|
||||
protected override Track GetBeatmapTrack() => trackStore.Get(firstAudioFile);
|
||||
|
@ -72,6 +72,7 @@ namespace osu.Game.Beatmaps
|
||||
private readonly RulesetStore rulesets;
|
||||
private readonly BeatmapStore beatmaps;
|
||||
private readonly AudioManager audioManager;
|
||||
private readonly IResourceStore<byte[]> resources;
|
||||
private readonly LargeTextureStore largeTextureStore;
|
||||
private readonly ITrackStore trackStore;
|
||||
|
||||
@ -81,12 +82,13 @@ namespace osu.Game.Beatmaps
|
||||
[CanBeNull]
|
||||
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)
|
||||
: base(storage, contextFactory, api, new BeatmapStore(contextFactory), host)
|
||||
{
|
||||
this.rulesets = rulesets;
|
||||
this.audioManager = audioManager;
|
||||
this.resources = resources;
|
||||
this.host = host;
|
||||
|
||||
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="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>
|
||||
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;
|
||||
|
||||
@ -280,23 +282,17 @@ namespace osu.Game.Beatmaps
|
||||
/// Retrieve a <see cref="WorkingBeatmap"/> instance for the provided <see cref="BeatmapInfo"/>
|
||||
/// </summary>
|
||||
/// <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>
|
||||
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)
|
||||
return previous;
|
||||
|
||||
if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
|
||||
return DefaultBeatmap;
|
||||
|
||||
if (beatmapInfo.BeatmapSet.Files == null)
|
||||
// if there are no files, presume the full beatmap info has not yet been fetched from the database.
|
||||
if (beatmapInfo?.BeatmapSet?.Files.Count == 0)
|
||||
{
|
||||
var info = beatmapInfo;
|
||||
beatmapInfo = QueryBeatmap(b => b.ID == info.ID);
|
||||
int lookupId = beatmapInfo.ID;
|
||||
beatmapInfo = QueryBeatmap(b => b.ID == lookupId);
|
||||
}
|
||||
|
||||
if (beatmapInfo == null)
|
||||
if (beatmapInfo?.BeatmapSet == null)
|
||||
return DefaultBeatmap;
|
||||
|
||||
lock (workingCache)
|
||||
@ -506,6 +502,7 @@ namespace osu.Game.Beatmaps
|
||||
ITrackStore IBeatmapResourceProvider.Tracks => trackStore;
|
||||
AudioManager IStorageResourceProvider.AudioManager => audioManager;
|
||||
IResourceStore<byte[]> IStorageResourceProvider.Files => Files.Store;
|
||||
IResourceStore<byte[]> IStorageResourceProvider.Resources => resources;
|
||||
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host?.CreateTextureLoaderStore(underlyingStore);
|
||||
|
||||
#endregion
|
||||
@ -526,6 +523,7 @@ namespace osu.Game.Beatmaps
|
||||
protected override IBeatmap GetBeatmap() => beatmap;
|
||||
protected override Texture GetBackground() => null;
|
||||
protected override Track GetBeatmapTrack() => null;
|
||||
protected override ISkin GetSkin() => null;
|
||||
public override Stream GetStream(string storagePath) => null;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Database;
|
||||
|
||||
@ -31,6 +32,9 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public List<BeatmapInfo> Beatmaps { get; set; }
|
||||
|
||||
[NotNull]
|
||||
public List<BeatmapSetFileInfo> Files { get; set; } = new List<BeatmapSetFileInfo>();
|
||||
|
||||
[NotMapped]
|
||||
public BeatmapSetOnlineInfo OnlineInfo { get; set; }
|
||||
|
||||
@ -57,16 +61,14 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
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>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <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 List<BeatmapSetFileInfo> Files { get; set; }
|
||||
public string GetPathForFile(string filename) => Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath;
|
||||
|
||||
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.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
@ -49,6 +50,8 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
protected override Track GetBeatmapTrack() => GetVirtualTrack();
|
||||
|
||||
protected override ISkin GetSkin() => null;
|
||||
|
||||
public override Stream GetStream(string storagePath) => null;
|
||||
|
||||
private class DummyRulesetInfo : RulesetInfo
|
||||
|
@ -324,7 +324,7 @@ namespace osu.Game.Beatmaps
|
||||
public bool SkinLoaded => skin.IsResultAvailable;
|
||||
public ISkin Skin => skin.Value;
|
||||
|
||||
protected virtual ISkin GetSkin() => new DefaultSkin(null);
|
||||
protected abstract ISkin GetSkin();
|
||||
private readonly RecyclableLazy<ISkin> skin;
|
||||
|
||||
public abstract Stream GetStream(string storagePath);
|
||||
|
@ -58,8 +58,13 @@ namespace osu.Game.Collections
|
||||
|
||||
if (storage.Exists(database_name))
|
||||
{
|
||||
List<BeatmapCollection> beatmapCollections;
|
||||
|
||||
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.Yaml;
|
||||
using Markdig.Syntax;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
@ -21,6 +23,16 @@ namespace osu.Game.Graphics.Containers.Markdown
|
||||
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)
|
||||
{
|
||||
switch (markdownObject)
|
||||
|
@ -1,48 +1,63 @@
|
||||
// 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 Markdig.Syntax.Inlines;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
public class OsuMarkdownLinkText : MarkdownLinkText
|
||||
{
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; }
|
||||
[Resolved(canBeNull: true)]
|
||||
private OsuGame game { get; set; }
|
||||
|
||||
private SpriteText spriteText;
|
||||
private readonly string text;
|
||||
private readonly string title;
|
||||
|
||||
public OsuMarkdownLinkText(string text, LinkInline linkInline)
|
||||
: base(text, linkInline)
|
||||
{
|
||||
this.text = text;
|
||||
title = linkInline.Title;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
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()
|
||||
{
|
||||
return spriteText = base.CreateSpriteText();
|
||||
}
|
||||
protected override void OnLinkPressed() => game?.HandleLink(Url);
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
private class OsuMarkdownLinkCompiler : DrawableLinkCompiler
|
||||
{
|
||||
spriteText.Colour = colourProvider.Light1;
|
||||
return base.OnHover(e);
|
||||
}
|
||||
public OsuMarkdownLinkCompiler(IEnumerable<Drawable> parts)
|
||||
: base(parts)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
spriteText.Colour = colourProvider.Light2;
|
||||
base.OnHoverLost(e);
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
IdleColour = colourProvider.Light2;
|
||||
HoverColour = colourProvider.Light1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,11 @@ namespace osu.Game.IO
|
||||
/// </summary>
|
||||
IResourceStore<byte[]> Files { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Access game-wide resources.
|
||||
/// </summary>
|
||||
IResourceStore<byte[]> Resources { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a texture loader store based on an underlying data store.
|
||||
/// </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);
|
||||
|
||||
if (!IsPlaying)
|
||||
return;
|
||||
|
||||
if (frame is IConvertibleReplayFrame convertible)
|
||||
pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap));
|
||||
|
||||
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
@ -41,7 +40,6 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Utils;
|
||||
using osuTK.Input;
|
||||
using RuntimeInfo = osu.Framework.RuntimeInfo;
|
||||
|
||||
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
|
||||
/// for provide dependencies to test cases without interfering with them.
|
||||
/// </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;
|
||||
|
||||
/// <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; }
|
||||
|
||||
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();
|
||||
|
||||
/// <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()
|
||||
{
|
||||
UseDevelopmentServer = DebugUtils.IsDebugBuild;
|
||||
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]
|
||||
private void load()
|
||||
{
|
||||
@ -217,7 +209,7 @@ namespace osu.Game
|
||||
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
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()
|
||||
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(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => difficultyCache, LocalConfig));
|
||||
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
|
||||
// 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);
|
||||
});
|
||||
|
||||
dependencies.Cache(DifficultyCache = new BeatmapDifficultyCache());
|
||||
AddInternal(DifficultyCache);
|
||||
dependencies.Cache(difficultyCache = new BeatmapDifficultyCache());
|
||||
AddInternal(difficultyCache);
|
||||
|
||||
dependencies.Cache(UserCache = new UserLookupCache());
|
||||
AddInternal(UserCache);
|
||||
dependencies.Cache(userCache = new UserLookupCache());
|
||||
AddInternal(userCache);
|
||||
|
||||
var scorePerformanceManager = new ScorePerformanceCache();
|
||||
dependencies.Cache(scorePerformanceManager);
|
||||
AddInternal(scorePerformanceManager);
|
||||
|
||||
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
|
||||
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
|
||||
dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore));
|
||||
dependencies.Cache(settingsStore = new SettingsStore(contextFactory));
|
||||
dependencies.Cache(rulesetConfigCache = new RulesetConfigCache(settingsStore));
|
||||
|
||||
var powerStatus = CreateBatteryInfo();
|
||||
if (powerStatus != null)
|
||||
@ -308,7 +300,7 @@ namespace osu.Game
|
||||
dependencies.CacheAs<IBindable<WorkingBeatmap>>(Beatmap);
|
||||
dependencies.CacheAs(Beatmap);
|
||||
|
||||
FileStore.Cleanup();
|
||||
fileStore.Cleanup();
|
||||
|
||||
// add api components to hierarchy.
|
||||
if (API is APIAccess apiAccess)
|
||||
@ -316,7 +308,7 @@ namespace osu.Game
|
||||
AddInternal(spectatorClient);
|
||||
AddInternal(multiplayerClient);
|
||||
|
||||
AddInternal(RulesetConfigCache);
|
||||
AddInternal(rulesetConfigCache);
|
||||
|
||||
GlobalActionContainer globalBindings;
|
||||
|
||||
@ -344,24 +336,6 @@ namespace osu.Game
|
||||
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()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -375,28 +349,8 @@ namespace osu.Game
|
||||
FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
||||
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
public override void SetHost(GameHost host)
|
||||
{
|
||||
@ -422,51 +376,59 @@ namespace osu.Game
|
||||
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);
|
||||
|
||||
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)
|
||||
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> r)
|
||||
{
|
||||
if (paths.Length == 0)
|
||||
return;
|
||||
var dict = new Dictionary<ModType, IReadOnlyList<Mod>>();
|
||||
|
||||
var filesPerExtension = paths.GroupBy(p => Path.GetExtension(p).ToLowerInvariant());
|
||||
|
||||
foreach (var groups in filesPerExtension)
|
||||
if (r.NewValue?.Available == true)
|
||||
{
|
||||
foreach (var importer in fileImporters)
|
||||
{
|
||||
if (importer.HandledExtensions.Contains(groups.Key))
|
||||
await importer.Import(groups.ToArray()).ConfigureAwait(false);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
@ -477,37 +439,5 @@ namespace osu.Game
|
||||
|
||||
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()
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (e.Repeat) return false;
|
||||
|
@ -35,15 +35,16 @@ namespace osu.Game.Overlays
|
||||
|
||||
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 = dialog;
|
||||
CurrentDialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue);
|
||||
|
||||
dialogContainer.Add(CurrentDialog);
|
||||
|
||||
CurrentDialog.Show();
|
||||
CurrentDialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue);
|
||||
Show();
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
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; }
|
||||
|
||||
private OsuSpriteText text;
|
||||
private FillFlowContainer cancelAndClearButtons;
|
||||
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 KeyBindingRow(object action, IEnumerable<Framework.Input.Bindings.KeyBinding> bindings)
|
||||
@ -61,9 +69,6 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = padding;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
@ -72,51 +77,72 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Radius = 2,
|
||||
Colour = colours.YellowDark.Opacity(0),
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Hollow = true,
|
||||
};
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS };
|
||||
|
||||
Children = new Drawable[]
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
new RestoreDefaultValueButton<bool>
|
||||
{
|
||||
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,
|
||||
Current = isDefault,
|
||||
Action = RestoreDefaults,
|
||||
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[]
|
||||
{
|
||||
new CancelButton { Action = finalise },
|
||||
new ClearButton { Action = clear },
|
||||
},
|
||||
new Box
|
||||
{
|
||||
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)
|
||||
buttons.Add(new KeyButton(b));
|
||||
|
||||
updateIsDefaultValue();
|
||||
}
|
||||
|
||||
public void RestoreDefaults()
|
||||
@ -129,18 +155,20 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
button.UpdateKeyCombination(d);
|
||||
store.Update(button.KeyBinding);
|
||||
}
|
||||
|
||||
isDefault.Value = true;
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
FadeEdgeEffectTo(1, transition_time, Easing.OutQuint);
|
||||
content.FadeEdgeEffectTo(1, transition_time, Easing.OutQuint);
|
||||
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
FadeEdgeEffectTo(0, transition_time, Easing.OutQuint);
|
||||
content.FadeEdgeEffectTo(0, transition_time, Easing.OutQuint);
|
||||
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
@ -288,6 +316,8 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
{
|
||||
store.Update(bindTarget.KeyBinding);
|
||||
|
||||
updateIsDefaultValue();
|
||||
|
||||
bindTarget.IsBinding = false;
|
||||
Schedule(() =>
|
||||
{
|
||||
@ -305,8 +335,8 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
{
|
||||
AutoSizeDuration = 500;
|
||||
AutoSizeEasing = Easing.OutQuint;
|
||||
content.AutoSizeDuration = 500;
|
||||
content.AutoSizeEasing = Easing.OutQuint;
|
||||
|
||||
cancelAndClearButtons.FadeIn(300, Easing.OutQuint);
|
||||
cancelAndClearButtons.BypassAutoSizeAxes &= ~Axes.Y;
|
||||
@ -331,6 +361,11 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
if (bindTarget != null) bindTarget.IsBinding = true;
|
||||
}
|
||||
|
||||
private void updateIsDefaultValue()
|
||||
{
|
||||
isDefault.Value = bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults);
|
||||
}
|
||||
|
||||
private class CancelButton : TriangleButton
|
||||
{
|
||||
public CancelButton()
|
||||
@ -379,9 +414,6 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
|
||||
Margin = new MarginPadding(padding);
|
||||
|
||||
// todo: use this in a meaningful way
|
||||
// var isDefault = keyBinding.Action is Enum;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = padding;
|
||||
|
||||
|
@ -61,8 +61,11 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
{
|
||||
Text = "Reset all bindings in section";
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Margin = new MarginPadding { Top = 5 };
|
||||
Height = 20;
|
||||
Width = 0.5f;
|
||||
Anchor = Anchor.TopCentre;
|
||||
Origin = Anchor.TopCentre;
|
||||
Margin = new MarginPadding { Top = 15 };
|
||||
Height = 30;
|
||||
|
||||
Content.CornerRadius = 5;
|
||||
}
|
||||
|
@ -252,7 +252,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
if (playable != null)
|
||||
{
|
||||
changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value));
|
||||
changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First()));
|
||||
restartTrack();
|
||||
return PreviousTrackResult.Previous;
|
||||
}
|
||||
@ -283,7 +283,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
if (playable != null)
|
||||
{
|
||||
changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value));
|
||||
changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First()));
|
||||
restartTrack();
|
||||
return true;
|
||||
}
|
||||
|
@ -2,14 +2,13 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osuTK;
|
||||
|
||||
@ -20,26 +19,16 @@ namespace osu.Game.Overlays.News.Displays
|
||||
/// </summary>
|
||||
public class ArticleListing : CompositeDrawable
|
||||
{
|
||||
public Action<APINewsSidebar> SidebarMetadataUpdated;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
private readonly Action fetchMorePosts;
|
||||
|
||||
private FillFlowContainer content;
|
||||
private ShowMoreButton showMore;
|
||||
|
||||
private GetNewsRequest request;
|
||||
private Cursor lastCursor;
|
||||
private CancellationTokenSource cancellationToken;
|
||||
|
||||
private readonly int? year;
|
||||
|
||||
/// <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)
|
||||
public ArticleListing(Action fetchMorePosts)
|
||||
{
|
||||
this.year = year;
|
||||
this.fetchMorePosts = fetchMorePosts;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -47,6 +36,7 @@ namespace osu.Game.Overlays.News.Displays
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Vertical = 20,
|
||||
@ -75,53 +65,25 @@ namespace osu.Game.Overlays.News.Displays
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Top = 15
|
||||
},
|
||||
Action = performFetch,
|
||||
Margin = new MarginPadding { Top = 15 },
|
||||
Action = fetchMorePosts,
|
||||
Alpha = 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
performFetch();
|
||||
}
|
||||
|
||||
private void performFetch()
|
||||
{
|
||||
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 =>
|
||||
public void AddPosts(IEnumerable<APINewsPost> posts, bool morePostsAvailable) => Schedule(() =>
|
||||
LoadComponentsAsync(posts.Select(p => new NewsCard(p)).ToList(), loaded =>
|
||||
{
|
||||
content.AddRange(loaded);
|
||||
|
||||
showMore.IsLoading = false;
|
||||
showMore.Alpha = response.Cursor != null ? 1 : 0;
|
||||
}, (cancellationToken = new CancellationTokenSource()).Token);
|
||||
}
|
||||
showMore.Alpha = morePostsAvailable ? 1 : 0;
|
||||
}, (cancellationToken = new CancellationTokenSource()).Token)
|
||||
);
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
request?.Cancel();
|
||||
cancellationToken?.Cancel();
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using System.Threading;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays.News;
|
||||
using osu.Game.Overlays.News.Displays;
|
||||
using osu.Game.Overlays.News.Sidebar;
|
||||
@ -14,13 +15,21 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
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 NewsSidebar sidebar;
|
||||
|
||||
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 bool displayUpdateRequired = true;
|
||||
@ -65,7 +74,13 @@ namespace osu.Game.Overlays
|
||||
base.LoadComplete();
|
||||
|
||||
// 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 };
|
||||
@ -95,7 +110,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
public void ShowYear(int year)
|
||||
{
|
||||
loadFrontPage(year);
|
||||
loadListing(year);
|
||||
Show();
|
||||
}
|
||||
|
||||
@ -108,7 +123,11 @@ namespace osu.Game.Overlays
|
||||
protected void LoadDisplay(Drawable display)
|
||||
{
|
||||
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()
|
||||
@ -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));
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
var page = new ArticleListing(year);
|
||||
page.SidebarMetadataUpdated += metadata => Schedule(() =>
|
||||
displayedYear = year;
|
||||
lastCursor = null;
|
||||
|
||||
beginLoading(true);
|
||||
|
||||
request = new GetNewsRequest(displayedYear);
|
||||
request.Success += response => Schedule(() =>
|
||||
{
|
||||
sidebar.Metadata.Value = metadata;
|
||||
Loading.Hide();
|
||||
lastCursor = response.Cursor;
|
||||
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)
|
||||
{
|
||||
beginLoading();
|
||||
// This is not yet implemented nor called from anywhere.
|
||||
beginLoading(true);
|
||||
|
||||
Header.SetArticle(article);
|
||||
|
||||
// Temporary, should be handled by ArticleDisplay later
|
||||
LoadDisplay(Empty());
|
||||
Loading.Hide();
|
||||
}
|
||||
|
||||
private void beginLoading()
|
||||
private void beginLoading(bool showLoadingOverlay)
|
||||
{
|
||||
request?.Cancel();
|
||||
cancellationToken?.Cancel();
|
||||
Loading.Show();
|
||||
|
||||
if (showLoadingOverlay)
|
||||
Loading.Show();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
request?.Cancel();
|
||||
cancellationToken?.Cancel();
|
||||
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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -108,7 +103,7 @@ namespace osu.Game.Overlays.Settings
|
||||
|
||||
protected SettingsItem()
|
||||
{
|
||||
RestoreDefaultValueButton restoreDefaultButton;
|
||||
RestoreDefaultValueButton<T> restoreDefaultButton;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
@ -116,7 +111,7 @@ namespace osu.Game.Overlays.Settings
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
restoreDefaultButton = new RestoreDefaultValueButton(),
|
||||
restoreDefaultButton = new RestoreDefaultValueButton<T>(),
|
||||
FlowContent = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@ -137,7 +132,7 @@ namespace osu.Game.Overlays.Settings
|
||||
controlWithCurrent.Current.DisabledChanged += _ => updateDisabled();
|
||||
|
||||
if (ShowsDefaultIndicator)
|
||||
restoreDefaultButton.Bindable = controlWithCurrent.Current;
|
||||
restoreDefaultButton.Current = controlWithCurrent.Current;
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,101 +141,5 @@ namespace osu.Game.Overlays.Settings
|
||||
if (labelText != null)
|
||||
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.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
@ -49,8 +50,6 @@ namespace osu.Game.Overlays
|
||||
|
||||
private readonly bool showSidebar;
|
||||
|
||||
protected Box Background;
|
||||
|
||||
protected SettingsPanel(bool showSidebar)
|
||||
{
|
||||
this.showSidebar = showSidebar;
|
||||
@ -63,13 +62,13 @@ namespace osu.Game.Overlays
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChild = ContentContainer = new Container
|
||||
InternalChild = ContentContainer = new NonMaskedContent
|
||||
{
|
||||
Width = WIDTH,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Background = new Box
|
||||
new Box
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
@ -165,7 +164,7 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
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);
|
||||
this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint);
|
||||
@ -191,6 +190,12 @@ namespace osu.Game.Overlays
|
||||
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 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.Extensions.TypeExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Performance;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Audio;
|
||||
@ -311,6 +310,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
protected virtual void OnApply()
|
||||
{
|
||||
@ -318,6 +318,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
protected virtual void OnFree()
|
||||
{
|
||||
@ -443,9 +444,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <remarks>
|
||||
/// 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);
|
||||
|
||||
/// <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"/>.
|
||||
/// </summary>
|
||||
/// <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.
|
||||
/// 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>
|
||||
/// The initial transformation (<see cref="UpdateInitialTransforms"/>) starts at this offset before the start time of <see cref="HitObject"/>.
|
||||
/// </remarks>
|
||||
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;
|
||||
|
||||
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.
|
||||
@ -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.
|
||||
/// </summary>
|
||||
/// <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.
|
||||
/// 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 for further optimisation (in <see cref="DrawableHitObject.LoadComplete"/>, for example).
|
||||
/// This is only used as an optimisation to delay the initial application of the <see cref="HitObject"/> to a <see cref="DrawableHitObject"/>.
|
||||
/// A more accurate <see cref="LifetimeEntry.LifetimeStart"/> should be set on the hit object application, for further optimisation.
|
||||
/// </remarks>
|
||||
protected virtual double InitialLifetimeOffset => 10000;
|
||||
|
||||
/// <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>
|
||||
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;
|
||||
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);
|
||||
if (drawable == null)
|
||||
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
|
||||
|
||||
aliveDrawableMap[entry] = drawable;
|
||||
|
||||
if (isPooled)
|
||||
{
|
||||
addDrawable(drawable);
|
||||
HitObjectUsageBegan?.Invoke(entry.HitObject);
|
||||
}
|
||||
|
||||
OnAdd(drawable);
|
||||
|
||||
if (isNonPooled) return;
|
||||
|
||||
addDrawable(drawable);
|
||||
HitObjectUsageBegan?.Invoke(entry.HitObject);
|
||||
}
|
||||
|
||||
private void entryBecameDead(LifetimeEntry lifetimeEntry)
|
||||
@ -142,17 +144,18 @@ namespace osu.Game.Rulesets.UI
|
||||
Debug.Assert(aliveDrawableMap.ContainsKey(entry));
|
||||
|
||||
var drawable = aliveDrawableMap[entry];
|
||||
bool isNonPooled = nonPooledDrawableMap.ContainsKey(entry);
|
||||
bool isPooled = !nonPooledDrawableMap.ContainsKey(entry);
|
||||
|
||||
drawable.OnKilled();
|
||||
aliveDrawableMap.Remove(entry);
|
||||
|
||||
if (isPooled)
|
||||
{
|
||||
removeDrawable(drawable);
|
||||
HitObjectUsageFinished?.Invoke(entry.HitObject);
|
||||
}
|
||||
|
||||
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)
|
||||
@ -211,21 +214,16 @@ namespace osu.Game.Rulesets.UI
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="DrawableHitObject"/> is added to this container.
|
||||
/// Invoked after a <see cref="DrawableHitObject"/> is added to this container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is not invoked for nested <see cref="DrawableHitObject"/>s.
|
||||
/// </remarks>
|
||||
protected virtual void OnAdd(DrawableHitObject drawableHitObject)
|
||||
{
|
||||
Debug.Assert(drawableHitObject.LoadState >= LoadState.Ready);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="DrawableHitObject"/> is removed from this container.
|
||||
/// Invoked after a <see cref="DrawableHitObject"/> is removed from this container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is not invoked for nested <see cref="DrawableHitObject"/>s.
|
||||
/// </remarks>
|
||||
protected virtual void OnRemove(DrawableHitObject drawableHitObject)
|
||||
{
|
||||
}
|
||||
|
@ -18,12 +18,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
/// <summary>
|
||||
/// Hit objects which require lifetime computation in the next update call.
|
||||
/// </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.
|
||||
/// A set of top-level <see cref="DrawableHitObject"/>s which have an up-to-date layout.
|
||||
/// </summary>
|
||||
private readonly HashSet<DrawableHitObject> layoutComputed = new HashSet<DrawableHitObject>();
|
||||
|
||||
@ -54,7 +49,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
{
|
||||
base.Clear();
|
||||
|
||||
toComputeLifetime.Clear();
|
||||
layoutComputed.Clear();
|
||||
}
|
||||
|
||||
@ -83,7 +77,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
|
||||
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>
|
||||
@ -91,7 +85,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
@ -106,16 +100,19 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
}
|
||||
}
|
||||
|
||||
private float getLength()
|
||||
private float scrollLength
|
||||
{
|
||||
switch (scrollingInfo.Direction.Value)
|
||||
get
|
||||
{
|
||||
case ScrollingDirection.Left:
|
||||
case ScrollingDirection.Right:
|
||||
return DrawWidth;
|
||||
switch (scrollingInfo.Direction.Value)
|
||||
{
|
||||
case ScrollingDirection.Left:
|
||||
case ScrollingDirection.Right:
|
||||
return DrawWidth;
|
||||
|
||||
default:
|
||||
return DrawHeight;
|
||||
default:
|
||||
return DrawHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,81 +147,40 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAdd(DrawableHitObject drawableHitObject) => onAddRecursive(drawableHitObject);
|
||||
|
||||
protected override void OnRemove(DrawableHitObject drawableHitObject) => onRemoveRecursive(drawableHitObject);
|
||||
|
||||
private void onAddRecursive(DrawableHitObject hitObject)
|
||||
protected override void OnAdd(DrawableHitObject drawableHitObject)
|
||||
{
|
||||
invalidateHitObject(hitObject);
|
||||
|
||||
hitObject.DefaultsApplied += invalidateHitObject;
|
||||
|
||||
foreach (var nested in hitObject.NestedHitObjects)
|
||||
onAddRecursive(nested);
|
||||
invalidateHitObject(drawableHitObject);
|
||||
drawableHitObject.DefaultsApplied += invalidateHitObject;
|
||||
}
|
||||
|
||||
private void onRemoveRecursive(DrawableHitObject hitObject)
|
||||
protected override void OnRemove(DrawableHitObject drawableHitObject)
|
||||
{
|
||||
toComputeLifetime.Remove(hitObject);
|
||||
layoutComputed.Remove(hitObject);
|
||||
layoutComputed.Remove(drawableHitObject);
|
||||
|
||||
hitObject.DefaultsApplied -= invalidateHitObject;
|
||||
|
||||
foreach (var nested in hitObject.NestedHitObjects)
|
||||
onRemoveRecursive(nested);
|
||||
drawableHitObject.DefaultsApplied -= invalidateHitObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make this <see cref="DrawableHitObject"/> lifetime and layout computed in next update.
|
||||
/// </summary>
|
||||
private void invalidateHitObject(DrawableHitObject hitObject)
|
||||
{
|
||||
// Lifetime computation is delayed until next update because
|
||||
// when the hit object is not pooled this container is not loaded here and `scrollLength` cannot be computed.
|
||||
toComputeLifetime.Add(hitObject);
|
||||
hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject);
|
||||
layoutComputed.Remove(hitObject);
|
||||
}
|
||||
|
||||
private float scrollLength;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!layoutCache.IsValid)
|
||||
if (layoutCache.IsValid) return;
|
||||
|
||||
foreach (var hitObject in Objects)
|
||||
{
|
||||
toComputeLifetime.Clear();
|
||||
|
||||
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();
|
||||
if (hitObject.HitObject != null)
|
||||
invalidateHitObject(hitObject);
|
||||
}
|
||||
|
||||
foreach (var hitObject in toComputeLifetime)
|
||||
hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject);
|
||||
scrollingInfo.Algorithm.Reset();
|
||||
|
||||
toComputeLifetime.Clear();
|
||||
layoutCache.Validate();
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildrenLife()
|
||||
|
@ -77,7 +77,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
double offset = result.Time.Value - blueprints.First().Item.StartTime;
|
||||
|
||||
if (offset != 0)
|
||||
Beatmap.PerformOnSelection(obj => obj.StartTime += offset);
|
||||
{
|
||||
Beatmap.PerformOnSelection(obj =>
|
||||
{
|
||||
obj.StartTime += offset;
|
||||
Beatmap.Update(obj);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -125,6 +125,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
return;
|
||||
|
||||
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>
|
||||
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>
|
||||
|
@ -276,7 +276,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
var timingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(selected.First().StartTime);
|
||||
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 the confirm dialog is already showing (or we can't show it, ie. in tests) exit without save.
|
||||
if (dialogOverlay == null || dialogOverlay.CurrentDialog is PromptForSaveDialog)
|
||||
// dialog overlay may not be available in visual tests.
|
||||
if (dialogOverlay == null)
|
||||
{
|
||||
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)
|
||||
{
|
||||
dialogOverlay?.Push(new PromptForSaveDialog(() =>
|
||||
{
|
||||
confirmExit();
|
||||
this.Exit();
|
||||
}, () =>
|
||||
{
|
||||
confirmExitWithSave();
|
||||
this.Exit();
|
||||
}));
|
||||
|
||||
dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -499,15 +497,23 @@ namespace osu.Game.Screens.Edit
|
||||
ApplyToBackground(b => b.FadeColour(Color4.White, 500));
|
||||
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);
|
||||
}
|
||||
|
||||
private void confirmExitWithSave()
|
||||
{
|
||||
exitConfirmed = true;
|
||||
Save();
|
||||
|
||||
exitConfirmed = true;
|
||||
this.Exit();
|
||||
}
|
||||
|
||||
private void confirmExit()
|
||||
@ -529,6 +535,7 @@ namespace osu.Game.Screens.Edit
|
||||
}
|
||||
|
||||
exitConfirmed = true;
|
||||
this.Exit();
|
||||
}
|
||||
|
||||
private readonly Bindable<string> clipboard = new Bindable<string>();
|
||||
|
@ -11,6 +11,7 @@ using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Skinning;
|
||||
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
|
||||
|
||||
namespace osu.Game.Screens.Edit
|
||||
@ -117,6 +118,8 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||
|
||||
protected override ISkin GetSkin() => 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.
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -22,6 +19,8 @@ using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Screens.Select;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Menu
|
||||
{
|
||||
@ -120,7 +119,7 @@ namespace osu.Game.Screens.Menu
|
||||
Origin = Anchor.TopRight,
|
||||
Margin = new MarginPadding { Right = 15, Top = 5 }
|
||||
},
|
||||
exitConfirmOverlay?.CreateProxy() ?? Drawable.Empty()
|
||||
exitConfirmOverlay?.CreateProxy() ?? Empty()
|
||||
});
|
||||
|
||||
buttons.StateChanged += state =>
|
||||
@ -270,15 +269,11 @@ namespace osu.Game.Screens.Menu
|
||||
if (!exitConfirmed && dialogOverlay != null)
|
||||
{
|
||||
if (dialogOverlay.CurrentDialog is ConfirmExitDialog exitDialog)
|
||||
{
|
||||
exitConfirmed = true;
|
||||
exitDialog.Buttons.First().Click();
|
||||
}
|
||||
exitDialog.PerformOkAction();
|
||||
else
|
||||
{
|
||||
dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort()));
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
buttons.State = ButtonSystemState.Exit;
|
||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
@ -32,9 +32,9 @@ namespace osu.Game.Screens.Play.HUD
|
||||
/// </summary>
|
||||
private const double rolling_duration = 20;
|
||||
|
||||
private Drawable popOutCount;
|
||||
private readonly Drawable popOutCount;
|
||||
|
||||
private Drawable displayedCountSpriteText;
|
||||
private readonly Drawable displayedCountSpriteText;
|
||||
|
||||
private int previousValue;
|
||||
|
||||
@ -45,6 +45,20 @@ namespace osu.Game.Screens.Play.HUD
|
||||
[Resolved]
|
||||
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()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
@ -55,6 +69,25 @@ namespace osu.Game.Screens.Play.HUD
|
||||
Margin = new MarginPadding(10);
|
||||
|
||||
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>
|
||||
@ -82,20 +115,6 @@ namespace osu.Game.Screens.Play.HUD
|
||||
[BackgroundDependencyLoader]
|
||||
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);
|
||||
}
|
||||
|
||||
@ -105,10 +124,12 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
((IHasText)displayedCountSpriteText).Text = formatCount(Current.Value);
|
||||
|
||||
counterContainer.Anchor = Anchor;
|
||||
counterContainer.Origin = Origin;
|
||||
displayedCountSpriteText.Anchor = Anchor;
|
||||
displayedCountSpriteText.Origin = Origin;
|
||||
popOutCount.Origin = Origin;
|
||||
popOutCount.Anchor = Anchor;
|
||||
popOutCount.Origin = Origin;
|
||||
|
||||
Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true);
|
||||
}
|
||||
|
@ -520,7 +520,10 @@ namespace osu.Game.Screens.Play
|
||||
if (!this.IsCurrentScreen())
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
[Resolved(CanBeNull = true)]
|
||||
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]
|
||||
private Container<DrawableCarouselItem> beatmapContainer;
|
||||
|
@ -505,12 +505,13 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\"");
|
||||
|
||||
WorkingBeatmap previous = Beatmap.Value;
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, previous);
|
||||
int? lastSetID = Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID;
|
||||
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap);
|
||||
|
||||
if (beatmap != null)
|
||||
{
|
||||
if (beatmap.BeatmapSetInfoID == previous?.BeatmapInfo.BeatmapSetInfoID)
|
||||
if (beatmap.BeatmapSetInfoID == lastSetID)
|
||||
sampleChangeDifficulty.Play();
|
||||
else
|
||||
sampleChangeBeatmap.Play();
|
||||
|
@ -11,14 +11,14 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
public class DefaultLegacySkin : LegacySkin
|
||||
{
|
||||
public DefaultLegacySkin(IResourceStore<byte[]> storage, IStorageResourceProvider resources)
|
||||
: this(Info, storage, resources)
|
||||
public DefaultLegacySkin(IStorageResourceProvider resources)
|
||||
: this(Info, resources)
|
||||
{
|
||||
}
|
||||
|
||||
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)]
|
||||
public DefaultLegacySkin(SkinInfo skin, IResourceStore<byte[]> storage, IStorageResourceProvider resources)
|
||||
: base(skin, storage, resources, string.Empty)
|
||||
public DefaultLegacySkin(SkinInfo skin, IStorageResourceProvider resources)
|
||||
: base(skin, new NamespacedResourceStore<byte[]>(resources.Resources, "Skins/Legacy"), resources, string.Empty)
|
||||
{
|
||||
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
|
||||
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.
|
||||
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.
|
||||
var adjustedRect = selectionRect;
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -23,6 +24,25 @@ namespace osu.Game.Skinning
|
||||
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)
|
||||
{
|
||||
switch (lookup)
|
||||
@ -51,6 +71,6 @@ namespace osu.Game.Skinning
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
case SkinnableTarget.MainHUDComponents:
|
||||
|
||||
var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container =>
|
||||
{
|
||||
var score = container.OfType<LegacyScoreCounter>().FirstOrDefault();
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
@ -28,16 +27,13 @@ namespace osu.Game.Skinning
|
||||
|
||||
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)
|
||||
// handle the case of skins imported before InstantiationInfo was added.
|
||||
? typeof(LegacySkin)
|
||||
: Type.GetType(InstantiationInfo).AsNonNull();
|
||||
|
||||
if (type == typeof(DefaultLegacySkin))
|
||||
return (Skin)Activator.CreateInstance(type, this, legacyDefaultResources, resources);
|
||||
|
||||
return (Skin)Activator.CreateInstance(type, this, resources);
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Skinning
|
||||
|
||||
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<SkinInfo> CurrentSkinInfo = new Bindable<SkinInfo>(SkinInfo.Default) { Default = SkinInfo.Default };
|
||||
@ -48,13 +48,12 @@ namespace osu.Game.Skinning
|
||||
|
||||
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)
|
||||
{
|
||||
this.audio = audio;
|
||||
this.host = host;
|
||||
|
||||
this.legacyDefaultResources = legacyDefaultResources;
|
||||
this.resources = resources;
|
||||
|
||||
CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue);
|
||||
CurrentSkin.ValueChanged += skin =>
|
||||
@ -152,7 +151,7 @@ namespace osu.Game.Skinning
|
||||
/// </summary>
|
||||
/// <param name="skinInfo">The skin to lookup.</param>
|
||||
/// <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>
|
||||
/// Ensure that the current skin is in a state it can accept user modifications.
|
||||
@ -216,6 +215,7 @@ namespace osu.Game.Skinning
|
||||
#region IResourceStorageProvider
|
||||
|
||||
AudioManager IStorageResourceProvider.AudioManager => audio;
|
||||
IResourceStore<byte[]> IStorageResourceProvider.Resources => resources;
|
||||
IResourceStore<byte[]> IStorageResourceProvider.Files => Files.Store;
|
||||
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host.CreateTextureLoaderStore(underlyingStore);
|
||||
|
||||
|
@ -57,7 +57,13 @@ namespace osu.Game.Storyboards.Drawables
|
||||
public DrawableStoryboard(Storyboard storyboard)
|
||||
{
|
||||
Storyboard = storyboard;
|
||||
|
||||
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;
|
||||
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