Merge branch 'master' into long-standard

This commit is contained in:
Dan Balasescu 2022-11-18 14:24:04 +09:00
commit 57b8495c7a
52 changed files with 569 additions and 239 deletions

View File

@ -51,8 +51,8 @@
<Reference Include="Java.Interop" /> <Reference Include="Java.Interop" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1103.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2022.1113.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1110.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2022.1113.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. --> <!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -248,6 +248,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
break; break;
} }
slider.Path.ExpectedDistance.Value = null;
piece.ControlPoint.Type = type; piece.ControlPoint.Type = type;
} }

View File

@ -56,6 +56,8 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
switch (nested) switch (nested)
{ {
//Freezing the SliderTicks doesnt play well with snaking sliders
case SliderTick:
//SliderRepeat wont layer correctly if preempt is changed. //SliderRepeat wont layer correctly if preempt is changed.
case SliderRepeat: case SliderRepeat:
break; break;

View File

@ -49,7 +49,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
private const float max_rotation = 0.25f; private const float max_rotation = 0.25f;
public IShader? TextureShader { get; private set; } public IShader? TextureShader { get; private set; }
public IShader? RoundedTextureShader { get; private set; }
protected Texture? Texture { get; set; } protected Texture? Texture { get; set; }
@ -69,7 +68,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(ShaderManager shaders) private void load(ShaderManager shaders)
{ {
RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED);
TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
} }
@ -247,18 +245,16 @@ namespace osu.Game.Rulesets.Osu.Skinning
texture ??= renderer.WhitePixel; texture ??= renderer.WhitePixel;
RectangleF textureRect = texture.GetTextureRect(); RectangleF textureRect = texture.GetTextureRect();
var shader = GetAppropriateShader(renderer);
renderer.SetBlend(BlendingParameters.Additive); renderer.SetBlend(BlendingParameters.Additive);
renderer.PushLocalMatrix(DrawInfo.Matrix); renderer.PushLocalMatrix(DrawInfo.Matrix);
shader.Bind(); TextureShader.Bind();
texture.Bind(); texture.Bind();
for (int i = 0; i < points.Count; i++) for (int i = 0; i < points.Count; i++)
drawPointQuad(points[i], textureRect, i + firstVisiblePointIndex); drawPointQuad(points[i], textureRect, i + firstVisiblePointIndex);
shader.Unbind(); TextureShader.Unbind();
renderer.PopLocalMatrix(); renderer.PopLocalMatrix();
} }

View File

@ -0,0 +1,39 @@
// 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.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{
[TestFixture]
public class TestSceneDrawableSwell : TaikoSkinnableTestScene
{
[Test]
public void TestHits()
{
AddStep("Centre hit", () => SetContents(_ => new DrawableSwell(createHitAtCurrentTime())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}));
}
private Swell createHitAtCurrentTime()
{
var hit = new Swell
{
StartTime = Time.Current + 3000,
EndTime = Time.Current + 6000,
};
hit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return hit;
}
}
}

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Objects;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Rulesets.Taiko.Skinning.Argon namespace osu.Game.Rulesets.Taiko.Skinning.Argon
@ -81,12 +82,15 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
updateStateTransforms(drawableHitObject, drawableHitObject.State.Value); updateStateTransforms(drawableHitObject, drawableHitObject.State.Value);
} }
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) private void updateStateTransforms(DrawableHitObject h, ArmedState state)
{ {
if (h.HitObject is not Hit)
return;
switch (state) switch (state)
{ {
case ArmedState.Hit: case ArmedState.Hit:
using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime)) using (BeginAbsoluteSequence(h.HitStateUpdateTime))
{ {
flash.FadeTo(0.9f).FadeOut(500, Easing.OutQuint); flash.FadeTo(0.9f).FadeOut(500, Easing.OutQuint);
} }

View File

@ -0,0 +1,34 @@
// 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.Colour;
using osu.Framework.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Taiko.Skinning.Argon
{
public class ArgonSwellCirclePiece : ArgonCirclePiece
{
[BackgroundDependencyLoader]
private void load()
{
AccentColour = ColourInfo.GradientVertical(
new Color4(240, 201, 0, 255),
new Color4(167, 139, 0, 255)
);
AddInternal(new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Icon = FontAwesome.Solid.Asterisk,
Size = new Vector2(ICON_SIZE),
Scale = new Vector2(0.8f, 1)
});
}
}
}

View File

@ -60,6 +60,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
case TaikoSkinComponents.TaikoExplosionMiss: case TaikoSkinComponents.TaikoExplosionMiss:
case TaikoSkinComponents.TaikoExplosionOk: case TaikoSkinComponents.TaikoExplosionOk:
return new ArgonHitExplosion(taikoComponent.Component); return new ArgonHitExplosion(taikoComponent.Component);
case TaikoSkinComponents.Swell:
return new ArgonSwellCirclePiece();
} }
break; break;

View File

@ -153,12 +153,15 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
updateStateTransforms(drawableHitObject, drawableHitObject.State.Value); updateStateTransforms(drawableHitObject, drawableHitObject.State.Value);
} }
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) private void updateStateTransforms(DrawableHitObject h, ArmedState state)
{ {
if (h.HitObject is not Hit)
return;
switch (state) switch (state)
{ {
case ArmedState.Hit: case ArmedState.Hit:
using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime)) using (BeginAbsoluteSequence(h.HitStateUpdateTime))
flashBox.FadeTo(0.9f).FadeOut(300); flashBox.FadeTo(0.9f).FadeOut(300);
break; break;
} }

View File

