mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 20:22:55 +08:00
Merge branch 'master' into make_skin_reset_dangerous
This commit is contained in:
commit
5213bfa5c9
@ -3,15 +3,53 @@
|
||||
#
|
||||
# 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"
|
||||
|
||||
dotnet remove $CSPROJ package ppy.osu.Framework;
|
||||
dotnet sln $SLN add ../osu-framework/osu.Framework/osu.Framework.csproj ../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj;
|
||||
dotnet add $CSPROJ reference ../osu-framework/osu.Framework/osu.Framework.csproj
|
||||
dotnet remove $GAME_CSPROJ reference ppy.osu.Framework;
|
||||
dotnet remove $ANDROID_PROPS reference ppy.osu.Framework.Android;
|
||||
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
|
||||
$TMP=New-TemporaryFile
|
||||
$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
|
||||
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
|
||||
|
@ -5,14 +5,41 @@
|
||||
#
|
||||
# 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"
|
||||
|
||||
dotnet remove $CSPROJ package ppy.osu.Framework
|
||||
dotnet sln $SLN add ../osu-framework/osu.Framework/osu.Framework.csproj ../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj
|
||||
dotnet add $CSPROJ reference ../osu-framework/osu.Framework/osu.Framework.csproj
|
||||
dotnet remove $GAME_CSPROJ reference ppy.osu.Framework
|
||||
dotnet remove $ANDROID_PROPS reference ppy.osu.Framework.Android
|
||||
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)
|
||||
|
||||
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
|
||||
|
@ -8,9 +8,13 @@
|
||||
<!-- NullabilityInfoContextSupport is disabled by default for Android -->
|
||||
<NullabilityInfoContextSupport>true</NullabilityInfoContextSupport>
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
|
||||
</PropertyGroup>
|
||||
<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>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
15
osu.Android/Properties/AndroidManifestOverlay.xml
Normal file
15
osu.Android/Properties/AndroidManifestOverlay.xml
Normal 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>
|
@ -167,8 +167,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
{
|
||||
if (bodySprite != null)
|
||||
{
|
||||
bodySprite.Origin = Anchor.BottomCentre;
|
||||
bodySprite.Scale = new Vector2(bodySprite.Scale.X, Math.Abs(bodySprite.Scale.Y) * -1);
|
||||
bodySprite.Origin = Anchor.TopCentre;
|
||||
bodySprite.Anchor = Anchor.BottomCentre; // needs to be flipped due to scale flip in Update.
|
||||
}
|
||||
|
||||
if (light != null)
|
||||
@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
if (bodySprite != null)
|
||||
{
|
||||
bodySprite.Origin = Anchor.TopCentre;
|
||||
bodySprite.Scale = new Vector2(bodySprite.Scale.X, Math.Abs(bodySprite.Scale.Y));
|
||||
bodySprite.Anchor = Anchor.TopCentre;
|
||||
}
|
||||
|
||||
if (light != null)
|
||||
@ -211,11 +211,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
base.Update();
|
||||
missFadeTime.Value ??= holdNote.HoldBrokenTime;
|
||||
|
||||
int scaleDirection = (direction.Value == ScrollingDirection.Down ? 1 : -1);
|
||||
|
||||
// here we go...
|
||||
switch (bodyStyle)
|
||||
{
|
||||
case LegacyNoteBodyStyle.Stretch:
|
||||
// this is how lazer works by default. nothing required.
|
||||
if (bodySprite != null)
|
||||
bodySprite.Scale = new Vector2(1, scaleDirection);
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -228,7 +232,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
|
||||
bodySprite.FillMode = FillMode.Stretch;
|
||||
// i dunno this looks about right??
|
||||
bodySprite.Scale = new Vector2(1, 32800 / sprite.DrawHeight);
|
||||
bodySprite.Scale = new Vector2(1, scaleDirection * 32800 / sprite.DrawHeight);
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -8,6 +8,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
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.";
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ using osu.Game.Overlays.Settings;
|
||||
namespace osu.Game.Tests.Mods
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class SettingsSourceAttributeTest
|
||||
public partial class SettingSourceAttributeTest
|
||||
{
|
||||
[Test]
|
||||
public void TestOrdering()
|
BIN
osu.Game.Tests/Resources/Archives/modified-argon-20230305.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-argon-20230305.osk
Normal file
Binary file not shown.
@ -48,7 +48,9 @@ namespace osu.Game.Tests.Skins
|
||||
// Covers BPM counter.
|
||||
"Archives/modified-default-20221205.osk",
|
||||
// Covers judgement counter.
|
||||
"Archives/modified-default-20230117.osk"
|
||||
"Archives/modified-default-20230117.osk",
|
||||
// Covers player avatar and flag.
|
||||
"Archives/modified-argon-20230305.osk",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,9 +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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Testing;
|
||||
@ -21,7 +23,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneSkinEditor : PlayerTestScene
|
||||
{
|
||||
private SkinEditor? skinEditor;
|
||||
private SkinEditor skinEditor = null!;
|
||||
|
||||
protected override bool Autoplay => true;
|
||||
|
||||
@ -31,26 +33,75 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Cached]
|
||||
public readonly EditorClipboard Clipboard = new EditorClipboard();
|
||||
|
||||
private SkinComponentsContainer targetContainer => Player.ChildrenOfType<SkinComponentsContainer>().First();
|
||||
|
||||
[SetUpSteps]
|
||||
public override void 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", () =>
|
||||
{
|
||||
skinEditor?.Expire();
|
||||
if (skinEditor.IsNotNull())
|
||||
skinEditor.Expire();
|
||||
Player.ScaleTo(0.4f);
|
||||
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]
|
||||
public void TestToggleEditor()
|
||||
{
|
||||
AddToggleStep("toggle editor visibility", _ => skinEditor!.ToggleVisibility());
|
||||
AddToggleStep("toggle editor visibility", _ => skinEditor.ToggleVisibility());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -63,7 +114,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
var blueprint = skinEditor.ChildrenOfType<SkinBlueprint>().First(b => b.Item is BarHitErrorMeter);
|
||||
|
||||
hitErrorMeter = (BarHitErrorMeter)blueprint.Item;
|
||||
skinEditor!.SelectedComponents.Clear();
|
||||
skinEditor.SelectedComponents.Clear();
|
||||
skinEditor.SelectedComponents.Add(blueprint.Item);
|
||||
});
|
||||
|
||||
|
@ -15,6 +15,7 @@ using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.SkinEditor;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||
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);
|
||||
}
|
||||
|
||||
[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()
|
||||
{
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
|
@ -516,15 +516,20 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
sets.Clear();
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
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);
|
||||
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.Title = $"submitted: {set.DateSubmitted}");
|
||||
|
||||
sets.Add(set);
|
||||
}
|
||||
});
|
||||
@ -532,15 +537,26 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
loadBeatmaps(sets);
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
{
|
||||
Sort = SortMode.DateSubmitted,
|
||||
SearchText = zzz_string
|
||||
}, false));
|
||||
checkVisibleItemCount(diff: false, count: 3);
|
||||
checkVisibleItemCount(diff: false, 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]
|
||||
@ -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.
|
||||
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()
|
||||
@ -1190,8 +1206,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
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;
|
||||
|
||||
if (item is DrawableCarouselBeatmapSet set)
|
||||
|
@ -61,6 +61,18 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("scroll to 500", () => scroll.ScrollTo(500));
|
||||
AddUntilStep("scrolled to 500", () => Precision.AlmostEquals(scroll.Current, 500, 0.1f));
|
||||
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]
|
||||
@ -71,6 +83,10 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("invoke action", () => scroll.Button.Action.Invoke());
|
||||
|
||||
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]
|
||||
@ -85,6 +101,14 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
});
|
||||
|
||||
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]
|
||||
@ -97,12 +121,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("hover button", () => InputManager.MoveMouseTo(scroll.Button));
|
||||
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
|
||||
{
|
||||
public new ScrollToTopButton Button => base.Button;
|
||||
public new ScrollBackButton Button => base.Button;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ using osu.Framework.Allocation;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Framework.Graphics.Rendering.Vertices;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
@ -259,8 +258,6 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
Vector2Extensions.Transform(triangleQuad.BottomRight * size, DrawInfo.Matrix)
|
||||
);
|
||||
|
||||
ColourInfo colourInfo = triangleColourInfo(DrawColourInfo.Colour, triangleQuad);
|
||||
|
||||
RectangleF textureCoords = new RectangleF(
|
||||
triangleQuad.TopLeft.X - topLeft.X,
|
||||
triangleQuad.TopLeft.Y - topLeft.Y,
|
||||
@ -268,23 +265,12 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
triangleQuad.Height
|
||||
) / 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();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
float leftClamped = Math.Clamp(topLeft.X, 0f, 1f);
|
||||
|
@ -249,13 +249,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
private ColourInfo getSegmentColour(SegmentInfo segment)
|
||||
{
|
||||
var segmentColour = new ColourInfo
|
||||
{
|
||||
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 segmentColour = DrawColourInfo.Colour.Interpolate(new Quad(segment.Start, 0f, segment.End - segment.Start, 1f));
|
||||
|
||||
var tierColour = segment.Tier >= 0 ? tierColours[segment.Tier] : new Colour4(0, 0, 0, 0);
|
||||
segmentColour.ApplyChild(tierColour);
|
||||
|
@ -39,6 +39,16 @@ namespace osu.Game.Localisation.SkinComponents
|
||||
/// </summary>
|
||||
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}";
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ namespace osu.Game.Overlays.Chat
|
||||
|
||||
private const float chatting_text_width = 220;
|
||||
private const float search_icon_width = 40;
|
||||
private const float padding = 5;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
@ -71,9 +72,10 @@ namespace osu.Game.Overlays.Chat
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = chatting_text_width,
|
||||
Masking = true,
|
||||
Padding = new MarginPadding { Right = 5 },
|
||||
Padding = new MarginPadding { Horizontal = padding },
|
||||
Child = chattingText = new OsuSpriteText
|
||||
{
|
||||
MaxWidth = chatting_text_width - padding * 2,
|
||||
Font = OsuFont.Torus.With(size: 20),
|
||||
Colour = colourProvider.Background1,
|
||||
Anchor = Anchor.CentreRight,
|
||||
@ -97,7 +99,7 @@ namespace osu.Game.Overlays.Chat
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Right = 5 },
|
||||
Padding = new MarginPadding { Right = padding },
|
||||
Child = chatTextBox = new ChatTextBox
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -21,25 +22,29 @@ using osuTK.Graphics;
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
/// <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>
|
||||
public partial class OverlayScrollContainer : UserTrackingScrollContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Scroll position at which the <see cref="ScrollToTopButton"/> will be shown.
|
||||
/// Scroll position at which the <see cref="ScrollBackButton"/> will be shown.
|
||||
/// </summary>
|
||||
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,
|
||||
Origin = Anchor.BottomRight,
|
||||
Margin = new MarginPadding(20),
|
||||
Action = scrollToTop
|
||||
Action = scrollBack,
|
||||
LastScrollTarget = { BindTarget = lastScrollTarget }
|
||||
});
|
||||
}
|
||||
|
||||
@ -53,16 +58,31 @@ namespace osu.Game.Overlays
|
||||
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();
|
||||
Button.State = Visibility.Hidden;
|
||||
base.OnUserScroll(value, animated, distanceDecay);
|
||||
|
||||
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;
|
||||
|
||||
@ -88,8 +108,11 @@ namespace osu.Game.Overlays
|
||||
|
||||
private readonly Container content;
|
||||
private readonly Box background;
|
||||
private readonly SpriteIcon spriteIcon;
|
||||
|
||||
public ScrollToTopButton()
|
||||
public Bindable<float?> LastScrollTarget = new Bindable<float?>();
|
||||
|
||||
public ScrollBackButton()
|
||||
: base(HoverSampleSet.ScrollToTop)
|
||||
{
|
||||
Size = new Vector2(50);
|
||||
@ -113,7 +136,7 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
new SpriteIcon
|
||||
spriteIcon = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -134,6 +157,17 @@ namespace osu.Game.Overlays
|
||||
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)
|
||||
{
|
||||
background.FlashColour(flashColour, 800, Easing.OutQuint);
|
||||
|
20
osu.Game/Overlays/Settings/SettingsPercentageSlider.cs
Normal file
20
osu.Game/Overlays/Settings/SettingsPercentageSlider.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -82,6 +82,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
Text = Item.GetType().Name,
|
||||
Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold),
|
||||
Alpha = 0,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.TopRight,
|
||||
},
|
||||
@ -99,7 +100,6 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
base.LoadComplete();
|
||||
|
||||
updateSelectedState();
|
||||
this.FadeInFromZero(200, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
|
@ -7,15 +7,7 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
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.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Skinning;
|
||||
@ -26,16 +18,16 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
public partial class SkinBlueprintContainer : BlueprintContainer<ISerialisableDrawable>
|
||||
{
|
||||
private readonly Drawable target;
|
||||
private readonly ISerialisableDrawableContainer targetContainer;
|
||||
|
||||
private readonly List<BindableList<ISerialisableDrawable>> targetComponents = new List<BindableList<ISerialisableDrawable>>();
|
||||
|
||||
[Resolved]
|
||||
private SkinEditor editor { get; set; } = null!;
|
||||
|
||||
public SkinBlueprintContainer(Drawable target)
|
||||
public SkinBlueprintContainer(ISerialisableDrawableContainer targetContainer)
|
||||
{
|
||||
this.target = target;
|
||||
this.targetContainer = targetContainer;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -44,22 +36,10 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
|
||||
SelectedItems.BindTo(editor.SelectedComponents);
|
||||
|
||||
// track each target container on the current screen.
|
||||
var targetContainers = target.ChildrenOfType<ISerialisableDrawableContainer>().ToArray();
|
||||
var bindableList = new BindableList<ISerialisableDrawable> { BindTarget = targetContainer.Components };
|
||||
bindableList.BindCollectionChanged(componentsChanged, true);
|
||||
|
||||
if (targetContainers.Length == 0)
|
||||
{
|
||||
AddInternal(new NonSkinnableScreenPlaceholder());
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var targetContainer in targetContainers)
|
||||
{
|
||||
var bindableList = new BindableList<ISerialisableDrawable> { BindTarget = targetContainer.Components };
|
||||
bindableList.BindCollectionChanged(componentsChanged, true);
|
||||
|
||||
targetComponents.Add(bindableList);
|
||||
}
|
||||
targetComponents.Add(bindableList);
|
||||
}
|
||||
|
||||
private void componentsChanged(object? sender, NotifyCollectionChangedEventArgs e) => Schedule(() =>
|
||||
@ -160,65 +140,5 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
foreach (var list in targetComponents)
|
||||
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"
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -309,7 +309,8 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
changeHandler?.Dispose();
|
||||
|
||||
// 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(populateSettings);
|
||||
@ -328,17 +329,18 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
foreach (var toolbox in componentsSidebar.OfType<SkinComponentToolbox>())
|
||||
toolbox.Expire();
|
||||
|
||||
if (target.NewValue == null)
|
||||
return;
|
||||
componentsSidebar.Clear();
|
||||
SelectedComponents.Clear();
|
||||
|
||||
Debug.Assert(content != null);
|
||||
|
||||
SelectedComponents.Clear();
|
||||
|
||||
var skinComponentsContainer = getTarget(target.NewValue);
|
||||
|
||||
if (skinComponentsContainer == null)
|
||||
if (target.NewValue == null || skinComponentsContainer == null)
|
||||
{
|
||||
content.Child = new NonSkinnableScreenPlaceholder();
|
||||
return;
|
||||
}
|
||||
|
||||
changeHandler = new SkinEditorChangeHandler(skinComponentsContainer);
|
||||
changeHandler.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true);
|
||||
@ -561,7 +563,50 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
changeHandler?.BeginChange();
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Utils;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Screens.Edit.Components.Menus;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
@ -206,6 +207,14 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
((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))
|
||||
yield return item;
|
||||
|
||||
|
@ -7,7 +7,6 @@ using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
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));
|
||||
|
||||
[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
|
||||
{
|
||||
MinValue = 0.60,
|
||||
@ -69,12 +68,4 @@ namespace osu.Game.Rulesets.Mods
|
||||
return scoreProcessor.ComputeAccuracy(score);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class PercentSlider : RoundedSliderBar<double>
|
||||
{
|
||||
public PercentSlider()
|
||||
{
|
||||
DisplayAsPercentage = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
58
osu.Game/Screens/Play/HUD/PlayerAvatar.cs
Normal file
58
osu.Game/Screens/Play/HUD/PlayerAvatar.cs
Normal 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; }
|
||||
}
|
||||
}
|
36
osu.Game/Screens/Play/HUD/PlayerFlag.cs
Normal file
36
osu.Game/Screens/Play/HUD/PlayerFlag.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -61,7 +61,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
if (!(other is CarouselBeatmapSet otherSet))
|
||||
return base.CompareTo(criteria, other);
|
||||
|
||||
int comparison = 0;
|
||||
int comparison;
|
||||
|
||||
switch (criteria.Sort)
|
||||
{
|
||||
@ -87,11 +87,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
break;
|
||||
|
||||
case SortMode.DateRanked:
|
||||
// Beatmaps which have no ranked date should already be filtered away in this mode.
|
||||
if (BeatmapSet.DateRanked == null || otherSet.BeatmapSet.DateRanked == null)
|
||||
break;
|
||||
|
||||
comparison = otherSet.BeatmapSet.DateRanked.Value.CompareTo(BeatmapSet.DateRanked.Value);
|
||||
comparison = Nullable.Compare(otherSet.BeatmapSet.DateRanked, BeatmapSet.DateRanked);
|
||||
break;
|
||||
|
||||
case SortMode.LastPlayed:
|
||||
@ -111,11 +107,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
break;
|
||||
|
||||
case SortMode.DateSubmitted:
|
||||
// Beatmaps which have no submitted date should already be filtered away in this mode.
|
||||
if (BeatmapSet.DateSubmitted == null || otherSet.BeatmapSet.DateSubmitted == null)
|
||||
break;
|
||||
|
||||
comparison = otherSet.BeatmapSet.DateSubmitted.Value.CompareTo(BeatmapSet.DateSubmitted.Value);
|
||||
comparison = Nullable.Compare(otherSet.BeatmapSet.DateSubmitted, BeatmapSet.DateSubmitted);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -153,12 +145,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
base.Filter(criteria);
|
||||
|
||||
bool filtered = 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;
|
||||
Filtered.Value = Items.All(i => i.Filtered.Value);
|
||||
}
|
||||
|
||||
public override string ToString() => BeatmapSet.ToString();
|
||||
|
@ -45,6 +45,7 @@ namespace osu.Game.Skinning
|
||||
/// Remove an existing skinnable component from this target.
|
||||
/// </summary>
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ namespace osu.Game.Skinning
|
||||
/// <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="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)
|
||||
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))
|
||||
throw new ArgumentException($"Provided argument must be of type {nameof(Drawable)}.", nameof(component));
|
||||
|
||||
content.Remove(drawable, true);
|
||||
content.Remove(drawable, disposeImmediately);
|
||||
components.Remove(component);
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,11 @@ namespace osu.Game.Storyboards.Drawables
|
||||
using (drawableVideo.BeginAbsoluteSequence(Video.StartTime))
|
||||
{
|
||||
Schedule(() => drawableVideo.PlaybackPosition = Time.Current - Video.StartTime);
|
||||
|
||||
drawableVideo.FadeIn(500);
|
||||
|
||||
using (drawableVideo.BeginDelayedSequence(drawableVideo.Duration - 500))
|
||||
drawableVideo.FadeOut(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@ -30,14 +28,14 @@ namespace osu.Game.Users.Drawables
|
||||
/// 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).
|
||||
/// </summary>
|
||||
public Action Action;
|
||||
public Action? Action;
|
||||
|
||||
public UpdateableFlag(CountryCode countryCode = CountryCode.Unknown)
|
||||
{
|
||||
CountryCode = countryCode;
|
||||
}
|
||||
|
||||
protected override Drawable CreateDrawable(CountryCode countryCode)
|
||||
protected override Drawable? CreateDrawable(CountryCode countryCode)
|
||||
{
|
||||
if (countryCode == CountryCode.Unknown && !ShowPlaceholderOnUnknown)
|
||||
return null;
|
||||
@ -56,8 +54,8 @@ namespace osu.Game.Users.Drawables
|
||||
};
|
||||
}
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private RankingsOverlay rankingsOverlay { get; set; }
|
||||
[Resolved]
|
||||
private RankingsOverlay? rankingsOverlay { get; set; }
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
|
@ -35,8 +35,8 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="10.20.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2023.131.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.202.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2023.228.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.228.0" />
|
||||
<PackageReference Include="Sentry" Version="3.28.1" />
|
||||
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
|
@ -16,6 +16,6 @@
|
||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.131.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.228.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
Loading…
Reference in New Issue
Block a user