diff --git a/README.md b/README.md index eb790ca18f..3054f19e79 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ Before committing your code, please run a code formatter. This can be achieved b We have adopted some cross-platform, compiler integrated analyzers. They can provide warnings when you are editing, building inside IDE or from command line, as-if they are provided by the compiler itself. -JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it from PowerShell with `.\InspectCode.ps1`, which is [only supported on Windows](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice. +JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it from PowerShell with `.\InspectCode.ps1`. Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice. ## Contributing diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index 992f954a3a..5eb5efa54c 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 7571d1827a..d7c116411a 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index 1c8ed54440..89b551286b 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 7571d1827a..d7c116411a 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/osu.Android.props b/osu.Android.props index 3d51357d8b..e95c7e6619 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index bfcf4ef35e..7a74563b2b 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -7,7 +7,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 77e9d672e3..83d0744588 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 8f8b99b092..b2a0912d19 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index e01e858873..ebe642803b 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 1bf9e76d7d..236af4b3f1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -182,8 +182,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // todo: temporary / arbitrary, used for lifetime optimisation. this.Delay(800).FadeOut(); - (CirclePiece.Drawable as IMainCirclePiece)?.Animate(state); - switch (state) { case ArmedState.Idle: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 7b4188edab..b7458b5695 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -97,8 +97,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateHitStateTransforms(state); - (CirclePiece.Drawable as IMainCirclePiece)?.Animate(state); - switch (state) { case ArmedState.Idle: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index cd6bf1d8d2..ec1387eb54 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -87,8 +87,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Debug.Assert(HitObject.HitWindows != null); - (CirclePiece.Drawable as IMainCirclePiece)?.Animate(state); - switch (state) { case ArmedState.Idle: diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/IMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/IMainCirclePiece.cs deleted file mode 100644 index 17a1e29094..0000000000 --- a/osu.Game.Rulesets.Osu/Skinning/Default/IMainCirclePiece.cs +++ /dev/null @@ -1,17 +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 osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables; - -namespace osu.Game.Rulesets.Osu.Skinning.Default -{ - public interface IMainCirclePiece - { - /// - /// Begins animating this . - /// - /// The of the related . - void Animate(ArmedState state); - } -} diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs index b46baa00ba..b52dc749f0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs @@ -13,7 +13,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Default { - public class MainCirclePiece : CompositeDrawable, IMainCirclePiece + public class MainCirclePiece : CompositeDrawable { private readonly CirclePiece circle; private readonly RingPiece ring; @@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private readonly IBindable accentColour = new Bindable(); private readonly IBindable indexInCurrentCombo = new Bindable(); + private readonly IBindable armedState = new Bindable(); [Resolved] private DrawableHitObject drawableObject { get; set; } @@ -53,6 +54,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default accentColour.BindTo(drawableObject.AccentColour); indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable); + armedState.BindTo(drawableObject.State); } protected override void LoadComplete() @@ -67,16 +69,20 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default }, true); indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true); + + armedState.BindValueChanged(animate, true); } - public void Animate(ArmedState state) + private void animate(ValueChangedEvent state) { + ClearTransforms(true); + using (BeginAbsoluteSequence(drawableObject.StateUpdateTime)) glow.FadeOut(400); using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) { - switch (state) + switch (state.NewValue) { case ArmedState.Hit: const double flash_in = 40; @@ -89,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default explode.FadeIn(flash_in); this.ScaleTo(1.5f, 400, Easing.OutQuad); - using (BeginDelayedSequence(flash_in, true)) + using (BeginDelayedSequence(flash_in)) { // after the flash, we can hide some elements that were behind it ring.FadeOut(); diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index cf62165929..ffbeea5e0e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -12,7 +12,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -20,7 +19,7 @@ using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public class LegacyMainCirclePiece : CompositeDrawable, IMainCirclePiece + public class LegacyMainCirclePiece : CompositeDrawable { private readonly string priorityLookup; private readonly bool hasNumber; @@ -41,6 +40,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private readonly Bindable accentColour = new Bindable(); private readonly IBindable indexInCurrentCombo = new Bindable(); + private readonly IBindable armedState = new Bindable(); [Resolved] private DrawableHitObject drawableObject { get; set; } @@ -115,6 +115,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy accentColour.BindTo(drawableObject.AccentColour); indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable); + armedState.BindTo(drawableObject.State); Texture getTextureWithFallback(string name) { @@ -139,15 +140,19 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy accentColour.BindValueChanged(colour => hitCircleSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true); if (hasNumber) indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); + + armedState.BindValueChanged(animate, true); } - public void Animate(ArmedState state) + private void animate(ValueChangedEvent state) { const double legacy_fade_duration = 240; - using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime, true)) + ClearTransforms(true); + + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) { - switch (state) + switch (state.NewValue) { case ArmedState.Hit: circleSprites.FadeOut(legacy_fade_duration, Easing.Out); diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 2dfa1dfbb7..8fb167ba10 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index c0fc19356e..ecb37706b0 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -489,5 +489,23 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(result.Links[2].Url, "\uD83D\uDE00"); Assert.AreEqual(result.Links[3].Url, "\uD83D\uDE20"); } + + [Test] + public void TestAbsoluteExternalLinks() + { + LinkDetails result = MessageFormatter.GetLinkDetails("https://google.com"); + + Assert.AreEqual(LinkAction.External, result.Action); + Assert.AreEqual("https://google.com", result.Argument); + } + + [Test] + public void TestRelativeExternalLinks() + { + LinkDetails result = MessageFormatter.GetLinkDetails("/relative"); + + Assert.AreEqual(LinkAction.External, result.Action); + Assert.AreEqual("/relative", result.Argument); + } } } diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs index 3de2dc72bb..da0d57f9d1 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs @@ -92,6 +92,41 @@ namespace osu.Game.Tests.Gameplay AddAssert("Lifetime is correct", () => dho.LifetimeStart == TestDrawableHitObject.LIFETIME_ON_APPLY && entry.LifetimeStart == TestDrawableHitObject.LIFETIME_ON_APPLY); } + [Test] + public void TestDrawableLifetimeUpdateOnEntryLifetimeChange() + { + TestDrawableHitObject dho = null; + TestLifetimeEntry entry = null; + AddStep("Create DHO", () => + { + dho = new TestDrawableHitObject(null); + dho.Apply(entry = new TestLifetimeEntry(new HitObject())); + Child = dho; + }); + + AddStep("Set entry lifetime", () => + { + entry.LifetimeStart = 777; + entry.LifetimeEnd = 888; + }); + AddAssert("Drawable lifetime is updated", () => dho.LifetimeStart == 777 && dho.LifetimeEnd == 888); + + AddStep("KeepAlive = true", () => entry.KeepAlive = true); + AddAssert("Drawable lifetime is updated", () => dho.LifetimeStart == double.MinValue && dho.LifetimeEnd == double.MaxValue); + + AddStep("Modify start time", () => entry.HitObject.StartTime = 100); + AddAssert("Drawable lifetime is correct", () => dho.LifetimeStart == double.MinValue); + + AddStep("Set LifetimeStart", () => dho.LifetimeStart = 666); + AddAssert("Lifetime change is blocked", () => dho.LifetimeStart == double.MinValue); + + AddStep("Set LifetimeEnd", () => dho.LifetimeEnd = 999); + AddAssert("Lifetime change is blocked", () => dho.LifetimeEnd == double.MaxValue); + + AddStep("KeepAlive = false", () => entry.KeepAlive = false); + AddAssert("Drawable lifetime is restored", () => dho.LifetimeStart == 666 && dho.LifetimeEnd == 999); + } + private class TestDrawableHitObject : DrawableHitObject { public const double INITIAL_LIFETIME_OFFSET = 100; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 9931ee4a45..75a5eec6f7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -90,6 +90,20 @@ namespace osu.Game.Tests.Visual.Gameplay assertChildPosition(5); } + [TestCase("pooled")] + [TestCase("non-pooled")] + public void TestLifetimeRecomputedWhenTimeRangeChanges(string pooled) + { + var beatmap = createBeatmap(_ => pooled == "pooled" ? new TestPooledHitObject() : new TestHitObject()); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); + createTest(beatmap); + + assertDead(3); + + AddStep("increase time range", () => drawableRuleset.TimeRange.Value = 3 * time_range); + assertPosition(3, 1); + } + [Test] public void TestRelativeBeatLengthScaleSingleTimingPoint() { diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 895518e1b9..35d3c7f202 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index d5dda39aa5..2084be765a 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 054febeec3..5ff2fdf6b2 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Graphics.Containers AddText(text[previousLinkEnd..link.Index]); string displayText = text.Substring(link.Index, link.Length); - string linkArgument = link.Argument ?? link.Url; + string linkArgument = link.Argument; string tooltip = displayText == link.Url ? null : link.Url; AddLink(displayText, link.Action, linkArgument, tooltip); @@ -57,7 +57,7 @@ namespace osu.Game.Graphics.Containers createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.External, url), url); public void AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null) - => createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.Custom, null), tooltipText, action); + => createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.Custom, string.Empty), tooltipText, action); public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) => createLink(AddText(text, creationParameters), new LinkDetails(action, argument), tooltipText); @@ -70,7 +70,7 @@ namespace osu.Game.Graphics.Containers createLink(spriteText.Yield(), new LinkDetails(action, argument), tooltipText); } - public void AddLink(IEnumerable text, LinkAction action = LinkAction.External, string linkArgument = null, string tooltipText = null) + public void AddLink(IEnumerable text, LinkAction action, string linkArgument, string tooltipText = null) { foreach (var t in text) AddArbitraryDrawable(t); diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs index c3527fa99a..f3308019ce 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs @@ -4,7 +4,9 @@ using Markdig.Syntax.Inlines; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Overlays; @@ -12,17 +14,50 @@ namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownTextFlowContainer : MarkdownTextFlowContainer { - [Resolved] - private OverlayColourProvider colourProvider { get; set; } - protected override void AddLinkText(string text, LinkInline linkInline) => AddDrawable(new OsuMarkdownLinkText(text, linkInline)); - // TODO : Add background (colour B6) and change font to monospace - protected override void AddCodeInLine(CodeInline codeInline) - => AddText(codeInline.Content, t => { t.Colour = colourProvider.Light1; }); + // TODO : Change font to monospace + protected override void AddCodeInLine(CodeInline codeInline) => AddDrawable(new OsuMarkdownInlineCode + { + Text = codeInline.Content + }); protected override SpriteText CreateEmphasisedSpriteText(bool bold, bool italic) => CreateSpriteText().With(t => t.Font = t.Font.With(weight: bold ? FontWeight.Bold : FontWeight.Regular, italics: italic)); + + private class OsuMarkdownInlineCode : Container + { + [Resolved] + private IMarkdownTextComponent parentTextComponent { get; set; } + + public string Text; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + AutoSizeAxes = Axes.Both; + CornerRadius = 4; + Masking = true; + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6, + }, + parentTextComponent.CreateSpriteText().With(t => + { + t.Colour = colourProvider.Light1; + t.Text = Text; + t.Padding = new MarginPadding + { + Vertical = 1, + Horizontal = 4, + }; + }), + }; + } + } } } diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index c57fc732be..df14d7eb1c 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +#nullable enable + namespace osu.Game.Online.Chat { public static class MessageFormatter @@ -61,7 +63,7 @@ namespace osu.Game.Online.Chat private static string websiteRootUrl = "osu.ppy.sh"; - private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null, char[] escapeChars = null) + private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null, char[]? escapeChars = null) { int captureOffset = 0; @@ -173,12 +175,12 @@ namespace osu.Game.Online.Chat } } - return new LinkDetails(LinkAction.External, url); + break; case "osu": // every internal link also needs some kind of argument if (args.Length < 3) - return new LinkDetails(LinkAction.External, null); + break; LinkAction linkType; @@ -220,7 +222,7 @@ namespace osu.Game.Online.Chat return new LinkDetails(LinkAction.JoinMultiplayerMatch, args[1]); } - return new LinkDetails(LinkAction.External, null); + return new LinkDetails(LinkAction.External, url); } private static MessageFormatterResult format(string toFormat, int startIndex = 0, int space = 3) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b3b0773eff..c51624341e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -277,7 +277,7 @@ namespace osu.Game { case LinkAction.OpenBeatmap: // TODO: proper query params handling - if (link.Argument != null && int.TryParse(link.Argument.Contains('?') ? link.Argument.Split('?')[0] : link.Argument, out int beatmapId)) + if (int.TryParse(link.Argument.Contains('?') ? link.Argument.Split('?')[0] : link.Argument, out int beatmapId)) ShowBeatmap(beatmapId); break; diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index f654fa91cf..096dad88bd 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -35,11 +35,11 @@ namespace osu.Game.Rulesets.Objects HitObject = hitObject; startTimeBindable.BindTo(HitObject.StartTimeBindable); - startTimeBindable.BindValueChanged(_ => setInitialLifetime(), true); + startTimeBindable.BindValueChanged(_ => SetInitialLifetime(), true); // Subscribe to this event before the DrawableHitObject so that the local callback is invoked before the entry is re-applied as a result of DefaultsApplied. // This way, the DrawableHitObject can use OnApply() to overwrite the LifetimeStart that was set inside setInitialLifetime(). - HitObject.DefaultsApplied += _ => setInitialLifetime(); + HitObject.DefaultsApplied += _ => SetInitialLifetime(); } // The lifetime, as set by the hitobject. @@ -94,6 +94,6 @@ namespace osu.Game.Rulesets.Objects /// /// Set using . /// - private void setInitialLifetime() => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; + internal void SetInitialLifetime() => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; } } diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index 64e1ac16bd..4440ca8d21 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -3,6 +3,7 @@ #nullable enable +using System; using System.Diagnostics; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Pooling; @@ -26,14 +27,13 @@ namespace osu.Game.Rulesets.Objects.Pooling /// protected bool HasEntryApplied { get; private set; } - // Drawable's lifetime gets out of sync with entry's lifetime if entry's lifetime is modified. - // We cannot delegate getter to `Entry.LifetimeStart` because it is incompatible with `LifetimeManagementContainer` due to how lifetime change is detected. public override double LifetimeStart { get => base.LifetimeStart; set { - base.LifetimeStart = value; + if (Entry == null && LifetimeStart != value) + throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime)} when entry is not set"); if (Entry != null) Entry.LifetimeStart = value; @@ -45,7 +45,8 @@ namespace osu.Game.Rulesets.Objects.Pooling get => base.LifetimeEnd; set { - base.LifetimeEnd = value; + if (Entry == null && LifetimeEnd != value) + throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime)} when entry is not set"); if (Entry != null) Entry.LifetimeEnd = value; @@ -79,9 +80,8 @@ namespace osu.Game.Rulesets.Objects.Pooling free(); Entry = entry; - - base.LifetimeStart = entry.LifetimeStart; - base.LifetimeEnd = entry.LifetimeEnd; + entry.LifetimeChanged += setLifetimeFromEntry; + setLifetimeFromEntry(entry); OnApply(entry); @@ -117,11 +117,19 @@ namespace osu.Game.Rulesets.Objects.Pooling OnFree(Entry); + Entry.LifetimeChanged -= setLifetimeFromEntry; Entry = null; base.LifetimeStart = double.MinValue; base.LifetimeEnd = double.MaxValue; HasEntryApplied = false; } + + private void setLifetimeFromEntry(LifetimeEntry entry) + { + Debug.Assert(entry == Entry); + base.LifetimeStart = entry.LifetimeStart; + base.LifetimeEnd = entry.LifetimeEnd; + } } } diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index b174632498..cde4182f2d 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -172,11 +172,12 @@ namespace osu.Game.Rulesets.UI.Scrolling if (layoutCache.IsValid) return; - foreach (var hitObject in Objects) - { - if (hitObject.HitObject != null) - invalidateHitObject(hitObject); - } + layoutComputed.Clear(); + + // Reset lifetime to the conservative estimation. + // If a drawable becomes alive by this lifetime, its lifetime will be updated to a more precise lifetime in the next update. + foreach (var entry in Entries) + entry.SetInitialLifetime(); scrollingInfo.Algorithm.Reset(); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d299ba4fda..49b86ad56e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,23 +20,23 @@ - + - - - + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + diff --git a/osu.iOS.props b/osu.iOS.props index 9e178b267a..c1d63f3236 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -89,11 +89,11 @@ - + - +