1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-13 02:32:55 +08:00

Merge branch 'master' into make_skin_reset_dangerous

This commit is contained in:
Bartłomiej Dach 2023-03-07 21:06:44 +01:00 committed by GitHub
commit 5213bfa5c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 586 additions and 204 deletions

View File

@ -3,15 +3,53 @@
# #
# https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects # https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects
$CSPROJ="osu.Game/osu.Game.csproj" $GAME_CSPROJ="osu.Game/osu.Game.csproj"
$ANDROID_PROPS="osu.Android.props"
$IOS_PROPS="osu.iOS.props"
$SLN="osu.sln" $SLN="osu.sln"
dotnet remove $CSPROJ package ppy.osu.Framework; dotnet remove $GAME_CSPROJ reference ppy.osu.Framework;
dotnet sln $SLN add ../osu-framework/osu.Framework/osu.Framework.csproj ../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj; dotnet remove $ANDROID_PROPS reference ppy.osu.Framework.Android;
dotnet add $CSPROJ reference ../osu-framework/osu.Framework/osu.Framework.csproj dotnet remove $IOS_PROPS reference ppy.osu.Framework.iOS;
dotnet sln $SLN add ../osu-framework/osu.Framework/osu.Framework.csproj `
../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj `
../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj `
../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj;
dotnet add $GAME_CSPROJ reference ../osu-framework/osu.Framework/osu.Framework.csproj;
dotnet add $ANDROID_PROPS reference ../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj;
dotnet add $IOS_PROPS reference ../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj;
# workaround for dotnet add not inserting $(MSBuildThisFileDirectory) on props files
(Get-Content "osu.Android.props") -replace "`"..\\osu-framework", "`"`$(MSBuildThisFileDirectory)..\osu-framework" | Set-Content "osu.Android.props"
(Get-Content "osu.iOS.props") -replace "`"..\\osu-framework", "`"`$(MSBuildThisFileDirectory)..\osu-framework" | Set-Content "osu.iOS.props"
# needed because iOS framework nupkg includes a set of properties to work around certain issues during building,
# and those get ignored when referencing framework via project, threfore we have to manually include it via props reference.
(Get-Content "osu.iOS.props") |
Foreach-Object {
if ($_ -match "</Project>")
{
" <Import Project=`"`$(MSBuildThisFileDirectory)../osu-framework/osu.Framework.iOS.props`"/>"
}
$_
} | Set-Content "osu.iOS.props"
$TMP=New-TemporaryFile
$SLNF=Get-Content "osu.Desktop.slnf" | ConvertFrom-Json $SLNF=Get-Content "osu.Desktop.slnf" | ConvertFrom-Json
$TMP=New-TemporaryFile
$SLNF.solution.projects += ("../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj") $SLNF.solution.projects += ("../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj")
ConvertTo-Json $SLNF | Out-File $TMP -Encoding UTF8 ConvertTo-Json $SLNF | Out-File $TMP -Encoding UTF8
Move-Item -Path $TMP -Destination "osu.Desktop.slnf" -Force Move-Item -Path $TMP -Destination "osu.Desktop.slnf" -Force
$SLNF=Get-Content "osu.Android.slnf" | ConvertFrom-Json
$SLNF.solution.projects += ("../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj", "../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj")
ConvertTo-Json $SLNF | Out-File $TMP -Encoding UTF8
Move-Item -Path $TMP -Destination "osu.Android.slnf" -Force
$SLNF=Get-Content "osu.iOS.slnf" | ConvertFrom-Json
$SLNF.solution.projects += ("../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj", "../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj")
ConvertTo-Json $SLNF | Out-File $TMP -Encoding UTF8
Move-Item -Path $TMP -Destination "osu.iOS.slnf" -Force

View File

@ -5,14 +5,41 @@
# #
# https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects # https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects
CSPROJ="osu.Game/osu.Game.csproj" GAME_CSPROJ="osu.Game/osu.Game.csproj"
ANDROID_PROPS="osu.Android.props"
IOS_PROPS="osu.iOS.props"
SLN="osu.sln" SLN="osu.sln"
dotnet remove $CSPROJ package ppy.osu.Framework dotnet remove $GAME_CSPROJ reference ppy.osu.Framework
dotnet sln $SLN add ../osu-framework/osu.Framework/osu.Framework.csproj ../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj dotnet remove $ANDROID_PROPS reference ppy.osu.Framework.Android
dotnet add $CSPROJ reference ../osu-framework/osu.Framework/osu.Framework.csproj dotnet remove $IOS_PROPS reference ppy.osu.Framework.iOS
dotnet sln $SLN add ../osu-framework/osu.Framework/osu.Framework.csproj \
../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj \
../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj \
../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj
dotnet add $GAME_CSPROJ reference ../osu-framework/osu.Framework/osu.Framework.csproj
dotnet add $ANDROID_PROPS reference ../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj
dotnet add $IOS_PROPS reference ../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj
# workaround for dotnet add not inserting $(MSBuildThisFileDirectory) on props files
sed -i.bak 's:"..\\osu-framework:"$(MSBuildThisFileDirectory)..\\osu-framework:g' ./osu.Android.props && rm osu.Android.props.bak
sed -i.bak 's:"..\\osu-framework:"$(MSBuildThisFileDirectory)..\\osu-framework:g' ./osu.iOS.props && rm osu.iOS.props.bak
# needed because iOS framework nupkg includes a set of properties to work around certain issues during building,
# and those get ignored when referencing framework via project, threfore we have to manually include it via props reference.
sed -i.bak '/<\/Project>/i\
<Import Project=\"$(MSBuildThisFileDirectory)../osu-framework/osu.Framework.iOS.props\"/>\
' ./osu.iOS.props && rm osu.iOS.props.bak
SLNF="osu.Desktop.slnf"
tmp=$(mktemp) tmp=$(mktemp)
jq '.solution.projects += ["../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj"]' osu.Desktop.slnf > $tmp jq '.solution.projects += ["../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj"]' osu.Desktop.slnf > $tmp
mv -f $tmp $SLNF mv -f $tmp osu.Desktop.slnf
jq '.solution.projects += ["../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj", "../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj"]' osu.Android.slnf > $tmp
mv -f $tmp osu.Android.slnf
jq '.solution.projects += ["../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj", "../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj"]' osu.iOS.slnf > $tmp
mv -f $tmp osu.iOS.slnf

View File

@ -8,9 +8,13 @@
<!-- NullabilityInfoContextSupport is disabled by default for Android --> <!-- NullabilityInfoContextSupport is disabled by default for Android -->
<NullabilityInfoContextSupport>true</NullabilityInfoContextSupport> <NullabilityInfoContextSupport>true</NullabilityInfoContextSupport>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk> <EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.131.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2023.228.0" />
</ItemGroup>
<ItemGroup>
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged. <!-- Fody does not handle Android build well, and warns when unchanged.

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="mailto" />
</intent>
</queries>
</manifest>