@ -23,6 +23,7 @@ namespace osu.Game.Tests.Chat
private ChannelManager channelManager; private ChannelManager channelManager;
private int currentMessageId; private int currentMessageId;
private List<Message> sentMessages; private List<Message> sentMessages;
private List<int> silencedUserIds;
[SetUp] [SetUp]
public void Setup() => Schedule(() => public void Setup() => Schedule(() =>
@ -39,6 +40,7 @@ namespace osu.Game.Tests.Chat
{ {
currentMessageId = 0; currentMessageId = 0;
sentMessages = new List<Message>(); sentMessages = new List<Message>();
silencedUserIds = new List<int>();
((DummyAPIAccess)API).HandleRequest = req => ((DummyAPIAccess)API).HandleRequest = req =>
{ {
@ -56,6 +58,11 @@ namespace osu.Game.Tests.Chat
handleMarkChannelAsReadRequest(markRead); handleMarkChannelAsReadRequest(markRead);
return true; return true;
case ChatAckRequest ack:
ack.TriggerSuccess(new ChatAckResponse { Silences = silencedUserIds.Select(u => new ChatSilence { UserId = u }).ToList() });
silencedUserIds.Clear();
return true;
case GetUpdatesRequest updatesRequest: case GetUpdatesRequest updatesRequest:
updatesRequest.TriggerSuccess(new GetUpdatesResponse updatesRequest.TriggerSuccess(new GetUpdatesResponse
{ {
@ -115,6 +122,28 @@ namespace osu.Game.Tests.Chat
AddAssert("channel's last read ID is set to the latest message", () => channel.LastReadId == sentMessages.Last().Id); AddAssert("channel's last read ID is set to the latest message", () => channel.LastReadId == sentMessages.Last().Id);
} }
[Test]
public void TestSilencedUsersAreRemoved()
{
Channel channel = null;
AddStep("join channel and select it", () =>
{
channelManager.JoinChannel(channel = createChannel(1, ChannelType.Public));
channelManager.CurrentChannel.Value = channel;
});
AddStep("post message", () => channelManager.PostMessage("Definitely something bad"));
AddStep("mark user as silenced and send ack request", () =>
{
silencedUserIds.Add(API.LocalUser.Value.OnlineID);
channelManager.SendAck();
});
AddAssert("channel has no more messages", () => channel.Messages, () => Is.Empty);
}
private void handlePostMessageRequest(PostMessageRequest request) private void handlePostMessageRequest(PostMessageRequest request)
{ {
var message = new Message(++currentMessageId) var message = new Message(++currentMessageId)

View File

@ -0,0 +1,40 @@
// 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.Game.Graphics.Backgrounds;
using osu.Framework.Graphics;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
namespace osu.Game.Tests.Visual.Background
{
public class TestSceneTrianglesBackground : OsuTestScene
{
private readonly Triangles triangles;
public TestSceneTrianglesBackground()
{
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black
},
triangles = new Triangles
{
RelativeSizeAxes = Axes.Both,
ColourLight = Color4.White,
ColourDark = Color4.Gray
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
AddSliderStep("Triangle scale", 0f, 10f, 1f, s => triangles.TriangleScale = s);
}
}
}

View File

@ -163,10 +163,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for bars to disappear", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any()); AddUntilStep("wait for bars to disappear", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
AddUntilStep("ensure max circles not exceeded", () => AddUntilStep("ensure max circles not exceeded", () =>
{ this.ChildrenOfType<ColourHitErrorMeter>().First().ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Count(), () => Is.LessThanOrEqualTo(max_displayed_judgements));
return this.ChildrenOfType<ColourHitErrorMeter>()
.All(m => m.ChildrenOfType<ColourHitErrorMeter.HitErrorShape>().Count() <= max_displayed_judgements);
});
AddStep("show displays", () => AddStep("show displays", () =>
{ {

View File

@ -436,6 +436,8 @@ namespace osu.Game.Tests.Visual.Navigation
{ {
AddUntilStep("Wait for toolbar to load", () => Game.Toolbar.IsLoaded); AddUntilStep("Wait for toolbar to load", () => Game.Toolbar.IsLoaded);
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
TestPlaySongSelect songSelect = null; TestPlaySongSelect songSelect = null;
PushAndConfirm(() => songSelect = new TestPlaySongSelect()); PushAndConfirm(() => songSelect = new TestPlaySongSelect());

View File

@ -40,8 +40,10 @@ namespace osu.Game.Tests.Visual.Online
private ChannelManager channelManager; private ChannelManager channelManager;
private readonly APIUser testUser = new APIUser { Username = "test user", Id = 5071479 }; private readonly APIUser testUser = new APIUser { Username = "test user", Id = 5071479 };
private readonly APIUser testUser1 = new APIUser { Username = "test user", Id = 5071480 };
private Channel[] testChannels; private Channel[] testChannels;
private Message[] initialMessages;
private Channel testChannel1 => testChannels[0]; private Channel testChannel1 => testChannels[0];
private Channel testChannel2 => testChannels[1]; private Channel testChannel2 => testChannels[1];
@ -49,10 +51,14 @@ namespace osu.Game.Tests.Visual.Online
[Resolved] [Resolved]
private OsuConfigManager config { get; set; } = null!; private OsuConfigManager config { get; set; } = null!;
private int currentMessageId;
[SetUp] [SetUp]
public void SetUp() => Schedule(() => public void SetUp() => Schedule(() =>
{ {
currentMessageId = 0;
testChannels = Enumerable.Range(1, 10).Select(createPublicChannel).ToArray(); testChannels = Enumerable.Range(1, 10).Select(createPublicChannel).ToArray();
initialMessages = testChannels.SelectMany(createChannelMessages).ToArray();
Child = new DependencyProvidingContainer Child = new DependencyProvidingContainer
{ {
@ -99,7 +105,7 @@ namespace osu.Game.Tests.Visual.Online
return true; return true;
case GetMessagesRequest getMessages: case GetMessagesRequest getMessages:
getMessages.TriggerSuccess(createChannelMessages(getMessages.Channel)); getMessages.TriggerSuccess(initialMessages.ToList());
return true; return true;
case GetUserRequest getUser: case GetUserRequest getUser:
@ -495,6 +501,35 @@ namespace osu.Game.Tests.Visual.Online
waitForChannel1Visible(); waitForChannel1Visible();
} }
[Test]
public void TestRemoveMessages()
{
AddStep("Show overlay with channel", () =>
{
chatOverlay.Show();
channelManager.CurrentChannel.Value = channelManager.JoinChannel(testChannel1);
});
AddAssert("Overlay is visible", () => chatOverlay.State.Value == Visibility.Visible);
waitForChannel1Visible();
AddStep("Send message from another user", () =>
{
testChannel1.AddNewMessages(new Message
{
ChannelId = testChannel1.Id,
Content = "Message from another user",
Timestamp = DateTimeOffset.Now,
Sender = testUser1,
});
});
AddStep("Remove messages from other user", () =>
{
testChannel1.RemoveMessagesFromUser(testUser.Id);
});
}
private void joinTestChannel(int i) private void joinTestChannel(int i)
{ {
AddStep($"Join test channel {i}", () => channelManager.JoinChannel(testChannels[i])); AddStep($"Join test channel {i}", () => channelManager.JoinChannel(testChannels[i]));
@ -546,7 +581,7 @@ namespace osu.Game.Tests.Visual.Online
private List<Message> createChannelMessages(Channel channel) private List<Message> createChannelMessages(Channel channel)
{ {
var message = new Message var message = new Message(currentMessageId++)
{ {
ChannelId = channel.Id, ChannelId = channel.Id,
Content = $"Hello, this is a message in {channel.Name}", Content = $"Hello, this is a message in {channel.Name}",

View File

@ -1055,6 +1055,18 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("mod overlay hidden", () => songSelect!.ModSelect.State.Value == Visibility.Hidden); AddUntilStep("mod overlay hidden", () => songSelect!.ModSelect.State.Value == Visibility.Hidden);
} }
[Test]
public void TestBeatmapOptionsDisabled()
{
createSongSelect();
addRulesetImportStep(0);
AddAssert("options enabled", () => songSelect.ChildrenOfType<FooterButtonOptions>().Single().Enabled.Value);
AddStep("delete all beatmaps", () => manager.Delete());
AddAssert("options disabled", () => !songSelect.ChildrenOfType<FooterButtonOptions>().Single().Enabled.Value);
}
private void waitForInitialSelection() private void waitForInitialSelection()
{ {
AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault);

View File

@ -3,8 +3,10 @@
#nullable disable #nullable disable
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osuTK; using osuTK;
using osuTK.Input; using osuTK.Input;
@ -43,6 +45,12 @@ namespace osu.Game.Tests.Visual.SongSelect
InputManager.MoveMouseTo(Vector2.Zero); InputManager.MoveMouseTo(Vector2.Zero);
}); });
[Test]
public void TestState()
{
AddRepeatStep("toggle options state", () => this.ChildrenOfType<FooterButton>().Last().Enabled.Toggle(), 20);
}
[Test] [Test]
public void TestFooterRandom() public void TestFooterRandom()
{ {

View File

@ -59,6 +59,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
return base.OnScroll(e); return base.OnScroll(e);
} }
protected override bool OnClick(ClickEvent e) => true;
private class ExpandedContentScrollbar : OsuScrollbar private class ExpandedContentScrollbar : OsuScrollbar
{ {
public ExpandedContentScrollbar(Direction scrollDir) public ExpandedContentScrollbar(Direction scrollDir)

View File

@ -8,7 +8,7 @@ namespace osu.Game.Configuration
{ {
/// <summary> /// <summary>
/// A settings provider which generally sources from <see cref="OsuConfigManager"/> (global user settings) /// A settings provider which generally sources from <see cref="OsuConfigManager"/> (global user settings)
/// but can allow overriding settings by caching more locally. For instance, in the editor. /// but can allow overriding settings by caching more locally. For instance, in the editor compose screen.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// More settings can be moved into this interface as required. /// More settings can be moved into this interface as required.

View File

@ -17,6 +17,7 @@ using System.Collections.Generic;
using osu.Framework.Graphics.Rendering; using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Rendering.Vertices; using osu.Framework.Graphics.Rendering.Vertices;
using osu.Framework.Lists; using osu.Framework.Lists;
using osu.Framework.Bindables;
namespace osu.Game.Graphics.Backgrounds namespace osu.Game.Graphics.Backgrounds
{ {
@ -25,6 +26,11 @@ namespace osu.Game.Graphics.Backgrounds
private const float triangle_size = 100; private const float triangle_size = 100;
private const float base_velocity = 50; private const float base_velocity = 50;
/// <summary>
/// sqrt(3) / 2
/// </summary>
private const float equilateral_triangle_ratio = 0.866f;
/// <summary> /// <summary>
/// How many screen-space pixels are smoothed over. /// How many screen-space pixels are smoothed over.
/// Same behavior as Sprite's EdgeSmoothness. /// Same behavior as Sprite's EdgeSmoothness.
@ -69,7 +75,13 @@ namespace osu.Game.Graphics.Backgrounds
/// </summary> /// </summary>
protected virtual float SpawnRatio => 1; protected virtual float SpawnRatio => 1;
private float triangleScale = 1; private readonly BindableFloat triangleScale = new BindableFloat(1f);
public float TriangleScale
{
get => triangleScale.Value;
set => triangleScale.Value = value;
}
/// <summary> /// <summary>
/// Whether we should drop-off alpha values of triangles more quickly to improve /// Whether we should drop-off alpha values of triangles more quickly to improve
@ -103,30 +115,13 @@ namespace osu.Game.Graphics.Backgrounds
private void load(IRenderer renderer, ShaderManager shaders) private void load(IRenderer renderer, ShaderManager shaders)
{ {
texture = renderer.WhitePixel; texture = renderer.WhitePixel;
shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
addTriangles(true); triangleScale.BindValueChanged(_ => Reset(), true);
}
public float TriangleScale
{
get => triangleScale;
set
{
float change = value / triangleScale;
triangleScale = value;
for (int i = 0; i < parts.Count; i++)
{
TriangleParticle newParticle = parts[i];
newParticle.Scale *= change;
parts[i] = newParticle;
}
}
} }
protected override void Update() protected override void Update()
@ -147,7 +142,7 @@ namespace osu.Game.Graphics.Backgrounds
// Since position is relative, the velocity needs to scale inversely with DrawHeight. // Since position is relative, the velocity needs to scale inversely with DrawHeight.
// Since we will later multiply by the scale of individual triangles we normalize by // Since we will later multiply by the scale of individual triangles we normalize by
// dividing by triangleScale. // dividing by triangleScale.
float movedDistance = -elapsedSeconds * Velocity * base_velocity / (DrawHeight * triangleScale); float movedDistance = -elapsedSeconds * Velocity * base_velocity / (DrawHeight * TriangleScale);
for (int i = 0; i < parts.Count; i++) for (int i = 0; i < parts.Count; i++)
{ {
@ -159,7 +154,7 @@ namespace osu.Game.Graphics.Backgrounds
parts[i] = newParticle; parts[i] = newParticle;
float bottomPos = parts[i].Position.Y + triangle_size * parts[i].Scale * 0.866f / DrawHeight; float bottomPos = parts[i].Position.Y + triangle_size * parts[i].Scale * equilateral_triangle_ratio / DrawHeight;
if (bottomPos < 0) if (bottomPos < 0)
parts.RemoveAt(i); parts.RemoveAt(i);
} }
@ -185,9 +180,11 @@ namespace osu.Game.Graphics.Backgrounds
// Limited by the maximum size of QuadVertexBuffer for safety. // Limited by the maximum size of QuadVertexBuffer for safety.
const int max_triangles = ushort.MaxValue / (IRenderer.VERTICES_PER_QUAD + 2); const int max_triangles = ushort.MaxValue / (IRenderer.VERTICES_PER_QUAD + 2);
AimCount = (int)Math.Min(max_triangles, (DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio)); AimCount = (int)Math.Min(max_triangles, DrawWidth * DrawHeight * 0.002f / (TriangleScale * TriangleScale) * SpawnRatio);
for (int i = 0; i < AimCount - parts.Count; i++) int currentCount = parts.Count;
for (int i = 0; i < AimCount - currentCount; i++)
parts.Add(createTriangle(randomY)); parts.Add(createTriangle(randomY));
} }
@ -195,13 +192,27 @@ namespace osu.Game.Graphics.Backgrounds
{ {
TriangleParticle particle = CreateTriangle(); TriangleParticle particle = CreateTriangle();
particle.Position = new Vector2(nextRandom(), randomY ? nextRandom() : 1); particle.Position = getRandomPosition(randomY, particle.Scale);
particle.ColourShade = nextRandom(); particle.ColourShade = nextRandom();
particle.Colour = CreateTriangleShade(particle.ColourShade); particle.Colour = CreateTriangleShade(particle.ColourShade);
return particle; return particle;
} }
private Vector2 getRandomPosition(bool randomY, float scale)
{
float y = 1;
if (randomY)
{
// since triangles are drawn from the top - allow them to be positioned a bit above the screen
float maxOffset = triangle_size * scale * equilateral_triangle_ratio / DrawHeight;
y = Interpolation.ValueAt(nextRandom(), -maxOffset, 1f, 0f, 1f);
}
return new Vector2(nextRandom(), y);
}
/// <summary> /// <summary>
/// Creates a triangle particle with a random scale. /// Creates a triangle particle with a random scale.
/// </summary> /// </summary>
@ -214,7 +225,7 @@ namespace osu.Game.Graphics.Backgrounds
float u1 = 1 - nextRandom(); //uniform(0,1] random floats float u1 = 1 - nextRandom(); //uniform(0,1] random floats
float u2 = 1 - nextRandom(); float u2 = 1 - nextRandom();
float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); // random normal(0,1) float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); // random normal(0,1)
float scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); // random normal(mean,stdDev^2) float scale = Math.Max(TriangleScale * (mean + std_dev * randStdNormal), 0.1f); // random normal(mean,stdDev^2)
return new TriangleParticle { Scale = scale }; return new TriangleParticle { Scale = scale };
} }
@ -284,7 +295,7 @@ namespace osu.Game.Graphics.Backgrounds
foreach (TriangleParticle particle in parts) foreach (TriangleParticle particle in parts)
{ {
var offset = triangle_size * new Vector2(particle.Scale * 0.5f, particle.Scale * 0.866f); var offset = triangle_size * new Vector2(particle.Scale * 0.5f, particle.Scale * equilateral_triangle_ratio);
var triangle = new Triangle( var triangle = new Triangle(
Vector2Extensions.Transform(particle.Position * size, DrawInfo.Matrix), Vector2Extensions.Transform(particle.Position * size, DrawInfo.Matrix),

View File

@ -17,7 +17,6 @@ namespace osu.Game.Graphics.Sprites
private void load(ShaderManager shaders) private void load(ShaderManager shaders)
{ {
TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"LogoAnimation"); TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"LogoAnimation");
RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"LogoAnimation"); // Masking isn't supported for now
} }
private float animationProgress; private float animationProgress;
@ -58,7 +57,7 @@ namespace osu.Game.Graphics.Sprites
protected override void Blit(IRenderer renderer) protected override void Blit(IRenderer renderer)
{ {
GetAppropriateShader(renderer).GetUniform<float>("progress").UpdateValue(ref progress); TextureShader.GetUniform<float>("progress").UpdateValue(ref progress);
base.Blit(renderer); base.Blit(renderer);
} }

View File

@ -7,12 +7,30 @@ using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
/// <summary>
/// A request which should be sent occasionally while interested in chat and online state.
///
/// This will:
/// - Mark the user as "online" (for 10 minutes since the last invocation).
/// - Return any silences since the last invocation (if either <see cref="SinceMessageId"/> or <see cref="SinceSilenceId"/> is not null).
///
/// For silence handling, a <see cref="SinceMessageId"/> should be provided as soon as a message is received by the client.
/// From that point forward, <see cref="SinceSilenceId"/> should be preferred after the first <see cref="ChatSilence"/>
/// arrives in a response from the ack request. Specifying both parameters will prioritise the latter.
/// </summary>
public class ChatAckRequest : APIRequest<ChatAckResponse> public class ChatAckRequest : APIRequest<ChatAckResponse>
{ {
public long? SinceMessageId;
public uint? SinceSilenceId;
protected override WebRequest CreateWebRequest() protected override WebRequest CreateWebRequest()
{ {
var req = base.CreateWebRequest(); var req = base.CreateWebRequest();
req.Method = HttpMethod.Post; req.Method = HttpMethod.Post;
if (SinceMessageId != null)
req.AddParameter(@"since", SinceMessageId.ToString());
if (SinceSilenceId != null)
req.AddParameter(@"history_since", SinceSilenceId.Value.ToString());
return req; return req;
} }

View File

@ -12,6 +12,6 @@ namespace osu.Game.Online.API.Requests.Responses
public uint Id { get; set; } public uint Id { get; set; }
[JsonProperty("user_id")] [JsonProperty("user_id")]
public uint UserId { get; set; } public int UserId { get; set; }
} }
} }

View File

@ -157,6 +157,20 @@ namespace osu.Game.Online.Chat
NewMessagesArrived?.Invoke(messages); NewMessagesArrived?.Invoke(messages);
} }
public void RemoveMessagesFromUser(int userId)
{
for (int i = 0; i < Messages.Count; i++)
{
var message = Messages[i];
if (message.SenderId == userId)
{
Messages.RemoveAt(i--);
MessageRemoved?.Invoke(message);
}
}
}
/// <summary> /// <summary>
/// Replace or remove a message from the channel. /// Replace or remove a message from the channel.
/// </summary> /// </summary>

View File

@ -74,6 +74,9 @@ namespace osu.Game.Online.Chat
private bool channelsInitialised; private bool channelsInitialised;
private ScheduledDelegate scheduledAck; private ScheduledDelegate scheduledAck;
private long? lastSilenceMessageId;
private uint? lastSilenceId;
public ChannelManager(IAPIProvider api) public ChannelManager(IAPIProvider api)
{ {
this.api = api; this.api = api;
@ -105,28 +108,7 @@ namespace osu.Game.Online.Chat
connector.Start(); connector.Start();
apiState.BindTo(api.State); apiState.BindTo(api.State);
apiState.BindValueChanged(_ => performChatAckRequest(), true); apiState.BindValueChanged(_ => SendAck(), true);
}
private void performChatAckRequest()
{
if (apiState.Value != APIState.Online)
return;
scheduledAck?.Cancel();
var req = new ChatAckRequest();
req.Success += _ => scheduleNextRequest();
req.Failure += _ => scheduleNextRequest();
api.Queue(req);
// Todo: Handle silences.
void scheduleNextRequest()
{
scheduledAck?.Cancel();
scheduledAck = Scheduler.AddDelayed(performChatAckRequest, 60000);
}
} }
/// <summary> /// <summary>
@ -349,6 +331,8 @@ namespace osu.Game.Online.Chat
foreach (var group in messages.GroupBy(m => m.ChannelId)) foreach (var group in messages.GroupBy(m => m.ChannelId))
channels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray()); channels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
lastSilenceMessageId ??= messages.LastOrDefault()?.Id;
} }
private void initializeChannels() private void initializeChannels()
@ -398,6 +382,44 @@ namespace osu.Game.Online.Chat
api.Queue(fetchInitialMsgReq); api.Queue(fetchInitialMsgReq);
} }
/// <summary>
/// Sends an acknowledgement request to the API.
/// This marks the user as online to receive messages from public channels, while also returning a list of silenced users.
/// It needs to be called at least once every 10 minutes to remain visibly marked as online.
/// </summary>
public void SendAck()
{
if (apiState.Value != APIState.Online)
return;
var req = new ChatAckRequest
{
SinceMessageId = lastSilenceMessageId,
SinceSilenceId = lastSilenceId
};
req.Failure += _ => scheduleNextRequest();
req.Success += ack =>
{
foreach (var silence in ack.Silences)
{
foreach (var channel in JoinedChannels)
channel.RemoveMessagesFromUser(silence.UserId);
lastSilenceId = Math.Max(lastSilenceId ?? 0, silence.Id);
}
scheduleNextRequest();
};
api.Queue(req);
void scheduleNextRequest()
{
scheduledAck?.Cancel();
scheduledAck = Scheduler.AddDelayed(SendAck, 60000);
}
}
/// <summary> /// <summary>
/// Find an existing channel instance for the provided channel. Lookup is performed basd on ID. /// Find an existing channel instance for the provided channel. Lookup is performed basd on ID.
/// The provided channel may be used if an existing instance is not found. /// The provided channel may be used if an existing instance is not found.

View File

@ -72,7 +72,6 @@ namespace osu.Game.Online.Notifications.WebSocket
break; break;
} }
Logger.Log($"{GetType().ReadableName()} handling event: {message.Event}");
await onMessageReceivedAsync(message); await onMessageReceivedAsync(message);
} }

View File

@ -41,11 +41,8 @@ namespace osu.Game.Overlays
private IBindable<APIUser> apiUser; private IBindable<APIUser> apiUser;
private Drawable currentContent;
private Container panelTarget; private Container panelTarget;
private FillFlowContainer<BeatmapCard> foundContent; private FillFlowContainer<BeatmapCard> foundContent;
private NotFoundDrawable notFoundContent;
private SupporterRequiredDrawable supporterRequiredContent;
private BeatmapListingFilterControl filterControl; private BeatmapListingFilterControl filterControl;
public BeatmapListingOverlay() public BeatmapListingOverlay()
@ -86,11 +83,6 @@ namespace osu.Game.Overlays
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Masking = true, Masking = true,
Padding = new MarginPadding { Horizontal = 20 }, Padding = new MarginPadding { Horizontal = 20 },
Children = new Drawable[]
{
notFoundContent = new NotFoundDrawable(),
supporterRequiredContent = new SupporterRequiredDrawable(),
}
} }
}, },
}, },
@ -107,7 +99,7 @@ namespace osu.Game.Overlays
apiUser.BindValueChanged(_ => Schedule(() => apiUser.BindValueChanged(_ => Schedule(() =>
{ {
if (api.IsLoggedIn) if (api.IsLoggedIn)
addContentToResultsArea(Drawable.Empty()); replaceResultsAreaContent(Drawable.Empty());
})); }));
} }
@ -155,8 +147,8 @@ namespace osu.Game.Overlays
if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilters) if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilters)
{ {
supporterRequiredContent.UpdateText(searchResult.SupporterOnlyFiltersUsed); var supporterOnly = new SupporterRequiredDrawable(searchResult.SupporterOnlyFiltersUsed);
addContentToResultsArea(supporterRequiredContent); replaceResultsAreaContent(supporterOnly);
return; return;
} }
@ -167,13 +159,13 @@ namespace osu.Game.Overlays
//No matches case //No matches case
if (!newCards.Any()) if (!newCards.Any())
{ {
addContentToResultsArea(notFoundContent); replaceResultsAreaContent(new NotFoundDrawable());
return; return;
} }
var content = createCardContainerFor(newCards); var content = createCardContainerFor(newCards);
panelLoadTask = LoadComponentAsync(foundContent = content, addContentToResultsArea, (cancellationToken = new CancellationTokenSource()).Token); panelLoadTask = LoadComponentAsync(foundContent = content, replaceResultsAreaContent, (cancellationToken = new CancellationTokenSource()).Token);
} }
else else
{ {
@ -221,36 +213,16 @@ namespace osu.Game.Overlays
return content; return content;
} }
private void addContentToResultsArea(Drawable content) private void replaceResultsAreaContent(Drawable content)
{ {
Loading.Hide(); Loading.Hide();
lastFetchDisplayedTime = Time.Current; lastFetchDisplayedTime = Time.Current;
if (content == currentContent) panelTarget.Child = content;
return;
var lastContent = currentContent;
if (lastContent != null)
{
lastContent.FadeOut();
if (!isPlaceholderContent(lastContent))
lastContent.Expire();
}
if (!content.IsAlive)
panelTarget.Add(content);
content.FadeInFromZero(); content.FadeInFromZero();
currentContent = content;
} }
/// <summary>
/// Whether <paramref name="drawable"/> is a static placeholder reused multiple times by this overlay.
/// </summary>
private bool isPlaceholderContent(Drawable drawable)
=> drawable == notFoundContent || drawable == supporterRequiredContent;
private void onCardSizeChanged() private void onCardSizeChanged()
{ {
if (foundContent?.IsAlive != true || !foundContent.Any()) if (foundContent?.IsAlive != true || !foundContent.Any())
@ -287,7 +259,7 @@ namespace osu.Game.Overlays
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(LargeTextureStore textures)
{ {
AddInternal(new FillFlowContainer AddInternal(new FillFlowContainer
{ {
@ -324,15 +296,19 @@ namespace osu.Game.Overlays
{ {
private LinkFlowContainer supporterRequiredText; private LinkFlowContainer supporterRequiredText;
public SupporterRequiredDrawable() private readonly List<LocalisableString> filtersUsed;
public SupporterRequiredDrawable(List<LocalisableString> filtersUsed)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = 225; Height = 225;
Alpha = 0; Alpha = 0;
this.filtersUsed = filtersUsed;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(LargeTextureStore textures)
{ {
AddInternal(new FillFlowContainer AddInternal(new FillFlowContainer
{ {
@ -360,14 +336,9 @@ namespace osu.Game.Overlays
}, },
} }
}); });
}
public void UpdateText(List<LocalisableString> filters)
{
supporterRequiredText.Clear();
supporterRequiredText.AddText( supporterRequiredText.AddText(
BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(), BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filtersUsed), "").ToString(),
t => t =>
{ {
t.Font = OsuFont.GetFont(size: 16); t.Font = OsuFont.GetFont(size: 16);

View File

@ -119,22 +119,17 @@ namespace osu.Game.Overlays.Dashboard.Home.News
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(GameHost host) private void load(GameHost host)
{ {
NewsPostBackground bg; Child = new DelayedLoadUnloadWrapper(() => new NewsPostBackground(post.FirstImage)
Child = new DelayedLoadWrapper(bg = new NewsPostBackground(post.FirstImage)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill, FillMode = FillMode.Fill,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Alpha = 0
}) })
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
}; };
bg.OnLoadComplete += d => d.FadeIn(250, Easing.In);
TooltipText = "view in browser"; TooltipText = "view in browser";
Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug); Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug);

View File

@ -49,7 +49,6 @@ namespace osu.Game.Overlays.News
Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug); Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug);
} }
NewsPostBackground bg;
AddRange(new Drawable[] AddRange(new Drawable[]
{ {
background = new Box background = new Box
@ -71,14 +70,14 @@ namespace osu.Game.Overlays.News
CornerRadius = 6, CornerRadius = 6,
Children = new Drawable[] Children = new Drawable[]
{ {
new DelayedLoadWrapper(bg = new NewsPostBackground(post.FirstImage) new DelayedLoadUnloadWrapper(() => new NewsPostBackground(post.FirstImage)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill, FillMode = FillMode.Fill,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Alpha = 0 Alpha = 0
}) }, timeBeforeUnload: 5000)
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
}, },
@ -116,8 +115,6 @@ namespace osu.Game.Overlays.News
IdleColour = colourProvider.Background4; IdleColour = colourProvider.Background4;
HoverColour = colourProvider.Background3; HoverColour = colourProvider.Background3;
bg.OnLoadComplete += d => d.FadeIn(250, Easing.In);
main.AddParagraph(post.Title, t => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold)); main.AddParagraph(post.Title, t => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold));
main.AddParagraph(post.Preview, t => t.Font = OsuFont.GetFont(size: 12)); // Should use sans-serif font main.AddParagraph(post.Preview, t => t.Font = OsuFont.GetFont(size: 12)); // Should use sans-serif font
main.AddParagraph("by ", t => t.Font = OsuFont.GetFont(size: 12)); main.AddParagraph("by ", t => t.Font = OsuFont.GetFont(size: 12));

View File

@ -4,6 +4,7 @@
#nullable disable #nullable disable
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
@ -25,6 +26,12 @@ namespace osu.Game.Overlays.News
Texture = store.Get(createUrl(sourceUrl)); Texture = store.Get(createUrl(sourceUrl));
} }
protected override void LoadComplete()
{
base.LoadComplete();
this.FadeInFromZero(500, Easing.OutQuint);
}
private string createUrl(string source) private string createUrl(string source)
{ {
if (string.IsNullOrEmpty(source)) if (string.IsNullOrEmpty(source))

View File

@ -100,7 +100,7 @@ namespace osu.Game.Overlays
}, },
Children = new[] Children = new[]
{ {
background = new Background(), background = Empty(),
title = new OsuSpriteText title = new OsuSpriteText
{ {
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
@ -413,7 +413,7 @@ namespace osu.Game.Overlays
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(LargeTextureStore textures)
{ {
sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4"); sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4");
} }

View File

@ -18,7 +18,7 @@ namespace osu.Game.Overlays
Height = 80; Height = 80;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Masking = true; Masking = true;
InternalChild = new Background(textureName); InternalChild = new DelayedLoadWrapper(() => new Background(textureName));
} }
private class Background : Sprite private class Background : Sprite
@ -36,10 +36,16 @@ namespace osu.Game.Overlays
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(LargeTextureStore textures)
{ {
Texture = textures.Get(textureName); Texture = textures.Get(textureName);
} }
protected override void LoadComplete()
{
base.LoadComplete();
this.FadeInFromZero(500, Easing.OutQuint);
}
} }
} }
} }

