mirror of
https://github.com/ppy/osu.git
synced 2026-05-18 06:21:22 +08:00
Merge branch 'master' into refactor-room-panels
This commit is contained in:
@@ -82,8 +82,18 @@ jobs:
|
||||
run: dotnet build -c Debug -warnaserror osu.Desktop.slnf
|
||||
|
||||
- name: Test
|
||||
run: dotnet test $pwd/**/*.Tests/bin/Debug/*/*.Tests.dll --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx" -- NUnit.ConsoleOut=0
|
||||
shell: pwsh
|
||||
run: >
|
||||
dotnet test
|
||||
osu.Game.Tests/bin/Debug/**/osu.Game.Tests.dll
|
||||
osu.Game.Rulesets.Osu.Tests/bin/Debug/**/osu.Game.Rulesets.Osu.Tests.dll
|
||||
osu.Game.Rulesets.Taiko.Tests/bin/Debug/**/osu.Game.Rulesets.Taiko.Tests.dll
|
||||
osu.Game.Rulesets.Catch.Tests/bin/Debug/**/osu.Game.Rulesets.Catch.Tests.dll
|
||||
osu.Game.Rulesets.Mania.Tests/bin/Debug/**/osu.Game.Rulesets.Mania.Tests.dll
|
||||
osu.Game.Tournament.Tests/bin/Debug/**/osu.Game.Tournament.Tests.dll
|
||||
Templates/**/*.Tests/bin/Debug/**/*.Tests.dll
|
||||
--logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx"
|
||||
--
|
||||
NUnit.ConsoleOut=0
|
||||
|
||||
# Attempt to upload results even if test fails.
|
||||
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always
|
||||
@@ -136,4 +146,4 @@ jobs:
|
||||
run: dotnet workload install ios --from-rollback-file https://raw.githubusercontent.com/ppy/osu-framework/refs/heads/master/workloads.json
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c Debug osu.iOS
|
||||
run: dotnet build -c Debug osu.iOS.slnf
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.318.1" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.321.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// 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.Linq;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
@@ -35,21 +36,21 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
[SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")]
|
||||
public BindableBool HardRockOffsets { get; } = new BindableBool();
|
||||
|
||||
public override string SettingDescription
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}";
|
||||
string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}";
|
||||
string spicyPatterns = HardRockOffsets.IsDefault ? string.Empty : "Spicy patterns";
|
||||
if (!CircleSize.IsDefault)
|
||||
yield return ("Circle size", $"{CircleSize.Value:N1}");
|
||||
|
||||
return string.Join(", ", new[]
|
||||
{
|
||||
circleSize,
|
||||
base.SettingDescription,
|
||||
approachRate,
|
||||
spicyPatterns,
|
||||
}.Where(s => !string.IsNullOrEmpty(s)));
|
||||
foreach (var setting in base.SettingDescription)
|
||||
yield return setting;
|
||||
|
||||
if (!ApproachRate.IsDefault)
|
||||
yield return ("Approach rate", $"{ApproachRate.Value:N1}");
|
||||
|
||||
if (!HardRockOffsets.IsDefault)
|
||||
yield return ("Spicy patterns", "On");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 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.Linq;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
@@ -36,19 +36,18 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
|
||||
};
|
||||
|
||||
public override string SettingDescription
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}";
|
||||
string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}";
|
||||
if (!CircleSize.IsDefault)
|
||||
yield return ("Circle size", $"{CircleSize.Value:N1}");
|
||||
|
||||
return string.Join(", ", new[]
|
||||
{
|
||||
circleSize,
|
||||
base.SettingDescription,
|
||||
approachRate
|
||||
}.Where(s => !string.IsNullOrEmpty(s)));
|
||||
foreach (var setting in base.SettingDescription)
|
||||
yield return setting;
|
||||
|
||||
if (!ApproachRate.IsDefault)
|
||||
yield return ("Approach rate", $"{ApproachRate.Value:N1}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// 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.Linq;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -19,17 +20,15 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
ReadCurrentFromDifficulty = _ => 1,
|
||||
};
|
||||
|
||||
public override string SettingDescription
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
string scrollSpeed = ScrollSpeed.IsDefault ? string.Empty : $"Scroll x{ScrollSpeed.Value:N2}";
|
||||
foreach (var setting in base.SettingDescription)
|
||||
yield return setting;
|
||||
|
||||
return string.Join(", ", new[]
|
||||
{
|
||||
base.SettingDescription,
|
||||
scrollSpeed
|
||||
}.Where(s => !string.IsNullOrEmpty(s)));
|
||||
if (!ScrollSpeed.IsDefault)
|
||||
yield return ("Scroll speed", $"x{ScrollSpeed.Value:N2}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,11 +60,17 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
{
|
||||
const double animation_time = 120;
|
||||
|
||||
(sprite as IFramedAnimation)?.GotoFrame(0);
|
||||
var animation = sprite as IFramedAnimation;
|
||||
|
||||
animation?.GotoFrame(0);
|
||||
(strongSprite as IFramedAnimation)?.GotoFrame(0);
|
||||
|
||||
this.FadeInFromZero(animation_time).Then().FadeOut(animation_time * 1.5);
|
||||
|
||||
// legacy judgements don't play any transforms if they are an animation.
|
||||
if (animation?.FrameCount > 1)
|
||||
return;
|
||||
|
||||
this.ScaleTo(0.6f)
|
||||
.Then().ScaleTo(1.1f, animation_time * 0.8)
|
||||
.Then().ScaleTo(0.9f, animation_time * 0.4)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\osu.iOS.props" />
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0-ios</TargetFramework>
|
||||
@@ -6,11 +7,19 @@
|
||||
<RootNamespace>osu.Game.Tests</RootNamespace>
|
||||
<AssemblyName>osu.Game.Tests.iOS</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\osu.iOS.props" />
|
||||
<PropertyGroup>
|
||||
<NoWarn>$(NoWarn);CA2007</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\osu.Game.Tests\**\*.cs" Exclude="**\obj\**">
|
||||
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
</Compile>
|
||||
<!-- TargetPath is relative to RootNamespace,
|
||||
and DllResourceStore is relative to AssemblyName. -->
|
||||
<EmbeddedResource Include="..\osu.Game.Tests\**\Resources\**\*">
|
||||
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
<TargetPath>iOS\%(RecursiveDir)%(Filename)%(Extension)</TargetPath>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Project References">
|
||||
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
||||
|
||||
@@ -62,12 +62,11 @@ namespace osu.Game.Tests.Database
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("Run background processor", () =>
|
||||
{
|
||||
Add(new TestBackgroundDataStoreProcessor());
|
||||
});
|
||||
TestBackgroundDataStoreProcessor processor = null!;
|
||||
AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor()));
|
||||
AddUntilStep("Wait for completion", () => processor.Completed);
|
||||
|
||||
AddUntilStep("wait for difficulties repopulated", () =>
|
||||
AddAssert("Difficulties repopulated", () =>
|
||||
{
|
||||
return Realm.Run(r =>
|
||||
{
|
||||
@@ -101,13 +100,10 @@ namespace osu.Game.Tests.Database
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("Run background processor", () =>
|
||||
{
|
||||
Add(new TestBackgroundDataStoreProcessor());
|
||||
});
|
||||
TestBackgroundDataStoreProcessor processor = null!;
|
||||
AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor()));
|
||||
|
||||
AddWaitStep("wait some", 500);
|
||||
|
||||
AddAssert("Difficulty still not populated", () =>
|
||||
{
|
||||
return Realm.Run(r =>
|
||||
@@ -118,8 +114,9 @@ namespace osu.Game.Tests.Database
|
||||
});
|
||||
|
||||
AddStep("Set not playing", () => isPlaying.Value = LocalUserPlayingState.NotPlaying);
|
||||
AddUntilStep("Wait for completion", () => processor.Completed);
|
||||
|
||||
AddUntilStep("wait for difficulties repopulated", () =>
|
||||
AddAssert("Difficulties repopulated", () =>
|
||||
{
|
||||
return Realm.Run(r =>
|
||||
{
|
||||
@@ -151,9 +148,11 @@ namespace osu.Game.Tests.Database
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor()));
|
||||
TestBackgroundDataStoreProcessor processor = null!;
|
||||
AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor()));
|
||||
AddUntilStep("Wait for completion", () => processor.Completed);
|
||||
|
||||
AddUntilStep("Score version upgraded", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(LegacyScoreEncoder.LATEST_VERSION));
|
||||
AddAssert("Score version upgraded", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(LegacyScoreEncoder.LATEST_VERSION));
|
||||
AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False);
|
||||
}
|
||||
|
||||
@@ -183,7 +182,7 @@ namespace osu.Game.Tests.Database
|
||||
AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor()));
|
||||
AddUntilStep("Wait for completion", () => processor.Completed);
|
||||
|
||||
AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.True);
|
||||
AddAssert("Score marked as failed", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.True);
|
||||
AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(scoreVersion));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osuTK;
|
||||
@@ -15,16 +13,18 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
{
|
||||
public partial class TestSceneDifficultySpectrumDisplay : OsuTestScene
|
||||
{
|
||||
private DifficultySpectrumDisplay display;
|
||||
private DifficultySpectrumDisplay display = null!;
|
||||
|
||||
private static APIBeatmapSet createBeatmapSetWith(params (int rulesetId, double stars)[] difficulties) => new APIBeatmapSet
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
Beatmaps = difficulties.Select(difficulty => new APIBeatmap
|
||||
AddStep("create spectrum display", () => Child = display = new DifficultySpectrumDisplay
|
||||
{
|
||||
RulesetID = difficulty.rulesetId,
|
||||
StarRating = difficulty.stars
|
||||
}).ToArray()
|
||||
};
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(3)
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSingleRuleset()
|
||||
@@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
(rulesetId: 0, stars: 3.2),
|
||||
(rulesetId: 0, stars: 5.6));
|
||||
|
||||
createDisplay(beatmapSet);
|
||||
AddStep("set beatmap to display", () => display.BeatmapSet = beatmapSet);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
(rulesetId: 1, stars: 4.3),
|
||||
(rulesetId: 0, stars: 5.6));
|
||||
|
||||
createDisplay(beatmapSet);
|
||||
AddStep("set beatmap to display", () => display.BeatmapSet = beatmapSet);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -61,52 +61,30 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
(rulesetId: 0, stars: 5.6),
|
||||
(rulesetId: 15, stars: 7.8));
|
||||
|
||||
createDisplay(beatmapSet);
|
||||
AddStep("set beatmap to display", () => display.BeatmapSet = beatmapSet);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMaximumUncollapsed()
|
||||
{
|
||||
var beatmapSet = createBeatmapSetWith(Enumerable.Range(0, 12).Select(i => (rulesetId: i % 4, stars: 2.5 + i * 0.25)).ToArray());
|
||||
createDisplay(beatmapSet);
|
||||
AddStep("set beatmap to display", () => display.BeatmapSet = beatmapSet);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMinimumCollapsed()
|
||||
{
|
||||
var beatmapSet = createBeatmapSetWith(Enumerable.Range(0, 13).Select(i => (rulesetId: i % 4, stars: 2.5 + i * 0.25)).ToArray());
|
||||
createDisplay(beatmapSet);
|
||||
AddStep("set beatmap to display", () => display.BeatmapSet = beatmapSet);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAdjustableDotSize()
|
||||
private static APIBeatmapSet createBeatmapSetWith(params (int rulesetId, double stars)[] difficulties) => new APIBeatmapSet
|
||||
{
|
||||
var beatmapSet = createBeatmapSetWith(
|
||||
(rulesetId: 0, stars: 2.0),
|
||||
(rulesetId: 3, stars: 2.3),
|
||||
(rulesetId: 0, stars: 3.2),
|
||||
(rulesetId: 1, stars: 4.3),
|
||||
(rulesetId: 0, stars: 5.6));
|
||||
|
||||
createDisplay(beatmapSet);
|
||||
|
||||
AddStep("change dot dimensions", () =>
|
||||
Beatmaps = difficulties.Select(difficulty => new APIBeatmap
|
||||
{
|
||||
display.DotSize = new Vector2(8, 12);
|
||||
display.DotSpacing = 2;
|
||||
});
|
||||
AddStep("change dot dimensions back", () =>
|
||||
{
|
||||
display.DotSize = new Vector2(4, 8);
|
||||
display.DotSpacing = 1;
|
||||
});
|
||||
}
|
||||
|
||||
private void createDisplay(IBeatmapSetInfo beatmapSetInfo) => AddStep("create spectrum display", () => Child = display = new DifficultySpectrumDisplay(beatmapSetInfo)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(3)
|
||||
});
|
||||
RulesetID = difficulty.rulesetId,
|
||||
StarRating = difficulty.stars
|
||||
}).ToArray()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,15 +101,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
PlaylistItem item = null!;
|
||||
|
||||
AddStep("reset state", () =>
|
||||
{
|
||||
multiplayerClient.Invocations.Clear();
|
||||
|
||||
beatmapAvailability.Value = BeatmapAvailability.LocallyAvailable();
|
||||
|
||||
item = new PlaylistItem(Beatmap.Value.BeatmapInfo)
|
||||
PlaylistItem item = new PlaylistItem(Beatmap.Value.BeatmapInfo)
|
||||
{
|
||||
RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID
|
||||
};
|
||||
@@ -127,7 +125,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
multiplayerRoom = new MultiplayerRoom(0)
|
||||
{
|
||||
Playlist = { TestMultiplayerClient.CreateMultiplayerPlaylistItem(item) },
|
||||
Playlist = { new MultiplayerPlaylistItem(item) },
|
||||
Users = { localUser },
|
||||
Host = localUser,
|
||||
};
|
||||
@@ -139,8 +137,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(250, 50),
|
||||
SelectedItem = new Bindable<PlaylistItem?>(item)
|
||||
Size = new Vector2(250, 50)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -924,7 +924,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
enterGameplay();
|
||||
AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 }));
|
||||
AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, TestMultiplayerClient.CreateMultiplayerPlaylistItem(
|
||||
AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(
|
||||
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
|
||||
{
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
@@ -956,7 +956,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
enterGameplay();
|
||||
|
||||
AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 }));
|
||||
AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, TestMultiplayerClient.CreateMultiplayerPlaylistItem(
|
||||
AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(
|
||||
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
|
||||
{
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
@@ -29,10 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 50,
|
||||
Child = new MultiplayerMatchFooter
|
||||
{
|
||||
SelectedItem = new Bindable<PlaylistItem?>()
|
||||
}
|
||||
Child = new MultiplayerMatchFooter()
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -220,7 +220,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
/// </summary>
|
||||
private void addItemStep(bool expired = false, int? userId = null) => AddStep("add item", () =>
|
||||
{
|
||||
MultiplayerClient.AddUserPlaylistItem(userId ?? API.LocalUser.Value.OnlineID, TestMultiplayerClient.CreateMultiplayerPlaylistItem(new PlaylistItem(importedBeatmap)
|
||||
MultiplayerClient.AddUserPlaylistItem(userId ?? API.LocalUser.Value.OnlineID, new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap)
|
||||
{
|
||||
Expired = expired,
|
||||
PlayedAt = DateTimeOffset.Now
|
||||
|
||||
@@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("add playlist item", () =>
|
||||
{
|
||||
MultiplayerPlaylistItem item = TestMultiplayerClient.CreateMultiplayerPlaylistItem(new PlaylistItem(importedBeatmap));
|
||||
MultiplayerPlaylistItem item = new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap));
|
||||
|
||||
MultiplayerClient.AddUserPlaylistItem(userId(), item).WaitSafely();
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@@ -71,15 +70,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(200, 50),
|
||||
SelectedItem = new Bindable<PlaylistItem?>(room.Playlist.First())
|
||||
Size = new Vector2(200, 50)
|
||||
},
|
||||
startControl = new MatchStartControl
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(200, 50),
|
||||
SelectedItem = new Bindable<PlaylistItem?>(room.Playlist.First())
|
||||
Size = new Vector2(200, 50)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
Username = "flyte",
|
||||
Id = 3103765,
|
||||
IsOnline = true,
|
||||
WasRecentlyOnline = true,
|
||||
Statistics = new UserStatistics { GlobalRank = 1111 },
|
||||
CountryCode = CountryCode.JP,
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
|
||||
@@ -243,7 +243,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
Username = "peppy",
|
||||
Id = 2,
|
||||
IsOnline = false,
|
||||
WasRecentlyOnline = false,
|
||||
Statistics = new UserStatistics { GlobalRank = 2222 },
|
||||
CountryCode = CountryCode.AU,
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
@@ -256,7 +256,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Id = 8195163,
|
||||
CountryCode = CountryCode.BY,
|
||||
CoverUrl = "https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
|
||||
IsOnline = false,
|
||||
WasRecentlyOnline = false,
|
||||
LastVisit = DateTimeOffset.Now
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.Containers.Markdown;
|
||||
using osu.Game.Overlays.Comments;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public partial class TestSceneImageProxying : OsuTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestExternalImageLink()
|
||||
{
|
||||
MarkdownContainer markdown = null!;
|
||||
|
||||
// use base MarkdownContainer as a method of directly attempting to load an image without proxying logic.
|
||||
AddStep("load external without proxying", () => Child = markdown = new MarkdownContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Text = "",
|
||||
});
|
||||
AddWaitStep("wait", 5);
|
||||
AddAssert("image not loaded", () => markdown.ChildrenOfType<Sprite>().SingleOrDefault()?.Texture == null);
|
||||
|
||||
AddStep("load external with proxying", () => Child = markdown = new OsuMarkdownContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Text = "",
|
||||
});
|
||||
AddUntilStep("image loaded", () => markdown.ChildrenOfType<Sprite>().SingleOrDefault()?.Texture != null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExternalImageLinkInComments()
|
||||
{
|
||||
MarkdownContainer markdown = null!;
|
||||
|
||||
AddStep("load external with proxying", () => Child = markdown = new CommentMarkdownContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Text = "",
|
||||
});
|
||||
AddUntilStep("image loaded", () => markdown.ChildrenOfType<Sprite>().SingleOrDefault()?.Texture != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
CountryCode = countryCode,
|
||||
CoverUrl = cover,
|
||||
Colour = color ?? "000000",
|
||||
IsOnline = true
|
||||
WasRecentlyOnline = true
|
||||
};
|
||||
|
||||
return new ClickableAvatar(user, showPanel)
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Id = 3103765,
|
||||
CountryCode = CountryCode.JP,
|
||||
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
|
||||
IsOnline = true
|
||||
WasRecentlyOnline = true
|
||||
}) { Width = 300 },
|
||||
new UserGridPanel(new APIUser
|
||||
{
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Id = 1001,
|
||||
Username = "IAmOnline",
|
||||
LastVisit = DateTimeOffset.Now,
|
||||
IsOnline = true,
|
||||
WasRecentlyOnline = true,
|
||||
}, new OsuRuleset().RulesetInfo));
|
||||
|
||||
AddStep("Show offline user", () => header.User.Value = new UserProfileData(new APIUser
|
||||
@@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Id = 1002,
|
||||
Username = "IAmOffline",
|
||||
LastVisit = DateTimeOffset.Now.AddDays(-10),
|
||||
IsOnline = false,
|
||||
WasRecentlyOnline = false,
|
||||
}, new OsuRuleset().RulesetInfo));
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,40 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
displayUpdate(statistics, statistics);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFromNothing()
|
||||
{
|
||||
createDisplay();
|
||||
displayUpdate(
|
||||
new UserStatistics(),
|
||||
new UserStatistics
|
||||
{
|
||||
GlobalRank = 12_345,
|
||||
Accuracy = 98.99,
|
||||
MaxCombo = 2_322,
|
||||
RankedScore = 23_123_543_456,
|
||||
TotalScore = 123_123_543_456,
|
||||
PP = 5_072
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestToNothing()
|
||||
{
|
||||
createDisplay();
|
||||
displayUpdate(
|
||||
new UserStatistics
|
||||
{
|
||||
GlobalRank = 12_345,
|
||||
Accuracy = 98.99,
|
||||
MaxCombo = 2_322,
|
||||
RankedScore = 23_123_543_456,
|
||||
TotalScore = 123_123_543_456,
|
||||
PP = 5_072
|
||||
},
|
||||
new UserStatistics());
|
||||
}
|
||||
|
||||
private void createDisplay() => AddStep("create display", () =>
|
||||
{
|
||||
statisticsUpdate.Value = null;
|
||||
|
||||
@@ -8,11 +8,15 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
@@ -24,8 +28,10 @@ using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking.Statistics;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@@ -42,6 +48,22 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||
|
||||
private ScoreManager scoreManager = null!;
|
||||
private RulesetStore rulesetStore = null!;
|
||||
private BeatmapManager beatmapManager = null!;
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm));
|
||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, API));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScoreWithPositionStatistics()
|
||||
{
|
||||
@@ -162,6 +184,24 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
|
||||
setUpTaggingRequests(() => score.BeatmapInfo);
|
||||
AddStep("load panel", () =>
|
||||
{
|
||||
Child = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new StatisticsPanel
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { Value = Visibility.Visible },
|
||||
Score = { Value = score },
|
||||
AchievedScore = score,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private void setUpTaggingRequests(Func<BeatmapInfo> beatmap) =>
|
||||
AddStep("set up network requests", () =>
|
||||
{
|
||||
dummyAPI.HandleRequest = request =>
|
||||
@@ -175,7 +215,11 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
Tags =
|
||||
[
|
||||
new APITag { Id = 1, Name = "tech", Description = "Tests uncommon skills.", },
|
||||
new APITag { Id = 2, Name = "alt", Description = "Colloquial term for maps which use rhythms that encourage the player to alternate notes. Typically distinct from burst or stream maps.", },
|
||||
new APITag
|
||||
{
|
||||
Id = 2, Name = "alt",
|
||||
Description = "Colloquial term for maps which use rhythms that encourage the player to alternate notes. Typically distinct from burst or stream maps.",
|
||||
},
|
||||
new APITag { Id = 3, Name = "aim", Description = "Category for difficulty relating to cursor movement.", },
|
||||
new APITag { Id = 4, Name = "tap", Description = "Category for difficulty relating to tapping input.", },
|
||||
]
|
||||
@@ -185,7 +229,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
|
||||
case GetBeatmapSetRequest getBeatmapSetRequest:
|
||||
{
|
||||
var beatmapSet = CreateAPIBeatmapSet(score.BeatmapInfo);
|
||||
var beatmapSet = CreateAPIBeatmapSet(beatmap.Invoke());
|
||||
beatmapSet.Beatmaps.Single().TopTags =
|
||||
[
|
||||
new APIBeatmapTag { TagId = 3, VoteCount = 9 },
|
||||
@@ -205,21 +249,6 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
return false;
|
||||
};
|
||||
});
|
||||
AddStep("load panel", () =>
|
||||
{
|
||||
Child = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new StatisticsPanel
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { Value = Visibility.Visible },
|
||||
Score = { Value = score },
|
||||
AchievedScore = score,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTaggingWhenRankTooLow()
|
||||
@@ -243,6 +272,100 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTaggingConvert()
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.Ruleset = new ManiaRuleset().RulesetInfo;
|
||||
|
||||
AddStep("load panel", () =>
|
||||
{
|
||||
Child = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new StatisticsPanel
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { Value = Visibility.Visible },
|
||||
Score = { Value = score },
|
||||
AchievedScore = score,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTaggingInteractionWithLocalScores()
|
||||
{
|
||||
BeatmapInfo beatmapInfo = null!;
|
||||
|
||||
AddStep(@"Import beatmap", () =>
|
||||
{
|
||||
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
|
||||
});
|
||||
|
||||
AddStep("import bad score", () =>
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.BeatmapInfo = beatmapInfo;
|
||||
score.BeatmapHash = beatmapInfo.Hash;
|
||||
score.Ruleset = beatmapInfo.Ruleset;
|
||||
score.Rank = ScoreRank.D;
|
||||
score.User = API.LocalUser.Value;
|
||||
scoreManager.Import(score);
|
||||
});
|
||||
|
||||
AddStep("import score by another user", () =>
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.BeatmapInfo = beatmapInfo;
|
||||
score.BeatmapHash = beatmapInfo.Hash;
|
||||
score.Ruleset = beatmapInfo.Ruleset;
|
||||
score.Rank = ScoreRank.D;
|
||||
score.User = new APIUser { Username = "notme", Id = 5678 };
|
||||
scoreManager.Import(score);
|
||||
});
|
||||
|
||||
AddStep("import convert score", () =>
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.BeatmapInfo = beatmapInfo;
|
||||
score.BeatmapHash = beatmapInfo.Hash;
|
||||
score.Ruleset = new OsuRuleset().RulesetInfo;
|
||||
score.User = API.LocalUser.Value;
|
||||
scoreManager.Import(score);
|
||||
});
|
||||
|
||||
AddStep("import correct score", () =>
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.BeatmapInfo = beatmapInfo;
|
||||
score.BeatmapHash = beatmapInfo.Hash;
|
||||
score.Ruleset = beatmapInfo.Ruleset;
|
||||
score.User = API.LocalUser.Value;
|
||||
scoreManager.Import(score);
|
||||
});
|
||||
|
||||
setUpTaggingRequests(() => beatmapInfo);
|
||||
AddStep("load panel", () =>
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.BeatmapInfo = beatmapInfo;
|
||||
|
||||
Child = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new StatisticsPanel
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { Value = Visibility.Visible },
|
||||
Score = { Value = score },
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private void loadPanel(ScoreInfo score) => AddStep("load panel", () =>
|
||||
{
|
||||
Child = new StatisticsPanel
|
||||
|
||||
@@ -993,7 +993,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddAssert("column not scrolled", () => modSelectOverlay.ChildrenOfType<ModSelectOverlay.ColumnScrollContainer>().Single().IsScrolledToStart());
|
||||
|
||||
AddStep("move mouse away", () => InputManager.MoveMouseTo(Vector2.Zero));
|
||||
AddAssert("customisation panel closed",
|
||||
AddUntilStep("customisation panel closed",
|
||||
() => this.ChildrenOfType<ModCustomisationPanel>().Single().ExpandedState.Value,
|
||||
() => Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed));
|
||||
|
||||
@@ -1018,7 +1018,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private void assertCustomisationToggleState(bool disabled, bool active)
|
||||
{
|
||||
AddUntilStep($"customisation panel is {(disabled ? "" : "not ")}disabled", () => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single().Enabled.Value == !disabled);
|
||||
AddAssert($"customisation panel is {(active ? "" : "not ")}active",
|
||||
AddUntilStep($"customisation panel is {(active ? "" : "not ")}active",
|
||||
() => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single().ExpandedState.Value,
|
||||
() => active ? Is.Not.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed) : Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed));
|
||||
}
|
||||
|
||||
@@ -196,6 +196,37 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddAssert("external overlay content still not shown", () => this.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent, () => Is.Not.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestButtonResizedAfterFooterIsDisplayed()
|
||||
{
|
||||
TestShearedOverlayContainer externalOverlay = null!;
|
||||
|
||||
AddStep("add overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer()));
|
||||
AddStep("set buttons", () => screenFooter.SetButtons(new[]
|
||||
{
|
||||
new ScreenFooterButton(externalOverlay)
|
||||
{
|
||||
AccentColour = Dependencies.Get<OsuColour>().Orange1,
|
||||
Icon = FontAwesome.Solid.Toolbox,
|
||||
Text = "One",
|
||||
},
|
||||
new ScreenFooterButton { Text = "Two", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Three", Action = () => { } },
|
||||
}));
|
||||
AddWaitStep("wait for transition", 3);
|
||||
|
||||
AddStep("show overlay", () => externalOverlay.Show());
|
||||
AddAssert("content displayed in footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().Single().IsPresent);
|
||||
AddUntilStep("other buttons hidden", () => screenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.Child.Parent!.Y > 0));
|
||||
|
||||
AddStep("resize active button", () => this.ChildrenOfType<ScreenFooterButton>().First().ResizeWidthTo(240, 300, Easing.OutQuint));
|
||||
AddStep("resize active button back", () => this.ChildrenOfType<ScreenFooterButton>().First().ResizeWidthTo(116, 300, Easing.OutQuint));
|
||||
|
||||
AddStep("hide overlay", () => externalOverlay.Hide());
|
||||
AddUntilStep("content hidden from footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent != true);
|
||||
AddUntilStep("other buttons returned", () => screenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.ChildrenOfType<Container>().First().Y == 0));
|
||||
}
|
||||
|
||||
private partial class TestShearedOverlayContainer : ShearedOverlayContainer
|
||||
{
|
||||
public TestShearedOverlayContainer()
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneShearedDropdown : ThemeComparisonTestScene
|
||||
{
|
||||
public TestSceneShearedDropdown()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent() => new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.Black.Opacity(0.75f),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new ShearedDropdown<string>("Test")
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Y = 300f,
|
||||
Width = 140,
|
||||
Current = new Bindable<string>(),
|
||||
Items = new[] { "Global", "Friends", "Local", "Really lonnnnnnng option" },
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,9 @@ using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online;
|
||||
|
||||
namespace osu.Game.Audio
|
||||
{
|
||||
@@ -30,7 +30,7 @@ namespace osu.Game.Audio
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audioManager)
|
||||
{
|
||||
trackStore = audioManager.GetTrackStore(new OnlineStore());
|
||||
trackStore = audioManager.GetTrackStore(new TrustedDomainOnlineStore());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -125,9 +125,10 @@ namespace osu.Game.Beatmaps
|
||||
/// <summary>
|
||||
/// Reset any fetched online linking information (and history).
|
||||
/// </summary>
|
||||
public void ResetOnlineInfo()
|
||||
public void ResetOnlineInfo(bool resetOnlineId = true)
|
||||
{
|
||||
OnlineID = -1;
|
||||
if (resetOnlineId)
|
||||
OnlineID = -1;
|
||||
LastOnlineUpdate = null;
|
||||
OnlineMD5Hash = string.Empty;
|
||||
if (Status != BeatmapOnlineStatus.LocallyModified)
|
||||
|
||||
@@ -36,11 +36,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
Origin = Anchor.CentreLeft,
|
||||
TextSize = 13f
|
||||
},
|
||||
new DifficultySpectrumDisplay(beatmapSet)
|
||||
new DifficultySpectrumDisplay
|
||||
{
|
||||
BeatmapSet = beatmapSet,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
DotSize = new Vector2(5, 10)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
@@ -18,34 +17,6 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
public partial class DifficultySpectrumDisplay : CompositeDrawable
|
||||
{
|
||||
private Vector2 dotSize = new Vector2(4, 8);
|
||||
|
||||
public Vector2 DotSize
|
||||
{
|
||||
get => dotSize;
|
||||
set
|
||||
{
|
||||
dotSize = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private float dotSpacing = 1;
|
||||
|
||||
public float DotSpacing
|
||||
{
|
||||
get => dotSpacing;
|
||||
set
|
||||
{
|
||||
dotSpacing = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private IBeatmapSetInfo? beatmapSet;
|
||||
|
||||
public IBeatmapSetInfo? BeatmapSet
|
||||
@@ -60,9 +31,12 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
private readonly FillFlowContainer<RulesetDifficultyGroup> flow;
|
||||
private FillFlowContainer<RulesetDifficultyGroup> flow = null!;
|
||||
|
||||
public DifficultySpectrumDisplay(IBeatmapSetInfo? beatmapSet = null)
|
||||
private const int max_difficulties_before_collapsing = 12;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
@@ -72,8 +46,6 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Spacing = new Vector2(10, 0),
|
||||
Direction = FillDirection.Horizontal,
|
||||
};
|
||||
|
||||
BeatmapSet = beatmapSet;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -84,36 +56,70 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
flow.Clear();
|
||||
foreach (var group in flow)
|
||||
group.Alpha = 0;
|
||||
|
||||
if (beatmapSet == null)
|
||||
{
|
||||
foreach (var group in flow)
|
||||
group.Beatmaps = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// matching web: https://github.com/ppy/osu-web/blob/d06d8c5e735eb1f48799b1654b528e9a7afb0a35/resources/assets/lib/beatmapset-panel.tsx#L127
|
||||
bool collapsed = beatmapSet.Beatmaps.Count() > 12;
|
||||
bool collapsed = beatmapSet.Beatmaps.Count() > max_difficulties_before_collapsing;
|
||||
|
||||
foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset).OrderBy(group => group.Key))
|
||||
{
|
||||
flow.Add(new RulesetDifficultyGroup(rulesetGrouping.Key.OnlineID, rulesetGrouping, collapsed, dotSize)
|
||||
int rulesetId = rulesetGrouping.Key.OnlineID;
|
||||
|
||||
var group = flow.SingleOrDefault(rg => rg.RulesetId == rulesetId);
|
||||
|
||||
if (group == null)
|
||||
{
|
||||
Spacing = new Vector2(DotSpacing, 0f),
|
||||
});
|
||||
group = new RulesetDifficultyGroup(rulesetId);
|
||||
flow.Add(group);
|
||||
flow.SetLayoutPosition(group, rulesetId);
|
||||
}
|
||||
|
||||
group.Alpha = 1;
|
||||
group.Beatmaps = rulesetGrouping.ToArray();
|
||||
group.Collapsed = collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private partial class RulesetDifficultyGroup : FillFlowContainer
|
||||
{
|
||||
private readonly int rulesetId;
|
||||
private readonly IEnumerable<IBeatmapInfo> beatmapInfos;
|
||||
private readonly bool collapsed;
|
||||
private readonly Vector2 dotSize;
|
||||
public readonly int RulesetId;
|
||||
|
||||
public RulesetDifficultyGroup(int rulesetId, IEnumerable<IBeatmapInfo> beatmapInfos, bool collapsed, Vector2 dotSize)
|
||||
private IBeatmapInfo[] beatmaps = [];
|
||||
|
||||
public IBeatmapInfo[] Beatmaps
|
||||
{
|
||||
this.rulesetId = rulesetId;
|
||||
this.beatmapInfos = beatmapInfos;
|
||||
this.collapsed = collapsed;
|
||||
this.dotSize = dotSize;
|
||||
set
|
||||
{
|
||||
beatmaps = value.OrderBy(bi => bi.StarRating).ToArray();
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private bool collapsed;
|
||||
|
||||
public bool Collapsed
|
||||
{
|
||||
get => collapsed;
|
||||
set
|
||||
{
|
||||
collapsed = value;
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private OsuSpriteText countText = null!;
|
||||
|
||||
public RulesetDifficultyGroup(int rulesetId)
|
||||
{
|
||||
RulesetId = rulesetId;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@@ -123,53 +129,83 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Spacing = new Vector2(1, 0);
|
||||
Direction = FillDirection.Horizontal;
|
||||
|
||||
var icon = rulesets.GetRuleset(rulesetId)?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle };
|
||||
var icon = rulesets.GetRuleset(RulesetId)?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle };
|
||||
Add(icon.With(i =>
|
||||
{
|
||||
i.Size = new Vector2(14);
|
||||
i.Anchor = i.Origin = Anchor.Centre;
|
||||
}));
|
||||
|
||||
if (!collapsed)
|
||||
for (int i = 0; i < max_difficulties_before_collapsing; i++)
|
||||
Add(new DifficultyDot());
|
||||
|
||||
Add(countText = new OsuSpriteText
|
||||
{
|
||||
foreach (var beatmapInfo in beatmapInfos.OrderBy(bi => bi.StarRating))
|
||||
Add(new DifficultyDot(beatmapInfo.StarRating, dotSize));
|
||||
}
|
||||
else
|
||||
Font = OsuFont.Default.With(size: 12),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Padding = new MarginPadding { Bottom = 1 }
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
countText.Alpha = collapsed ? 1 : 0;
|
||||
countText.Text = beatmaps.Length.ToLocalisableString(@"N0");
|
||||
|
||||
var dots = this.OfType<DifficultyDot>().ToArray();
|
||||
|
||||
for (int i = 0; i < max_difficulties_before_collapsing; i++)
|
||||
{
|
||||
Add(new OsuSpriteText
|
||||
var dot = dots[i];
|
||||
|
||||
if (collapsed || i >= beatmaps.Length)
|
||||
{
|
||||
Text = beatmapInfos.Count().ToLocalisableString(@"N0"),
|
||||
Font = OsuFont.Default.With(size: 12),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Padding = new MarginPadding { Bottom = 1 }
|
||||
});
|
||||
dot.Alpha = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
dot.Alpha = 1;
|
||||
dot.StarDifficulty = beatmaps[i].StarRating;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private partial class DifficultyDot : CircularContainer
|
||||
private partial class DifficultyDot : Circle
|
||||
{
|
||||
private readonly double starDifficulty;
|
||||
private double starDifficulty;
|
||||
|
||||
public DifficultyDot(double starDifficulty, Vector2 dotSize)
|
||||
public double StarDifficulty
|
||||
{
|
||||
this.starDifficulty = starDifficulty;
|
||||
Size = dotSize;
|
||||
get => starDifficulty;
|
||||
set
|
||||
{
|
||||
starDifficulty = value;
|
||||
updateColour();
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Anchor = Origin = Anchor.Centre;
|
||||
Masking = true;
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.ForStarDifficulty(starDifficulty)
|
||||
};
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size = new Vector2(5, 10);
|
||||
Anchor = Origin = Anchor.Centre;
|
||||
|
||||
updateColour();
|
||||
}
|
||||
|
||||
private void updateColour()
|
||||
{
|
||||
Colour = colours.ForStarDifficulty(starDifficulty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,8 +97,9 @@ namespace osu.Game.Database
|
||||
/// 45 2024-12-23 Change beat snap divisor adjust defaults to be Ctrl+Scroll instead of Ctrl+Shift+Scroll, if not already changed by user.
|
||||
/// 46 2024-12-26 Change beat snap divisor bindings to match stable directionality ¯\_(ツ)_/¯.
|
||||
/// 47 2025-01-21 Remove right mouse button binding for absolute scroll. Never use mouse buttons (or scroll) for global actions.
|
||||
/// 48 2025-03-19 Clear online status for all qualified beatmaps (some were stuck in a qualified state due to local caching issues).
|
||||
/// </summary>
|
||||
private const int schema_version = 47;
|
||||
private const int schema_version = 48;
|
||||
|
||||
/// <summary>
|
||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||
@@ -1245,6 +1246,15 @@ namespace osu.Game.Database
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 48:
|
||||
const int qualified = (int)BeatmapOnlineStatus.Qualified;
|
||||
|
||||
var beatmaps = migration.NewRealm.All<BeatmapInfo>().Where(b => b.StatusInt == qualified);
|
||||
|
||||
foreach (var beatmap in beatmaps)
|
||||
beatmap.ResetOnlineInfo(resetOnlineId: false);
|
||||
break;
|
||||
}
|
||||
|
||||
Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms");
|
||||
|
||||
@@ -127,8 +127,6 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
{
|
||||
base.Update();
|
||||
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
|
||||
if (CreateNewTriangles)
|
||||
addTriangles(false);
|
||||
|
||||
@@ -138,6 +136,10 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
: 1;
|
||||
|
||||
float elapsedSeconds = (float)Time.Elapsed / 1000;
|
||||
|
||||
if (elapsedSeconds == 0)
|
||||
return;
|
||||
|
||||
// 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
|
||||
// dividing by triangleScale.
|
||||
@@ -157,6 +159,8 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
if (bottomPos < 0)
|
||||
parts.RemoveAt(i);
|
||||
}
|
||||
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -183,8 +187,13 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
|
||||
int currentCount = parts.Count;
|
||||
|
||||
if (AimCount - currentCount == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < AimCount - currentCount; i++)
|
||||
parts.Add(createTriangle(randomY));
|
||||
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
}
|
||||
|
||||
private TriangleParticle createTriangle(bool randomY)
|
||||
|
||||
@@ -91,12 +91,14 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
{
|
||||
base.Update();
|
||||
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
|
||||
if (CreateNewTriangles)
|
||||
addTriangles(false);
|
||||
|
||||
float elapsedSeconds = (float)Time.Elapsed / 1000;
|
||||
|
||||
if (elapsedSeconds == 0)
|
||||
return;
|
||||
|
||||
// Since position is relative, the velocity needs to scale inversely with DrawHeight.
|
||||
float movedDistance = -elapsedSeconds * Velocity * base_velocity / DrawHeight;
|
||||
|
||||
@@ -112,6 +114,8 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
if (bottomPos < 0)
|
||||
parts.RemoveAt(i);
|
||||
}
|
||||
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -138,8 +142,13 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
|
||||
int currentCount = parts.Count;
|
||||
|
||||
if (AimCount - currentCount == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < AimCount - currentCount; i++)
|
||||
parts.Add(createTriangle(randomY));
|
||||
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
}
|
||||
|
||||
private TriangleParticle createTriangle(bool randomY)
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace osu.Game.Graphics.Containers.Markdown
|
||||
public LocalisableString TooltipText { get; }
|
||||
|
||||
public OsuMarkdownImage(LinkInline linkInline)
|
||||
: base(linkInline.Url)
|
||||
: base($"https://osu.ppy.sh/media-url?url={linkInline.Url}")
|
||||
{
|
||||
TooltipText = linkInline.Title;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
#region OsuDropdownMenu
|
||||
|
||||
protected partial class OsuDropdownMenu : DropdownMenu
|
||||
public partial class OsuDropdownMenu : DropdownMenu
|
||||
{
|
||||
public override bool HandleNonPositionalInput => State == MenuState.Open;
|
||||
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
// 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.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
public partial class ShearedDropdown<T> : Dropdown<T>, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
protected override DropdownHeader CreateHeader() => new ShearedDropdownHeader();
|
||||
|
||||
protected override DropdownMenu CreateMenu() => new ShearedDropdownMenu();
|
||||
|
||||
public ShearedDropdown(LocalisableString label)
|
||||
{
|
||||
if (Header is ShearedDropdownHeader osuHeader)
|
||||
{
|
||||
osuHeader.Dropdown = this;
|
||||
osuHeader.LeftSideLabel = label;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
var header = (ShearedDropdownHeader)Header;
|
||||
var menu = (ShearedDropdownMenu)Menu;
|
||||
|
||||
menu.Padding = new MarginPadding { Left = header.LabelContainer.DrawWidth - 10f, Right = 6f };
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
if (e.Repeat) return false;
|
||||
|
||||
if (e.Action == GlobalAction.Back)
|
||||
return Back();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||
{
|
||||
}
|
||||
|
||||
protected partial class ShearedDropdownMenu : OsuDropdown<T>.OsuDropdownMenu
|
||||
{
|
||||
private readonly Vector2 shear = new Vector2(OsuGame.SHEAR, 0);
|
||||
|
||||
public new MarginPadding Padding
|
||||
{
|
||||
get => base.Padding;
|
||||
set => base.Padding = value;
|
||||
}
|
||||
|
||||
public ShearedDropdownMenu()
|
||||
{
|
||||
Shear = shear;
|
||||
Margin = new MarginPadding { Top = 5f };
|
||||
}
|
||||
|
||||
protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new ShearedMenuItem(item)
|
||||
{
|
||||
BackgroundColourHover = HoverColour,
|
||||
BackgroundColourSelected = SelectionColour
|
||||
};
|
||||
|
||||
public partial class ShearedMenuItem : DrawableOsuDropdownMenuItem
|
||||
{
|
||||
private readonly Vector2 shear = new Vector2(OsuGame.SHEAR, 0);
|
||||
|
||||
public ShearedMenuItem(MenuItem item)
|
||||
: base(item)
|
||||
{
|
||||
Foreground.Shear = -shear;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ShearedDropdownHeader : DropdownHeader
|
||||
{
|
||||
private const float corner_radius = 5f;
|
||||
|
||||
private LocalisableString label;
|
||||
|
||||
protected override LocalisableString Label
|
||||
{
|
||||
get => label;
|
||||
set
|
||||
{
|
||||
label = value;
|
||||
valueText.Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
public LocalisableString LeftSideLabel
|
||||
{
|
||||
set => labelText.Text = value;
|
||||
}
|
||||
|
||||
private readonly OsuSpriteText labelText;
|
||||
private readonly OsuSpriteText valueText;
|
||||
private readonly Box labelBox;
|
||||
private readonly SpriteIcon chevron;
|
||||
|
||||
public Container LabelContainer { get; }
|
||||
|
||||
public ShearedDropdown<T> Dropdown = null!;
|
||||
private ShearedDropdownSearchBar searchBar = null!;
|
||||
|
||||
private readonly Vector2 shear = new Vector2(OsuGame.SHEAR, 0);
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
public ShearedDropdownHeader()
|
||||
{
|
||||
Shear = shear;
|
||||
CornerRadius = corner_radius;
|
||||
Masking = true;
|
||||
|
||||
Foreground.Children = new Drawable[]
|
||||
{
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension()
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
LabelContainer = new Container
|
||||
{
|
||||
CornerRadius = corner_radius,
|
||||
Masking = true,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
labelBox = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
labelText = new OsuSpriteText
|
||||
{
|
||||
Margin = new MarginPadding { Horizontal = 10f, Vertical = 8f },
|
||||
Font = OsuFont.Torus.With(size: 16.8f, weight: FontWeight.SemiBold),
|
||||
Shear = -shear,
|
||||
},
|
||||
},
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Horizontal = 10f },
|
||||
Shear = -shear,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
valueText = new TruncatingSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Padding = new MarginPadding { Right = 15f },
|
||||
Font = OsuFont.Torus.With(size: 16.8f, weight: FontWeight.SemiBold),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
chevron = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Y = 1f,
|
||||
Icon = FontAwesome.Solid.ChevronDown,
|
||||
Size = new Vector2(10f),
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
AddInternal(LabelContainer.CreateProxy());
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
labelBox.Colour = colourProvider.Background3;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Dropdown.Menu.StateChanged += _ => updateChevron();
|
||||
SearchBar.State.ValueChanged += _ => updateColour();
|
||||
Enabled.BindValueChanged(_ => updateColour());
|
||||
updateColour();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
searchBar.Padding = new MarginPadding { Left = LabelContainer.DrawWidth };
|
||||
|
||||
// By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it.
|
||||
Background.Padding = new MarginPadding { Left = LabelContainer.DrawWidth - corner_radius };
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateColour();
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
updateColour();
|
||||
}
|
||||
|
||||
private void updateColour()
|
||||
{
|
||||
bool hovered = Enabled.Value && IsHovered;
|
||||
var hoveredColour = colourProvider.Light4;
|
||||
var unhoveredColour = colourProvider.Background5;
|
||||
|
||||
Colour = Color4.White;
|
||||
Alpha = Enabled.Value ? 1 : 0.3f;
|
||||
|
||||
if (SearchBar.State.Value == Visibility.Visible)
|
||||
{
|
||||
chevron.Colour = hovered ? hoveredColour.Lighten(0.5f) : Colour4.White;
|
||||
Background.Colour = unhoveredColour;
|
||||
}
|
||||
else
|
||||
{
|
||||
chevron.Colour = Color4.White;
|
||||
Background.Colour = hovered ? hoveredColour : unhoveredColour;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChevron()
|
||||
{
|
||||
Debug.Assert(Dropdown != null);
|
||||
bool open = Dropdown.Menu.State == MenuState.Open;
|
||||
chevron.ScaleTo(open ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override DropdownSearchBar CreateSearchBar() => searchBar = new ShearedDropdownSearchBar();
|
||||
|
||||
private partial class ShearedDropdownSearchBar : DropdownSearchBar
|
||||
{
|
||||
protected override void PopIn() => this.FadeIn();
|
||||
|
||||
protected override void PopOut() => this.FadeOut();
|
||||
|
||||
protected override TextBox CreateTextBox() => new DropdownSearchTextBox
|
||||
{
|
||||
FontSize = OsuFont.Default.Size,
|
||||
};
|
||||
|
||||
private partial class DropdownSearchTextBox : OsuTextBox
|
||||
{
|
||||
private readonly Vector2 shear = new Vector2(OsuGame.SHEAR, 0);
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider? colourProvider)
|
||||
{
|
||||
TextContainer.Shear = -shear;
|
||||
BackgroundUnfocused = colourProvider?.Background5 ?? new Color4(10, 10, 10, 255);
|
||||
BackgroundFocused = colourProvider?.Background5 ?? new Color4(10, 10, 10, 255);
|
||||
}
|
||||
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
{
|
||||
base.OnFocus(e);
|
||||
BorderThickness = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,13 @@ namespace osu.Game.Localisation.SkinComponents
|
||||
/// <summary>
|
||||
/// "Whether to show extended information for each mod."
|
||||
/// </summary>
|
||||
public static LocalisableString ShowExtendedInformationDescription => new TranslatableString(getKey(@"whether_to_show_extended_information"), @"Whether to show extended information for each mod.");
|
||||
public static LocalisableString ShowExtendedInformationDescription =>
|
||||
new TranslatableString(getKey(@"whether_to_show_extended_information"), @"Whether to show extended information for each mod.");
|
||||
|
||||
/// <summary>
|
||||
/// "Display direction"
|
||||
/// </summary>
|
||||
public static LocalisableString DisplayDirection => new TranslatableString(getKey(@"display_direction"), "Display direction");
|
||||
|
||||
/// <summary>
|
||||
/// "Expansion mode"
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online.Metadata;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API.Requests.Responses
|
||||
@@ -111,8 +112,13 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty(@"is_active")]
|
||||
public bool Active;
|
||||
|
||||
/// <summary>
|
||||
/// From osu-web's perspective, whether a user was recently online.
|
||||
/// This doesn't imply the user is online in a lazer client (may be updated from stable or web browser).
|
||||
/// Use <see cref="MetadataClient.GetPresence"/> for real-time lazer online status checks.
|
||||
/// </summary>
|
||||
[JsonProperty(@"is_online")]
|
||||
public bool IsOnline;
|
||||
public bool WasRecentlyOnline;
|
||||
|
||||
[JsonProperty(@"pm_friends_only")]
|
||||
public bool PMFriendsOnly;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@@ -219,7 +220,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
}
|
||||
};
|
||||
|
||||
string description = mod.SettingDescription;
|
||||
string description = string.Join(", ", mod.SettingDescription.Select(svp => $"{svp.setting}: {svp.value}"));
|
||||
|
||||
if (!string.IsNullOrEmpty(description))
|
||||
{
|
||||
@@ -227,7 +228,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold),
|
||||
Text = mod.SettingDescription,
|
||||
Text = description,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Top = 1 },
|
||||
|
||||
@@ -57,6 +57,9 @@ namespace osu.Game.Online.Metadata
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the presence of a user.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will return data if the client is currently receiving presence data. See <see cref="BeginWatchingUserPresence"/>.
|
||||
/// </remarks>
|
||||
/// <param name="userId">The user ID.</param>
|
||||
/// <returns>The user presence, or null if not available or the user's offline.</returns>
|
||||
public UserPresence? GetPresence(int userId)
|
||||
|
||||
@@ -44,6 +44,8 @@ namespace osu.Game.Online
|
||||
(typeof(UserActivity.EditingBeatmap), typeof(UserActivity)),
|
||||
(typeof(UserActivity.ModdingBeatmap), typeof(UserActivity)),
|
||||
(typeof(UserActivity.TestingBeatmap), typeof(UserActivity)),
|
||||
(typeof(UserActivity.InDailyChallengeLobby), typeof(UserActivity)),
|
||||
(typeof(UserActivity.PlayingDailyChallenge), typeof(UserActivity)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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.IO.Stores;
|
||||
using osu.Framework.Logging;
|
||||
|
||||
namespace osu.Game.Online
|
||||
{
|
||||
public sealed class TrustedDomainOnlineStore : OnlineStore
|
||||
{
|
||||
protected override string GetLookupUrl(string url)
|
||||
{
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? uri) || !uri.Host.EndsWith(@".ppy.sh", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.Log($@"Blocking resource lookup from external website: {url}", LoggingTarget.Network, LogLevel.Important);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,6 +108,8 @@ namespace osu.Game
|
||||
public virtual EndpointConfiguration CreateEndpoints() =>
|
||||
UseDevelopmentServer ? new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration();
|
||||
|
||||
protected override OnlineStore CreateOnlineStore() => new TrustedDomainOnlineStore();
|
||||
|
||||
public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version();
|
||||
|
||||
/// <summary>
|
||||
@@ -278,7 +280,7 @@ namespace osu.Game
|
||||
dependencies.CacheAs(Storage);
|
||||
|
||||
var largeStore = new LargeTextureStore(Host.Renderer, Host.CreateTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures")));
|
||||
largeStore.AddTextureSource(Host.CreateTextureLoaderStore(new OnlineStore()));
|
||||
largeStore.AddTextureSource(Host.CreateTextureLoaderStore(CreateOnlineStore()));
|
||||
dependencies.Cache(largeStore);
|
||||
|
||||
dependencies.CacheAs(LocalConfig);
|
||||
|
||||
@@ -249,6 +249,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
getScoresRequest = null;
|
||||
|
||||
noScoresPlaceholder.Hide();
|
||||
noTeamPlaceholder.Hide();
|
||||
notSupporterPlaceholder.Hide();
|
||||
|
||||
if (Beatmap.Value == null || Beatmap.Value.OnlineID <= 0 || (Beatmap.Value.Status <= BeatmapOnlineStatus.Pending))
|
||||
{
|
||||
@@ -271,9 +273,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
return;
|
||||
}
|
||||
|
||||
noTeamPlaceholder.Hide();
|
||||
notSupporterPlaceholder.Hide();
|
||||
|
||||
Show();
|
||||
loading.Show();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
@@ -14,6 +15,7 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@@ -22,7 +24,10 @@ using osu.Game.Localisation;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Play;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using ChatStrings = osu.Game.Localisation.ChatStrings;
|
||||
@@ -69,6 +74,12 @@ namespace osu.Game.Overlays.Chat
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private MultiplayerClient? multiplayerClient { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IPerformFromScreenRunner? performer { get; set; }
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private ChannelManager? chatManager { get; set; }
|
||||
|
||||
@@ -161,13 +172,10 @@ namespace osu.Game.Overlays.Chat
|
||||
if (user.Equals(APIUser.SYSTEM_USER))
|
||||
return Array.Empty<MenuItem>();
|
||||
|
||||
List<MenuItem> items = new List<MenuItem>
|
||||
{
|
||||
new OsuMenuItem(ContextMenuStrings.ViewProfile, MenuItemType.Highlighted, openUserProfile)
|
||||
};
|
||||
if (user.Equals(api.LocalUser.Value))
|
||||
return Array.Empty<MenuItem>();
|
||||
|
||||
if (!user.Equals(api.LocalUser.Value))
|
||||
items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, openUserChannel));
|
||||
List<MenuItem> items = new List<MenuItem>();
|
||||
|
||||
if (currentChannel?.Value != null)
|
||||
{
|
||||
@@ -177,8 +185,29 @@ namespace osu.Game.Overlays.Chat
|
||||
}));
|
||||
}
|
||||
|
||||
if (!user.Equals(api.LocalUser.Value))
|
||||
items.Add(new OsuMenuItem(UsersStrings.ReportButtonText, MenuItemType.Destructive, ReportRequested));
|
||||
items.Add(new OsuMenuItem(ContextMenuStrings.ViewProfile, MenuItemType.Highlighted, openUserProfile));
|
||||
|
||||
items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, openUserChannel));
|
||||
|
||||
// We should probably be checking against an online state here.
|
||||
// But we can't use MetadataClient.GetPresence because we may not be requesting/receiving presences.
|
||||
// This isn't really too bad – worst case scenario the client will open spectator view and show the user as "offline".
|
||||
{
|
||||
items.Add(new OsuMenuItemSpacer());
|
||||
|
||||
items.Add(new OsuMenuItem(ContextMenuStrings.SpectatePlayer, MenuItemType.Standard, () =>
|
||||
{
|
||||
performer?.PerformFromScreen(s => s.Push(new SoloSpectatorScreen(user)));
|
||||
}));
|
||||
|
||||
if (multiplayerClient?.Room?.Users.All(u => u.UserID != user.Id) == true)
|
||||
{
|
||||
items.Add(new OsuMenuItem(ContextMenuStrings.InvitePlayer, MenuItemType.Standard, () => multiplayerClient.InvitePlayer(user.Id)));
|
||||
}
|
||||
}
|
||||
|
||||
items.Add(new OsuMenuItemSpacer());
|
||||
items.Add(new OsuMenuItem(UsersStrings.ReportButtonText, MenuItemType.Destructive, ReportRequested));
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
@@ -75,17 +76,31 @@ namespace osu.Game.Overlays.Mods
|
||||
TabbableContentContainer = this,
|
||||
Current = { Value = preset.PerformRead(p => p.Description) },
|
||||
},
|
||||
new OsuScrollContainer
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 100,
|
||||
Padding = new MarginPadding(7),
|
||||
Child = scrollContent = new FillFlowContainer
|
||||
CornerRadius = 10,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding(7),
|
||||
Spacing = new Vector2(7),
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(7),
|
||||
Child = scrollContent = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding(7),
|
||||
Spacing = new Vector2(7),
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// 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.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
@@ -14,12 +15,20 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public partial class ModPresetRow : FillFlowContainer
|
||||
{
|
||||
private readonly Mod mod;
|
||||
|
||||
public ModPresetRow(Mod mod)
|
||||
{
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
Direction = FillDirection.Vertical;
|
||||
Spacing = new Vector2(4);
|
||||
Spacing = new Vector2(5);
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
@@ -39,26 +48,47 @@ namespace osu.Game.Overlays.Mods
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = mod.Name,
|
||||
Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold),
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Bottom = 2 }
|
||||
}
|
||||
Origin = Anchor.CentreLeft,
|
||||
Font = OsuFont.Torus.With(size: 16f, weight: FontWeight.SemiBold),
|
||||
Colour = colourProvider.Content1,
|
||||
UseFullGlyphHeight = false,
|
||||
Text = mod.Name,
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(mod.SettingDescription))
|
||||
{
|
||||
AddInternal(new OsuTextFlowContainer
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Left = 14 },
|
||||
Text = mod.SettingDescription
|
||||
});
|
||||
}
|
||||
Padding = new MarginPadding { Horizontal = 10f },
|
||||
Alpha = mod.SettingDescription.Any() ? 1 : 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new TextFlowContainer(t =>
|
||||
{
|
||||
t.Font = OsuFont.Torus.With(size: 12f, weight: FontWeight.SemiBold);
|
||||
})
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Content2,
|
||||
Text = string.Join('\n', mod.SettingDescription.Select(svp => svp.setting)),
|
||||
},
|
||||
new TextFlowContainer(t =>
|
||||
{
|
||||
t.Font = OsuFont.Torus.With(size: 12f, weight: FontWeight.SemiBold);
|
||||
})
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Content1,
|
||||
TextAnchor = Anchor.TopRight,
|
||||
Text = string.Join('\n', mod.SettingDescription.Select(svp => svp.value)),
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
@@ -14,6 +15,9 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public partial class ModPresetTooltip : VisibilityContainer, ITooltip<ModPreset>
|
||||
{
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider;
|
||||
|
||||
protected override Container<Drawable> Content { get; }
|
||||
|
||||
private const double transition_duration = 200;
|
||||
@@ -22,6 +26,8 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
public ModPresetTooltip(OverlayColourProvider colourProvider)
|
||||
{
|
||||
this.colourProvider = colourProvider;
|
||||
|
||||
Width = 250;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
@@ -39,7 +45,7 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 },
|
||||
Padding = new MarginPadding(10f),
|
||||
Spacing = new Vector2(7),
|
||||
Children = new[]
|
||||
{
|
||||
@@ -51,6 +57,7 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Margin = new MarginPadding { Bottom = 5f },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,7 +71,13 @@ namespace osu.Game.Overlays.Mods
|
||||
if (ReferenceEquals(preset, lastPreset))
|
||||
return;
|
||||
|
||||
descriptionText.Text = preset.Description;
|
||||
if (!string.IsNullOrEmpty(preset.Description))
|
||||
{
|
||||
descriptionText.Show();
|
||||
descriptionText.Text = preset.Description;
|
||||
}
|
||||
else
|
||||
descriptionText.Hide();
|
||||
|
||||
lastPreset = preset;
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Seasonal;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
@@ -256,8 +257,8 @@ namespace osu.Game.Overlays
|
||||
playableSet = getNextRandom(-1, allowProtectedTracks);
|
||||
else
|
||||
{
|
||||
playableSet = getBeatmapSets().TakeWhile(i => !i.Value.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Value.Protected || allowProtectedTracks)
|
||||
?? getBeatmapSets().LastOrDefault(s => !s.Value.Protected || allowProtectedTracks);
|
||||
playableSet = getBeatmapSets(allowProtectedTracks).TakeWhile(i => !i.Value.Equals(current?.BeatmapSetInfo)).LastOrDefault()
|
||||
?? getBeatmapSets(allowProtectedTracks).LastOrDefault();
|
||||
}
|
||||
|
||||
if (playableSet != null)
|
||||
@@ -352,10 +353,8 @@ namespace osu.Game.Overlays
|
||||
playableSet = getNextRandom(1, allowProtectedTracks);
|
||||
else
|
||||
{
|
||||
playableSet = getBeatmapSets().SkipWhile(i => !i.Value.Equals(current?.BeatmapSetInfo))
|
||||
.Where(i => !i.Value.Protected || allowProtectedTracks)
|
||||
.ElementAtOrDefault(1)
|
||||
?? getBeatmapSets().FirstOrDefault(i => !i.Value.Protected || allowProtectedTracks);
|
||||
playableSet = getBeatmapSets(allowProtectedTracks).SkipWhile(i => !i.Value.Equals(current?.BeatmapSetInfo)).ElementAtOrDefault(1)
|
||||
?? getBeatmapSets(allowProtectedTracks).FirstOrDefault();
|
||||
}
|
||||
|
||||
var playableBeatmap = playableSet?.Value.Beatmaps.FirstOrDefault();
|
||||
@@ -376,12 +375,13 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
Live<BeatmapSetInfo> result;
|
||||
|
||||
var possibleSets = getBeatmapSets().Where(s => !s.Value.Protected || allowProtectedTracks).ToList();
|
||||
var possibleSets = getBeatmapSets(allowProtectedTracks).ToList();
|
||||
|
||||
if (possibleSets.Count == 0)
|
||||
return null;
|
||||
|
||||
// if there is only one possible set left, play it, even if it is the same as the current track.
|
||||
// if there is only
|
||||
// one possible set left, play it, even if it is the same as the current track.
|
||||
// looping is preferable over playing nothing.
|
||||
if (possibleSets.Count == 1)
|
||||
return possibleSets.Single();
|
||||
@@ -459,9 +459,12 @@ namespace osu.Game.Overlays
|
||||
|
||||
private TrackChangeDirection? queuedDirection;
|
||||
|
||||
private IEnumerable<Live<BeatmapSetInfo>> getBeatmapSets() => realm.Realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending)
|
||||
.AsEnumerable()
|
||||
.Select(s => new RealmLive<BeatmapSetInfo>(s, realm));
|
||||
private IEnumerable<Live<BeatmapSetInfo>> getBeatmapSets(bool allowProtectedTracks) =>
|
||||
realm.Realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending)
|
||||
.AsEnumerable()
|
||||
.Select(s => new RealmLive<BeatmapSetInfo>(s, realm))
|
||||
.Where(i => (allowProtectedTracks || !i.Value.Protected)
|
||||
&& (SeasonalUIConfig.ENABLED || i.Value.Hash != IntroChristmas.CHRISTMAS_BEATMAP_SET_HASH));
|
||||
|
||||
private void changeBeatmap(WorkingBeatmap newWorking)
|
||||
{
|
||||
@@ -488,8 +491,8 @@ namespace osu.Game.Overlays
|
||||
else
|
||||
{
|
||||
// figure out the best direction based on order in playlist.
|
||||
int last = getBeatmapSets().TakeWhile(b => !b.Value.Equals(current.BeatmapSetInfo)).Count();
|
||||
int next = getBeatmapSets().TakeWhile(b => !b.Value.Equals(newWorking.BeatmapSetInfo)).Count();
|
||||
int last = getBeatmapSets(allowProtectedTracks: false).TakeWhile(b => !b.Value.Equals(current.BeatmapSetInfo)).Count();
|
||||
int next = getBeatmapSets(allowProtectedTracks: false).TakeWhile(b => !b.Value.Equals(newWorking.BeatmapSetInfo)).Count();
|
||||
|
||||
direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
|
||||
addSpacer(topLinkContainer);
|
||||
|
||||
if (user.IsOnline)
|
||||
if (user.WasRecentlyOnline)
|
||||
{
|
||||
topLinkContainer.AddText(UsersStrings.ShowLastvisitOnline);
|
||||
addSpacer(topLinkContainer);
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Layout;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@@ -29,8 +28,6 @@ namespace osu.Game.Overlays
|
||||
private const int header_height = 30;
|
||||
private const int corner_radius = 5;
|
||||
|
||||
private readonly Cached headerTextVisibilityCache = new Cached();
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
private readonly FillFlowContainer content = new FillFlowContainer
|
||||
@@ -156,13 +153,9 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!headerTextVisibilityCache.IsValid)
|
||||
{
|
||||
// These toolbox grouped may be contracted to only show icons.
|
||||
// For now, let's hide the header to avoid text truncation weirdness in such cases.
|
||||
headerText.FadeTo(headerText.DrawWidth < DrawWidth ? 1 : 0, 150, Easing.OutQuint);
|
||||
headerTextVisibilityCache.Validate();
|
||||
}
|
||||
// These toolbox grouped may be contracted to only show icons.
|
||||
// For now, let's hide the header to avoid text truncation weirdness in such cases.
|
||||
headerText.Alpha = (float)Interpolation.DampContinuously(headerText.Alpha, headerText.DrawWidth < DrawWidth ? 1 : 0, 40, Time.Elapsed);
|
||||
|
||||
// Dragged child finished its drag operation.
|
||||
if (draggedChild != null && inputManager.DraggedDrawable != draggedChild)
|
||||
@@ -172,14 +165,6 @@ namespace osu.Game.Overlays
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
|
||||
{
|
||||
if (invalidation.HasFlag(Invalidation.DrawSize))
|
||||
headerTextVisibilityCache.Invalidate();
|
||||
|
||||
return base.OnInvalidate(invalidation, source);
|
||||
}
|
||||
|
||||
private void updateExpandedState(bool animate)
|
||||
{
|
||||
// before we collapse down, let's double check the user is not dragging a UI control contained within us.
|
||||
|
||||
@@ -14,7 +14,6 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
@@ -43,36 +42,16 @@ namespace osu.Game.Rulesets.Mods
|
||||
public abstract LocalisableString Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The tooltip to display for this mod when used in a <see cref="ModIcon"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Differs from <see cref="Name"/>, as the value of attributes (AR, CS, etc) changeable via the mod
|
||||
/// are displayed in the tooltip.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public string IconTooltip
|
||||
{
|
||||
get
|
||||
{
|
||||
string description = SettingDescription;
|
||||
|
||||
return string.IsNullOrEmpty(description) ? Name : $"{Name} ({description})";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The description of editable settings of a mod to use in the <see cref="IconTooltip"/>.
|
||||
/// The description of editable settings of a mod.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Parentheses are added to the tooltip, surrounding the value of this property. If this property is <c>string.Empty</c>,
|
||||
/// the tooltip will not have parentheses.
|
||||
/// </remarks>
|
||||
public virtual string SettingDescription
|
||||
public virtual IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
var tooltipTexts = new List<string>();
|
||||
|
||||
foreach ((SettingSourceAttribute attr, PropertyInfo property) in this.GetOrderedSettingsSourceProperties())
|
||||
{
|
||||
var bindable = (IBindable)property.GetValue(this)!;
|
||||
@@ -82,7 +61,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
switch (bindable)
|
||||
{
|
||||
case Bindable<bool> b:
|
||||
valueText = b.Value ? "on" : "off";
|
||||
valueText = b.Value ? "On" : "Off";
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -91,10 +70,8 @@ namespace osu.Game.Rulesets.Mods
|
||||
}
|
||||
|
||||
if (!bindable.IsDefault)
|
||||
tooltipTexts.Add($"{attr.Label}: {valueText}");
|
||||
yield return (attr.Label, valueText);
|
||||
}
|
||||
|
||||
return string.Join(", ", tooltipTexts.Where(s => !string.IsNullOrEmpty(s)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Localisation.HUD;
|
||||
@@ -33,7 +34,20 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public override bool Ranked => true;
|
||||
|
||||
public override string SettingDescription => base.SettingDescription.Replace(MinimumAccuracy.ToString(), MinimumAccuracy.Value.ToString("##%", NumberFormatInfo.InvariantInfo));
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!MinimumAccuracy.IsDefault)
|
||||
yield return ("Minimum accuracy", $"{MinimumAccuracy.Value:##%}");
|
||||
|
||||
if (!AccuracyJudgeMode.IsDefault)
|
||||
yield return ("Accuracy mode", AccuracyJudgeMode.Value.ToLocalisableString());
|
||||
|
||||
if (!Restart.IsDefault)
|
||||
yield return ("Restart on fail", "On");
|
||||
}
|
||||
}
|
||||
|
||||
[SettingSource("Minimum accuracy", "Trigger a failure if your accuracy goes below this value.", SettingControlType = typeof(SettingsPercentageSlider<double>))]
|
||||
public BindableNumber<double> MinimumAccuracy { get; } = new BindableDouble
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -38,7 +39,14 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override LocalisableString Description => "The whole playfield is on a wheel!";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override string SettingDescription => $"{SpinSpeed.Value:N2} rpm {Direction.Value.GetDescription().ToLowerInvariant()}";
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return ("Roll speed", $"{SpinSpeed.Value:N2} rpm");
|
||||
yield return ("Direction", Direction.Value.GetDescription());
|
||||
}
|
||||
}
|
||||
|
||||
private PlayfieldAdjustmentContainer playfieldAdjustmentContainer = null!;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
@@ -65,18 +65,15 @@ namespace osu.Game.Rulesets.Mods
|
||||
}
|
||||
}
|
||||
|
||||
public override string SettingDescription
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
string drainRate = DrainRate.IsDefault ? string.Empty : $"HP {DrainRate.Value:N1}";
|
||||
string overallDifficulty = OverallDifficulty.IsDefault ? string.Empty : $"OD {OverallDifficulty.Value:N1}";
|
||||
if (!DrainRate.IsDefault)
|
||||
yield return ("HP drain", $"{DrainRate.Value:N1}");
|
||||
|
||||
return string.Join(", ", new[]
|
||||
{
|
||||
drainRate,
|
||||
overallDifficulty
|
||||
}.Where(s => !string.IsNullOrEmpty(s)));
|
||||
if (!OverallDifficulty.IsDefault)
|
||||
yield return ("Accuracy", $"{OverallDifficulty.Value:N1}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@@ -20,7 +22,15 @@ namespace osu.Game.Rulesets.Mods
|
||||
MaxValue = 10
|
||||
};
|
||||
|
||||
public override string SettingDescription => Retries.IsDefault ? string.Empty : $"{"lives".ToQuantity(Retries.Value)}";
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Retries.IsDefault)
|
||||
yield return ("Extra lives", "lives".ToQuantity(Retries.Value));
|
||||
}
|
||||
}
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModAccuracyChallenge)).ToArray();
|
||||
|
||||
private int retries;
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
@@ -24,8 +26,13 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp), typeof(ModAdaptiveSpeed), typeof(ModRateAdjust) };
|
||||
|
||||
public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x";
|
||||
|
||||
public override string ExtendedIconInformation => SettingDescription;
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!SpeedChange.IsDefault)
|
||||
yield return ("Speed change", $"{SpeedChange.Value:N2}x");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Overlays.Settings;
|
||||
@@ -34,7 +36,13 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModAdaptiveSpeed) };
|
||||
|
||||
public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x";
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return ("Speed change", $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x");
|
||||
}
|
||||
}
|
||||
|
||||
private double finalRateTime;
|
||||
private double beginRampTime;
|
||||
|
||||
@@ -10,11 +10,11 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.UI
|
||||
/// <summary>
|
||||
/// Display the specified mod at a fixed size.
|
||||
/// </summary>
|
||||
public partial class ModIcon : Container, IHasTooltip
|
||||
public partial class ModIcon : Container, IHasCustomTooltip<Mod>
|
||||
{
|
||||
public readonly BindableBool Selected = new BindableBool();
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
public static readonly Vector2 MOD_ICON_SIZE = new Vector2(80);
|
||||
|
||||
public virtual LocalisableString TooltipText => showTooltip ? ((mod as Mod)?.IconTooltip ?? mod.Name) : string.Empty;
|
||||
public Mod? TooltipContent { get; private set; }
|
||||
|
||||
private IMod mod;
|
||||
|
||||
@@ -70,6 +70,9 @@ namespace osu.Game.Rulesets.UI
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider? colourProvider { get; set; }
|
||||
|
||||
private Color4 backgroundColour;
|
||||
|
||||
private Sprite extendedBackground = null!;
|
||||
@@ -188,6 +191,7 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
modAcronym.Text = value.Acronym;
|
||||
modIcon.Icon = value.Icon ?? FontAwesome.Solid.Question;
|
||||
TooltipContent = showTooltip ? value as Mod : null;
|
||||
|
||||
if (value.Icon == null)
|
||||
{
|
||||
@@ -227,5 +231,7 @@ namespace osu.Game.Rulesets.UI
|
||||
base.Dispose(isDisposing);
|
||||
modSettingsChangeTracker?.Dispose();
|
||||
}
|
||||
|
||||
public ITooltip<Mod> GetCustomTooltip() => new ModTooltip(colourProvider);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
// 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.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
public partial class ModTooltip : VisibilityContainer, ITooltip<Mod>
|
||||
{
|
||||
private readonly OverlayColourProvider colourProvider;
|
||||
|
||||
private OsuSpriteText nameText = null!;
|
||||
private TextFlowContainer settingsLabelsFlow = null!;
|
||||
private TextFlowContainer settingsValuesFlow = null!;
|
||||
|
||||
public ModTooltip(OverlayColourProvider? colourProvider = null)
|
||||
{
|
||||
this.colourProvider = colourProvider ?? new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
CornerRadius = 7;
|
||||
Masking = true;
|
||||
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = Color4.Black.Opacity(0.2f),
|
||||
Radius = 10f,
|
||||
};
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background6,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Padding = new MarginPadding(10f),
|
||||
Spacing = new Vector2(20f, 0f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
nameText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.Torus.With(size: 16f, weight: FontWeight.SemiBold),
|
||||
Colour = colourProvider.Content1,
|
||||
UseFullGlyphHeight = false,
|
||||
},
|
||||
settingsLabelsFlow = new TextFlowContainer(t =>
|
||||
{
|
||||
t.Font = OsuFont.Torus.With(size: 12f, weight: FontWeight.SemiBold);
|
||||
})
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Content2,
|
||||
},
|
||||
},
|
||||
},
|
||||
settingsValuesFlow = new TextFlowContainer(t =>
|
||||
{
|
||||
t.Font = OsuFont.Torus.With(size: 12f, weight: FontWeight.SemiBold);
|
||||
})
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Content1,
|
||||
TextAnchor = Anchor.TopRight,
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Mod? displayedContent;
|
||||
|
||||
public void SetContent(Mod content)
|
||||
{
|
||||
if (content == displayedContent)
|
||||
return;
|
||||
|
||||
displayedContent = content;
|
||||
nameText.Text = content.Name;
|
||||
settingsLabelsFlow.Clear();
|
||||
settingsValuesFlow.Clear();
|
||||
|
||||
if (content.SettingDescription.Any())
|
||||
{
|
||||
settingsLabelsFlow.Show();
|
||||
settingsValuesFlow.Show();
|
||||
|
||||
foreach (var part in content.SettingDescription)
|
||||
{
|
||||
settingsLabelsFlow.AddText(part.setting);
|
||||
settingsLabelsFlow.NewLine();
|
||||
|
||||
settingsValuesFlow.AddText(part.value);
|
||||
settingsValuesFlow.NewLine();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
settingsLabelsFlow.Hide();
|
||||
settingsValuesFlow.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PopIn() => this.FadeIn(300, Easing.OutQuint);
|
||||
protected override void PopOut() => this.FadeOut(300, Easing.OutQuint);
|
||||
public void Move(Vector2 pos) => Position = pos;
|
||||
}
|
||||
}
|
||||
@@ -146,22 +146,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
}
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new TextFlowContainer(s => s.Font = s.Font.With(size: 14))
|
||||
{
|
||||
Padding = new MarginPadding { Horizontal = 15, Vertical = 2 },
|
||||
Text = "beat snap",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
TextAnchor = Anchor.TopCentre,
|
||||
},
|
||||
},
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.Absolute, 40),
|
||||
new Dimension(GridSizeMode.Absolute, 20),
|
||||
new Dimension(GridSizeMode.Absolute, 15)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -33,7 +33,8 @@ namespace osu.Game.Screens.Footer
|
||||
|
||||
private Box background = null!;
|
||||
private FillFlowContainer<ScreenFooterButton> buttonsFlow = null!;
|
||||
private Container<ScreenFooterButton> removedButtonsContainer = null!;
|
||||
private Container footerContentContainer = null!;
|
||||
private Container<ScreenFooterButton> hiddenButtonsContainer = null!;
|
||||
private LogoTrackingContainer logoTrackingContainer = null!;
|
||||
|
||||
[Cached]
|
||||
@@ -71,15 +72,35 @@ namespace osu.Game.Screens.Footer
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5
|
||||
},
|
||||
buttonsFlow = new FillFlowContainer<ScreenFooterButton>
|
||||
new GridContainer
|
||||
{
|
||||
Margin = new MarginPadding { Left = 12f + ScreenBackButton.BUTTON_WIDTH + padding },
|
||||
Y = 10f,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(7, 0),
|
||||
AutoSizeAxes = Axes.Both
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Left = 12f + ScreenBackButton.BUTTON_WIDTH + padding },
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
buttonsFlow = new FillFlowContainer<ScreenFooterButton>
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Y = 10f,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(7, 0),
|
||||
AutoSizeAxes = Axes.Both,
|
||||
},
|
||||
footerContentContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Y = -15f,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
BackButton = new ScreenBackButton
|
||||
{
|
||||
@@ -88,7 +109,7 @@ namespace osu.Game.Screens.Footer
|
||||
Origin = Anchor.BottomLeft,
|
||||
Action = onBackPressed,
|
||||
},
|
||||
removedButtonsContainer = new Container<ScreenFooterButton>
|
||||
hiddenButtonsContainer = new Container<ScreenFooterButton>
|
||||
{
|
||||
Margin = new MarginPadding { Left = 12f + ScreenBackButton.BUTTON_WIDTH + padding },
|
||||
Y = 10f,
|
||||
@@ -153,7 +174,7 @@ namespace osu.Game.Screens.Footer
|
||||
var oldButton = oldButtons[i];
|
||||
|
||||
buttonsFlow.Remove(oldButton, false);
|
||||
removedButtonsContainer.Add(oldButton);
|
||||
hiddenButtonsContainer.Add(oldButton);
|
||||
|
||||
if (buttons.Count > 0)
|
||||
makeButtonDisappearToRight(oldButton, i, oldButtons.Length, true);
|
||||
@@ -188,7 +209,7 @@ namespace osu.Game.Screens.Footer
|
||||
}
|
||||
|
||||
private ShearedOverlayContainer? activeOverlay;
|
||||
private Container? contentContainer;
|
||||
private VisibilityContainer? activeFooterContent;
|
||||
|
||||
private readonly List<ScreenFooterButton> temporarilyHiddenButtons = new List<ScreenFooterButton>();
|
||||
|
||||
@@ -210,33 +231,28 @@ namespace osu.Game.Screens.Footer
|
||||
? buttonsFlow.SkipWhile(b => b != targetButton).Skip(1)
|
||||
: buttonsFlow);
|
||||
|
||||
for (int i = 0; i < temporarilyHiddenButtons.Count; i++)
|
||||
makeButtonDisappearToBottom(temporarilyHiddenButtons[i], 0, 0, false);
|
||||
for (int i = temporarilyHiddenButtons.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var button = temporarilyHiddenButtons[i];
|
||||
buttonsFlow.Remove(button, false);
|
||||
hiddenButtonsContainer.Add(button);
|
||||
|
||||
var fallbackPosition = buttonsFlow.Any()
|
||||
? buttonsFlow.ToSpaceOfOtherDrawable(Vector2.Zero, this)
|
||||
: BackButton.ToSpaceOfOtherDrawable(BackButton.LayoutRectangle.TopRight + new Vector2(5f, 0f), this);
|
||||
|
||||
var targetPosition = targetButton?.ToSpaceOfOtherDrawable(targetButton.LayoutRectangle.TopRight, this) ?? fallbackPosition;
|
||||
makeButtonDisappearToBottom(button, 0, 0, false);
|
||||
}
|
||||
|
||||
updateColourScheme(overlay.ColourProvider.Hue);
|
||||
|
||||
footerContent = overlay.CreateFooterContent();
|
||||
activeFooterContent = footerContent;
|
||||
var content = footerContent;
|
||||
|
||||
var content = footerContent ?? Empty();
|
||||
|
||||
Add(contentContainer = new Container
|
||||
{
|
||||
Y = -15f,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Left = targetPosition.X },
|
||||
Child = content,
|
||||
});
|
||||
if (content != null)
|
||||
footerContentContainer.Child = content;
|
||||
|
||||
if (temporarilyHiddenButtons.Count > 0)
|
||||
this.Delay(60).Schedule(() => content.Show());
|
||||
this.Delay(60).Schedule(() => content?.Show());
|
||||
else
|
||||
content.Show();
|
||||
content?.Show();
|
||||
|
||||
return new InvokeOnDisposal(clearActiveOverlayContainer);
|
||||
}
|
||||
@@ -246,20 +262,26 @@ namespace osu.Game.Screens.Footer
|
||||
if (activeOverlay == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(contentContainer != null);
|
||||
contentContainer.Child.Hide();
|
||||
Debug.Assert(activeFooterContent != null);
|
||||
activeFooterContent.Hide();
|
||||
|
||||
double timeUntilRun = contentContainer.Child.LatestTransformEndTime - Time.Current;
|
||||
double timeUntilRun = activeFooterContent.LatestTransformEndTime - Time.Current;
|
||||
|
||||
for (int i = 0; i < temporarilyHiddenButtons.Count; i++)
|
||||
makeButtonAppearFromBottom(temporarilyHiddenButtons[i], 0);
|
||||
{
|
||||
var button = temporarilyHiddenButtons[i];
|
||||
hiddenButtonsContainer.Remove(button, false);
|
||||
buttonsFlow.Add(button);
|
||||
|
||||
makeButtonAppearFromBottom(button, 0);
|
||||
}
|
||||
|
||||
temporarilyHiddenButtons.Clear();
|
||||
|
||||
updateColourScheme(OverlayColourScheme.Aquamarine.GetHue());
|
||||
|
||||
contentContainer.Delay(timeUntilRun).Expire();
|
||||
contentContainer = null;
|
||||
activeFooterContent.Delay(timeUntilRun).Expire();
|
||||
activeFooterContent = null;
|
||||
activeOverlay = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
@@ -45,7 +46,7 @@ using osu.Game.Localisation;
|
||||
|
||||
namespace osu.Game.Screens.Menu
|
||||
{
|
||||
public partial class MainMenu : OsuScreen, IHandlePresentBeatmap, IKeyBindingHandler<GlobalAction>
|
||||
public partial class MainMenu : OsuScreen, IHandlePresentBeatmap, IKeyBindingHandler<GlobalAction>, ISamplePlaybackDisabler
|
||||
{
|
||||
public const float FADE_IN_DURATION = 300;
|
||||
|
||||
@@ -84,6 +85,10 @@ namespace osu.Game.Screens.Menu
|
||||
[Resolved(canBeNull: true)]
|
||||
private IDialogOverlay dialogOverlay { get; set; }
|
||||
|
||||
// used to stop kiai fountain samples when navigating to other screens
|
||||
IBindable<bool> ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled;
|
||||
private readonly Bindable<bool> samplePlaybackDisabled = new Bindable<bool>();
|
||||
|
||||
protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault();
|
||||
|
||||
protected override bool PlayExitSound => false;
|
||||
@@ -369,6 +374,8 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
supporterDisplay
|
||||
.FadeOut(500, Easing.OutQuint);
|
||||
|
||||
samplePlaybackDisabled.Value = true;
|
||||
}
|
||||
|
||||
public override void OnResuming(ScreenTransitionEvent e)
|
||||
@@ -389,6 +396,8 @@ namespace osu.Game.Screens.Menu
|
||||
bottomElementsFlow
|
||||
.ScaleTo(1, 1000, Easing.OutQuint)
|
||||
.FadeIn(1000, Easing.OutQuint);
|
||||
|
||||
samplePlaybackDisabled.Value = false;
|
||||
}
|
||||
|
||||
public override bool OnExiting(ScreenExitEvent e)
|
||||
|
||||
@@ -39,6 +39,7 @@ using osu.Game.Screens.OnlinePlay.Match;
|
||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
@@ -107,6 +108,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
|
||||
public override bool? ApplyModTrackAdjustments => true;
|
||||
|
||||
protected override UserActivity InitialActivity => new UserActivity.InDailyChallengeLobby();
|
||||
|
||||
public DailyChallenge(Room room)
|
||||
{
|
||||
this.room = room;
|
||||
@@ -526,7 +529,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
private void startPlay()
|
||||
{
|
||||
sampleStart?.Play();
|
||||
this.Push(new PlayerLoader(() => new PlaylistsPlayer(room, playlistItem)
|
||||
this.Push(new PlayerLoader(() => new DailyChallengePlayer(room, playlistItem)
|
||||
{
|
||||
Exited = () => Scheduler.AddOnce(() => leaderboard.RefetchScores())
|
||||
}));
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
{
|
||||
public partial class DailyChallengePlayer : PlaylistsPlayer
|
||||
{
|
||||
protected override UserActivity InitialActivity => new UserActivity.PlayingDailyChallenge(Beatmap.Value.BeatmapInfo, Ruleset.Value);
|
||||
|
||||
public DailyChallengePlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration? configuration = null)
|
||||
: base(room, playlistItem, configuration)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.Countdown;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osuTK;
|
||||
@@ -23,22 +22,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
public partial class MatchStartControl : CompositeDrawable
|
||||
{
|
||||
public required Bindable<PlaylistItem?> SelectedItem
|
||||
{
|
||||
get => selectedItem;
|
||||
set => selectedItem.Current = value;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
[Resolved]
|
||||
private IDialogOverlay? dialogOverlay { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
private readonly BindableWithCurrent<PlaylistItem?> selectedItem = new BindableWithCurrent<PlaylistItem?>();
|
||||
private readonly MultiplayerReadyButton readyButton;
|
||||
private readonly MultiplayerCountdownButton countdownButton;
|
||||
|
||||
@@ -98,9 +90,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
SelectedItem.BindValueChanged(_ => updateState());
|
||||
client.RoomUpdated += onRoomUpdated;
|
||||
client.LoadRequested += onLoadRequested;
|
||||
|
||||
updateState();
|
||||
}
|
||||
|
||||
@@ -214,8 +206,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
|
||||
readyButton.Enabled.Value = countdownButton.Enabled.Value =
|
||||
client.Room.State != MultiplayerRoomState.Closed
|
||||
&& SelectedItem.Value?.ID == client.Room.Settings.PlaylistItemId
|
||||
&& !client.Room.Playlist.Single(i => i.ID == client.Room.Settings.PlaylistItemId).Expired
|
||||
&& !client.Room.CurrentPlaylistItem.Expired
|
||||
&& !operationInProgress.Value;
|
||||
|
||||
// When the local user is the host and spectating the match, the ready button should be enabled only if any users are ready.
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.Rooms;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
@@ -13,14 +11,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
private const float ready_button_width = 600;
|
||||
private const float spectate_button_width = 200;
|
||||
|
||||
public required Bindable<PlaylistItem?> SelectedItem
|
||||
{
|
||||
get => selectedItem;
|
||||
set => selectedItem.Current = value;
|
||||
}
|
||||
|
||||
private readonly BindableWithCurrent<PlaylistItem?> selectedItem = new BindableWithCurrent<PlaylistItem?>();
|
||||
|
||||
public MultiplayerMatchFooter()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
@@ -36,13 +26,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
new MultiplayerSpectateButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
SelectedItem = selectedItem
|
||||
},
|
||||
null,
|
||||
new MatchStartControl
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
SelectedItem = selectedItem
|
||||
},
|
||||
null
|
||||
}
|
||||
|
||||
@@ -21,12 +21,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
public partial class MultiplayerSpectateButton : CompositeDrawable
|
||||
{
|
||||
public required Bindable<PlaylistItem?> SelectedItem
|
||||
{
|
||||
get => selectedItem;
|
||||
set => selectedItem.Current = value;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!;
|
||||
|
||||
@@ -36,7 +30,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
private readonly BindableWithCurrent<PlaylistItem?> selectedItem = new BindableWithCurrent<PlaylistItem?>();
|
||||
private readonly RoundedButton button;
|
||||
|
||||
private IBindable<bool> operationInProgress = null!;
|
||||
@@ -75,7 +68,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(checkForAutomaticDownload), true);
|
||||
client.RoomUpdated += onRoomUpdated;
|
||||
updateState();
|
||||
}
|
||||
@@ -121,11 +113,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
|
||||
private void checkForAutomaticDownload()
|
||||
{
|
||||
PlaylistItem? item = SelectedItem.Value;
|
||||
|
||||
downloadCheckCancellation?.Cancel();
|
||||
|
||||
if (item == null)
|
||||
if (client.Room == null)
|
||||
return;
|
||||
|
||||
if (!automaticallyDownload.Value)
|
||||
@@ -140,10 +130,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
if (client.LocalUser?.State != MultiplayerUserState.Spectating)
|
||||
return;
|
||||
|
||||
MultiplayerPlaylistItem item = client.Room.CurrentPlaylistItem;
|
||||
|
||||
// In a perfect world we'd use BeatmapAvailability, but there's no event-driven flow for when a selection changes.
|
||||
// ie. if selection changes from "not downloaded" to another "not downloaded" we wouldn't get a value changed raised.
|
||||
beatmapLookupCache
|
||||
.GetBeatmapAsync(item.Beatmap.OnlineID, (downloadCheckCancellation = new CancellationTokenSource()).Token)
|
||||
.GetBeatmapAsync(item.BeatmapID, (downloadCheckCancellation = new CancellationTokenSource()).Token)
|
||||
.ContinueWith(resolved => Schedule(() =>
|
||||
{
|
||||
var beatmapSet = resolved.GetResultSafely()?.BeatmapSet;
|
||||
|
||||
@@ -60,6 +60,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!base.OnStart())
|
||||
return false;
|
||||
|
||||
selectionOperation = operationTracker.BeginOperation();
|
||||
|
||||
client.ChangeUserStyle(Beatmap.Value.BeatmapInfo.OnlineID, Ruleset.Value.OnlineID)
|
||||
|
||||
@@ -254,10 +254,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
this.Push(new MultiplayerMatchFreestyleSelect(Room, item));
|
||||
}
|
||||
|
||||
protected override Drawable CreateFooter() => new MultiplayerMatchFooter
|
||||
{
|
||||
SelectedItem = SelectedItem
|
||||
};
|
||||
protected override Drawable CreateFooter() => new MultiplayerMatchFooter();
|
||||
|
||||
protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new MultiplayerMatchSettingsOverlay(room);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using Humanizer;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.Rooms;
|
||||
@@ -43,6 +44,40 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
LeftArea.Padding = new MarginPadding { Top = Header.HEIGHT };
|
||||
}
|
||||
|
||||
protected override bool OnStart()
|
||||
{
|
||||
FilterCriteria criteria = FilterControl.CreateCriteria();
|
||||
|
||||
// Beatmaps with too different of a duration are filtered away; this is just a final safety.
|
||||
if (!criteria.Length.IsInRange(Beatmap.Value.BeatmapInfo.Length))
|
||||
{
|
||||
Logger.Log("The selected beatmap's duration differs too much from the host's selection.", level: LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Beatmaps without a valid online ID are filtered away; this is just a final safety.
|
||||
if (Beatmap.Value.BeatmapInfo.OnlineID < 0)
|
||||
{
|
||||
Logger.Log("The selected beatmap is not available online.", level: LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Beatmaps from different sets are filtered away; this is just a final safety.
|
||||
if (Beatmap.Value.BeatmapSetInfo.OnlineID != criteria.BeatmapSetId)
|
||||
{
|
||||
Logger.Log("The selected beatmap is from a different beatmap set.", level: LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Ruleset.Value.OnlineID < 0)
|
||||
{
|
||||
Logger.Log("The selected ruleset is not available online.", level: LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override FilterControl CreateFilterControl() => new DifficultySelectFilterControl(item);
|
||||
|
||||
protected override IEnumerable<(FooterButton button, OverlayContainer? overlay)> CreateSongSelectFooterButtons()
|
||||
|
||||
@@ -21,15 +21,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
|
||||
protected override bool OnStart()
|
||||
{
|
||||
// Beatmaps without a valid online ID are filtered away; this is just a final safety.
|
||||
if (base.Beatmap.Value.BeatmapInfo.OnlineID < 0)
|
||||
return false;
|
||||
|
||||
if (base.Ruleset.Value.OnlineID < 0)
|
||||
if (!base.OnStart())
|
||||
return false;
|
||||
|
||||
Beatmap.Value = base.Beatmap.Value.BeatmapInfo;
|
||||
Ruleset.Value = base.Ruleset.Value;
|
||||
|
||||
this.Exit();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -67,6 +67,12 @@ namespace osu.Game.Screens.Play.HUD
|
||||
}
|
||||
}
|
||||
|
||||
public FillDirection FillDirection
|
||||
{
|
||||
get => iconsContainer.Direction;
|
||||
set => iconsContainer.Direction = value;
|
||||
}
|
||||
|
||||
private readonly FillFlowContainer<ModIcon> iconsContainer;
|
||||
|
||||
public ModDisplay(bool showExtendedInformation = true)
|
||||
@@ -122,13 +128,13 @@ namespace osu.Game.Screens.Play.HUD
|
||||
private void expand(double duration = 500)
|
||||
{
|
||||
if (ExpansionMode != ExpansionMode.AlwaysContracted)
|
||||
iconsContainer.TransformSpacingTo(new Vector2(5, 0), duration, Easing.OutQuint);
|
||||
iconsContainer.TransformSpacingTo(new Vector2(5, -10), duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private void contract(double duration = 500)
|
||||
{
|
||||
if (ExpansionMode != ExpansionMode.AlwaysExpanded)
|
||||
iconsContainer.TransformSpacingTo(new Vector2(-25, 0), duration, Easing.OutQuint);
|
||||
iconsContainer.TransformSpacingTo(new Vector2(-25), duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
|
||||
@@ -30,6 +30,9 @@ namespace osu.Game.Screens.Play.HUD
|
||||
[SettingSource(typeof(SkinnableModDisplayStrings), nameof(SkinnableModDisplayStrings.ExpansionMode), nameof(SkinnableModDisplayStrings.ExpansionModeDescription))]
|
||||
public Bindable<ExpansionMode> ExpansionModeSetting { get; } = new Bindable<ExpansionMode>();
|
||||
|
||||
[SettingSource(typeof(SkinnableModDisplayStrings), nameof(SkinnableModDisplayStrings.DisplayDirection))]
|
||||
public Bindable<Direction> Direction { get; } = new Bindable<Direction>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@@ -50,6 +53,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
ShowExtendedInformation.BindValueChanged(_ => modDisplay.ShowExtendedInformation = ShowExtendedInformation.Value, true);
|
||||
ExpansionModeSetting.BindValueChanged(_ => modDisplay.ExpansionMode = ExpansionModeSetting.Value, true);
|
||||
Direction.BindValueChanged(_ => modDisplay.FillDirection = Direction.Value == Framework.Graphics.Direction.Horizontal ? FillDirection.Horizontal : FillDirection.Vertical, true);
|
||||
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
@@ -147,6 +147,9 @@ namespace osu.Game.Screens.Play
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
// This display is potentially a duplicate of users with a local ModDisplay in their skins.
|
||||
// It would be very nice to remove this, but the version here has special logic with regards to replays
|
||||
// and initial states, so needs a bit of thought before doing so.
|
||||
ModDisplay = CreateModsContainer(),
|
||||
}
|
||||
},
|
||||
|
||||
@@ -5,9 +5,12 @@ using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Localisation;
|
||||
@@ -31,14 +34,29 @@ namespace osu.Game.Screens.Play
|
||||
OnResume?.Invoke();
|
||||
};
|
||||
|
||||
private readonly IBindable<bool> windowActive = new Bindable<bool>(true);
|
||||
|
||||
private float targetVolume => windowActive.Value && State.Value == Visibility.Visible ? 1.0f : 0;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(GameHost? host)
|
||||
{
|
||||
AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("Gameplay/pause-loop"))
|
||||
{
|
||||
Looping = true,
|
||||
Volume = { Value = 0 }
|
||||
});
|
||||
|
||||
if (host != null)
|
||||
windowActive.BindTo(host.IsActive);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
// Schedule required because host.IsActive doesn't seem to always run on the update thread.
|
||||
windowActive.BindValueChanged(_ => Schedule(() => pauseLoop.VolumeTo(targetVolume, 1000, Easing.Out)));
|
||||
}
|
||||
|
||||
public void StopAllSamples()
|
||||
@@ -53,7 +71,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
base.PopIn();
|
||||
|
||||
pauseLoop.VolumeTo(1.0f, TRANSITION_DURATION, Easing.InQuint);
|
||||
pauseLoop.VolumeTo(targetVolume, TRANSITION_DURATION, Easing.InQuint);
|
||||
pauseLoop.Play();
|
||||
}
|
||||
|
||||
@@ -61,7 +79,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
base.PopOut();
|
||||
|
||||
pauseLoop.VolumeTo(0, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop());
|
||||
pauseLoop.VolumeTo(targetVolume, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop());
|
||||
}
|
||||
|
||||
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
|
||||
@@ -108,12 +108,10 @@ namespace osu.Game.Screens.Ranking.Contracted
|
||||
Offset = new Vector2(0, 1),
|
||||
}
|
||||
},
|
||||
new OsuSpriteText
|
||||
new ClickableUsername(score.User)
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = score.RealmUser.Username,
|
||||
Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold)
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
|
||||
@@ -106,22 +106,7 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new TruncatingSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = new RomanisableString(metadata.TitleUnicode, metadata.Title),
|
||||
Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold),
|
||||
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
|
||||
},
|
||||
new TruncatingSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist),
|
||||
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
|
||||
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
|
||||
},
|
||||
new ClickableMetadata(beatmap.OnlineID, metadata),
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
@@ -316,5 +301,47 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
time.ToLocalTime().ToLocalisableString(prefer24HourTime.Value ? @"d MMMM yyyy HH:mm" : @"d MMMM yyyy h:mm tt"));
|
||||
}
|
||||
}
|
||||
|
||||
internal partial class ClickableMetadata : OsuHoverContainer
|
||||
{
|
||||
[Resolved]
|
||||
private OsuGame? game { get; set; }
|
||||
|
||||
public ClickableMetadata(int beatmapId, IBeatmapMetadataInfo metadata)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
Anchor = Anchor.TopCentre;
|
||||
Origin = Anchor.TopCentre;
|
||||
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new TruncatingSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = new RomanisableString(metadata.TitleUnicode, metadata.Title),
|
||||
Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold),
|
||||
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
|
||||
},
|
||||
new TruncatingSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist),
|
||||
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
|
||||
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (beatmapId > 0)
|
||||
Action = () => game?.ShowBeatmap(beatmapId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Users.Drawables;
|
||||
using osuTK;
|
||||
@@ -62,12 +60,10 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
CornerExponent = 2.5f,
|
||||
Masking = true,
|
||||
},
|
||||
new OsuSpriteText
|
||||
new ClickableUsername(user)
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = user.Username,
|
||||
Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -14,14 +14,18 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Placeholders;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking.Statistics.User;
|
||||
using osuTK;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Screens.Ranking.Statistics
|
||||
{
|
||||
@@ -43,6 +47,9 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
[Resolved]
|
||||
private BeatmapManager beatmapManager { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RealmAccess realm { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
@@ -231,14 +238,32 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
});
|
||||
}
|
||||
|
||||
if (AchievedScore != null
|
||||
&& newScore.BeatmapInfo!.OnlineID > 0
|
||||
if (newScore.BeatmapInfo!.OnlineID > 0
|
||||
&& api.IsLoggedIn)
|
||||
{
|
||||
if (
|
||||
// We may want to iterate on this condition
|
||||
AchievedScore.Rank >= ScoreRank.C
|
||||
)
|
||||
string? preventTaggingReason = null;
|
||||
|
||||
// We may want to iterate on the following conditions further in the future
|
||||
|
||||
var localUserScore = AchievedScore ?? realm.Run(r =>
|
||||
r.All<ScoreInfo>()
|
||||
.Filter($@"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0"
|
||||
+ $@" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1"
|
||||
+ $@" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.Hash)} == {nameof(ScoreInfo.BeatmapHash)}"
|
||||
+ $@" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, newScore.BeatmapInfo.ID, newScore.BeatmapInfo.Ruleset.ShortName)
|
||||
.AsEnumerable()
|
||||
.OrderByDescending(score => score.Ruleset.MatchesOnlineID(newScore.BeatmapInfo.Ruleset))
|
||||
.ThenByDescending(score => score.Rank)
|
||||
.FirstOrDefault());
|
||||
|
||||
if (localUserScore == null)
|
||||
preventTaggingReason = "Play the beatmap to contribute to beatmap tags!";
|
||||
else if (localUserScore.Ruleset.OnlineID != newScore.BeatmapInfo!.Ruleset.OnlineID)
|
||||
preventTaggingReason = "Play the beatmap in its original ruleset to contribute to beatmap tags!";
|
||||
else if (localUserScore.Rank < ScoreRank.C)
|
||||
preventTaggingReason = "Set a better score to contribute to beatmap tags!";
|
||||
|
||||
if (preventTaggingReason == null)
|
||||
{
|
||||
yield return new StatisticItem("Tag the beatmap!", () => new UserTagControl(newScore.BeatmapInfo)
|
||||
{
|
||||
@@ -254,7 +279,7 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
TextAnchor = Anchor.Centre,
|
||||
Text = "Set a better score to contribute to beatmap tags!",
|
||||
Text = preventTaggingReason,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Screens.Ranking.Statistics.User
|
||||
{
|
||||
@@ -18,7 +18,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User
|
||||
protected override LocalisableString Label => UsersStrings.ShowRankGlobalSimple;
|
||||
|
||||
protected override LocalisableString FormatCurrentValue(int? current)
|
||||
=> current == null ? string.Empty : current.Value.FormatRank();
|
||||
=> current?.ToLocalisableString(@"N0") ?? string.Empty;
|
||||
|
||||
protected override int CalculateDifference(int? previous, int? current, out LocalisableString formattedDifference)
|
||||
{
|
||||
@@ -30,13 +30,13 @@ namespace osu.Game.Screens.Ranking.Statistics.User
|
||||
|
||||
if (previous == null && current != null)
|
||||
{
|
||||
formattedDifference = LocalisableString.Interpolate($"+{current.Value.FormatRank()}");
|
||||
formattedDifference = LocalisableString.Interpolate($"+{current.Value:N0}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (previous != null && current == null)
|
||||
{
|
||||
formattedDifference = LocalisableString.Interpolate($"-{previous.Value.FormatRank()}");
|
||||
formattedDifference = LocalisableString.Interpolate($"-{previous.Value:N0}");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -46,9 +46,9 @@ namespace osu.Game.Screens.Ranking.Statistics.User
|
||||
int difference = previous.Value - current.Value;
|
||||
|
||||
if (difference < 0)
|
||||
formattedDifference = difference.FormatRank();
|
||||
formattedDifference = difference.ToLocalisableString(@"N0");
|
||||
else if (difference > 0)
|
||||
formattedDifference = LocalisableString.Interpolate($"+{difference.FormatRank()}");
|
||||
formattedDifference = LocalisableString.Interpolate($"+{difference:N0}");
|
||||
else
|
||||
formattedDifference = string.Empty;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public Action<BeatmapInfo>? RequestPresentBeatmap { private get; init; }
|
||||
|
||||
public const float SPACING = 5f;
|
||||
public const float SPACING = 3f;
|
||||
|
||||
private IBindableList<BeatmapSetInfo> detachedBeatmaps = null!;
|
||||
|
||||
|
||||
@@ -244,18 +244,18 @@ namespace osu.Game.Screens.SelectV2.Footer
|
||||
Mods.BindValueChanged(v => Text = ModSelectOverlayStrings.Mods(v.NewValue.Count).ToUpper(), true);
|
||||
}
|
||||
|
||||
public ITooltip<IReadOnlyList<Mod>> GetCustomTooltip() => new ModTooltip(colourProvider);
|
||||
public ITooltip<IReadOnlyList<Mod>> GetCustomTooltip() => new ModOverflowTooltip(colourProvider);
|
||||
|
||||
public IReadOnlyList<Mod>? TooltipContent => Mods.Value;
|
||||
|
||||
public partial class ModTooltip : VisibilityContainer, ITooltip<IReadOnlyList<Mod>>
|
||||
public partial class ModOverflowTooltip : VisibilityContainer, ITooltip<IReadOnlyList<Mod>>
|
||||
{
|
||||
private ModDisplay extendedModDisplay = null!;
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider;
|
||||
|
||||
public ModTooltip(OverlayColourProvider colourProvider)
|
||||
public ModOverflowTooltip(OverlayColourProvider colourProvider)
|
||||
{
|
||||
this.colourProvider = colourProvider;
|
||||
}
|
||||
|
||||
@@ -715,18 +715,21 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
||||
public LocalisableString TooltipText { get; }
|
||||
}
|
||||
|
||||
private sealed partial class ColouredModSwitchTiny : ModSwitchTiny, IHasTooltip
|
||||
private sealed partial class ColouredModSwitchTiny : ModSwitchTiny, IHasCustomTooltip<Mod>
|
||||
{
|
||||
private readonly IMod mod;
|
||||
public Mod? TooltipContent { get; }
|
||||
|
||||
public ColouredModSwitchTiny(IMod mod)
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
public ColouredModSwitchTiny(Mod mod)
|
||||
: base(mod)
|
||||
{
|
||||
this.mod = mod;
|
||||
TooltipContent = mod;
|
||||
Active.Value = true;
|
||||
}
|
||||
|
||||
public LocalisableString TooltipText => (mod as Mod)?.IconTooltip ?? mod.Name;
|
||||
public ITooltip<Mod> GetCustomTooltip() => new ModTooltip(colourProvider);
|
||||
}
|
||||
|
||||
private sealed partial class MoreModSwitchTiny : CompositeDrawable
|
||||
|
||||
@@ -106,8 +106,6 @@ namespace osu.Game.Screens.SelectV2
|
||||
},
|
||||
difficultiesDisplay = new DifficultySpectrumDisplay
|
||||
{
|
||||
DotSize = new Vector2(5, 10),
|
||||
DotSpacing = 2,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
private const float logo_scale = 0.4f;
|
||||
|
||||
private readonly ModSelectOverlay modSelectOverlay = new ModSelectOverlay
|
||||
private readonly ModSelectOverlay modSelectOverlay = new ModSelectOverlay(OverlayColourScheme.Aquamarine)
|
||||
{
|
||||
ShowPresets = true,
|
||||
};
|
||||
|
||||
@@ -238,7 +238,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
QueueMode = ServerAPIRoom.QueueMode,
|
||||
AutoStartDuration = ServerAPIRoom.AutoStartDuration
|
||||
},
|
||||
Playlist = ServerAPIRoom.Playlist.Select(CreateMultiplayerPlaylistItem).ToList(),
|
||||
Playlist = ServerAPIRoom.Playlist.Select(item => new MultiplayerPlaylistItem(item)).ToList(),
|
||||
Users = { localUser },
|
||||
Host = localUser
|
||||
};
|
||||
@@ -687,21 +687,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
return MessagePackSerializer.Deserialize<T>(serialized, SignalRUnionWorkaroundResolver.OPTIONS);
|
||||
}
|
||||
|
||||
public static MultiplayerPlaylistItem CreateMultiplayerPlaylistItem(PlaylistItem item) => new MultiplayerPlaylistItem
|
||||
{
|
||||
ID = item.ID,
|
||||
OwnerID = item.OwnerID,
|
||||
BeatmapID = item.Beatmap.OnlineID,
|
||||
BeatmapChecksum = item.Beatmap.MD5Hash,
|
||||
RulesetID = item.RulesetID,
|
||||
RequiredMods = item.RequiredMods.ToArray(),
|
||||
AllowedMods = item.AllowedMods.ToArray(),
|
||||
Expired = item.Expired,
|
||||
PlaylistOrder = item.PlaylistOrder ?? 0,
|
||||
PlayedAt = item.PlayedAt,
|
||||
StarRating = item.Beatmap.StarRating,
|
||||
};
|
||||
|
||||
public override Task DisconnectInternal()
|
||||
{
|
||||
isConnected.Value = false;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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.Cursor;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Users.Drawables
|
||||
{
|
||||
internal partial class ClickableUsername : OsuHoverContainer, IHasCustomTooltip<APIUser>
|
||||
{
|
||||
public ITooltip<APIUser?> GetCustomTooltip() => new ClickableAvatar.NoCardTooltip();
|
||||
|
||||
public APIUser? TooltipContent { get; }
|
||||
|
||||
private readonly APIUser user;
|
||||
|
||||
[Resolved]
|
||||
private OsuGame? game { get; set; }
|
||||
|
||||
public ClickableUsername(APIUser? user)
|
||||
{
|
||||
TooltipContent = this.user = user ?? new GuestUser();
|
||||
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
Child = new OsuSpriteText
|
||||
{
|
||||
Text = user!.Username,
|
||||
Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold),
|
||||
};
|
||||
|
||||
if (user.Id != APIUser.SYSTEM_USER_ID)
|
||||
Action = openProfile;
|
||||
}
|
||||
|
||||
private void openProfile()
|
||||
{
|
||||
if (user.Id > 1 || !string.IsNullOrEmpty(user.Username))
|
||||
game?.ShowUser(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,10 @@ using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Users.Drawables
|
||||
@@ -64,6 +66,12 @@ namespace osu.Game.Users.Drawables
|
||||
|
||||
public LocalisableString TooltipText { get; }
|
||||
|
||||
[Resolved]
|
||||
private OsuGame? game { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
public TeamFlag(APITeam team)
|
||||
{
|
||||
this.team = team;
|
||||
@@ -91,6 +99,12 @@ namespace osu.Game.Users.Drawables
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
game?.OpenUrlExternally($"{api.Endpoints.WebsiteUrl}/teams/{team.Id}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace osu.Game.Users
|
||||
[Union(41, typeof(EditingBeatmap))]
|
||||
[Union(42, typeof(ModdingBeatmap))]
|
||||
[Union(43, typeof(TestingBeatmap))]
|
||||
[Union(51, typeof(InDailyChallengeLobby))]
|
||||
[Union(52, typeof(PlayingDailyChallenge))]
|
||||
public abstract class UserActivity
|
||||
{
|
||||
public abstract string GetStatus(bool hideIdentifiableInformation = false);
|
||||
@@ -58,6 +60,7 @@ namespace osu.Game.Users
|
||||
[Union(23, typeof(InMultiplayerGame))]
|
||||
[Union(24, typeof(SpectatingMultiplayerGame))]
|
||||
[Union(31, typeof(InPlaylistGame))]
|
||||
[Union(52, typeof(PlayingDailyChallenge))]
|
||||
public abstract class InGame : UserActivity
|
||||
{
|
||||
[Key(0)]
|
||||
@@ -244,7 +247,7 @@ namespace osu.Game.Users
|
||||
[SerializationConstructor]
|
||||
public SpectatingMultiplayerGame() { }
|
||||
|
||||
public override string GetStatus(bool hideIdentifiableInformation = false) => $"Watching others {base.GetStatus(hideIdentifiableInformation).ToLowerInvariant()}";
|
||||
public override string GetStatus(bool hideIdentifiableInformation = false) => @"Spectating a multiplayer game";
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
@@ -277,5 +280,30 @@ namespace osu.Game.Users
|
||||
? null
|
||||
: RoomName;
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
public class InDailyChallengeLobby : UserActivity
|
||||
{
|
||||
[SerializationConstructor]
|
||||
public InDailyChallengeLobby() { }
|
||||
|
||||
public override string GetStatus(bool hideIdentifiableInformation = false) => @"In daily challenge lobby";
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
public class PlayingDailyChallenge : InGame
|
||||
{
|
||||
public PlayingDailyChallenge(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset)
|
||||
: base(beatmapInfo, ruleset)
|
||||
{
|
||||
}
|
||||
|
||||
[SerializationConstructor]
|
||||
public PlayingDailyChallenge()
|
||||
{
|
||||
}
|
||||
|
||||
public override string GetStatus(bool hideIdentifiableInformation = false) => @$"{RulesetPlayingVerb} in daily challenge";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="20.1.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2025.318.1" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2025.318.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2025.321.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2025.321.0" />
|
||||
<PackageReference Include="Sentry" Version="5.1.1" />
|
||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||
<PackageReference Include="SharpCompress" Version="0.39.0" />
|
||||
|
||||
+1
-1
@@ -17,6 +17,6 @@
|
||||
<MtouchInterpreter>-all</MtouchInterpreter>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2025.318.1" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2025.321.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user