View File

@ -167,8 +167,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{ {
if (bodySprite != null) if (bodySprite != null)
{ {
bodySprite.Origin = Anchor.BottomCentre; bodySprite.Origin = Anchor.TopCentre;
bodySprite.Scale = new Vector2(bodySprite.Scale.X, Math.Abs(bodySprite.Scale.Y) * -1); bodySprite.Anchor = Anchor.BottomCentre; // needs to be flipped due to scale flip in Update.
} }
if (light != null) if (light != null)
@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
if (bodySprite != null) if (bodySprite != null)
{ {
bodySprite.Origin = Anchor.TopCentre; bodySprite.Origin = Anchor.TopCentre;
bodySprite.Scale = new Vector2(bodySprite.Scale.X, Math.Abs(bodySprite.Scale.Y)); bodySprite.Anchor = Anchor.TopCentre;
} }
if (light != null) if (light != null)
@ -211,11 +211,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
base.Update(); base.Update();
missFadeTime.Value ??= holdNote.HoldBrokenTime; missFadeTime.Value ??= holdNote.HoldBrokenTime;
int scaleDirection = (direction.Value == ScrollingDirection.Down ? 1 : -1);
// here we go... // here we go...
switch (bodyStyle) switch (bodyStyle)
{ {
case LegacyNoteBodyStyle.Stretch: case LegacyNoteBodyStyle.Stretch:
// this is how lazer works by default. nothing required. // this is how lazer works by default. nothing required.
if (bodySprite != null)
bodySprite.Scale = new Vector2(1, scaleDirection);
break; break;
default: default:
@ -228,7 +232,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
bodySprite.FillMode = FillMode.Stretch; bodySprite.FillMode = FillMode.Stretch;
// i dunno this looks about right?? // i dunno this looks about right??
bodySprite.Scale = new Vector2(1, 32800 / sprite.DrawHeight); bodySprite.Scale = new Vector2(1, scaleDirection * 32800 / sprite.DrawHeight);
} }
break; break;

View File

@ -8,6 +8,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
{ {
public class TaikoModRelax : ModRelax public class TaikoModRelax : ModRelax
{ {
public override LocalisableString Description => @"No ninja-like spinners, demanding drumrolls or unexpected katu's."; public override LocalisableString Description => @"No ninja-like spinners, demanding drumrolls or unexpected katus.";
} }
} }

View File

@ -12,7 +12,7 @@ using osu.Game.Overlays.Settings;
namespace osu.Game.Tests.Mods namespace osu.Game.Tests.Mods
{ {
[TestFixture] [TestFixture]
public partial class SettingsSourceAttributeTest public partial class SettingSourceAttributeTest
{ {
[Test] [Test]
public void TestOrdering() public void TestOrdering()

View File

@ -48,7 +48,9 @@ namespace osu.Game.Tests.Skins
// Covers BPM counter. // Covers BPM counter.
"Archives/modified-default-20221205.osk", "Archives/modified-default-20221205.osk",
// Covers judgement counter. // Covers judgement counter.
"Archives/modified-default-20230117.osk" "Archives/modified-default-20230117.osk",
// Covers player avatar and flag.
"Archives/modified-argon-20230305.osk",
}; };
/// <summary> /// <summary>

View File

@ -1,9 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -21,7 +23,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
public partial class TestSceneSkinEditor : PlayerTestScene public partial class TestSceneSkinEditor : PlayerTestScene
{ {
private SkinEditor? skinEditor; private SkinEditor skinEditor = null!;
protected override bool Autoplay => true; protected override bool Autoplay => true;
@ -31,26 +33,75 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached] [Cached]
public readonly EditorClipboard Clipboard = new EditorClipboard(); public readonly EditorClipboard Clipboard = new EditorClipboard();
private SkinComponentsContainer targetContainer => Player.ChildrenOfType<SkinComponentsContainer>().First();
[SetUpSteps] [SetUpSteps]
public override void SetUpSteps() public override void SetUpSteps()
{ {
base.SetUpSteps(); base.SetUpSteps();
AddUntilStep("wait for hud load", () => Player.ChildrenOfType<SkinComponentsContainer>().All(c => c.ComponentsLoaded)); AddUntilStep("wait for hud load", () => targetContainer.ComponentsLoaded);
AddStep("reload skin editor", () => AddStep("reload skin editor", () =>
{ {
skinEditor?.Expire(); if (skinEditor.IsNotNull())
skinEditor.Expire();
Player.ScaleTo(0.4f); Player.ScaleTo(0.4f);
LoadComponentAsync(skinEditor = new SkinEditor(Player), Add); LoadComponentAsync(skinEditor = new SkinEditor(Player), Add);
}); });
AddUntilStep("wait for loaded", () => skinEditor!.IsLoaded); AddUntilStep("wait for loaded", () => skinEditor.IsLoaded);
}
[TestCase(false)]
[TestCase(true)]
public void TestBringToFront(bool alterSelectionOrder)
{
AddAssert("Ensure over three components available", () => targetContainer.Components.Count, () => Is.GreaterThan(3));
IEnumerable<ISerialisableDrawable> originalOrder = null!;
AddStep("Save order of components before operation", () => originalOrder = targetContainer.Components.Take(3).ToArray());
if (alterSelectionOrder)
AddStep("Select first three components in reverse order", () => skinEditor.SelectedComponents.AddRange(originalOrder.Reverse()));
else
AddStep("Select first three components", () => skinEditor.SelectedComponents.AddRange(originalOrder));
AddAssert("Components are not front-most", () => targetContainer.Components.TakeLast(3).ToArray(), () => Is.Not.EqualTo(skinEditor.SelectedComponents));
AddStep("Bring to front", () => skinEditor.BringSelectionToFront());
AddAssert("Ensure components are now front-most in original order", () => targetContainer.Components.TakeLast(3).ToArray(), () => Is.EqualTo(originalOrder));
AddStep("Bring to front again", () => skinEditor.BringSelectionToFront());
AddAssert("Ensure components are still front-most in original order", () => targetContainer.Components.TakeLast(3).ToArray(), () => Is.EqualTo(originalOrder));
}
[TestCase(false)]
[TestCase(true)]
public void TestSendToBack(bool alterSelectionOrder)
{
AddAssert("Ensure over three components available", () => targetContainer.Components.Count, () => Is.GreaterThan(3));
IEnumerable<ISerialisableDrawable> originalOrder = null!;
AddStep("Save order of components before operation", () => originalOrder = targetContainer.Components.TakeLast(3).ToArray());
if (alterSelectionOrder)
AddStep("Select last three components in reverse order", () => skinEditor.SelectedComponents.AddRange(originalOrder.Reverse()));
else
AddStep("Select last three components", () => skinEditor.SelectedComponents.AddRange(originalOrder));
AddAssert("Components are not back-most", () => targetContainer.Components.Take(3).ToArray(), () => Is.Not.EqualTo(skinEditor.SelectedComponents));
AddStep("Send to back", () => skinEditor.SendSelectionToBack());
AddAssert("Ensure components are now back-most in original order", () => targetContainer.Components.Take(3).ToArray(), () => Is.EqualTo(originalOrder));
AddStep("Send to back again", () => skinEditor.SendSelectionToBack());
AddAssert("Ensure components are still back-most in original order", () => targetContainer.Components.Take(3).ToArray(), () => Is.EqualTo(originalOrder));
} }
[Test] [Test]
public void TestToggleEditor() public void TestToggleEditor()
{ {
AddToggleStep("toggle editor visibility", _ => skinEditor!.ToggleVisibility()); AddToggleStep("toggle editor visibility", _ => skinEditor.ToggleVisibility());
} }
[Test] [Test]
@ -63,7 +114,7 @@ namespace osu.Game.Tests.Visual.Gameplay
var blueprint = skinEditor.ChildrenOfType<SkinBlueprint>().First(b => b.Item is BarHitErrorMeter); var blueprint = skinEditor.ChildrenOfType<SkinBlueprint>().First(b => b.Item is BarHitErrorMeter);
hitErrorMeter = (BarHitErrorMeter)blueprint.Item; hitErrorMeter = (BarHitErrorMeter)blueprint.Item;
skinEditor!.SelectedComponents.Clear(); skinEditor.SelectedComponents.Clear();
skinEditor.SelectedComponents.Add(blueprint.Item); skinEditor.SelectedComponents.Add(blueprint.Item);
}); });

View File

@ -15,6 +15,7 @@ using osu.Game.Overlays.Settings;
using osu.Game.Overlays.SkinEditor; using osu.Game.Overlays.SkinEditor;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Edit.Components;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Beatmaps.IO;
@ -188,6 +189,33 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("mod overlay closed", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); AddUntilStep("mod overlay closed", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
} }
[Test]
public void TestChangeToNonSkinnableScreen()
{
advanceToSongSelect();
openSkinEditor();
AddAssert("blueprint container present", () => skinEditor.ChildrenOfType<SkinBlueprintContainer>().Count(), () => Is.EqualTo(1));
AddAssert("placeholder not present", () => skinEditor.ChildrenOfType<NonSkinnableScreenPlaceholder>().Count(), () => Is.Zero);
AddAssert("editor sidebars not empty", () => skinEditor.ChildrenOfType<EditorSidebar>().SelectMany(sidebar => sidebar.Children).Count(), () => Is.GreaterThan(0));
AddStep("add skinnable component", () =>
{
skinEditor.ChildrenOfType<SkinComponentToolbox.ToolboxComponentButton>().First().TriggerClick();
});
AddUntilStep("newly added component selected", () => skinEditor.SelectedComponents, () => Has.Count.EqualTo(1));
AddStep("exit to main menu", () => Game.ScreenStack.CurrentScreen.Exit());
AddAssert("selection cleared", () => skinEditor.SelectedComponents, () => Has.Count.Zero);
AddAssert("blueprint container not present", () => skinEditor.ChildrenOfType<SkinBlueprintContainer>().Count(), () => Is.Zero);
AddAssert("placeholder present", () => skinEditor.ChildrenOfType<NonSkinnableScreenPlaceholder>().Count(), () => Is.EqualTo(1));
AddAssert("editor sidebars empty", () => skinEditor.ChildrenOfType<EditorSidebar>().SelectMany(sidebar => sidebar.Children).Count(), () => Is.Zero);
advanceToSongSelect();
AddAssert("blueprint container present", () => skinEditor.ChildrenOfType<SkinBlueprintContainer>().Count(), () => Is.EqualTo(1));
AddAssert("placeholder not present", () => skinEditor.ChildrenOfType<NonSkinnableScreenPlaceholder>().Count(), () => Is.Zero);
AddAssert("editor sidebars not empty", () => skinEditor.ChildrenOfType<EditorSidebar>().SelectMany(sidebar => sidebar.Children).Count(), () => Is.GreaterThan(0));
}
private void advanceToSongSelect() private void advanceToSongSelect()
{ {
PushAndConfirm(() => songSelect = new TestPlaySongSelect()); PushAndConfirm(() => songSelect = new TestPlaySongSelect());

View File

@ -516,15 +516,20 @@ namespace osu.Game.Tests.Visual.SongSelect
{ {
sets.Clear(); sets.Clear();
for (int i = 0; i < 20; i++) for (int i = 0; i < 10; i++)
{ {
var set = TestResources.CreateTestBeatmapSetInfo(5); var set = TestResources.CreateTestBeatmapSetInfo(5);
if (i >= 2 && i < 10) // A total of 6 sets have date submitted (4 don't)
// A total of 5 sets have artist string (3 of which also have date submitted)
if (i >= 2 && i < 8) // i = 2, 3, 4, 5, 6, 7 have submitted date
set.DateSubmitted = DateTimeOffset.Now.AddMinutes(i); set.DateSubmitted = DateTimeOffset.Now.AddMinutes(i);
if (i < 5) if (i < 5) // i = 0, 1, 2, 3, 4 have matching string
set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_string); set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_string);
set.Beatmaps.ForEach(b => b.Metadata.Title = $"submitted: {set.DateSubmitted}");
sets.Add(set); sets.Add(set);
} }
}); });
@ -532,15 +537,26 @@ namespace osu.Game.Tests.Visual.SongSelect
loadBeatmaps(sets); loadBeatmaps(sets);
AddStep("Sort by date submitted", () => carousel.Filter(new FilterCriteria { Sort = SortMode.DateSubmitted }, false)); AddStep("Sort by date submitted", () => carousel.Filter(new FilterCriteria { Sort = SortMode.DateSubmitted }, false));
checkVisibleItemCount(diff: false, count: 8); checkVisibleItemCount(diff: false, count: 10);
checkVisibleItemCount(diff: true, count: 5); checkVisibleItemCount(diff: true, count: 5);
AddAssert("missing date are at end",
() => carousel.Items.OfType<DrawableCarouselBeatmapSet>().Reverse().TakeWhile(i => i.Item is CarouselBeatmapSet s && s.BeatmapSet.DateSubmitted == null).Count(), () => Is.EqualTo(4));
AddAssert("rest are at start", () => carousel.Items.OfType<DrawableCarouselBeatmapSet>().TakeWhile(i => i.Item is CarouselBeatmapSet s && s.BeatmapSet.DateSubmitted != null).Count(),
() => Is.EqualTo(6));
AddStep("Sort by date submitted and string", () => carousel.Filter(new FilterCriteria AddStep("Sort by date submitted and string", () => carousel.Filter(new FilterCriteria
{ {
Sort = SortMode.DateSubmitted, Sort = SortMode.DateSubmitted,
SearchText = zzz_string SearchText = zzz_string
}, false)); }, false));
checkVisibleItemCount(diff: false, count: 3); checkVisibleItemCount(diff: false, count: 5);
checkVisibleItemCount(diff: true, count: 5); checkVisibleItemCount(diff: true, count: 5);
AddAssert("missing date are at end",
() => carousel.Items.OfType<DrawableCarouselBeatmapSet>().Reverse().TakeWhile(i => i.Item is CarouselBeatmapSet s && s.BeatmapSet.DateSubmitted == null).Count(), () => Is.EqualTo(2));
AddAssert("rest are at start", () => carousel.Items.OfType<DrawableCarouselBeatmapSet>().TakeWhile(i => i.Item is CarouselBeatmapSet s && s.BeatmapSet.DateSubmitted != null).Count(),
() => Is.EqualTo(3));
} }
[Test] [Test]
@ -1129,7 +1145,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{ {
// until step required as we are querying against alive items, which are loaded asynchronously inside DrawableCarouselBeatmapSet. // until step required as we are querying against alive items, which are loaded asynchronously inside DrawableCarouselBeatmapSet.
AddUntilStep($"{count} {(diff ? "diffs" : "sets")} visible", () => AddUntilStep($"{count} {(diff ? "diffs" : "sets")} visible", () =>
carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count); carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible), () => Is.EqualTo(count));
} }
private void checkSelectionIsCentered() private void checkSelectionIsCentered()
@ -1190,8 +1206,11 @@ namespace osu.Game.Tests.Visual.SongSelect
{ {
get get
{ {
foreach (var item in Scroll.Children) foreach (var item in Scroll.Children.OrderBy(c => c.Y))
{ {
if (item.Item?.Visible != true)
continue;
yield return item; yield return item;
if (item is DrawableCarouselBeatmapSet set) if (item is DrawableCarouselBeatmapSet set)

View File

@ -61,6 +61,18 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("scroll to 500", () => scroll.ScrollTo(500)); AddStep("scroll to 500", () => scroll.ScrollTo(500));
AddUntilStep("scrolled to 500", () => Precision.AlmostEquals(scroll.Current, 500, 0.1f)); AddUntilStep("scrolled to 500", () => Precision.AlmostEquals(scroll.Current, 500, 0.1f));
AddAssert("button is visible", () => scroll.Button.State == Visibility.Visible); AddAssert("button is visible", () => scroll.Button.State == Visibility.Visible);
AddStep("click button", () =>
{
InputManager.MoveMouseTo(scroll.Button);
InputManager.Click(MouseButton.Left);
});
AddAssert("button is visible", () => scroll.Button.State == Visibility.Visible);
AddStep("user scroll down by 1", () => InputManager.ScrollVerticalBy(-1));
AddAssert("button is hidden", () => scroll.Button.State == Visibility.Hidden);
} }
[Test] [Test]
@ -71,6 +83,10 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("invoke action", () => scroll.Button.Action.Invoke()); AddStep("invoke action", () => scroll.Button.Action.Invoke());
AddUntilStep("scrolled back to start", () => Precision.AlmostEquals(scroll.Current, 0, 0.1f)); AddUntilStep("scrolled back to start", () => Precision.AlmostEquals(scroll.Current, 0, 0.1f));
AddStep("invoke action", () => scroll.Button.Action.Invoke());
AddAssert("scrolled to end", () => scroll.IsScrolledToEnd());
} }
[Test] [Test]
@ -85,6 +101,14 @@ namespace osu.Game.Tests.Visual.UserInterface
}); });
AddUntilStep("scrolled back to start", () => Precision.AlmostEquals(scroll.Current, 0, 0.1f)); AddUntilStep("scrolled back to start", () => Precision.AlmostEquals(scroll.Current, 0, 0.1f));
AddStep("click button", () =>
{
InputManager.MoveMouseTo(scroll.Button);
InputManager.Click(MouseButton.Left);
});
AddAssert("scrolled to end", () => scroll.IsScrolledToEnd());
} }
[Test] [Test]
@ -97,12 +121,12 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("hover button", () => InputManager.MoveMouseTo(scroll.Button)); AddStep("hover button", () => InputManager.MoveMouseTo(scroll.Button));
AddRepeatStep("click button", () => InputManager.Click(MouseButton.Left), 3); AddRepeatStep("click button", () => InputManager.Click(MouseButton.Left), 3);
AddAssert("invocation count is 1", () => invocationCount == 1); AddAssert("invocation count is 3", () => invocationCount == 3);
} }
private partial class TestScrollContainer : OverlayScrollContainer private partial class TestScrollContainer : OverlayScrollContainer
{ {
public new ScrollToTopButton Button => base.Button; public new ScrollBackButton Button => base.Button;
} }
} }
} }

