1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 06:35:05 +08:00

Merge remote-tracking branch 'refs/remotes/ppy/master' into toolbar-rulesets-refactor

This commit is contained in:
Andrei Zavatski 2019-06-24 22:52:24 +03:00
commit 35516e7304
25 changed files with 692 additions and 290 deletions

View File

@ -0,0 +1,105 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using 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<Type> 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<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration =>
throw new NotImplementedException();
}
}
}

View File

@ -6,8 +6,6 @@ using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -141,7 +139,7 @@ namespace osu.Game.Rulesets.Catch.UI
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Children = new Drawable[] Children = new[]
{ {
caughtFruit = new Container<DrawableHitObject> caughtFruit = new Container<DrawableHitObject>
{ {
@ -212,7 +210,7 @@ namespace osu.Game.Rulesets.Catch.UI
Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50); Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
} }
private Sprite createCatcherSprite() => new CatcherSprite(); private Drawable createCatcherSprite() => new CatcherSprite();
/// <summary> /// <summary>
/// Add a caught fruit to the catcher's stack. /// Add a caught fruit to the catcher's stack.
@ -444,23 +442,6 @@ namespace osu.Game.Rulesets.Catch.UI
fruit.Expire(); 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");
}
}
} }
} }
} }

View File

@ -0,0 +1,33 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.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,
};
}
}
}

View File

@ -4,7 +4,6 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -25,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(TextureStore textures)
{ {
Child = new SkinnableDrawable("Play/osu/approachcircle", name => new Sprite { Texture = textures.Get(name) }); Child = new SkinnableSprite("Play/osu/approachcircle");
} }
} }
} }

View File