View File

@ -3,6 +3,8 @@
#nullable disable #nullable disable
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Localisation; using osu.Game.Localisation;
@ -13,6 +15,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{ {
protected override LocalisableString Header => BindingSettingsStrings.ShortcutAndGameplayBindings; protected override LocalisableString Header => BindingSettingsStrings.ShortcutAndGameplayBindings;
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "keybindings" });
public BindingSettings(KeyBindingPanel keyConfig) public BindingSettings(KeyBindingPanel keyConfig)
{ {
Children = new Drawable[] Children = new Drawable[]

View File

@ -111,9 +111,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux) if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux)
{ {
t.NewLine(); t.NewLine();
var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedBindableString(TabletSettingsStrings.NoTabletDetectedDescription(RuntimeInfo.OS == RuntimeInfo.Platform.Windows var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedBindableString(TabletSettingsStrings.NoTabletDetectedDescription(
? @"https://opentabletdriver.net/Wiki/FAQ/Windows" RuntimeInfo.OS == RuntimeInfo.Platform.Windows
: @"https://opentabletdriver.net/Wiki/FAQ/Linux")).Value); ? @"https://opentabletdriver.net/Wiki/FAQ/Windows"
: @"https://opentabletdriver.net/Wiki/FAQ/Linux")).Value);
t.AddLinks(formattedSource.Text, formattedSource.Links); t.AddLinks(formattedSource.Text, formattedSource.Links);
} }
}), }),
@ -274,6 +275,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
sizeY.Default = sizeY.MaxValue = tab.Size.Y; sizeY.Default = sizeY.MaxValue = tab.Size.Y;
areaSize.Default = new Vector2(sizeX.Default, sizeY.Default); areaSize.Default = new Vector2(sizeX.Default, sizeY.Default);
areaOffset.Default = new Vector2(offsetX.Default, offsetY.Default);
}), true); }), true);
} }