View File

@ -11,7 +11,6 @@ using osu.Framework.Allocation;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics.Rendering; using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Rendering.Vertices; using osu.Framework.Graphics.Rendering.Vertices;
using osu.Framework.Graphics.Colour;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -259,8 +258,6 @@ namespace osu.Game.Graphics.Backgrounds
Vector2Extensions.Transform(triangleQuad.BottomRight * size, DrawInfo.Matrix) Vector2Extensions.Transform(triangleQuad.BottomRight * size, DrawInfo.Matrix)
); );
ColourInfo colourInfo = triangleColourInfo(DrawColourInfo.Colour, triangleQuad);
RectangleF textureCoords = new RectangleF( RectangleF textureCoords = new RectangleF(
triangleQuad.TopLeft.X - topLeft.X, triangleQuad.TopLeft.X - topLeft.X,
triangleQuad.TopLeft.Y - topLeft.Y, triangleQuad.TopLeft.Y - topLeft.Y,
@ -268,23 +265,12 @@ namespace osu.Game.Graphics.Backgrounds
triangleQuad.Height triangleQuad.Height
) / relativeSize; ) / relativeSize;
renderer.DrawQuad(texture, drawQuad, colourInfo, new RectangleF(0, 0, 1, 1), vertexBatch.AddAction, textureCoords: textureCoords); renderer.DrawQuad(texture, drawQuad, DrawColourInfo.Colour.Interpolate(triangleQuad), new RectangleF(0, 0, 1, 1), vertexBatch.AddAction, textureCoords: textureCoords);
} }
shader.Unbind(); shader.Unbind();
} }
private static ColourInfo triangleColourInfo(ColourInfo source, Quad quad)
{
return new ColourInfo
{
TopLeft = source.Interpolate(quad.TopLeft),
TopRight = source.Interpolate(quad.TopRight),
BottomLeft = source.Interpolate(quad.BottomLeft),
BottomRight = source.Interpolate(quad.BottomRight)
};
}
private static Quad clampToDrawable(Vector2 topLeft, Vector2 size) private static Quad clampToDrawable(Vector2 topLeft, Vector2 size)
{ {
float leftClamped = Math.Clamp(topLeft.X, 0f, 1f); float leftClamped = Math.Clamp(topLeft.X, 0f, 1f);

View File

@ -249,13 +249,7 @@ namespace osu.Game.Graphics.UserInterface
private ColourInfo getSegmentColour(SegmentInfo segment) private ColourInfo getSegmentColour(SegmentInfo segment)
{ {
var segmentColour = new ColourInfo var segmentColour = DrawColourInfo.Colour.Interpolate(new Quad(segment.Start, 0f, segment.End - segment.Start, 1f));
{
TopLeft = DrawColourInfo.Colour.Interpolate(new Vector2(segment.Start, 0f)),
TopRight = DrawColourInfo.Colour.Interpolate(new Vector2(segment.End, 0f)),
BottomLeft = DrawColourInfo.Colour.Interpolate(new Vector2(segment.Start, 1f)),
BottomRight = DrawColourInfo.Colour.Interpolate(new Vector2(segment.End, 1f))
};
var tierColour = segment.Tier >= 0 ? tierColours[segment.Tier] : new Colour4(0, 0, 0, 0); var tierColour = segment.Tier >= 0 ? tierColours[segment.Tier] : new Colour4(0, 0, 0, 0);
segmentColour.ApplyChild(tierColour); segmentColour.ApplyChild(tierColour);

View File

@ -39,6 +39,16 @@ namespace osu.Game.Localisation.SkinComponents
/// </summary> /// </summary>
public static LocalisableString TextElementTextDescription => new TranslatableString(getKey(@"text_element_text_description"), "The text to be displayed."); public static LocalisableString TextElementTextDescription => new TranslatableString(getKey(@"text_element_text_description"), "The text to be displayed.");
/// <summary>
/// "Corner radius"
/// </summary>
public static LocalisableString CornerRadius => new TranslatableString(getKey(@"corner_radius"), "Corner radius");
/// <summary>
/// "How rounded the corners should be."
/// </summary>
public static LocalisableString CornerRadiusDescription => new TranslatableString(getKey(@"corner_radius_description"), "How rounded the corners should be.");
private static string getKey(string key) => $"{prefix}:{key}"; private static string getKey(string key) => $"{prefix}:{key}";
} }
} }