@ -14,16 +14,21 @@ namespace osu.Game.Tests.Visual.Components
[TestFixture] [TestFixture]
public class TestSceneIdleTracker : ManualInputManagerTestScene public class TestSceneIdleTracker : ManualInputManagerTestScene
{ {
private readonly IdleTrackingBox box1; private IdleTrackingBox box1;
private readonly IdleTrackingBox box2; private IdleTrackingBox box2;
private readonly IdleTrackingBox box3; private IdleTrackingBox box3;
private readonly IdleTrackingBox box4; 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", Name = "TopLeft",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -31,7 +36,7 @@ namespace osu.Game.Tests.Visual.Components
Anchor = Anchor.TopLeft, Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,
}, },
box2 = new IdleTrackingBox(2000) box2 = new IdleTrackingBox(4000)
{ {
Name = "TopRight", Name = "TopRight",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -39,7 +44,7 @@ namespace osu.Game.Tests.Visual.Components
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
}, },
box3 = new IdleTrackingBox(3000) box3 = new IdleTrackingBox(6000)
{ {
Name = "BottomLeft", Name = "BottomLeft",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -47,7 +52,7 @@ namespace osu.Game.Tests.Visual.Components
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
}, },
box4 = new IdleTrackingBox(4000) box4 = new IdleTrackingBox(8000)
{ {
Name = "BottomRight", Name = "BottomRight",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -56,7 +61,7 @@ namespace osu.Game.Tests.Visual.Components
Origin = Anchor.BottomRight, Origin = Anchor.BottomRight,
}, },
}; };
} });
[Test] [Test]
public void TestNudge() public void TestNudge()
@ -67,10 +72,10 @@ namespace osu.Game.Tests.Visual.Components
AddStep("nudge mouse", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre + new Vector2(1))); AddStep("nudge mouse", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre + new Vector2(1)));
checkIdleStatus(box1, false); checkIdleStatus(1, false);
checkIdleStatus(box2, true); checkIdleStatus(2, true);
checkIdleStatus(box3, true); checkIdleStatus(3, true);
checkIdleStatus(box4, true); checkIdleStatus(4, true);
} }
[Test] [Test]
@ -78,18 +83,18 @@ namespace osu.Game.Tests.Visual.Components
{ {
AddStep("move to top right", () => InputManager.MoveMouseTo(box2)); AddStep("move to top right", () => InputManager.MoveMouseTo(box2));
checkIdleStatus(box1, true); checkIdleStatus(1, true);
checkIdleStatus(box2, false); checkIdleStatus(2, false);
checkIdleStatus(box3, true); checkIdleStatus(3, true);
checkIdleStatus(box4, true); checkIdleStatus(4, true);
AddStep("move to bottom left", () => InputManager.MoveMouseTo(box3)); AddStep("move to bottom left", () => InputManager.MoveMouseTo(box3));
AddStep("move to bottom right", () => InputManager.MoveMouseTo(box4)); AddStep("move to bottom right", () => InputManager.MoveMouseTo(box4));
checkIdleStatus(box1, true); checkIdleStatus(1, true);
checkIdleStatus(box2, false); checkIdleStatus(2, false);
checkIdleStatus(box3, false); checkIdleStatus(3, false);
checkIdleStatus(box4, false); checkIdleStatus(4, false);
waitForAllIdle(); waitForAllIdle();
} }
@ -99,38 +104,38 @@ namespace osu.Game.Tests.Visual.Components
{ {
AddStep("move to centre", () => InputManager.MoveMouseTo(Content)); AddStep("move to centre", () => InputManager.MoveMouseTo(Content));
checkIdleStatus(box1, false); checkIdleStatus(1, false);
checkIdleStatus(box2, false); checkIdleStatus(2, false);
checkIdleStatus(box3, false); checkIdleStatus(3, false);
checkIdleStatus(box4, false); checkIdleStatus(4, false);
AddUntilStep("Wait for idle", () => box1.IsIdle); AddUntilStep("Wait for idle", () => box1.IsIdle);
checkIdleStatus(box1, true); checkIdleStatus(1, true);
checkIdleStatus(box2, false); checkIdleStatus(2, false);
checkIdleStatus(box3, false); checkIdleStatus(3, false);
checkIdleStatus(box4, false); checkIdleStatus(4, false);
AddUntilStep("Wait for idle", () => box2.IsIdle); AddUntilStep("Wait for idle", () => box2.IsIdle);
checkIdleStatus(box1, true); checkIdleStatus(1, true);
checkIdleStatus(box2, true); checkIdleStatus(2, true);
checkIdleStatus(box3, false); checkIdleStatus(3, false);
checkIdleStatus(box4, false); checkIdleStatus(4, false);
AddUntilStep("Wait for idle", () => box3.IsIdle); AddUntilStep("Wait for idle", () => box3.IsIdle);
checkIdleStatus(box1, true); checkIdleStatus(1, true);
checkIdleStatus(box2, true); checkIdleStatus(2, true);
checkIdleStatus(box3, true); checkIdleStatus(3, true);
checkIdleStatus(box4, false); checkIdleStatus(4, false);
waitForAllIdle(); waitForAllIdle();
} }
private void checkIdleStatus(IdleTrackingBox box, bool expectedIdle) private void checkIdleStatus(int box, bool expectedIdle)
{ {
AddAssert($"{box.Name} is {(expectedIdle ? "idle" : "active")}", () => box.IsIdle == expectedIdle); AddAssert($"box {box} is {(expectedIdle ? "idle" : "active")}", () => boxes[box - 1].IsIdle == expectedIdle);
} }
private void waitForAllIdle() private void waitForAllIdle()

View File

@ -0,0 +1,55 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.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<Type> 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);
}
}

View File