View File

@ -3,8 +3,6 @@
#nullable disable #nullable disable
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
@ -22,8 +20,6 @@ namespace osu.Game.Overlays.Settings.Sections
public override LocalisableString Header => InputSettingsStrings.InputSectionHeader; public override LocalisableString Header => InputSettingsStrings.InputSectionHeader;
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "keybindings" });
public override Drawable CreateIcon() => new SpriteIcon public override Drawable CreateIcon() => new SpriteIcon
{ {
Icon = FontAwesome.Solid.Keyboard Icon = FontAwesome.Solid.Keyboard

View File

@ -115,7 +115,11 @@ namespace osu.Game.Rulesets.UI
public Sample Get(string name) => primary.Get(name) ?? fallback.Get(name); public Sample Get(string name) => primary.Get(name) ?? fallback.Get(name);
public Task<Sample> GetAsync(string name, CancellationToken cancellationToken = default) => primary.GetAsync(name, cancellationToken) ?? fallback.GetAsync(name, cancellationToken); public async Task<Sample> GetAsync(string name, CancellationToken cancellationToken = default)
{
return await primary.GetAsync(name, cancellationToken).ConfigureAwait(false)
?? await fallback.GetAsync(name, cancellationToken).ConfigureAwait(false);
}
public Stream GetStream(string name) => primary.GetStream(name) ?? fallback.GetStream(name); public Stream GetStream(string name) => primary.GetStream(name) ?? fallback.GetStream(name);

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Extensions; using osu.Game.Extensions;
using osu.Game.IO.Serialization; using osu.Game.IO.Serialization;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -19,7 +20,7 @@ using osu.Game.Screens.Edit.Compose.Components.Timeline;
namespace osu.Game.Screens.Edit.Compose namespace osu.Game.Screens.Edit.Compose
{ {
public class ComposeScreen : EditorScreenWithTimeline public class ComposeScreen : EditorScreenWithTimeline, IGameplaySettings
{ {
[Resolved] [Resolved]
private GameHost host { get; set; } private GameHost host { get; set; }
@ -27,6 +28,9 @@ namespace osu.Game.Screens.Edit.Compose
[Resolved] [Resolved]
private EditorClock clock { get; set; } private EditorClock clock { get; set; }
[Resolved]
private IGameplaySettings globalGameplaySettings { get; set; }
private Bindable<string> clipboard { get; set; } private Bindable<string> clipboard { get; set; }
private HitObjectComposer composer; private HitObjectComposer composer;
@ -157,5 +161,12 @@ namespace osu.Game.Screens.Edit.Compose
} }
#endregion #endregion
// Combo colour normalisation should not be applied in the editor.
// Note this doesn't affect editor test mode.
IBindable<float> IGameplaySettings.ComboColourNormalisationAmount => new Bindable<float>();
// Arguable.
IBindable<float> IGameplaySettings.PositionalHitsoundsLevel => globalGameplaySettings.PositionalHitsoundsLevel;
} }
} }