View File

@ -39,6 +39,7 @@ namespace osu.Game.Overlays.Chat
private const float chatting_text_width = 220; private const float chatting_text_width = 220;
private const float search_icon_width = 40; private const float search_icon_width = 40;
private const float padding = 5;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider) private void load(OverlayColourProvider colourProvider)
@ -71,9 +72,10 @@ namespace osu.Game.Overlays.Chat
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Width = chatting_text_width, Width = chatting_text_width,
Masking = true, Masking = true,
Padding = new MarginPadding { Right = 5 }, Padding = new MarginPadding { Horizontal = padding },
Child = chattingText = new OsuSpriteText Child = chattingText = new OsuSpriteText
{ {
MaxWidth = chatting_text_width - padding * 2,
Font = OsuFont.Torus.With(size: 20), Font = OsuFont.Torus.With(size: 20),
Colour = colourProvider.Background1, Colour = colourProvider.Background1,
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
@ -97,7 +99,7 @@ namespace osu.Game.Overlays.Chat
new Container new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = 5 }, Padding = new MarginPadding { Right = padding },
Child = chatTextBox = new ChatTextBox Child = chatTextBox = new ChatTextBox
{ {
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,

View File

@ -5,6 +5,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -21,25 +22,29 @@ using osuTK.Graphics;
namespace osu.Game.Overlays namespace osu.Game.Overlays
{ {
/// <summary> /// <summary>
/// <see cref="UserTrackingScrollContainer"/> which provides <see cref="ScrollToTopButton"/>. Mostly used in <see cref="FullscreenOverlay{T}"/>. /// <see cref="UserTrackingScrollContainer"/> which provides <see cref="ScrollBackButton"/>. Mostly used in <see cref="FullscreenOverlay{T}"/>.
/// </summary> /// </summary>
public partial class OverlayScrollContainer : UserTrackingScrollContainer public partial class OverlayScrollContainer : UserTrackingScrollContainer
{ {
/// <summary> /// <summary>
/// Scroll position at which the <see cref="ScrollToTopButton"/> will be shown. /// Scroll position at which the <see cref="ScrollBackButton"/> will be shown.
/// </summary> /// </summary>
private const int button_scroll_position = 200; private const int button_scroll_position = 200;
protected readonly ScrollToTopButton Button; protected ScrollBackButton Button;
public OverlayScrollContainer() private readonly Bindable<float?> lastScrollTarget = new Bindable<float?>();
[BackgroundDependencyLoader]
private void load()
{ {
AddInternal(Button = new ScrollToTopButton AddInternal(Button = new ScrollBackButton
{ {
Anchor = Anchor.BottomRight, Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight, Origin = Anchor.BottomRight,
Margin = new MarginPadding(20), Margin = new MarginPadding(20),
Action = scrollToTop Action = scrollBack,
LastScrollTarget = { BindTarget = lastScrollTarget }
}); });
} }
@ -53,16 +58,31 @@ namespace osu.Game.Overlays
return; return;
} }
Button.State = Target > button_scroll_position ? Visibility.Visible : Visibility.Hidden; Button.State = Target > button_scroll_position || lastScrollTarget.Value != null ? Visibility.Visible : Visibility.Hidden;
} }
private void scrollToTop() protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default)
{ {
ScrollToStart(); base.OnUserScroll(value, animated, distanceDecay);
Button.State = Visibility.Hidden;
lastScrollTarget.Value = null;
} }
public partial class ScrollToTopButton : OsuHoverContainer private void scrollBack()
{
if (lastScrollTarget.Value == null)
{
lastScrollTarget.Value = Target;
ScrollToStart();
}
else
{
ScrollTo(lastScrollTarget.Value.Value);
lastScrollTarget.Value = null;
}
}
public partial class ScrollBackButton : OsuHoverContainer
{ {
private const int fade_duration = 500; private const int fade_duration = 500;
@ -88,8 +108,11 @@ namespace osu.Game.Overlays
private readonly Container content; private readonly Container content;
private readonly Box background; private readonly Box background;
private readonly SpriteIcon spriteIcon;
public ScrollToTopButton() public Bindable<float?> LastScrollTarget = new Bindable<float?>();
public ScrollBackButton()
: base(HoverSampleSet.ScrollToTop) : base(HoverSampleSet.ScrollToTop)
{ {
Size = new Vector2(50); Size = new Vector2(50);
@ -113,7 +136,7 @@ namespace osu.Game.Overlays
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
}, },
new SpriteIcon spriteIcon = new SpriteIcon
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
@ -134,6 +157,17 @@ namespace osu.Game.Overlays
flashColour = colourProvider.Light1; flashColour = colourProvider.Light1;
} }
protected override void LoadComplete()
{
base.LoadComplete();
LastScrollTarget.BindValueChanged(target =>
{
spriteIcon.RotateTo(target.NewValue != null ? 180 : 0, fade_duration, Easing.OutQuint);
TooltipText = target.NewValue != null ? CommonStrings.ButtonsBackToPrevious : CommonStrings.ButtonsBackToTop;
}, true);
}
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
{ {
background.FlashColour(flashColour, 800, Easing.OutQuint); background.FlashColour(flashColour, 800, Easing.OutQuint);

View File

@ -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 System;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Settings
{
/// <summary>
/// A <see cref="SettingsSlider{TValue,TSlider}"/> that displays its value as a percentage by default.
/// Mostly provided for convenience of use with <see cref="SettingSourceAttribute"/>.
/// </summary>
public partial class SettingsPercentageSlider<TValue> : SettingsSlider<TValue>
where TValue : struct, IEquatable<TValue>, IComparable<TValue>, IConvertible
{
protected override Drawable CreateControl() => ((RoundedSliderBar<TValue>)base.CreateControl()).With(sliderBar => sliderBar.DisplayAsPercentage = true);
}
}

View File

@ -0,0 +1,75 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterfaceV2;
using osuTK;
namespace osu.Game.Overlays.SkinEditor
{
public partial class NonSkinnableScreenPlaceholder : CompositeDrawable
{
[Resolved]
private SkinEditorOverlay? skinEditorOverlay { get; set; }
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
new Box
{
Colour = colourProvider.Dark6,
RelativeSizeAxes = Axes.Both,
Alpha = 0.95f,
},
new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0, 5),
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new SpriteIcon
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Icon = FontAwesome.Solid.ExclamationCircle,
Size = new Vector2(24),
Y = -5,
},
new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 18))
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
TextAnchor = Anchor.Centre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Text = "Please navigate to a skinnable screen using the scene library",
},
new RoundedButton
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 200,
Margin = new MarginPadding { Top = 20 },
Action = () => skinEditorOverlay?.Hide(),
Text = "Return to game"
}
}
},
};
}
}
}

