1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 10:07:52 +08:00

Merge branch 'master' into main-page-wiki

This commit is contained in:
Dean Herbert 2021-05-30 21:01:05 +09:00 committed by GitHub
commit f35cf2d846
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 781 additions and 406 deletions

View File

@ -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:**

View File

@ -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.

View File

@ -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>

View File

@ -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);

View File

@ -28,53 +28,56 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
case HUDSkinComponents.ComboCounter:
// catch may provide its own combo counter; hide the default.
return providesComboCounter ? Drawable.Empty() : null;
if (providesComboCounter)
return Drawable.Empty();
break;
}
}
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)

View File

@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
break;
}
return null;
return Source.GetDrawableComponent(component);
}
private Drawable getResult(HitResult result)

View File

@ -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);
}
}
}

View File

@ -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)

View File

@ -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;

View File

@ -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);
}
});
}

View File

@ -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

View File

@ -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();

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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());
}
}
}

View 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)
{
}
}
}
}
}

View File

@ -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());
}

View File

@ -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]

View File

@ -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]

View File

@ -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()
{

View File

@ -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();
}
}
}
}

View File

@ -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);

View File

@ -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);

View File

@ -526,6 +526,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;
}
}

View File

@ -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

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;
}

View 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);
}
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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;

View File

@ -311,6 +311,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 +319,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()
{

View File

@ -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;

View File

@ -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>

View File

@ -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);
});
}
}

View File

@ -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();
}
}

View File

@ -522,7 +522,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;
}

View File

@ -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;

View File

@ -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 };
}
}

View File

@ -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;

View File

@ -5,6 +5,7 @@ using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osuTK;
namespace osu.Game.Storyboards.Drawables
{
@ -15,6 +16,8 @@ namespace osu.Game.Storyboards.Drawables
public override bool IsPresent => Enabled && base.IsPresent;
protected LayerElementContainer ElementContainer { get; }
public DrawableStoryboardLayer(StoryboardLayer layer)
{
Layer = layer;
@ -24,10 +27,10 @@ namespace osu.Game.Storyboards.Drawables
Enabled = layer.VisibleWhenPassing;
Masking = layer.Masking;
InternalChild = new LayerElementContainer(layer);
InternalChild = ElementContainer = new LayerElementContainer(layer);
}
private class LayerElementContainer : LifetimeManagementContainer
protected class LayerElementContainer : LifetimeManagementContainer
{
private readonly StoryboardLayer storyboardLayer;
@ -35,8 +38,8 @@ namespace osu.Game.Storyboards.Drawables
{
storyboardLayer = layer;
Width = 640;
Height = 480;
Size = new Vector2(640, 480);
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
}

View File

@ -53,7 +53,7 @@ namespace osu.Game.Storyboards
public Storyboard()
{
layers.Add("Video", new StoryboardLayer("Video", 4, false));
layers.Add("Video", new StoryboardVideoLayer("Video", 4, false));
layers.Add("Background", new StoryboardLayer("Background", 3));
layers.Add("Fail", new StoryboardLayer("Fail", 2) { VisibleWhenPassing = false, });
layers.Add("Pass", new StoryboardLayer("Pass", 1) { VisibleWhenFailing = false, });
@ -85,12 +85,8 @@ namespace osu.Game.Storyboards
}
}
public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null)
{
var drawable = new DrawableStoryboard(this);
drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f);
return drawable;
}
public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null) =>
new DrawableStoryboard(this);
public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore)
{

View File

@ -32,7 +32,7 @@ namespace osu.Game.Storyboards
Elements.Add(element);
}
public DrawableStoryboardLayer CreateDrawable()
public virtual DrawableStoryboardLayer CreateDrawable()
=> new DrawableStoryboardLayer(this) { Depth = Depth, Name = Name };
}
}

View File

@ -0,0 +1,32 @@
// 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.Graphics;
using osu.Game.Storyboards.Drawables;
using osuTK;
namespace osu.Game.Storyboards
{
public class StoryboardVideoLayer : StoryboardLayer
{
public StoryboardVideoLayer(string name, int depth, bool masking)
: base(name, depth, masking)
{
}
public override DrawableStoryboardLayer CreateDrawable()
=> new DrawableStoryboardVideoLayer(this) { Depth = Depth, Name = Name };
public class DrawableStoryboardVideoLayer : DrawableStoryboardLayer
{
public DrawableStoryboardVideoLayer(StoryboardVideoLayer layer)
: base(layer)
{
// for videos we want to take on the full size of the storyboard container hierarchy
// to allow the video to fill the full available region.
ElementContainer.RelativeSizeAxes = Axes.Both;
ElementContainer.Size = Vector2.One;
}
}
}
}

View File

@ -17,6 +17,7 @@ using osu.Game.IO;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Skinning;
namespace osu.Game.Tests.Beatmaps
{
@ -216,6 +217,8 @@ namespace osu.Game.Tests.Beatmaps
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
protected override ISkin GetSkin() => throw new NotImplementedException();
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
protected override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)

View File

@ -6,6 +6,7 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;
using osu.Game.Skinning;
using osu.Game.Storyboards;
namespace osu.Game.Tests.Beatmaps
@ -36,6 +37,8 @@ namespace osu.Game.Tests.Beatmaps
protected override Storyboard GetStoryboard() => storyboard ?? base.GetStoryboard();
protected override ISkin GetSkin() => null;
public override Stream GetStream(string storagePath) => null;
protected override Texture GetBackground() => null;

View File

@ -3,6 +3,7 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@ -53,6 +54,8 @@ namespace osu.Game.Tests.Visual.Spectator
});
}
public new void Schedule(Action action) => base.Schedule(action);
/// <summary>
/// Sends frames for an arbitrary user.
/// </summary>

View File

@ -34,7 +34,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="ppy.osu.Framework" Version="2021.524.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.528.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
<PackageReference Include="Sentry" Version="3.3.4" />
<PackageReference Include="SharpCompress" Version="0.28.2" />

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.524.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.528.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
</ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
@ -93,7 +93,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2021.524.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.528.0" />
<PackageReference Include="SharpCompress" Version="0.28.2" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" />