@ -183,7 +183,7 @@ namespace osu.Game.Tournament.Screens.Editors
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
{ {
new SettingsTextBox new SettingsNumberBox
{ {
LabelText = "Beatmap ID", LabelText = "Beatmap ID",
RelativeSizeAxes = Axes.None, RelativeSizeAxes = Axes.None,

View File

@ -231,7 +231,7 @@ namespace osu.Game.Tournament.Screens.Editors
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
{ {
new SettingsTextBox new SettingsNumberBox
{ {
LabelText = "User ID", LabelText = "User ID",
RelativeSizeAxes = Axes.None, RelativeSizeAxes = Axes.None,

View File

@ -5,7 +5,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
@ -191,8 +190,6 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
public RoundDisplay() public RoundDisplay()
{ {
CornerRadius = 10;
Masking = true;
Width = 200; Width = 200;
Height = 20; Height = 20;
} }
@ -208,11 +205,6 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
{ {
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new Box
{
Colour = new Color4(47, 71, 67, 255),
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText new OsuSpriteText
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,

View File

@ -6,17 +6,25 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osuTK;
namespace osu.Game.Tournament.Screens.Showcase namespace osu.Game.Tournament.Screens.Showcase
{ {
public class TournamentLogo : CompositeDrawable public class TournamentLogo : CompositeDrawable
{ {
public TournamentLogo() public TournamentLogo(bool includeRoundBackground = true)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = 95;
Margin = new MarginPadding { Vertical = 5 }; Margin = new MarginPadding { Vertical = 5 };
if (includeRoundBackground)
{
AutoSizeAxes = Axes.Y;
}
else
{
Masking = true;
Height = 100;
}
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -27,9 +35,6 @@ namespace osu.Game.Tournament.Screens.Showcase
Texture = textures.Get("game-screen-logo"), Texture = textures.Get("game-screen-logo"),
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
FillMode = FillMode.Fit,
RelativeSizeAxes = Axes.Both,
Size = Vector2.One
}; };
} }
} }

View File

@ -34,7 +34,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Loop = true, Loop = true,
}, },
new TournamentLogo(), new TournamentLogo(false),
mainContainer = new Container mainContainer = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,

View File

@ -0,0 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
namespace osu.Game.Graphics.UserInterface
{
public class OsuNumberBox : OsuTextBox
{
protected override bool CanAddCharacter(char character) => char.IsNumber(character);
}
}

View File

@ -50,6 +50,7 @@ namespace osu.Game.Input.Bindings
{ {
new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene), new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene),
new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry), 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.Plus }, GlobalAction.IncreaseScrollSpeed),
new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed),
}; };
@ -94,6 +95,9 @@ namespace osu.Game.Input.Bindings
[Description("Quick retry (hold)")] [Description("Quick retry (hold)")]
QuickRetry, QuickRetry,
[Description("Quick exit (Hold)")]
QuickExit,
[Description("Take screenshot")] [Description("Take screenshot")]
TakeScreenshot, TakeScreenshot,

View File

@ -134,9 +134,9 @@ namespace osu.Game.Overlays.Dialog
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Position = new Vector2(0f, -50f),
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Spacing = new Vector2(0f, 10f), Spacing = new Vector2(0f, 10f),
Padding = new MarginPadding { Bottom = 10 },
Children = new Drawable[] Children = new Drawable[]
{ {
new Container new Container
@ -144,10 +144,6 @@ namespace osu.Game.Overlays.Dialog
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Size = ringSize, Size = ringSize,
Margin = new MarginPadding
{
Bottom = 30,
},
Children = new Drawable[] Children = new Drawable[]
{ {
ring = new CircularContainer ring = new CircularContainer
@ -181,15 +177,15 @@ namespace osu.Game.Overlays.Dialog
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(15),
TextAnchor = Anchor.TopCentre, TextAnchor = Anchor.TopCentre,
}, },
body = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 18)) body = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 18))
{ {
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
TextAnchor = Anchor.TopCentre,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(15),
TextAnchor = Anchor.TopCentre,
}, },
}, },
}, },

View File

@ -1,153 +1,23 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // 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.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 namespace osu.Game.Overlays
{ {
public class OverlayHeaderTabControl : TabControl<string> public class OverlayHeaderTabControl : OverlayTabControl<string>
{ {
private readonly Box bar; protected override TabItem<string> CreateTabItem(string value) => new OverlayHeaderTabItem(value)
private Color4 accentColour = Color4.White;
public Color4 AccentColour
{ {
get => accentColour; AccentColour = AccentColour,
set
{
if (accentColour == value)
return;
accentColour = value;
bar.Colour = value;
foreach (TabItem<string> 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<string> CreateDropdown() => null;
protected override TabItem<string> CreateTabItem(string value) => new HeaderTabItem(value)
{
AccentColour = AccentColour
}; };
private class HeaderTabItem : TabItem<string> private class OverlayHeaderTabItem : OverlayTabItem<string>
{ {
private readonly OsuSpriteText text; public OverlayHeaderTabItem(string value)
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)
: base(value) : base(value)
{ {
AutoSizeAxes = Axes.X; Text.Text = value;
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);
}
} }
} }
} }

View File

@ -0,0 +1,163 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.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<T> : TabControl<T>
{
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<T> tabItem in TabContainer)
{
((OverlayTabItem<T>)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<T> CreateDropdown() => null;
protected override TabItem<T> CreateTabItem(T value) => new OverlayTabItem<T>(value);
protected class OverlayTabItem<U> : TabItem<U>
{
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);
}
}
}
}