View File

@ -82,6 +82,7 @@ namespace osu.Game.Overlays.SkinEditor
{ {
Text = Item.GetType().Name, Text = Item.GetType().Name,
Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold), Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold),
Alpha = 0,
Anchor = Anchor.BottomRight, Anchor = Anchor.BottomRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
}, },
@ -99,7 +100,6 @@ namespace osu.Game.Overlays.SkinEditor
base.LoadComplete(); base.LoadComplete();
updateSelectedState(); updateSelectedState();
this.FadeInFromZero(200, Easing.OutQuint);
} }
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)

View File

@ -7,15 +7,7 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Testing;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -26,16 +18,16 @@ namespace osu.Game.Overlays.SkinEditor
{ {
public partial class SkinBlueprintContainer : BlueprintContainer<ISerialisableDrawable> public partial class SkinBlueprintContainer : BlueprintContainer<ISerialisableDrawable>
{ {
private readonly Drawable target; private readonly ISerialisableDrawableContainer targetContainer;
private readonly List<BindableList<ISerialisableDrawable>> targetComponents = new List<BindableList<ISerialisableDrawable>>(); private readonly List<BindableList<ISerialisableDrawable>> targetComponents = new List<BindableList<ISerialisableDrawable>>();
[Resolved] [Resolved]
private SkinEditor editor { get; set; } = null!; private SkinEditor editor { get; set; } = null!;
public SkinBlueprintContainer(Drawable target) public SkinBlueprintContainer(ISerialisableDrawableContainer targetContainer)
{ {
this.target = target; this.targetContainer = targetContainer;
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -44,23 +36,11 @@ namespace osu.Game.Overlays.SkinEditor
SelectedItems.BindTo(editor.SelectedComponents); SelectedItems.BindTo(editor.SelectedComponents);
// track each target container on the current screen.
var targetContainers = target.ChildrenOfType<ISerialisableDrawableContainer>().ToArray();
if (targetContainers.Length == 0)
{
AddInternal(new NonSkinnableScreenPlaceholder());
return;
}
foreach (var targetContainer in targetContainers)
{
var bindableList = new BindableList<ISerialisableDrawable> { BindTarget = targetContainer.Components }; var bindableList = new BindableList<ISerialisableDrawable> { BindTarget = targetContainer.Components };
bindableList.BindCollectionChanged(componentsChanged, true); bindableList.BindCollectionChanged(componentsChanged, true);
targetComponents.Add(bindableList); targetComponents.Add(bindableList);
} }
}
private void componentsChanged(object? sender, NotifyCollectionChangedEventArgs e) => Schedule(() => private void componentsChanged(object? sender, NotifyCollectionChangedEventArgs e) => Schedule(() =>
{ {
@ -160,65 +140,5 @@ namespace osu.Game.Overlays.SkinEditor
foreach (var list in targetComponents) foreach (var list in targetComponents)
list.UnbindAll(); list.UnbindAll();
} }
public partial class NonSkinnableScreenPlaceholder : CompositeDrawable
{
[Resolved]
private SkinEditorOverlay? skinEditorOverlay { get; set; }
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
new Box
{
Colour = colourProvider.Dark6,
RelativeSizeAxes = Axes.Both,
Alpha = 0.95f,
},
new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0, 5),
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new SpriteIcon
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Icon = FontAwesome.Solid.ExclamationCircle,
Size = new Vector2(24),
Y = -5,
},
new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 18))
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
TextAnchor = Anchor.Centre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Text = "Please navigate to a skinnable screen using the scene library",
},
new RoundedButton
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 200,
Margin = new MarginPadding { Top = 20 },
Action = () => skinEditorOverlay?.Hide(),
Text = "Return to game"
}
}
},
};
}
}
} }
} }