View File

@ -58,8 +58,7 @@ namespace osu.Game.Screens.Edit
{ {
[Cached(typeof(IBeatSnapProvider))] [Cached(typeof(IBeatSnapProvider))]
[Cached] [Cached]
public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>, IKeyBindingHandler<PlatformAction>, IBeatSnapProvider, ISamplePlaybackDisabler, IBeatSyncProvider, public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>, IKeyBindingHandler<PlatformAction>, IBeatSnapProvider, ISamplePlaybackDisabler, IBeatSyncProvider
IGameplaySettings
{ {
public override float BackgroundParallaxAmount => 0.1f; public override float BackgroundParallaxAmount => 0.1f;
@ -99,9 +98,6 @@ namespace osu.Game.Screens.Edit
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
private INotificationOverlay notifications { get; set; } private INotificationOverlay notifications { get; set; }
[Resolved]
private IGameplaySettings globalGameplaySettings { get; set; }
public readonly Bindable<EditorScreenMode> Mode = new Bindable<EditorScreenMode>(); public readonly Bindable<EditorScreenMode> Mode = new Bindable<EditorScreenMode>();
public IBindable<bool> SamplePlaybackDisabled => samplePlaybackDisabled; public IBindable<bool> SamplePlaybackDisabled => samplePlaybackDisabled;
@ -1045,11 +1041,5 @@ namespace osu.Game.Screens.Edit
{ {
} }
} }
// Combo colour normalisation should not be applied in the editor.
IBindable<float> IGameplaySettings.ComboColourNormalisationAmount => new Bindable<float>();
// Arguable.
IBindable<float> IGameplaySettings.PositionalHitsoundsLevel => globalGameplaySettings.PositionalHitsoundsLevel;
} }
} }