View File

@ -1,25 +1,29 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Users; using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Profile namespace osu.Game.Overlays.Profile
{ {
public abstract class ProfileSection : FillFlowContainer public abstract class ProfileSection : Container
{ {
public abstract string Title { get; } public abstract string Title { get; }
public abstract string Identifier { get; } public abstract string Identifier { get; }
private readonly FillFlowContainer content; private readonly FillFlowContainer content;
private readonly Box background;
private readonly Box underscore;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
@ -27,19 +31,51 @@ namespace osu.Game.Overlays.Profile
protected ProfileSection() protected ProfileSection()
{ {
Direction = FillDirection.Vertical;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
},
new SectionTriangles
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
},
new FillFlowContainer
{
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Children = new Drawable[]
{
new Container
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding
{
Horizontal = UserProfileOverlay.CONTENT_X_MARGIN,
Top = 15,
Bottom = 10,
},
Children = new Drawable[]
{ {
new OsuSpriteText new OsuSpriteText
{ {
Text = Title, Text = Title,
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular, italics: true), Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold),
Margin = new MarginPadding },
underscore = new Box
{ {
Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Anchor = Anchor.BottomCentre,
Vertical = 10 Origin = Anchor.TopCentre,
Margin = new MarginPadding { Top = 4 },
RelativeSizeAxes = Axes.X,
Height = 2,
}
} }
}, },
content = new FillFlowContainer content = new FillFlowContainer
@ -53,24 +89,51 @@ namespace osu.Game.Overlays.Profile
Bottom = 20 Bottom = 20
} }
}, },
new Box },
{
RelativeSizeAxes = Axes.X,
Height = 1,
Colour = OsuColour.Gray(34),
EdgeSmoothness = new Vector2(1)
} }
}; };
}
// placeholder [BackgroundDependencyLoader]
Add(new OsuSpriteText private void load(OsuColour colours)
{ {
Text = @"coming soon!", background.Colour = colours.GreySeafoamDarker;
Colour = Color4.Gray, underscore.Colour = colours.Seafoam;
Anchor = Anchor.Centre, }
Origin = Anchor.Centre,
Margin = new MarginPadding { Top = 100, Bottom = 100 } private class SectionTriangles : Container
}); {
private readonly Triangles triangles;
private readonly Box foreground;
public SectionTriangles()
{
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));
}
} }
} }
} }

View File

@ -0,0 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Settings
{
public class SettingsNumberBox : SettingsItem<string>
{
protected override Drawable CreateControl() => new OsuNumberBox
{
Margin = new MarginPadding { Top = 5 },
RelativeSizeAxes = Axes.X,
};
}
}

View File

@ -4,11 +4,11 @@
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile;
using osu.Game.Overlays.Profile.Sections; using osu.Game.Overlays.Profile.Sections;
@ -23,7 +23,7 @@ namespace osu.Game.Overlays
private ProfileSection[] sections; private ProfileSection[] sections;
private GetUserRequest userReq; private GetUserRequest userReq;
protected ProfileHeader Header; protected ProfileHeader Header;
private SectionsContainer<ProfileSection> sectionsContainer; private ProfileSectionsContainer sectionsContainer;
private ProfileTabControl tabs; private ProfileTabControl tabs;
public const float CONTENT_X_MARGIN = 70; public const float CONTENT_X_MARGIN = 70;
@ -65,12 +65,11 @@ namespace osu.Game.Overlays
Add(new Box Add(new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.2f) Colour = OsuColour.Gray(0.1f)
}); });
Add(sectionsContainer = new SectionsContainer<ProfileSection> Add(sectionsContainer = new ProfileSectionsContainer
{ {
RelativeSizeAxes = Axes.Both,
ExpandableHeader = Header = new ProfileHeader(), ExpandableHeader = Header = new ProfileHeader(),
FixedHeader = tabs, FixedHeader = tabs,
HeaderBackground = new Box HeaderBackground = new Box
@ -141,31 +140,28 @@ namespace osu.Game.Overlays
} }
} }
private class ProfileTabControl : PageTabControl<ProfileSection> private class ProfileTabControl : OverlayTabControl<ProfileSection>
{ {
private readonly Box bottom;
public ProfileTabControl() public ProfileTabControl()
{ {
TabContainer.RelativeSizeAxes &= ~Axes.X; TabContainer.RelativeSizeAxes &= ~Axes.X;
TabContainer.AutoSizeAxes |= Axes.X; TabContainer.AutoSizeAxes |= Axes.X;
TabContainer.Anchor |= Anchor.x1; TabContainer.Anchor |= Anchor.x1;
TabContainer.Origin |= 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<ProfileSection> CreateTabItem(ProfileSection value) => new ProfileTabItem(value); protected override TabItem<ProfileSection> CreateTabItem(ProfileSection value) => new ProfileTabItem(value)
{
AccentColour = AccentColour
};
protected override Dropdown<ProfileSection> CreateDropdown() => null; [BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AccentColour = colours.Seafoam;
}
private class ProfileTabItem : PageTabItem private class ProfileTabItem : OverlayTabItem<ProfileSection>
{ {
public ProfileTabItem(ProfileSection value) public ProfileTabItem(ProfileSection value)
: base(value) : base(value)
@ -173,12 +169,22 @@ namespace osu.Game.Overlays
Text.Text = value.Title; Text.Text = value.Title;
} }
} }
}
[BackgroundDependencyLoader] private class ProfileSectionsContainer : SectionsContainer<ProfileSection>
private void load(OsuColour colours)
{ {
bottom.Colour = colours.Yellow; public ProfileSectionsContainer()
} {
RelativeSizeAxes = Axes.Both;
}
protected override FlowContainer<ProfileSection> CreateScrollContentContainer() => new FillFlowContainer<ProfileSection>
{
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Spacing = new Vector2(0, 20),
};
} }
} }
} }

View File

@ -102,6 +102,10 @@ namespace osu.Game.Overlays
case GlobalAction.DecreaseVolume: case GlobalAction.DecreaseVolume:
if (State.Value == Visibility.Hidden) if (State.Value == Visibility.Hidden)
Show(); Show();
else if (volumeMeterMusic.IsHovered)
volumeMeterMusic.Decrease(amount, isPrecise);
else if (volumeMeterEffect.IsHovered)
volumeMeterEffect.Decrease(amount, isPrecise);
else else
volumeMeterMaster.Decrease(amount, isPrecise); volumeMeterMaster.Decrease(amount, isPrecise);
return true; return true;
@ -109,6 +113,10 @@ namespace osu.Game.Overlays
case GlobalAction.IncreaseVolume: case GlobalAction.IncreaseVolume:
if (State.Value == Visibility.Hidden) if (State.Value == Visibility.Hidden)
Show(); Show();
else if (volumeMeterMusic.IsHovered)
volumeMeterMusic.Increase(amount, isPrecise);
else if (volumeMeterEffect.IsHovered)
volumeMeterEffect.Increase(amount, isPrecise);
else else
volumeMeterMaster.Increase(amount, isPrecise); volumeMeterMaster.Increase(amount, isPrecise);
return true; return true;

View File

@ -257,7 +257,7 @@ namespace osu.Game.Screens.Multi
private void subScreenChanged(IScreen newScreen) private void subScreenChanged(IScreen newScreen)
{ {
updatePollingRate(isIdle.Value); updatePollingRate(isIdle.Value);
createButton.FadeTo(newScreen is MatchSubScreen ? 0 : 1, 200); createButton.FadeTo(newScreen is LoungeSubScreen ? 1 : 0, 200);
updateTrack(); updateTrack();
} }

View File

@ -0,0 +1,28 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
using osu.Game.Overlays;
namespace osu.Game.Screens.Play
{
public class HotkeyExitOverlay : HoldToConfirmOverlay, IKeyBindingHandler<GlobalAction>
{
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;
}
}
}

View File

@ -177,6 +177,16 @@ namespace osu.Game.Screens.Play
Restart(); Restart();
}, },
}, },
new HotkeyExitOverlay
{
Action = () =>
{
if (!this.IsCurrentScreen()) return;
fadeOut(true);
performUserRequestedExit();
},
},
failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, } failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }
}; };
@ -245,6 +255,11 @@ namespace osu.Game.Screens.Play
{ {
if (!this.IsCurrentScreen()) return; 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(); this.Exit();
} }

View File