View File

@ -309,7 +309,8 @@ namespace osu.Game.Overlays.SkinEditor
changeHandler?.Dispose(); changeHandler?.Dispose();
// Immediately clear the previous blueprint container to ensure it doesn't try to interact with the old target. // Immediately clear the previous blueprint container to ensure it doesn't try to interact with the old target.
content?.Clear(); if (content?.Child is SkinBlueprintContainer)
content.Clear();
Scheduler.AddOnce(loadBlueprintContainer); Scheduler.AddOnce(loadBlueprintContainer);
Scheduler.AddOnce(populateSettings); Scheduler.AddOnce(populateSettings);
@ -328,17 +329,18 @@ namespace osu.Game.Overlays.SkinEditor
foreach (var toolbox in componentsSidebar.OfType<SkinComponentToolbox>()) foreach (var toolbox in componentsSidebar.OfType<SkinComponentToolbox>())
toolbox.Expire(); toolbox.Expire();
if (target.NewValue == null) componentsSidebar.Clear();
return; SelectedComponents.Clear();
Debug.Assert(content != null); Debug.Assert(content != null);
SelectedComponents.Clear();
var skinComponentsContainer = getTarget(target.NewValue); var skinComponentsContainer = getTarget(target.NewValue);
if (skinComponentsContainer == null) if (target.NewValue == null || skinComponentsContainer == null)
{
content.Child = new NonSkinnableScreenPlaceholder();
return; return;
}
changeHandler = new SkinEditorChangeHandler(skinComponentsContainer); changeHandler = new SkinEditorChangeHandler(skinComponentsContainer);
changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true);
@ -561,7 +563,50 @@ namespace osu.Game.Overlays.SkinEditor
changeHandler?.BeginChange(); changeHandler?.BeginChange();
foreach (var item in items) foreach (var item in items)
availableTargets.FirstOrDefault(t => t.Components.Contains(item))?.Remove(item); availableTargets.FirstOrDefault(t => t.Components.Contains(item))?.Remove(item, true);
changeHandler?.EndChange();
}
public void BringSelectionToFront()
{
if (getTarget(selectedTarget.Value) is not SkinComponentsContainer target)
return;
changeHandler?.BeginChange();
// Iterating by target components order ensures we maintain the same order across selected components, regardless
// of the order they were selected in.
foreach (var d in target.Components.ToArray())
{
if (!SelectedComponents.Contains(d))
continue;
target.Remove(d, false);
// Selection would be reset by the remove.
SelectedComponents.Add(d);
target.Add(d);
}
changeHandler?.EndChange();
}
public void SendSelectionToBack()
{
if (getTarget(selectedTarget.Value) is not SkinComponentsContainer target)
return;
changeHandler?.BeginChange();
foreach (var d in target.Components.ToArray())
{
if (SelectedComponents.Contains(d))
continue;
target.Remove(d, false);
target.Add(d);
}
changeHandler?.EndChange(); changeHandler?.EndChange();
} }