View File

@ -1,8 +1,7 @@
// 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.
#nullable disable using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -17,7 +16,7 @@ namespace osu.Game.Screens.Edit.GameplayTest
private readonly EditorState editorState; private readonly EditorState editorState;
[Resolved] [Resolved]
private MusicController musicController { get; set; } private MusicController musicController { get; set; } = null!;
public EditorPlayer(Editor editor) public EditorPlayer(Editor editor)
: base(new PlayerConfiguration { ShowResults = false }) : base(new PlayerConfiguration { ShowResults = false })
@ -29,7 +28,12 @@ namespace osu.Game.Screens.Edit.GameplayTest
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart)
{ {
var masterGameplayClockContainer = new MasterGameplayClockContainer(beatmap, gameplayStart); var masterGameplayClockContainer = new MasterGameplayClockContainer(beatmap, gameplayStart);
masterGameplayClockContainer.Reset(editorState.Time);
// Only reset the time to the current point if the editor is later than the normal start time (and the first object).
// This allows more sane test playing from the start of the beatmap (ie. correctly adding lead-in time).
if (editorState.Time > gameplayStart && editorState.Time > DrawableRuleset.Objects.FirstOrDefault()?.StartTime)
masterGameplayClockContainer.Reset(editorState.Time);
return masterGameplayClockContainer; return masterGameplayClockContainer;
} }

View File

@ -125,13 +125,11 @@ namespace osu.Game.Screens
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(ShaderManager manager) private void load(ShaderManager manager)
{ {
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED));
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR));
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE)); loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE));
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.BLUR));
loadTargets.Add(manager.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE)); loadTargets.Add(manager.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE));
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE_ROUNDED));
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE)); loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE));
} }

View File

