diff --git a/osu.Android.props b/osu.Android.props
index b24493665e..6744590f0d 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -63,6 +63,6 @@
-
+
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index ddf708d0f1..2d940479f3 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
@@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Description => @"Play with no approach circles and fading circles/sliders.";
public override double ScoreMultiplier => 1.06;
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn) };
private const double fade_in_duration_multiplier = 0.4;
private const double fade_out_duration_multiplier = 0.3;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
new file mode 100644
index 0000000000..62b5ecfd58
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
@@ -0,0 +1,92 @@
+// 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.Collections.Generic;
+using System.Linq;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ public class OsuModSpinIn : Mod, IApplicableToDrawableHitObjects, IReadFromConfig
+ {
+ public override string Name => "Spin In";
+ public override string Acronym => "SI";
+ public override IconUsage Icon => FontAwesome.Solid.Undo;
+ public override ModType Type => ModType.Fun;
+ public override string Description => "Circles spin in. No approach circles.";
+ public override double ScoreMultiplier => 1;
+
+ // todo: this mod should be able to be compatible with hidden with a bit of further implementation.
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModeObjectScaleTween), typeof(OsuModHidden) };
+
+ private const int rotate_offset = 360;
+ private const float rotate_starting_width = 2;
+
+ private Bindable increaseFirstObjectVisibility = new Bindable();
+
+ public void ReadFromConfig(OsuConfigManager config)
+ {
+ increaseFirstObjectVisibility = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility);
+ }
+
+ public void ApplyToDrawableHitObjects(IEnumerable drawables)
+ {
+ foreach (var drawable in drawables.Skip(increaseFirstObjectVisibility.Value ? 1 : 0))
+ {
+ switch (drawable)
+ {
+ case DrawableSpinner _:
+ continue;
+
+ default:
+ drawable.ApplyCustomUpdateState += applyZoomState;
+ break;
+ }
+ }
+ }
+
+ private void applyZoomState(DrawableHitObject drawable, ArmedState state)
+ {
+ var h = (OsuHitObject)drawable.HitObject;
+
+ switch (drawable)
+ {
+ case DrawableHitCircle circle:
+ using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
+ {
+ circle.ApproachCircle.Hide();
+
+ circle.RotateTo(rotate_offset).Then().RotateTo(0, h.TimePreempt, Easing.InOutSine);
+ circle.ScaleTo(new Vector2(rotate_starting_width, 0)).Then().ScaleTo(1, h.TimePreempt, Easing.InOutSine);
+
+ // bypass fade in.
+ if (state == ArmedState.Idle)
+ circle.FadeIn();
+ }
+
+ break;
+
+ case DrawableSlider slider:
+ using (slider.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
+ {
+ slider.ScaleTo(0).Then().ScaleTo(1, h.TimePreempt, Easing.InOutSine);
+
+ // bypass fade in.
+ if (state == ArmedState.Idle)
+ slider.FadeIn();
+ }
+
+ break;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs
index ad6a15718a..e926ade41b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.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;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
@@ -28,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Mods
private Bindable increaseFirstObjectVisibility = new Bindable();
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn) };
+
public void ReadFromConfig(OsuConfigManager config)
{
increaseFirstObjectVisibility = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility);
@@ -64,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Mods
case DrawableSlider _:
case DrawableHitCircle _:
{
- using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
+ using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
drawable.ScaleTo(StartScale).Then().ScaleTo(EndScale, h.TimePreempt, Easing.OutSine);
break;
}
@@ -75,7 +78,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
case DrawableHitCircle circle:
// we don't want to see the approach circle
- using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
+ using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
circle.ApproachCircle.Hide();
break;
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
index aacf3ee08d..a2a23e9ff7 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
Anchor = Anchor.Centre,
Alpha = 0.5f,
}
- }, restrictSize: false);
+ }, confineMode: ConfineMode.NoScaling);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
index 1e2c0ae59f..2e6e7e03ac 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
RelativeSizeAxes = Axes.Both,
Icon = FontAwesome.Solid.ChevronRight
- }, restrictSize: false)
+ })
};
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
index f5f92dd05d..c8062b67b5 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public DrawableSliderTick(SliderTick sliderTick)
: base(sliderTick)
{
- Size = new Vector2(16) * sliderTick.Scale;
+ Size = new Vector2(16 * sliderTick.Scale);
Origin = Anchor.Centre;
InternalChildren = new Drawable[]
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Colour = AccentColour.Value,
Alpha = 0.3f,
}
- }, restrictSize: false)
+ })
};
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
index 84034d3ee9..e8dc63abca 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
Font = OsuFont.Numeric.With(size: 40),
UseFullGlyphHeight = false,
- }, restrictSize: false)
+ }, confineMode: ConfineMode.NoScaling)
{
Text = @"1"
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
index 97c7c9cec5..6bc19ee3b5 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.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;
using System.Collections.Generic;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines;
@@ -75,22 +76,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
protected SliderBody()
{
- InternalChild = path = new SliderPath();
+ RecyclePath();
}
///
/// Initialises a new , releasing all resources retained by the old one.
///
- public void RecyclePath()
+ public virtual void RecyclePath()
{
InternalChild = path = new SliderPath
{
- Position = path.Position,
- PathRadius = path.PathRadius,
- AccentColour = path.AccentColour,
- BorderColour = path.BorderColour,
- BorderSize = path.BorderSize,
- Vertices = path.Vertices
+ Position = path?.Position ?? Vector2.Zero,
+ PathRadius = path?.PathRadius ?? 10,
+ AccentColour = path?.AccentColour ?? Color4.White,
+ BorderColour = path?.BorderColour ?? Color4.White,
+ BorderSize = path?.BorderSize ?? DEFAULT_BORDER_SIZE,
+ Vertices = path?.Vertices ?? Array.Empty()
};
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
index 73b184bffe..a3d3893c8b 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Types;
using osuTK;
@@ -78,9 +79,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
slider.Path.GetPathToProgress(CurrentCurve, 0, 1);
SetVertices(CurrentCurve);
- // The body is sized to the full path size to avoid excessive autosize computations
+ // Force the body to be the final path size to avoid excessive autosize computations
+ Path.AutoSizeAxes = Axes.Both;
Size = Path.Size;
+ updatePathSize();
+
snakedPosition = Path.PositionInBoundingBox(Vector2.Zero);
snakedPathOffset = Path.PositionInBoundingBox(Path.Vertices[0]);
@@ -93,6 +97,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
setRange(lastSnakedStart, lastSnakedEnd);
}
+ public override void RecyclePath()
+ {
+ base.RecyclePath();
+ updatePathSize();
+ }
+
+ private void updatePathSize()
+ {
+ // Force the path to its final size to avoid excessive framebuffer resizes
+ Path.AutoSizeAxes = Axes.None;
+ Path.Size = Size;
+ }
+
private void setRange(double p0, double p1)
{
if (p0 > p1)
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 8df0f77629..7f45fbe1dd 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -134,6 +134,7 @@ namespace osu.Game.Rulesets.Osu
{
new OsuModTransform(),
new OsuModWiggle(),
+ new OsuModSpinIn(),
new MultiMod(new OsuModGrow(), new OsuModDeflate()),
new MultiMod(new ModWindUp(), new ModWindDown()),
};
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
index 27546fa424..b3492a2b31 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
@@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
},
},
}
- }, restrictSize: false)
+ })
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs
deleted file mode 100644
index c7a0df6e9f..0000000000
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs
+++ /dev/null
@@ -1,145 +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;
-using NUnit.Framework;
-using osu.Framework.Audio.Sample;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Textures;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Skinning;
-using osuTK.Graphics;
-
-namespace osu.Game.Tests.Visual.Gameplay
-{
- public class TestSceneSkinReloadable : OsuTestScene
- {
- [Test]
- public void TestInitialLoad()
- {
- var secondarySource = new SecondarySource();
- SkinConsumer consumer = null;
-
- AddStep("setup layout", () =>
- {
- Child = new SkinSourceContainer
- {
- RelativeSizeAxes = Axes.Both,
- Child = new LocalSkinOverrideContainer(secondarySource)
- {
- RelativeSizeAxes = Axes.Both,
- Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)
- }
- };
- });
-
- AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
- AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
- }
-
- [Test]
- public void TestOverride()
- {
- var secondarySource = new SecondarySource();
-
- SkinConsumer consumer = null;
- Container target = null;
-
- AddStep("setup layout", () =>
- {
- Child = new SkinSourceContainer
- {
- RelativeSizeAxes = Axes.Both,
- Child = target = new LocalSkinOverrideContainer(secondarySource)
- {
- RelativeSizeAxes = Axes.Both,
- }
- };
- });
-
- AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)));
- AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
- AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
- }
-
- private class NamedBox : Container
- {
- public NamedBox(string name)
- {
- Children = new Drawable[]
- {
- new Box
- {
- Colour = Color4.Black,
- RelativeSizeAxes = Axes.Both,
- },
- new OsuSpriteText
- {
- Font = OsuFont.Default.With(size: 40),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Text = name
- }
- };
- }
- }
-
- private class SkinConsumer : SkinnableDrawable
- {
- public new Drawable Drawable => base.Drawable;
- public int SkinChangedCount { get; private set; }
-
- public SkinConsumer(string name, Func defaultImplementation, Func allowFallback = null, bool restrictSize = true)
- : base(name, defaultImplementation, allowFallback, restrictSize)
- {
- }
-
- protected override void SkinChanged(ISkinSource skin, bool allowFallback)
- {
- base.SkinChanged(skin, allowFallback);
- SkinChangedCount++;
- }
- }
-
- private class BaseSourceBox : NamedBox
- {
- public BaseSourceBox()
- : base("Base Source")
- {
- }
- }
-
- private class SecondarySourceBox : NamedBox
- {
- public SecondarySourceBox()
- : base("Secondary Source")
- {
- }
- }
-
- private class SecondarySource : ISkin
- {
- public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox();
-
- public Texture GetTexture(string componentName) => throw new NotImplementedException();
-
- public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
-
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
- }
-
- private class SkinSourceContainer : Container, ISkin
- {
- public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox();
-
- public Texture GetTexture(string componentName) => throw new NotImplementedException();
-
- public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
-
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
new file mode 100644
index 0000000000..0b5978e3eb
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
@@ -0,0 +1,283 @@
+// 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.Globalization;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Skinning;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneSkinnableDrawable : OsuTestScene
+ {
+ [Test]
+ public void TestConfineScaleDown()
+ {
+ FillFlowContainer fill = null;
+
+ AddStep("setup layout larger source", () =>
+ {
+ Child = new LocalSkinOverrideContainer(new SizedSource(50))
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = fill = new FillFlowContainer
+ {
+ Size = new Vector2(30),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Spacing = new Vector2(10),
+ Children = new[]
+ {
+ new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling)
+ }
+ },
+ };
+ });
+
+ AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 }));
+ AddStep("adjust scale", () => fill.Scale = new Vector2(2));
+ AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 }));
+ }
+
+ [Test]
+ public void TestConfineScaleUp()
+ {
+ FillFlowContainer fill = null;
+
+ AddStep("setup layout larger source", () =>
+ {
+ Child = new LocalSkinOverrideContainer(new SizedSource(30))
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = fill = new FillFlowContainer
+ {
+ Size = new Vector2(50),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Spacing = new Vector2(10),
+ Children = new[]
+ {
+ new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling)
+ }
+ },
+ };
+ });
+
+ AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 }));
+ AddStep("adjust scale", () => fill.Scale = new Vector2(2));
+ AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 }));
+ }
+
+ [Test]
+ public void TestInitialLoad()
+ {
+ var secondarySource = new SecondarySource();
+ SkinConsumer consumer = null;
+
+ AddStep("setup layout", () =>
+ {
+ Child = new SkinSourceContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new LocalSkinOverrideContainer(secondarySource)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)
+ }
+ };
+ });
+
+ AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
+ AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
+ }
+
+ [Test]
+ public void TestOverride()
+ {
+ var secondarySource = new SecondarySource();
+
+ SkinConsumer consumer = null;
+ Container target = null;
+
+ AddStep("setup layout", () =>
+ {
+ Child = new SkinSourceContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = target = new LocalSkinOverrideContainer(secondarySource)
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ };
+ });
+
+ AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)));
+ AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
+ AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
+ }
+
+ private class ExposedSkinnableDrawable : SkinnableDrawable
+ {
+ public new Drawable Drawable => base.Drawable;
+
+ public ExposedSkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ : base(name, defaultImplementation, allowFallback, confineMode)
+ {
+ }
+ }
+
+ private class DefaultBox : DrawWidthBox
+ {
+ public DefaultBox()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+ }
+
+ private class DrawWidthBox : Container
+ {
+ private readonly OsuSpriteText text;
+
+ public DrawWidthBox()
+ {
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Gray,
+ RelativeSizeAxes = Axes.Both,
+ },
+ text = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ };
+ }
+
+ protected override void UpdateAfterChildren()
+ {
+ base.UpdateAfterChildren();
+ text.Text = DrawWidth.ToString(CultureInfo.InvariantCulture);
+ }
+ }
+
+ private class NamedBox : Container
+ {
+ public NamedBox(string name)
+ {
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Black,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Font = OsuFont.Default.With(size: 40),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = name
+ }
+ };
+ }
+ }
+
+ private class SkinConsumer : SkinnableDrawable
+ {
+ public new Drawable Drawable => base.Drawable;
+ public int SkinChangedCount { get; private set; }
+
+ public SkinConsumer(string name, Func defaultImplementation, Func allowFallback = null)
+ : base(name, defaultImplementation, allowFallback)
+ {
+ }
+
+ protected override void SkinChanged(ISkinSource skin, bool allowFallback)
+ {
+ base.SkinChanged(skin, allowFallback);
+ SkinChangedCount++;
+ }
+ }
+
+ private class BaseSourceBox : NamedBox
+ {
+ public BaseSourceBox()
+ : base("Base Source")
+ {
+ }
+ }
+
+ private class SecondarySourceBox : NamedBox
+ {
+ public SecondarySourceBox()
+ : base("Secondary Source")
+ {
+ }
+ }
+
+ private class SizedSource : ISkin
+ {
+ private readonly float size;
+
+ public SizedSource(float size)
+ {
+ this.size = size;
+ }
+
+ public Drawable GetDrawableComponent(string componentName) =>
+ componentName == "available"
+ ? new DrawWidthBox
+ {
+ Colour = Color4.Yellow,
+ Size = new Vector2(size)
+ }
+ : null;
+
+ public Texture GetTexture(string componentName) => throw new NotImplementedException();
+
+ public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
+
+ public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
+ }
+
+ private class SecondarySource : ISkin
+ {
+ public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox();
+
+ public Texture GetTexture(string componentName) => throw new NotImplementedException();
+
+ public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
+
+ public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
+ }
+
+ private class SkinSourceContainer : Container, ISkin
+ {
+ public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox();
+
+ public Texture GetTexture(string componentName) => throw new NotImplementedException();
+
+ public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
+
+ public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
index 4d3992ce13..9196513a55 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Online
private TestChatOverlay chatOverlay;
private ChannelManager channelManager;
- private readonly Channel channel1 = new Channel(new User()) { Name = "test1" };
+ private readonly Channel channel1 = new Channel(new User()) { Name = "test really long username" };
private readonly Channel channel2 = new Channel(new User()) { Name = "test2" };
[SetUp]
diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
index 46d4cfa98c..d32c0d6156 100644
--- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
+++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
@@ -47,8 +47,8 @@ namespace osu.Game.Tournament.Screens.MapPool
mapFlows = new FillFlowContainer>
{
Y = 100,
- Spacing = new Vector2(10, 20),
- Padding = new MarginPadding(50),
+ Spacing = new Vector2(10, 10),
+ Padding = new MarginPadding(25),
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.Both,
},
@@ -218,7 +218,7 @@ namespace osu.Game.Tournament.Screens.MapPool
{
mapFlows.Add(currentFlow = new FillFlowContainer
{
- Spacing = new Vector2(10, 20),
+ Spacing = new Vector2(10, 5),
Direction = FillDirection.Full,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 65efcaa949..166ba5111c 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -386,7 +386,7 @@ namespace osu.Game.Beatmaps
beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
};
- req.Failure += e => { LogForModel(set, $"Online retrieval failed for {beatmap}", e); };
+ req.Failure += e => { LogForModel(set, $"Online retrieval failed for {beatmap} ({e.Message})"); };
// intentionally blocking to limit web request concurrency
req.Perform(api);
diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index ed65bdc069..efb76deff8 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -253,7 +253,7 @@ namespace osu.Game.Database
using (Stream s = reader.GetStream(file))
s.CopyTo(hashable);
- return hashable.ComputeSHA2Hash();
+ return hashable.Length > 0 ? hashable.ComputeSHA2Hash() : null;
}
///
diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs
index 757a9a349c..714e953816 100644
--- a/osu.Game/Graphics/UserInterface/LineGraph.cs
+++ b/osu.Game/Graphics/UserInterface/LineGraph.cs
@@ -76,7 +76,12 @@ namespace osu.Game.Graphics.UserInterface
{
Masking = true,
RelativeSizeAxes = Axes.Both,
- Child = path = new SmoothPath { RelativeSizeAxes = Axes.Both, PathRadius = 1 }
+ Child = path = new SmoothPath
+ {
+ AutoSizeAxes = Axes.None,
+ RelativeSizeAxes = Axes.Both,
+ PathRadius = 1
+ }
});
}
diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs
index 8134cfb42d..d158186899 100644
--- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs
@@ -64,7 +64,7 @@ namespace osu.Game.Graphics.UserInterface
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
- text = new OsuSpriteText { Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold) },
+ text = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) },
icon = new SpriteIcon
{
Size = new Vector2(14),
@@ -84,7 +84,11 @@ namespace osu.Game.Graphics.UserInterface
}
};
- Current.ValueChanged += selected => { icon.Icon = selected.NewValue ? FontAwesome.Regular.CheckCircle : FontAwesome.Regular.Circle; };
+ Current.ValueChanged += selected =>
+ {
+ icon.Icon = selected.NewValue ? FontAwesome.Regular.CheckCircle : FontAwesome.Regular.Circle;
+ text.Font = text.Font.With(weight: selected.NewValue ? FontWeight.Bold : FontWeight.Medium);
+ };
}
[BackgroundDependencyLoader]
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 41b67f343a..ceaf0c3d5e 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -589,7 +589,7 @@ namespace osu.Game
{
int recentLogCount = 0;
- const double debounce = 5000;
+ const double debounce = 60000;
Logger.NewEntry += entry =>
{
diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs
index 7386bffb1a..d5d9a6c2ce 100644
--- a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs
+++ b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs
@@ -13,6 +13,8 @@ namespace osu.Game.Overlays.Chat.Tabs
public override bool IsSwitchable => false;
+ protected override bool IsBoldWhenActive => false;
+
public ChannelSelectorTabItem()
: base(new ChannelSelectorTabChannel())
{
@@ -22,7 +24,7 @@ namespace osu.Game.Overlays.Chat.Tabs
Icon.Alpha = 0;
Text.Font = Text.Font.With(size: 45);
- TextBold.Font = Text.Font.With(size: 45);
+ Text.Truncate = false;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs
index 7f820e4ff7..266e68f17e 100644
--- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs
+++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs
@@ -16,6 +16,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
using osuTK;
using osuTK.Graphics;
+using osuTK.Input;
namespace osu.Game.Overlays.Chat.Tabs
{
@@ -28,7 +29,6 @@ namespace osu.Game.Overlays.Chat.Tabs
public override bool IsRemovable => !Pinned;
protected readonly SpriteText Text;
- protected readonly SpriteText TextBold;
protected readonly ClickableContainer CloseButton;
private readonly Box box;
private readonly Box highlightBox;
@@ -87,20 +87,17 @@ namespace osu.Game.Overlays.Chat.Tabs
},
Text = new OsuSpriteText
{
- Margin = new MarginPadding(5),
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
Text = value.ToString(),
- Font = OsuFont.GetFont(size: 18)
- },
- TextBold = new OsuSpriteText
- {
- Alpha = 0,
- Margin = new MarginPadding(5),
- Origin = Anchor.CentreLeft,
- Anchor = Anchor.CentreLeft,
- Text = value.ToString(),
- Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold)
+ Font = OsuFont.GetFont(size: 18),
+ Padding = new MarginPadding(5)
+ {
+ Left = LeftTextPadding,
+ Right = RightTextPadding,
+ },
+ RelativeSizeAxes = Axes.X,
+ Truncate = true,
},
CloseButton = new TabCloseButton
{
@@ -118,10 +115,16 @@ namespace osu.Game.Overlays.Chat.Tabs
};
}
+ protected virtual float LeftTextPadding => 5;
+
+ protected virtual float RightTextPadding => IsRemovable ? 40 : 5;
+
protected virtual IconUsage DisplayIcon => FontAwesome.Solid.Hashtag;
protected virtual bool ShowCloseOnHover => true;
+ protected virtual bool IsBoldWhenActive => true;
+
protected override bool OnHover(HoverEvent e)
{
if (IsRemovable && ShowCloseOnHover)
@@ -138,6 +141,19 @@ namespace osu.Game.Overlays.Chat.Tabs
updateState();
}
+ protected override bool OnMouseUp(MouseUpEvent e)
+ {
+ switch (e.Button)
+ {
+ case MouseButton.Middle:
+ CloseButton.Click();
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
@@ -189,8 +205,7 @@ namespace osu.Game.Overlays.Chat.Tabs
box.FadeColour(BackgroundActive, TRANSITION_LENGTH, Easing.OutQuint);
highlightBox.FadeIn(TRANSITION_LENGTH, Easing.OutQuint);
- Text.FadeOut(TRANSITION_LENGTH, Easing.OutQuint);
- TextBold.FadeIn(TRANSITION_LENGTH, Easing.OutQuint);
+ if (IsBoldWhenActive) Text.Font = Text.Font.With(weight: FontWeight.Bold);
}
protected virtual void FadeInactive()
@@ -202,8 +217,7 @@ namespace osu.Game.Overlays.Chat.Tabs
box.FadeColour(BackgroundInactive, TRANSITION_LENGTH, Easing.OutQuint);
highlightBox.FadeOut(TRANSITION_LENGTH, Easing.OutQuint);
- Text.FadeIn(TRANSITION_LENGTH, Easing.OutQuint);
- TextBold.FadeOut(TRANSITION_LENGTH, Easing.OutQuint);
+ Text.Font = Text.Font.With(weight: FontWeight.Medium);
}
protected override void OnActivated() => updateState();
diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs
index 9e87bae864..1413b8fe78 100644
--- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs
+++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs
@@ -62,11 +62,10 @@ namespace osu.Game.Overlays.Chat.Tabs
});
avatar.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint);
-
- Text.X = ChatOverlay.TAB_AREA_HEIGHT;
- TextBold.X = ChatOverlay.TAB_AREA_HEIGHT;
}
+ protected override float LeftTextPadding => base.LeftTextPadding + ChatOverlay.TAB_AREA_HEIGHT;
+
protected override bool ShowCloseOnHover => false;
protected override void FadeActive()
diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
index 2150726a42..61c2644c6f 100644
--- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
+++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
@@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Judgements
Font = OsuFont.Numeric.With(size: 12),
Colour = judgementColour(Result.Type),
Scale = new Vector2(0.85f, 1),
- }, restrictSize: false)
+ })
};
}
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 1d9d885527..181ae37a8b 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -147,12 +147,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (State.Value == newState && !force)
return;
- // apply any custom state overrides
- ApplyCustomUpdateState?.Invoke(this, newState);
-
- if (newState == ArmedState.Hit)
- PlaySamples();
-
if (UseTransformStateManagement)
{
double transformTime = HitObject.StartTime - InitialLifetimeOffset;
@@ -177,6 +171,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
state.Value = newState;
UpdateState(newState);
+
+ // apply any custom state overrides
+ ApplyCustomUpdateState?.Invoke(this, newState);
+
+ if (newState == ArmedState.Hit)
+ PlaySamples();
}
///
diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs
index 9ffd620e55..90806bab6e 100644
--- a/osu.Game/Screens/Multi/Multiplayer.cs
+++ b/osu.Game/Screens/Multi/Multiplayer.cs
@@ -212,7 +212,7 @@ namespace osu.Game.Screens.Multi
public override bool OnExiting(IScreen next)
{
- if (!(screenStack.CurrentScreen is LoungeSubScreen))
+ if (screenStack.CurrentScreen != null && !(screenStack.CurrentScreen is LoungeSubScreen))
{
screenStack.Exit();
return true;
diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs
index d390787090..2b401778a6 100644
--- a/osu.Game/Screens/Play/BreakOverlay.cs
+++ b/osu.Game/Screens/Play/BreakOverlay.cs
@@ -19,11 +19,11 @@ namespace osu.Game.Screens.Play
private const float remaining_time_container_max_size = 0.3f;
private const int vertical_margin = 25;
- private List breaks;
-
private readonly Container fadeContainer;
- public List Breaks
+ private IReadOnlyList breaks;
+
+ public IReadOnlyList Breaks
{
get => breaks;
set
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index 16354534f4..cf88b697d9 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -26,6 +26,9 @@ namespace osu.Game.Screens.Select
{
public class BeatmapCarousel : OsuScrollContainer
{
+ private const float bleed_top = FilterControl.HEIGHT;
+ private const float bleed_bottom = Footer.HEIGHT;
+
///
/// Triggered when the loaded change and are completely loaded.
///
@@ -81,7 +84,8 @@ namespace osu.Game.Screens.Select
itemsCache.Invalidate();
scrollPositionCache.Invalidate();
- Schedule(() =>
+ // Run on late scheduler want to ensure this runs after all pending UpdateBeatmapSet / RemoveBeatmapSet operations are run.
+ SchedulerAfterChildren.Add(() =>
{
BeatmapSetsChanged?.Invoke();
BeatmapSetsLoaded = true;
@@ -129,19 +133,16 @@ namespace osu.Game.Screens.Select
loadBeatmapSets(beatmaps.GetAllUsableBeatmapSetsEnumerable());
}
- public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet)
+ public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() =>
{
- Schedule(() =>
- {
- var existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.ID == beatmapSet.ID);
+ var existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.ID == beatmapSet.ID);
- if (existingSet == null)
- return;
+ if (existingSet == null)
+ return;
- root.RemoveChild(existingSet);
- itemsCache.Invalidate();
- });
- }
+ root.RemoveChild(existingSet);
+ itemsCache.Invalidate();
+ });
public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() =>
{
@@ -338,6 +339,25 @@ namespace osu.Game.Screens.Select
public bool AllowSelection = true;
+ ///
+ /// Half the height of the visible content.
+ ///
+ /// This is different from the height of , since
+ /// the beatmap carousel bleeds into the and the
+ ///
+ ///
+ private float visibleHalfHeight => (DrawHeight + bleed_bottom + bleed_top) / 2;
+
+ ///
+ /// The position of the lower visible bound with respect to the current scroll position.
+ ///
+ private float visibleBottomBound => Current + DrawHeight + bleed_bottom;
+
+ ///
+ /// The position of the upper visible bound with respect to the current scroll position.
+ ///
+ private float visibleUpperBound => Current - bleed_top;
+
public void FlushPendingFilterOperations()
{
if (PendingFilter?.Completed == false)
@@ -414,6 +434,8 @@ namespace osu.Game.Screens.Select
return true;
}
+ protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => ReceivePositionalInputAt(screenSpacePos);
+
protected override void Update()
{
base.Update();
@@ -424,17 +446,15 @@ namespace osu.Game.Screens.Select
if (!scrollPositionCache.IsValid)
updateScrollPosition();
- float drawHeight = DrawHeight;
-
// Remove all items that should no longer be on-screen
- scrollableContent.RemoveAll(p => p.Y < Current - p.DrawHeight || p.Y > Current + drawHeight || !p.IsPresent);
+ scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent);
// Find index range of all items that should be on-screen
Trace.Assert(Items.Count == yPositions.Count);
- int firstIndex = yPositions.BinarySearch(Current - DrawableCarouselItem.MAX_HEIGHT);
+ int firstIndex = yPositions.BinarySearch(visibleUpperBound - DrawableCarouselItem.MAX_HEIGHT);
if (firstIndex < 0) firstIndex = ~firstIndex;
- int lastIndex = yPositions.BinarySearch(Current + drawHeight);
+ int lastIndex = yPositions.BinarySearch(visibleBottomBound);
if (lastIndex < 0) lastIndex = ~lastIndex;
int notVisibleCount = 0;
@@ -486,9 +506,8 @@ namespace osu.Game.Screens.Select
// Update externally controlled state of currently visible items
// (e.g. x-offset and opacity).
- float halfHeight = drawHeight / 2;
foreach (DrawableCarouselItem p in scrollableContent.Children)
- updateItem(p, halfHeight);
+ updateItem(p);
}
protected override void Dispose(bool isDisposing)
@@ -542,7 +561,7 @@ namespace osu.Game.Screens.Select
yPositions.Clear();
- float currentY = DrawHeight / 2;
+ float currentY = visibleHalfHeight;
DrawableCarouselBeatmapSet lastSet = null;
scrollTarget = null;
@@ -575,7 +594,6 @@ namespace osu.Game.Screens.Select
float? setY = null;
if (!d.IsLoaded || beatmap.Alpha == 0) // can't use IsPresent due to DrawableCarouselItem override.
- // ReSharper disable once PossibleNullReferenceException (resharper broken?)
setY = lastSet.Y + lastSet.DrawHeight + 5;
if (d.IsLoaded)
@@ -596,7 +614,7 @@ namespace osu.Game.Screens.Select
currentY += d.DrawHeight + 5;
}
- currentY += DrawHeight / 2;
+ currentY += visibleHalfHeight;
scrollableContent.Height = currentY;
if (BeatmapSetsLoaded && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected))
@@ -637,18 +655,15 @@ namespace osu.Game.Screens.Select
/// the current scroll position.
///
/// The item to be updated.
- /// Half the draw height of the carousel container.
- private void updateItem(DrawableCarouselItem p, float halfHeight)
+ private void updateItem(DrawableCarouselItem p)
{
- var height = p.IsPresent ? p.DrawHeight : 0;
-
- float itemDrawY = p.Position.Y - Current + height / 2;
- float dist = Math.Abs(1f - itemDrawY / halfHeight);
+ float itemDrawY = p.Position.Y - visibleUpperBound + p.DrawHeight / 2;
+ float dist = Math.Abs(1f - itemDrawY / visibleHalfHeight);
// Setting the origin position serves as an additive position on top of potential
// local transformation we may want to apply (e.g. when a item gets selected, we
// may want to smoothly transform it leftwards.)
- p.OriginPosition = new Vector2(-offsetX(dist, halfHeight), 0);
+ p.OriginPosition = new Vector2(-offsetX(dist, visibleHalfHeight), 0);
// We are applying a multiplicative alpha (which is internally done by nesting an
// additional container and setting that container's alpha) such that we can
diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs
index f66cd2b29a..7f82d3cc12 100644
--- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs
+++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs
@@ -25,22 +25,6 @@ namespace osu.Game.Screens.Select
private Bindable selectedTab;
- private void invokeOnFilter()
- {
- OnFilter?.Invoke(tabs.Current.Value, modsCheckbox.Current.Value);
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colour, OsuConfigManager config)
- {
- modsCheckbox.AccentColour = tabs.AccentColour = colour.YellowLight;
-
- selectedTab = config.GetBindable(OsuSetting.BeatmapDetailTab);
-
- tabs.Current.BindTo(selectedTab);
- tabs.Current.TriggerChange();
- }
-
public BeatmapDetailAreaTabControl()
{
Height = HEIGHT;
@@ -66,12 +50,31 @@ namespace osu.Game.Screens.Select
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Text = @"Mods",
+ Alpha = 0,
},
};
tabs.Current.ValueChanged += _ => invokeOnFilter();
modsCheckbox.Current.ValueChanged += _ => invokeOnFilter();
}
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colour, OsuConfigManager config)
+ {
+ modsCheckbox.AccentColour = tabs.AccentColour = colour.YellowLight;
+
+ selectedTab = config.GetBindable(OsuSetting.BeatmapDetailTab);
+
+ tabs.Current.BindTo(selectedTab);
+ tabs.Current.TriggerChange();
+ }
+
+ private void invokeOnFilter()
+ {
+ OnFilter?.Invoke(tabs.Current.Value, modsCheckbox.Current.Value);
+
+ modsCheckbox.FadeTo(tabs.Current.Value == BeatmapDetailTab.Details ? 0 : 1, 200, Easing.OutQuint);
+ }
}
public enum BeatmapDetailTab
diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs
index 57fe15fd99..84e8e90f54 100644
--- a/osu.Game/Screens/Select/FilterControl.cs
+++ b/osu.Game/Screens/Select/FilterControl.cs
@@ -14,7 +14,6 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Select.Filter;
using Container = osu.Framework.Graphics.Containers.Container;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Input.Events;
using osu.Game.Configuration;
using osu.Game.Rulesets;
@@ -22,6 +21,8 @@ namespace osu.Game.Screens.Select
{
public class FilterControl : Container
{
+ public const float HEIGHT = 100;
+
public Action FilterChanged;
private readonly OsuTabControl sortTabs;
@@ -187,11 +188,5 @@ namespace osu.Game.Screens.Select
}
private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria());
-
- protected override bool OnMouseDown(MouseDownEvent e) => true;
-
- protected override bool OnMouseMove(MouseMoveEvent e) => true;
-
- protected override bool OnClick(ClickEvent e) => true;
}
}
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index 20dbcf2693..7dd934f91a 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -121,7 +121,7 @@ namespace osu.Game.Screens.Select
Size = new Vector2(wedged_container_size.X, 1),
Padding = new MarginPadding
{
- Bottom = 50,
+ Bottom = Footer.HEIGHT,
Top = wedged_container_size.Y + left_area_padding,
Left = left_area_padding,
Right = left_area_padding * 2,
@@ -147,20 +147,29 @@ namespace osu.Game.Screens.Select
Width = 0.5f,
Children = new Drawable[]
{
- Carousel = new BeatmapCarousel
+ new Container
{
- Masking = false,
RelativeSizeAxes = Axes.Both,
- Size = new Vector2(1 - wedged_container_size.X, 1),
- Anchor = Anchor.CentreRight,
- Origin = Anchor.CentreRight,
- SelectionChanged = updateSelectedBeatmap,
- BeatmapSetsChanged = carouselBeatmapsLoaded,
+ Padding = new MarginPadding
+ {
+ Top = FilterControl.HEIGHT,
+ Bottom = Footer.HEIGHT
+ },
+ Child = Carousel = new BeatmapCarousel
+ {
+ Masking = false,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(1 - wedged_container_size.X, 1),
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ SelectionChanged = updateSelectedBeatmap,
+ BeatmapSetsChanged = carouselBeatmapsLoaded,
+ },
},
FilterControl = new FilterControl
{
RelativeSizeAxes = Axes.X,
- Height = 100,
+ Height = FilterControl.HEIGHT,
FilterChanged = c => Carousel.Filter(c),
Background = { Width = 2 },
Exit = () =>
diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs
index 995cb15136..eb0508b568 100644
--- a/osu.Game/Skinning/SkinnableDrawable.cs
+++ b/osu.Game/Skinning/SkinnableDrawable.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using osu.Framework.Caching;
using osu.Framework.Graphics;
using osuTK;
@@ -9,8 +10,8 @@ namespace osu.Game.Skinning
{
public class SkinnableDrawable : SkinnableDrawable
{
- public SkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, bool restrictSize = true)
- : base(name, defaultImplementation, allowFallback, restrictSize)
+ public SkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ : base(name, defaultImplementation, allowFallback, confineMode)
{
}
}
@@ -29,7 +30,7 @@ namespace osu.Game.Skinning
private readonly string componentName;
- private readonly bool restrictSize;
+ private readonly ConfineMode confineMode;
///
/// Create a new skinnable drawable.
@@ -37,28 +38,32 @@ namespace osu.Game.Skinning
/// The namespace-complete resource name for this skinnable element.
/// A function to create the default skin implementation of this element.
/// A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present.
- /// Whether a user-skin drawable should be limited to the size of our parent.
- public SkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, bool restrictSize = true)
- : this(name, allowFallback, restrictSize)
+ /// How (if at all) the should be resize to fit within our own bounds.
+ public SkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ : this(name, allowFallback, confineMode)
{
createDefault = defaultImplementation;
}
- protected SkinnableDrawable(string name, Func allowFallback = null, bool restrictSize = true)
+ protected SkinnableDrawable(string name, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
: base(allowFallback)
{
componentName = name;
- this.restrictSize = restrictSize;
+ this.confineMode = confineMode;
RelativeSizeAxes = Axes.Both;
}
private readonly Func createDefault;
+ private readonly Cached scaling = new Cached();
+
+ private bool isDefault;
+
protected virtual T CreateDefault(string name) => createDefault(name);
///
- /// Whether to apply size restrictions (specified via ) to the default implementation.
+ /// Whether to apply size restrictions (specified via ) to the default implementation.
///
protected virtual bool ApplySizeRestrictionsToDefault => false;
@@ -66,7 +71,7 @@ namespace osu.Game.Skinning
{
Drawable = skin.GetDrawableComponent(componentName);
- bool isDefault = false;
+ isDefault = false;
if (Drawable == null && allowFallback)
{
@@ -76,21 +81,57 @@ namespace osu.Game.Skinning
if (Drawable != null)
{
- if (restrictSize && (!isDefault || ApplySizeRestrictionsToDefault))
- {
- Drawable.RelativeSizeAxes = Axes.Both;
- Drawable.Size = Vector2.One;
- Drawable.Scale = Vector2.One;
- Drawable.FillMode = FillMode.Fit;
- }
-
+ scaling.Invalidate();
Drawable.Origin = Anchor.Centre;
Drawable.Anchor = Anchor.Centre;
-
InternalChild = Drawable;
}
else
ClearInternal();
}
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (!scaling.IsValid)
+ {
+ try
+ {
+ if (Drawable == null || (isDefault && !ApplySizeRestrictionsToDefault)) return;
+
+ switch (confineMode)
+ {
+ case ConfineMode.NoScaling:
+ return;
+
+ case ConfineMode.ScaleDownToFit:
+ if (Drawable.DrawSize.X <= DrawSize.X && Drawable.DrawSize.Y <= DrawSize.Y)
+ return;
+
+ break;
+ }
+
+ Drawable.RelativeSizeAxes = Axes.Both;
+ Drawable.Size = Vector2.One;
+ Drawable.Scale = Vector2.One;
+ Drawable.FillMode = FillMode.Fit;
+ }
+ finally
+ {
+ scaling.Validate();
+ }
+ }
+ }
+ }
+
+ public enum ConfineMode
+ {
+ ///
+ /// Don't apply any scaling. This allows the user element to be of any size, exceeding specified bounds.
+ ///
+ NoScaling,
+ ScaleDownToFit,
+ ScaleToFit,
}
}
diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs
index ceb1ed0f70..1716ec71de 100644
--- a/osu.Game/Skinning/SkinnableSprite.cs
+++ b/osu.Game/Skinning/SkinnableSprite.cs
@@ -18,8 +18,8 @@ namespace osu.Game.Skinning
[Resolved]
private TextureStore textures { get; set; }
- public SkinnableSprite(string name, Func allowFallback = null, bool restrictSize = true)
- : base(name, allowFallback, restrictSize)
+ public SkinnableSprite(string name, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ : base(name, allowFallback, confineMode)
{
}
diff --git a/osu.Game/Skinning/SkinnableSpriteText.cs b/osu.Game/Skinning/SkinnableSpriteText.cs
index 36e646d743..d12a6f74f7 100644
--- a/osu.Game/Skinning/SkinnableSpriteText.cs
+++ b/osu.Game/Skinning/SkinnableSpriteText.cs
@@ -8,8 +8,8 @@ namespace osu.Game.Skinning
{
public class SkinnableSpriteText : SkinnableDrawable, IHasText
{
- public SkinnableSpriteText(string name, Func defaultImplementation, Func allowFallback = null, bool restrictSize = true)
- : base(name, defaultImplementation, allowFallback, restrictSize)
+ public SkinnableSpriteText(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ : base(name, defaultImplementation, allowFallback, confineMode)
{
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index c05cc6f9dd..0b2baa982b 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -15,7 +15,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 3b18039600..55d1afa645 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -105,8 +105,8 @@
-
-
+
+
diff --git a/osu.iOS/AppDelegate.cs b/osu.iOS/AppDelegate.cs
index 058e246ed8..9ef21e014c 100644
--- a/osu.iOS/AppDelegate.cs
+++ b/osu.iOS/AppDelegate.cs
@@ -1,15 +1,25 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Threading.Tasks;
using Foundation;
using osu.Framework.iOS;
using osu.Game;
+using UIKit;
namespace osu.iOS
{
[Register("AppDelegate")]
public class AppDelegate : GameAppDelegate
{
- protected override Framework.Game CreateGame() => new OsuGameIOS();
+ private OsuGameIOS game;
+
+ protected override Framework.Game CreateGame() => game = new OsuGameIOS();
+
+ public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation)
+ {
+ Task.Run(() => game.Import(url.Path));
+ return true;
+ }
}
}
diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist
index d7992353cf..0775d1522d 100644
--- a/osu.iOS/Info.plist
+++ b/osu.iOS/Info.plist
@@ -40,5 +40,70 @@
XSAppIconAssets
Assets.xcassets/AppIcon.appiconset
+ UTExportedTypeDeclarations
+
+
+ UTTypeConformsTo
+
+
+
+ UTTypeIdentifier
+ sh.ppy.osu.items
+ UTTypeTagSpecification
+
+
+
+ UTTypeConformsTo
+
+ sh.ppy.osu.items
+
+ UTTypeIdentifier
+ sh.ppy.osu.osr
+ UTTypeTagSpecification
+
+ public.filename-extension
+ osr
+
+
+
+ UTTypeConformsTo
+
+ sh.ppy.osu.items
+
+ UTTypeIdentifier
+ sh.ppy.osu.osk
+ UTTypeTagSpecification
+
+ public.filename-extension
+ osk
+
+
+
+ UTTypeConformsTo
+
+ sh.ppy.osu.items
+
+ UTTypeIdentifier
+ sh.ppy.osu.osz
+ UTTypeTagSpecification
+
+ public.filename-extension
+ osz
+
+
+
+ CFBundleDocumentTypes
+
+
+ LSHandlerRank
+ Owner
+ CFBundleTypeName
+ Supported osu! files
+ LSItemContentTypes
+
+ sh.ppy.osu.items
+
+
+