mirror of
https://github.com/ppy/osu.git
synced 2025-03-11 10:57:20 +08:00
Merge branch 'master' into room-management-lio
This commit is contained in:
commit
38cd4fa56a
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -96,7 +96,7 @@ jobs:
|
|||||||
|
|
||||||
build-only-android:
|
build-only-android:
|
||||||
name: Build only (Android)
|
name: Build only (Android)
|
||||||
runs-on: windows-latest
|
runs-on: windows-2019
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.115.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.204.0" />
|
||||||
</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.
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
// 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 Android.Content.PM;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Game.Screens.Play;
|
|
||||||
|
|
||||||
namespace osu.Android
|
|
||||||
{
|
|
||||||
public partial class GameplayScreenRotationLocker : Component
|
|
||||||
{
|
|
||||||
private IBindable<LocalUserPlayingState> localUserPlaying = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private OsuGameActivity gameActivity { get; set; } = null!;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(ILocalUserPlayInfo localUserPlayInfo)
|
|
||||||
{
|
|
||||||
localUserPlaying = localUserPlayInfo.PlayingState.GetBoundCopy();
|
|
||||||
localUserPlaying.BindValueChanged(updateLock, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateLock(ValueChangedEvent<LocalUserPlayingState> userPlaying)
|
|
||||||
{
|
|
||||||
gameActivity.RunOnUiThread(() =>
|
|
||||||
{
|
|
||||||
gameActivity.RequestedOrientation = userPlaying.NewValue == LocalUserPlayingState.Playing ? ScreenOrientation.Locked : gameActivity.DefaultOrientation;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -49,6 +49,8 @@ namespace osu.Android
|
|||||||
/// <remarks>Adjusted on startup to match expected UX for the current device type (phone/tablet).</remarks>
|
/// <remarks>Adjusted on startup to match expected UX for the current device type (phone/tablet).</remarks>
|
||||||
public ScreenOrientation DefaultOrientation = ScreenOrientation.Unspecified;
|
public ScreenOrientation DefaultOrientation = ScreenOrientation.Unspecified;
|
||||||
|
|
||||||
|
public new bool IsTablet { get; private set; }
|
||||||
|
|
||||||
private readonly OsuGameAndroid game;
|
private readonly OsuGameAndroid game;
|
||||||
|
|
||||||
private bool gameCreated;
|
private bool gameCreated;
|
||||||
@ -89,9 +91,9 @@ namespace osu.Android
|
|||||||
WindowManager.DefaultDisplay.GetSize(displaySize);
|
WindowManager.DefaultDisplay.GetSize(displaySize);
|
||||||
#pragma warning restore CA1422
|
#pragma warning restore CA1422
|
||||||
float smallestWidthDp = Math.Min(displaySize.X, displaySize.Y) / Resources.DisplayMetrics.Density;
|
float smallestWidthDp = Math.Min(displaySize.X, displaySize.Y) / Resources.DisplayMetrics.Density;
|
||||||
bool isTablet = smallestWidthDp >= 600f;
|
IsTablet = smallestWidthDp >= 600f;
|
||||||
|
|
||||||
RequestedOrientation = DefaultOrientation = isTablet ? ScreenOrientation.FullUser : ScreenOrientation.SensorLandscape;
|
RequestedOrientation = DefaultOrientation = IsTablet ? ScreenOrientation.FullUser : ScreenOrientation.SensorLandscape;
|
||||||
|
|
||||||
// Currently (SDK 6.0.200), BundleAssemblies is not runnable for net6-android.
|
// Currently (SDK 6.0.200), BundleAssemblies is not runnable for net6-android.
|
||||||
// The assembly files are not available as files either after native AOT.
|
// The assembly files are not available as files either after native AOT.
|
||||||
|
@ -3,11 +3,13 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
|
using Android.Content.PM;
|
||||||
using Microsoft.Maui.Devices;
|
using Microsoft.Maui.Devices;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game;
|
using osu.Game;
|
||||||
|
using osu.Game.Screens;
|
||||||
using osu.Game.Updater;
|
using osu.Game.Updater;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
|
|
||||||
@ -71,7 +73,35 @@ namespace osu.Android
|
|||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
LoadComponentAsync(new GameplayScreenRotationLocker(), Add);
|
UserPlayingState.BindValueChanged(_ => updateOrientation());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ScreenChanged(IOsuScreen? current, IOsuScreen? newScreen)
|
||||||
|
{
|
||||||
|
base.ScreenChanged(current, newScreen);
|
||||||
|
|
||||||
|
if (newScreen != null)
|
||||||
|
updateOrientation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateOrientation()
|
||||||
|
{
|
||||||
|
var orientation = MobileUtils.GetOrientation(this, (IOsuScreen)ScreenStack.CurrentScreen, gameActivity.IsTablet);
|
||||||
|
|
||||||
|
switch (orientation)
|
||||||
|
{
|
||||||
|
case MobileUtils.Orientation.Locked:
|
||||||
|
gameActivity.RequestedOrientation = ScreenOrientation.Locked;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MobileUtils.Orientation.Portrait:
|
||||||
|
gameActivity.RequestedOrientation = ScreenOrientation.Portrait;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MobileUtils.Orientation.Default:
|
||||||
|
gameActivity.RequestedOrientation = gameActivity.DefaultOrientation;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetHost(GameHost host)
|
public override void SetHost(GameHost host)
|
||||||
|
@ -82,7 +82,7 @@ namespace osu.Desktop
|
|||||||
};
|
};
|
||||||
|
|
||||||
client.OnReady += onReady;
|
client.OnReady += onReady;
|
||||||
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Message} ({e.Code})", LoggingTarget.Network, LogLevel.Error);
|
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Message} ({e.Code})", LoggingTarget.Network);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -173,7 +173,7 @@ namespace osu.Desktop
|
|||||||
new Button
|
new Button
|
||||||
{
|
{
|
||||||
Label = "View beatmap",
|
Label = "View beatmap",
|
||||||
Url = $@"{api.WebsiteRootUrl}/beatmaps/{beatmapId}?mode={ruleset.Value.ShortName}"
|
Url = $@"{api.Endpoints.WebsiteUrl}/beatmaps/{beatmapId}?mode={ruleset.Value.ShortName}"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,6 @@ namespace osu.Desktop.Security
|
|||||||
|
|
||||||
private partial class ElevatedPrivilegesNotification : SimpleNotification
|
private partial class ElevatedPrivilegesNotification : SimpleNotification
|
||||||
{
|
{
|
||||||
public override bool IsImportant => true;
|
|
||||||
|
|
||||||
public ElevatedPrivilegesNotification()
|
public ElevatedPrivilegesNotification()
|
||||||
{
|
{
|
||||||
Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance, may break integrations and poses a security risk. Please run the game as a normal user.";
|
Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance, may break integrations and poses a security risk. Please run the game as a normal user.";
|
||||||
|
@ -12,7 +12,6 @@ using osu.Framework.Testing;
|
|||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Edit;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
@ -71,11 +70,11 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
contentContainer.Playfield.HitObjectContainer.Add(hitObject);
|
contentContainer.Playfield.HitObjectContainer.Add(hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint)
|
protected override void UpdatePlacementTimeAndPosition()
|
||||||
{
|
{
|
||||||
var result = base.SnapForBlueprint(blueprint);
|
var position = InputManager.CurrentState.Mouse.Position;
|
||||||
result.Time = Math.Round(HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition) / TIME_SNAP) * TIME_SNAP;
|
double time = Math.Round(HitObjectContainer.TimeAtScreenSpacePosition(position) / TIME_SNAP) * TIME_SNAP;
|
||||||
return result;
|
CurrentBlueprint.UpdateTimeAndPosition(position, time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ using osu.Game.Graphics;
|
|||||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Difficulty;
|
using osu.Game.Rulesets.Catch.Difficulty;
|
||||||
using osu.Game.Rulesets.Catch.Edit;
|
using osu.Game.Rulesets.Catch.Edit;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Setup;
|
||||||
using osu.Game.Rulesets.Catch.Mods;
|
using osu.Game.Rulesets.Catch.Mods;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Replays;
|
using osu.Game.Rulesets.Catch.Replays;
|
||||||
@ -228,7 +229,7 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
public override IEnumerable<Drawable> CreateEditorSetupSections() =>
|
public override IEnumerable<Drawable> CreateEditorSetupSections() =>
|
||||||
[
|
[
|
||||||
new MetadataSection(),
|
new MetadataSection(),
|
||||||
new DifficultySection(),
|
new CatchDifficultySection(),
|
||||||
new FillFlowContainer
|
new FillFlowContainer
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Utils;
|
|||||||
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||||
@ -59,11 +60,13 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
return base.OnMouseDown(e);
|
return base.OnMouseDown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double fallbackTime)
|
||||||
{
|
{
|
||||||
base.UpdateTimeAndPosition(result);
|
var result = Composer?.FindSnappedPositionAndTime(screenSpacePosition) ?? new SnapResult(screenSpacePosition, fallbackTime);
|
||||||
|
|
||||||
if (!(result.Time is double time)) return;
|
base.UpdateTimeAndPosition(result.ScreenSpacePosition, result.Time ?? fallbackTime);
|
||||||
|
|
||||||
|
if (!(result.Time is double time)) return result;
|
||||||
|
|
||||||
switch (PlacementActive)
|
switch (PlacementActive)
|
||||||
{
|
{
|
||||||
@ -78,6 +81,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
|
|
||||||
HitObject.StartTime = Math.Min(placementStartTime, placementEndTime);
|
HitObject.StartTime = Math.Min(placementStartTime, placementEndTime);
|
||||||
HitObject.EndTime = Math.Max(placementStartTime, placementEndTime);
|
HitObject.EndTime = Math.Max(placementStartTime, placementEndTime);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ using osu.Game.Rulesets.UI.Scrolling;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||||
{
|
{
|
||||||
public partial class CatchPlacementBlueprint<THitObject> : HitObjectPlacementBlueprint
|
public abstract partial class CatchPlacementBlueprint<THitObject> : HitObjectPlacementBlueprint
|
||||||
where THitObject : CatchHitObject, new()
|
where THitObject : CatchHitObject, new()
|
||||||
{
|
{
|
||||||
protected new THitObject HitObject => (THitObject)base.HitObject;
|
protected new THitObject HitObject => (THitObject)base.HitObject;
|
||||||
@ -19,7 +19,10 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private Playfield playfield { get; set; } = null!;
|
private Playfield playfield { get; set; } = null!;
|
||||||
|
|
||||||
public CatchPlacementBlueprint()
|
[Resolved]
|
||||||
|
protected CatchHitObjectComposer? Composer { get; private set; }
|
||||||
|
|
||||||
|
protected CatchPlacementBlueprint()
|
||||||
: base(new THitObject())
|
: base(new THitObject())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||||
@ -41,11 +42,20 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double fallbackTime)
|
||||||
{
|
{
|
||||||
base.UpdateTimeAndPosition(result);
|
var gridSnapResult = Composer?.FindSnappedPositionAndTime(screenSpacePosition) ?? new SnapResult(screenSpacePosition, fallbackTime);
|
||||||
|
gridSnapResult.ScreenSpacePosition.X = screenSpacePosition.X;
|
||||||
|
var distanceSnapResult = Composer?.TryDistanceSnap(gridSnapResult.ScreenSpacePosition);
|
||||||
|
|
||||||
|
var result = distanceSnapResult != null && Vector2.Distance(gridSnapResult.ScreenSpacePosition, distanceSnapResult.ScreenSpacePosition) < CatchHitObjectComposer.DISTANCE_SNAP_RADIUS
|
||||||
|
? distanceSnapResult
|
||||||
|
: gridSnapResult;
|
||||||
|
|
||||||
|
base.UpdateTimeAndPosition(result.ScreenSpacePosition, result.Time ?? fallbackTime);
|
||||||
|
|
||||||
HitObject.X = ToLocalSpace(result.ScreenSpacePosition).X;
|
HitObject.X = ToLocalSpace(result.ScreenSpacePosition).X;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,8 +83,16 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
return base.OnMouseDown(e);
|
return base.OnMouseDown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double fallbackTime)
|
||||||
{
|
{
|
||||||
|
var gridSnapResult = Composer?.FindSnappedPositionAndTime(screenSpacePosition) ?? new SnapResult(screenSpacePosition, fallbackTime);
|
||||||
|
gridSnapResult.ScreenSpacePosition.X = screenSpacePosition.X;
|
||||||
|
var distanceSnapResult = Composer?.TryDistanceSnap(gridSnapResult.ScreenSpacePosition);
|
||||||
|
|
||||||
|
var result = distanceSnapResult != null && Vector2.Distance(gridSnapResult.ScreenSpacePosition, distanceSnapResult.ScreenSpacePosition) < CatchHitObjectComposer.DISTANCE_SNAP_RADIUS
|
||||||
|
? distanceSnapResult
|
||||||
|
: gridSnapResult;
|
||||||
|
|
||||||
switch (PlacementActive)
|
switch (PlacementActive)
|
||||||
{
|
{
|
||||||
case PlacementState.Waiting:
|
case PlacementState.Waiting:
|
||||||
@ -99,7 +107,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the up-to-date position is used for outlines.
|
// Make sure the up-to-date position is used for outlines.
|
||||||
@ -113,6 +121,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
ApplyDefaultsToHitObject();
|
ApplyDefaultsToHitObject();
|
||||||
scrollingPath.UpdatePathFrom(HitObjectContainer, HitObject);
|
scrollingPath.UpdatePathFrom(HitObjectContainer, HitObject);
|
||||||
nestedOutlineContainer.UpdateNestedObjectsFrom(HitObjectContainer, HitObject);
|
nestedOutlineContainer.UpdateNestedObjectsFrom(HitObjectContainer, HitObject);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private double positionToTime(float relativeYPosition)
|
private double positionToTime(float relativeYPosition)
|
||||||
|
@ -1,16 +1,22 @@
|
|||||||
// 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 osu.Framework.Input.Events;
|
||||||
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
{
|
{
|
||||||
public partial class CatchBlueprintContainer : ComposeBlueprintContainer
|
public partial class CatchBlueprintContainer : ComposeBlueprintContainer
|
||||||
{
|
{
|
||||||
|
public new CatchHitObjectComposer Composer => (CatchHitObjectComposer)base.Composer;
|
||||||
|
|
||||||
public CatchBlueprintContainer(CatchHitObjectComposer composer)
|
public CatchBlueprintContainer(CatchHitObjectComposer composer)
|
||||||
: base(composer)
|
: base(composer)
|
||||||
{
|
{
|
||||||
@ -36,5 +42,28 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected sealed override DragBox CreateDragBox() => new ScrollingDragBox(Composer.Playfield);
|
protected sealed override DragBox CreateDragBox() => new ScrollingDragBox(Composer.Playfield);
|
||||||
|
|
||||||
|
protected override bool TryMoveBlueprints(DragEvent e, IList<(SelectionBlueprint<HitObject> blueprint, Vector2[] originalSnapPositions)> blueprints)
|
||||||
|
{
|
||||||
|
Vector2 distanceTravelled = e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition;
|
||||||
|
|
||||||
|
// The final movement position, relative to movementBlueprintOriginalPosition.
|
||||||
|
Vector2 movePosition = blueprints.First().originalSnapPositions.First() + distanceTravelled;
|
||||||
|
|
||||||
|
// Retrieve a snapped position.
|
||||||
|
var gridSnapResult = Composer.FindSnappedPositionAndTime(movePosition);
|
||||||
|
gridSnapResult.ScreenSpacePosition.X = movePosition.X;
|
||||||
|
var distanceSnapResult = Composer.TryDistanceSnap(gridSnapResult.ScreenSpacePosition);
|
||||||
|
|
||||||
|
var result = distanceSnapResult != null && Vector2.Distance(gridSnapResult.ScreenSpacePosition, distanceSnapResult.ScreenSpacePosition) < CatchHitObjectComposer.DISTANCE_SNAP_RADIUS
|
||||||
|
? distanceSnapResult
|
||||||
|
: gridSnapResult;
|
||||||
|
|
||||||
|
var referenceBlueprint = blueprints.First().blueprint;
|
||||||
|
bool moved = SelectionHandler.HandleMovement(new MoveSelectionEvent<HitObject>(referenceBlueprint, result.ScreenSpacePosition - referenceBlueprint.ScreenSpaceSelectionPoint));
|
||||||
|
if (moved)
|
||||||
|
ApplySnapResultTime(result, referenceBlueprint.Item.StartTime);
|
||||||
|
return moved;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
//
|
//
|
||||||
// The implementation below is probably correct but should be checked if/when exposed via controls.
|
// The implementation below is probably correct but should be checked if/when exposed via controls.
|
||||||
|
|
||||||
float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
|
float expectedDistance = DurationToDistance(after.StartTime - before.GetEndTime(), before.StartTime);
|
||||||
|
|
||||||
float previousEndX = (before as JuiceStream)?.EndX ?? ((CatchHitObject)before).EffectiveX;
|
float previousEndX = (before as JuiceStream)?.EndX ?? ((CatchHitObject)before).EffectiveX;
|
||||||
float actualDistance = Math.Abs(previousEndX - ((CatchHitObject)after).EffectiveX);
|
float actualDistance = Math.Abs(previousEndX - ((CatchHitObject)after).EffectiveX);
|
||||||
|
@ -23,9 +23,10 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
public partial class CatchHitObjectComposer : ScrollingHitObjectComposer<CatchHitObject>, IKeyBindingHandler<GlobalAction>
|
public partial class CatchHitObjectComposer : ScrollingHitObjectComposer<CatchHitObject>, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
private const float distance_snap_radius = 50;
|
public const float DISTANCE_SNAP_RADIUS = 50;
|
||||||
|
|
||||||
private CatchDistanceSnapGrid distanceSnapGrid = null!;
|
private CatchDistanceSnapGrid distanceSnapGrid = null!;
|
||||||
|
|
||||||
@ -135,22 +136,12 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
DistanceSnapProvider.HandleToggleViaKey(key);
|
DistanceSnapProvider.HandleToggleViaKey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
public SnapResult? TryDistanceSnap(Vector2 screenSpacePosition)
|
||||||
{
|
{
|
||||||
var result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(screenSpacePosition) is SnapResult snapResult)
|
||||||
|
return snapResult;
|
||||||
|
|
||||||
result.ScreenSpacePosition.X = screenSpacePosition.X;
|
return null;
|
||||||
|
|
||||||
if (snapType.HasFlag(SnapType.RelativeGrids))
|
|
||||||
{
|
|
||||||
if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult &&
|
|
||||||
Vector2.Distance(snapResult.ScreenSpacePosition, result.ScreenSpacePosition) < distance_snap_radius)
|
|
||||||
{
|
|
||||||
result = snapResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PalpableCatchHitObject? getLastSnappableHitObject(double time)
|
private PalpableCatchHitObject? getLastSnappableHitObject(double time)
|
||||||
|
125
osu.Game.Rulesets.Catch/Edit/Setup/CatchDifficultySection.cs
Normal file
125
osu.Game.Rulesets.Catch/Edit/Setup/CatchDifficultySection.cs
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osu.Game.Screens.Edit.Setup;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit.Setup
|
||||||
|
{
|
||||||
|
public partial class CatchDifficultySection : SetupSection
|
||||||
|
{
|
||||||
|
private FormSliderBar<float> circleSizeSlider { get; set; } = null!;
|
||||||
|
private FormSliderBar<float> healthDrainSlider { get; set; } = null!;
|
||||||
|
private FormSliderBar<float> approachRateSlider { get; set; } = null!;
|
||||||
|
private FormSliderBar<double> baseVelocitySlider { get; set; } = null!;
|
||||||
|
private FormSliderBar<double> tickRateSlider { get; set; } = null!;
|
||||||
|
|
||||||
|
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
circleSizeSlider = new FormSliderBar<float>
|
||||||
|
{
|
||||||
|
Caption = BeatmapsetsStrings.ShowStatsCs,
|
||||||
|
HintText = EditorSetupStrings.CircleSizeDescription,
|
||||||
|
Current = new BindableFloat(Beatmap.Difficulty.CircleSize)
|
||||||
|
{
|
||||||
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 0.1f,
|
||||||
|
},
|
||||||
|
TransferValueOnCommit = true,
|
||||||
|
TabbableContentContainer = this,
|
||||||
|
},
|
||||||
|
healthDrainSlider = new FormSliderBar<float>
|
||||||
|
{
|
||||||
|
Caption = BeatmapsetsStrings.ShowStatsDrain,
|
||||||
|
HintText = EditorSetupStrings.DrainRateDescription,
|
||||||
|
Current = new BindableFloat(Beatmap.Difficulty.DrainRate)
|
||||||
|
{
|
||||||
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 0.1f,
|
||||||
|
},
|
||||||
|
TransferValueOnCommit = true,
|
||||||
|
TabbableContentContainer = this,
|
||||||
|
},
|
||||||
|
approachRateSlider = new FormSliderBar<float>
|
||||||
|
{
|
||||||
|
Caption = BeatmapsetsStrings.ShowStatsAr,
|
||||||
|
HintText = EditorSetupStrings.ApproachRateDescription,
|
||||||
|
Current = new BindableFloat(Beatmap.Difficulty.ApproachRate)
|
||||||
|
{
|
||||||
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 0.1f,
|
||||||
|
},
|
||||||
|
TransferValueOnCommit = true,
|
||||||
|
TabbableContentContainer = this,
|
||||||
|
},
|
||||||
|
baseVelocitySlider = new FormSliderBar<double>
|
||||||
|
{
|
||||||
|
Caption = EditorSetupStrings.BaseVelocity,
|
||||||
|
HintText = EditorSetupStrings.BaseVelocityDescription,
|
||||||
|
Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier)
|
||||||
|
{
|
||||||
|
Default = 1.4,
|
||||||
|
MinValue = 0.4,
|
||||||
|
MaxValue = 3.6,
|
||||||
|
Precision = 0.01f,
|
||||||
|
},
|
||||||
|
TransferValueOnCommit = true,
|
||||||
|
TabbableContentContainer = this,
|
||||||
|
},
|
||||||
|
tickRateSlider = new FormSliderBar<double>
|
||||||
|
{
|
||||||
|
Caption = EditorSetupStrings.TickRate,
|
||||||
|
HintText = EditorSetupStrings.TickRateDescription,
|
||||||
|
Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate)
|
||||||
|
{
|
||||||
|
Default = 1,
|
||||||
|
MinValue = 1,
|
||||||
|
MaxValue = 4,
|
||||||
|
Precision = 1,
|
||||||
|
},
|
||||||
|
TransferValueOnCommit = true,
|
||||||
|
TabbableContentContainer = this,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var item in Children.OfType<FormSliderBar<float>>())
|
||||||
|
item.Current.ValueChanged += _ => updateValues();
|
||||||
|
|
||||||
|
foreach (var item in Children.OfType<FormSliderBar<double>>())
|
||||||
|
item.Current.ValueChanged += _ => updateValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateValues()
|
||||||
|
{
|
||||||
|
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
||||||
|
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
||||||
|
Beatmap.Difficulty.CircleSize = circleSizeSlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.ApproachRate = approachRateSlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.SliderTickRate = tickRateSlider.Current.Value;
|
||||||
|
|
||||||
|
Beatmap.UpdateAllHitObjects();
|
||||||
|
Beatmap.SaveState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -47,6 +48,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
return new DefaultSkinComponentsContainer(container =>
|
return new DefaultSkinComponentsContainer(container =>
|
||||||
{
|
{
|
||||||
var keyCounter = container.OfType<LegacyKeyCounterDisplay>().FirstOrDefault();
|
var keyCounter = container.OfType<LegacyKeyCounterDisplay>().FirstOrDefault();
|
||||||
|
var spectatorList = container.OfType<SpectatorList>().FirstOrDefault();
|
||||||
|
|
||||||
if (keyCounter != null)
|
if (keyCounter != null)
|
||||||
{
|
{
|
||||||
@ -55,11 +57,19 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
keyCounter.Origin = Anchor.TopRight;
|
keyCounter.Origin = Anchor.TopRight;
|
||||||
keyCounter.Position = new Vector2(0, -40) * 1.6f;
|
keyCounter.Position = new Vector2(0, -40) * 1.6f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (spectatorList != null)
|
||||||
|
{
|
||||||
|
spectatorList.Anchor = Anchor.BottomLeft;
|
||||||
|
spectatorList.Origin = Anchor.BottomLeft;
|
||||||
|
spectatorList.Position = new Vector2(10, -10);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new LegacyKeyCounterDisplay(),
|
new LegacyKeyCounterDisplay(),
|
||||||
|
new SpectatorList(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Rulesets.Edit;
|
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
@ -47,12 +46,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint)
|
protected override void UpdatePlacementTimeAndPosition()
|
||||||
{
|
{
|
||||||
double time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
|
double time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
|
||||||
var pos = column.ScreenSpacePositionAtTime(time);
|
var pos = column.ScreenSpacePositionAtTime(time);
|
||||||
|
CurrentBlueprint.UpdateTimeAndPosition(pos, time);
|
||||||
return new SnapResult(pos, time, column);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };
|
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };
|
||||||
|
@ -20,7 +20,6 @@ using osu.Game.Rulesets.UI.Scrolling;
|
|||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
{
|
{
|
||||||
@ -100,10 +99,5 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
{
|
{
|
||||||
set => InternalChild = value;
|
set => InternalChild = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
|||||||
{
|
{
|
||||||
CreateModTest(new ModTestData
|
CreateModTest(new ModTestData
|
||||||
{
|
{
|
||||||
Mod = new ManiaModHidden(),
|
Mod = new ManiaModFadeIn(),
|
||||||
PassCondition = () => checkCoverage(ManiaModHidden.MIN_COVERAGE)
|
PassCondition = () => checkCoverage(ManiaModHidden.MIN_COVERAGE)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
|||||||
{
|
{
|
||||||
CreateModTest(new ModTestData
|
CreateModTest(new ModTestData
|
||||||
{
|
{
|
||||||
Mod = new ManiaModHidden(),
|
Mod = new ManiaModFadeIn(),
|
||||||
PassCondition = () => checkCoverage(ManiaModHidden.MIN_COVERAGE)
|
PassCondition = () => checkCoverage(ManiaModHidden.MIN_COVERAGE)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
|||||||
{
|
{
|
||||||
CreateModTest(new ModTestData
|
CreateModTest(new ModTestData
|
||||||
{
|
{
|
||||||
Mod = new ManiaModHidden(),
|
Mod = new ManiaModFadeIn(),
|
||||||
PassCondition = () => checkCoverage(ManiaModHidden.MAX_COVERAGE)
|
PassCondition = () => checkCoverage(ManiaModHidden.MAX_COVERAGE)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
|||||||
{
|
{
|
||||||
CreateModTest(new ModTestData
|
CreateModTest(new ModTestData
|
||||||
{
|
{
|
||||||
Mod = new ManiaModHidden(),
|
Mod = new ManiaModFadeIn(),
|
||||||
PassCondition = () => checkCoverage(ManiaModHidden.MAX_COVERAGE)
|
PassCondition = () => checkCoverage(ManiaModHidden.MAX_COVERAGE)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
|||||||
{
|
{
|
||||||
CreateModTest(new ModTestData
|
CreateModTest(new ModTestData
|
||||||
{
|
{
|
||||||
Mod = new ManiaModHidden(),
|
Mod = new ManiaModFadeIn(),
|
||||||
CreateBeatmap = () => new Beatmap
|
CreateBeatmap = () => new Beatmap
|
||||||
{
|
{
|
||||||
HitObjects = Enumerable.Range(1, 100).Select(i => (HitObject)new Note { StartTime = 1000 + 200 * i }).ToList(),
|
HitObjects = Enumerable.Range(1, 100).Select(i => (HitObject)new Note { StartTime = 1000 + 200 * i }).ToList(),
|
||||||
|
@ -28,18 +28,20 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Child = new ColumnHitObjectArea(new HitObjectContainer())
|
Child = new ColumnHitObjectArea
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = new HitObjectContainer(),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new ColumnTestContainer(1, ManiaAction.Key2)
|
new ColumnTestContainer(1, ManiaAction.Key2)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Child = new ColumnHitObjectArea(new HitObjectContainer())
|
Child = new ColumnHitObjectArea
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = new HitObjectContainer(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
68
osu.Game.Rulesets.Mania.Tests/TestSceneManiaTouchInput.cs
Normal file
68
osu.Game.Rulesets.Mania.Tests/TestSceneManiaTouchInput.cs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
public partial class TestSceneManiaTouchInput : PlayerTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTouchInput()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
int index = i;
|
||||||
|
|
||||||
|
AddStep($"touch column {index}", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, getColumn(index).ScreenSpaceDrawQuad.Centre)));
|
||||||
|
|
||||||
|
AddAssert("action sent",
|
||||||
|
() => this.ChildrenOfType<ManiaInputManager>().SelectMany(m => m.KeyBindingContainer.PressedActions),
|
||||||
|
() => Does.Contain(getColumn(index).Action.Value));
|
||||||
|
|
||||||
|
AddStep($"release column {index}", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, getColumn(index).ScreenSpaceDrawQuad.Centre)));
|
||||||
|
|
||||||
|
AddAssert("action released",
|
||||||
|
() => this.ChildrenOfType<ManiaInputManager>().SelectMany(m => m.KeyBindingContainer.PressedActions),
|
||||||
|
() => Does.Not.Contain(getColumn(index).Action.Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOneColumnMultipleTouches()
|
||||||
|
{
|
||||||
|
AddStep("touch column 0", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, getColumn(0).ScreenSpaceDrawQuad.Centre)));
|
||||||
|
|
||||||
|
AddAssert("action sent",
|
||||||
|
() => this.ChildrenOfType<ManiaInputManager>().SelectMany(m => m.KeyBindingContainer.PressedActions),
|
||||||
|
() => Does.Contain(getColumn(0).Action.Value));
|
||||||
|
|
||||||
|
AddStep("touch another finger", () => InputManager.BeginTouch(new Touch(TouchSource.Touch2, getColumn(0).ScreenSpaceDrawQuad.Centre)));
|
||||||
|
|
||||||
|
AddAssert("action still pressed",
|
||||||
|
() => this.ChildrenOfType<ManiaInputManager>().SelectMany(m => m.KeyBindingContainer.PressedActions),
|
||||||
|
() => Does.Contain(getColumn(0).Action.Value));
|
||||||
|
|
||||||
|
AddStep("release first finger", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, getColumn(0).ScreenSpaceDrawQuad.Centre)));
|
||||||
|
|
||||||
|
AddAssert("action still pressed",
|
||||||
|
() => this.ChildrenOfType<ManiaInputManager>().SelectMany(m => m.KeyBindingContainer.PressedActions),
|
||||||
|
() => Does.Contain(getColumn(0).Action.Value));
|
||||||
|
|
||||||
|
AddStep("release second finger", () => InputManager.EndTouch(new Touch(TouchSource.Touch2, getColumn(0).ScreenSpaceDrawQuad.Centre)));
|
||||||
|
|
||||||
|
AddAssert("action released",
|
||||||
|
() => this.ChildrenOfType<ManiaInputManager>().SelectMany(m => m.KeyBindingContainer.PressedActions),
|
||||||
|
() => Does.Not.Contain(getColumn(0).Action.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Column getColumn(int index) => this.ChildrenOfType<Column>().ElementAt(index);
|
||||||
|
}
|
||||||
|
}
|
@ -1,49 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Input;
|
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
|
||||||
{
|
|
||||||
public partial class TestSceneManiaTouchInputArea : PlayerTestScene
|
|
||||||
{
|
|
||||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestTouchAreaNotInitiallyVisible()
|
|
||||||
{
|
|
||||||
AddAssert("touch area not visible", () => getTouchOverlay()?.State.Value == Visibility.Hidden);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestPressReceptors()
|
|
||||||
{
|
|
||||||
AddAssert("touch area not visible", () => getTouchOverlay()?.State.Value == Visibility.Hidden);
|
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
int index = i;
|
|
||||||
|
|
||||||
AddStep($"touch receptor {index}", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, getReceptor(index).ScreenSpaceDrawQuad.Centre)));
|
|
||||||
|
|
||||||
AddAssert("action sent",
|
|
||||||
() => this.ChildrenOfType<ManiaInputManager>().SelectMany(m => m.KeyBindingContainer.PressedActions),
|
|
||||||
() => Does.Contain(getReceptor(index).Action.Value));
|
|
||||||
|
|
||||||
AddStep($"release receptor {index}", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, getReceptor(index).ScreenSpaceDrawQuad.Centre)));
|
|
||||||
|
|
||||||
AddAssert("touch area visible", () => getTouchOverlay()?.State.Value == Visibility.Visible);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ManiaTouchInputArea? getTouchOverlay() => this.ChildrenOfType<ManiaTouchInputArea>().SingleOrDefault();
|
|
||||||
|
|
||||||
private ManiaTouchInputArea.ColumnInputReceptor getReceptor(int index) => this.ChildrenOfType<ManiaTouchInputArea.ColumnInputReceptor>().ElementAt(index);
|
|
||||||
}
|
|
||||||
}
|
|
@ -98,9 +98,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
|
|
||||||
private double originalStartTime;
|
private double originalStartTime;
|
||||||
|
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double fallbackTime)
|
||||||
{
|
{
|
||||||
base.UpdateTimeAndPosition(result);
|
var result = base.UpdateTimeAndPosition(screenSpacePosition, fallbackTime);
|
||||||
|
|
||||||
if (PlacementActive == PlacementState.Active)
|
if (PlacementActive == PlacementState.Active)
|
||||||
{
|
{
|
||||||
@ -121,6 +121,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
if (result.Time is double startTime)
|
if (result.Time is double startTime)
|
||||||
originalStartTime = HitObject.StartTime = startTime;
|
originalStartTime = HitObject.StartTime = startTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Game.Rulesets.Edit;
|
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
private EditorBeatmap? editorBeatmap { get; set; }
|
private EditorBeatmap? editorBeatmap { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IPositionSnapProvider? positionSnapProvider { get; set; }
|
private ManiaHitObjectComposer? positionSnapProvider { get; set; }
|
||||||
|
|
||||||
private EditBodyPiece body = null!;
|
private EditBodyPiece body = null!;
|
||||||
private EditHoldNoteEndPiece head = null!;
|
private EditHoldNoteEndPiece head = null!;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
// 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 osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
@ -20,13 +20,18 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
protected new T HitObject => (T)base.HitObject;
|
protected new T HitObject => (T)base.HitObject;
|
||||||
|
|
||||||
private Column column;
|
[Resolved]
|
||||||
|
private ManiaHitObjectComposer? composer { get; set; }
|
||||||
|
|
||||||
public Column Column
|
private Column? column;
|
||||||
|
|
||||||
|
public Column? Column
|
||||||
{
|
{
|
||||||
get => column;
|
get => column;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(value);
|
||||||
|
|
||||||
if (value == column)
|
if (value == column)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -53,9 +58,11 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double fallbackTime)
|
||||||
{
|
{
|
||||||
base.UpdateTimeAndPosition(result);
|
var result = composer?.FindSnappedPositionAndTime(screenSpacePosition) ?? new SnapResult(screenSpacePosition, fallbackTime);
|
||||||
|
|
||||||
|
base.UpdateTimeAndPosition(result.ScreenSpacePosition, result.Time ?? fallbackTime);
|
||||||
|
|
||||||
if (result.Playfield is Column col)
|
if (result.Playfield is Column col)
|
||||||
{
|
{
|
||||||
@ -76,6 +83,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
if (PlacementActive == PlacementState.Waiting)
|
if (PlacementActive == PlacementState.Waiting)
|
||||||
Column = col;
|
Column = col;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private float getNoteHeight(Column resultPlayfield) =>
|
private float getNoteHeight(Column resultPlayfield) =>
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||||
@ -35,15 +36,17 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double referenceTime)
|
||||||
{
|
{
|
||||||
base.UpdateTimeAndPosition(result);
|
var result = base.UpdateTimeAndPosition(screenSpacePosition, referenceTime);
|
||||||
|
|
||||||
if (result.Playfield != null)
|
if (result.Playfield != null)
|
||||||
{
|
{
|
||||||
piece.Width = result.Playfield.DrawWidth;
|
piece.Width = result.Playfield.DrawWidth;
|
||||||
piece.Position = ToLocalSpace(result.ScreenSpacePosition);
|
piece.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit
|
namespace osu.Game.Rulesets.Mania.Edit
|
||||||
{
|
{
|
||||||
public partial class ManiaBlueprintContainer : ComposeBlueprintContainer
|
public partial class ManiaBlueprintContainer : ComposeBlueprintContainer
|
||||||
{
|
{
|
||||||
public ManiaBlueprintContainer(HitObjectComposer composer)
|
public new ManiaHitObjectComposer Composer => (ManiaHitObjectComposer)base.Composer;
|
||||||
|
|
||||||
|
public ManiaBlueprintContainer(ManiaHitObjectComposer composer)
|
||||||
: base(composer)
|
: base(composer)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -33,5 +39,22 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new ManiaSelectionHandler();
|
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new ManiaSelectionHandler();
|
||||||
|
|
||||||
protected sealed override DragBox CreateDragBox() => new ScrollingDragBox(Composer.Playfield);
|
protected sealed override DragBox CreateDragBox() => new ScrollingDragBox(Composer.Playfield);
|
||||||
|
|
||||||
|
protected override bool TryMoveBlueprints(DragEvent e, IList<(SelectionBlueprint<HitObject> blueprint, Vector2[] originalSnapPositions)> blueprints)
|
||||||
|
{
|
||||||
|
Vector2 distanceTravelled = e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition;
|
||||||
|
|
||||||
|
// The final movement position, relative to movementBlueprintOriginalPosition.
|
||||||
|
Vector2 movePosition = blueprints.First().originalSnapPositions.First() + distanceTravelled;
|
||||||
|
|
||||||
|
// Retrieve a snapped position.
|
||||||
|
var result = Composer.FindSnappedPositionAndTime(movePosition);
|
||||||
|
|
||||||
|
var referenceBlueprint = blueprints.First().blueprint;
|
||||||
|
bool moved = SelectionHandler.HandleMovement(new MoveSelectionEvent<HitObject>(referenceBlueprint, result.ScreenSpacePosition - referenceBlueprint.ScreenSpaceSelectionPoint));
|
||||||
|
if (moved)
|
||||||
|
ApplySnapResultTime(result, referenceBlueprint.Item.StartTime);
|
||||||
|
return moved;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit
|
namespace osu.Game.Rulesets.Mania.Edit
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
public partial class ManiaHitObjectComposer : ScrollingHitObjectComposer<ManiaHitObject>
|
public partial class ManiaHitObjectComposer : ScrollingHitObjectComposer<ManiaHitObject>
|
||||||
{
|
{
|
||||||
private DrawableManiaEditorRuleset drawableRuleset = null!;
|
private DrawableManiaEditorRuleset drawableRuleset = null!;
|
||||||
@ -64,11 +65,11 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
List<ManiaHitObject> remainingHitObjects = EditorBeatmap.HitObjects.Cast<ManiaHitObject>().Where(h => h.StartTime >= timestamp).ToList();
|
List<ManiaHitObject> remainingHitObjects = EditorBeatmap.HitObjects.Cast<ManiaHitObject>().Where(h => h.StartTime >= timestamp).ToList();
|
||||||
string[] objectDescriptions = objectDescription.Split(',').ToArray();
|
string[] objectDescriptions = objectDescription.Split(',');
|
||||||
|
|
||||||
for (int i = 0; i < objectDescriptions.Length; i++)
|
for (int i = 0; i < objectDescriptions.Length; i++)
|
||||||
{
|
{
|
||||||
string[] split = objectDescriptions[i].Split('|').ToArray();
|
string[] split = objectDescriptions[i].Split('|');
|
||||||
if (split.Length != 2)
|
if (split.Length != 2)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ using osu.Game.Rulesets.UI;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania
|
namespace osu.Game.Rulesets.Mania
|
||||||
{
|
{
|
||||||
[Cached] // Used for touch input, see ColumnTouchInputArea.
|
[Cached] // Used for touch input, see Column.OnTouchDown/OnTouchUp.
|
||||||
public partial class ManiaInputManager : RulesetInputManager<ManiaAction>
|
public partial class ManiaInputManager : RulesetInputManager<ManiaAction>
|
||||||
{
|
{
|
||||||
public ManiaInputManager(RulesetInfo ruleset, int variant)
|
public ManiaInputManager(RulesetInfo ruleset, int variant)
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
|
|
||||||
foreach (Column column in maniaPlayfield.Stages.SelectMany(stage => stage.Columns))
|
foreach (Column column in maniaPlayfield.Stages.SelectMany(stage => stage.Columns))
|
||||||
{
|
{
|
||||||
HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer;
|
HitObjectContainer hoc = column.HitObjectContainer;
|
||||||
Container hocParent = (Container)hoc.Parent!;
|
Container hocParent = (Container)hoc.Parent!;
|
||||||
|
|
||||||
hocParent.Remove(hoc, false);
|
hocParent.Remove(hoc, false);
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
{
|
{
|
||||||
public partial class ArgonJudgementPiece : TextJudgementPiece, IAnimatableJudgement
|
public partial class ArgonJudgementPiece : TextJudgementPiece, IAnimatableJudgement
|
||||||
{
|
{
|
||||||
private const float judgement_y_position = 160;
|
private const float judgement_y_position = -180f;
|
||||||
|
|
||||||
private RingExplosion? ringExplosion;
|
private RingExplosion? ringExplosion;
|
||||||
|
|
||||||
|
@ -9,7 +9,9 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
@ -39,6 +41,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
return new DefaultSkinComponentsContainer(container =>
|
return new DefaultSkinComponentsContainer(container =>
|
||||||
{
|
{
|
||||||
var combo = container.ChildrenOfType<ArgonManiaComboCounter>().FirstOrDefault();
|
var combo = container.ChildrenOfType<ArgonManiaComboCounter>().FirstOrDefault();
|
||||||
|
var spectatorList = container.OfType<SpectatorList>().FirstOrDefault();
|
||||||
|
|
||||||
if (combo != null)
|
if (combo != null)
|
||||||
{
|
{
|
||||||
@ -47,9 +50,17 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
combo.Origin = Anchor.Centre;
|
combo.Origin = Anchor.Centre;
|
||||||
combo.Y = 200;
|
combo.Y = 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (spectatorList != null)
|
||||||
|
spectatorList.Position = new Vector2(36, -66);
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
new ArgonManiaComboCounter(),
|
new ArgonManiaComboCounter(),
|
||||||
|
new SpectatorList
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ using osu.Framework.Graphics.Animations;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
@ -23,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
this.result = result;
|
this.result = result;
|
||||||
this.animation = animation;
|
this.animation = animation;
|
||||||
|
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.BottomCentre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
@ -32,12 +31,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource skin)
|
private void load(ISkinSource skin)
|
||||||
{
|
{
|
||||||
float? scorePosition = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.ScorePosition)?.Value;
|
float hitPosition = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? 0;
|
||||||
|
float scorePosition = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.ScorePosition)?.Value ?? 0;
|
||||||
|
|
||||||
if (scorePosition != null)
|
float absoluteHitPosition = 480f * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR - hitPosition;
|
||||||
scorePosition -= Stage.HIT_TARGET_POSITION + 150;
|
Y = scorePosition - absoluteHitPosition;
|
||||||
|
|
||||||
Y = scorePosition ?? 0;
|
|
||||||
|
|
||||||
InternalChild = animation.With(d =>
|
InternalChild = animation.With(d =>
|
||||||
{
|
{
|
||||||
|
@ -15,7 +15,9 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Objects.Legacy;
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||||
{
|
{
|
||||||
@ -95,6 +97,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
return new DefaultSkinComponentsContainer(container =>
|
return new DefaultSkinComponentsContainer(container =>
|
||||||
{
|
{
|
||||||
var combo = container.ChildrenOfType<LegacyManiaComboCounter>().FirstOrDefault();
|
var combo = container.ChildrenOfType<LegacyManiaComboCounter>().FirstOrDefault();
|
||||||
|
var spectatorList = container.OfType<SpectatorList>().FirstOrDefault();
|
||||||
|
|
||||||
if (combo != null)
|
if (combo != null)
|
||||||
{
|
{
|
||||||
@ -102,9 +105,17 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
combo.Origin = Anchor.Centre;
|
combo.Origin = Anchor.Centre;
|
||||||
combo.Y = this.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.ComboPosition)?.Value ?? 0;
|
combo.Y = this.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.ComboPosition)?.Value ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (spectatorList != null)
|
||||||
|
{
|
||||||
|
spectatorList.Anchor = Anchor.BottomLeft;
|
||||||
|
spectatorList.Origin = Anchor.BottomLeft;
|
||||||
|
spectatorList.Position = new Vector2(10, -10);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
new LegacyManiaComboCounter(),
|
new LegacyManiaComboCounter(),
|
||||||
|
new SpectatorList(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
// 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 osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
@ -45,11 +44,11 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
internal readonly Container TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both };
|
internal readonly Container TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
private DrawablePool<PoolableHitExplosion> hitExplosionPool;
|
private DrawablePool<PoolableHitExplosion> hitExplosionPool = null!;
|
||||||
private readonly OrderedHitPolicy hitPolicy;
|
private readonly OrderedHitPolicy hitPolicy;
|
||||||
public Container UnderlayElements => HitObjectArea.UnderlayElements;
|
public Container UnderlayElements => HitObjectArea.UnderlayElements;
|
||||||
|
|
||||||
private GameplaySampleTriggerSource sampleTriggerSource;
|
private GameplaySampleTriggerSource sampleTriggerSource = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this is a special (ie. scratch) column.
|
/// Whether this is a special (ie. scratch) column.
|
||||||
@ -67,11 +66,15 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
Width = COLUMN_WIDTH;
|
Width = COLUMN_WIDTH;
|
||||||
|
|
||||||
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
||||||
HitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both };
|
HitObjectArea = new ColumnHitObjectArea
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = HitObjectContainer,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private ISkinSource skin { get; set; }
|
private ISkinSource skin { get; set; } = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host)
|
private void load(GameHost host)
|
||||||
@ -132,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
if (skin != null)
|
if (skin.IsNotNull())
|
||||||
skin.SourceChanged -= onSourceChanged;
|
skin.SourceChanged -= onSourceChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,5 +183,29 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||||
// This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border
|
// This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border
|
||||||
=> DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos));
|
=> DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos));
|
||||||
|
|
||||||
|
#region Touch Input
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ManiaInputManager? maniaInputManager { get; set; }
|
||||||
|
|
||||||
|
private int touchActivationCount;
|
||||||
|
|
||||||
|
protected override bool OnTouchDown(TouchDownEvent e)
|
||||||
|
{
|
||||||
|
maniaInputManager?.KeyBindingContainer.TriggerPressed(Action.Value);
|
||||||
|
touchActivationCount++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnTouchUp(TouchUpEvent e)
|
||||||
|
{
|
||||||
|
touchActivationCount--;
|
||||||
|
|
||||||
|
if (touchActivationCount == 0)
|
||||||
|
maniaInputManager?.KeyBindingContainer.TriggerReleased(Action.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,12 @@
|
|||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.UI;
|
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI.Components
|
namespace osu.Game.Rulesets.Mania.UI.Components
|
||||||
{
|
{
|
||||||
public partial class ColumnHitObjectArea : HitObjectArea
|
public partial class ColumnHitObjectArea : HitPositionPaddedContainer
|
||||||
{
|
{
|
||||||
public readonly Container Explosions;
|
public readonly Container Explosions;
|
||||||
|
|
||||||
@ -17,25 +16,29 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
|
|
||||||
private readonly Drawable hitTarget;
|
private readonly Drawable hitTarget;
|
||||||
|
|
||||||
public ColumnHitObjectArea(HitObjectContainer hitObjectContainer)
|
protected override Container<Drawable> Content => content;
|
||||||
: base(hitObjectContainer)
|
|
||||||
|
private readonly Container content;
|
||||||
|
|
||||||
|
public ColumnHitObjectArea()
|
||||||
{
|
{
|
||||||
AddRangeInternal(new[]
|
AddRangeInternal(new[]
|
||||||
{
|
{
|
||||||
UnderlayElements = new Container
|
UnderlayElements = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Depth = 2,
|
|
||||||
},
|
},
|
||||||
hitTarget = new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.HitTarget), _ => new DefaultHitTarget())
|
hitTarget = new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.HitTarget), _ => new DefaultHitTarget())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Depth = 1
|
},
|
||||||
|
content = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
Explosions = new Container
|
Explosions = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Depth = -1,
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,52 +1,38 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Mania.Skinning;
|
using osu.Game.Rulesets.Mania.Skinning;
|
||||||
using osu.Game.Rulesets.UI;
|
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI.Components
|
namespace osu.Game.Rulesets.Mania.UI.Components
|
||||||
{
|
{
|
||||||
public partial class HitObjectArea : SkinReloadableDrawable
|
public partial class HitPositionPaddedContainer : Container
|
||||||
{
|
{
|
||||||
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
||||||
public readonly HitObjectContainer HitObjectContainer;
|
|
||||||
|
|
||||||
public HitObjectArea(HitObjectContainer hitObjectContainer)
|
[Resolved]
|
||||||
{
|
private ISkinSource skin { get; set; } = null!;
|
||||||
InternalChild = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Child = HitObjectContainer = hitObjectContainer
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(IScrollingInfo scrollingInfo)
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
{
|
{
|
||||||
Direction.BindTo(scrollingInfo.Direction);
|
Direction.BindTo(scrollingInfo.Direction);
|
||||||
Direction.BindValueChanged(onDirectionChanged, true);
|
Direction.BindValueChanged(_ => UpdateHitPosition(), true);
|
||||||
|
|
||||||
|
skin.SourceChanged += onSkinChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void SkinChanged(ISkinSource skin)
|
private void onSkinChanged() => UpdateHitPosition();
|
||||||
{
|
|
||||||
base.SkinChanged(skin);
|
|
||||||
UpdateHitPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
|
||||||
{
|
|
||||||
UpdateHitPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void UpdateHitPosition()
|
protected virtual void UpdateHitPosition()
|
||||||
{
|
{
|
||||||
float hitPosition = CurrentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(
|
float hitPosition = skin.GetConfig<ManiaSkinConfigurationLookup, float>(
|
||||||
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value
|
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value
|
||||||
?? Stage.HIT_TARGET_POSITION;
|
?? Stage.HIT_TARGET_POSITION;
|
||||||
|
|
||||||
@ -54,5 +40,13 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
? new MarginPadding { Top = hitPosition }
|
? new MarginPadding { Top = hitPosition }
|
||||||
: new MarginPadding { Bottom = hitPosition };
|
: new MarginPadding { Bottom = hitPosition };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (skin.IsNotNull())
|
||||||
|
skin.SourceChanged -= onSkinChanged;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,6 +6,7 @@
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
{
|
{
|
||||||
@ -15,9 +16,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
private partial class DefaultManiaJudgementPiece : DefaultJudgementPiece
|
private partial class DefaultManiaJudgementPiece : DefaultJudgementPiece
|
||||||
{
|
{
|
||||||
|
private const float judgement_y_position = -180f;
|
||||||
|
|
||||||
public DefaultManiaJudgementPiece(HitResult result)
|
public DefaultManiaJudgementPiece(HitResult result)
|
||||||
: base(result)
|
: base(result)
|
||||||
{
|
{
|
||||||
|
Y = judgement_y_position;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -32,8 +36,20 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
switch (Result)
|
switch (Result)
|
||||||
{
|
{
|
||||||
case HitResult.None:
|
case HitResult.None:
|
||||||
|
this.FadeOutFromOne(800);
|
||||||
|
break;
|
||||||
|
|
||||||
case HitResult.Miss:
|
case HitResult.Miss:
|
||||||
base.PlayAnimation();
|
this.ScaleTo(1.6f);
|
||||||
|
this.ScaleTo(1, 100, Easing.In);
|
||||||
|
|
||||||
|
this.MoveToY(judgement_y_position);
|
||||||
|
this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
|
||||||
|
|
||||||
|
this.RotateTo(0);
|
||||||
|
this.RotateTo(40, 800, Easing.InQuint);
|
||||||
|
|
||||||
|
this.FadeOutFromOne(800);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -43,8 +59,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
this.Delay(50)
|
this.Delay(50)
|
||||||
.ScaleTo(0.75f, 250)
|
.ScaleTo(0.75f, 250)
|
||||||
.FadeOut(200);
|
.FadeOut(200);
|
||||||
|
|
||||||
// osu!mania uses a custom fade length, so the base call is intentionally omitted.
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,6 @@ using osu.Game.Skinning;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
{
|
{
|
||||||
[Cached]
|
|
||||||
public partial class DrawableManiaRuleset : DrawableScrollingRuleset<ManiaHitObject>
|
public partial class DrawableManiaRuleset : DrawableScrollingRuleset<ManiaHitObject>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -51,6 +50,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
public IEnumerable<BarLine> BarLines;
|
public IEnumerable<BarLine> BarLines;
|
||||||
|
|
||||||
|
public override bool RequiresPortraitOrientation => Beatmap.Stages.Count == 1;
|
||||||
|
|
||||||
protected override bool RelativeScaleBeatLengths => true;
|
protected override bool RelativeScaleBeatLengths => true;
|
||||||
|
|
||||||
protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config;
|
protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config;
|
||||||
@ -110,8 +111,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
configScrollSpeed.BindValueChanged(speed => TargetTimeRange = ComputeScrollTime(speed.NewValue));
|
configScrollSpeed.BindValueChanged(speed => TargetTimeRange = ComputeScrollTime(speed.NewValue));
|
||||||
|
|
||||||
TimeRange.Value = TargetTimeRange = currentTimeRange = ComputeScrollTime(configScrollSpeed.Value);
|
TimeRange.Value = TargetTimeRange = currentTimeRange = ComputeScrollTime(configScrollSpeed.Value);
|
||||||
|
|
||||||
KeyBindingInputManager.Add(new ManiaTouchInputArea());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void AdjustScrollSpeed(int amount) => configScrollSpeed.Value += amount;
|
protected override void AdjustScrollSpeed(int amount) => configScrollSpeed.Value += amount;
|
||||||
@ -162,7 +161,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
/// <returns>The scroll time.</returns>
|
/// <returns>The scroll time.</returns>
|
||||||
public static double ComputeScrollTime(double scrollSpeed) => MAX_TIME_RANGE / scrollSpeed;
|
public static double ComputeScrollTime(double scrollSpeed) => MAX_TIME_RANGE / scrollSpeed;
|
||||||
|
|
||||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer();
|
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer(this);
|
||||||
|
|
||||||
protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages);
|
protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages);
|
||||||
|
|
||||||
|
@ -1,17 +1,63 @@
|
|||||||
// 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;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
{
|
{
|
||||||
public partial class ManiaPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
|
public partial class ManiaPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
|
||||||
{
|
{
|
||||||
public ManiaPlayfieldAdjustmentContainer()
|
protected override Container<Drawable> Content { get; }
|
||||||
|
|
||||||
|
private readonly DrawSizePreservingFillContainer scalingContainer;
|
||||||
|
|
||||||
|
private readonly DrawableManiaRuleset drawableManiaRuleset;
|
||||||
|
|
||||||
|
public ManiaPlayfieldAdjustmentContainer(DrawableManiaRuleset drawableManiaRuleset)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre;
|
this.drawableManiaRuleset = drawableManiaRuleset;
|
||||||
Origin = Anchor.Centre;
|
InternalChild = scalingContainer = new DrawSizePreservingFillContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = Content = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
float aspectRatio = DrawWidth / DrawHeight;
|
||||||
|
bool isPortrait = aspectRatio < 1f;
|
||||||
|
|
||||||
|
if (isPortrait && drawableManiaRuleset.Beatmap.Stages.Count == 1)
|
||||||
|
{
|
||||||
|
// Scale playfield up by 25% to become playable on mobile devices,
|
||||||
|
// and leave a 10% horizontal gap if the playfield is scaled down due to being too wide.
|
||||||
|
const float base_scale = 1.25f;
|
||||||
|
const float base_width = 768f / base_scale;
|
||||||
|
const float side_gap = 0.9f;
|
||||||
|
|
||||||
|
scalingContainer.Strategy = DrawSizePreservationStrategy.Maximum;
|
||||||
|
float stageWidth = drawableManiaRuleset.Playfield.Stages[0].DrawWidth;
|
||||||
|
scalingContainer.TargetDrawSize = new Vector2(1024, base_width * Math.Max(stageWidth / aspectRatio / (base_width * side_gap), 1f));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
scalingContainer.Strategy = DrawSizePreservationStrategy.Minimum;
|
||||||
|
scalingContainer.Scale = new Vector2(1f);
|
||||||
|
scalingContainer.Size = new Vector2(1f);
|
||||||
|
scalingContainer.TargetDrawSize = new Vector2(1024, 768);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,199 +0,0 @@
|
|||||||
// 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 osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Game.Configuration;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An overlay that captures and displays osu!mania mouse and touch input.
|
|
||||||
/// </summary>
|
|
||||||
public partial class ManiaTouchInputArea : VisibilityContainer
|
|
||||||
{
|
|
||||||
// visibility state affects our child. we always want to handle input.
|
|
||||||
public override bool PropagatePositionalInputSubTree => true;
|
|
||||||
public override bool PropagateNonPositionalInputSubTree => true;
|
|
||||||
|
|
||||||
[SettingSource("Spacing", "The spacing between receptors.")]
|
|
||||||
public BindableFloat Spacing { get; } = new BindableFloat(10)
|
|
||||||
{
|
|
||||||
Precision = 1,
|
|
||||||
MinValue = 0,
|
|
||||||
MaxValue = 100,
|
|
||||||
};
|
|
||||||
|
|
||||||
[SettingSource("Opacity", "The receptor opacity.")]
|
|
||||||
public BindableFloat Opacity { get; } = new BindableFloat(1)
|
|
||||||
{
|
|
||||||
Precision = 0.1f,
|
|
||||||
MinValue = 0,
|
|
||||||
MaxValue = 1
|
|
||||||
};
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private DrawableManiaRuleset drawableRuleset { get; set; } = null!;
|
|
||||||
|
|
||||||
private GridContainer gridContainer = null!;
|
|
||||||
|
|
||||||
public ManiaTouchInputArea()
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomCentre;
|
|
||||||
Origin = Anchor.BottomCentre;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
Height = 0.5f;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
List<Drawable> receptorGridContent = new List<Drawable>();
|
|
||||||
List<Dimension> receptorGridDimensions = new List<Dimension>();
|
|
||||||
|
|
||||||
bool first = true;
|
|
||||||
|
|
||||||
foreach (var stage in drawableRuleset.Playfield.Stages)
|
|
||||||
{
|
|
||||||
foreach (var column in stage.Columns)
|
|
||||||
{
|
|
||||||
if (!first)
|
|
||||||
{
|
|
||||||
receptorGridContent.Add(new Gutter { Spacing = { BindTarget = Spacing } });
|
|
||||||
receptorGridDimensions.Add(new Dimension(GridSizeMode.AutoSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
receptorGridContent.Add(new ColumnInputReceptor { Action = { BindTarget = column.Action } });
|
|
||||||
receptorGridDimensions.Add(new Dimension());
|
|
||||||
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InternalChild = gridContainer = new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
AlwaysPresent = true,
|
|
||||||
Content = new[] { receptorGridContent.ToArray() },
|
|
||||||
ColumnDimensions = receptorGridDimensions.ToArray()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
Opacity.BindValueChanged(o => Alpha = o.NewValue, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
|
||||||
{
|
|
||||||
// Hide whenever the keyboard is used.
|
|
||||||
Hide();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnTouchDown(TouchDownEvent e)
|
|
||||||
{
|
|
||||||
Show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void PopIn()
|
|
||||||
{
|
|
||||||
gridContainer.FadeIn(500, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void PopOut()
|
|
||||||
{
|
|
||||||
gridContainer.FadeOut(300);
|
|
||||||
}
|
|
||||||
|
|
||||||
public partial class ColumnInputReceptor : CompositeDrawable
|
|
||||||
{
|
|
||||||
public readonly IBindable<ManiaAction> Action = new Bindable<ManiaAction>();
|
|
||||||
|
|
||||||
private readonly Box highlightOverlay;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private ManiaInputManager? inputManager { get; set; }
|
|
||||||
|
|
||||||
private bool isPressed;
|
|
||||||
|
|
||||||
public ColumnInputReceptor()
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
CornerRadius = 10,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Alpha = 0.15f,
|
|
||||||
},
|
|
||||||
highlightOverlay = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Alpha = 0,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnTouchDown(TouchDownEvent e)
|
|
||||||
{
|
|
||||||
updateButton(true);
|
|
||||||
return false; // handled by parent container to show overlay.
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnTouchUp(TouchUpEvent e)
|
|
||||||
{
|
|
||||||
updateButton(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateButton(bool press)
|
|
||||||
{
|
|
||||||
if (press == isPressed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
isPressed = press;
|
|
||||||
|
|
||||||
if (press)
|
|
||||||
{
|
|
||||||
inputManager?.KeyBindingContainer.TriggerPressed(Action.Value);
|
|
||||||
highlightOverlay.FadeTo(0.1f, 80, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
inputManager?.KeyBindingContainer.TriggerReleased(Action.Value);
|
|
||||||
highlightOverlay.FadeTo(0, 400, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private partial class Gutter : Drawable
|
|
||||||
{
|
|
||||||
public readonly IBindable<float> Spacing = new Bindable<float>();
|
|
||||||
|
|
||||||
public Gutter()
|
|
||||||
{
|
|
||||||
Spacing.BindValueChanged(s => Size = new Vector2(s.NewValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -103,12 +103,13 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
Width = 1366, // Bar lines should only be masked on the vertical axis
|
Width = 1366, // Bar lines should only be masked on the vertical axis
|
||||||
BypassAutoSizeAxes = Axes.Both,
|
BypassAutoSizeAxes = Axes.Both,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Child = barLineContainer = new HitObjectArea(HitObjectContainer)
|
Child = barLineContainer = new HitPositionPaddedContainer
|
||||||
{
|
{
|
||||||
Name = "Bar lines",
|
Name = "Bar lines",
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Child = HitObjectContainer,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
columnFlow = new ColumnFlow<Column>(definition)
|
columnFlow = new ColumnFlow<Column>(definition)
|
||||||
@ -119,12 +120,13 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
judgements = new JudgementContainer<DrawableManiaJudgement>
|
new HitPositionPaddedContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Y = HIT_TARGET_POSITION + 150
|
Child = judgements = new JudgementContainer<DrawableManiaJudgement>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
topLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
|
topLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
|
||||||
}
|
}
|
||||||
@ -218,7 +220,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
{
|
{
|
||||||
j.Apply(result, judgedObject);
|
j.Apply(result, judgedObject);
|
||||||
|
|
||||||
j.Anchor = Anchor.Centre;
|
j.Anchor = Anchor.BottomCentre;
|
||||||
j.Origin = Anchor.Centre;
|
j.Origin = Anchor.Centre;
|
||||||
})!);
|
})!);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
|||||||
public partial class GridPlacementBlueprint : PlacementBlueprint
|
public partial class GridPlacementBlueprint : PlacementBlueprint
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private HitObjectComposer? hitObjectComposer { get; set; }
|
private OsuHitObjectComposer? hitObjectComposer { get; set; }
|
||||||
|
|
||||||
private OsuGridToolboxGroup gridToolboxGroup = null!;
|
private OsuGridToolboxGroup gridToolboxGroup = null!;
|
||||||
private Vector2 originalOrigin;
|
private Vector2 originalOrigin;
|
||||||
@ -95,12 +95,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
|||||||
base.OnDragEnd(e);
|
base.OnDragEnd(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SnapType SnapType => ~SnapType.GlobalGrids;
|
public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double fallbackTime)
|
||||||
|
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
|
||||||
{
|
{
|
||||||
if (State.Value == Visibility.Hidden)
|
if (State.Value == Visibility.Hidden)
|
||||||
return;
|
return new SnapResult(screenSpacePosition, fallbackTime);
|
||||||
|
|
||||||
|
var result = hitObjectComposer?.TrySnapToNearbyObjects(screenSpacePosition) ?? new SnapResult(screenSpacePosition, fallbackTime);
|
||||||
|
|
||||||
var pos = ToLocalSpace(result.ScreenSpacePosition);
|
var pos = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
|
|
||||||
@ -120,6 +120,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
|||||||
gridToolboxGroup.SetGridFromPoints(gridToolboxGroup.StartPosition.Value, pos);
|
gridToolboxGroup.SetGridFromPoints(gridToolboxGroup.StartPosition.Value, pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PopOut()
|
protected override void PopOut()
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
||||||
@ -15,12 +20,26 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
|||||||
|
|
||||||
private readonly HitCirclePiece circlePiece;
|
private readonly HitCirclePiece circlePiece;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuHitObjectComposer? composer { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorClock? editorClock { get; set; }
|
||||||
|
|
||||||
|
private Bindable<bool> limitedDistanceSnap { get; set; } = null!;
|
||||||
|
|
||||||
public HitCirclePlacementBlueprint()
|
public HitCirclePlacementBlueprint()
|
||||||
: base(new HitCircle())
|
: base(new HitCircle())
|
||||||
{
|
{
|
||||||
InternalChild = circlePiece = new HitCirclePiece();
|
InternalChild = circlePiece = new HitCirclePiece();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
limitedDistanceSnap = config.GetBindable<bool>(OsuSetting.EditorLimitedDistanceSnap);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -45,10 +64,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
|||||||
return base.OnMouseDown(e);
|
return base.OnMouseDown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double fallbackTime)
|
||||||
{
|
{
|
||||||
base.UpdateTimeAndPosition(result);
|
var result = composer?.TrySnapToNearbyObjects(screenSpacePosition, fallbackTime);
|
||||||
|
result ??= composer?.TrySnapToDistanceGrid(screenSpacePosition, limitedDistanceSnap.Value && editorClock != null ? editorClock.CurrentTime : null);
|
||||||
|
if (composer?.TrySnapToPositionGrid(result?.ScreenSpacePosition ?? screenSpacePosition, result?.Time ?? fallbackTime) is SnapResult gridSnapResult)
|
||||||
|
result = gridSnapResult;
|
||||||
|
result ??= new SnapResult(screenSpacePosition, fallbackTime);
|
||||||
|
|
||||||
|
base.UpdateTimeAndPosition(result.ScreenSpacePosition, result.Time ?? fallbackTime);
|
||||||
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ using System.Collections.Specialized;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -20,6 +21,7 @@ using osu.Framework.Input;
|
|||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -32,7 +34,7 @@ using osuTK.Input;
|
|||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||||
{
|
{
|
||||||
public partial class PathControlPointVisualiser<T> : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
|
public partial class PathControlPointVisualiser<T> : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
|
||||||
where T : OsuHitObject, IHasPath
|
where T : OsuHitObject, IHasPath, IHasSliderVelocity
|
||||||
{
|
{
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside the playfield.
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside the playfield.
|
||||||
|
|
||||||
@ -48,11 +50,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
public Action<List<PathControlPoint>> SplitControlPointsRequested;
|
public Action<List<PathControlPoint>> SplitControlPointsRequested;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IPositionSnapProvider positionSnapProvider { get; set; }
|
[CanBeNull]
|
||||||
|
private OsuHitObjectComposer positionSnapProvider { get; set; }
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IDistanceSnapProvider distanceSnapProvider { get; set; }
|
private IDistanceSnapProvider distanceSnapProvider { get; set; }
|
||||||
|
|
||||||
|
private Bindable<bool> limitedDistanceSnap { get; set; } = null!;
|
||||||
|
|
||||||
public PathControlPointVisualiser(T hitObject, bool allowSelection)
|
public PathControlPointVisualiser(T hitObject, bool allowSelection)
|
||||||
{
|
{
|
||||||
this.hitObject = hitObject;
|
this.hitObject = hitObject;
|
||||||
@ -67,6 +72,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
limitedDistanceSnap = config.GetBindable<bool>(OsuSetting.EditorLimitedDistanceSnap);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -433,12 +444,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
{
|
{
|
||||||
// Special handling for selections containing head control point - the position of the hit object changes which means the snapped position and time have to be taken into account
|
// Special handling for selections containing head control point - the position of the hit object changes which means the snapped position and time have to be taken into account
|
||||||
Vector2 newHeadPosition = Parent!.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex]));
|
Vector2 newHeadPosition = Parent!.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex]));
|
||||||
SnapResult result = positionSnapProvider?.FindSnappedPositionAndTime(newHeadPosition);
|
|
||||||
|
|
||||||
Vector2 movementDelta = Parent!.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - hitObject.Position;
|
var result = positionSnapProvider?.TrySnapToNearbyObjects(newHeadPosition, oldStartTime);
|
||||||
|
result ??= positionSnapProvider?.TrySnapToDistanceGrid(newHeadPosition, limitedDistanceSnap.Value ? oldStartTime : null);
|
||||||
|
if (positionSnapProvider?.TrySnapToPositionGrid(result?.ScreenSpacePosition ?? newHeadPosition, result?.Time ?? oldStartTime) is SnapResult gridSnapResult)
|
||||||
|
result = gridSnapResult;
|
||||||
|
result ??= new SnapResult(newHeadPosition, oldStartTime);
|
||||||
|
|
||||||
|
Vector2 movementDelta = Parent!.ToLocalSpace(result.ScreenSpacePosition) - hitObject.Position;
|
||||||
|
|
||||||
hitObject.Position += movementDelta;
|
hitObject.Position += movementDelta;
|
||||||
hitObject.StartTime = result?.Time ?? hitObject.StartTime;
|
hitObject.StartTime = result.Time ?? hitObject.StartTime;
|
||||||
|
|
||||||
for (int i = 1; i < hitObject.Path.ControlPoints.Count; i++)
|
for (int i = 1; i < hitObject.Path.ControlPoints.Count; i++)
|
||||||
{
|
{
|
||||||
@ -453,7 +469,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SnapResult result = positionSnapProvider?.FindSnappedPositionAndTime(Parent!.ToScreenSpace(e.MousePosition), SnapType.GlobalGrids);
|
SnapResult result = positionSnapProvider?.TrySnapToPositionGrid(Parent!.ToScreenSpace(e.MousePosition));
|
||||||
|
|
||||||
Vector2 movementDelta = Parent!.ToLocalSpace(result?.ScreenSpacePosition ?? Parent!.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - hitObject.Position;
|
Vector2 movementDelta = Parent!.ToLocalSpace(result?.ScreenSpacePosition ?? Parent!.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - hitObject.Position;
|
||||||
|
|
||||||
|
@ -5,10 +5,12 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
@ -25,6 +27,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
public new Slider HitObject => (Slider)base.HitObject;
|
public new Slider HitObject => (Slider)base.HitObject;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuHitObjectComposer? composer { get; set; }
|
||||||
|
|
||||||
private SliderBodyPiece bodyPiece = null!;
|
private SliderBodyPiece bodyPiece = null!;
|
||||||
private HitCirclePiece headCirclePiece = null!;
|
private HitCirclePiece headCirclePiece = null!;
|
||||||
private HitCirclePiece tailCirclePiece = null!;
|
private HitCirclePiece tailCirclePiece = null!;
|
||||||
@ -40,15 +45,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
private int currentSegmentLength;
|
private int currentSegmentLength;
|
||||||
private bool usingCustomSegmentType;
|
private bool usingCustomSegmentType;
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private IPositionSnapProvider? positionSnapProvider { get; set; }
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IDistanceSnapProvider? distanceSnapProvider { get; set; }
|
private IDistanceSnapProvider? distanceSnapProvider { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private FreehandSliderToolboxGroup? freehandToolboxGroup { get; set; }
|
private FreehandSliderToolboxGroup? freehandToolboxGroup { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorClock? editorClock { get; set; }
|
||||||
|
|
||||||
|
private Bindable<bool> limitedDistanceSnap { get; set; } = null!;
|
||||||
|
|
||||||
private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder { Degree = 4 };
|
private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder { Degree = 4 };
|
||||||
|
|
||||||
protected override bool IsValidForPlacement => HitObject.Path.HasValidLength;
|
protected override bool IsValidForPlacement => HitObject.Path.HasValidLength;
|
||||||
@ -63,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -74,6 +81,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
};
|
};
|
||||||
|
|
||||||
state = SliderPlacementState.Initial;
|
state = SliderPlacementState.Initial;
|
||||||
|
limitedDistanceSnap = config.GetBindable<bool>(OsuSetting.EditorLimitedDistanceSnap);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -106,9 +114,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||||
|
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double fallbackTime)
|
||||||
{
|
{
|
||||||
base.UpdateTimeAndPosition(result);
|
var result = composer?.TrySnapToNearbyObjects(screenSpacePosition, fallbackTime);
|
||||||
|
result ??= composer?.TrySnapToDistanceGrid(screenSpacePosition, limitedDistanceSnap.Value && editorClock != null ? editorClock.CurrentTime : null);
|
||||||
|
if (composer?.TrySnapToPositionGrid(result?.ScreenSpacePosition ?? screenSpacePosition, result?.Time ?? fallbackTime) is SnapResult gridSnapResult)
|
||||||
|
result = gridSnapResult;
|
||||||
|
result ??= new SnapResult(screenSpacePosition, fallbackTime);
|
||||||
|
|
||||||
|
base.UpdateTimeAndPosition(result.ScreenSpacePosition, result.Time ?? fallbackTime);
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
@ -131,6 +145,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
updateCursor();
|
updateCursor();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
@ -375,7 +391,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
private Vector2 getCursorPosition()
|
private Vector2 getCursorPosition()
|
||||||
{
|
{
|
||||||
var result = positionSnapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.ControlPoints ? SnapType.GlobalGrids : SnapType.All);
|
SnapResult? result = null;
|
||||||
|
var mousePosition = inputManager.CurrentState.Mouse.Position;
|
||||||
|
|
||||||
|
if (state != SliderPlacementState.ControlPoints)
|
||||||
|
{
|
||||||
|
result ??= composer?.TrySnapToNearbyObjects(mousePosition);
|
||||||
|
result ??= composer?.TrySnapToDistanceGrid(mousePosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
result ??= composer?.TrySnapToPositionGrid(mousePosition);
|
||||||
|
|
||||||
return ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
return ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,7 +434,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
if (state == SliderPlacementState.Drawing)
|
if (state == SliderPlacementState.Drawing)
|
||||||
HitObject.Path.ExpectedDistance.Value = (float)HitObject.Path.CalculatedDistance;
|
HitObject.Path.ExpectedDistance.Value = (float)HitObject.Path.CalculatedDistance;
|
||||||
else
|
else
|
||||||
HitObject.Path.ExpectedDistance.Value = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)HitObject.Path.CalculatedDistance, DistanceSnapTarget.Start) ?? (float)HitObject.Path.CalculatedDistance;
|
HitObject.Path.ExpectedDistance.Value = distanceSnapProvider?.FindSnappedDistance((float)HitObject.Path.CalculatedDistance, HitObject.StartTime, HitObject) ?? (float)HitObject.Path.CalculatedDistance;
|
||||||
|
|
||||||
bodyPiece.UpdateFrom(HitObject);
|
bodyPiece.UpdateFrom(HitObject);
|
||||||
headCirclePiece.UpdateFrom(HitObject.HeadCircle);
|
headCirclePiece.UpdateFrom(HitObject.HeadCircle);
|
||||||
|
@ -274,9 +274,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
double minDistance = distanceSnapProvider?.GetBeatSnapDistanceAt(HitObject, false) * oldVelocityMultiplier ?? 1;
|
double minDistance = distanceSnapProvider?.GetBeatSnapDistance() * oldVelocityMultiplier ?? 1;
|
||||||
// Add a small amount to the proposed distance to make it easier to snap to the full length of the slider.
|
// Add a small amount to the proposed distance to make it easier to snap to the full length of the slider.
|
||||||
proposedDistance = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)proposedDistance + 1, DistanceSnapTarget.Start) ?? proposedDistance;
|
proposedDistance = distanceSnapProvider?.FindSnappedDistance((float)proposedDistance + 1, HitObject.StartTime, HitObject) ?? proposedDistance;
|
||||||
proposedDistance = MathHelper.Clamp(proposedDistance, minDistance, HitObject.Path.CalculatedDistance);
|
proposedDistance = MathHelper.Clamp(proposedDistance, minDistance, HitObject.Path.CalculatedDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
// 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 osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
||||||
@ -8,16 +14,27 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
|||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
{
|
{
|
||||||
public partial class OsuBlueprintContainer : ComposeBlueprintContainer
|
public partial class OsuBlueprintContainer : ComposeBlueprintContainer
|
||||||
{
|
{
|
||||||
public OsuBlueprintContainer(HitObjectComposer composer)
|
private Bindable<bool> limitedDistanceSnap { get; set; } = null!;
|
||||||
|
|
||||||
|
public new OsuHitObjectComposer Composer => (OsuHitObjectComposer)base.Composer;
|
||||||
|
|
||||||
|
public OsuBlueprintContainer(OsuHitObjectComposer composer)
|
||||||
: base(composer)
|
: base(composer)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
limitedDistanceSnap = config.GetBindable<bool>(OsuSetting.EditorLimitedDistanceSnap);
|
||||||
|
}
|
||||||
|
|
||||||
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new OsuSelectionHandler();
|
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new OsuSelectionHandler();
|
||||||
|
|
||||||
public override HitObjectSelectionBlueprint? CreateHitObjectBlueprintFor(HitObject hitObject)
|
public override HitObjectSelectionBlueprint? CreateHitObjectBlueprintFor(HitObject hitObject)
|
||||||
@ -36,5 +53,68 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
return base.CreateHitObjectBlueprintFor(hitObject);
|
return base.CreateHitObjectBlueprintFor(hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool TryMoveBlueprints(DragEvent e, IList<(SelectionBlueprint<HitObject> blueprint, Vector2[] originalSnapPositions)> blueprints)
|
||||||
|
{
|
||||||
|
Vector2 distanceTravelled = e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition;
|
||||||
|
|
||||||
|
for (int i = 0; i < blueprints.Count; i++)
|
||||||
|
{
|
||||||
|
if (checkSnappingBlueprintToNearbyObjects(blueprints[i].blueprint, distanceTravelled, blueprints[i].originalSnapPositions))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no positional snapping could be performed, try unrestricted snapping from the earliest
|
||||||
|
// item in the selection.
|
||||||
|
|
||||||
|
// The final movement position, relative to movementBlueprintOriginalPosition.
|
||||||
|
Vector2 movePosition = blueprints.First().originalSnapPositions.First() + distanceTravelled;
|
||||||
|
var referenceBlueprint = blueprints.First().blueprint;
|
||||||
|
|
||||||
|
// Retrieve a snapped position.
|
||||||
|
var result = Composer.TrySnapToNearbyObjects(movePosition);
|
||||||
|
result ??= Composer.TrySnapToDistanceGrid(movePosition, limitedDistanceSnap.Value ? referenceBlueprint.Item.StartTime : null);
|
||||||
|
if (Composer.TrySnapToPositionGrid(result?.ScreenSpacePosition ?? movePosition, result?.Time) is SnapResult gridSnapResult)
|
||||||
|
result = gridSnapResult;
|
||||||
|
result ??= new SnapResult(movePosition, null);
|
||||||
|
|
||||||
|
bool moved = SelectionHandler.HandleMovement(new MoveSelectionEvent<HitObject>(referenceBlueprint, result.ScreenSpacePosition - referenceBlueprint.ScreenSpaceSelectionPoint));
|
||||||
|
if (moved)
|
||||||
|
ApplySnapResultTime(result, referenceBlueprint.Item.StartTime);
|
||||||
|
return moved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check for positional snap for given blueprint.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="blueprint">The blueprint to check for snapping.</param>
|
||||||
|
/// <param name="distanceTravelled">Distance travelled since start of dragging action.</param>
|
||||||
|
/// <param name="originalPositions">The snap positions of blueprint before start of dragging action.</param>
|
||||||
|
/// <returns>Whether an object to snap to was found.</returns>
|
||||||
|
private bool checkSnappingBlueprintToNearbyObjects(SelectionBlueprint<HitObject> blueprint, Vector2 distanceTravelled, Vector2[] originalPositions)
|
||||||
|
{
|
||||||
|
var currentPositions = blueprint.ScreenSpaceSnapPoints;
|
||||||
|
|
||||||
|
for (int i = 0; i < originalPositions.Length; i++)
|
||||||
|
{
|
||||||
|
Vector2 originalPosition = originalPositions[i];
|
||||||
|
var testPosition = originalPosition + distanceTravelled;
|
||||||
|
|
||||||
|
var positionalResult = Composer.TrySnapToNearbyObjects(testPosition);
|
||||||
|
|
||||||
|
if (positionalResult == null || positionalResult.ScreenSpacePosition == testPosition) continue;
|
||||||
|
|
||||||
|
var delta = positionalResult.ScreenSpacePosition - currentPositions[i];
|
||||||
|
|
||||||
|
// attempt to move the objects, and apply any time based snapping if we can.
|
||||||
|
if (SelectionHandler.HandleMovement(new MoveSelectionEvent<HitObject>(blueprint, delta)))
|
||||||
|
{
|
||||||
|
ApplySnapResultTime(positionalResult, blueprint.Item.StartTime);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
|
||||||
@ -12,8 +13,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
public partial class OsuDistanceSnapGrid : CircularDistanceSnapGrid
|
public partial class OsuDistanceSnapGrid : CircularDistanceSnapGrid
|
||||||
{
|
{
|
||||||
public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null)
|
public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null, [CanBeNull] IHasSliderVelocity sliderVelocitySource = null)
|
||||||
: base(hitObject, hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime - 1)
|
: base(hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime - 1, sliderVelocitySource)
|
||||||
{
|
{
|
||||||
Masking = true;
|
Masking = true;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
public override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
|
public override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
|
||||||
{
|
{
|
||||||
float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
|
float expectedDistance = DurationToDistance(after.StartTime - before.GetEndTime(), before.StartTime);
|
||||||
float actualDistance = Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).Position);
|
float actualDistance = Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).Position);
|
||||||
|
|
||||||
return actualDistance / expectedDistance;
|
return actualDistance / expectedDistance;
|
||||||
|
@ -7,6 +7,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Caching;
|
using osu.Framework.Caching;
|
||||||
@ -22,6 +23,7 @@ using osu.Game.Rulesets.Edit;
|
|||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -31,6 +33,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
public partial class OsuHitObjectComposer : HitObjectComposer<OsuHitObject>
|
public partial class OsuHitObjectComposer : HitObjectComposer<OsuHitObject>
|
||||||
{
|
{
|
||||||
public OsuHitObjectComposer(Ruleset ruleset)
|
public OsuHitObjectComposer(Ruleset ruleset)
|
||||||
@ -178,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
List<OsuHitObject> remainingHitObjects = EditorBeatmap.HitObjects.Cast<OsuHitObject>().Where(h => h.StartTime >= timestamp).ToList();
|
List<OsuHitObject> remainingHitObjects = EditorBeatmap.HitObjects.Cast<OsuHitObject>().Where(h => h.StartTime >= timestamp).ToList();
|
||||||
string[] splitDescription = objectDescription.Split(',').ToArray();
|
string[] splitDescription = objectDescription.Split(',');
|
||||||
|
|
||||||
for (int i = 0; i < splitDescription.Length; i++)
|
for (int i = 0; i < splitDescription.Length; i++)
|
||||||
{
|
{
|
||||||
@ -222,56 +225,56 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
[CanBeNull]
|
||||||
|
public SnapResult TrySnapToNearbyObjects(Vector2 screenSpacePosition, double? fallbackTime = null)
|
||||||
{
|
{
|
||||||
if (snapType.HasFlag(SnapType.NearbyObjects) && snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
|
if (!snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
|
||||||
{
|
return null;
|
||||||
// In the case of snapping to nearby objects, a time value is not provided.
|
|
||||||
// This matches the stable editor (which also uses current time), but with the introduction of time-snapping distance snap
|
|
||||||
// this could result in unexpected behaviour when distance snapping is turned on and a user attempts to place an object that is
|
|
||||||
// BOTH on a valid distance snap ring, and also at the same position as a previous object.
|
|
||||||
//
|
|
||||||
// We want to ensure that in this particular case, the time-snapping component of distance snap is still applied.
|
|
||||||
// The easiest way to ensure this is to attempt application of distance snap after a nearby object is found, and copy over
|
|
||||||
// the time value if the proposed positions are roughly the same.
|
|
||||||
if (snapType.HasFlag(SnapType.RelativeGrids) && DistanceSnapProvider.DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
|
||||||
{
|
|
||||||
(Vector2 distanceSnappedPosition, double distanceSnappedTime) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(snapResult.ScreenSpacePosition));
|
|
||||||
if (Precision.AlmostEquals(distanceSnapGrid.ToScreenSpace(distanceSnappedPosition), snapResult.ScreenSpacePosition, 1))
|
|
||||||
snapResult.Time = distanceSnappedTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (DistanceSnapProvider.DistanceSnapToggle.Value != TernaryState.True || distanceSnapGrid == null)
|
||||||
return snapResult;
|
return snapResult;
|
||||||
}
|
|
||||||
|
|
||||||
SnapResult result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
// In the case of snapping to nearby objects, a time value is not provided.
|
||||||
|
// This matches the stable editor (which also uses current time), but with the introduction of time-snapping distance snap
|
||||||
|
// this could result in unexpected behaviour when distance snapping is turned on and a user attempts to place an object that is
|
||||||
|
// BOTH on a valid distance snap ring, and also at the same position as a previous object.
|
||||||
|
//
|
||||||
|
// We want to ensure that in this particular case, the time-snapping component of distance snap is still applied.
|
||||||
|
// The easiest way to ensure this is to attempt application of distance snap after a nearby object is found, and copy over
|
||||||
|
// the time value if the proposed positions are roughly the same.
|
||||||
|
(Vector2 distanceSnappedPosition, double distanceSnappedTime) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(snapResult.ScreenSpacePosition));
|
||||||
|
snapResult.Time = Precision.AlmostEquals(distanceSnapGrid.ToScreenSpace(distanceSnappedPosition), snapResult.ScreenSpacePosition, 1)
|
||||||
|
? distanceSnappedTime
|
||||||
|
: fallbackTime;
|
||||||
|
|
||||||
if (snapType.HasFlag(SnapType.RelativeGrids))
|
return snapResult;
|
||||||
{
|
}
|
||||||
if (DistanceSnapProvider.DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
|
||||||
{
|
|
||||||
(Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition));
|
|
||||||
|
|
||||||
result.ScreenSpacePosition = distanceSnapGrid.ToScreenSpace(pos);
|
[CanBeNull]
|
||||||
result.Time = time;
|
public SnapResult TrySnapToDistanceGrid(Vector2 screenSpacePosition, double? fixedTime = null)
|
||||||
}
|
{
|
||||||
}
|
if (DistanceSnapProvider.DistanceSnapToggle.Value != TernaryState.True || distanceSnapGrid == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
if (snapType.HasFlag(SnapType.GlobalGrids))
|
var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition);
|
||||||
{
|
(Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition), fixedTime);
|
||||||
if (rectangularGridSnapToggle.Value == TernaryState.True)
|
return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, playfield);
|
||||||
{
|
}
|
||||||
Vector2 pos = positionSnapGrid.GetSnappedPosition(positionSnapGrid.ToLocalSpace(result.ScreenSpacePosition));
|
|
||||||
|
|
||||||
// A grid which doesn't perfectly fit the playfield can produce a position that is outside of the playfield.
|
[CanBeNull]
|
||||||
// We need to clamp the position to the playfield bounds to ensure that the snapped position is always in bounds.
|
public SnapResult TrySnapToPositionGrid(Vector2 screenSpacePosition, double? fallbackTime = null)
|
||||||
pos = Vector2.Clamp(pos, Vector2.Zero, OsuPlayfield.BASE_SIZE);
|
{
|
||||||
|
if (rectangularGridSnapToggle.Value != TernaryState.True)
|
||||||
|
return null;
|
||||||
|
|
||||||
result.ScreenSpacePosition = positionSnapGrid.ToScreenSpace(pos);
|
Vector2 pos = positionSnapGrid.GetSnappedPosition(positionSnapGrid.ToLocalSpace(screenSpacePosition));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
// A grid which doesn't perfectly fit the playfield can produce a position that is outside of the playfield.
|
||||||
|
// We need to clamp the position to the playfield bounds to ensure that the snapped position is always in bounds.
|
||||||
|
pos = Vector2.Clamp(pos, Vector2.Zero, OsuPlayfield.BASE_SIZE);
|
||||||
|
|
||||||
|
var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition);
|
||||||
|
return new SnapResult(positionSnapGrid.ToScreenSpace(pos), fallbackTime, playfield);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool snapToVisibleBlueprints(Vector2 screenSpacePosition, out SnapResult snapResult)
|
private bool snapToVisibleBlueprints(Vector2 screenSpacePosition, out SnapResult snapResult)
|
||||||
@ -404,22 +407,26 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(targetOffset);
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(targetOffset);
|
||||||
|
|
||||||
int sourceIndex = -1;
|
int positionSourceObjectIndex = -1;
|
||||||
|
IHasSliderVelocity sliderVelocitySource = null;
|
||||||
|
|
||||||
for (int i = 0; i < EditorBeatmap.HitObjects.Count; i++)
|
for (int i = 0; i < EditorBeatmap.HitObjects.Count; i++)
|
||||||
{
|
{
|
||||||
if (!sourceSelector(EditorBeatmap.HitObjects[i]))
|
if (!sourceSelector(EditorBeatmap.HitObjects[i]))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
sourceIndex = i;
|
positionSourceObjectIndex = i;
|
||||||
|
|
||||||
|
if (EditorBeatmap.HitObjects[i] is IHasSliderVelocity hasSliderVelocity)
|
||||||
|
sliderVelocitySource = hasSliderVelocity;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourceIndex == -1)
|
if (positionSourceObjectIndex == -1)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
HitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex];
|
HitObject sourceObject = EditorBeatmap.HitObjects[positionSourceObjectIndex];
|
||||||
|
|
||||||
int targetIndex = sourceIndex + targetOffset;
|
int targetIndex = positionSourceObjectIndex + targetOffset;
|
||||||
HitObject targetObject = null;
|
HitObject targetObject = null;
|
||||||
|
|
||||||
// Keep advancing the target object while its start time falls before the end time of the source object
|
// Keep advancing the target object while its start time falls before the end time of the source object
|
||||||
@ -440,7 +447,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
if (sourceObject is Spinner)
|
if (sourceObject is Spinner)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new OsuDistanceSnapGrid((OsuHitObject)sourceObject, (OsuHitObject)targetObject);
|
return new OsuDistanceSnapGrid((OsuHitObject)sourceObject, (OsuHitObject)targetObject, sliderVelocitySource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
186
osu.Game.Rulesets.Osu/Edit/PreciseMovementPopover.cs
Normal file
186
osu.Game.Rulesets.Osu/Edit/PreciseMovementPopover.cs
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
|
{
|
||||||
|
public partial class PreciseMovementPopover : OsuPopover
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||||
|
|
||||||
|
private readonly Dictionary<HitObject, Vector2> initialPositions = new Dictionary<HitObject, Vector2>();
|
||||||
|
private RectangleF initialSurroundingQuad;
|
||||||
|
|
||||||
|
private BindableNumber<float> xBindable = null!;
|
||||||
|
private BindableNumber<float> yBindable = null!;
|
||||||
|
|
||||||
|
private SliderWithTextBoxInput<float> xInput = null!;
|
||||||
|
private OsuCheckbox relativeCheckbox = null!;
|
||||||
|
|
||||||
|
public PreciseMovementPopover()
|
||||||
|
{
|
||||||
|
AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight };
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Width = 220,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(20),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
xInput = new SliderWithTextBoxInput<float>("X:")
|
||||||
|
{
|
||||||
|
Current = xBindable = new BindableNumber<float>
|
||||||
|
{
|
||||||
|
Precision = 1,
|
||||||
|
},
|
||||||
|
Instantaneous = true,
|
||||||
|
TabbableContentContainer = this,
|
||||||
|
},
|
||||||
|
new SliderWithTextBoxInput<float>("Y:")
|
||||||
|
{
|
||||||
|
Current = yBindable = new BindableNumber<float>
|
||||||
|
{
|
||||||
|
Precision = 1,
|
||||||
|
},
|
||||||
|
Instantaneous = true,
|
||||||
|
TabbableContentContainer = this,
|
||||||
|
},
|
||||||
|
relativeCheckbox = new OsuCheckbox(false)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
LabelText = "Relative movement",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
ScheduleAfterChildren(() => xInput.TakeFocus());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopIn()
|
||||||
|
{
|
||||||
|
base.PopIn();
|
||||||
|
editorBeatmap.BeginChange();
|
||||||
|
initialPositions.AddRange(editorBeatmap.SelectedHitObjects.Where(ho => ho is not Spinner).Select(ho => new KeyValuePair<HitObject, Vector2>(ho, ((IHasPosition)ho).Position)));
|
||||||
|
initialSurroundingQuad = GeometryUtils.GetSurroundingQuad(initialPositions.Keys.Cast<IHasPosition>()).AABBFloat;
|
||||||
|
|
||||||
|
Debug.Assert(initialPositions.Count > 0);
|
||||||
|
|
||||||
|
if (initialPositions.Count > 1)
|
||||||
|
{
|
||||||
|
relativeCheckbox.Current.Value = true;
|
||||||
|
relativeCheckbox.Current.Disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
relativeCheckbox.Current.BindValueChanged(_ => relativeChanged(), true);
|
||||||
|
xBindable.BindValueChanged(_ => applyPosition());
|
||||||
|
yBindable.BindValueChanged(_ => applyPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopOut()
|
||||||
|
{
|
||||||
|
base.PopOut();
|
||||||
|
if (IsLoaded) editorBeatmap.EndChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void relativeChanged()
|
||||||
|
{
|
||||||
|
// reset bindable bounds to something that is guaranteed to be larger than any previous value.
|
||||||
|
// this prevents crashes that can happen in the middle of changing the bounds, as updating both bound ends at the same is not atomic -
|
||||||
|
// if the old and new bounds are disjoint, assigning X first can produce a situation where MinValue > MaxValue.
|
||||||
|
(xBindable.MinValue, xBindable.MaxValue) = (float.MinValue, float.MaxValue);
|
||||||
|
(yBindable.MinValue, yBindable.MaxValue) = (float.MinValue, float.MaxValue);
|
||||||
|
|
||||||
|
float previousX = xBindable.Value;
|
||||||
|
float previousY = yBindable.Value;
|
||||||
|
|
||||||
|
if (relativeCheckbox.Current.Value)
|
||||||
|
{
|
||||||
|
(xBindable.MinValue, xBindable.MaxValue) = (0 - initialSurroundingQuad.TopLeft.X, OsuPlayfield.BASE_SIZE.X - initialSurroundingQuad.BottomRight.X);
|
||||||
|
(yBindable.MinValue, yBindable.MaxValue) = (0 - initialSurroundingQuad.TopLeft.Y, OsuPlayfield.BASE_SIZE.Y - initialSurroundingQuad.BottomRight.Y);
|
||||||
|
|
||||||
|
xBindable.Default = yBindable.Default = 0;
|
||||||
|
|
||||||
|
if (initialPositions.Count == 1)
|
||||||
|
{
|
||||||
|
var initialPosition = initialPositions.Single().Value;
|
||||||
|
xBindable.Value = previousX - initialPosition.X;
|
||||||
|
yBindable.Value = previousY - initialPosition.Y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Assert(initialPositions.Count == 1);
|
||||||
|
var initialPosition = initialPositions.Single().Value;
|
||||||
|
|
||||||
|
var quadRelativeToPosition = new RectangleF(initialSurroundingQuad.Location - initialPosition, initialSurroundingQuad.Size);
|
||||||
|
|
||||||
|
(xBindable.MinValue, xBindable.MaxValue) = (0 - quadRelativeToPosition.TopLeft.X, OsuPlayfield.BASE_SIZE.X - quadRelativeToPosition.BottomRight.X);
|
||||||
|
(yBindable.MinValue, yBindable.MaxValue) = (0 - quadRelativeToPosition.TopLeft.Y, OsuPlayfield.BASE_SIZE.Y - quadRelativeToPosition.BottomRight.Y);
|
||||||
|
|
||||||
|
xBindable.Default = initialPosition.X;
|
||||||
|
yBindable.Default = initialPosition.Y;
|
||||||
|
|
||||||
|
xBindable.Value = xBindable.Default + previousX;
|
||||||
|
yBindable.Value = yBindable.Default + previousY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyPosition()
|
||||||
|
{
|
||||||
|
editorBeatmap.PerformOnSelection(ho =>
|
||||||
|
{
|
||||||
|
if (!initialPositions.TryGetValue(ho, out var initialPosition))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var pos = new Vector2(xBindable.Value, yBindable.Value);
|
||||||
|
if (relativeCheckbox.Current.Value)
|
||||||
|
((IHasPosition)ho).Position = initialPosition + pos;
|
||||||
|
else
|
||||||
|
((IHasPosition)ho).Position = pos;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
if (e.Action == GlobalAction.Select && !e.Repeat)
|
||||||
|
{
|
||||||
|
this.HidePopover();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnPressed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -96,11 +96,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
ScheduleAfterChildren(() =>
|
ScheduleAfterChildren(() => angleInput.TakeFocus());
|
||||||
{
|
|
||||||
angleInput.TakeFocus();
|
|
||||||
angleInput.SelectAll();
|
|
||||||
});
|
|
||||||
angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue });
|
angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue });
|
||||||
|
|
||||||
rotationHandler.CanRotateAroundSelectionOrigin.BindValueChanged(e =>
|
rotationHandler.CanRotateAroundSelectionOrigin.BindValueChanged(e =>
|
||||||
|
@ -139,11 +139,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
ScheduleAfterChildren(() =>
|
ScheduleAfterChildren(() => scaleInput.TakeFocus());
|
||||||
{
|
|
||||||
scaleInput.TakeFocus();
|
|
||||||
scaleInput.SelectAll();
|
|
||||||
});
|
|
||||||
scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue });
|
scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue });
|
||||||
|
|
||||||
xCheckBox.Current.BindValueChanged(_ =>
|
xCheckBox.Current.BindValueChanged(_ =>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
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;
|
||||||
@ -10,6 +11,9 @@ using osu.Framework.Input.Bindings;
|
|||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Components;
|
using osu.Game.Screens.Edit.Components;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -18,9 +22,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
public partial class TransformToolboxGroup : EditorToolboxGroup, IKeyBindingHandler<GlobalAction>
|
public partial class TransformToolboxGroup : EditorToolboxGroup, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
|
private readonly BindableList<HitObject> selectedHitObjects = new BindableList<HitObject>();
|
||||||
|
private readonly BindableBool canMove = new BindableBool();
|
||||||
private readonly AggregateBindable<bool> canRotate = new AggregateBindable<bool>((x, y) => x || y);
|
private readonly AggregateBindable<bool> canRotate = new AggregateBindable<bool>((x, y) => x || y);
|
||||||
private readonly AggregateBindable<bool> canScale = new AggregateBindable<bool>((x, y) => x || y);
|
private readonly AggregateBindable<bool> canScale = new AggregateBindable<bool>((x, y) => x || y);
|
||||||
|
|
||||||
|
private EditorToolButton moveButton = null!;
|
||||||
private EditorToolButton rotateButton = null!;
|
private EditorToolButton rotateButton = null!;
|
||||||
private EditorToolButton scaleButton = null!;
|
private EditorToolButton scaleButton = null!;
|
||||||
|
|
||||||
@ -35,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(EditorBeatmap editorBeatmap)
|
||||||
{
|
{
|
||||||
Child = new FillFlowContainer
|
Child = new FillFlowContainer
|
||||||
{
|
{
|
||||||
@ -44,20 +51,27 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
Spacing = new Vector2(5),
|
Spacing = new Vector2(5),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
moveButton = new EditorToolButton("Move",
|
||||||
|
() => new SpriteIcon { Icon = FontAwesome.Solid.ArrowsAlt },
|
||||||
|
() => new PreciseMovementPopover()),
|
||||||
rotateButton = new EditorToolButton("Rotate",
|
rotateButton = new EditorToolButton("Rotate",
|
||||||
() => new SpriteIcon { Icon = FontAwesome.Solid.Undo },
|
() => new SpriteIcon { Icon = FontAwesome.Solid.Undo },
|
||||||
() => new PreciseRotationPopover(RotationHandler, GridToolbox)),
|
() => new PreciseRotationPopover(RotationHandler, GridToolbox)),
|
||||||
scaleButton = new EditorToolButton("Scale",
|
scaleButton = new EditorToolButton("Scale",
|
||||||
() => new SpriteIcon { Icon = FontAwesome.Solid.ArrowsAlt },
|
() => new SpriteIcon { Icon = FontAwesome.Solid.ExpandArrowsAlt },
|
||||||
() => new PreciseScalePopover(ScaleHandler, GridToolbox))
|
() => new PreciseScalePopover(ScaleHandler, GridToolbox))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
selectedHitObjects.BindTo(editorBeatmap.SelectedHitObjects);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
|
selectedHitObjects.BindCollectionChanged((_, _) => canMove.Value = selectedHitObjects.Any(ho => ho is not Spinner), true);
|
||||||
|
|
||||||
canRotate.AddSource(RotationHandler.CanRotateAroundPlayfieldOrigin);
|
canRotate.AddSource(RotationHandler.CanRotateAroundPlayfieldOrigin);
|
||||||
canRotate.AddSource(RotationHandler.CanRotateAroundSelectionOrigin);
|
canRotate.AddSource(RotationHandler.CanRotateAroundSelectionOrigin);
|
||||||
|
|
||||||
@ -67,6 +81,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
// bindings to `Enabled` on the buttons are decoupled on purpose
|
// bindings to `Enabled` on the buttons are decoupled on purpose
|
||||||
// due to the weird `OsuButton` behaviour of resetting `Enabled` to `false` when `Action` is set.
|
// due to the weird `OsuButton` behaviour of resetting `Enabled` to `false` when `Action` is set.
|
||||||
|
canMove.BindValueChanged(move => moveButton.Enabled.Value = move.NewValue, true);
|
||||||
canRotate.Result.BindValueChanged(rotate => rotateButton.Enabled.Value = rotate.NewValue, true);
|
canRotate.Result.BindValueChanged(rotate => rotateButton.Enabled.Value = rotate.NewValue, true);
|
||||||
canScale.Result.BindValueChanged(scale => scaleButton.Enabled.Value = scale.NewValue, true);
|
canScale.Result.BindValueChanged(scale => scaleButton.Enabled.Value = scale.NewValue, true);
|
||||||
}
|
}
|
||||||
@ -77,6 +92,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
|
case GlobalAction.EditorToggleMoveControl:
|
||||||
|
{
|
||||||
|
moveButton.TriggerClick();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
case GlobalAction.EditorToggleRotateControl:
|
case GlobalAction.EditorToggleRotateControl:
|
||||||
{
|
{
|
||||||
if (!RotationHandler.OperationInProgress.Value || rotateButton.Selected.Value)
|
if (!RotationHandler.OperationInProgress.Value || rotateButton.Selected.Value)
|
||||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override LocalisableString Description => "Flip objects on the chosen axes.";
|
public override LocalisableString Description => "Flip objects on the chosen axes.";
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) };
|
||||||
|
|
||||||
[SettingSource("Mirrored axes", "Choose which axes objects are mirrored over.")]
|
[SettingSource("Flipped axes")]
|
||||||
public Bindable<MirrorType> Reflection { get; } = new Bindable<MirrorType>();
|
public Bindable<MirrorType> Reflection { get; } = new Bindable<MirrorType>();
|
||||||
|
|
||||||
public void ApplyToHitObject(HitObject hitObject)
|
public void ApplyToHitObject(HitObject hitObject)
|
||||||
|
@ -66,8 +66,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Slider slider = drawableSlider.HitObject;
|
Slider slider = drawableSlider.HitObject;
|
||||||
Position = slider.CurvePositionAt(completionProgress);
|
Position = slider.CurvePositionAt(completionProgress);
|
||||||
|
|
||||||
//0.1 / slider.Path.Distance is the additional progress needed to ensure the diff length is 0.1
|
// 0.1 / slider.Path.Distance is the additional progress needed to ensure the diff length is 0.1
|
||||||
var diff = slider.CurvePositionAt(completionProgress) - slider.CurvePositionAt(Math.Min(1, completionProgress + 0.1 / slider.Path.Distance));
|
double checkDistance = 0.1 / slider.Path.Distance;
|
||||||
|
var diff = slider.CurvePositionAt(Math.Min(1 - checkDistance, completionProgress)) - slider.CurvePositionAt(Math.Min(1, completionProgress + checkDistance));
|
||||||
|
|
||||||
// Ensure the value is substantially high enough to allow for Atan2 to get a valid angle.
|
// Ensure the value is substantially high enough to allow for Atan2 to get a valid angle.
|
||||||
// Needed for when near completion, or in case of a very short slider.
|
// Needed for when near completion, or in case of a very short slider.
|
||||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -70,12 +71,24 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
}
|
}
|
||||||
|
|
||||||
var combo = container.OfType<LegacyDefaultComboCounter>().FirstOrDefault();
|
var combo = container.OfType<LegacyDefaultComboCounter>().FirstOrDefault();
|
||||||
|
var spectatorList = container.OfType<SpectatorList>().FirstOrDefault();
|
||||||
|
|
||||||
|
Vector2 pos = new Vector2();
|
||||||
|
|
||||||
if (combo != null)
|
if (combo != null)
|
||||||
{
|
{
|
||||||
combo.Anchor = Anchor.BottomLeft;
|
combo.Anchor = Anchor.BottomLeft;
|
||||||
combo.Origin = Anchor.BottomLeft;
|
combo.Origin = Anchor.BottomLeft;
|
||||||
combo.Scale = new Vector2(1.28f);
|
combo.Scale = new Vector2(1.28f);
|
||||||
|
|
||||||
|
pos += new Vector2(10, -(combo.DrawHeight * 1.56f + 20) * combo.Scale.X);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spectatorList != null)
|
||||||
|
{
|
||||||
|
spectatorList.Anchor = Anchor.BottomLeft;
|
||||||
|
spectatorList.Origin = Anchor.BottomLeft;
|
||||||
|
spectatorList.Position = pos;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
@ -83,6 +96,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
new LegacyDefaultComboCounter(),
|
new LegacyDefaultComboCounter(),
|
||||||
new LegacyKeyCounterDisplay(),
|
new LegacyKeyCounterDisplay(),
|
||||||
|
new SpectatorList(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
@ -16,6 +17,9 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
|||||||
|
|
||||||
public new Hit HitObject => (Hit)base.HitObject;
|
public new Hit HitObject => (Hit)base.HitObject;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private TaikoHitObjectComposer? composer { get; set; }
|
||||||
|
|
||||||
public HitPlacementBlueprint()
|
public HitPlacementBlueprint()
|
||||||
: base(new Hit())
|
: base(new Hit())
|
||||||
{
|
{
|
||||||
@ -40,10 +44,12 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double fallbackTime)
|
||||||
{
|
{
|
||||||
|
var result = composer?.FindSnappedPositionAndTime(screenSpacePosition) ?? new SnapResult(screenSpacePosition, fallbackTime);
|
||||||
piece.Position = ToLocalSpace(result.ScreenSpacePosition);
|
piece.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
base.UpdateTimeAndPosition(result);
|
base.UpdateTimeAndPosition(result.ScreenSpacePosition, result.Time ?? fallbackTime);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
// 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.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
@ -26,12 +25,15 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
|||||||
|
|
||||||
private readonly IHasDuration spanPlacementObject;
|
private readonly IHasDuration spanPlacementObject;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private TaikoHitObjectComposer? composer { get; set; }
|
||||||
|
|
||||||
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(spanPlacementObject.Duration, 0);
|
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(spanPlacementObject.Duration, 0);
|
||||||
|
|
||||||
public TaikoSpanPlacementBlueprint(HitObject hitObject)
|
public TaikoSpanPlacementBlueprint(HitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
spanPlacementObject = hitObject as IHasDuration;
|
spanPlacementObject = (hitObject as IHasDuration)!;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
@ -79,9 +81,11 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
|||||||
EndPlacement(true);
|
EndPlacement(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double fallbackTime)
|
||||||
{
|
{
|
||||||
base.UpdateTimeAndPosition(result);
|
var result = composer?.FindSnappedPositionAndTime(screenSpacePosition) ?? new SnapResult(screenSpacePosition, fallbackTime);
|
||||||
|
|
||||||
|
base.UpdateTimeAndPosition(result.ScreenSpacePosition, result.Time ?? fallbackTime);
|
||||||
|
|
||||||
if (PlacementActive == PlacementState.Active)
|
if (PlacementActive == PlacementState.Active)
|
||||||
{
|
{
|
||||||
@ -116,6 +120,8 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
|||||||
originalPosition = ToLocalSpace(result.ScreenSpacePosition);
|
originalPosition = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,22 @@
|
|||||||
// 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 osu.Framework.Input.Events;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Edit
|
namespace osu.Game.Rulesets.Taiko.Edit
|
||||||
{
|
{
|
||||||
public partial class TaikoBlueprintContainer : ComposeBlueprintContainer
|
public partial class TaikoBlueprintContainer : ComposeBlueprintContainer
|
||||||
{
|
{
|
||||||
public TaikoBlueprintContainer(HitObjectComposer composer)
|
public new TaikoHitObjectComposer Composer => (TaikoHitObjectComposer)base.Composer;
|
||||||
|
|
||||||
|
public TaikoBlueprintContainer(TaikoHitObjectComposer composer)
|
||||||
: base(composer)
|
: base(composer)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -19,5 +25,22 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
|
|
||||||
public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) =>
|
public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) =>
|
||||||
new TaikoSelectionBlueprint(hitObject);
|
new TaikoSelectionBlueprint(hitObject);
|
||||||
|
|
||||||
|
protected override bool TryMoveBlueprints(DragEvent e, IList<(SelectionBlueprint<HitObject> blueprint, Vector2[] originalSnapPositions)> blueprints)
|
||||||
|
{
|
||||||
|
Vector2 distanceTravelled = e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition;
|
||||||
|
|
||||||
|
// The final movement position, relative to movementBlueprintOriginalPosition.
|
||||||
|
Vector2 movePosition = blueprints.First().originalSnapPositions.First() + distanceTravelled;
|
||||||
|
|
||||||
|
// Retrieve a snapped position.
|
||||||
|
var result = Composer.FindSnappedPositionAndTime(movePosition);
|
||||||
|
|
||||||
|
var referenceBlueprint = blueprints.First().blueprint;
|
||||||
|
bool moved = SelectionHandler.HandleMovement(new MoveSelectionEvent<HitObject>(referenceBlueprint, result.ScreenSpacePosition - referenceBlueprint.ScreenSpaceSelectionPoint));
|
||||||
|
if (moved)
|
||||||
|
ApplySnapResultTime(result, referenceBlueprint.Item.StartTime);
|
||||||
|
return moved;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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.Collections.Generic;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
@ -12,6 +13,7 @@ using osu.Game.Screens.Edit.Compose.Components;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Edit
|
namespace osu.Game.Rulesets.Taiko.Edit
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
public partial class TaikoHitObjectComposer : ScrollingHitObjectComposer<TaikoHitObject>
|
public partial class TaikoHitObjectComposer : ScrollingHitObjectComposer<TaikoHitObject>
|
||||||
{
|
{
|
||||||
protected override bool ApplyHorizontalCentering => false;
|
protected override bool ApplyHorizontalCentering => false;
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Utils;
|
|||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.Edit;
|
using osu.Game.Rulesets.Osu.Edit;
|
||||||
@ -67,17 +68,7 @@ namespace osu.Game.Tests.Editing
|
|||||||
{
|
{
|
||||||
AddStep($"set slider multiplier = {multiplier}", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = multiplier);
|
AddStep($"set slider multiplier = {multiplier}", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = multiplier);
|
||||||
|
|
||||||
assertSnapDistance(100 * multiplier, null, true);
|
assertSnapDistance(100 * multiplier);
|
||||||
}
|
|
||||||
|
|
||||||
[TestCase(1)]
|
|
||||||
[TestCase(2)]
|
|
||||||
public void TestSpeedMultiplierDoesNotChangeDistanceSnap(float multiplier)
|
|
||||||
{
|
|
||||||
assertSnapDistance(100, new Slider
|
|
||||||
{
|
|
||||||
SliderVelocityMultiplier = multiplier
|
|
||||||
}, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(1)]
|
[TestCase(1)]
|
||||||
@ -87,7 +78,7 @@ namespace osu.Game.Tests.Editing
|
|||||||
assertSnapDistance(100 * multiplier, new Slider
|
assertSnapDistance(100 * multiplier, new Slider
|
||||||
{
|
{
|
||||||
SliderVelocityMultiplier = multiplier
|
SliderVelocityMultiplier = multiplier
|
||||||
}, true);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(1)]
|
[TestCase(1)]
|
||||||
@ -96,7 +87,7 @@ namespace osu.Game.Tests.Editing
|
|||||||
{
|
{
|
||||||
AddStep($"set divisor = {divisor}", () => BeatDivisor.Value = divisor);
|
AddStep($"set divisor = {divisor}", () => BeatDivisor.Value = divisor);
|
||||||
|
|
||||||
assertSnapDistance(100f / divisor, null, true);
|
assertSnapDistance(100f / divisor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -114,9 +105,8 @@ namespace osu.Game.Tests.Editing
|
|||||||
};
|
};
|
||||||
AddStep("add to beatmap", () => composer.EditorBeatmap.Add(referenceObject));
|
AddStep("add to beatmap", () => composer.EditorBeatmap.Add(referenceObject));
|
||||||
|
|
||||||
assertSnapDistance(base_distance * slider_velocity, referenceObject, true);
|
assertSnapDistance(base_distance * slider_velocity, referenceObject);
|
||||||
assertSnappedDistance(base_distance * slider_velocity + 10, base_distance * slider_velocity, referenceObject);
|
assertSnappedDistance(base_distance * slider_velocity + 10, base_distance * slider_velocity, referenceObject);
|
||||||
assertSnappedDuration(base_distance * slider_velocity + 10, 1000, referenceObject);
|
|
||||||
|
|
||||||
assertDistanceToDuration(base_distance * slider_velocity, 1000, referenceObject);
|
assertDistanceToDuration(base_distance * slider_velocity, 1000, referenceObject);
|
||||||
assertDurationToDistance(1000, base_distance * slider_velocity, referenceObject);
|
assertDurationToDistance(1000, base_distance * slider_velocity, referenceObject);
|
||||||
@ -164,39 +154,6 @@ namespace osu.Game.Tests.Editing
|
|||||||
assertDistanceToDuration(400, 1000);
|
assertDistanceToDuration(400, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestGetSnappedDurationFromDistance()
|
|
||||||
{
|
|
||||||
assertSnappedDuration(0, 0);
|
|
||||||
assertSnappedDuration(50, 1000);
|
|
||||||
assertSnappedDuration(100, 1000);
|
|
||||||
assertSnappedDuration(150, 2000);
|
|
||||||
assertSnappedDuration(200, 2000);
|
|
||||||
assertSnappedDuration(250, 3000);
|
|
||||||
|
|
||||||
AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = 2);
|
|
||||||
|
|
||||||
assertSnappedDuration(0, 0);
|
|
||||||
assertSnappedDuration(50, 0);
|
|
||||||
assertSnappedDuration(100, 1000);
|
|
||||||
assertSnappedDuration(150, 1000);
|
|
||||||
assertSnappedDuration(200, 1000);
|
|
||||||
assertSnappedDuration(250, 1000);
|
|
||||||
|
|
||||||
AddStep("set beat length = 500", () =>
|
|
||||||
{
|
|
||||||
composer.EditorBeatmap.ControlPointInfo.Clear();
|
|
||||||
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
|
||||||
});
|
|
||||||
|
|
||||||
assertSnappedDuration(50, 0);
|
|
||||||
assertSnappedDuration(100, 500);
|
|
||||||
assertSnappedDuration(150, 500);
|
|
||||||
assertSnappedDuration(200, 500);
|
|
||||||
assertSnappedDuration(250, 500);
|
|
||||||
assertSnappedDuration(400, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void GetSnappedDistanceFromDistance()
|
public void GetSnappedDistanceFromDistance()
|
||||||
{
|
{
|
||||||
@ -289,20 +246,17 @@ namespace osu.Game.Tests.Editing
|
|||||||
AddUntilStep("use current snap not available", () => getCurrentSnapButton().Enabled.Value, () => Is.False);
|
AddUntilStep("use current snap not available", () => getCurrentSnapButton().Enabled.Value, () => Is.False);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertSnapDistance(float expectedDistance, HitObject? referenceObject, bool includeSliderVelocity)
|
private void assertSnapDistance(float expectedDistance, IHasSliderVelocity? hasSliderVelocity = null)
|
||||||
=> AddAssert($"distance is {expectedDistance}", () => composer.DistanceSnapProvider.GetBeatSnapDistanceAt(referenceObject ?? new HitObject(), includeSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
=> AddAssert($"distance is {expectedDistance}", () => composer.DistanceSnapProvider.GetBeatSnapDistance(hasSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
||||||
|
|
||||||
private void assertDurationToDistance(double duration, float expectedDistance, HitObject? referenceObject = null)
|
private void assertDurationToDistance(double duration, float expectedDistance, HitObject? referenceObject = null)
|
||||||
=> AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DistanceSnapProvider.DurationToDistance(referenceObject ?? new HitObject(), duration), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
=> AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DistanceSnapProvider.DurationToDistance(duration, referenceObject?.StartTime ?? 0, referenceObject as IHasSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
||||||
|
|
||||||
private void assertDistanceToDuration(float distance, double expectedDuration, HitObject? referenceObject = null)
|
private void assertDistanceToDuration(float distance, double expectedDuration, HitObject? referenceObject = null)
|
||||||
=> AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceSnapProvider.DistanceToDuration(referenceObject ?? new HitObject(), distance), () => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON));
|
=> AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceSnapProvider.DistanceToDuration(distance, referenceObject?.StartTime ?? 0, referenceObject as IHasSliderVelocity), () => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON));
|
||||||
|
|
||||||
private void assertSnappedDuration(float distance, double expectedDuration, HitObject? referenceObject = null)
|
|
||||||
=> AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.DistanceSnapProvider.FindSnappedDuration(referenceObject ?? new HitObject(), distance), () => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON));
|
|
||||||
|
|
||||||
private void assertSnappedDistance(float distance, float expectedDistance, HitObject? referenceObject = null)
|
private void assertSnappedDistance(float distance, float expectedDistance, HitObject? referenceObject = null)
|
||||||
=> AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.DistanceSnapProvider.FindSnappedDistance(referenceObject ?? new HitObject(), distance, DistanceSnapTarget.End), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
=> AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.DistanceSnapProvider.FindSnappedDistance(distance, referenceObject?.GetEndTime() ?? 0, referenceObject as IHasSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
||||||
|
|
||||||
private partial class TestHitObjectComposer : OsuHitObjectComposer
|
private partial class TestHitObjectComposer : OsuHitObjectComposer
|
||||||
{
|
{
|
||||||
|
@ -36,6 +36,10 @@ namespace osu.Game.Tests.NonVisual.Ranking
|
|||||||
.Select(t => new HitEvent(t - 5, 1.0, HitResult.Great, new HitObject(), null, null))
|
.Select(t => new HitEvent(t - 5, 1.0, HitResult.Great, new HitObject(), null, null))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
// Add some red herrings
|
||||||
|
events.Insert(4, new HitEvent(200, 1.0, HitResult.Meh, new HitObject { HitWindows = HitWindows.Empty }, null, null));
|
||||||
|
events.Insert(8, new HitEvent(-100, 1.0, HitResult.Miss, new HitObject(), null, null));
|
||||||
|
|
||||||
HitEventExtensions.UnstableRateCalculationResult result = null;
|
HitEventExtensions.UnstableRateCalculationResult result = null;
|
||||||
|
|
||||||
for (int i = 0; i < events.Count; i++)
|
for (int i = 0; i < events.Count; i++)
|
||||||
@ -57,6 +61,10 @@ namespace osu.Game.Tests.NonVisual.Ranking
|
|||||||
.Select(t => new HitEvent(t - 5, 1.0, HitResult.Great, new HitObject(), null, null))
|
.Select(t => new HitEvent(t - 5, 1.0, HitResult.Great, new HitObject(), null, null))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
// Add some red herrings
|
||||||
|
events.Insert(4, new HitEvent(200, 1.0, HitResult.Meh, new HitObject { HitWindows = HitWindows.Empty }, null, null));
|
||||||
|
events.Insert(8, new HitEvent(-100, 1.0, HitResult.Miss, new HitObject(), null, null));
|
||||||
|
|
||||||
HitEventExtensions.UnstableRateCalculationResult result = null;
|
HitEventExtensions.UnstableRateCalculationResult result = null;
|
||||||
|
|
||||||
for (int i = 0; i < events.Count; i++)
|
for (int i = 0; i < events.Count; i++)
|
||||||
|
52
osu.Game.Tests/Online/TestSceneMetadataClient.cs
Normal file
52
osu.Game.Tests/Online/TestSceneMetadataClient.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osu.Game.Tests.Visual.Metadata;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Online
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
[HeadlessTest]
|
||||||
|
public partial class TestSceneMetadataClient : OsuTestScene
|
||||||
|
{
|
||||||
|
private TestMetadataClient client = null!;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Child = client = new TestMetadataClient();
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestWatchingMultipleTimesInvokesServerMethodsOnce()
|
||||||
|
{
|
||||||
|
int countBegin = 0;
|
||||||
|
int countEnd = 0;
|
||||||
|
|
||||||
|
IDisposable token1 = null!;
|
||||||
|
IDisposable token2 = null!;
|
||||||
|
|
||||||
|
AddStep("setup", () =>
|
||||||
|
{
|
||||||
|
client.OnBeginWatchingUserPresence += () => countBegin++;
|
||||||
|
client.OnEndWatchingUserPresence += () => countEnd++;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("begin watching presence (1)", () => token1 = client.BeginWatchingUserPresence());
|
||||||
|
AddAssert("server method invoked once", () => countBegin, () => Is.EqualTo(1));
|
||||||
|
|
||||||
|
AddStep("begin watching presence (2)", () => token2 = client.BeginWatchingUserPresence());
|
||||||
|
AddAssert("server method not invoked a second time", () => countBegin, () => Is.EqualTo(1));
|
||||||
|
|
||||||
|
AddStep("end watching presence (1)", () => token1.Dispose());
|
||||||
|
AddAssert("server method not invoked", () => countEnd, () => Is.EqualTo(0));
|
||||||
|
|
||||||
|
AddStep("end watching presence (2)", () => token2.Dispose());
|
||||||
|
AddAssert("server method invoked once", () => countEnd, () => Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
osu.Game.Tests/Resources/Archives/modified-argon-20250116.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-argon-20250116.osk
Normal file
Binary file not shown.
@ -71,6 +71,8 @@ namespace osu.Game.Tests.Skins
|
|||||||
"Archives/modified-classic-20240724.osk",
|
"Archives/modified-classic-20240724.osk",
|
||||||
// Covers skinnable mod display
|
// Covers skinnable mod display
|
||||||
"Archives/modified-default-20241207.osk",
|
"Archives/modified-default-20241207.osk",
|
||||||
|
// Covers skinnable spectator list
|
||||||
|
"Archives/modified-argon-20250116.osk",
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Screens.Edit.Submission;
|
||||||
|
using osu.Game.Screens.Footer;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
public partial class TestSceneBeatmapSubmissionOverlay : OsuTestScene
|
||||||
|
{
|
||||||
|
private ScreenFooter footer = null!;
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("add overlay", () =>
|
||||||
|
{
|
||||||
|
var receptor = new ScreenFooter.BackReceptor();
|
||||||
|
footer = new ScreenFooter(receptor);
|
||||||
|
|
||||||
|
Child = new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CachedDependencies = new[] { (typeof(ScreenFooter), (object)footer) },
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
receptor,
|
||||||
|
new BeatmapSubmissionOverlay
|
||||||
|
{
|
||||||
|
State = { Value = Visibility.Visible, },
|
||||||
|
},
|
||||||
|
footer,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
public new int MaxIntervals => base.MaxIntervals;
|
public new int MaxIntervals => base.MaxIntervals;
|
||||||
|
|
||||||
public TestDistanceSnapGrid(double? endTime = null)
|
public TestDistanceSnapGrid(double? endTime = null)
|
||||||
: base(new HitObject(), grid_position, 0, endTime)
|
: base(grid_position, 0, endTime)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +181,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override (Vector2 position, double time) GetSnappedPosition(Vector2 screenSpacePosition)
|
public override (Vector2 position, double time) GetSnappedPosition(Vector2 screenSpacePosition, double? fixedTime = null)
|
||||||
=> (Vector2.Zero, 0);
|
=> (Vector2.Zero, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,15 +191,13 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
Bindable<double> IDistanceSnapProvider.DistanceSpacingMultiplier => DistanceSpacingMultiplier;
|
Bindable<double> IDistanceSnapProvider.DistanceSpacingMultiplier => DistanceSpacingMultiplier;
|
||||||
|
|
||||||
public float GetBeatSnapDistanceAt(HitObject referenceObject, bool useReferenceSliderVelocity = true) => beat_snap_distance;
|
public float GetBeatSnapDistance(IHasSliderVelocity withVelocity = null) => beat_snap_distance;
|
||||||
|
|
||||||
public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration;
|
public float DurationToDistance(double duration, double timingReference, IHasSliderVelocity withVelocity = null) => (float)duration;
|
||||||
|
|
||||||
public double DistanceToDuration(HitObject referenceObject, float distance) => distance;
|
public double DistanceToDuration(float distance, double timingReference, IHasSliderVelocity withVelocity = null) => distance;
|
||||||
|
|
||||||
public double FindSnappedDuration(HitObject referenceObject, float distance) => 0;
|
public float FindSnappedDistance(float distance, double snapReferenceTime, IHasSliderVelocity withVelocity = null) => 0;
|
||||||
|
|
||||||
public float FindSnappedDistance(HitObject referenceObject, float distance, DistanceSnapTarget target) => 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1);
|
AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1);
|
||||||
|
|
||||||
AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == newTime);
|
AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == EditorBeatmap.SnapTime(newTime, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -122,6 +122,8 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
public void TestCopyPaste(bool deselectAfterCopy)
|
public void TestCopyPaste(bool deselectAfterCopy)
|
||||||
{
|
{
|
||||||
|
const int paste_time = 2000;
|
||||||
|
|
||||||
var addedObject = new HitCircle { StartTime = 1000 };
|
var addedObject = new HitCircle { StartTime = 1000 };
|
||||||
|
|
||||||
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
||||||
@ -130,7 +132,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddStep("copy hitobject", () => Editor.Copy());
|
AddStep("copy hitobject", () => Editor.Copy());
|
||||||
|
|
||||||
AddStep("move forward in time", () => EditorClock.Seek(2000));
|
AddStep("move forward in time", () => EditorClock.Seek(paste_time));
|
||||||
|
|
||||||
if (deselectAfterCopy)
|
if (deselectAfterCopy)
|
||||||
{
|
{
|
||||||
@ -144,7 +146,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddAssert("are two objects", () => EditorBeatmap.HitObjects.Count == 2);
|
AddAssert("are two objects", () => EditorBeatmap.HitObjects.Count == 2);
|
||||||
|
|
||||||
AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == 2000);
|
AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == EditorBeatmap.SnapTime(paste_time, null));
|
||||||
|
|
||||||
AddUntilStep("timeline selection box is visible", () => Editor.ChildrenOfType<Timeline>().First().ChildrenOfType<EditorSelectionHandler>().First().Alpha > 0);
|
AddUntilStep("timeline selection box is visible", () => Editor.ChildrenOfType<Timeline>().First().ChildrenOfType<EditorSelectionHandler>().First().Alpha > 0);
|
||||||
AddUntilStep("composer selection box is visible", () => Editor.ChildrenOfType<HitObjectComposer>().First().ChildrenOfType<EditorSelectionHandler>().First().Alpha > 0);
|
AddUntilStep("composer selection box is visible", () => Editor.ChildrenOfType<HitObjectComposer>().First().ChildrenOfType<EditorSelectionHandler>().First().Alpha > 0);
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
public partial class TestSceneEditorClipboardSnapping : EditorTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
private const double beat_length = 60_000 / 180.0; // 180 bpm
|
||||||
|
private const double timing_point_time = 1500;
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||||
|
{
|
||||||
|
var controlPointInfo = new ControlPointInfo();
|
||||||
|
controlPointInfo.Add(timing_point_time, new TimingControlPoint { BeatLength = beat_length });
|
||||||
|
return new TestBeatmap(ruleset, false)
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPointInfo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(1)]
|
||||||
|
[TestCase(2)]
|
||||||
|
[TestCase(3)]
|
||||||
|
[TestCase(4)]
|
||||||
|
[TestCase(6)]
|
||||||
|
[TestCase(8)]
|
||||||
|
[TestCase(12)]
|
||||||
|
[TestCase(16)]
|
||||||
|
public void TestPasteSnapping(int divisor)
|
||||||
|
{
|
||||||
|
const double paste_time = timing_point_time + 1271; // arbitrary timestamp that doesn't snap to the timing point at any divisor
|
||||||
|
|
||||||
|
var addedObjects = new HitObject[]
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000 },
|
||||||
|
new HitCircle { StartTime = 1200 },
|
||||||
|
};
|
||||||
|
|
||||||
|
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
|
||||||
|
AddStep("select added objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects));
|
||||||
|
AddStep("copy hitobjects", () => Editor.Copy());
|
||||||
|
|
||||||
|
AddStep($"set beat divisor to 1/{divisor}", () =>
|
||||||
|
{
|
||||||
|
var beatDivisor = (BindableBeatDivisor)Editor.Dependencies.Get(typeof(BindableBeatDivisor));
|
||||||
|
beatDivisor.SetArbitraryDivisor(divisor);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("move forward in time", () => EditorClock.Seek(paste_time));
|
||||||
|
AddAssert("not at snapped time", () => EditorClock.CurrentTime != EditorBeatmap.SnapTime(EditorClock.CurrentTime, null));
|
||||||
|
|
||||||
|
AddStep("paste hitobjects", () => Editor.Paste());
|
||||||
|
|
||||||
|
AddAssert("first object is snapped", () => Precision.AlmostEquals(
|
||||||
|
EditorBeatmap.SelectedHitObjects.MinBy(h => h.StartTime)!.StartTime,
|
||||||
|
EditorBeatmap.ControlPointInfo.GetClosestSnappedTime(paste_time, divisor)
|
||||||
|
));
|
||||||
|
|
||||||
|
AddAssert("duration between pasted objects is same", () =>
|
||||||
|
{
|
||||||
|
var firstObject = EditorBeatmap.SelectedHitObjects.MinBy(h => h.StartTime)!;
|
||||||
|
var secondObject = EditorBeatmap.SelectedHitObjects.MaxBy(h => h.StartTime)!;
|
||||||
|
|
||||||
|
return Precision.AlmostEquals(secondObject.StartTime - firstObject.StartTime, addedObjects[1].StartTime - addedObjects[0].StartTime);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
@ -25,13 +26,16 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestLocallyModifyingOnlineBeatmap()
|
public void TestLocallyModifyingOnlineBeatmap()
|
||||||
{
|
{
|
||||||
|
string initialHash = string.Empty;
|
||||||
AddAssert("editor beatmap has online ID", () => EditorBeatmap.BeatmapInfo.OnlineID, () => Is.GreaterThan(0));
|
AddAssert("editor beatmap has online ID", () => EditorBeatmap.BeatmapInfo.OnlineID, () => Is.GreaterThan(0));
|
||||||
|
AddStep("store hash for later", () => initialHash = EditorBeatmap.BeatmapInfo.MD5Hash);
|
||||||
|
|
||||||
AddStep("delete first hitobject", () => EditorBeatmap.RemoveAt(0));
|
AddStep("delete first hitobject", () => EditorBeatmap.RemoveAt(0));
|
||||||
SaveEditor();
|
SaveEditor();
|
||||||
|
|
||||||
ReloadEditorToSameBeatmap();
|
ReloadEditorToSameBeatmap();
|
||||||
AddAssert("editor beatmap online ID reset", () => EditorBeatmap.BeatmapInfo.OnlineID, () => Is.EqualTo(-1));
|
AddAssert("beatmap marked as locally modified", () => EditorBeatmap.BeatmapInfo.Status, () => Is.EqualTo(BeatmapOnlineStatus.LocallyModified));
|
||||||
|
AddAssert("beatmap hash changed", () => EditorBeatmap.BeatmapInfo.MD5Hash, () => Is.Not.EqualTo(initialHash));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,6 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[Solo]
|
|
||||||
public void TestCommitPlacementViaRightClick()
|
public void TestCommitPlacementViaRightClick()
|
||||||
{
|
{
|
||||||
Playfield playfield = null!;
|
Playfield playfield = null!;
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Screens.Edit.Submission;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
public partial class TestSceneSubmissionStageProgress : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAppearance()
|
||||||
|
{
|
||||||
|
SubmissionStageProgress progress = null!;
|
||||||
|
|
||||||
|
AddStep("create content", () => Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Size = new Vector2(0.8f),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Child = progress = new SubmissionStageProgress
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
StageDescription = "Frobnicating the foobarator...",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AddStep("not started", () => progress.SetNotStarted());
|
||||||
|
AddStep("indeterminate progress", () => progress.SetInProgress());
|
||||||
|
AddStep("30% progress", () => progress.SetInProgress(0.3f));
|
||||||
|
AddStep("70% progress", () => progress.SetInProgress(0.7f));
|
||||||
|
AddStep("completed", () => progress.SetCompleted());
|
||||||
|
AddStep("failed", () => progress.SetFailed("the foobarator has defrobnicated"));
|
||||||
|
AddStep("failed with long message", () => progress.SetFailed("this is a very very very very VERY VEEEEEEEEEEEEEEEEEEEEEEEEERY long error message like you would never believe"));
|
||||||
|
AddStep("canceled", () => progress.SetCanceled());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -120,6 +120,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
public double FramesPerSecond => throw new NotImplementedException();
|
public double FramesPerSecond => throw new NotImplementedException();
|
||||||
public FrameTimeInfo TimeInfo => throw new NotImplementedException();
|
public FrameTimeInfo TimeInfo => throw new NotImplementedException();
|
||||||
public double StartTime => throw new NotImplementedException();
|
public double StartTime => throw new NotImplementedException();
|
||||||
|
public double GameplayStartTime => throw new NotImplementedException();
|
||||||
|
|
||||||
public IAdjustableAudioComponent AdjustmentsFromMods => adjustableAudioComponent;
|
public IAdjustableAudioComponent AdjustmentsFromMods => adjustableAudioComponent;
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ using osu.Game.Online.API;
|
|||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Online.Solo;
|
using osu.Game.Online.Solo;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania;
|
using osu.Game.Rulesets.Mania;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -234,6 +235,31 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
|
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNoSubmissionWhenScoreZero()
|
||||||
|
{
|
||||||
|
prepareTestAPI(true);
|
||||||
|
|
||||||
|
createPlayerTest();
|
||||||
|
|
||||||
|
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||||
|
|
||||||
|
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||||
|
AddUntilStep("wait for first result", () => Player.Results.Count > 0);
|
||||||
|
|
||||||
|
AddStep("add fake non-scoring hit", () =>
|
||||||
|
{
|
||||||
|
Player.ScoreProcessor.RevertResult(Player.Results.First());
|
||||||
|
Player.ScoreProcessor.ApplyResult(new OsuJudgementResult(Beatmap.Value.Beatmap.HitObjects.First(), new IgnoreJudgement())
|
||||||
|
{
|
||||||
|
Type = HitResult.IgnoreHit,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("exit", () => Player.Exit());
|
||||||
|
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSubmissionOnExit()
|
public void TestSubmissionOnExit()
|
||||||
{
|
{
|
||||||
|
@ -6,48 +6,81 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Online.Spectator;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Tests.Visual.Multiplayer;
|
||||||
|
using osu.Game.Tests.Visual.OnlinePlay;
|
||||||
|
using osu.Game.Tests.Visual.Spectator;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public partial class TestSceneSpectatorList : OsuTestScene
|
public partial class TestSceneSpectatorList : OsuTestScene
|
||||||
{
|
{
|
||||||
private readonly BindableList<SpectatorList.Spectator> spectators = new BindableList<SpectatorList.Spectator>();
|
|
||||||
private readonly Bindable<LocalUserPlayingState> localUserPlayingState = new Bindable<LocalUserPlayingState>();
|
|
||||||
|
|
||||||
private int counter;
|
private int counter;
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBasics()
|
public void TestBasics()
|
||||||
{
|
{
|
||||||
SpectatorList list = null!;
|
SpectatorList list = null!;
|
||||||
AddStep("create spectator list", () => Child = list = new SpectatorList
|
Bindable<LocalUserPlayingState> playingState = new Bindable<LocalUserPlayingState>();
|
||||||
|
GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), healthProcessor: new OsuHealthProcessor(0), localUserPlayingState: playingState);
|
||||||
|
TestSpectatorClient spectatorClient = new TestSpectatorClient();
|
||||||
|
TestMultiplayerClient multiplayerClient = new TestMultiplayerClient(new TestMultiplayerRoomManager(new TestRoomRequestsHandler()));
|
||||||
|
|
||||||
|
AddStep("create spectator list", () =>
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Children = new Drawable[]
|
||||||
Origin = Anchor.Centre,
|
{
|
||||||
Spectators = { BindTarget = spectators },
|
spectatorClient,
|
||||||
UserPlayingState = { BindTarget = localUserPlayingState }
|
multiplayerClient,
|
||||||
|
new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CachedDependencies =
|
||||||
|
[
|
||||||
|
(typeof(GameplayState), gameplayState),
|
||||||
|
(typeof(SpectatorClient), spectatorClient),
|
||||||
|
(typeof(MultiplayerClient), multiplayerClient),
|
||||||
|
],
|
||||||
|
Child = list = new SpectatorList
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("start playing", () => localUserPlayingState.Value = LocalUserPlayingState.Playing);
|
AddStep("start playing", () => playingState.Value = LocalUserPlayingState.Playing);
|
||||||
|
|
||||||
AddRepeatStep("add a user", () =>
|
AddRepeatStep("add a user", () =>
|
||||||
{
|
{
|
||||||
int id = Interlocked.Increment(ref counter);
|
int id = Interlocked.Increment(ref counter);
|
||||||
spectators.Add(new SpectatorList.Spectator(id, $"User {id}"));
|
((ISpectatorClient)spectatorClient).UserStartedWatching([
|
||||||
|
new SpectatorUser
|
||||||
|
{
|
||||||
|
OnlineID = id,
|
||||||
|
Username = $"User {id}"
|
||||||
|
}
|
||||||
|
]);
|
||||||
}, 10);
|
}, 10);
|
||||||
|
|
||||||
AddRepeatStep("remove random user", () => spectators.RemoveAt(RNG.Next(0, spectators.Count)), 5);
|
AddRepeatStep("remove random user", () => ((ISpectatorClient)spectatorClient).UserEndedWatching(
|
||||||
|
spectatorClient.WatchingUsers[RNG.Next(spectatorClient.WatchingUsers.Count)].OnlineID), 5);
|
||||||
|
|
||||||
AddStep("change font to venera", () => list.Font.Value = Typeface.Venera);
|
AddStep("change font to venera", () => list.Font.Value = Typeface.Venera);
|
||||||
AddStep("change font to torus", () => list.Font.Value = Typeface.Torus);
|
AddStep("change font to torus", () => list.Font.Value = Typeface.Torus);
|
||||||
AddStep("change header colour", () => list.HeaderColour.Value = new Colour4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1));
|
AddStep("change header colour", () => list.HeaderColour.Value = new Colour4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1));
|
||||||
|
|
||||||
AddStep("enter break", () => localUserPlayingState.Value = LocalUserPlayingState.Break);
|
AddStep("enter break", () => playingState.Value = LocalUserPlayingState.Break);
|
||||||
AddStep("stop playing", () => localUserPlayingState.Value = LocalUserPlayingState.NotPlaying);
|
AddStep("stop playing", () => playingState.Value = LocalUserPlayingState.NotPlaying);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
new APIMenuImage
|
new APIMenuImage
|
||||||
{
|
{
|
||||||
Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png",
|
Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png",
|
||||||
Url = $@"{API.WebsiteRootUrl}/home/news/2023-12-21-project-loved-december-2023",
|
Url = $@"{API.Endpoints.WebsiteUrl}/home/news/2023-12-21-project-loved-december-2023",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -60,14 +60,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
private void setUp()
|
private void setUp()
|
||||||
{
|
{
|
||||||
AddStep("reset", () =>
|
AddStep("create song select", () =>
|
||||||
{
|
{
|
||||||
Ruleset.Value = new OsuRuleset().RulesetInfo;
|
Ruleset.Value = new OsuRuleset().RulesetInfo;
|
||||||
Beatmap.SetDefault();
|
Beatmap.SetDefault();
|
||||||
SelectedMods.SetDefault();
|
SelectedMods.SetDefault();
|
||||||
|
|
||||||
|
LoadScreen(songSelect = new TestMultiplayerMatchSongSelect(SelectedRoom.Value!));
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("create song select", () => LoadScreen(songSelect = new TestMultiplayerMatchSongSelect(SelectedRoom.Value!)));
|
|
||||||
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
|
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ using osu.Game.Screens.OnlinePlay;
|
|||||||
using osu.Game.Screens.OnlinePlay.Match;
|
using osu.Game.Screens.OnlinePlay.Match;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
@ -271,7 +272,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
AddUntilStep("last playlist item selected", () =>
|
AddUntilStep("last playlist item selected", () =>
|
||||||
{
|
{
|
||||||
var lastItem = this.ChildrenOfType<DrawableRoomPlaylistItem>().Single(p => p.Item.ID == MultiplayerClient.ServerAPIRoom?.Playlist.Last().ID);
|
var lastItem = this.ChildrenOfType<MultiplayerQueueList>()
|
||||||
|
.Single()
|
||||||
|
.ChildrenOfType<DrawableRoomPlaylistItem>()
|
||||||
|
.Single(p => p.Item.ID == MultiplayerClient.ServerAPIRoom?.Playlist.Last().ID);
|
||||||
return lastItem.IsSelectedItem;
|
return lastItem.IsSelectedItem;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -308,6 +308,33 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddStep("set state: locally available", () => MultiplayerClient.ChangeUserBeatmapAvailability(0, BeatmapAvailability.LocallyAvailable()));
|
AddStep("set state: locally available", () => MultiplayerClient.ChangeUserBeatmapAvailability(0, BeatmapAvailability.LocallyAvailable()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUserWithStyle()
|
||||||
|
{
|
||||||
|
AddStep("add users", () =>
|
||||||
|
{
|
||||||
|
MultiplayerClient.AddUser(new APIUser
|
||||||
|
{
|
||||||
|
Id = 0,
|
||||||
|
Username = "User 0",
|
||||||
|
RulesetsStatistics = new Dictionary<string, UserStatistics>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
Ruleset.Value.ShortName,
|
||||||
|
new UserStatistics { GlobalRank = RNG.Next(1, 100000), }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||||
|
});
|
||||||
|
|
||||||
|
MultiplayerClient.ChangeUserStyle(0, 259, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set beatmap locally available", () => MultiplayerClient.ChangeUserBeatmapAvailability(0, BeatmapAvailability.LocallyAvailable()));
|
||||||
|
AddStep("change user style to beatmap: 258, ruleset: 1", () => MultiplayerClient.ChangeUserStyle(0, 258, 1));
|
||||||
|
AddStep("change user style to beatmap: null, ruleset: null", () => MultiplayerClient.ChangeUserStyle(0, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestModOverlap()
|
public void TestModOverlap()
|
||||||
{
|
{
|
||||||
|
@ -165,7 +165,6 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[Solo]
|
|
||||||
public void TestEditorGameplayTestAlwaysUsesOriginalRuleset()
|
public void TestEditorGameplayTestAlwaysUsesOriginalRuleset()
|
||||||
{
|
{
|
||||||
prepareBeatmap();
|
prepareBeatmap();
|
||||||
|
@ -48,6 +48,7 @@ using osu.Game.Screens.Select.Carousel;
|
|||||||
using osu.Game.Screens.Select.Leaderboards;
|
using osu.Game.Screens.Select.Leaderboards;
|
||||||
using osu.Game.Screens.Select.Options;
|
using osu.Game.Screens.Select.Options;
|
||||||
using osu.Game.Tests.Beatmaps.IO;
|
using osu.Game.Tests.Beatmaps.IO;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -202,6 +203,38 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
TextBox filterControlTextBox() => songSelect.ChildrenOfType<FilterControl.FilterControlTextBox>().Single();
|
TextBox filterControlTextBox() => songSelect.ChildrenOfType<FilterControl.FilterControlTextBox>().Single();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSongSelectRandomRewindButton()
|
||||||
|
{
|
||||||
|
Guid? originalSelection = null;
|
||||||
|
TestPlaySongSelect songSelect = null;
|
||||||
|
|
||||||
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
|
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
|
AddStep("Add two beatmaps", () =>
|
||||||
|
{
|
||||||
|
Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo(8));
|
||||||
|
Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo(8));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for selected", () =>
|
||||||
|
{
|
||||||
|
originalSelection = Game.Beatmap.Value.BeatmapInfo.ID;
|
||||||
|
return !Game.Beatmap.IsDefault;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("hit random", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(Game.ChildrenOfType<FooterButtonRandom>().Single());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddUntilStep("wait for selection changed", () => Game.Beatmap.Value.BeatmapInfo.ID, () => Is.Not.EqualTo(originalSelection));
|
||||||
|
|
||||||
|
AddStep("hit random rewind", () => InputManager.Click(MouseButton.Right));
|
||||||
|
AddUntilStep("wait for selection reverted", () => Game.Beatmap.Value.BeatmapInfo.ID, () => Is.EqualTo(originalSelection));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSongSelectScrollHandling()
|
public void TestSongSelectScrollHandling()
|
||||||
{
|
{
|
||||||
|
@ -65,7 +65,9 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestBasicDisplay()
|
public void TestBasicDisplay()
|
||||||
{
|
{
|
||||||
AddStep("Begin watching user presence", () => metadataClient.BeginWatchingUserPresence());
|
IDisposable token = null!;
|
||||||
|
|
||||||
|
AddStep("Begin watching user presence", () => token = metadataClient.BeginWatchingUserPresence());
|
||||||
AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
|
AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
|
||||||
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<UserGridPanel>().FirstOrDefault()?.User.Id == 2);
|
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<UserGridPanel>().FirstOrDefault()?.User.Id == 2);
|
||||||
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
|
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
|
||||||
@ -78,14 +80,16 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null));
|
AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null));
|
||||||
AddUntilStep("Panel no longer present", () => !currentlyOnline.ChildrenOfType<UserGridPanel>().Any());
|
AddUntilStep("Panel no longer present", () => !currentlyOnline.ChildrenOfType<UserGridPanel>().Any());
|
||||||
AddStep("End watching user presence", () => metadataClient.EndWatchingUserPresence());
|
AddStep("End watching user presence", () => token.Dispose());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestUserWasPlayingBeforeWatchingUserPresence()
|
public void TestUserWasPlayingBeforeWatchingUserPresence()
|
||||||
{
|
{
|
||||||
|
IDisposable token = null!;
|
||||||
|
|
||||||
AddStep("User began playing", () => spectatorClient.SendStartPlay(streamingUser.Id, 0));
|
AddStep("User began playing", () => spectatorClient.SendStartPlay(streamingUser.Id, 0));
|
||||||
AddStep("Begin watching user presence", () => metadataClient.BeginWatchingUserPresence());
|
AddStep("Begin watching user presence", () => token = metadataClient.BeginWatchingUserPresence());
|
||||||
AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
|
AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
|
||||||
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<UserGridPanel>().FirstOrDefault()?.User.Id == 2);
|
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<UserGridPanel>().FirstOrDefault()?.User.Id == 2);
|
||||||
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.True);
|
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.True);
|
||||||
@ -93,7 +97,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddStep("User finished playing", () => spectatorClient.SendEndPlay(streamingUser.Id));
|
AddStep("User finished playing", () => spectatorClient.SendEndPlay(streamingUser.Id));
|
||||||
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
|
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
|
||||||
AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null));
|
AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null));
|
||||||
AddStep("End watching user presence", () => metadataClient.EndWatchingUserPresence());
|
AddStep("End watching user presence", () => token.Dispose());
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class TestUserLookupCache : UserLookupCache
|
internal partial class TestUserLookupCache : UserLookupCache
|
||||||
|
@ -4,17 +4,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online;
|
using osu.Game.Online;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Online.Metadata;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osu.Game.Tests.Visual.Metadata;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -23,144 +24,142 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public partial class TestSceneUserPanel : OsuTestScene
|
public partial class TestSceneUserPanel : OsuTestScene
|
||||||
{
|
{
|
||||||
private readonly Bindable<UserActivity?> activity = new Bindable<UserActivity?>();
|
|
||||||
private readonly Bindable<UserStatus?> status = new Bindable<UserStatus?>();
|
|
||||||
|
|
||||||
private UserGridPanel boundPanel1 = null!;
|
|
||||||
private TestUserListPanel boundPanel2 = null!;
|
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
[Cached(typeof(LocalUserStatisticsProvider))]
|
|
||||||
private readonly TestUserStatisticsProvider statisticsProvider = new TestUserStatisticsProvider();
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IRulesetStore rulesetStore { get; set; } = null!;
|
private IRulesetStore rulesetStore { get; set; } = null!;
|
||||||
|
|
||||||
|
private TestUserStatisticsProvider statisticsProvider = null!;
|
||||||
|
private TestMetadataClient metadataClient = null!;
|
||||||
|
private TestUserListPanel panel = null!;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() => Schedule(() =>
|
public void SetUp() => Schedule(() =>
|
||||||
{
|
{
|
||||||
activity.Value = null;
|
Child = new DependencyProvidingContainer
|
||||||
status.Value = null;
|
|
||||||
|
|
||||||
Remove(statisticsProvider, false);
|
|
||||||
Clear();
|
|
||||||
Add(statisticsProvider);
|
|
||||||
|
|
||||||
Add(new FillFlowContainer
|
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Origin = Anchor.Centre,
|
CachedDependencies =
|
||||||
AutoSizeAxes = Axes.Y,
|
[
|
||||||
RelativeSizeAxes = Axes.X,
|
(typeof(LocalUserStatisticsProvider), statisticsProvider = new TestUserStatisticsProvider()),
|
||||||
Spacing = new Vector2(10f),
|
(typeof(MetadataClient), metadataClient = new TestMetadataClient())
|
||||||
|
],
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new UserBrickPanel(new APIUser
|
statisticsProvider,
|
||||||
|
metadataClient,
|
||||||
|
new FillFlowContainer
|
||||||
{
|
{
|
||||||
Username = @"flyte",
|
Anchor = Anchor.Centre,
|
||||||
Id = 3103765,
|
Origin = Anchor.Centre,
|
||||||
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
|
AutoSizeAxes = Axes.Y,
|
||||||
}),
|
RelativeSizeAxes = Axes.X,
|
||||||
new UserBrickPanel(new APIUser
|
Spacing = new Vector2(10f),
|
||||||
{
|
Children = new Drawable[]
|
||||||
Username = @"peppy",
|
{
|
||||||
Id = 2,
|
new UserBrickPanel(new APIUser
|
||||||
Colour = "99EB47",
|
{
|
||||||
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
|
Username = @"flyte",
|
||||||
}),
|
Id = 3103765,
|
||||||
new UserGridPanel(new APIUser
|
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
|
||||||
{
|
}),
|
||||||
Username = @"flyte",
|
new UserBrickPanel(new APIUser
|
||||||
Id = 3103765,
|
{
|
||||||
CountryCode = CountryCode.JP,
|
Username = @"peppy",
|
||||||
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
|
Id = 2,
|
||||||
IsOnline = true
|
Colour = "99EB47",
|
||||||
}) { Width = 300 },
|
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
|
||||||
boundPanel1 = new UserGridPanel(new APIUser
|
}),
|
||||||
{
|
new UserGridPanel(new APIUser
|
||||||
Username = @"peppy",
|
{
|
||||||
Id = 2,
|
Username = @"flyte",
|
||||||
CountryCode = CountryCode.AU,
|
Id = 3103765,
|
||||||
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
|
CountryCode = CountryCode.JP,
|
||||||
IsSupporter = true,
|
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
|
||||||
SupportLevel = 3,
|
IsOnline = true
|
||||||
}) { Width = 300 },
|
}) { Width = 300 },
|
||||||
boundPanel2 = new TestUserListPanel(new APIUser
|
new UserGridPanel(new APIUser
|
||||||
{
|
{
|
||||||
Username = @"Evast",
|
Username = @"peppy",
|
||||||
Id = 8195163,
|
Id = 2,
|
||||||
CountryCode = CountryCode.BY,
|
CountryCode = CountryCode.AU,
|
||||||
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
|
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
|
||||||
IsOnline = false,
|
IsSupporter = true,
|
||||||
LastVisit = DateTimeOffset.Now
|
SupportLevel = 3,
|
||||||
}),
|
}) { Width = 300 },
|
||||||
new UserRankPanel(new APIUser
|
panel = new TestUserListPanel(new APIUser
|
||||||
{
|
{
|
||||||
Username = @"flyte",
|
Username = @"peppy",
|
||||||
Id = 3103765,
|
Id = 2,
|
||||||
CountryCode = CountryCode.JP,
|
CountryCode = CountryCode.AU,
|
||||||
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
|
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
|
||||||
Statistics = new UserStatistics { GlobalRank = 12345, CountryRank = 1234 }
|
LastVisit = DateTimeOffset.Now
|
||||||
}) { Width = 300 },
|
}),
|
||||||
new UserRankPanel(new APIUser
|
new UserRankPanel(new APIUser
|
||||||
{
|
{
|
||||||
Username = @"peppy",
|
Username = @"flyte",
|
||||||
Id = 2,
|
Id = 3103765,
|
||||||
Colour = "99EB47",
|
CountryCode = CountryCode.JP,
|
||||||
CountryCode = CountryCode.AU,
|
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
|
||||||
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
|
Statistics = new UserStatistics { GlobalRank = 12345, CountryRank = 1234 }
|
||||||
Statistics = new UserStatistics { GlobalRank = null, CountryRank = null }
|
}) { Width = 300 },
|
||||||
}) { Width = 300 }
|
new UserRankPanel(new APIUser
|
||||||
|
{
|
||||||
|
Username = @"peppy",
|
||||||
|
Id = 2,
|
||||||
|
Colour = "99EB47",
|
||||||
|
CountryCode = CountryCode.AU,
|
||||||
|
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
|
||||||
|
Statistics = new UserStatistics { GlobalRank = null, CountryRank = null }
|
||||||
|
}) { Width = 300 },
|
||||||
|
new UserGridPanel(API.LocalUser.Value)
|
||||||
|
{
|
||||||
|
Width = 300
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
boundPanel1.Status.BindTo(status);
|
metadataClient.BeginWatchingUserPresence();
|
||||||
boundPanel1.Activity.BindTo(activity);
|
|
||||||
|
|
||||||
boundPanel2.Status.BindTo(status);
|
|
||||||
boundPanel2.Activity.BindTo(activity);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestUserStatus()
|
public void TestUserStatus()
|
||||||
{
|
{
|
||||||
AddStep("online", () => status.Value = UserStatus.Online);
|
AddStep("online", () => setPresence(UserStatus.Online, null));
|
||||||
AddStep("do not disturb", () => status.Value = UserStatus.DoNotDisturb);
|
AddStep("do not disturb", () => setPresence(UserStatus.DoNotDisturb, null));
|
||||||
AddStep("offline", () => status.Value = UserStatus.Offline);
|
AddStep("offline", () => setPresence(UserStatus.Offline, null));
|
||||||
AddStep("null status", () => status.Value = null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestUserActivity()
|
public void TestUserActivity()
|
||||||
{
|
{
|
||||||
AddStep("set online status", () => status.Value = UserStatus.Online);
|
AddStep("idle", () => setPresence(UserStatus.Online, null));
|
||||||
|
AddStep("watching replay", () => setPresence(UserStatus.Online, new UserActivity.WatchingReplay(createScore(@"nats"))));
|
||||||
AddStep("idle", () => activity.Value = null);
|
AddStep("spectating user", () => setPresence(UserStatus.Online, new UserActivity.SpectatingUser(createScore(@"mrekk"))));
|
||||||
AddStep("watching replay", () => activity.Value = new UserActivity.WatchingReplay(createScore(@"nats")));
|
AddStep("solo (osu!)", () => setPresence(UserStatus.Online, soloGameStatusForRuleset(0)));
|
||||||
AddStep("spectating user", () => activity.Value = new UserActivity.SpectatingUser(createScore(@"mrekk")));
|
AddStep("solo (osu!taiko)", () => setPresence(UserStatus.Online, soloGameStatusForRuleset(1)));
|
||||||
AddStep("solo (osu!)", () => activity.Value = soloGameStatusForRuleset(0));
|
AddStep("solo (osu!catch)", () => setPresence(UserStatus.Online, soloGameStatusForRuleset(2)));
|
||||||
AddStep("solo (osu!taiko)", () => activity.Value = soloGameStatusForRuleset(1));
|
AddStep("solo (osu!mania)", () => setPresence(UserStatus.Online, soloGameStatusForRuleset(3)));
|
||||||
AddStep("solo (osu!catch)", () => activity.Value = soloGameStatusForRuleset(2));
|
AddStep("choosing", () => setPresence(UserStatus.Online, new UserActivity.ChoosingBeatmap()));
|
||||||
AddStep("solo (osu!mania)", () => activity.Value = soloGameStatusForRuleset(3));
|
AddStep("editing beatmap", () => setPresence(UserStatus.Online, new UserActivity.EditingBeatmap(new BeatmapInfo())));
|
||||||
AddStep("choosing", () => activity.Value = new UserActivity.ChoosingBeatmap());
|
AddStep("modding beatmap", () => setPresence(UserStatus.Online, new UserActivity.ModdingBeatmap(new BeatmapInfo())));
|
||||||
AddStep("editing beatmap", () => activity.Value = new UserActivity.EditingBeatmap(new BeatmapInfo()));
|
AddStep("testing beatmap", () => setPresence(UserStatus.Online, new UserActivity.TestingBeatmap(new BeatmapInfo())));
|
||||||
AddStep("modding beatmap", () => activity.Value = new UserActivity.ModdingBeatmap(new BeatmapInfo()));
|
|
||||||
AddStep("testing beatmap", () => activity.Value = new UserActivity.TestingBeatmap(new BeatmapInfo()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestUserActivityChange()
|
public void TestUserActivityChange()
|
||||||
{
|
{
|
||||||
AddAssert("visit message is visible", () => boundPanel2.LastVisitMessage.IsPresent);
|
AddAssert("visit message is visible", () => panel.LastVisitMessage.IsPresent);
|
||||||
AddStep("set online status", () => status.Value = UserStatus.Online);
|
AddStep("set online status", () => setPresence(UserStatus.Online, null));
|
||||||
AddAssert("visit message is not visible", () => !boundPanel2.LastVisitMessage.IsPresent);
|
AddAssert("visit message is not visible", () => !panel.LastVisitMessage.IsPresent);
|
||||||
AddStep("set choosing activity", () => activity.Value = new UserActivity.ChoosingBeatmap());
|
AddStep("set choosing activity", () => setPresence(UserStatus.Online, new UserActivity.ChoosingBeatmap()));
|
||||||
AddStep("set offline status", () => status.Value = UserStatus.Offline);
|
AddStep("set offline status", () => setPresence(UserStatus.Offline, null));
|
||||||
AddAssert("visit message is visible", () => boundPanel2.LastVisitMessage.IsPresent);
|
AddAssert("visit message is visible", () => panel.LastVisitMessage.IsPresent);
|
||||||
AddStep("set online status", () => status.Value = UserStatus.Online);
|
AddStep("set online status", () => setPresence(UserStatus.Online, null));
|
||||||
AddAssert("visit message is not visible", () => !boundPanel2.LastVisitMessage.IsPresent);
|
AddAssert("visit message is not visible", () => !panel.LastVisitMessage.IsPresent);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -185,6 +184,31 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddStep("set statistics to empty", () => statisticsProvider.UpdateStatistics(new UserStatistics(), Ruleset.Value));
|
AddStep("set statistics to empty", () => statisticsProvider.UpdateStatistics(new UserStatistics(), Ruleset.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLocalUserActivity()
|
||||||
|
{
|
||||||
|
AddStep("idle", () => setPresence(UserStatus.Online, null, API.LocalUser.Value.OnlineID));
|
||||||
|
AddStep("watching replay", () => setPresence(UserStatus.Online, new UserActivity.WatchingReplay(createScore(@"nats")), API.LocalUser.Value.OnlineID));
|
||||||
|
AddStep("spectating user", () => setPresence(UserStatus.Online, new UserActivity.SpectatingUser(createScore(@"mrekk")), API.LocalUser.Value.OnlineID));
|
||||||
|
AddStep("solo (osu!)", () => setPresence(UserStatus.Online, soloGameStatusForRuleset(0), API.LocalUser.Value.OnlineID));
|
||||||
|
AddStep("solo (osu!taiko)", () => setPresence(UserStatus.Online, soloGameStatusForRuleset(1), API.LocalUser.Value.OnlineID));
|
||||||
|
AddStep("solo (osu!catch)", () => setPresence(UserStatus.Online, soloGameStatusForRuleset(2), API.LocalUser.Value.OnlineID));
|
||||||
|
AddStep("solo (osu!mania)", () => setPresence(UserStatus.Online, soloGameStatusForRuleset(3), API.LocalUser.Value.OnlineID));
|
||||||
|
AddStep("choosing", () => setPresence(UserStatus.Online, new UserActivity.ChoosingBeatmap(), API.LocalUser.Value.OnlineID));
|
||||||
|
AddStep("editing beatmap", () => setPresence(UserStatus.Online, new UserActivity.EditingBeatmap(new BeatmapInfo()), API.LocalUser.Value.OnlineID));
|
||||||
|
AddStep("modding beatmap", () => setPresence(UserStatus.Online, new UserActivity.ModdingBeatmap(new BeatmapInfo()), API.LocalUser.Value.OnlineID));
|
||||||
|
AddStep("testing beatmap", () => setPresence(UserStatus.Online, new UserActivity.TestingBeatmap(new BeatmapInfo()), API.LocalUser.Value.OnlineID));
|
||||||
|
AddStep("set offline status", () => setPresence(UserStatus.Offline, null, API.LocalUser.Value.OnlineID));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPresence(UserStatus status, UserActivity? activity, int? userId = null)
|
||||||
|
{
|
||||||
|
if (status == UserStatus.Offline)
|
||||||
|
metadataClient.UserPresenceUpdated(userId ?? panel.User.OnlineID, null);
|
||||||
|
else
|
||||||
|
metadataClient.UserPresenceUpdated(userId ?? panel.User.OnlineID, new UserPresence { Status = status, Activity = activity });
|
||||||
|
}
|
||||||
|
|
||||||
private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.InSoloGame(new BeatmapInfo(), rulesetStore.GetRuleset(rulesetId)!);
|
private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.InSoloGame(new BeatmapInfo(), rulesetStore.GetRuleset(rulesetId)!);
|
||||||
|
|
||||||
private ScoreInfo createScore(string name) => new ScoreInfo(new TestBeatmap(Ruleset.Value).BeatmapInfo)
|
private ScoreInfo createScore(string name) => new ScoreInfo(new TestBeatmap(Ruleset.Value).BeatmapInfo)
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Profile;
|
using osu.Game.Overlays.Profile;
|
||||||
@ -20,24 +21,16 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
public partial class TestSceneUserProfileDailyChallenge : OsuManualInputManagerTestScene
|
public partial class TestSceneUserProfileDailyChallenge : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
[Cached]
|
[Cached]
|
||||||
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>(new UserProfileData(new APIUser(), new OsuRuleset().RulesetInfo));
|
private readonly Bindable<UserProfileData?> userProfileData = new Bindable<UserProfileData?>(new UserProfileData(new APIUser(), new OsuRuleset().RulesetInfo));
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
|
||||||
|
|
||||||
protected override void LoadComplete()
|
private DailyChallengeStatsDisplay display = null!;
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
DailyChallengeStatsDisplay display = null!;
|
|
||||||
|
|
||||||
AddSliderStep("daily", 0, 999, 2, v => update(s => s.DailyStreakCurrent = v));
|
|
||||||
AddSliderStep("daily best", 0, 999, 2, v => update(s => s.DailyStreakBest = v));
|
|
||||||
AddSliderStep("weekly", 0, 250, 1, v => update(s => s.WeeklyStreakCurrent = v));
|
|
||||||
AddSliderStep("weekly best", 0, 250, 1, v => update(s => s.WeeklyStreakBest = v));
|
|
||||||
AddSliderStep("top 10%", 0, 999, 0, v => update(s => s.Top10PercentPlacements = v));
|
|
||||||
AddSliderStep("top 50%", 0, 999, 0, v => update(s => s.Top50PercentPlacements = v));
|
|
||||||
AddSliderStep("playcount", 0, 1500, 1, v => update(s => s.PlayCount = v));
|
|
||||||
AddStep("create", () =>
|
AddStep("create", () =>
|
||||||
{
|
{
|
||||||
Clear();
|
Clear();
|
||||||
@ -51,16 +44,40 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Scale = new Vector2(1f),
|
Scale = new Vector2(1f),
|
||||||
User = { BindTarget = User },
|
User = { BindTarget = userProfileData },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddStep("set local user", () => update(s => s.UserID = API.LocalUser.Value.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
AddSliderStep("daily", 0, 999, 2, v => update(s => s.DailyStreakCurrent = v));
|
||||||
|
AddSliderStep("daily best", 0, 999, 2, v => update(s => s.DailyStreakBest = v));
|
||||||
|
AddSliderStep("weekly", 0, 250, 1, v => update(s => s.WeeklyStreakCurrent = v));
|
||||||
|
AddSliderStep("weekly best", 0, 250, 1, v => update(s => s.WeeklyStreakBest = v));
|
||||||
|
AddSliderStep("top 10%", 0, 999, 0, v => update(s => s.Top10PercentPlacements = v));
|
||||||
|
AddSliderStep("top 50%", 0, 999, 0, v => update(s => s.Top50PercentPlacements = v));
|
||||||
|
AddSliderStep("playcount", 0, 1500, 1, v => update(s => s.PlayCount = v));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStates()
|
||||||
|
{
|
||||||
|
AddStep("played today", () => update(s => s.LastUpdate = DateTimeOffset.UtcNow.Date));
|
||||||
|
AddStep("played yesterday", () => update(s => s.LastUpdate = DateTimeOffset.UtcNow.Date.AddDays(-1)));
|
||||||
|
AddStep("change to non-local user", () => update(s => s.UserID = API.LocalUser.Value.Id + 1000));
|
||||||
|
|
||||||
AddStep("hover", () => InputManager.MoveMouseTo(display));
|
AddStep("hover", () => InputManager.MoveMouseTo(display));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update(Action<APIUserDailyChallengeStatistics> change)
|
private void update(Action<APIUserDailyChallengeStatistics> change)
|
||||||
{
|
{
|
||||||
change.Invoke(User.Value!.User.DailyChallengeStatistics);
|
change.Invoke(userProfileData.Value!.User.DailyChallengeStatistics);
|
||||||
User.Value = new UserProfileData(User.Value.User, User.Value.Ruleset);
|
userProfileData.Value = new UserProfileData(userProfileData.Value.User, userProfileData.Value.Ruleset);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -67,19 +67,19 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestLink()
|
public void TestLink()
|
||||||
{
|
{
|
||||||
AddStep("set current path", () => markdownContainer.CurrentPath = $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/");
|
AddStep("set current path", () => markdownContainer.CurrentPath = $"{API.Endpoints.WebsiteUrl}/wiki/Article_styling_criteria/");
|
||||||
|
|
||||||
AddStep("set '/wiki/Main_page''", () => markdownContainer.Text = "[wiki main page](/wiki/Main_page)");
|
AddStep("set '/wiki/Main_page''", () => markdownContainer.Text = "[wiki main page](/wiki/Main_page)");
|
||||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Main_page");
|
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.Endpoints.WebsiteUrl}/wiki/Main_page");
|
||||||
|
|
||||||
AddStep("set '../FAQ''", () => markdownContainer.Text = "[FAQ](../FAQ)");
|
AddStep("set '../FAQ''", () => markdownContainer.Text = "[FAQ](../FAQ)");
|
||||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/FAQ");
|
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.Endpoints.WebsiteUrl}/wiki/FAQ");
|
||||||
|
|
||||||
AddStep("set './Writing''", () => markdownContainer.Text = "[wiki writing guidline](./Writing)");
|
AddStep("set './Writing''", () => markdownContainer.Text = "[wiki writing guidline](./Writing)");
|
||||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/Writing");
|
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.Endpoints.WebsiteUrl}/wiki/Article_styling_criteria/Writing");
|
||||||
|
|
||||||
AddStep("set 'Formatting''", () => markdownContainer.Text = "[wiki formatting guidline](Formatting)");
|
AddStep("set 'Formatting''", () => markdownContainer.Text = "[wiki formatting guidline](Formatting)");
|
||||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/Formatting");
|
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.Endpoints.WebsiteUrl}/wiki/Article_styling_criteria/Formatting");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -62,12 +62,6 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
if (beatmapInfo != null)
|
if (beatmapInfo != null)
|
||||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddToggleStep("toggle legacy classic skin", v =>
|
|
||||||
{
|
|
||||||
if (skins != null)
|
|
||||||
skins.CurrentSkinInfo.Value = v ? skins.DefaultClassicSkin.SkinInfo : skins.CurrentSkinInfo.Default;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
@ -84,6 +78,16 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLegacySkin()
|
||||||
|
{
|
||||||
|
AddToggleStep("toggle legacy classic skin", v =>
|
||||||
|
{
|
||||||
|
if (skins != null)
|
||||||
|
skins.CurrentSkinInfo.Value = v ? skins.DefaultClassicSkin.SkinInfo : skins.CurrentSkinInfo.Default;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private int onlineScoreID = 1;
|
private int onlineScoreID = 1;
|
||||||
|
|
||||||
[TestCase(1, ScoreRank.X, 0)]
|
[TestCase(1, ScoreRank.X, 0)]
|
||||||
|
292
osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs
Normal file
292
osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
using osu.Game.Screens.SelectV2;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osuTK.Input;
|
||||||
|
using BeatmapCarousel = osu.Game.Screens.SelectV2.BeatmapCarousel;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
|
{
|
||||||
|
public abstract partial class BeatmapCarouselV2TestScene : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
protected readonly BindableList<BeatmapSetInfo> BeatmapSets = new BindableList<BeatmapSetInfo>();
|
||||||
|
|
||||||
|
protected BeatmapCarousel Carousel = null!;
|
||||||
|
|
||||||
|
protected OsuScrollContainer<Drawable> Scroll => Carousel.ChildrenOfType<OsuScrollContainer<Drawable>>().Single();
|
||||||
|
|
||||||
|
[Cached(typeof(BeatmapStore))]
|
||||||
|
private BeatmapStore store;
|
||||||
|
|
||||||
|
private OsuTextFlowContainer stats = null!;
|
||||||
|
|
||||||
|
private int beatmapCount;
|
||||||
|
|
||||||
|
protected BeatmapCarouselV2TestScene()
|
||||||
|
{
|
||||||
|
store = new TestBeatmapStore
|
||||||
|
{
|
||||||
|
BeatmapSets = { BindTarget = BeatmapSets }
|
||||||
|
};
|
||||||
|
|
||||||
|
BeatmapSets.BindCollectionChanged((_, _) => beatmapCount = BeatmapSets.Sum(s => s.Beatmaps.Count));
|
||||||
|
|
||||||
|
Scheduler.AddDelayed(updateStats, 100, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void CreateCarousel()
|
||||||
|
{
|
||||||
|
AddStep("create components", () =>
|
||||||
|
{
|
||||||
|
Box topBox;
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.Relative, 1),
|
||||||
|
},
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.Absolute, 200),
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.Absolute, 200),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
topBox = new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Colour = Color4.Cyan,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.4f,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
Carousel = new BeatmapCarousel
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Width = 500,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Colour = Color4.Cyan,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.4f,
|
||||||
|
},
|
||||||
|
topBox.CreateProxy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stats = new OsuTextFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(10),
|
||||||
|
TextAnchor = Anchor.CentreLeft,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void SortBy(FilterCriteria criteria) => AddStep($"sort:{criteria.Sort} group:{criteria.Group}", () => Carousel.Filter(criteria));
|
||||||
|
|
||||||
|
protected void WaitForDrawablePanels() => AddUntilStep("drawable panels loaded", () => Carousel.ChildrenOfType<ICarouselPanel>().Count(), () => Is.GreaterThan(0));
|
||||||
|
protected void WaitForSorting() => AddUntilStep("sorting finished", () => Carousel.IsFiltering, () => Is.False);
|
||||||
|
protected void WaitForScrolling() => AddUntilStep("scroll finished", () => Scroll.Current, () => Is.EqualTo(Scroll.Target));
|
||||||
|
|
||||||
|
protected void SelectNextPanel() => AddStep("select next panel", () => InputManager.Key(Key.Down));
|
||||||
|
protected void SelectPrevPanel() => AddStep("select prev panel", () => InputManager.Key(Key.Up));
|
||||||
|
protected void SelectNextGroup() => AddStep("select next group", () => InputManager.Key(Key.Right));
|
||||||
|
protected void SelectPrevGroup() => AddStep("select prev group", () => InputManager.Key(Key.Left));
|
||||||
|
|
||||||
|
protected void Select() => AddStep("select", () => InputManager.Key(Key.Enter));
|
||||||
|
|
||||||
|
protected void CheckNoSelection() => AddAssert("has no selection", () => Carousel.CurrentSelection, () => Is.Null);
|
||||||
|
protected void CheckHasSelection() => AddAssert("has selection", () => Carousel.CurrentSelection, () => Is.Not.Null);
|
||||||
|
|
||||||
|
protected ICarouselPanel? GetSelectedPanel() => Carousel.ChildrenOfType<ICarouselPanel>().SingleOrDefault(p => p.Selected.Value);
|
||||||
|
protected ICarouselPanel? GetKeyboardSelectedPanel() => Carousel.ChildrenOfType<ICarouselPanel>().SingleOrDefault(p => p.KeyboardSelected.Value);
|
||||||
|
|
||||||
|
protected void WaitForGroupSelection(int group, int panel)
|
||||||
|
{
|
||||||
|
AddUntilStep($"selected is group{group} panel{panel}", () =>
|
||||||
|
{
|
||||||
|
var groupingFilter = Carousel.Filters.OfType<BeatmapCarouselFilterGrouping>().Single();
|
||||||
|
|
||||||
|
GroupDefinition g = groupingFilter.GroupItems.Keys.ElementAt(group);
|
||||||
|
// offset by one because the group itself is included in the items list.
|
||||||
|
CarouselItem item = groupingFilter.GroupItems[g].ElementAt(panel + 1);
|
||||||
|
|
||||||
|
return ReferenceEquals(Carousel.CurrentSelection, item.Model);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void WaitForSelection(int set, int? diff = null)
|
||||||
|
{
|
||||||
|
AddUntilStep($"selected is set{set}{(diff.HasValue ? $" diff{diff.Value}" : "")}", () =>
|
||||||
|
{
|
||||||
|
if (diff != null)
|
||||||
|
return ReferenceEquals(Carousel.CurrentSelection, BeatmapSets[set].Beatmaps[diff.Value]);
|
||||||
|
|
||||||
|
return BeatmapSets[set].Beatmaps.Contains(Carousel.CurrentSelection);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IEnumerable<T> GetVisiblePanels<T>()
|
||||||
|
where T : Drawable
|
||||||
|
{
|
||||||
|
return Carousel.ChildrenOfType<UserTrackingScrollContainer>().Single()
|
||||||
|
.ChildrenOfType<T>()
|
||||||
|
.Where(p => ((ICarouselPanel)p).Item?.IsVisible == true)
|
||||||
|
.OrderBy(p => p.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ClickVisiblePanel<T>(int index)
|
||||||
|
where T : Drawable
|
||||||
|
{
|
||||||
|
AddStep($"click panel at index {index}", () =>
|
||||||
|
{
|
||||||
|
Carousel.ChildrenOfType<UserTrackingScrollContainer>().Single()
|
||||||
|
.ChildrenOfType<T>()
|
||||||
|
.Where(p => ((ICarouselPanel)p).Item?.IsVisible == true)
|
||||||
|
.OrderBy(p => p.Y)
|
||||||
|
.ElementAt(index)
|
||||||
|
.TriggerClick();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ClickVisiblePanelWithOffset<T>(int index, Vector2 positionOffsetFromCentre)
|
||||||
|
where T : Drawable
|
||||||
|
{
|
||||||
|
AddStep($"move mouse to panel {index} with offset {positionOffsetFromCentre}", () =>
|
||||||
|
{
|
||||||
|
var panel = Carousel.ChildrenOfType<UserTrackingScrollContainer>().Single()
|
||||||
|
.ChildrenOfType<T>()
|
||||||
|
.Where(p => ((ICarouselPanel)p).Item?.IsVisible == true)
|
||||||
|
.OrderBy(p => p.Y)
|
||||||
|
.ElementAt(index);
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre + panel.ToScreenSpace(positionOffsetFromCentre) - panel.ToScreenSpace(Vector2.Zero));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add requested beatmap sets count to list.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="count">The count of beatmap sets to add.</param>
|
||||||
|
/// <param name="fixedDifficultiesPerSet">If not null, the number of difficulties per set. If null, randomised difficulty count will be used.</param>
|
||||||
|
/// <param name="randomMetadata">Whether to randomise the metadata to make groupings more uniform.</param>
|
||||||
|
protected void AddBeatmaps(int count, int? fixedDifficultiesPerSet = null, bool randomMetadata = false) => AddStep($"add {count} beatmaps{(randomMetadata ? " with random data" : "")}", () =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
BeatmapSets.Add(CreateTestBeatmapSetInfo(fixedDifficultiesPerSet, randomMetadata));
|
||||||
|
});
|
||||||
|
|
||||||
|
protected static BeatmapSetInfo CreateTestBeatmapSetInfo(int? fixedDifficultiesPerSet, bool randomMetadata)
|
||||||
|
{
|
||||||
|
var beatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(fixedDifficultiesPerSet ?? RNG.Next(1, 4));
|
||||||
|
|
||||||
|
if (randomMetadata)
|
||||||
|
{
|
||||||
|
char randomCharacter = getRandomCharacter();
|
||||||
|
|
||||||
|
var metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
// Create random metadata, then we can check if sorting works based on these
|
||||||
|
Artist = $"{randomCharacter}ome Artist " + RNG.Next(0, 9),
|
||||||
|
Title = $"{randomCharacter}ome Song (set id {beatmapSetInfo.OnlineID:000}) {Guid.NewGuid()}",
|
||||||
|
Author = { Username = $"{randomCharacter}ome Guy " + RNG.Next(0, 9) },
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var beatmap in beatmapSetInfo.Beatmaps)
|
||||||
|
beatmap.Metadata = metadata.DeepClone();
|
||||||
|
}
|
||||||
|
|
||||||
|
return beatmapSetInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long randomCharPointer;
|
||||||
|
|
||||||
|
private static char getRandomCharacter()
|
||||||
|
{
|
||||||
|
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz*";
|
||||||
|
return chars[(int)((randomCharPointer++ / 2) % chars.Length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RemoveAllBeatmaps() => AddStep("clear all beatmaps", () => BeatmapSets.Clear());
|
||||||
|
|
||||||
|
protected void RemoveFirstBeatmap() =>
|
||||||
|
AddStep("remove first beatmap", () =>
|
||||||
|
{
|
||||||
|
if (BeatmapSets.Count == 0) return;
|
||||||
|
|
||||||
|
BeatmapSets.Remove(BeatmapSets.First());
|
||||||
|
});
|
||||||
|
|
||||||
|
private void updateStats()
|
||||||
|
{
|
||||||
|
if (Carousel.IsNull())
|
||||||
|
return;
|
||||||
|
|
||||||
|
stats.Clear();
|
||||||
|
createHeader("beatmap store");
|
||||||
|
stats.AddParagraph($"""
|
||||||
|
sets: {BeatmapSets.Count}
|
||||||
|
beatmaps: {beatmapCount}
|
||||||
|
""");
|
||||||
|
createHeader("carousel");
|
||||||
|
stats.AddParagraph($"""
|
||||||
|
sorting: {Carousel.IsFiltering}
|
||||||
|
tracked: {Carousel.ItemsTracked}
|
||||||
|
displayable: {Carousel.DisplayableItems}
|
||||||
|
displayed: {Carousel.VisibleItems}
|
||||||
|
selected: {Carousel.CurrentSelection}
|
||||||
|
""");
|
||||||
|
|
||||||
|
void createHeader(string text)
|
||||||
|
{
|
||||||
|
stats.AddParagraph(string.Empty);
|
||||||
|
stats.AddParagraph(text, cp =>
|
||||||
|
{
|
||||||
|
cp.Font = cp.Font.With(size: 18, weight: FontWeight.Bold);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,235 +2,70 @@
|
|||||||
// 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.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Primitives;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osu.Game.Screens.Select.Filter;
|
using osu.Game.Screens.Select.Filter;
|
||||||
using osu.Game.Screens.SelectV2;
|
|
||||||
using osu.Game.Tests.Beatmaps;
|
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using osuTK.Graphics;
|
|
||||||
using BeatmapCarousel = osu.Game.Screens.SelectV2.BeatmapCarousel;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.SongSelect
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Covers common steps which can be used for manual testing.
|
||||||
|
/// </summary>
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public partial class TestSceneBeatmapCarouselV2 : OsuManualInputManagerTestScene
|
public partial class TestSceneBeatmapCarouselV2 : BeatmapCarouselV2TestScene
|
||||||
{
|
{
|
||||||
private readonly BindableList<BeatmapSetInfo> beatmapSets = new BindableList<BeatmapSetInfo>();
|
|
||||||
|
|
||||||
[Cached(typeof(BeatmapStore))]
|
|
||||||
private BeatmapStore store;
|
|
||||||
|
|
||||||
private OsuTextFlowContainer stats = null!;
|
|
||||||
private BeatmapCarousel carousel = null!;
|
|
||||||
|
|
||||||
private OsuScrollContainer<Drawable> scroll => carousel.ChildrenOfType<OsuScrollContainer<Drawable>>().Single();
|
|
||||||
|
|
||||||
private int beatmapCount;
|
|
||||||
|
|
||||||
public TestSceneBeatmapCarouselV2()
|
|
||||||
{
|
|
||||||
store = new TestBeatmapStore
|
|
||||||
{
|
|
||||||
BeatmapSets = { BindTarget = beatmapSets }
|
|
||||||
};
|
|
||||||
|
|
||||||
beatmapSets.BindCollectionChanged((_, _) =>
|
|
||||||
{
|
|
||||||
beatmapCount = beatmapSets.Sum(s => s.Beatmaps.Count);
|
|
||||||
});
|
|
||||||
|
|
||||||
Scheduler.AddDelayed(updateStats, 100, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
[SetUpSteps]
|
|
||||||
public void SetUpSteps()
|
|
||||||
{
|
|
||||||
AddStep("create components", () =>
|
|
||||||
{
|
|
||||||
beatmapSets.Clear();
|
|
||||||
|
|
||||||
Box topBox;
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
ColumnDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(GridSizeMode.Relative, 1),
|
|
||||||
},
|
|
||||||
RowDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(GridSizeMode.Absolute, 200),
|
|
||||||
new Dimension(),
|
|
||||||
new Dimension(GridSizeMode.Absolute, 200),
|
|
||||||
},
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
topBox = new Box
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Colour = Color4.Cyan,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Alpha = 0.4f,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
carousel = new BeatmapCarousel
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Width = 500,
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Colour = Color4.Cyan,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Alpha = 0.4f,
|
|
||||||
},
|
|
||||||
topBox.CreateProxy(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stats = new OsuTextFlowContainer(cp => cp.Font = FrameworkFont.Regular.With())
|
|
||||||
{
|
|
||||||
Padding = new MarginPadding(10),
|
|
||||||
TextAnchor = Anchor.CentreLeft,
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("sort by title", () =>
|
|
||||||
{
|
|
||||||
carousel.Filter(new FilterCriteria { Sort = SortMode.Title });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBasic()
|
[Explicit]
|
||||||
|
public void TestBasics()
|
||||||
{
|
{
|
||||||
AddStep("add 10 beatmaps", () =>
|
CreateCarousel();
|
||||||
{
|
RemoveAllBeatmaps();
|
||||||
for (int i = 0; i < 10; i++)
|
|
||||||
beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo(RNG.Next(1, 4)));
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("add 1 beatmap", () => beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo(RNG.Next(1, 4))));
|
AddBeatmaps(10, randomMetadata: true);
|
||||||
|
AddBeatmaps(10);
|
||||||
AddStep("remove all beatmaps", () => beatmapSets.Clear());
|
AddBeatmaps(1);
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestSorting()
|
|
||||||
{
|
|
||||||
AddStep("add 10 beatmaps", () =>
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 10; i++)
|
|
||||||
beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo(RNG.Next(1, 4)));
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("sort by difficulty", () =>
|
|
||||||
{
|
|
||||||
carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty });
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("sort by artist", () =>
|
|
||||||
{
|
|
||||||
carousel.Filter(new FilterCriteria { Sort = SortMode.Artist });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestScrollPositionMaintainedOnAddSecondSelected()
|
|
||||||
{
|
|
||||||
Quad positionBefore = default;
|
|
||||||
|
|
||||||
AddStep("add 10 beatmaps", () =>
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 10; i++)
|
|
||||||
beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo(RNG.Next(1, 4)));
|
|
||||||
});
|
|
||||||
|
|
||||||
AddUntilStep("visual item added", () => carousel.ChildrenOfType<BeatmapCarouselPanel>().Count(), () => Is.GreaterThan(0));
|
|
||||||
|
|
||||||
AddStep("select middle beatmap", () => carousel.CurrentSelection = beatmapSets.ElementAt(beatmapSets.Count - 2));
|
|
||||||
AddStep("scroll to selected item", () => scroll.ScrollTo(scroll.ChildrenOfType<BeatmapCarouselPanel>().Single(p => p.Item!.Selected.Value)));
|
|
||||||
|
|
||||||
AddUntilStep("wait for scroll finished", () => scroll.Current, () => Is.EqualTo(scroll.Target));
|
|
||||||
|
|
||||||
AddStep("save selected screen position", () => positionBefore = carousel.ChildrenOfType<BeatmapCarouselPanel>().FirstOrDefault(p => p.Item!.Selected.Value)!.ScreenSpaceDrawQuad);
|
|
||||||
|
|
||||||
AddStep("remove first beatmap", () => beatmapSets.Remove(beatmapSets.Last()));
|
|
||||||
AddUntilStep("sorting finished", () => carousel.IsFiltering, () => Is.False);
|
|
||||||
AddAssert("select screen position unchanged", () => carousel.ChildrenOfType<BeatmapCarouselPanel>().Single(p => p.Item!.Selected.Value).ScreenSpaceDrawQuad,
|
|
||||||
() => Is.EqualTo(positionBefore));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestScrollPositionMaintainedOnAddLastSelected()
|
|
||||||
{
|
|
||||||
Quad positionBefore = default;
|
|
||||||
|
|
||||||
AddStep("add 10 beatmaps", () =>
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 10; i++)
|
|
||||||
beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo(RNG.Next(1, 4)));
|
|
||||||
});
|
|
||||||
|
|
||||||
AddUntilStep("visual item added", () => carousel.ChildrenOfType<BeatmapCarouselPanel>().Count(), () => Is.GreaterThan(0));
|
|
||||||
|
|
||||||
AddStep("scroll to last item", () => scroll.ScrollToEnd(false));
|
|
||||||
|
|
||||||
AddStep("select last beatmap", () => carousel.CurrentSelection = beatmapSets.First());
|
|
||||||
|
|
||||||
AddUntilStep("wait for scroll finished", () => scroll.Current, () => Is.EqualTo(scroll.Target));
|
|
||||||
|
|
||||||
AddStep("save selected screen position", () => positionBefore = carousel.ChildrenOfType<BeatmapCarouselPanel>().FirstOrDefault(p => p.Item!.Selected.Value)!.ScreenSpaceDrawQuad);
|
|
||||||
|
|
||||||
AddStep("remove first beatmap", () => beatmapSets.Remove(beatmapSets.Last()));
|
|
||||||
AddUntilStep("sorting finished", () => carousel.IsFiltering, () => Is.False);
|
|
||||||
AddAssert("select screen position unchanged", () => carousel.ChildrenOfType<BeatmapCarouselPanel>().Single(p => p.Item!.Selected.Value).ScreenSpaceDrawQuad,
|
|
||||||
() => Is.EqualTo(positionBefore));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestAddRemoveOneByOne()
|
|
||||||
{
|
|
||||||
AddRepeatStep("add beatmaps", () => beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo(RNG.Next(1, 4))), 20);
|
|
||||||
|
|
||||||
AddRepeatStep("remove beatmaps", () => beatmapSets.RemoveAt(RNG.Next(0, beatmapSets.Count)), 20);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[Explicit]
|
[Explicit]
|
||||||
public void TestInsane()
|
public void TestSorting()
|
||||||
|
{
|
||||||
|
SortBy(new FilterCriteria { Sort = SortMode.Artist });
|
||||||
|
SortBy(new FilterCriteria { Group = GroupMode.Difficulty, Sort = SortMode.Difficulty });
|
||||||
|
SortBy(new FilterCriteria { Group = GroupMode.Artist, Sort = SortMode.Artist });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Explicit]
|
||||||
|
public void TestRemovals()
|
||||||
|
{
|
||||||
|
RemoveFirstBeatmap();
|
||||||
|
RemoveAllBeatmaps();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Explicit]
|
||||||
|
public void TestAddRemoveRepeatedOps()
|
||||||
|
{
|
||||||
|
AddRepeatStep("add beatmaps", () => BeatmapSets.Add(TestResources.CreateTestBeatmapSetInfo(RNG.Next(1, 4))), 20);
|
||||||
|
AddRepeatStep("remove beatmaps", () => BeatmapSets.RemoveAt(RNG.Next(0, BeatmapSets.Count)), 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Explicit]
|
||||||
|
public void TestMasking()
|
||||||
|
{
|
||||||
|
AddStep("disable masking", () => Scroll.Masking = false);
|
||||||
|
AddStep("enable masking", () => Scroll.Masking = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Explicit]
|
||||||
|
public void TestPerformanceWithManyBeatmaps()
|
||||||
{
|
{
|
||||||
const int count = 200000;
|
const int count = 200000;
|
||||||
|
|
||||||
@ -242,7 +77,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
for (int j = 0; j < count; j++)
|
for (int j = 0; j < count; j++)
|
||||||
generated.Add(TestResources.CreateTestBeatmapSetInfo(RNG.Next(1, 4)));
|
generated.Add(CreateTestBeatmapSetInfo(3, true));
|
||||||
}).ConfigureAwait(true);
|
}).ConfigureAwait(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -250,24 +85,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddUntilStep("this takes a while", () => generated.Count, () => Is.GreaterThan(count / 3 * 2));
|
AddUntilStep("this takes a while", () => generated.Count, () => Is.GreaterThan(count / 3 * 2));
|
||||||
AddUntilStep("maybe they are done now", () => generated.Count, () => Is.EqualTo(count));
|
AddUntilStep("maybe they are done now", () => generated.Count, () => Is.EqualTo(count));
|
||||||
|
|
||||||
AddStep("add all beatmaps", () => beatmapSets.AddRange(generated));
|
AddStep("add all beatmaps", () => BeatmapSets.AddRange(generated));
|
||||||
}
|
|
||||||
|
|
||||||
private void updateStats()
|
|
||||||
{
|
|
||||||
if (carousel.IsNull())
|
|
||||||
return;
|
|
||||||
|
|
||||||
stats.Text = $"""
|
|
||||||
store
|
|
||||||
sets: {beatmapSets.Count}
|
|
||||||
beatmaps: {beatmapCount}
|
|
||||||
carousel:
|
|
||||||
sorting: {carousel.IsFiltering}
|
|
||||||
tracked: {carousel.ItemsTracked}
|
|
||||||
displayable: {carousel.DisplayableItems}
|
|
||||||
displayed: {carousel.VisibleItems}
|
|
||||||
""";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,177 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
using osu.Game.Screens.Select.Filter;
|
||||||
|
using osu.Game.Screens.SelectV2;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public partial class TestSceneBeatmapCarouselV2ArtistGrouping : BeatmapCarouselV2TestScene
|
||||||
|
{
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
RemoveAllBeatmaps();
|
||||||
|
CreateCarousel();
|
||||||
|
SortBy(new FilterCriteria { Group = GroupMode.Artist, Sort = SortMode.Artist });
|
||||||
|
|
||||||
|
AddBeatmaps(10, 3, true);
|
||||||
|
WaitForDrawablePanels();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOpenCloseGroupWithNoSelectionMouse()
|
||||||
|
{
|
||||||
|
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||||
|
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
ClickVisiblePanel<GroupPanel>(0);
|
||||||
|
|
||||||
|
AddUntilStep("some sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||||
|
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
ClickVisiblePanel<GroupPanel>(0);
|
||||||
|
|
||||||
|
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||||
|
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||||
|
CheckNoSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOpenCloseGroupWithNoSelectionKeyboard()
|
||||||
|
{
|
||||||
|
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||||
|
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
SelectNextPanel();
|
||||||
|
Select();
|
||||||
|
|
||||||
|
AddUntilStep("some sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||||
|
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||||
|
AddAssert("keyboard selected is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
Select();
|
||||||
|
|
||||||
|
AddUntilStep("no sets visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||||
|
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||||
|
AddAssert("keyboard selected is collapsed", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
|
||||||
|
CheckNoSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCarouselRemembersSelection()
|
||||||
|
{
|
||||||
|
SelectNextGroup();
|
||||||
|
|
||||||
|
object? selection = null;
|
||||||
|
|
||||||
|
AddStep("store drawable selection", () => selection = GetSelectedPanel()?.Item?.Model);
|
||||||
|
|
||||||
|
CheckHasSelection();
|
||||||
|
AddAssert("drawable selection non-null", () => selection, () => Is.Not.Null);
|
||||||
|
AddAssert("drawable selection matches carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
||||||
|
|
||||||
|
RemoveAllBeatmaps();
|
||||||
|
AddUntilStep("no drawable selection", GetSelectedPanel, () => Is.Null);
|
||||||
|
|
||||||
|
AddBeatmaps(10);
|
||||||
|
WaitForDrawablePanels();
|
||||||
|
|
||||||
|
CheckHasSelection();
|
||||||
|
AddAssert("no drawable selection", GetSelectedPanel, () => Is.Null);
|
||||||
|
|
||||||
|
AddStep("add previous selection", () => BeatmapSets.Add(((BeatmapInfo)selection!).BeatmapSet!));
|
||||||
|
|
||||||
|
AddAssert("selection matches original carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
||||||
|
AddUntilStep("drawable selection restored", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(selection));
|
||||||
|
AddAssert("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||||
|
|
||||||
|
ClickVisiblePanel<GroupPanel>(0);
|
||||||
|
AddUntilStep("carousel item not visible", GetSelectedPanel, () => Is.Null);
|
||||||
|
|
||||||
|
ClickVisiblePanel<GroupPanel>(0);
|
||||||
|
AddUntilStep("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGroupSelectionOnHeader()
|
||||||
|
{
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForGroupSelection(0, 1);
|
||||||
|
|
||||||
|
SelectPrevPanel();
|
||||||
|
SelectPrevPanel();
|
||||||
|
|
||||||
|
AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||||
|
|
||||||
|
SelectPrevGroup();
|
||||||
|
|
||||||
|
WaitForGroupSelection(0, 1);
|
||||||
|
AddAssert("keyboard selected panel is contracted", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
|
||||||
|
|
||||||
|
SelectPrevGroup();
|
||||||
|
|
||||||
|
WaitForGroupSelection(0, 1);
|
||||||
|
AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestKeyboardSelection()
|
||||||
|
{
|
||||||
|
SelectNextPanel();
|
||||||
|
SelectNextPanel();
|
||||||
|
SelectNextPanel();
|
||||||
|
SelectNextPanel();
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
// open first group
|
||||||
|
Select();
|
||||||
|
CheckNoSelection();
|
||||||
|
AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType<BeatmapSetPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||||
|
|
||||||
|
SelectNextPanel();
|
||||||
|
Select();
|
||||||
|
WaitForGroupSelection(3, 1);
|
||||||
|
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForGroupSelection(3, 5);
|
||||||
|
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForGroupSelection(4, 1);
|
||||||
|
|
||||||
|
SelectPrevGroup();
|
||||||
|
WaitForGroupSelection(3, 5);
|
||||||
|
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForGroupSelection(4, 1);
|
||||||
|
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForGroupSelection(4, 5);
|
||||||
|
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForGroupSelection(0, 1);
|
||||||
|
|
||||||
|
SelectNextPanel();
|
||||||
|
SelectNextPanel();
|
||||||
|
SelectNextPanel();
|
||||||
|
SelectNextPanel();
|
||||||
|
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForGroupSelection(0, 1);
|
||||||
|
|
||||||
|
SelectNextPanel();
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForGroupSelection(1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,194 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
using osu.Game.Screens.Select.Filter;
|
||||||
|
using osu.Game.Screens.SelectV2;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public partial class TestSceneBeatmapCarouselV2DifficultyGrouping : BeatmapCarouselV2TestScene
|
||||||
|
{
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
RemoveAllBeatmaps();
|
||||||
|
CreateCarousel();
|
||||||
|
SortBy(new FilterCriteria { Group = GroupMode.Difficulty, Sort = SortMode.Difficulty });
|
||||||
|
|
||||||
|
AddBeatmaps(10, 3);
|
||||||
|
WaitForDrawablePanels();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOpenCloseGroupWithNoSelectionMouse()
|
||||||
|
{
|
||||||
|
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
ClickVisiblePanel<GroupPanel>(0);
|
||||||
|
AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
ClickVisiblePanel<GroupPanel>(0);
|
||||||
|
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||||
|
CheckNoSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOpenCloseGroupWithNoSelectionKeyboard()
|
||||||
|
{
|
||||||
|
AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
SelectNextPanel();
|
||||||
|
Select();
|
||||||
|
AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||||
|
AddAssert("keyboard selected is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
Select();
|
||||||
|
AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.Zero);
|
||||||
|
AddAssert("keyboard selected is collapsed", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
|
||||||
|
CheckNoSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCarouselRemembersSelection()
|
||||||
|
{
|
||||||
|
SelectNextGroup();
|
||||||
|
|
||||||
|
object? selection = null;
|
||||||
|
|
||||||
|
AddStep("store drawable selection", () => selection = GetSelectedPanel()?.Item?.Model);
|
||||||
|
|
||||||
|
CheckHasSelection();
|
||||||
|
AddAssert("drawable selection non-null", () => selection, () => Is.Not.Null);
|
||||||
|
AddAssert("drawable selection matches carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
||||||
|
|
||||||
|
RemoveAllBeatmaps();
|
||||||
|
AddUntilStep("no drawable selection", GetSelectedPanel, () => Is.Null);
|
||||||
|
|
||||||
|
AddBeatmaps(3);
|
||||||
|
WaitForDrawablePanels();
|
||||||
|
|
||||||
|
CheckHasSelection();
|
||||||
|
AddAssert("no drawable selection", GetSelectedPanel, () => Is.Null);
|
||||||
|
|
||||||
|
AddStep("add previous selection", () => BeatmapSets.Add(((BeatmapInfo)selection!).BeatmapSet!));
|
||||||
|
|
||||||
|
AddAssert("selection matches original carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
||||||
|
AddUntilStep("drawable selection restored", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(selection));
|
||||||
|
AddAssert("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||||
|
|
||||||
|
ClickVisiblePanel<GroupPanel>(0);
|
||||||
|
AddUntilStep("carousel item not visible", GetSelectedPanel, () => Is.Null);
|
||||||
|
|
||||||
|
ClickVisiblePanel<GroupPanel>(0);
|
||||||
|
AddUntilStep("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGroupSelectionOnHeaderKeyboard()
|
||||||
|
{
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForGroupSelection(0, 0);
|
||||||
|
|
||||||
|
SelectPrevPanel();
|
||||||
|
AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||||
|
|
||||||
|
SelectPrevGroup();
|
||||||
|
|
||||||
|
WaitForGroupSelection(0, 0);
|
||||||
|
AddAssert("keyboard selected panel is contracted", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
|
||||||
|
|
||||||
|
SelectPrevGroup();
|
||||||
|
|
||||||
|
WaitForGroupSelection(0, 0);
|
||||||
|
AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGroupSelectionOnHeaderMouse()
|
||||||
|
{
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForGroupSelection(0, 0);
|
||||||
|
|
||||||
|
AddAssert("keyboard selected panel is beatmap", GetKeyboardSelectedPanel, Is.TypeOf<BeatmapPanel>);
|
||||||
|
AddAssert("selected panel is beatmap", GetSelectedPanel, Is.TypeOf<BeatmapPanel>);
|
||||||
|
|
||||||
|
ClickVisiblePanel<GroupPanel>(0);
|
||||||
|
AddAssert("keyboard selected panel is group", GetKeyboardSelectedPanel, Is.TypeOf<GroupPanel>);
|
||||||
|
AddAssert("keyboard selected panel is contracted", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False);
|
||||||
|
|
||||||
|
ClickVisiblePanel<GroupPanel>(0);
|
||||||
|
AddAssert("keyboard selected panel is group", GetKeyboardSelectedPanel, Is.TypeOf<GroupPanel>);
|
||||||
|
AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True);
|
||||||
|
|
||||||
|
AddAssert("selected panel is still beatmap", GetSelectedPanel, Is.TypeOf<BeatmapPanel>);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestKeyboardSelection()
|
||||||
|
{
|
||||||
|
SelectNextPanel();
|
||||||
|
SelectNextPanel();
|
||||||
|
SelectNextPanel();
|
||||||
|
SelectNextPanel();
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
// open first group
|
||||||
|
Select();
|
||||||
|
CheckNoSelection();
|
||||||
|
AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType<BeatmapPanel>().Count(p => p.Alpha > 0), () => Is.GreaterThan(0));
|
||||||
|
|
||||||
|
SelectNextPanel();
|
||||||
|
Select();
|
||||||
|
WaitForGroupSelection(0, 0);
|
||||||
|
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForGroupSelection(0, 1);
|
||||||
|
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForGroupSelection(0, 2);
|
||||||
|
|
||||||
|
SelectPrevGroup();
|
||||||
|
WaitForGroupSelection(0, 1);
|
||||||
|
|
||||||
|
SelectPrevGroup();
|
||||||
|
WaitForGroupSelection(0, 0);
|
||||||
|
|
||||||
|
SelectPrevGroup();
|
||||||
|
WaitForGroupSelection(2, 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInputHandlingWithinGaps()
|
||||||
|
{
|
||||||
|
AddAssert("no beatmaps visible", () => !GetVisiblePanels<BeatmapPanel>().Any());
|
||||||
|
|
||||||
|
// Clicks just above the first group panel should not actuate any action.
|
||||||
|
ClickVisiblePanelWithOffset<GroupPanel>(0, new Vector2(0, -(GroupPanel.HEIGHT / 2 + 1)));
|
||||||
|
|
||||||
|
AddAssert("no beatmaps visible", () => !GetVisiblePanels<BeatmapPanel>().Any());
|
||||||
|
|
||||||
|
ClickVisiblePanelWithOffset<GroupPanel>(0, new Vector2(0, -(GroupPanel.HEIGHT / 2)));
|
||||||
|
|
||||||
|
AddUntilStep("wait for beatmaps visible", () => GetVisiblePanels<BeatmapPanel>().Any());
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
// Beatmap panels expand their selection area to cover holes from spacing.
|
||||||
|
ClickVisiblePanelWithOffset<BeatmapPanel>(0, new Vector2(0, -(CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||||
|
WaitForGroupSelection(0, 0);
|
||||||
|
|
||||||
|
ClickVisiblePanelWithOffset<BeatmapPanel>(1, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||||
|
WaitForGroupSelection(0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,254 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
using osu.Game.Screens.Select.Filter;
|
||||||
|
using osu.Game.Screens.SelectV2;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public partial class TestSceneBeatmapCarouselV2NoGrouping : BeatmapCarouselV2TestScene
|
||||||
|
{
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
RemoveAllBeatmaps();
|
||||||
|
CreateCarousel();
|
||||||
|
SortBy(new FilterCriteria { Sort = SortMode.Title });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Keyboard selection via up and down arrows doesn't actually change the selection until
|
||||||
|
/// the select key is pressed.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestKeyboardSelectionKeyRepeat()
|
||||||
|
{
|
||||||
|
AddBeatmaps(10);
|
||||||
|
WaitForDrawablePanels();
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
Select();
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
AddStep("press down arrow", () => InputManager.PressKey(Key.Down));
|
||||||
|
checkSelectionIterating(false);
|
||||||
|
|
||||||
|
AddStep("press up arrow", () => InputManager.PressKey(Key.Up));
|
||||||
|
checkSelectionIterating(false);
|
||||||
|
|
||||||
|
AddStep("release down arrow", () => InputManager.ReleaseKey(Key.Down));
|
||||||
|
checkSelectionIterating(false);
|
||||||
|
|
||||||
|
AddStep("release up arrow", () => InputManager.ReleaseKey(Key.Up));
|
||||||
|
checkSelectionIterating(false);
|
||||||
|
|
||||||
|
Select();
|
||||||
|
CheckHasSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Keyboard selection via left and right arrows moves between groups, updating the selection
|
||||||
|
/// immediately.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestGroupSelectionKeyRepeat()
|
||||||
|
{
|
||||||
|
AddBeatmaps(10);
|
||||||
|
WaitForDrawablePanels();
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
AddStep("press right arrow", () => InputManager.PressKey(Key.Right));
|
||||||
|
checkSelectionIterating(true);
|
||||||
|
|
||||||
|
AddStep("press left arrow", () => InputManager.PressKey(Key.Left));
|
||||||
|
checkSelectionIterating(true);
|
||||||
|
|
||||||
|
AddStep("release right arrow", () => InputManager.ReleaseKey(Key.Right));
|
||||||
|
checkSelectionIterating(true);
|
||||||
|
|
||||||
|
AddStep("release left arrow", () => InputManager.ReleaseKey(Key.Left));
|
||||||
|
checkSelectionIterating(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCarouselRemembersSelection()
|
||||||
|
{
|
||||||
|
AddBeatmaps(10);
|
||||||
|
WaitForDrawablePanels();
|
||||||
|
|
||||||
|
SelectNextGroup();
|
||||||
|
|
||||||
|
object? selection = null;
|
||||||
|
|
||||||
|
AddStep("store drawable selection", () => selection = GetSelectedPanel()?.Item?.Model);
|
||||||
|
|
||||||
|
CheckHasSelection();
|
||||||
|
AddAssert("drawable selection non-null", () => selection, () => Is.Not.Null);
|
||||||
|
AddAssert("drawable selection matches carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
||||||
|
|
||||||
|
RemoveAllBeatmaps();
|
||||||
|
AddUntilStep("no drawable selection", GetSelectedPanel, () => Is.Null);
|
||||||
|
|
||||||
|
AddBeatmaps(10);
|
||||||
|
WaitForDrawablePanels();
|
||||||
|
|
||||||
|
CheckHasSelection();
|
||||||
|
AddAssert("no drawable selection", GetSelectedPanel, () => Is.Null);
|
||||||
|
|
||||||
|
AddStep("add previous selection", () => BeatmapSets.Add(((BeatmapInfo)selection!).BeatmapSet!));
|
||||||
|
|
||||||
|
AddAssert("selection matches original carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection));
|
||||||
|
AddUntilStep("drawable selection restored", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(selection));
|
||||||
|
AddAssert("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTraversalBeyondStart()
|
||||||
|
{
|
||||||
|
const int total_set_count = 200;
|
||||||
|
|
||||||
|
AddBeatmaps(total_set_count);
|
||||||
|
WaitForDrawablePanels();
|
||||||
|
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForSelection(0, 0);
|
||||||
|
SelectPrevGroup();
|
||||||
|
WaitForSelection(total_set_count - 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTraversalBeyondEnd()
|
||||||
|
{
|
||||||
|
const int total_set_count = 200;
|
||||||
|
|
||||||
|
AddBeatmaps(total_set_count);
|
||||||
|
WaitForDrawablePanels();
|
||||||
|
|
||||||
|
SelectPrevGroup();
|
||||||
|
WaitForSelection(total_set_count - 1, 0);
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForSelection(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGroupSelectionOnHeader()
|
||||||
|
{
|
||||||
|
AddBeatmaps(10, 3);
|
||||||
|
WaitForDrawablePanels();
|
||||||
|
|
||||||
|
SelectNextGroup();
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForSelection(1, 0);
|
||||||
|
|
||||||
|
SelectPrevPanel();
|
||||||
|
SelectPrevGroup();
|
||||||
|
WaitForSelection(1, 0);
|
||||||
|
|
||||||
|
SelectPrevPanel();
|
||||||
|
SelectNextGroup();
|
||||||
|
WaitForSelection(1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestKeyboardSelection()
|
||||||
|
{
|
||||||
|
AddBeatmaps(10, 3);
|
||||||
|
WaitForDrawablePanels();
|
||||||
|
|
||||||
|
SelectNextPanel();
|
||||||
|
SelectNextPanel();
|
||||||
|
SelectNextPanel();
|
||||||
|
SelectNextPanel();
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
Select();
|
||||||
|
WaitForSelection(3, 0);
|
||||||
|
|
||||||
|
SelectNextPanel();
|
||||||
|
WaitForSelection(3, 0);
|
||||||
|
|
||||||
|
Select();
|
||||||
|
WaitForSelection(3, 1);
|
||||||
|
|
||||||
|
SelectNextPanel();
|
||||||
|
WaitForSelection(3, 1);
|
||||||
|
|
||||||
|
Select();
|
||||||
|
WaitForSelection(3, 2);
|
||||||
|
|
||||||
|
SelectNextPanel();
|
||||||
|
WaitForSelection(3, 2);
|
||||||
|
|
||||||
|
Select();
|
||||||
|
WaitForSelection(4, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEmptyTraversal()
|
||||||
|
{
|
||||||
|
SelectNextPanel();
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
SelectNextGroup();
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
SelectPrevPanel();
|
||||||
|
CheckNoSelection();
|
||||||
|
|
||||||
|
SelectPrevGroup();
|
||||||
|
CheckNoSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInputHandlingWithinGaps()
|
||||||
|
{
|
||||||
|
AddBeatmaps(2, 5);
|
||||||
|
WaitForDrawablePanels();
|
||||||
|
|
||||||
|
AddAssert("no beatmaps visible", () => !GetVisiblePanels<BeatmapPanel>().Any());
|
||||||
|
|
||||||
|
// Clicks just above the first group panel should not actuate any action.
|
||||||
|
ClickVisiblePanelWithOffset<BeatmapSetPanel>(0, new Vector2(0, -(BeatmapSetPanel.HEIGHT / 2 + 1)));
|
||||||
|
|
||||||
|
AddAssert("no beatmaps visible", () => !GetVisiblePanels<BeatmapPanel>().Any());
|
||||||
|
|
||||||
|
ClickVisiblePanelWithOffset<BeatmapSetPanel>(0, new Vector2(0, -(BeatmapSetPanel.HEIGHT / 2)));
|
||||||
|
|
||||||
|
AddUntilStep("wait for beatmaps visible", () => GetVisiblePanels<BeatmapPanel>().Any());
|
||||||
|
WaitForSelection(0, 0);
|
||||||
|
|
||||||
|
// Beatmap panels expand their selection area to cover holes from spacing.
|
||||||
|
ClickVisiblePanelWithOffset<BeatmapPanel>(1, new Vector2(0, -(CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||||
|
WaitForSelection(0, 0);
|
||||||
|
|
||||||
|
// Panels with higher depth will handle clicks in the gutters for simplicity.
|
||||||
|
ClickVisiblePanelWithOffset<BeatmapPanel>(2, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||||
|
WaitForSelection(0, 2);
|
||||||
|
|
||||||
|
ClickVisiblePanelWithOffset<BeatmapPanel>(3, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||||
|
WaitForSelection(0, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkSelectionIterating(bool isIterating)
|
||||||
|
{
|
||||||
|
object? selection = null;
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
AddStep("store selection", () => selection = Carousel.CurrentSelection);
|
||||||
|
if (isIterating)
|
||||||
|
AddUntilStep("selection changed", () => Carousel.CurrentSelection != selection);
|
||||||
|
else
|
||||||
|
AddUntilStep("selection not changed", () => Carousel.CurrentSelection == selection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
using osu.Game.Screens.SelectV2;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public partial class TestSceneBeatmapCarouselV2Scrolling : BeatmapCarouselV2TestScene
|
||||||
|
{
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
RemoveAllBeatmaps();
|
||||||
|
CreateCarousel();
|
||||||
|
SortBy(new FilterCriteria());
|
||||||
|
|
||||||
|
AddBeatmaps(10);
|
||||||
|
WaitForDrawablePanels();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScrollPositionMaintainedOnAddSecondSelected()
|
||||||
|
{
|
||||||
|
Quad positionBefore = default;
|
||||||
|
|
||||||
|
AddStep("select middle beatmap", () => Carousel.CurrentSelection = BeatmapSets.ElementAt(BeatmapSets.Count - 2).Beatmaps.First());
|
||||||
|
AddStep("scroll to selected item", () => Scroll.ScrollTo(Scroll.ChildrenOfType<BeatmapPanel>().Single(p => p.Selected.Value)));
|
||||||
|
|
||||||
|
WaitForScrolling();
|
||||||
|
|
||||||
|
AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType<BeatmapPanel>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
|
||||||
|
|
||||||
|
RemoveFirstBeatmap();
|
||||||
|
WaitForSorting();
|
||||||
|
|
||||||
|
AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType<BeatmapPanel>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
|
||||||
|
() => Is.EqualTo(positionBefore));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScrollPositionMaintainedOnAddLastSelected()
|
||||||
|
{
|
||||||
|
Quad positionBefore = default;
|
||||||
|
|
||||||
|
AddStep("scroll to last item", () => Scroll.ScrollToEnd(false));
|
||||||
|
|
||||||
|
AddStep("select last beatmap", () => Carousel.CurrentSelection = BeatmapSets.Last().Beatmaps.Last());
|
||||||
|
|
||||||
|
WaitForScrolling();
|
||||||
|
|
||||||
|
AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType<BeatmapPanel>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
|
||||||
|
|
||||||
|
RemoveFirstBeatmap();
|
||||||
|
WaitForSorting();
|
||||||
|
AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType<BeatmapPanel>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
|
||||||
|
() => Is.EqualTo(positionBefore));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1239,7 +1239,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[Solo]
|
|
||||||
public void TestHardDeleteHandledCorrectly()
|
public void TestHardDeleteHandledCorrectly()
|
||||||
{
|
{
|
||||||
createSongSelect();
|
createSongSelect();
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Cursor;
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
@ -27,87 +28,102 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Child = new PopoverContainer
|
Child = new PopoverContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = new FillFlowContainer
|
Child = new OsuScrollContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Anchor = Anchor.Centre,
|
Child = new FillFlowContainer
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Width = 400,
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Spacing = new Vector2(5),
|
|
||||||
Padding = new MarginPadding(10),
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new FormTextBox
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Width = 400,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(5),
|
||||||
|
Padding = new MarginPadding(10),
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
Caption = "Artist",
|
new FormTextBox
|
||||||
HintText = "Poot artist here!",
|
|
||||||
PlaceholderText = "Here is an artist",
|
|
||||||
TabbableContentContainer = this,
|
|
||||||
},
|
|
||||||
new FormTextBox
|
|
||||||
{
|
|
||||||
Caption = "Artist",
|
|
||||||
HintText = "Poot artist here!",
|
|
||||||
PlaceholderText = "Here is an artist",
|
|
||||||
Current = { Disabled = true },
|
|
||||||
TabbableContentContainer = this,
|
|
||||||
},
|
|
||||||
new FormNumberBox
|
|
||||||
{
|
|
||||||
Caption = "Number",
|
|
||||||
HintText = "Insert your favourite number",
|
|
||||||
PlaceholderText = "Mine is 42!",
|
|
||||||
TabbableContentContainer = this,
|
|
||||||
},
|
|
||||||
new FormCheckBox
|
|
||||||
{
|
|
||||||
Caption = EditorSetupStrings.LetterboxDuringBreaks,
|
|
||||||
HintText = EditorSetupStrings.LetterboxDuringBreaksDescription,
|
|
||||||
},
|
|
||||||
new FormCheckBox
|
|
||||||
{
|
|
||||||
Caption = EditorSetupStrings.LetterboxDuringBreaks,
|
|
||||||
HintText = EditorSetupStrings.LetterboxDuringBreaksDescription,
|
|
||||||
Current = { Disabled = true },
|
|
||||||
},
|
|
||||||
new FormSliderBar<float>
|
|
||||||
{
|
|
||||||
Caption = "Slider",
|
|
||||||
Current = new BindableFloat
|
|
||||||
{
|
{
|
||||||
MinValue = 0,
|
Caption = "Artist",
|
||||||
MaxValue = 10,
|
HintText = "Poot artist here!",
|
||||||
Value = 5,
|
PlaceholderText = "Here is an artist",
|
||||||
Precision = 0.1f,
|
TabbableContentContainer = this,
|
||||||
},
|
},
|
||||||
TabbableContentContainer = this,
|
new FormTextBox
|
||||||
},
|
|
||||||
new FormEnumDropdown<CountdownType>
|
|
||||||
{
|
|
||||||
Caption = EditorSetupStrings.EnableCountdown,
|
|
||||||
HintText = EditorSetupStrings.CountdownDescription,
|
|
||||||
},
|
|
||||||
new FormFileSelector
|
|
||||||
{
|
|
||||||
Caption = "File selector",
|
|
||||||
PlaceholderText = "Select a file",
|
|
||||||
},
|
|
||||||
new FormBeatmapFileSelector(true)
|
|
||||||
{
|
|
||||||
Caption = "File selector with intermediate choice dialog",
|
|
||||||
PlaceholderText = "Select a file",
|
|
||||||
},
|
|
||||||
new FormColourPalette
|
|
||||||
{
|
|
||||||
Caption = "Combo colours",
|
|
||||||
Colours =
|
|
||||||
{
|
{
|
||||||
Colour4.Red,
|
Caption = "Artist",
|
||||||
Colour4.Green,
|
HintText = "Poot artist here!",
|
||||||
Colour4.Blue,
|
PlaceholderText = "Here is an artist",
|
||||||
Colour4.Yellow,
|
Current = { Disabled = true },
|
||||||
}
|
TabbableContentContainer = this,
|
||||||
|
},
|
||||||
|
new FormNumberBox(allowDecimals: true)
|
||||||
|
{
|
||||||
|
Caption = "Number",
|
||||||
|
HintText = "Insert your favourite number",
|
||||||
|
PlaceholderText = "Mine is 42!",
|
||||||
|
TabbableContentContainer = this,
|
||||||
|
},
|
||||||
|
new FormCheckBox
|
||||||
|
{
|
||||||
|
Caption = EditorSetupStrings.LetterboxDuringBreaks,
|
||||||
|
HintText = EditorSetupStrings.LetterboxDuringBreaksDescription,
|
||||||
|
},
|
||||||
|
new FormCheckBox
|
||||||
|
{
|
||||||
|
Caption = EditorSetupStrings.LetterboxDuringBreaks,
|
||||||
|
HintText = EditorSetupStrings.LetterboxDuringBreaksDescription,
|
||||||
|
Current = { Disabled = true },
|
||||||
|
},
|
||||||
|
new FormSliderBar<float>
|
||||||
|
{
|
||||||
|
Caption = "Slider",
|
||||||
|
Current = new BindableFloat
|
||||||
|
{
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Value = 5,
|
||||||
|
Precision = 0.1f,
|
||||||
|
},
|
||||||
|
TabbableContentContainer = this,
|
||||||
|
},
|
||||||
|
new FormEnumDropdown<CountdownType>
|
||||||
|
{
|
||||||
|
Caption = EditorSetupStrings.EnableCountdown,
|
||||||
|
HintText = EditorSetupStrings.CountdownDescription,
|
||||||
|
},
|
||||||
|
new FormFileSelector
|
||||||
|
{
|
||||||
|
Caption = "File selector",
|
||||||
|
PlaceholderText = "Select a file",
|
||||||
|
},
|
||||||
|
new FormBeatmapFileSelector(true)
|
||||||
|
{
|
||||||
|
Caption = "File selector with intermediate choice dialog",
|
||||||
|
PlaceholderText = "Select a file",
|
||||||
|
},
|
||||||
|
new FormColourPalette
|
||||||
|
{
|
||||||
|
Caption = "Combo colours",
|
||||||
|
Colours =
|
||||||
|
{
|
||||||
|
Colour4.Red,
|
||||||
|
Colour4.Green,
|
||||||
|
Colour4.Blue,
|
||||||
|
Colour4.Yellow,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new FormButton
|
||||||
|
{
|
||||||
|
Caption = "No text in button",
|
||||||
|
Action = () => { },
|
||||||
|
},
|
||||||
|
new FormButton
|
||||||
|
{
|
||||||
|
Caption = "Text in button which is pretty long and is very likely to wrap",
|
||||||
|
ButtonText = "Foo the bar",
|
||||||
|
Action = () => { },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -83,6 +83,40 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
waitForCompletion();
|
waitForCompletion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNormalDoesForwardToOverlay()
|
||||||
|
{
|
||||||
|
SimpleNotification notification = null!;
|
||||||
|
|
||||||
|
AddStep(@"simple #1", () => notificationOverlay.Post(notification = new SimpleNotification
|
||||||
|
{
|
||||||
|
Text = @"This shouldn't annoy you too much",
|
||||||
|
Transient = false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddAssert("notification in toast tray", () => notification.IsInToastTray, () => Is.True);
|
||||||
|
AddUntilStep("wait for dismissed", () => notification.IsInToastTray, () => Is.False);
|
||||||
|
|
||||||
|
checkDisplayedCount(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTransientDoesNotForwardToOverlay()
|
||||||
|
{
|
||||||
|
SimpleNotification notification = null!;
|
||||||
|
|
||||||
|
AddStep(@"simple #1", () => notificationOverlay.Post(notification = new SimpleNotification
|
||||||
|
{
|
||||||
|
Text = @"This shouldn't annoy you too much",
|
||||||
|
Transient = true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddAssert("notification in toast tray", () => notification.IsInToastTray, () => Is.True);
|
||||||
|
AddUntilStep("wait for dismissed", () => notification.IsInToastTray, () => Is.False);
|
||||||
|
|
||||||
|
checkDisplayedCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestForwardWithFlingRight()
|
public void TestForwardWithFlingRight()
|
||||||
{
|
{
|
||||||
@ -634,12 +668,18 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
private partial class BackgroundNotification : SimpleNotification
|
private partial class BackgroundNotification : SimpleNotification
|
||||||
{
|
{
|
||||||
public override bool IsImportant => false;
|
public BackgroundNotification()
|
||||||
|
{
|
||||||
|
IsImportant = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class BackgroundProgressNotification : ProgressNotification
|
private partial class BackgroundProgressNotification : ProgressNotification
|
||||||
{
|
{
|
||||||
public override bool IsImportant => false;
|
public BackgroundProgressNotification()
|
||||||
|
{
|
||||||
|
IsImportant = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,8 @@ namespace osu.Game.Tournament.Screens.Editors
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private LadderInfo ladderInfo { get; set; } = null!;
|
private LadderInfo ladderInfo { get; set; } = null!;
|
||||||
|
|
||||||
|
private readonly SettingsTextBox acronymTextBox;
|
||||||
|
|
||||||
public TeamRow(TournamentTeam team, TournamentScreen parent)
|
public TeamRow(TournamentTeam team, TournamentScreen parent)
|
||||||
{
|
{
|
||||||
Model = team;
|
Model = team;
|
||||||
@ -112,7 +114,7 @@ namespace osu.Game.Tournament.Screens.Editors
|
|||||||
Width = 0.2f,
|
Width = 0.2f,
|
||||||
Current = Model.FullName
|
Current = Model.FullName
|
||||||
},
|
},
|
||||||
new SettingsTextBox
|
acronymTextBox = new SettingsTextBox
|
||||||
{
|
{
|
||||||
LabelText = "Acronym",
|
LabelText = "Acronym",
|
||||||
Width = 0.2f,
|
Width = 0.2f,
|
||||||
@ -177,6 +179,27 @@ namespace osu.Game.Tournament.Screens.Editors
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Model.Acronym.BindValueChanged(acronym =>
|
||||||
|
{
|
||||||
|
var teamsWithSameAcronym = ladderInfo.Teams
|
||||||
|
.Where(t => t.Acronym.Value == acronym.NewValue && t != Model)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (teamsWithSameAcronym.Count > 0)
|
||||||
|
{
|
||||||
|
acronymTextBox.SetNoticeText(
|
||||||
|
$"Acronym '{acronym.NewValue}' is already in use by team{(teamsWithSameAcronym.Count > 1 ? "s" : "")}:\n"
|
||||||
|
+ $"{string.Join(",\n", teamsWithSameAcronym)}", true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
acronymTextBox.ClearNoticeText();
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
private partial class LastYearPlacementSlider : RoundedSliderBar<int>
|
private partial class LastYearPlacementSlider : RoundedSliderBar<int>
|
||||||
{
|
{
|
||||||
public override LocalisableString TooltipText => Current.Value == 0 ? "N/A" : base.TooltipText;
|
public override LocalisableString TooltipText => Current.Value == 0 ? "N/A" : base.TooltipText;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user