diff --git a/README.md b/README.md index 19aba5a31f..55f2eebec9 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ If the build fails, try to restore nuget packages with `dotnet restore`. On Linux, the environment variable `LD_LIBRARY_PATH` must point to the build directory, located at `osu.Desktop/bin/Debug/$NETCORE_VERSION`. -`$NETCORE_VERSION` is the version of .NET Core SDK. You can have it with `grep TargetFramework osu.Desktop/osu.Desktop.csproj | sed -r 's/.*>(.*)<\/.*/\1/'`. +`$NETCORE_VERSION` is the version of the targeted .NET Core SDK. You can check it by running `grep TargetFramework osu.Desktop/osu.Desktop.csproj | sed -r 's/.*>(.*)<\/.*/\1/'`. For example, you can run osu! with the following command: diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs new file mode 100644 index 0000000000..33f93cdb4a --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -0,0 +1,105 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Tests.Visual; +using System; +using System.Collections.Generic; +using osu.Game.Skinning; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osuTK.Graphics; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestSceneCatcher : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(CatcherSprite), + }; + + private readonly Container container; + + public TestSceneCatcher() + { + Child = container = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + + [BackgroundDependencyLoader] + private void load() + { + AddStep("show default catcher implementation", () => { container.Child = new CatcherSprite(); }); + + AddStep("show custom catcher implementation", () => + { + container.Child = new CatchCustomSkinSourceContainer + { + Child = new CatcherSprite() + }; + }); + } + + private class CatcherCustomSkin : Container + { + public CatcherCustomSkin() + { + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Blue + }, + new SpriteText + { + Text = "custom" + } + }; + } + } + + [Cached(typeof(ISkinSource))] + private class CatchCustomSkinSourceContainer : Container, ISkinSource + { + public event Action SourceChanged + { + add { } + remove { } + } + + public Drawable GetDrawableComponent(string componentName) + { + switch (componentName) + { + case "Play/Catch/fruit-catcher-idle": + return new CatcherCustomSkin(); + } + + return null; + } + + public SampleChannel GetSample(string sampleName) => + throw new NotImplementedException(); + + public Texture GetTexture(string componentName) => + throw new NotImplementedException(); + + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => + throw new NotImplementedException(); + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 90052d9b11..0b06e958e6 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -6,8 +6,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.MathUtils; using osu.Game.Beatmaps; @@ -141,7 +139,7 @@ namespace osu.Game.Rulesets.Catch.UI [BackgroundDependencyLoader] private void load() { - Children = new Drawable[] + Children = new[] { caughtFruit = new Container { @@ -212,7 +210,7 @@ namespace osu.Game.Rulesets.Catch.UI Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50); } - private Sprite createCatcherSprite() => new CatcherSprite(); + private Drawable createCatcherSprite() => new CatcherSprite(); /// /// Add a caught fruit to the catcher's stack. @@ -444,23 +442,6 @@ namespace osu.Game.Rulesets.Catch.UI fruit.Expire(); } - - private class CatcherSprite : Sprite - { - public CatcherSprite() - { - Size = new Vector2(CATCHER_SIZE); - - // Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling. - OriginPosition = new Vector2(-0.02f, 0.06f) * CATCHER_SIZE; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - Texture = textures.Get(@"Play/Catch/fruit-catcher-idle"); - } - } } } } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs new file mode 100644 index 0000000000..c0c1952064 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs @@ -0,0 +1,33 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class CatcherSprite : CompositeDrawable + { + public CatcherSprite() + { + Size = new Vector2(CatcherArea.CATCHER_SIZE); + + // Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling. + OriginPosition = new Vector2(-0.02f, 0.06f) * CatcherArea.CATCHER_SIZE; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new SkinnableSprite(@"Play/Catch/fruit-catcher-idle") + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs index 8ee065aaea..9981585f9e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Skinning; @@ -25,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces [BackgroundDependencyLoader] private void load(TextureStore textures) { - Child = new SkinnableDrawable("Play/osu/approachcircle", name => new Sprite { Texture = textures.Get(name) }); + Child = new SkinnableSprite("Play/osu/approachcircle"); } } } diff --git a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs index e97983dd8b..55aaeed8bf 100644 --- a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs +++ b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs @@ -14,94 +14,132 @@ namespace osu.Game.Tests.Visual.Components [TestFixture] public class TestSceneIdleTracker : ManualInputManagerTestScene { - private readonly IdleTrackingBox box1; - private readonly IdleTrackingBox box2; - private readonly IdleTrackingBox box3; - private readonly IdleTrackingBox box4; + private IdleTrackingBox box1; + private IdleTrackingBox box2; + private IdleTrackingBox box3; + private IdleTrackingBox box4; - public TestSceneIdleTracker() + private IdleTrackingBox[] boxes; + + [SetUp] + public void SetUp() => Schedule(() => { - Children = new Drawable[] + InputManager.MoveMouseTo(Vector2.Zero); + + Children = boxes = new[] { - box1 = new IdleTrackingBox(1000) + box1 = new IdleTrackingBox(2000) { + Name = "TopLeft", RelativeSizeAxes = Axes.Both, Colour = Color4.Red, Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, }, - box2 = new IdleTrackingBox(2000) + box2 = new IdleTrackingBox(4000) { + Name = "TopRight", RelativeSizeAxes = Axes.Both, Colour = Color4.Green, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, - box3 = new IdleTrackingBox(3000) + box3 = new IdleTrackingBox(6000) { + Name = "BottomLeft", RelativeSizeAxes = Axes.Both, Colour = Color4.Blue, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, }, - box4 = new IdleTrackingBox(4000) + box4 = new IdleTrackingBox(8000) { + Name = "BottomRight", RelativeSizeAxes = Axes.Both, Colour = Color4.Orange, Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, }, }; - } + }); [Test] public void TestNudge() { - AddStep("move mouse to top left", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre)); + AddStep("move to top left", () => InputManager.MoveMouseTo(box1)); - AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle); + waitForAllIdle(); AddStep("nudge mouse", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre + new Vector2(1))); - AddAssert("check not idle", () => !box1.IsIdle); - AddAssert("check idle", () => box2.IsIdle); - AddAssert("check idle", () => box3.IsIdle); - AddAssert("check idle", () => box4.IsIdle); + checkIdleStatus(1, false); + checkIdleStatus(2, true); + checkIdleStatus(3, true); + checkIdleStatus(4, true); } [Test] public void TestMovement() { - AddStep("move mouse", () => InputManager.MoveMouseTo(box2.ScreenSpaceDrawQuad.Centre)); + AddStep("move to top right", () => InputManager.MoveMouseTo(box2)); - AddAssert("check not idle", () => box1.IsIdle); - AddAssert("check not idle", () => !box2.IsIdle); - AddAssert("check idle", () => box3.IsIdle); - AddAssert("check idle", () => box4.IsIdle); + checkIdleStatus(1, true); + checkIdleStatus(2, false); + checkIdleStatus(3, true); + checkIdleStatus(4, true); - AddStep("move mouse", () => InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.Centre)); - AddStep("move mouse", () => InputManager.MoveMouseTo(box4.ScreenSpaceDrawQuad.Centre)); + AddStep("move to bottom left", () => InputManager.MoveMouseTo(box3)); + AddStep("move to bottom right", () => InputManager.MoveMouseTo(box4)); - AddAssert("check not idle", () => box1.IsIdle); - AddAssert("check not idle", () => !box2.IsIdle); - AddAssert("check idle", () => !box3.IsIdle); - AddAssert("check idle", () => !box4.IsIdle); + checkIdleStatus(1, true); + checkIdleStatus(2, false); + checkIdleStatus(3, false); + checkIdleStatus(4, false); - AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle); + waitForAllIdle(); } [Test] public void TestTimings() { - AddStep("move mouse", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre)); + AddStep("move to centre", () => InputManager.MoveMouseTo(Content)); + + checkIdleStatus(1, false); + checkIdleStatus(2, false); + checkIdleStatus(3, false); + checkIdleStatus(4, false); - AddAssert("check not idle", () => !box1.IsIdle && !box2.IsIdle && !box3.IsIdle && !box4.IsIdle); AddUntilStep("Wait for idle", () => box1.IsIdle); - AddAssert("check not idle", () => !box2.IsIdle && !box3.IsIdle && !box4.IsIdle); + + checkIdleStatus(1, true); + checkIdleStatus(2, false); + checkIdleStatus(3, false); + checkIdleStatus(4, false); + AddUntilStep("Wait for idle", () => box2.IsIdle); - AddAssert("check not idle", () => !box3.IsIdle && !box4.IsIdle); + + checkIdleStatus(1, true); + checkIdleStatus(2, true); + checkIdleStatus(3, false); + checkIdleStatus(4, false); + AddUntilStep("Wait for idle", () => box3.IsIdle); + checkIdleStatus(1, true); + checkIdleStatus(2, true); + checkIdleStatus(3, true); + checkIdleStatus(4, false); + + waitForAllIdle(); + } + + private void checkIdleStatus(int box, bool expectedIdle) + { + AddAssert($"box {box} is {(expectedIdle ? "idle" : "active")}", () => boxes[box - 1].IsIdle == expectedIdle); + } + + private void waitForAllIdle() + { AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNumberBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNumberBox.cs new file mode 100644 index 0000000000..f73450db60 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNumberBox.cs @@ -0,0 +1,55 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneNumberBox : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuNumberBox), + }; + + private OsuNumberBox numberBox; + + [BackgroundDependencyLoader] + private void load() + { + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Padding = new MarginPadding { Horizontal = 250 }, + Child = numberBox = new OsuNumberBox + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + PlaceholderText = "Insert numbers here" + } + }; + + clearInput(); + AddStep("enter numbers", () => numberBox.Text = "987654321"); + expectedValue("987654321"); + clearInput(); + AddStep("enter text + single number", () => numberBox.Text = "1 hello 2 world 3"); + expectedValue("123"); + clearInput(); + } + + private void clearInput() => AddStep("clear input", () => numberBox.Text = null); + + private void expectedValue(string value) => AddAssert("expect number", () => numberBox.Text == value); + } +} diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs index 1dc91abe6d..b036350879 100644 --- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -183,7 +183,7 @@ namespace osu.Game.Tournament.Screens.Editors AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new SettingsTextBox + new SettingsNumberBox { LabelText = "Beatmap ID", RelativeSizeAxes = Axes.None, diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 93745acabf..a4479f3cfd 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -231,7 +231,7 @@ namespace osu.Game.Tournament.Screens.Editors AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new SettingsTextBox + new SettingsNumberBox { LabelText = "User ID", RelativeSizeAxes = Axes.None, diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs index cfa44537d6..9e1888b44b 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -191,8 +190,6 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components public RoundDisplay() { - CornerRadius = 10; - Masking = true; Width = 200; Height = 20; } @@ -208,11 +205,6 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components { InternalChildren = new Drawable[] { - new Box - { - Colour = new Color4(47, 71, 67, 255), - RelativeSizeAxes = Axes.Both, - }, new OsuSpriteText { Anchor = Anchor.Centre, diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs index 67d6bc4fa6..dacd98d3b8 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs @@ -51,8 +51,8 @@ namespace osu.Game.Tournament.Screens.Ladder.Components name = round.Name.GetBoundCopy(); name.BindValueChanged(n => textName.Text = ((losers ? "Losers " : "") + round.Name).ToUpper(), true); - description = round.Name.GetBoundCopy(); - description.BindValueChanged(n => textDescription.Text = round.Description.Value.ToUpper(), true); + description = round.Description.GetBoundCopy(); + description.BindValueChanged(n => textDescription.Text = round.Description.Value?.ToUpper(), true); } } } diff --git a/osu.Game.Tournament/Screens/Showcase/TournamentLogo.cs b/osu.Game.Tournament/Screens/Showcase/TournamentLogo.cs index 0db3348e5d..1fee2b29e8 100644 --- a/osu.Game.Tournament/Screens/Showcase/TournamentLogo.cs +++ b/osu.Game.Tournament/Screens/Showcase/TournamentLogo.cs @@ -6,17 +6,25 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osuTK; namespace osu.Game.Tournament.Screens.Showcase { public class TournamentLogo : CompositeDrawable { - public TournamentLogo() + public TournamentLogo(bool includeRoundBackground = true) { RelativeSizeAxes = Axes.X; - Height = 95; Margin = new MarginPadding { Vertical = 5 }; + + if (includeRoundBackground) + { + AutoSizeAxes = Axes.Y; + } + else + { + Masking = true; + Height = 100; + } } [BackgroundDependencyLoader] @@ -27,9 +35,6 @@ namespace osu.Game.Tournament.Screens.Showcase Texture = textures.Get("game-screen-logo"), Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Size = Vector2.One }; } } diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs index 2cb4ffe4e9..c901a5c7ef 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro RelativeSizeAxes = Axes.Both, Loop = true, }, - new TournamentLogo(), + new TournamentLogo(false), mainContainer = new Container { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 628f64dc73..06fb52da77 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -222,6 +222,7 @@ namespace osu.Game.Tournament sw.Write(JsonConvert.SerializeObject(ladder, new JsonSerializerSettings { + Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore, })); diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index 6e162ca95e..e12c46ef16 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.IO.Stores; using osu.Game.Beatmaps; @@ -26,7 +25,7 @@ namespace osu.Game.Audio private TrackManagerPreviewTrack current; [BackgroundDependencyLoader] - private void load(AudioManager audio, FrameworkConfigManager config) + private void load(AudioManager audio) { // this is a temporary solution to get around muting ourselves. // todo: update this once we have a BackgroundTrackManager or similar. @@ -36,8 +35,6 @@ namespace osu.Game.Audio trackStore.AddAdjustment(AdjustableProperty.Volume, audio.VolumeTrack); this.audio = audio; - - config.BindWith(FrameworkSetting.VolumeMusic, trackStore.Volume); } /// diff --git a/osu.Game/Graphics/UserInterface/OsuNumberBox.cs b/osu.Game/Graphics/UserInterface/OsuNumberBox.cs new file mode 100644 index 0000000000..36288c745a --- /dev/null +++ b/osu.Game/Graphics/UserInterface/OsuNumberBox.cs @@ -0,0 +1,10 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Graphics.UserInterface +{ + public class OsuNumberBox : OsuTextBox + { + protected override bool CanAddCharacter(char character) => char.IsNumber(character); + } +} diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 97f4a9771f..904aa9c8c0 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -50,6 +50,7 @@ namespace osu.Game.Input.Bindings { new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene), new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry), + new KeyBinding(new[] { InputKey.Control, InputKey.Tilde }, GlobalAction.QuickExit), new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed), new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), }; @@ -94,6 +95,9 @@ namespace osu.Game.Input.Bindings [Description("Quick retry (hold)")] QuickRetry, + [Description("Quick exit (Hold)")] + QuickExit, + [Description("Take screenshot")] TakeScreenshot, diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 5949f1fcd4..1022edfe81 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -134,9 +134,9 @@ namespace osu.Game.Overlays.Dialog Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Position = new Vector2(0f, -50f), Direction = FillDirection.Vertical, Spacing = new Vector2(0f, 10f), + Padding = new MarginPadding { Bottom = 10 }, Children = new Drawable[] { new Container @@ -144,10 +144,6 @@ namespace osu.Game.Overlays.Dialog Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Size = ringSize, - Margin = new MarginPadding - { - Bottom = 30, - }, Children = new Drawable[] { ring = new CircularContainer @@ -181,15 +177,15 @@ namespace osu.Game.Overlays.Dialog Anchor = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(15), TextAnchor = Anchor.TopCentre, }, body = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 18)) { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + TextAnchor = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(15), - TextAnchor = Anchor.TopCentre, }, }, }, diff --git a/osu.Game/Overlays/OverlayHeaderTabControl.cs b/osu.Game/Overlays/OverlayHeaderTabControl.cs index dfe7e52420..5b56771dc1 100644 --- a/osu.Game/Overlays/OverlayHeaderTabControl.cs +++ b/osu.Game/Overlays/OverlayHeaderTabControl.cs @@ -1,153 +1,23 @@ // 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.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays { - public class OverlayHeaderTabControl : TabControl + public class OverlayHeaderTabControl : OverlayTabControl { - private readonly Box bar; - - private Color4 accentColour = Color4.White; - - public Color4 AccentColour + protected override TabItem CreateTabItem(string value) => new OverlayHeaderTabItem(value) { - get => accentColour; - set - { - if (accentColour == value) - return; - - accentColour = value; - bar.Colour = value; - - foreach (TabItem tabItem in TabContainer) - { - ((HeaderTabItem)tabItem).AccentColour = value; - } - } - } - - public new MarginPadding Padding - { - get => TabContainer.Padding; - set => TabContainer.Padding = value; - } - - public OverlayHeaderTabControl() - { - TabContainer.Masking = false; - TabContainer.Spacing = new Vector2(15, 0); - - AddInternal(bar = new Box - { - RelativeSizeAxes = Axes.X, - Height = 2, - Anchor = Anchor.BottomLeft, - Origin = Anchor.CentreLeft - }); - } - - protected override Dropdown CreateDropdown() => null; - - protected override TabItem CreateTabItem(string value) => new HeaderTabItem(value) - { - AccentColour = AccentColour + AccentColour = AccentColour, }; - private class HeaderTabItem : TabItem + private class OverlayHeaderTabItem : OverlayTabItem { - private readonly OsuSpriteText text; - private readonly ExpandingBar bar; - - private Color4 accentColour; - - public Color4 AccentColour - { - get => accentColour; - set - { - if (accentColour == value) - return; - - accentColour = value; - bar.Colour = value; - - updateState(); - } - } - - public HeaderTabItem(string value) + public OverlayHeaderTabItem(string value) : base(value) { - AutoSizeAxes = Axes.X; - RelativeSizeAxes = Axes.Y; - - Children = new Drawable[] - { - text = new OsuSpriteText - { - Margin = new MarginPadding { Bottom = 10 }, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Text = value, - Font = OsuFont.GetFont() - }, - bar = new ExpandingBar - { - Anchor = Anchor.BottomCentre, - ExpandedSize = 7.5f, - CollapsedSize = 0 - }, - new HoverClickSounds() - }; - } - - protected override bool OnHover(HoverEvent e) - { - base.OnHover(e); - - updateState(); - - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - - updateState(); - } - - protected override void OnActivated() => updateState(); - - protected override void OnDeactivated() => updateState(); - - private void updateState() - { - if (Active.Value || IsHovered) - { - text.FadeColour(Color4.White, 120, Easing.InQuad); - bar.Expand(); - - if (Active.Value) - text.Font = text.Font.With(weight: FontWeight.Bold); - } - else - { - text.FadeColour(AccentColour, 120, Easing.InQuad); - bar.Collapse(); - text.Font = text.Font.With(weight: FontWeight.Medium); - } + Text.Text = value; } } } diff --git a/osu.Game/Overlays/OverlayTabControl.cs b/osu.Game/Overlays/OverlayTabControl.cs new file mode 100644 index 0000000000..20649c8a74 --- /dev/null +++ b/osu.Game/Overlays/OverlayTabControl.cs @@ -0,0 +1,163 @@ +// 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.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays +{ + public abstract class OverlayTabControl : TabControl + { + private readonly Box bar; + + private Color4 accentColour = Color4.White; + + public Color4 AccentColour + { + get => accentColour; + set + { + if (accentColour == value) + return; + + accentColour = value; + bar.Colour = value; + + foreach (TabItem tabItem in TabContainer) + { + ((OverlayTabItem)tabItem).AccentColour = value; + } + } + } + + public new MarginPadding Padding + { + get => TabContainer.Padding; + set => TabContainer.Padding = value; + } + + protected OverlayTabControl() + { + TabContainer.Masking = false; + TabContainer.Spacing = new Vector2(15, 0); + + AddInternal(bar = new Box + { + RelativeSizeAxes = Axes.X, + Height = 2, + Anchor = Anchor.BottomLeft, + Origin = Anchor.CentreLeft + }); + } + + protected override Dropdown CreateDropdown() => null; + + protected override TabItem CreateTabItem(T value) => new OverlayTabItem(value); + + protected class OverlayTabItem : TabItem + { + private readonly ExpandingBar bar; + + protected readonly OsuSpriteText Text; + + private Color4 accentColour; + + public Color4 AccentColour + { + get => accentColour; + set + { + if (accentColour == value) + return; + + accentColour = value; + bar.Colour = value; + + updateState(); + } + } + + public OverlayTabItem(U value) + : base(value) + { + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + + Children = new Drawable[] + { + Text = new OsuSpriteText + { + Margin = new MarginPadding { Bottom = 10 }, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Font = OsuFont.GetFont(), + }, + bar = new ExpandingBar + { + Anchor = Anchor.BottomCentre, + ExpandedSize = 7.5f, + CollapsedSize = 0 + }, + new HoverClickSounds() + }; + } + + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + + if (!Active.Value) + HoverAction(); + + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + + if (!Active.Value) + UnhoverAction(); + } + + protected override void OnActivated() + { + HoverAction(); + Text.Font = Text.Font.With(weight: FontWeight.Bold); + } + + protected override void OnDeactivated() + { + UnhoverAction(); + Text.Font = Text.Font.With(weight: FontWeight.Medium); + } + + private void updateState() + { + if (Active.Value) + OnActivated(); + else + OnDeactivated(); + } + + protected virtual void HoverAction() + { + bar.Expand(); + Text.FadeColour(Color4.White, 120, Easing.InQuad); + } + + protected virtual void UnhoverAction() + { + bar.Collapse(); + Text.FadeColour(AccentColour, 120, Easing.InQuad); + } + } + } +} diff --git a/osu.Game/Overlays/Profile/ProfileSection.cs b/osu.Game/Overlays/Profile/ProfileSection.cs index 4d891384e8..f3590d4bb7 100644 --- a/osu.Game/Overlays/Profile/ProfileSection.cs +++ b/osu.Game/Overlays/Profile/ProfileSection.cs @@ -1,25 +1,29 @@ // 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 osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Users; -using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays.Profile { - public abstract class ProfileSection : FillFlowContainer + public abstract class ProfileSection : Container { public abstract string Title { get; } public abstract string Identifier { get; } private readonly FillFlowContainer content; + private readonly Box background; + private readonly Box underscore; protected override Container Content => content; @@ -27,50 +31,109 @@ namespace osu.Game.Overlays.Profile protected ProfileSection() { - Direction = FillDirection.Vertical; AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; + InternalChildren = new Drawable[] { - new OsuSpriteText + background = new Box { - Text = Title, - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular, italics: true), - Margin = new MarginPadding - { - Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, - Vertical = 10 - } + RelativeSizeAxes = Axes.Both, }, - content = new FillFlowContainer + new SectionTriangles + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }, + new FillFlowContainer { Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Padding = new MarginPadding + Children = new Drawable[] { - Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, - Bottom = 20 - } - }, - new Box - { - RelativeSizeAxes = Axes.X, - Height = 1, - Colour = OsuColour.Gray(34), - EdgeSmoothness = new Vector2(1) + new Container + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding + { + Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, + Top = 15, + Bottom = 10, + }, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = Title, + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold), + }, + underscore = new Box + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = 4 }, + RelativeSizeAxes = Axes.X, + Height = 2, + } + } + }, + content = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Padding = new MarginPadding + { + Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, + Bottom = 20 + } + }, + }, } }; + } - // placeholder - Add(new OsuSpriteText + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.GreySeafoamDarker; + underscore.Colour = colours.Seafoam; + } + + private class SectionTriangles : Container + { + private readonly Triangles triangles; + private readonly Box foreground; + + public SectionTriangles() { - Text = @"coming soon!", - Colour = Color4.Gray, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Margin = new MarginPadding { Top = 100, Bottom = 100 } - }); + RelativeSizeAxes = Axes.X; + Height = 100; + Masking = true; + Children = new Drawable[] + { + triangles = new Triangles + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + TriangleScale = 3, + }, + foreground = new Box + { + RelativeSizeAxes = Axes.Both, + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + triangles.ColourLight = colours.GreySeafoamDark; + triangles.ColourDark = colours.GreySeafoamDarker.Darken(0.2f); + foreground.Colour = ColourInfo.GradientVertical(colours.GreySeafoamDarker, colours.GreySeafoamDarker.Opacity(0)); + } } } } diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs new file mode 100644 index 0000000000..cb7e63ae6f --- /dev/null +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -0,0 +1,17 @@ +// 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.Graphics.UserInterface; + +namespace osu.Game.Overlays.Settings +{ + public class SettingsNumberBox : SettingsItem + { + protected override Drawable CreateControl() => new OsuNumberBox + { + Margin = new MarginPadding { Top = 5 }, + RelativeSizeAxes = Axes.X, + }; + } +} diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 8a133a1d1e..058d84d32f 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -4,11 +4,11 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile.Sections; @@ -23,7 +23,7 @@ namespace osu.Game.Overlays private ProfileSection[] sections; private GetUserRequest userReq; protected ProfileHeader Header; - private SectionsContainer sectionsContainer; + private ProfileSectionsContainer sectionsContainer; private ProfileTabControl tabs; public const float CONTENT_X_MARGIN = 70; @@ -65,12 +65,11 @@ namespace osu.Game.Overlays Add(new Box { RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(0.2f) + Colour = OsuColour.Gray(0.1f) }); - Add(sectionsContainer = new SectionsContainer + Add(sectionsContainer = new ProfileSectionsContainer { - RelativeSizeAxes = Axes.Both, ExpandableHeader = Header = new ProfileHeader(), FixedHeader = tabs, HeaderBackground = new Box @@ -141,31 +140,28 @@ namespace osu.Game.Overlays } } - private class ProfileTabControl : PageTabControl + private class ProfileTabControl : OverlayTabControl { - private readonly Box bottom; - public ProfileTabControl() { TabContainer.RelativeSizeAxes &= ~Axes.X; TabContainer.AutoSizeAxes |= Axes.X; TabContainer.Anchor |= Anchor.x1; TabContainer.Origin |= Anchor.x1; - AddInternal(bottom = new Box - { - RelativeSizeAxes = Axes.X, - Height = 1, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - EdgeSmoothness = new Vector2(1) - }); } - protected override TabItem CreateTabItem(ProfileSection value) => new ProfileTabItem(value); + protected override TabItem CreateTabItem(ProfileSection value) => new ProfileTabItem(value) + { + AccentColour = AccentColour + }; - protected override Dropdown CreateDropdown() => null; + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Seafoam; + } - private class ProfileTabItem : PageTabItem + private class ProfileTabItem : OverlayTabItem { public ProfileTabItem(ProfileSection value) : base(value) @@ -173,12 +169,22 @@ namespace osu.Game.Overlays Text.Text = value.Title; } } + } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + private class ProfileSectionsContainer : SectionsContainer + { + public ProfileSectionsContainer() { - bottom.Colour = colours.Yellow; + RelativeSizeAxes = Axes.Both; } + + protected override FlowContainer CreateScrollContentContainer() => new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Spacing = new Vector2(0, 20), + }; } } } diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 02e0f59f26..e6204a3179 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -102,6 +102,10 @@ namespace osu.Game.Overlays case GlobalAction.DecreaseVolume: if (State.Value == Visibility.Hidden) Show(); + else if (volumeMeterMusic.IsHovered) + volumeMeterMusic.Decrease(amount, isPrecise); + else if (volumeMeterEffect.IsHovered) + volumeMeterEffect.Decrease(amount, isPrecise); else volumeMeterMaster.Decrease(amount, isPrecise); return true; @@ -109,6 +113,10 @@ namespace osu.Game.Overlays case GlobalAction.IncreaseVolume: if (State.Value == Visibility.Hidden) Show(); + else if (volumeMeterMusic.IsHovered) + volumeMeterMusic.Increase(amount, isPrecise); + else if (volumeMeterEffect.IsHovered) + volumeMeterEffect.Increase(amount, isPrecise); else volumeMeterMaster.Increase(amount, isPrecise); return true; diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 5252b41dfd..a56d153646 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -257,7 +257,7 @@ namespace osu.Game.Screens.Multi private void subScreenChanged(IScreen newScreen) { updatePollingRate(isIdle.Value); - createButton.FadeTo(newScreen is MatchSubScreen ? 0 : 1, 200); + createButton.FadeTo(newScreen is LoungeSubScreen ? 1 : 0, 200); updateTrack(); } diff --git a/osu.Game/Screens/Play/HotkeyExitOverlay.cs b/osu.Game/Screens/Play/HotkeyExitOverlay.cs new file mode 100644 index 0000000000..c18aecda55 --- /dev/null +++ b/osu.Game/Screens/Play/HotkeyExitOverlay.cs @@ -0,0 +1,28 @@ +// 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.Input.Bindings; +using osu.Game.Input.Bindings; +using osu.Game.Overlays; + +namespace osu.Game.Screens.Play +{ + public class HotkeyExitOverlay : HoldToConfirmOverlay, IKeyBindingHandler + { + public bool OnPressed(GlobalAction action) + { + if (action != GlobalAction.QuickExit) return false; + + BeginConfirm(); + return true; + } + + public bool OnReleased(GlobalAction action) + { + if (action != GlobalAction.QuickExit) return false; + + AbortConfirm(); + return true; + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c3e351a0ca..c3a9ffdaba 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -177,6 +177,16 @@ namespace osu.Game.Screens.Play Restart(); }, }, + new HotkeyExitOverlay + { + Action = () => + { + if (!this.IsCurrentScreen()) return; + + fadeOut(true); + performUserRequestedExit(); + }, + }, failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, } }; @@ -245,6 +255,11 @@ namespace osu.Game.Screens.Play { if (!this.IsCurrentScreen()) return; + // if a restart has been requested, cancel any pending completion (user has shown intent to restart). + onCompletionEvent = null; + + ValidForResume = false; + this.Exit(); } diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index 3ca58dc625..995cb15136 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -15,6 +15,10 @@ namespace osu.Game.Skinning } } + /// + /// A drawable which can be skinned via an . + /// + /// The type of drawable. public class SkinnableDrawable : SkinReloadableDrawable where T : Drawable { @@ -23,48 +27,63 @@ namespace osu.Game.Skinning /// protected Drawable Drawable { get; private set; } - private readonly Func createDefault; - private readonly string componentName; private readonly bool restrictSize; /// - /// + /// Create a new skinnable drawable. /// /// 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) + { + createDefault = defaultImplementation; + } + + protected SkinnableDrawable(string name, Func allowFallback = null, bool restrictSize = true) : base(allowFallback) { componentName = name; - createDefault = defaultImplementation; this.restrictSize = restrictSize; RelativeSizeAxes = Axes.Both; } + private readonly Func createDefault; + + protected virtual T CreateDefault(string name) => createDefault(name); + + /// + /// Whether to apply size restrictions (specified via ) to the default implementation. + /// + protected virtual bool ApplySizeRestrictionsToDefault => false; + protected override void SkinChanged(ISkinSource skin, bool allowFallback) { Drawable = skin.GetDrawableComponent(componentName); + bool isDefault = false; + + if (Drawable == null && allowFallback) + { + Drawable = CreateDefault(componentName); + isDefault = true; + } + if (Drawable != null) { - if (restrictSize) + if (restrictSize && (!isDefault || ApplySizeRestrictionsToDefault)) { Drawable.RelativeSizeAxes = Axes.Both; Drawable.Size = Vector2.One; Drawable.Scale = Vector2.One; Drawable.FillMode = FillMode.Fit; } - } - else if (allowFallback) - Drawable = createDefault(componentName); - if (Drawable != null) - { Drawable.Origin = Anchor.Centre; Drawable.Anchor = Anchor.Centre; diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs new file mode 100644 index 0000000000..ceb1ed0f70 --- /dev/null +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -0,0 +1,28 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Skinning +{ + /// + /// A skinnable element which uses a stable sprite and can therefore share implementation logic. + /// + public class SkinnableSprite : SkinnableDrawable + { + protected override bool ApplySizeRestrictionsToDefault => true; + + [Resolved] + private TextureStore textures { get; set; } + + public SkinnableSprite(string name, Func allowFallback = null, bool restrictSize = true) + : base(name, allowFallback, restrictSize) + { + } + + protected override Sprite CreateDefault(string name) => new Sprite { Texture = textures.Get(name) }; + } +}