View File

@ -13,6 +13,7 @@ using osu.Framework.Utils;
using osu.Game.Extensions; using osu.Game.Extensions;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit.Components.Menus;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK; using osuTK;
@ -206,6 +207,14 @@ namespace osu.Game.Overlays.SkinEditor
((Drawable)blueprint.Item).Position = Vector2.Zero; ((Drawable)blueprint.Item).Position = Vector2.Zero;
}); });
yield return new EditorMenuItemSpacer();
yield return new OsuMenuItem("Bring to front", MenuItemType.Standard, () => skinEditor.BringSelectionToFront());
yield return new OsuMenuItem("Send to back", MenuItemType.Standard, () => skinEditor.SendSelectionToBack());
yield return new EditorMenuItemSpacer();
foreach (var item in base.GetContextMenuItemsForSelection(selection)) foreach (var item in base.GetContextMenuItemsForSelection(selection))
yield return item; yield return item;

View File

@ -7,7 +7,6 @@ using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
@ -33,7 +32,7 @@ namespace osu.Game.Rulesets.Mods
public override string SettingDescription => base.SettingDescription.Replace(MinimumAccuracy.ToString(), MinimumAccuracy.Value.ToString("##%", NumberFormatInfo.InvariantInfo)); public override string SettingDescription => base.SettingDescription.Replace(MinimumAccuracy.ToString(), MinimumAccuracy.Value.ToString("##%", NumberFormatInfo.InvariantInfo));
[SettingSource("Minimum accuracy", "Trigger a failure if your accuracy goes below this value.", SettingControlType = typeof(SettingsSlider<double, PercentSlider>))] [SettingSource("Minimum accuracy", "Trigger a failure if your accuracy goes below this value.", SettingControlType = typeof(SettingsPercentageSlider<double>))]
public BindableNumber<double> MinimumAccuracy { get; } = new BindableDouble public BindableNumber<double> MinimumAccuracy { get; } = new BindableDouble
{ {
MinValue = 0.60, MinValue = 0.60,
@ -69,12 +68,4 @@ namespace osu.Game.Rulesets.Mods
return scoreProcessor.ComputeAccuracy(score); return scoreProcessor.ComputeAccuracy(score);
} }
} }
public partial class PercentSlider : RoundedSliderBar<double>
{
public PercentSlider()
{
DisplayAsPercentage = true;
}
}
} }

View File

@ -0,0 +1,58 @@
// 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.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Configuration;
using osu.Game.Localisation.SkinComponents;
using osu.Game.Overlays.Settings;
using osu.Game.Skinning;
using osu.Game.Users.Drawables;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
public partial class PlayerAvatar : CompositeDrawable, ISerialisableDrawable
{
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.CornerRadius), nameof(SkinnableComponentStrings.CornerRadiusDescription),
SettingControlType = typeof(SettingsPercentageSlider<float>))]
public new BindableFloat CornerRadius { get; set; } = new BindableFloat(0.25f)
{
MinValue = 0,
MaxValue = 0.5f,
Precision = 0.01f
};
private readonly UpdateableAvatar avatar;
private const float default_size = 80f;
public PlayerAvatar()
{
Size = new Vector2(default_size);
InternalChild = avatar = new UpdateableAvatar(isInteractive: false)
{
RelativeSizeAxes = Axes.Both,
Masking = true
};
}
[BackgroundDependencyLoader]
private void load(GameplayState gameplayState)
{
avatar.User = gameplayState.Score.ScoreInfo.User;
}
protected override void LoadComplete()
{
base.LoadComplete();
CornerRadius.BindValueChanged(e => avatar.CornerRadius = e.NewValue * default_size, true);
}
public bool UsesFixedAnchor { get; set; }
}
}

View File

@ -0,0 +1,36 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Skinning;
using osu.Game.Users.Drawables;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
public partial class PlayerFlag : CompositeDrawable, ISerialisableDrawable
{
private readonly UpdateableFlag flag;
private const float default_size = 40f;
public PlayerFlag()
{
Size = new Vector2(default_size, default_size / 1.4f);
InternalChild = flag = new UpdateableFlag
{
RelativeSizeAxes = Axes.Both,
};
}
[BackgroundDependencyLoader]
private void load(GameplayState gameplayState)
{
flag.CountryCode = gameplayState.Score.ScoreInfo.User.CountryCode;
}
public bool UsesFixedAnchor { get; set; }
}
}

View File