@ -224,8 +224,8 @@ namespace osu.Game.Screens.Menu
{ {
rulesetsScale.ScaleTo(0.8f, 1000); rulesetsScale.ScaleTo(0.8f, 1000);
rulesets.FadeIn().ScaleTo(1).TransformSpacingTo(new Vector2(200, 0)); rulesets.FadeIn().ScaleTo(1).TransformSpacingTo(new Vector2(200, 0));
welcomeText.FadeOut(); welcomeText.FadeOut().Expire();
triangles.FadeOut(); triangles.FadeOut().Expire();
} }
using (BeginDelayedSequence(rulesets_2)) using (BeginDelayedSequence(rulesets_2))
@ -307,7 +307,7 @@ namespace osu.Game.Screens.Menu
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(LargeTextureStore textures)
{ {
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {

View File

@ -89,7 +89,7 @@ namespace osu.Game.Screens.Menu
private void load(IRenderer renderer, ShaderManager shaders) private void load(IRenderer renderer, ShaderManager shaders)
{ {
texture = renderer.WhitePixel; texture = renderer.WhitePixel;
shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
} }
private readonly float[] temporalAmplitudes = new float[ChannelAmplitudes.AMPLITUDES_SIZE]; private readonly float[] temporalAmplitudes = new float[ChannelAmplitudes.AMPLITUDES_SIZE];

View File

@ -1,8 +1,6 @@
// 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.
#nullable disable
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -11,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration; using osu.Game.Configuration;
@ -23,10 +22,9 @@ using osuTK;
namespace osu.Game.Screens.Play.HUD.HitErrorMeters namespace osu.Game.Screens.Play.HUD.HitErrorMeters
{ {
[Cached]
public class BarHitErrorMeter : HitErrorMeter public class BarHitErrorMeter : HitErrorMeter
{ {
private const int judgement_line_width = 14;
[SettingSource("Judgement line thickness", "How thick the individual lines should be.")] [SettingSource("Judgement line thickness", "How thick the individual lines should be.")]
public BindableNumber<float> JudgementLineThickness { get; } = new BindableNumber<float>(4) public BindableNumber<float> JudgementLineThickness { get; } = new BindableNumber<float>(4)
{ {
@ -44,28 +42,33 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
[SettingSource("Label style", "How to show early/late extremities")] [SettingSource("Label style", "How to show early/late extremities")]
public Bindable<LabelStyles> LabelStyle { get; } = new Bindable<LabelStyles>(LabelStyles.Icons); public Bindable<LabelStyles> LabelStyle { get; } = new Bindable<LabelStyles>(LabelStyles.Icons);
private SpriteIcon arrow; private const int judgement_line_width = 14;
private UprightAspectMaintainingContainer labelEarly;
private UprightAspectMaintainingContainer labelLate;
private Container colourBarsEarly; private const int max_concurrent_judgements = 50;
private Container colourBarsLate;
private Container judgementsContainer; private const int centre_marker_size = 8;
private double maxHitWindow; private double maxHitWindow;
private double floatingAverage; private double floatingAverage;
private Container colourBars;
private Container arrowContainer;
private (HitResult result, double length)[] hitWindows; private readonly DrawablePool<JudgementLine> judgementLinePool = new DrawablePool<JudgementLine>(50);
private const int max_concurrent_judgements = 50; private SpriteIcon arrow = null!;
private UprightAspectMaintainingContainer labelEarly = null!;
private UprightAspectMaintainingContainer labelLate = null!;
private Drawable[] centreMarkerDrawables; private Container colourBarsEarly = null!;
private Container colourBarsLate = null!;
private const int centre_marker_size = 8; private Container judgementsContainer = null!;
private Container colourBars = null!;
private Container arrowContainer = null!;
private (HitResult result, double length)[] hitWindows = null!;
private Drawable[]? centreMarkerDrawables;
public BarHitErrorMeter() public BarHitErrorMeter()
{ {
@ -88,6 +91,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
Margin = new MarginPadding(2), Margin = new MarginPadding(2),
Children = new Drawable[] Children = new Drawable[]
{ {
judgementLinePool,
colourBars = new Container colourBars = new Container
{ {
Name = "colour axis", Name = "colour axis",
@ -403,11 +407,12 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
} }
} }
judgementsContainer.Add(new JudgementLine judgementLinePool.Get(drawableJudgement =>
{ {
JudgementLineThickness = { BindTarget = JudgementLineThickness }, drawableJudgement.Y = getRelativeJudgementPosition(judgement.TimeOffset);
Y = getRelativeJudgementPosition(judgement.TimeOffset), drawableJudgement.Colour = GetColourForHitResult(judgement.Type);
Colour = GetColourForHitResult(judgement.Type),
judgementsContainer.Add(drawableJudgement);
}); });
arrow.MoveToY( arrow.MoveToY(
@ -417,10 +422,13 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
private float getRelativeJudgementPosition(double value) => Math.Clamp((float)((value / maxHitWindow) + 1) / 2, 0, 1); private float getRelativeJudgementPosition(double value) => Math.Clamp((float)((value / maxHitWindow) + 1) / 2, 0, 1);
internal class JudgementLine : CompositeDrawable internal class JudgementLine : PoolableDrawable
{ {
public readonly BindableNumber<float> JudgementLineThickness = new BindableFloat(); public readonly BindableNumber<float> JudgementLineThickness = new BindableFloat();
[Resolved]
private BarHitErrorMeter barHitErrorMeter { get; set; } = null!;
public JudgementLine() public JudgementLine()
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
@ -439,16 +447,22 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete();
JudgementLineThickness.BindTo(barHitErrorMeter.JudgementLineThickness);
JudgementLineThickness.BindValueChanged(thickness => Height = thickness.NewValue, true);
}
protected override void PrepareForUse()
{
base.PrepareForUse();
const int judgement_fade_in_duration = 100; const int judgement_fade_in_duration = 100;
const int judgement_fade_out_duration = 5000; const int judgement_fade_out_duration = 5000;
base.LoadComplete();
Alpha = 0; Alpha = 0;
Width = 0; Width = 0;
JudgementLineThickness.BindValueChanged(thickness => Height = thickness.NewValue, true);
this this
.FadeTo(0.6f, judgement_fade_in_duration, Easing.OutQuint) .FadeTo(0.6f, judgement_fade_in_duration, Easing.OutQuint)
.ResizeWidthTo(1, judgement_fade_in_duration, Easing.OutQuint) .ResizeWidthTo(1, judgement_fade_in_duration, Easing.OutQuint)

View File

@ -3,9 +3,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
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.Pooling;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
@ -15,6 +17,7 @@ using osuTK.Graphics;
namespace osu.Game.Screens.Play.HUD.HitErrorMeters namespace osu.Game.Screens.Play.HUD.HitErrorMeters
{ {
[Cached]
public class ColourHitErrorMeter : HitErrorMeter public class ColourHitErrorMeter : HitErrorMeter
{ {
private const int animation_duration = 200; private const int animation_duration = 200;
@ -82,7 +85,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
{ {
base.LoadComplete(); base.LoadComplete();
JudgementCount.BindValueChanged(count => JudgementCount.BindValueChanged(_ =>
{ {
removeExtraJudgements(); removeExtraJudgements();
updateMetrics(); updateMetrics();
@ -91,14 +94,17 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
JudgementSpacing.BindValueChanged(_ => updateMetrics(), true); JudgementSpacing.BindValueChanged(_ => updateMetrics(), true);
} }
private readonly DrawablePool<HitErrorShape> judgementLinePool = new DrawablePool<HitErrorShape>(50);
public void Push(Color4 colour) public void Push(Color4 colour)
{ {
Add(new HitErrorShape(colour, drawable_judgement_size) judgementLinePool.Get(shape =>
{ {
Shape = { BindTarget = JudgementShape }, shape.Colour = colour;
}); Add(shape);
removeExtraJudgements(); removeExtraJudgements();
});
} }
private void removeExtraJudgements() private void removeExtraJudgements()
@ -116,32 +122,32 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
} }
} }
public class HitErrorShape : Container public class HitErrorShape : PoolableDrawable
{ {
public bool IsRemoved { get; private set; } public bool IsRemoved { get; private set; }
public readonly Bindable<ShapeStyle> Shape = new Bindable<ShapeStyle>(); public readonly Bindable<ShapeStyle> Shape = new Bindable<ShapeStyle>();
private readonly Color4 colour; [Resolved]
private ColourHitErrorMeter hitErrorMeter { get; set; } = null!;
private Container content = null!; private Container content = null!;
public HitErrorShape(Color4 colour, int size) public HitErrorShape()
{ {
this.colour = colour; Size = new Vector2(drawable_judgement_size);
Size = new Vector2(size);
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
Child = content = new Container InternalChild = content = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = colour
}; };
Shape.BindTo(hitErrorMeter.JudgementShape);
Shape.BindValueChanged(shape => Shape.BindValueChanged(shape =>
{ {
switch (shape.NewValue) switch (shape.NewValue)
@ -155,17 +161,32 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
break; break;
} }
}, true); }, true);
}
content.FadeInFromZero(animation_duration, Easing.OutQuint); protected override void PrepareForUse()
content.MoveToY(-DrawSize.Y); {
content.MoveToY(0, animation_duration, Easing.OutQuint); base.PrepareForUse();
this.FadeInFromZero(animation_duration, Easing.OutQuint)
// On pool re-use, start flow animation from (0,0).
.MoveTo(Vector2.Zero);
content.MoveToY(-DrawSize.Y)
.MoveToY(0, animation_duration, Easing.OutQuint);
}
protected override void FreeAfterUse()
{
base.FreeAfterUse();
IsRemoved = false;
} }
public void Remove() public void Remove()
{ {
IsRemoved = true; IsRemoved = true;
this.FadeOut(animation_duration, Easing.OutQuint).Expire(); this.FadeOut(animation_duration, Easing.OutQuint)
.Expire();
} }
} }

View File

@ -57,7 +57,18 @@ namespace osu.Game.Screens.Select
} }
} }
private void updateModeLight() => modeLight.FadeColour(buttons.FirstOrDefault(b => b.IsHovered)?.SelectedColour ?? Color4.Transparent, TRANSITION_LENGTH, Easing.OutQuint); private void updateModeLight()
{
var selectedButton = buttons.FirstOrDefault(b => b.Enabled.Value && b.IsHovered);
if (selectedButton != null)
{
modeLight.FadeIn(TRANSITION_LENGTH, Easing.OutQuint);
modeLight.FadeColour(selectedButton.SelectedColour, TRANSITION_LENGTH, Easing.OutQuint);
}
else
modeLight.FadeOut(TRANSITION_LENGTH, Easing.OutQuint);
}
public Footer() public Footer()
{ {
@ -78,6 +89,7 @@ namespace osu.Game.Screens.Select
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = 3, Height = 3,
Position = new Vector2(0, -3), Position = new Vector2(0, -3),
Colour = Color4.Black,
}, },
new FillFlowContainer new FillFlowContainer
{ {

View File

@ -120,10 +120,18 @@ namespace osu.Game.Screens.Select
}; };
} }
protected override void LoadComplete()
{
base.LoadComplete();
Enabled.BindValueChanged(_ => updateDisplay(), true);
}
public Action Hovered; public Action Hovered;
public Action HoverLost; public Action HoverLost;
public GlobalAction? Hotkey; public GlobalAction? Hotkey;
private bool mouseDown;
protected override void UpdateAfterChildren() protected override void UpdateAfterChildren()
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
@ -140,32 +148,38 @@ namespace osu.Game.Screens.Select
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)
{ {
Hovered?.Invoke(); Hovered?.Invoke();
light.ScaleTo(new Vector2(1, 2), Footer.TRANSITION_LENGTH, Easing.OutQuint); updateDisplay();
light.FadeColour(SelectedColour, Footer.TRANSITION_LENGTH, Easing.OutQuint);
return true; return true;
} }
protected override void OnHoverLost(HoverLostEvent e) protected override void OnHoverLost(HoverLostEvent e)
{ {
HoverLost?.Invoke(); HoverLost?.Invoke();
light.ScaleTo(new Vector2(1, 1), Footer.TRANSITION_LENGTH, Easing.OutQuint); updateDisplay();
light.FadeColour(DeselectedColour, Footer.TRANSITION_LENGTH, Easing.OutQuint);
} }
protected override bool OnMouseDown(MouseDownEvent e) protected override bool OnMouseDown(MouseDownEvent e)
{ {
box.FadeTo(0.3f, Footer.TRANSITION_LENGTH * 2, Easing.OutQuint); if (!Enabled.Value)
return true;
mouseDown = true;
updateDisplay();
return base.OnMouseDown(e); return base.OnMouseDown(e);
} }
protected override void OnMouseUp(MouseUpEvent e) protected override void OnMouseUp(MouseUpEvent e)
{ {
box.FadeOut(Footer.TRANSITION_LENGTH, Easing.OutQuint); mouseDown = false;
updateDisplay();
base.OnMouseUp(e); base.OnMouseUp(e);
} }
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
{ {
if (!Enabled.Value)
return true;
box.ClearTransforms(); box.ClearTransforms();
box.Alpha = 1; box.Alpha = 1;
box.FadeOut(Footer.TRANSITION_LENGTH * 3, Easing.OutQuint); box.FadeOut(Footer.TRANSITION_LENGTH * 3, Easing.OutQuint);
@ -184,5 +198,20 @@ namespace osu.Game.Screens.Select
} }
public virtual void OnReleased(KeyBindingReleaseEvent<GlobalAction> e) { } public virtual void OnReleased(KeyBindingReleaseEvent<GlobalAction> e) { }
private void updateDisplay()
{
this.FadeTo(Enabled.Value ? 1 : 0.25f, Footer.TRANSITION_LENGTH, Easing.OutQuint);
light.ScaleTo(Enabled.Value && IsHovered ? new Vector2(1, 2) : new Vector2(1), Footer.TRANSITION_LENGTH, Easing.OutQuint);
light.FadeColour(Enabled.Value && IsHovered ? SelectedColour : DeselectedColour, Footer.TRANSITION_LENGTH, Easing.OutQuint);
box.FadeTo(Enabled.Value & mouseDown ? 0.3f : 0f, Footer.TRANSITION_LENGTH * 2, Easing.OutQuint);
if (Enabled.Value && IsHovered)
Hovered?.Invoke();
else
HoverLost?.Invoke();
}
} }
} }

View File

@ -112,6 +112,8 @@ namespace osu.Game.Screens.Select
protected BeatmapDetailArea BeatmapDetails { get; private set; } protected BeatmapDetailArea BeatmapDetails { get; private set; }
private FooterButtonOptions beatmapOptionsButton;
private readonly Bindable<RulesetInfo> decoupledRuleset = new Bindable<RulesetInfo>(); private readonly Bindable<RulesetInfo> decoupledRuleset = new Bindable<RulesetInfo>();
private double audioFeedbackLastPlaybackTime; private double audioFeedbackLastPlaybackTime;
@ -314,7 +316,7 @@ namespace osu.Game.Screens.Select
NextRandom = () => Carousel.SelectNextRandom(), NextRandom = () => Carousel.SelectNextRandom(),
PreviousRandom = Carousel.SelectPreviousRandom PreviousRandom = Carousel.SelectPreviousRandom
}, null), }, null),
(new FooterButtonOptions(), BeatmapOptions) (beatmapOptionsButton = new FooterButtonOptions(), BeatmapOptions)
}; };
protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(); protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay();
@ -739,6 +741,16 @@ namespace osu.Game.Screens.Select
beatmapInfoWedge.Beatmap = beatmap; beatmapInfoWedge.Beatmap = beatmap;
BeatmapDetails.Beatmap = beatmap; BeatmapDetails.Beatmap = beatmap;
bool beatmapSelected = beatmap is not DummyWorkingBeatmap;
if (beatmapSelected)
beatmapOptionsButton.Enabled.Value = true;
else
{
beatmapOptionsButton.Enabled.Value = false;
BeatmapOptions.Hide();
}
} }
private readonly WeakReference<ITrack> lastTrack = new WeakReference<ITrack>(null); private readonly WeakReference<ITrack> lastTrack = new WeakReference<ITrack>(null);