@ -15,6 +15,10 @@ namespace osu.Game.Skinning
} }
} }
/// <summary>
/// A drawable which can be skinned via an <see cref="ISkinSource"/>.
/// </summary>
/// <typeparam name="T">The type of drawable.</typeparam>
public class SkinnableDrawable<T> : SkinReloadableDrawable public class SkinnableDrawable<T> : SkinReloadableDrawable
where T : Drawable where T : Drawable
{ {
@ -23,48 +27,63 @@ namespace osu.Game.Skinning
/// </summary> /// </summary>
protected Drawable Drawable { get; private set; } protected Drawable Drawable { get; private set; }
private readonly Func<string, T> createDefault;
private readonly string componentName; private readonly string componentName;
private readonly bool restrictSize; private readonly bool restrictSize;
/// <summary> /// <summary>
/// /// Create a new skinnable drawable.
/// </summary> /// </summary>
/// <param name="name">The namespace-complete resource name for this skinnable element.</param> /// <param name="name">The namespace-complete resource name for this skinnable element.</param>
/// <param name="defaultImplementation">A function to create the default skin implementation of this element.</param> /// <param name="defaultImplementation">A function to create the default skin implementation of this element.</param>
/// <param name="allowFallback">A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present.</param> /// <param name="allowFallback">A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present.</param>
/// <param name="restrictSize">Whether a user-skin drawable should be limited to the size of our parent.</param> /// <param name="restrictSize">Whether a user-skin drawable should be limited to the size of our parent.</param>
public SkinnableDrawable(string name, Func<string, T> defaultImplementation, Func<ISkinSource, bool> allowFallback = null, bool restrictSize = true) public SkinnableDrawable(string name, Func<string, T> defaultImplementation, Func<ISkinSource, bool> allowFallback = null, bool restrictSize = true)
: this(name, allowFallback, restrictSize)
{
createDefault = defaultImplementation;
}
protected SkinnableDrawable(string name, Func<ISkinSource, bool> allowFallback = null, bool restrictSize = true)
: base(allowFallback) : base(allowFallback)
{ {
componentName = name; componentName = name;
createDefault = defaultImplementation;
this.restrictSize = restrictSize; this.restrictSize = restrictSize;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
} }
private readonly Func<string, T> createDefault;
protected virtual T CreateDefault(string name) => createDefault(name);
/// <summary>
/// Whether to apply size restrictions (specified via <see cref="restrictSize"/>) to the default implementation.
/// </summary>
protected virtual bool ApplySizeRestrictionsToDefault => false;
protected override void SkinChanged(ISkinSource skin, bool allowFallback) protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{ {
Drawable = skin.GetDrawableComponent(componentName); Drawable = skin.GetDrawableComponent(componentName);
bool isDefault = false;
if (Drawable == null && allowFallback)
{
Drawable = CreateDefault(componentName);
isDefault = true;
}
if (Drawable != null) if (Drawable != null)
{ {
if (restrictSize) if (restrictSize && (!isDefault || ApplySizeRestrictionsToDefault))
{ {
Drawable.RelativeSizeAxes = Axes.Both; Drawable.RelativeSizeAxes = Axes.Both;
Drawable.Size = Vector2.One; Drawable.Size = Vector2.One;
Drawable.Scale = Vector2.One; Drawable.Scale = Vector2.One;
Drawable.FillMode = FillMode.Fit; Drawable.FillMode = FillMode.Fit;
} }
}
else if (allowFallback)
Drawable = createDefault(componentName);
if (Drawable != null)
{
Drawable.Origin = Anchor.Centre; Drawable.Origin = Anchor.Centre;
Drawable.Anchor = Anchor.Centre; Drawable.Anchor = Anchor.Centre;

View File

@ -0,0 +1,28 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
namespace osu.Game.Skinning
{
/// <summary>
/// A skinnable element which uses a stable sprite and can therefore share implementation logic.
/// </summary>
public class SkinnableSprite : SkinnableDrawable<Sprite>
{
protected override bool ApplySizeRestrictionsToDefault => true;
[Resolved]
private TextureStore textures { get; set; }
public SkinnableSprite(string name, Func<ISkinSource, bool> allowFallback = null, bool restrictSize = true)
: base(name, allowFallback, restrictSize)
{
}
protected override Sprite CreateDefault(string name) => new Sprite { Texture = textures.Get(name) };
}
}