@ -61,7 +61,7 @@ namespace osu.Game.Screens.Select.Carousel
if (!(other is CarouselBeatmapSet otherSet)) if (!(other is CarouselBeatmapSet otherSet))
return base.CompareTo(criteria, other); return base.CompareTo(criteria, other);
int comparison = 0; int comparison;
switch (criteria.Sort) switch (criteria.Sort)
{ {
@ -87,11 +87,7 @@ namespace osu.Game.Screens.Select.Carousel
break; break;
case SortMode.DateRanked: case SortMode.DateRanked:
// Beatmaps which have no ranked date should already be filtered away in this mode. comparison = Nullable.Compare(otherSet.BeatmapSet.DateRanked, BeatmapSet.DateRanked);
if (BeatmapSet.DateRanked == null || otherSet.BeatmapSet.DateRanked == null)
break;
comparison = otherSet.BeatmapSet.DateRanked.Value.CompareTo(BeatmapSet.DateRanked.Value);
break; break;
case SortMode.LastPlayed: case SortMode.LastPlayed:
@ -111,11 +107,7 @@ namespace osu.Game.Screens.Select.Carousel
break; break;
case SortMode.DateSubmitted: case SortMode.DateSubmitted:
// Beatmaps which have no submitted date should already be filtered away in this mode. comparison = Nullable.Compare(otherSet.BeatmapSet.DateSubmitted, BeatmapSet.DateSubmitted);
if (BeatmapSet.DateSubmitted == null || otherSet.BeatmapSet.DateSubmitted == null)
break;
comparison = otherSet.BeatmapSet.DateSubmitted.Value.CompareTo(BeatmapSet.DateSubmitted.Value);
break; break;
} }
@ -153,12 +145,7 @@ namespace osu.Game.Screens.Select.Carousel
{ {
base.Filter(criteria); base.Filter(criteria);
bool filtered = Items.All(i => i.Filtered.Value); Filtered.Value = Items.All(i => i.Filtered.Value);
filtered |= criteria.Sort == SortMode.DateRanked && BeatmapSet.DateRanked == null;
filtered |= criteria.Sort == SortMode.DateSubmitted && BeatmapSet.DateSubmitted == null;
Filtered.Value = filtered;
} }
public override string ToString() => BeatmapSet.ToString(); public override string ToString() => BeatmapSet.ToString();

View File

@ -45,6 +45,7 @@ namespace osu.Game.Skinning
/// Remove an existing skinnable component from this target. /// Remove an existing skinnable component from this target.
/// </summary> /// </summary>
/// <param name="component">The component to remove.</param> /// <param name="component">The component to remove.</param>
void Remove(ISerialisableDrawable component); /// <param name="disposeImmediately">Whether removed items should be immediately disposed.</param>
void Remove(ISerialisableDrawable component, bool disposeImmediately);
} }
} }

View File

@ -100,7 +100,7 @@ namespace osu.Game.Skinning
/// <inheritdoc cref="ISerialisableDrawableContainer"/> /// <inheritdoc cref="ISerialisableDrawableContainer"/>
/// <exception cref="NotSupportedException">Thrown when attempting to add an element to a target which is not supported by the current skin.</exception> /// <exception cref="NotSupportedException">Thrown when attempting to add an element to a target which is not supported by the current skin.</exception>
/// <exception cref="ArgumentException">Thrown if the provided instance is not a <see cref="Drawable"/>.</exception> /// <exception cref="ArgumentException">Thrown if the provided instance is not a <see cref="Drawable"/>.</exception>
public void Remove(ISerialisableDrawable component) public void Remove(ISerialisableDrawable component, bool disposeImmediately)
{ {
if (content == null) if (content == null)
throw new NotSupportedException("Attempting to remove a new component from a target container which is not supported by the current skin."); throw new NotSupportedException("Attempting to remove a new component from a target container which is not supported by the current skin.");
@ -108,7 +108,7 @@ namespace osu.Game.Skinning
if (!(component is Drawable drawable)) if (!(component is Drawable drawable))
throw new ArgumentException($"Provided argument must be of type {nameof(Drawable)}.", nameof(component)); throw new ArgumentException($"Provided argument must be of type {nameof(Drawable)}.", nameof(component));
content.Remove(drawable, true); content.Remove(drawable, disposeImmediately);
components.Remove(component); components.Remove(component);
} }

View File

@ -58,7 +58,11 @@ namespace osu.Game.Storyboards.Drawables
using (drawableVideo.BeginAbsoluteSequence(Video.StartTime)) using (drawableVideo.BeginAbsoluteSequence(Video.StartTime))
{ {
Schedule(() => drawableVideo.PlaybackPosition = Time.Current - Video.StartTime); Schedule(() => drawableVideo.PlaybackPosition = Time.Current - Video.StartTime);
drawableVideo.FadeIn(500); drawableVideo.FadeIn(500);
using (drawableVideo.BeginDelayedSequence(drawableVideo.Duration - 500))
drawableVideo.FadeOut(500);
} }
} }
} }

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -30,14 +28,14 @@ namespace osu.Game.Users.Drawables
/// Perform an action in addition to showing the country ranking. /// Perform an action in addition to showing the country ranking.
/// This should be used to perform auxiliary tasks and not as a primary action for clicking a flag (to maintain a consistent UX). /// This should be used to perform auxiliary tasks and not as a primary action for clicking a flag (to maintain a consistent UX).
/// </summary> /// </summary>
public Action Action; public Action? Action;
public UpdateableFlag(CountryCode countryCode = CountryCode.Unknown) public UpdateableFlag(CountryCode countryCode = CountryCode.Unknown)
{ {
CountryCode = countryCode; CountryCode = countryCode;
} }
protected override Drawable CreateDrawable(CountryCode countryCode) protected override Drawable? CreateDrawable(CountryCode countryCode)
{ {
if (countryCode == CountryCode.Unknown && !ShowPlaceholderOnUnknown) if (countryCode == CountryCode.Unknown && !ShowPlaceholderOnUnknown)
return null; return null;
@ -56,8 +54,8 @@ namespace osu.Game.Users.Drawables
}; };
} }
[Resolved(canBeNull: true)] [Resolved]
private RankingsOverlay rankingsOverlay { get; set; } private RankingsOverlay? rankingsOverlay { get; set; }
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
{ {

View File

@ -35,8 +35,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="10.20.0" /> <PackageReference Include="Realm" Version="10.20.0" />
<PackageReference Include="ppy.osu.Framework" Version="2023.131.0" /> <PackageReference Include="ppy.osu.Framework" Version="2023.228.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.202.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2023.228.0" />
<PackageReference Include="Sentry" Version="3.28.1" /> <PackageReference Include="Sentry" Version="3.28.1" />
<PackageReference Include="SharpCompress" Version="0.32.2" /> <PackageReference Include="SharpCompress" Version="0.32.2" />
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit" Version="3.13.3" />

View File

@ -16,6 +16,6 @@
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier> <RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.131.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2023.228.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>