diff --git a/.github/ISSUE_TEMPLATE/02-feature-request-issues.md b/.github/ISSUE_TEMPLATE/02-feature-request-issues.md
deleted file mode 100644
index c3357dd780..0000000000
--- a/.github/ISSUE_TEMPLATE/02-feature-request-issues.md
+++ /dev/null
@@ -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:**
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 69baeee60c..c62231e8e0 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -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.
+
diff --git a/osu.Android.props b/osu.Android.props
index f7ad06f5ca..3d51357d8b 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index e3c457693e..23ce444560 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -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);
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
index 1b48832ed6..e43d88ad40 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
@@ -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 GetConfig(TLookup lookup)
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
index 24ccae895d..261b8b1fad 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
@@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
break;
}
- return null;
+ return Source.GetDrawableComponent(component);
}
private Drawable getResult(HitResult result)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
index d81af053d1..cd6bf1d8d2 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
@@ -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);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
index 88302ebc57..ffd4f78400 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
@@ -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 GetConfig(TLookup lookup)
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index b51f096d7d..90c99316b1 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -79,8 +79,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
// Old osu! used hit sounding to determine various hit type information
IList 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 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;
diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
index a24130d6ac..ab3b729307 100644
--- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
+++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
@@ -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);
+ }
});
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs
index f4a66c39a8..b4ed242893 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs
@@ -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;
+ }
+
+ ///
+ /// Returns an array of any samples which would cause this object to be a "rim" type hit.
+ ///
+ 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
}
}
- ///
- /// Returns an array of any samples which would cause this object to be a "rim" type hit.
- ///
- 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
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs
index cac56d1269..6c17573b50 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs
@@ -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();
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs
index 3e506f69ce..e0557c8617 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs
@@ -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);
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
index a18f82fe4a..059432eeaf 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
@@ -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();
}
}
diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
index a8ee1bcc2e..a47631a83b 100644
--- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
+++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
@@ -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());
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
new file mode 100644
index 0000000000..cc53e50884
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
@@ -0,0 +1,130 @@
+// Copyright (c) ppy Pty Ltd . 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 getBeatmapSkin)
+ {
+ CreateTest(() =>
+ {
+ AddStep("setup skins", () =>
+ {
+ skinManager.CurrentSkinInfo.Value = gameCurrentSkin;
+ currentBeatmapSkin = getBeatmapSkin();
+ });
+ });
+ }
+
+ protected bool AssertComponentsFromExpectedSource(SkinnableTarget target, ISkin expectedSource)
+ {
+ var actualComponentsContainer = Player.ChildrenOfType().First(s => s.Target == target)
+ .ChildrenOfType().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(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)
+ {
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
index cfdea31a75..8160a62991 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
@@ -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())
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());
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
index 0ac8e01482..5ef3eff856 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
@@ -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]
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
index 80b9aa8228..af2f6fa5fe 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
@@ -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]
diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs
index 67030631b0..1e19af933a 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs
@@ -10,7 +10,6 @@ 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.Online.API;
using osu.Game.Overlays;
using osu.Game.Overlays.Wiki.Markdown;
@@ -23,9 +22,6 @@ namespace osu.Game.Tests.Visual.Online
[Cached]
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange);
- [Cached]
- private readonly IAPIProvider api = new DummyAPIAccess();
-
[SetUp]
public void Setup() => Schedule(() =>
{
@@ -55,16 +51,16 @@ namespace osu.Game.Tests.Visual.Online
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");
+ 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");
+ 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");
+ 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");
+ AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/Formatting");
}
[Test]
@@ -106,6 +102,7 @@ needs_cleanup: true
{
AddStep("Add absolute image", () =>
{
+ markdownContainer.DocumentUrl = "https://dev.ppy.sh";
markdownContainer.Text = "![intro](/wiki/Interface/img/intro-screen.jpg)";
});
}
@@ -115,6 +112,7 @@ needs_cleanup: true
{
AddStep("Add relative image", () =>
{
+ markdownContainer.DocumentUrl = "https://dev.ppy.sh";
markdownContainer.CurrentPath = "Interface/";
markdownContainer.Text = "![intro](img/intro-screen.jpg)";
});
@@ -125,6 +123,7 @@ needs_cleanup: true
{
AddStep("Add paragraph with block image", () =>
{
+ markdownContainer.DocumentUrl = "https://dev.ppy.sh";
markdownContainer.CurrentPath = "Interface/";
markdownContainer.Text = @"Line before image
@@ -139,6 +138,7 @@ Line after image";
{
AddStep("Add inline image", () =>
{
+ markdownContainer.DocumentUrl = "https://dev.ppy.sh";
markdownContainer.Text = "![osu! mode icon](/wiki/shared/mode/osu.png) osu!";
});
}
@@ -147,6 +147,11 @@ Line after image";
{
public LinkInline Link;
+ public new string DocumentUrl
+ {
+ set => base.DocumentUrl = value;
+ }
+
public override MarkdownTextFlowContainer CreateTextFlow() => new TestMarkdownTextFlowContainer
{
UrlAdded = link => Link = link,
@@ -162,6 +167,8 @@ Line after image";
UrlAdded?.Invoke(linkInline);
}
+
+ protected override void AddImage(LinkInline linkInline) => AddDrawable(new WikiMarkdownImage(linkInline));
}
}
}
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
index 55c5b5b9c2..acf9deb3cb 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
@@ -70,6 +70,7 @@ namespace osu.Game.Tests.Visual.Settings
AddStep("click first row", () =>
{
firstRow = panel.ChildrenOfType().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().First();
+
+ InputManager.MoveMouseTo(settingsKeyBindingRow);
+ InputManager.Click(MouseButton.Left);
+ InputManager.PressKey(Key.P);
+ InputManager.ReleaseKey(Key.P);
+ });
+
+ AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha > 0);
+
+ AddStep("click reset button for bindings", () =>
+ {
+ var resetButton = settingsKeyBindingRow.ChildrenOfType>().First();
+
+ resetButton.Click();
+ });
+
+ AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha == 0);
+
+ AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0)));
+ }
+
+ [Test]
+ public void TestResetAllBindingsButton()
+ {
+ KeyBindingRow settingsKeyBindingRow = null;
+
+ AddStep("click first row", () =>
+ {
+ settingsKeyBindingRow = panel.ChildrenOfType().First();
+
+ InputManager.MoveMouseTo(settingsKeyBindingRow);
+ InputManager.Click(MouseButton.Left);
+ InputManager.PressKey(Key.P);
+ InputManager.ReleaseKey(Key.P);
+ });
+
+ AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha > 0);
+
+ AddStep("click reset button for bindings", () =>
+ {
+ var resetButton = panel.ChildrenOfType().First();
+
+ resetButton.Click();
+ });
+
+ AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType>().First().Alpha == 0);
+
+ AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0)));
+ }
+
[Test]
public void TestClickRowSelectsFirstBinding()
{
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs
index 8f1c17ed29..f63145f534 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs
@@ -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().Single();
+ public Drawable RestoreDefaultValueButton => this.ChildrenOfType>().Single();
}
}
-}
+}
\ No newline at end of file
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
index 44c9361ff8..78ddfa9ed2 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
@@ -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);
diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs
index cbed28641c..5477e4a0f8 100644
--- a/osu.Game.Tests/WaveformTestBeatmap.cs
+++ b/osu.Game.Tests/WaveformTestBeatmap.cs
@@ -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);
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 5e975de77c..46e3a4f6d7 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -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;
}
}
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index 6922d1c286..ea7f45e53f 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -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
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 3576b149bf..0a55678fb7 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -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 skin;
public abstract Stream GetStream(string storagePath);
diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs
index 3a63587b30..086cc573d5 100644
--- a/osu.Game/Collections/CollectionManager.cs
+++ b/osu.Game/Collections/CollectionManager.cs
@@ -58,8 +58,13 @@ namespace osu.Game.Collections
if (storage.Exists(database_name))
{
+ List beatmapCollections;
+
using (var stream = storage.GetStream(database_name))
- importCollections(readCollections(stream));
+ beatmapCollections = readCollections(stream);
+
+ // intentionally fire-and-forget async.
+ importCollections(beatmapCollections);
}
}
diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs
index 9ecedba59a..ad11a9625e 100644
--- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs
+++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs
@@ -23,10 +23,14 @@ namespace osu.Game.Graphics.Containers.Markdown
LineSpacing = 21;
}
- [BackgroundDependencyLoader]
- private void load(IAPIProvider api)
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
+ var api = parent.Get();
+
+ // 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)
diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs
index f44f818bf0..f91a0e40e3 100644
--- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs
+++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs
@@ -16,28 +16,29 @@ namespace osu.Game.Graphics.Containers.Markdown
[Resolved(canBeNull: true)]
private OsuGame game { get; set; }
- protected string Text;
- protected string Title;
+ private readonly string text;
+ private readonly string title;
public OsuMarkdownLinkText(string text, LinkInline linkInline)
: base(text, linkInline)
{
- Text = text;
- Title = linkInline.Title;
+ this.text = text;
+ title = linkInline.Title;
}
[BackgroundDependencyLoader]
- private void load(OverlayColourProvider colourProvider)
+ private void load()
{
- var text = CreateSpriteText().With(t => t.Text = Text);
+ var textDrawable = CreateSpriteText().With(t => t.Text = text);
+
InternalChildren = new Drawable[]
{
- text,
- new OsuMarkdownLinkCompiler(new[] { text })
+ textDrawable,
+ new OsuMarkdownLinkCompiler(new[] { textDrawable })
{
RelativeSizeAxes = Axes.Both,
Action = OnLinkPressed,
- TooltipText = Title ?? Url,
+ TooltipText = title ?? Url,
}
};
}
diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
index 9c09b6e7d0..0df3359c28 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
@@ -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 buttons;
+ private Bindable isDefault { get; } = new BindableBool(true);
+
public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text.ToString());
public KeyBindingRow(object action, IEnumerable 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
{
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- Alpha = 0.6f,
- },
- text = new OsuSpriteText
- {
- Text = action.GetDescription(),
- Margin = new MarginPadding(padding),
- },
- buttons = new FillFlowContainer
- {
- 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
+ {
+ 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;
diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs
index 707176e63e..5e1f9d8f75 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs
@@ -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;
}
diff --git a/osu.Game/Overlays/News/Displays/ArticleListing.cs b/osu.Game/Overlays/News/Displays/ArticleListing.cs
index b49326a1f1..dc3b17b323 100644
--- a/osu.Game/Overlays/News/Displays/ArticleListing.cs
+++ b/osu.Game/Overlays/News/Displays/ArticleListing.cs
@@ -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
///
public class ArticleListing : CompositeDrawable
{
- public Action 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;
-
- ///
- /// Instantiate a listing for the specified year.
- ///
- /// The year to load articles from. If null, will show the most recent articles.
- 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 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);
}
diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs
index dd6de40ecb..12e3f81ca1 100644
--- a/osu.Game/Overlays/NewsOverlay.cs
+++ b/osu.Game/Overlays/NewsOverlay.cs
@@ -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
{
- private readonly Bindable article = new Bindable(null);
+ private readonly Bindable article = new Bindable();
private readonly Container sidebarContainer;
private readonly NewsSidebar sidebar;
-
private readonly Container content;
+ private GetNewsRequest request;
+
+ private Cursor lastCursor;
+
+ ///
+ /// The year currently being displayed. If null, the main listing is being displayed.
+ ///
+ 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 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);
}
diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs
new file mode 100644
index 0000000000..213ad2ba68
--- /dev/null
+++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs
@@ -0,0 +1,106 @@
+// Copyright (c) ppy Pty Ltd . 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 : OsuButton, IHasTooltip, IHasCurrentValue
+ {
+ public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
+
+ private readonly BindableWithCurrent current = new BindableWithCurrent();
+
+ // 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 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);
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs
index 86a836d29b..807916e7f6 100644
--- a/osu.Game/Overlays/Settings/SettingsItem.cs
+++ b/osu.Game/Overlays/Settings/SettingsItem.cs
@@ -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 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(),
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 bindable;
-
- public Bindable 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);
- }
- }
}
-}
+}
\ No newline at end of file
diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs
index f0a11d67b7..eae828c142 100644
--- a/osu.Game/Overlays/SettingsPanel.cs
+++ b/osu.Game/Overlays/SettingsPanel.cs
@@ -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
+ {
+ // masking breaks the pan-out transform with nested sub-settings panels.
+ protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
+ }
+
public class SettingsSectionsContainer : SectionsContainer
{
public SearchContainer SearchContainer;
diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs
index d4ad0bee4d..4e671cca6d 100644
--- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs
+++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . 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;
@@ -14,7 +15,7 @@ namespace osu.Game.Overlays.Wiki.Markdown
{
public string CurrentPath
{
- set => Schedule(() => DocumentUrl += $"wiki/{value}");
+ set => DocumentUrl = $"{DocumentUrl}wiki/{value}";
}
protected override void AddMarkdownComponent(IMarkdownObject markdownObject, FillFlowContainer container, int level)
@@ -22,21 +23,25 @@ namespace osu.Game.Overlays.Wiki.Markdown
switch (markdownObject)
{
case YamlFrontMatterBlock yamlFrontMatterBlock:
- container.Add(CreateNotice(yamlFrontMatterBlock));
+ container.Add(new WikiNoticeContainer(yamlFrontMatterBlock));
break;
- default:
- base.AddMarkdownComponent(markdownObject, container, level);
+ 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();
- protected override MarkdownParagraph CreateParagraph(ParagraphBlock paragraphBlock, int level) => new WikiMarkdownParagraph(paragraphBlock);
-
- protected virtual FillFlowContainer CreateNotice(YamlFrontMatterBlock yamlFrontMatterBlock) => new WikiNoticeContainer(yamlFrontMatterBlock);
-
private class WikiMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer
{
protected override void AddImage(LinkInline linkInline) => AddDrawable(new WikiMarkdownImage(linkInline));
diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs
index 361aa2e95f..c2115efeb5 100644
--- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs
+++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs
@@ -2,37 +2,28 @@
// See the LICENCE file in the repository root for full licence text.
using Markdig.Syntax.Inlines;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Cursor;
-using osu.Game.Online.API;
namespace osu.Game.Overlays.Wiki.Markdown
{
public class WikiMarkdownImage : MarkdownImage, IHasTooltip
{
- private readonly string url;
-
public string TooltipText { get; }
public WikiMarkdownImage(LinkInline linkInline)
: base(linkInline.Url)
{
- url = linkInline.Url;
TooltipText = linkInline.Title;
}
- [BackgroundDependencyLoader]
- private void load(IAPIProvider api)
+ protected override ImageContainer CreateImageContainer(string url)
{
- // The idea is replace "{api.WebsiteRootUrl}/wiki/{path-to-image}" to "{api.WebsiteRootUrl}/wiki/images/{path-to-image}"
+ // 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)
- // Currently all image in dev server (https://dev.ppy.sh/wiki/image/*) is 404
- // So for now just replace "{api.WebsiteRootUrl}/wiki/*" to "https://osu.ppy.sh/wiki/images/*" for simplicity
- var imageUrl = url.Replace($"{api.WebsiteRootUrl}/wiki", "https://osu.ppy.sh/wiki/images");
+ url = url.Replace("/wiki/", "/wiki/images/");
- InternalChild = new DelayedLoadWrapper(CreateImageContainer(imageUrl));
+ return base.CreateImageContainer(url);
}
}
}
diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs
new file mode 100644
index 0000000000..179762103a
--- /dev/null
+++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs
@@ -0,0 +1,49 @@
+// Copyright (c) ppy Pty Ltd . 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;
+ }),
+ };
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownParagraph.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownParagraph.cs
deleted file mode 100644
index 4a7ce24aba..0000000000
--- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownParagraph.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Linq;
-using Markdig.Syntax;
-using Markdig.Syntax.Inlines;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers.Markdown;
-using osuTK;
-
-namespace osu.Game.Overlays.Wiki.Markdown
-{
- public class WikiMarkdownParagraph : MarkdownParagraph
- {
- private readonly ParagraphBlock paragraphBlock;
-
- public WikiMarkdownParagraph(ParagraphBlock paragraphBlock)
- : base(paragraphBlock)
- {
- this.paragraphBlock = paragraphBlock;
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- MarkdownTextFlowContainer textFlow;
- InternalChild = textFlow = CreateTextFlow();
- textFlow.AddInlineText(paragraphBlock.Inline);
-
- // Check if paragraph only contains an image.
- if (paragraphBlock.Inline.Count() == 1 && paragraphBlock.Inline.FirstChild is LinkInline { IsImage: true } linkInline)
- {
- textFlow.TextAnchor = Anchor.TopCentre;
- textFlow.Spacing = new Vector2(0, 5);
- textFlow.AddText($"\n{linkInline.Title}");
- }
- }
- }
-}
diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs
index 8ff85c1404..c4c0b83ef4 100644
--- a/osu.Game/Overlays/Wiki/WikiMainPage.cs
+++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs
@@ -2,6 +2,7 @@
// 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;
@@ -45,7 +46,7 @@ namespace osu.Game.Overlays.Wiki
private Container createBlurb(HtmlDocument html)
{
- var blurbNode = html.DocumentNode.SelectNodes("//div[contains(@class, 'wiki-main-page__blurb')]").First();
+ var blurbNode = html.DocumentNode.SelectSingleNode("//div[contains(@class, 'wiki-main-page__blurb')]");
return new Container
{
@@ -69,7 +70,11 @@ namespace osu.Game.Overlays.Wiki
{
var panelsNode = html.DocumentNode.SelectNodes("//div[contains(@class, 'wiki-main-page-panel')]").ToArray();
- for (var i = 0; i < panelsNode.Length; i++)
+ Debug.Assert(panelsNode.Length > 1);
+
+ var i = 0;
+
+ while (i < panelsNode.Length)
{
var isFullWidth = panelsNode[i].HasClass("wiki-main-page-panel--full");
@@ -77,28 +82,20 @@ namespace osu.Game.Overlays.Wiki
{
yield return new Drawable[]
{
- new WikiPanelContainer
+ new WikiPanelContainer(panelsNode[i++].InnerText, true)
{
- Text = panelsNode[i].InnerText,
- IsFullWidth = true,
+ // This is required to fill up the space of "null" drawable below.
Width = 2,
},
null,
};
}
-
- if (i % 2 == 1)
+ else
{
yield return new Drawable[]
{
- new WikiPanelContainer
- {
- Text = panelsNode[i].InnerText,
- },
- new WikiPanelContainer
- {
- Text = panelsNode[i + 1].InnerText,
- },
+ new WikiPanelContainer(panelsNode[i++].InnerText),
+ i < panelsNode.Length ? new WikiPanelContainer(panelsNode[i++].InnerText) : null,
};
}
}
diff --git a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs
index 71b492b375..db213e4951 100644
--- a/osu.Game/Overlays/Wiki/WikiPanelContainer.cs
+++ b/osu.Game/Overlays/Wiki/WikiPanelContainer.cs
@@ -21,23 +21,23 @@ namespace osu.Game.Overlays.Wiki
{
public class WikiPanelContainer : Container
{
- [Resolved]
- private OverlayColourProvider colourProvider { get; set; }
-
private WikiPanelMarkdownContainer panelContainer;
- public string Text;
+ private readonly string text;
- public bool IsFullWidth;
+ private readonly bool isFullWidth;
- public WikiPanelContainer()
+ public WikiPanelContainer(string text, bool isFullWidth = false)
{
+ this.text = text;
+ this.isFullWidth = isFullWidth;
+
RelativeSizeAxes = Axes.X;
Padding = new MarginPadding(3);
}
[BackgroundDependencyLoader]
- private void load()
+ private void load(OverlayColourProvider colourProvider)
{
Children = new Drawable[]
{
@@ -59,12 +59,11 @@ namespace osu.Game.Overlays.Wiki
RelativeSizeAxes = Axes.Both,
},
},
- panelContainer = new WikiPanelMarkdownContainer
+ panelContainer = new WikiPanelMarkdownContainer(isFullWidth)
{
- Text = Text,
+ Text = text,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- IsFullWidth = IsFullWidth,
}
};
}
@@ -77,10 +76,12 @@ namespace osu.Game.Overlays.Wiki
private class WikiPanelMarkdownContainer : WikiMarkdownContainer
{
- public bool IsFullWidth;
+ private readonly bool isFullWidth;
- public WikiPanelMarkdownContainer()
+ public WikiPanelMarkdownContainer(bool isFullWidth)
{
+ this.isFullWidth = isFullWidth;
+
LineSpacing = 0;
DocumentPadding = new MarginPadding(30);
DocumentMargin = new MarginPadding(0);
@@ -95,7 +96,7 @@ namespace osu.Game.Overlays.Wiki
protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new WikiPanelHeading(headingBlock)
{
- IsFullWidth = IsFullWidth,
+ IsFullWidth = isFullWidth,
};
}
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index cc663c37af..cca55819c5 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -311,6 +311,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
/// Invoked for this to take on any values from a newly-applied .
+ /// This is also fired after any changes which occurred via an call.
///
protected virtual void OnApply()
{
@@ -318,6 +319,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
/// Invoked for this to revert any values previously taken on from the currently-applied .
+ /// This is also fired after any changes which occurred via an call.
///
protected virtual void OnFree()
{
diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs
index 5a6f98f504..22b211f257 100644
--- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs
@@ -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;
diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
index 2141c490df..246d4aa8d7 100644
--- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
@@ -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
/// The name of the hit sample.
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);
+ });
}
///
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs
index 7c1bbd65f9..6f04f36b83 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs
@@ -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);
+ });
}
}
diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs
index 66784fda54..6f92db98ee 100644
--- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs
+++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs
@@ -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();
}
}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 39f9e2d388..a9f3edf049 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -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;
}
diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
index a3fca3d4e1..9773bd5ce9 100644
--- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
+++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Screens.Select.Carousel
[Resolved(CanBeNull = true)]
private ManageCollectionsDialog manageCollectionsDialog { get; set; }
- public IEnumerable DrawableBeatmaps => beatmapContainer?.Children ?? Enumerable.Empty();
+ public IEnumerable DrawableBeatmaps => beatmapContainer?.IsLoaded != true ? Enumerable.Empty() : beatmapContainer.AliveChildren;
[CanBeNull]
private Container beatmapContainer;
diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs
index 99bd22c0bf..9cca0ba2c7 100644
--- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs
+++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs
@@ -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;
diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs
index 3ec205e897..caf37e5bc9 100644
--- a/osu.Game/Skinning/LegacyBeatmapSkin.cs
+++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs
@@ -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 GetConfig(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 };
}
}
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs
index 4c42823779..ca041da801 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs
@@ -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;
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs
index 2ada83c3b4..1085b52d65 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs
@@ -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;
}
diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs
index bc61f704dd..3486c1d66a 100644
--- a/osu.Game/Storyboards/Storyboard.cs
+++ b/osu.Game/Storyboards/Storyboard.cs
@@ -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)
{
diff --git a/osu.Game/Storyboards/StoryboardLayer.cs b/osu.Game/Storyboards/StoryboardLayer.cs
index 1cde7cf67a..fa9d4ebfea 100644
--- a/osu.Game/Storyboards/StoryboardLayer.cs
+++ b/osu.Game/Storyboards/StoryboardLayer.cs
@@ -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 };
}
}
diff --git a/osu.Game/Storyboards/StoryboardVideoLayer.cs b/osu.Game/Storyboards/StoryboardVideoLayer.cs
new file mode 100644
index 0000000000..2a01c2274a
--- /dev/null
+++ b/osu.Game/Storyboards/StoryboardVideoLayer.cs
@@ -0,0 +1,32 @@
+// Copyright (c) ppy Pty Ltd . 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;
+ }
+ }
+ }
+}
diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
index a97f6defe9..4935f7fc13 100644
--- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
+++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
@@ -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)
diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
index 852006bc9b..bfce59c7de 100644
--- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
+++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
@@ -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;
diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs
index 3a5ffa8770..c7aa43b377 100644
--- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs
+++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs
@@ -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);
+
///
/// Sends frames for an arbitrary user.
///
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index a9152b8cb8..d299ba4fda 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -34,7 +34,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index a0894bc86a..9e178b267a 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -93,7 +93,7 @@
-
+