View File

@ -82,21 +82,14 @@ namespace osu.Game.Skinning
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
{ {
// Temporary until default skin has a valid hit lighting.
if ((lookup as SkinnableSprite.SpriteComponentLookup)?.LookupName == @"lighting") return Drawable.Empty();
if (base.GetDrawableComponent(lookup) is Drawable c) if (base.GetDrawableComponent(lookup) is Drawable c)
return c; return c;
switch (lookup) switch (lookup)
{ {
case SkinnableSprite.SpriteComponentLookup spriteLookup:
switch (spriteLookup.LookupName)
{
// Temporary until default skin has a valid hit lighting.
case @"lighting":
return Drawable.Empty();
}
break;
case GlobalSkinComponentLookup globalLookup: case GlobalSkinComponentLookup globalLookup:
switch (globalLookup.Lookup) switch (globalLookup.Lookup)
{ {

View File

@ -396,9 +396,6 @@ namespace osu.Game.Skinning
} }
return null; return null;
case SkinnableSprite.SpriteComponentLookup sprite:
return this.GetAnimation(sprite.LookupName, false, false);
} }
return null; return null;

View File

@ -158,6 +158,10 @@ namespace osu.Game.Skinning
{ {
switch (lookup) switch (lookup)
{ {
// This fallback is important for user skins which use SkinnableSprites.
case SkinnableSprite.SpriteComponentLookup sprite:
return this.GetAnimation(sprite.LookupName, false, false);
case GlobalSkinComponentLookup target: case GlobalSkinComponentLookup target:
if (!DrawableComponentInfo.TryGetValue(target.Lookup, out var skinnableInfo)) if (!DrawableComponentInfo.TryGetValue(target.Lookup, out var skinnableInfo))
return null; return null;

View File

@ -60,21 +60,14 @@ namespace osu.Game.Skinning
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
{ {
// Temporary until default skin has a valid hit lighting.
if ((lookup as SkinnableSprite.SpriteComponentLookup)?.LookupName == @"lighting") return Drawable.Empty();
if (base.GetDrawableComponent(lookup) is Drawable c) if (base.GetDrawableComponent(lookup) is Drawable c)
return c; return c;
switch (lookup) switch (lookup)
{ {
case SkinnableSprite.SpriteComponentLookup spriteLookup:
switch (spriteLookup.LookupName)
{
// Temporary until default skin has a valid hit lighting.
case @"lighting":
return Drawable.Empty();
}
break;
case GlobalSkinComponentLookup target: case GlobalSkinComponentLookup target:
switch (target.Lookup) switch (target.Lookup)
{ {

View File

@ -35,8 +35,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="10.18.0" /> <PackageReference Include="Realm" Version="10.18.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.1110.0" /> <PackageReference Include="ppy.osu.Framework" Version="2022.1113.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1103.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2022.1113.0" />
<PackageReference Include="Sentry" Version="3.23.1" /> <PackageReference Include="Sentry" Version="3.23.1" />
<PackageReference Include="SharpCompress" Version="0.32.2" /> <PackageReference Include="SharpCompress" Version="0.32.2" />
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit" Version="3.13.3" />

View File

@ -61,8 +61,8 @@
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1103.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2022.1113.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.1110.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2022.1113.0" />
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) --> <!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
<PropertyGroup> <PropertyGroup>
@ -82,7 +82,7 @@
<PackageReference Include="DiffPlex" Version="1.7.1" /> <PackageReference Include="DiffPlex" Version="1.7.1" />
<PackageReference Include="Humanizer" Version="2.14.1" /> <PackageReference Include="Humanizer" Version="2.14.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2022.1110.0" /> <PackageReference Include="ppy.osu.Framework" Version="2022.1113.0" />
<PackageReference Include="SharpCompress" Version="0.32.2" /> <PackageReference Include="SharpCompress" Version="0.32.2" />
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />