mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 11:37:28 +08:00
Merge branch 'master' into maniastatacc
This commit is contained in:
commit
0f47f463f8
@ -21,7 +21,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ppy.localisationanalyser.tools": {
|
"ppy.localisationanalyser.tools": {
|
||||||
"version": "2023.1117.0",
|
"version": "2024.517.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"localisation"
|
"localisation"
|
||||||
]
|
]
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup Label="C#">
|
<PropertyGroup Label="C#">
|
||||||
<LangVersion>12.0</LangVersion>
|
<LangVersion>12.0</LangVersion>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.509.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.523.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.
|
||||||
|
@ -164,8 +164,8 @@ namespace osu.Desktop
|
|||||||
// user activity
|
// user activity
|
||||||
if (activity.Value != null)
|
if (activity.Value != null)
|
||||||
{
|
{
|
||||||
presence.State = truncate(activity.Value.GetStatus(hideIdentifiableInformation));
|
presence.State = clampLength(activity.Value.GetStatus(hideIdentifiableInformation));
|
||||||
presence.Details = truncate(activity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty);
|
presence.Details = clampLength(activity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty);
|
||||||
|
|
||||||
if (getBeatmapID(activity.Value) is int beatmapId && beatmapId > 0)
|
if (getBeatmapID(activity.Value) is int beatmapId && beatmapId > 0)
|
||||||
{
|
{
|
||||||
@ -271,8 +271,19 @@ namespace osu.Desktop
|
|||||||
|
|
||||||
private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' });
|
private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' });
|
||||||
|
|
||||||
private static string truncate(string str)
|
private static string clampLength(string str)
|
||||||
{
|
{
|
||||||
|
// Empty strings are fine to discord even though single-character strings are not. Make it make sense.
|
||||||
|
if (string.IsNullOrEmpty(str))
|
||||||
|
return str;
|
||||||
|
|
||||||
|
// As above, discord decides that *non-empty* strings shorter than 2 characters cannot possibly be valid input, because... reasons?
|
||||||
|
// And yes, that is two *characters*, or *codepoints*, not *bytes* as further down below (as determined by empirical testing).
|
||||||
|
// That seems very questionable, and isn't even documented anywhere. So to *make it* accept such valid input,
|
||||||
|
// just tack on enough of U+200B ZERO WIDTH SPACEs at the end.
|
||||||
|
if (str.Length < 2)
|
||||||
|
return str.PadRight(2, '\u200B');
|
||||||
|
|
||||||
if (Encoding.UTF8.GetByteCount(str) <= 128)
|
if (Encoding.UTF8.GetByteCount(str) <= 128)
|
||||||
return str;
|
return str;
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ using osu.Game.IPC;
|
|||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Performance;
|
using osu.Game.Performance;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
using SDL;
|
|
||||||
|
|
||||||
namespace osu.Desktop
|
namespace osu.Desktop
|
||||||
{
|
{
|
||||||
@ -161,7 +160,7 @@ namespace osu.Desktop
|
|||||||
host.Window.Title = Name;
|
host.Window.Title = Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BatteryInfo CreateBatteryInfo() => new SDL3BatteryInfo();
|
protected override BatteryInfo CreateBatteryInfo() => FrameworkEnvironment.UseSDL3 ? new SDL3BatteryInfo() : new SDL2BatteryInfo();
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
@ -169,24 +168,5 @@ namespace osu.Desktop
|
|||||||
osuSchemeLinkIPCChannel?.Dispose();
|
osuSchemeLinkIPCChannel?.Dispose();
|
||||||
archiveImportIPCChannel?.Dispose();
|
archiveImportIPCChannel?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe class SDL3BatteryInfo : BatteryInfo
|
|
||||||
{
|
|
||||||
public override double? ChargeLevel
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
int percentage;
|
|
||||||
SDL3.SDL_GetPowerInfo(null, &percentage);
|
|
||||||
|
|
||||||
if (percentage == -1)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return percentage / 100.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool OnBattery => SDL3.SDL_GetPowerInfo(null, null) == SDL_PowerState.SDL_POWERSTATE_ON_BATTERY;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
osu.Desktop/SDL2BatteryInfo.cs
Normal file
25
osu.Desktop/SDL2BatteryInfo.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
|
namespace osu.Desktop
|
||||||
|
{
|
||||||
|
internal class SDL2BatteryInfo : BatteryInfo
|
||||||
|
{
|
||||||
|
public override double? ChargeLevel
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
SDL2.SDL.SDL_GetPowerInfo(out _, out int percentage);
|
||||||
|
|
||||||
|
if (percentage == -1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return percentage / 100.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnBattery => SDL2.SDL.SDL_GetPowerInfo(out _, out _) == SDL2.SDL.SDL_PowerState.SDL_POWERSTATE_ON_BATTERY;
|
||||||
|
}
|
||||||
|
}
|
27
osu.Desktop/SDL3BatteryInfo.cs
Normal file
27
osu.Desktop/SDL3BatteryInfo.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Utils;
|
||||||
|
using SDL;
|
||||||
|
|
||||||
|
namespace osu.Desktop
|
||||||
|
{
|
||||||
|
internal unsafe class SDL3BatteryInfo : BatteryInfo
|
||||||
|
{
|
||||||
|
public override double? ChargeLevel
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int percentage;
|
||||||
|
SDL3.SDL_GetPowerInfo(null, &percentage);
|
||||||
|
|
||||||
|
if (percentage == -1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return percentage / 100.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnBattery => SDL3.SDL_GetPowerInfo(null, null) == SDL_PowerState.SDL_POWERSTATE_ON_BATTERY;
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
@ -3,15 +3,12 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Framework;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Skinning;
|
using osu.Game.Rulesets.Mania.Skinning;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
{
|
{
|
||||||
@ -62,12 +59,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
onSkinChanged();
|
onSkinChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
updateMobileSizing();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onSkinChanged()
|
private void onSkinChanged()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < stageDefinition.Columns; i++)
|
for (int i = 0; i < stageDefinition.Columns; i++)
|
||||||
@ -92,8 +83,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
columns[i].Width = width.Value;
|
columns[i].Width = width.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMobileSizing();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -106,31 +95,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
Content[column] = columns[column].Child = content;
|
Content[column] = columns[column].Child = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMobileSizing()
|
|
||||||
{
|
|
||||||
if (!IsLoaded || !RuntimeInfo.IsMobile)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// GridContainer+CellContainer containing this stage (gets split up for dual stages).
|
|
||||||
Vector2? containingCell = this.FindClosestParent<Stage>()?.Parent?.DrawSize;
|
|
||||||
|
|
||||||
// Will be null in tests.
|
|
||||||
if (containingCell == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
float aspectRatio = containingCell.Value.X / containingCell.Value.Y;
|
|
||||||
|
|
||||||
// 2.83 is a mostly arbitrary scale-up (170 / 60, based on original implementation for argon)
|
|
||||||
float mobileAdjust = 2.83f * Math.Min(1, 7f / stageDefinition.Columns);
|
|
||||||
// 1.92 is a "reference" mobile screen aspect ratio for phones.
|
|
||||||
// We should scale it back for cases like tablets which aren't so extreme.
|
|
||||||
mobileAdjust *= aspectRatio / 1.92f;
|
|
||||||
|
|
||||||
// Best effort until we have better mobile support.
|
|
||||||
for (int i = 0; i < stageDefinition.Columns; i++)
|
|
||||||
columns[i].Width *= mobileAdjust;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
@ -36,11 +36,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
while (rhythmStart < historicalNoteCount - 2 && current.StartTime - current.Previous(rhythmStart).StartTime < history_time_max)
|
while (rhythmStart < historicalNoteCount - 2 && current.StartTime - current.Previous(rhythmStart).StartTime < history_time_max)
|
||||||
rhythmStart++;
|
rhythmStart++;
|
||||||
|
|
||||||
|
OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)current.Previous(rhythmStart);
|
||||||
|
OsuDifficultyHitObject lastObj = (OsuDifficultyHitObject)current.Previous(rhythmStart + 1);
|
||||||
|
|
||||||
for (int i = rhythmStart; i > 0; i--)
|
for (int i = rhythmStart; i > 0; i--)
|
||||||
{
|
{
|
||||||
OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current.Previous(i - 1);
|
OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current.Previous(i - 1);
|
||||||
OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)current.Previous(i);
|
|
||||||
OsuDifficultyHitObject lastObj = (OsuDifficultyHitObject)current.Previous(i + 1);
|
|
||||||
|
|
||||||
double currHistoricalDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; // scales note 0 to 1 from history to now
|
double currHistoricalDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; // scales note 0 to 1 from history to now
|
||||||
|
|
||||||
@ -66,10 +67,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (current.Previous(i - 1).BaseObject is Slider) // bpm change is into slider, this is easy acc window
|
if (currObj.BaseObject is Slider) // bpm change is into slider, this is easy acc window
|
||||||
effectiveRatio *= 0.125;
|
effectiveRatio *= 0.125;
|
||||||
|
|
||||||
if (current.Previous(i).BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle
|
if (prevObj.BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle
|
||||||
effectiveRatio *= 0.25;
|
effectiveRatio *= 0.25;
|
||||||
|
|
||||||
if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet)
|
if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet)
|
||||||
@ -100,6 +101,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
startRatio = effectiveRatio;
|
startRatio = effectiveRatio;
|
||||||
islandSize = 1;
|
islandSize = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastObj = prevObj;
|
||||||
|
prevObj = currObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2; //produces multiplier that can be applied to strain. range [1, infinity) (not really though)
|
return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2; //produces multiplier that can be applied to strain. range [1, infinity) (not really though)
|
||||||
|
@ -8,9 +8,9 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
public readonly PathControlPoint ControlPoint;
|
public readonly PathControlPoint ControlPoint;
|
||||||
|
|
||||||
private readonly T hitObject;
|
private readonly T hitObject;
|
||||||
private readonly Container marker;
|
private readonly Circle circle;
|
||||||
private readonly Drawable markerRing;
|
private readonly Drawable markerRing;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
@ -60,38 +60,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
marker = new Container
|
circle = new Circle
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
AutoSizeAxes = Axes.Both,
|
Size = new Vector2(20),
|
||||||
Children = new[]
|
},
|
||||||
{
|
markerRing = new CircularProgress
|
||||||
new Circle
|
{
|
||||||
{
|
Anchor = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Size = new Vector2(28),
|
||||||
Size = new Vector2(20),
|
Alpha = 0,
|
||||||
},
|
InnerRadius = 0.1f,
|
||||||
markerRing = new CircularContainer
|
Progress = 1
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Size = new Vector2(28),
|
|
||||||
Masking = true,
|
|
||||||
BorderThickness = 2,
|
|
||||||
BorderColour = Color4.White,
|
|
||||||
Alpha = 0,
|
|
||||||
Child = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Alpha = 0,
|
|
||||||
AlwaysPresent = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -115,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The connecting path is excluded from positional input
|
// The connecting path is excluded from positional input
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
@ -209,8 +193,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
if (IsHovered || IsSelected.Value)
|
if (IsHovered || IsSelected.Value)
|
||||||
colour = colour.Lighten(1);
|
colour = colour.Lighten(1);
|
||||||
|
|
||||||
marker.Colour = colour;
|
Colour = colour;
|
||||||
marker.Scale = new Vector2(hitObject.Scale);
|
Scale = new Vector2(hitObject.Scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color4 getColourFromNodeType()
|
private Color4 getColourFromNodeType()
|
||||||
|
@ -435,10 +435,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
var item = new TernaryStateRadioMenuItem(type?.Description ?? "Inherit", MenuItemType.Standard, _ =>
|
var item = new TernaryStateRadioMenuItem(type?.Description ?? "Inherit", MenuItemType.Standard, _ =>
|
||||||
{
|
{
|
||||||
|
changeHandler?.BeginChange();
|
||||||
|
|
||||||
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
||||||
updatePathType(p, type);
|
updatePathType(p, type);
|
||||||
|
|
||||||
EnsureValidPathTypes();
|
EnsureValidPathTypes();
|
||||||
|
|
||||||
|
changeHandler?.EndChange();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (countOfState == totalCount)
|
if (countOfState == totalCount)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
@ -27,14 +28,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
public SliderBodyPiece()
|
public SliderBodyPiece()
|
||||||
{
|
{
|
||||||
InternalChild = body = new ManualSliderBody
|
AutoSizeAxes = Axes.Both;
|
||||||
{
|
|
||||||
AccentColour = Color4.Transparent
|
|
||||||
};
|
|
||||||
|
|
||||||
// SliderSelectionBlueprint relies on calling ReceivePositionalInputAt on this drawable to determine whether selection should occur.
|
// SliderSelectionBlueprint relies on calling ReceivePositionalInputAt on this drawable to determine whether selection should occur.
|
||||||
// Without AlwaysPresent, a movement in a parent container (ie. the editor composer area resizing) could cause incorrect input handling.
|
// Without AlwaysPresent, a movement in a parent container (ie. the editor composer area resizing) could cause incorrect input handling.
|
||||||
AlwaysPresent = true;
|
AlwaysPresent = true;
|
||||||
|
|
||||||
|
InternalChild = body = new ManualSliderBody
|
||||||
|
{
|
||||||
|
AccentColour = Color4.Transparent
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -61,7 +64,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
body.SetVertices(vertices);
|
body.SetVertices(vertices);
|
||||||
}
|
}
|
||||||
|
|
||||||
Size = body.Size;
|
|
||||||
OriginPosition = body.PathOffset;
|
OriginPosition = body.PathOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
@ -25,33 +24,17 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
public partial class OsuSelectionHandler : EditorSelectionHandler
|
public partial class OsuSelectionHandler : EditorSelectionHandler
|
||||||
{
|
{
|
||||||
[Resolved(CanBeNull = true)]
|
|
||||||
private IDistanceSnapProvider? snapProvider { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// During a transform, the initial path types of a single selected slider are stored so they
|
|
||||||
/// can be maintained throughout the operation.
|
|
||||||
/// </summary>
|
|
||||||
private List<PathType?>? referencePathTypes;
|
|
||||||
|
|
||||||
protected override void OnSelectionChanged()
|
protected override void OnSelectionChanged()
|
||||||
{
|
{
|
||||||
base.OnSelectionChanged();
|
base.OnSelectionChanged();
|
||||||
|
|
||||||
Quad quad = selectedMovableObjects.Length > 0 ? GeometryUtils.GetSurroundingQuad(selectedMovableObjects) : new Quad();
|
Quad quad = selectedMovableObjects.Length > 0 ? GeometryUtils.GetSurroundingQuad(selectedMovableObjects) : new Quad();
|
||||||
|
|
||||||
SelectionBox.CanFlipX = SelectionBox.CanScaleX = quad.Width > 0;
|
SelectionBox.CanFlipX = quad.Width > 0;
|
||||||
SelectionBox.CanFlipY = SelectionBox.CanScaleY = quad.Height > 0;
|
SelectionBox.CanFlipY = quad.Height > 0;
|
||||||
SelectionBox.CanScaleDiagonally = SelectionBox.CanScaleX && SelectionBox.CanScaleY;
|
|
||||||
SelectionBox.CanReverse = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider);
|
SelectionBox.CanReverse = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnOperationEnded()
|
|
||||||
{
|
|
||||||
base.OnOperationEnded();
|
|
||||||
referencePathTypes = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
{
|
{
|
||||||
if (e.Key == Key.M && e.ControlPressed && e.ShiftPressed)
|
if (e.Key == Key.M && e.ControlPressed && e.ShiftPressed)
|
||||||
@ -149,96 +132,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
return didFlip;
|
return didFlip;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool HandleScale(Vector2 scale, Anchor reference)
|
|
||||||
{
|
|
||||||
adjustScaleFromAnchor(ref scale, reference);
|
|
||||||
|
|
||||||
var hitObjects = selectedMovableObjects;
|
|
||||||
|
|
||||||
// for the time being, allow resizing of slider paths only if the slider is
|
|
||||||
// the only hit object selected. with a group selection, it's likely the user
|
|
||||||
// is not looking to change the duration of the slider but expand the whole pattern.
|
|
||||||
if (hitObjects.Length == 1 && hitObjects.First() is Slider slider)
|
|
||||||
scaleSlider(slider, scale);
|
|
||||||
else
|
|
||||||
scaleHitObjects(hitObjects, reference, scale);
|
|
||||||
|
|
||||||
moveSelectionInBounds();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference)
|
|
||||||
{
|
|
||||||
// cancel out scale in axes we don't care about (based on which drag handle was used).
|
|
||||||
if ((reference & Anchor.x1) > 0) scale.X = 0;
|
|
||||||
if ((reference & Anchor.y1) > 0) scale.Y = 0;
|
|
||||||
|
|
||||||
// reverse the scale direction if dragging from top or left.
|
|
||||||
if ((reference & Anchor.x0) > 0) scale.X = -scale.X;
|
|
||||||
if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override SelectionRotationHandler CreateRotationHandler() => new OsuSelectionRotationHandler();
|
public override SelectionRotationHandler CreateRotationHandler() => new OsuSelectionRotationHandler();
|
||||||
|
|
||||||
private void scaleSlider(Slider slider, Vector2 scale)
|
public override SelectionScaleHandler CreateScaleHandler() => new OsuSelectionScaleHandler();
|
||||||
{
|
|
||||||
referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type).ToList();
|
|
||||||
|
|
||||||
Quad sliderQuad = GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position));
|
|
||||||
|
|
||||||
// Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0.
|
|
||||||
scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size;
|
|
||||||
|
|
||||||
Vector2 pathRelativeDeltaScale = new Vector2(
|
|
||||||
sliderQuad.Width == 0 ? 0 : 1 + scale.X / sliderQuad.Width,
|
|
||||||
sliderQuad.Height == 0 ? 0 : 1 + scale.Y / sliderQuad.Height);
|
|
||||||
|
|
||||||
Queue<Vector2> oldControlPoints = new Queue<Vector2>();
|
|
||||||
|
|
||||||
foreach (var point in slider.Path.ControlPoints)
|
|
||||||
{
|
|
||||||
oldControlPoints.Enqueue(point.Position);
|
|
||||||
point.Position *= pathRelativeDeltaScale;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maintain the path types in case they were defaulted to bezier at some point during scaling
|
|
||||||
for (int i = 0; i < slider.Path.ControlPoints.Count; ++i)
|
|
||||||
slider.Path.ControlPoints[i].Type = referencePathTypes[i];
|
|
||||||
|
|
||||||
// Snap the slider's length to the current beat divisor
|
|
||||||
// to calculate the final resulting duration / bounding box before the final checks.
|
|
||||||
slider.SnapTo(snapProvider);
|
|
||||||
|
|
||||||
//if sliderhead or sliderend end up outside playfield, revert scaling.
|
|
||||||
Quad scaledQuad = GeometryUtils.GetSurroundingQuad(new OsuHitObject[] { slider });
|
|
||||||
(bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad);
|
|
||||||
|
|
||||||
if (xInBounds && yInBounds && slider.Path.HasValidLength)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var point in slider.Path.ControlPoints)
|
|
||||||
point.Position = oldControlPoints.Dequeue();
|
|
||||||
|
|
||||||
// Snap the slider's length again to undo the potentially-invalid length applied by the previous snap.
|
|
||||||
slider.SnapTo(snapProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale)
|
|
||||||
{
|
|
||||||
scale = getClampedScale(hitObjects, reference, scale);
|
|
||||||
Quad selectionQuad = GeometryUtils.GetSurroundingQuad(hitObjects);
|
|
||||||
|
|
||||||
foreach (var h in hitObjects)
|
|
||||||
h.Position = GeometryUtils.GetScaledPosition(reference, scale, selectionQuad, h.Position);
|
|
||||||
}
|
|
||||||
|
|
||||||
private (bool X, bool Y) isQuadInBounds(Quad quad)
|
|
||||||
{
|
|
||||||
bool xInBounds = (quad.TopLeft.X >= 0) && (quad.BottomRight.X <= DrawWidth);
|
|
||||||
bool yInBounds = (quad.TopLeft.Y >= 0) && (quad.BottomRight.Y <= DrawHeight);
|
|
||||||
|
|
||||||
return (xInBounds, yInBounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void moveSelectionInBounds()
|
private void moveSelectionInBounds()
|
||||||
{
|
{
|
||||||
@ -262,43 +158,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
h.Position += delta;
|
h.Position += delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="hitObjects">The hitobjects to be scaled</param>
|
|
||||||
/// <param name="reference">The anchor from which the scale operation is performed</param>
|
|
||||||
/// <param name="scale">The scale to be clamped</param>
|
|
||||||
/// <returns>The clamped scale vector</returns>
|
|
||||||
private Vector2 getClampedScale(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale)
|
|
||||||
{
|
|
||||||
float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0;
|
|
||||||
float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0;
|
|
||||||
|
|
||||||
Quad selectionQuad = GeometryUtils.GetSurroundingQuad(hitObjects);
|
|
||||||
|
|
||||||
//todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead.
|
|
||||||
Quad scaledQuad = new Quad(selectionQuad.TopLeft.X + xOffset, selectionQuad.TopLeft.Y + yOffset, selectionQuad.Width + scale.X, selectionQuad.Height + scale.Y);
|
|
||||||
|
|
||||||
//max Size -> playfield bounds
|
|
||||||
if (scaledQuad.TopLeft.X < 0)
|
|
||||||
scale.X += scaledQuad.TopLeft.X;
|
|
||||||
if (scaledQuad.TopLeft.Y < 0)
|
|
||||||
scale.Y += scaledQuad.TopLeft.Y;
|
|
||||||
|
|
||||||
if (scaledQuad.BottomRight.X > DrawWidth)
|
|
||||||
scale.X -= scaledQuad.BottomRight.X - DrawWidth;
|
|
||||||
if (scaledQuad.BottomRight.Y > DrawHeight)
|
|
||||||
scale.Y -= scaledQuad.BottomRight.Y - DrawHeight;
|
|
||||||
|
|
||||||
//min Size -> almost 0. Less than 0 causes the quad to flip, exactly 0 causes scaling to get stuck at minimum scale.
|
|
||||||
Vector2 scaledSize = selectionQuad.Size + scale;
|
|
||||||
Vector2 minSize = new Vector2(Precision.FLOAT_EPSILON);
|
|
||||||
|
|
||||||
scale = Vector2.ComponentMax(minSize, scaledSize) - selectionQuad.Size;
|
|
||||||
|
|
||||||
return scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All osu! hitobjects which can be moved/rotated/scaled.
|
/// All osu! hitobjects which can be moved/rotated/scaled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
220
osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
Normal file
220
osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
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.Screens.Edit.Compose.Components;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
|
{
|
||||||
|
public partial class OsuSelectionScaleHandler : SelectionScaleHandler
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private IEditorChangeHandler? changeHandler { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private IDistanceSnapProvider? snapProvider { get; set; }
|
||||||
|
|
||||||
|
private BindableList<HitObject> selectedItems { get; } = new BindableList<HitObject>();
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(EditorBeatmap editorBeatmap)
|
||||||
|
{
|
||||||
|
selectedItems.BindTo(editorBeatmap.SelectedHitObjects);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
selectedItems.CollectionChanged += (_, __) => updateState();
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
|
{
|
||||||
|
var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects);
|
||||||
|
|
||||||
|
CanScaleX.Value = quad.Width > 0;
|
||||||
|
CanScaleY.Value = quad.Height > 0;
|
||||||
|
CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<OsuHitObject, OriginalHitObjectState>? objectsInScale;
|
||||||
|
private Vector2? defaultOrigin;
|
||||||
|
|
||||||
|
public override void Begin()
|
||||||
|
{
|
||||||
|
if (objectsInScale != null)
|
||||||
|
throw new InvalidOperationException($"Cannot {nameof(Begin)} a scale operation while another is in progress!");
|
||||||
|
|
||||||
|
changeHandler?.BeginChange();
|
||||||
|
|
||||||
|
objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho));
|
||||||
|
OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider
|
||||||
|
? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position))
|
||||||
|
: GeometryUtils.GetSurroundingQuad(objectsInScale.Keys);
|
||||||
|
defaultOrigin = OriginalSurroundingQuad.Value.Centre;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both)
|
||||||
|
{
|
||||||
|
if (objectsInScale == null)
|
||||||
|
throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!");
|
||||||
|
|
||||||
|
Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null);
|
||||||
|
|
||||||
|
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
|
||||||
|
|
||||||
|
// for the time being, allow resizing of slider paths only if the slider is
|
||||||
|
// the only hit object selected. with a group selection, it's likely the user
|
||||||
|
// is not looking to change the duration of the slider but expand the whole pattern.
|
||||||
|
if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider)
|
||||||
|
{
|
||||||
|
var originalInfo = objectsInScale[slider];
|
||||||
|
Debug.Assert(originalInfo.PathControlPointPositions != null && originalInfo.PathControlPointTypes != null);
|
||||||
|
scaleSlider(slider, scale, originalInfo.PathControlPointPositions, originalInfo.PathControlPointTypes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
scale = getClampedScale(OriginalSurroundingQuad.Value, actualOrigin, scale);
|
||||||
|
|
||||||
|
foreach (var (ho, originalState) in objectsInScale)
|
||||||
|
{
|
||||||
|
ho.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, originalState.Position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moveSelectionInBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Commit()
|
||||||
|
{
|
||||||
|
if (objectsInScale == null)
|
||||||
|
throw new InvalidOperationException($"Cannot {nameof(Commit)} a rotate operation without calling {nameof(Begin)} first!");
|
||||||
|
|
||||||
|
changeHandler?.EndChange();
|
||||||
|
|
||||||
|
objectsInScale = null;
|
||||||
|
OriginalSurroundingQuad = null;
|
||||||
|
defaultOrigin = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<OsuHitObject> selectedMovableObjects => selectedItems.Cast<OsuHitObject>()
|
||||||
|
.Where(h => h is not Spinner);
|
||||||
|
|
||||||
|
private void scaleSlider(Slider slider, Vector2 scale, Vector2[] originalPathPositions, PathType?[] originalPathTypes)
|
||||||
|
{
|
||||||
|
scale = Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
|
||||||
|
|
||||||
|
// Maintain the path types in case they were defaulted to bezier at some point during scaling
|
||||||
|
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
|
||||||
|
{
|
||||||
|
slider.Path.ControlPoints[i].Position = originalPathPositions[i] * scale;
|
||||||
|
slider.Path.ControlPoints[i].Type = originalPathTypes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snap the slider's length to the current beat divisor
|
||||||
|
// to calculate the final resulting duration / bounding box before the final checks.
|
||||||
|
slider.SnapTo(snapProvider);
|
||||||
|
|
||||||
|
//if sliderhead or sliderend end up outside playfield, revert scaling.
|
||||||
|
Quad scaledQuad = GeometryUtils.GetSurroundingQuad(new OsuHitObject[] { slider });
|
||||||
|
(bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad);
|
||||||
|
|
||||||
|
if (xInBounds && yInBounds && slider.Path.HasValidLength)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
|
||||||
|
slider.Path.ControlPoints[i].Position = originalPathPositions[i];
|
||||||
|
|
||||||
|
// Snap the slider's length again to undo the potentially-invalid length applied by the previous snap.
|
||||||
|
slider.SnapTo(snapProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private (bool X, bool Y) isQuadInBounds(Quad quad)
|
||||||
|
{
|
||||||
|
bool xInBounds = (quad.TopLeft.X >= 0) && (quad.BottomRight.X <= OsuPlayfield.BASE_SIZE.X);
|
||||||
|
bool yInBounds = (quad.TopLeft.Y >= 0) && (quad.BottomRight.Y <= OsuPlayfield.BASE_SIZE.Y);
|
||||||
|
|
||||||
|
return (xInBounds, yInBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="selectionQuad">The quad surrounding the hitobjects</param>
|
||||||
|
/// <param name="origin">The origin from which the scale operation is performed</param>
|
||||||
|
/// <param name="scale">The scale to be clamped</param>
|
||||||
|
/// <returns>The clamped scale vector</returns>
|
||||||
|
private Vector2 getClampedScale(Quad selectionQuad, Vector2 origin, Vector2 scale)
|
||||||
|
{
|
||||||
|
//todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead.
|
||||||
|
|
||||||
|
var tl1 = Vector2.Divide(-origin, selectionQuad.TopLeft - origin);
|
||||||
|
var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.TopLeft - origin);
|
||||||
|
var br1 = Vector2.Divide(-origin, selectionQuad.BottomRight - origin);
|
||||||
|
var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.BottomRight - origin);
|
||||||
|
|
||||||
|
if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - origin.X, 0))
|
||||||
|
scale.X = selectionQuad.TopLeft.X - origin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X);
|
||||||
|
if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - origin.Y, 0))
|
||||||
|
scale.Y = selectionQuad.TopLeft.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y);
|
||||||
|
if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - origin.X, 0))
|
||||||
|
scale.X = selectionQuad.BottomRight.X - origin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X);
|
||||||
|
if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - origin.Y, 0))
|
||||||
|
scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y);
|
||||||
|
|
||||||
|
return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveSelectionInBounds()
|
||||||
|
{
|
||||||
|
Quad quad = GeometryUtils.GetSurroundingQuad(objectsInScale!.Keys);
|
||||||
|
|
||||||
|
Vector2 delta = Vector2.Zero;
|
||||||
|
|
||||||
|
if (quad.TopLeft.X < 0)
|
||||||
|
delta.X -= quad.TopLeft.X;
|
||||||
|
if (quad.TopLeft.Y < 0)
|
||||||
|
delta.Y -= quad.TopLeft.Y;
|
||||||
|
|
||||||
|
if (quad.BottomRight.X > OsuPlayfield.BASE_SIZE.X)
|
||||||
|
delta.X -= quad.BottomRight.X - OsuPlayfield.BASE_SIZE.X;
|
||||||
|
if (quad.BottomRight.Y > OsuPlayfield.BASE_SIZE.Y)
|
||||||
|
delta.Y -= quad.BottomRight.Y - OsuPlayfield.BASE_SIZE.Y;
|
||||||
|
|
||||||
|
foreach (var (h, _) in objectsInScale!)
|
||||||
|
h.Position += delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct OriginalHitObjectState
|
||||||
|
{
|
||||||
|
public Vector2 Position { get; }
|
||||||
|
public Vector2[]? PathControlPointPositions { get; }
|
||||||
|
public PathType?[]? PathControlPointTypes { get; }
|
||||||
|
|
||||||
|
public OriginalHitObjectState(OsuHitObject hitObject)
|
||||||
|
{
|
||||||
|
Position = hitObject.Position;
|
||||||
|
PathControlPointPositions = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Position).ToArray();
|
||||||
|
PathControlPointTypes = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Type).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override LocalisableString Description => "Put your faith in the approach circles...";
|
public override LocalisableString Description => "Put your faith in the approach circles...";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
public override bool Ranked => true;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModDepth) };
|
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModDepth) };
|
||||||
|
|
||||||
|
@ -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.Graphics;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||||
@ -11,10 +12,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class ManualSliderBody : SliderBody
|
public partial class ManualSliderBody : SliderBody
|
||||||
{
|
{
|
||||||
public new void SetVertices(IReadOnlyList<Vector2> vertices)
|
public ManualSliderBody()
|
||||||
{
|
{
|
||||||
base.SetVertices(vertices);
|
AutoSizeAxes = Axes.Both;
|
||||||
Size = Path.Size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public new void SetVertices(IReadOnlyList<Vector2> vertices) => base.SetVertices(vertices);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,12 +26,5 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
|
|
||||||
ShowSpeedChanges.BindValueChanged(showChanges => VisualisationMethod = showChanges.NewValue ? ScrollVisualisationMethod.Overlapping : ScrollVisualisationMethod.Constant, true);
|
ShowSpeedChanges.BindValueChanged(showChanges => VisualisationMethod = showChanges.NewValue ? ScrollVisualisationMethod.Overlapping : ScrollVisualisationMethod.Constant, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double ComputeTimeRange()
|
|
||||||
{
|
|
||||||
// Adjust when we're using constant algorithm to not be sluggish.
|
|
||||||
double multiplier = ShowSpeedChanges.Value ? 1 : 4;
|
|
||||||
return base.ComputeTimeRange() / multiplier;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
29
osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs
Normal file
29
osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// 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.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Mods
|
||||||
|
{
|
||||||
|
public class TaikoModConstantSpeed : Mod, IApplicableToDrawableRuleset<TaikoHitObject>
|
||||||
|
{
|
||||||
|
public override string Name => "Constant Speed";
|
||||||
|
public override string Acronym => "CS";
|
||||||
|
public override double ScoreMultiplier => 0.9;
|
||||||
|
public override LocalisableString Description => "No more tricky speed changes!";
|
||||||
|
public override IconUsage? Icon => FontAwesome.Solid.Equals;
|
||||||
|
public override ModType Type => ModType.Conversion;
|
||||||
|
|
||||||
|
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
var taikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
|
||||||
|
taikoRuleset.VisualisationMethod = ScrollVisualisationMethod.Constant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
Scale = new Vector2(0.7f),
|
Scale = new Vector2(TaikoLegacyHitTarget.SCALE),
|
||||||
Colour = new Colour4(255, 228, 0, 255),
|
Colour = new Colour4(255, 228, 0, 255),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -58,8 +58,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
if (!result.IsHit || !isKiaiActive)
|
if (!result.IsHit || !isKiaiActive)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
sprite.ScaleTo(0.85f).Then()
|
sprite.ScaleTo(TaikoLegacyHitTarget.SCALE + 0.15f).Then()
|
||||||
.ScaleTo(0.7f, 80, Easing.OutQuad);
|
.ScaleTo(TaikoLegacyHitTarget.SCALE, 80, Easing.OutQuad);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
public partial class TaikoLegacyHitTarget : CompositeDrawable
|
public partial class TaikoLegacyHitTarget : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// In stable this is 0.7f (see https://github.com/peppy/osu-stable-reference/blob/7519cafd1823f1879c0d9c991ba0e5c7fd3bfa02/osu!/GameModes/Play/Rulesets/Taiko/RulesetTaiko.cs#L592)
|
||||||
|
/// but for whatever reason this doesn't match visually.
|
||||||
|
/// </summary>
|
||||||
|
public const float SCALE = 0.8f;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource skin)
|
private void load(ISkinSource skin)
|
||||||
{
|
{
|
||||||
@ -22,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
new Sprite
|
new Sprite
|
||||||
{
|
{
|
||||||
Texture = skin.GetTexture("approachcircle"),
|
Texture = skin.GetTexture("approachcircle"),
|
||||||
Scale = new Vector2(0.83f),
|
Scale = new Vector2(SCALE + 0.03f),
|
||||||
Alpha = 0.47f, // eyeballed to match stable
|
Alpha = 0.47f, // eyeballed to match stable
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -30,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
new Sprite
|
new Sprite
|
||||||
{
|
{
|
||||||
Texture = skin.GetTexture("taikobigcircle"),
|
Texture = skin.GetTexture("taikobigcircle"),
|
||||||
Scale = new Vector2(0.8f),
|
Scale = new Vector2(SCALE),
|
||||||
Alpha = 0.22f, // eyeballed to match stable
|
Alpha = 0.22f, // eyeballed to match stable
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -150,6 +150,7 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
new TaikoModClassic(),
|
new TaikoModClassic(),
|
||||||
new TaikoModSwap(),
|
new TaikoModSwap(),
|
||||||
new TaikoModSingleTap(),
|
new TaikoModSingleTap(),
|
||||||
|
new TaikoModConstantSpeed(),
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.Automation:
|
case ModType.Automation:
|
||||||
|
@ -82,7 +82,12 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
TimeRange.Value = ComputeTimeRange();
|
TimeRange.Value = ComputeTimeRange();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual double ComputeTimeRange() => PlayfieldAdjustmentContainer.ComputeTimeRange();
|
protected virtual double ComputeTimeRange()
|
||||||
|
{
|
||||||
|
// Adjust when we're using constant algorithm to not be sluggish.
|
||||||
|
double multiplier = VisualisationMethod == ScrollVisualisationMethod.Constant ? 4 * Beatmap.Difficulty.SliderMultiplier : 1;
|
||||||
|
return PlayfieldAdjustmentContainer.ComputeTimeRange() / multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
{
|
{
|
||||||
|
@ -1188,5 +1188,36 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.That(beatmap.HitObjects[0].GetEndTime(), Is.EqualTo(3153));
|
Assert.That(beatmap.HitObjects[0].GetEndTime(), Is.EqualTo(3153));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBeatmapDifficultyIsClamped()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("out-of-range-difficulties.osu"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var decoded = decoder.Decode(stream).Difficulty;
|
||||||
|
Assert.That(decoded.DrainRate, Is.EqualTo(10));
|
||||||
|
Assert.That(decoded.CircleSize, Is.EqualTo(10));
|
||||||
|
Assert.That(decoded.OverallDifficulty, Is.EqualTo(10));
|
||||||
|
Assert.That(decoded.ApproachRate, Is.EqualTo(10));
|
||||||
|
Assert.That(decoded.SliderMultiplier, Is.EqualTo(3.6));
|
||||||
|
Assert.That(decoded.SliderTickRate, Is.EqualTo(8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestManiaBeatmapDifficultyCircleSizeClamp()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("out-of-range-difficulties-mania.osu"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var decoded = decoder.Decode(stream).Difficulty;
|
||||||
|
Assert.That(decoded.CircleSize, Is.EqualTo(14));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
[General]
|
||||||
|
Mode: 3
|
||||||
|
|
||||||
|
[Difficulty]
|
||||||
|
CircleSize:14
|
10
osu.Game.Tests/Resources/out-of-range-difficulties.osu
Normal file
10
osu.Game.Tests/Resources/out-of-range-difficulties.osu
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[General]
|
||||||
|
Mode: 0
|
||||||
|
|
||||||
|
[Difficulty]
|
||||||
|
HPDrainRate:25
|
||||||
|
CircleSize:25
|
||||||
|
OverallDifficulty:25
|
||||||
|
ApproachRate:30
|
||||||
|
SliderMultiplier:30
|
||||||
|
SliderTickRate:30
|
@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
|||||||
var beatmapSet = CreateAPIBeatmapSet(Ruleset.Value);
|
var beatmapSet = CreateAPIBeatmapSet(Ruleset.Value);
|
||||||
beatmapSet.OnlineID = 241526; // ID hardcoded to ensure that the preview track exists online.
|
beatmapSet.OnlineID = 241526; // ID hardcoded to ensure that the preview track exists online.
|
||||||
|
|
||||||
Child = thumbnail = new BeatmapCardThumbnail(beatmapSet)
|
Child = thumbnail = new BeatmapCardThumbnail(beatmapSet, beatmapSet)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -10,9 +10,11 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Allocation;
|
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.Graphics.Primitives;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -26,9 +28,13 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
[Cached(typeof(SelectionRotationHandler))]
|
[Cached(typeof(SelectionRotationHandler))]
|
||||||
private TestSelectionRotationHandler rotationHandler;
|
private TestSelectionRotationHandler rotationHandler;
|
||||||
|
|
||||||
|
[Cached(typeof(SelectionScaleHandler))]
|
||||||
|
private TestSelectionScaleHandler scaleHandler;
|
||||||
|
|
||||||
public TestSceneComposeSelectBox()
|
public TestSceneComposeSelectBox()
|
||||||
{
|
{
|
||||||
rotationHandler = new TestSelectionRotationHandler(() => selectionArea);
|
rotationHandler = new TestSelectionRotationHandler(() => selectionArea);
|
||||||
|
scaleHandler = new TestSelectionScaleHandler(() => selectionArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
@ -45,13 +51,8 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
|
||||||
CanScaleX = true,
|
|
||||||
CanScaleY = true,
|
|
||||||
CanScaleDiagonally = true,
|
|
||||||
CanFlipX = true,
|
CanFlipX = true,
|
||||||
CanFlipY = true,
|
CanFlipY = true,
|
||||||
|
|
||||||
OnScale = handleScale
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -60,27 +61,6 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
InputManager.ReleaseButton(MouseButton.Left);
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
private bool handleScale(Vector2 amount, Anchor reference)
|
|
||||||
{
|
|
||||||
if ((reference & Anchor.y1) == 0)
|
|
||||||
{
|
|
||||||
int directionY = (reference & Anchor.y0) > 0 ? -1 : 1;
|
|
||||||
if (directionY < 0)
|
|
||||||
selectionArea.Y += amount.Y;
|
|
||||||
selectionArea.Height += directionY * amount.Y;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((reference & Anchor.x1) == 0)
|
|
||||||
{
|
|
||||||
int directionX = (reference & Anchor.x0) > 0 ? -1 : 1;
|
|
||||||
if (directionX < 0)
|
|
||||||
selectionArea.X += amount.X;
|
|
||||||
selectionArea.Width += directionX * amount.X;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private partial class TestSelectionRotationHandler : SelectionRotationHandler
|
private partial class TestSelectionRotationHandler : SelectionRotationHandler
|
||||||
{
|
{
|
||||||
private readonly Func<Container> getTargetContainer;
|
private readonly Func<Container> getTargetContainer;
|
||||||
@ -125,6 +105,51 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private partial class TestSelectionScaleHandler : SelectionScaleHandler
|
||||||
|
{
|
||||||
|
private readonly Func<Container> getTargetContainer;
|
||||||
|
|
||||||
|
public TestSelectionScaleHandler(Func<Container> getTargetContainer)
|
||||||
|
{
|
||||||
|
this.getTargetContainer = getTargetContainer;
|
||||||
|
|
||||||
|
CanScaleX.Value = true;
|
||||||
|
CanScaleY.Value = true;
|
||||||
|
CanScaleDiagonally.Value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private Container targetContainer;
|
||||||
|
|
||||||
|
public override void Begin()
|
||||||
|
{
|
||||||
|
if (targetContainer != null)
|
||||||
|
throw new InvalidOperationException($"Cannot {nameof(Begin)} a scale operation while another is in progress!");
|
||||||
|
|
||||||
|
targetContainer = getTargetContainer();
|
||||||
|
OriginalSurroundingQuad = new Quad(targetContainer!.X, targetContainer.Y, targetContainer.Width, targetContainer.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both)
|
||||||
|
{
|
||||||
|
if (targetContainer == null)
|
||||||
|
throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!");
|
||||||
|
|
||||||
|
Vector2 actualOrigin = origin ?? Vector2.Zero;
|
||||||
|
|
||||||
|
targetContainer.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, OriginalSurroundingQuad!.Value.TopLeft);
|
||||||
|
targetContainer.Size = OriginalSurroundingQuad!.Value.Size * scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Commit()
|
||||||
|
{
|
||||||
|
if (targetContainer == null)
|
||||||
|
throw new InvalidOperationException($"Cannot {nameof(Commit)} a scale operation without calling {nameof(Begin)} first!");
|
||||||
|
|
||||||
|
targetContainer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestRotationHandleShownOnHover()
|
public void TestRotationHandleShownOnHover()
|
||||||
{
|
{
|
||||||
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -396,7 +397,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
textBox.Current.Value = bank;
|
textBox.Current.Value = bank;
|
||||||
// force a commit via keyboard.
|
// force a commit via keyboard.
|
||||||
// this is needed when testing attempting to set empty bank - which should revert to the previous value, but only on commit.
|
// this is needed when testing attempting to set empty bank - which should revert to the previous value, but only on commit.
|
||||||
InputManager.ChangeFocus(textBox);
|
((IFocusManager)InputManager).ChangeFocus(textBox);
|
||||||
InputManager.Key(Key.Enter);
|
InputManager.Key(Key.Enter);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps.Timing;
|
using osu.Game.Beatmaps.Timing;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -62,12 +63,12 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
createLabelledTimeSignature(TimeSignature.SimpleQuadruple);
|
createLabelledTimeSignature(TimeSignature.SimpleQuadruple);
|
||||||
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
|
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
|
||||||
|
|
||||||
AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox));
|
AddStep("focus text box", () => ((IFocusManager)InputManager).ChangeFocus(numeratorTextBox));
|
||||||
|
|
||||||
AddStep("set numerator to 7", () => numeratorTextBox.Current.Value = "7");
|
AddStep("set numerator to 7", () => numeratorTextBox.Current.Value = "7");
|
||||||
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
|
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
|
||||||
|
|
||||||
AddStep("drop focus", () => InputManager.ChangeFocus(null));
|
AddStep("drop focus", () => ((IFocusManager)InputManager).ChangeFocus(null));
|
||||||
AddAssert("current is 7/4", () => timeSignature.Current.Value.Equals(new TimeSignature(7)));
|
AddAssert("current is 7/4", () => timeSignature.Current.Value.Equals(new TimeSignature(7)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,12 +78,12 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
createLabelledTimeSignature(TimeSignature.SimpleQuadruple);
|
createLabelledTimeSignature(TimeSignature.SimpleQuadruple);
|
||||||
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
|
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
|
||||||
|
|
||||||
AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox));
|
AddStep("focus text box", () => ((IFocusManager)InputManager).ChangeFocus(numeratorTextBox));
|
||||||
|
|
||||||
AddStep("set numerator to 0", () => numeratorTextBox.Current.Value = "0");
|
AddStep("set numerator to 0", () => numeratorTextBox.Current.Value = "0");
|
||||||
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
|
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
|
||||||
|
|
||||||
AddStep("drop focus", () => InputManager.ChangeFocus(null));
|
AddStep("drop focus", () => ((IFocusManager)InputManager).ChangeFocus(null));
|
||||||
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
|
AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
|
||||||
AddAssert("numerator is 4", () => numeratorTextBox.Current.Value == "4");
|
AddAssert("numerator is 4", () => numeratorTextBox.Current.Value == "4");
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,22 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Setup;
|
using osu.Game.Screens.Edit.Setup;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Editing
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
{
|
{
|
||||||
public partial class TestSceneMetadataSection : OsuTestScene
|
public partial class TestSceneMetadataSection : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
[Cached]
|
[Cached]
|
||||||
private EditorBeatmap editorBeatmap = new EditorBeatmap(new Beatmap
|
private EditorBeatmap editorBeatmap = new EditorBeatmap(new Beatmap
|
||||||
@ -26,6 +31,81 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
private TestMetadataSection metadataSection;
|
private TestMetadataSection metadataSection;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUpdateViaTextBoxOnFocusLoss()
|
||||||
|
{
|
||||||
|
AddStep("set metadata", () =>
|
||||||
|
{
|
||||||
|
editorBeatmap.Metadata.Artist = "Example Artist";
|
||||||
|
editorBeatmap.Metadata.ArtistUnicode = string.Empty;
|
||||||
|
});
|
||||||
|
|
||||||
|
createSection();
|
||||||
|
|
||||||
|
TextBox textbox;
|
||||||
|
|
||||||
|
AddStep("focus first textbox", () =>
|
||||||
|
{
|
||||||
|
textbox = metadataSection.ChildrenOfType<TextBox>().First();
|
||||||
|
InputManager.MoveMouseTo(textbox);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("simulate changing textbox", () =>
|
||||||
|
{
|
||||||
|
// Can't simulate text input but this should work.
|
||||||
|
InputManager.Keys(PlatformAction.SelectAll);
|
||||||
|
InputManager.Keys(PlatformAction.Copy);
|
||||||
|
InputManager.Keys(PlatformAction.Paste);
|
||||||
|
InputManager.Keys(PlatformAction.Paste);
|
||||||
|
});
|
||||||
|
|
||||||
|
assertArtistMetadata("Example Artist");
|
||||||
|
|
||||||
|
// It's important values are committed immediately on focus loss so the editor exit sequence detects them.
|
||||||
|
AddAssert("value immediately changed on focus loss", () =>
|
||||||
|
{
|
||||||
|
((IFocusManager)InputManager).TriggerFocusContention(metadataSection);
|
||||||
|
return editorBeatmap.Metadata.Artist;
|
||||||
|
}, () => Is.EqualTo("Example ArtistExample Artist"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUpdateViaTextBoxOnCommit()
|
||||||
|
{
|
||||||
|
AddStep("set metadata", () =>
|
||||||
|
{
|
||||||
|
editorBeatmap.Metadata.Artist = "Example Artist";
|
||||||
|
editorBeatmap.Metadata.ArtistUnicode = string.Empty;
|
||||||
|
});
|
||||||
|
|
||||||
|
createSection();
|
||||||
|
|
||||||
|
TextBox textbox;
|
||||||
|
|
||||||
|
AddStep("focus first textbox", () =>
|
||||||
|
{
|
||||||
|
textbox = metadataSection.ChildrenOfType<TextBox>().First();
|
||||||
|
InputManager.MoveMouseTo(textbox);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("simulate changing textbox", () =>
|
||||||
|
{
|
||||||
|
// Can't simulate text input but this should work.
|
||||||
|
InputManager.Keys(PlatformAction.SelectAll);
|
||||||
|
InputManager.Keys(PlatformAction.Copy);
|
||||||
|
InputManager.Keys(PlatformAction.Paste);
|
||||||
|
InputManager.Keys(PlatformAction.Paste);
|
||||||
|
});
|
||||||
|
|
||||||
|
assertArtistMetadata("Example Artist");
|
||||||
|
|
||||||
|
AddStep("commit", () => InputManager.Key(Key.Enter));
|
||||||
|
|
||||||
|
assertArtistMetadata("Example ArtistExample Artist");
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestMinimalMetadata()
|
public void TestMinimalMetadata()
|
||||||
{
|
{
|
||||||
@ -40,7 +120,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
createSection();
|
createSection();
|
||||||
|
|
||||||
assertArtist("Example Artist");
|
assertArtistTextBox("Example Artist");
|
||||||
assertRomanisedArtist("Example Artist", false);
|
assertRomanisedArtist("Example Artist", false);
|
||||||
|
|
||||||
assertTitle("Example Title");
|
assertTitle("Example Title");
|
||||||
@ -61,7 +141,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
createSection();
|
createSection();
|
||||||
|
|
||||||
assertArtist("*なみりん");
|
assertArtistTextBox("*なみりん");
|
||||||
assertRomanisedArtist(string.Empty, true);
|
assertRomanisedArtist(string.Empty, true);
|
||||||
|
|
||||||
assertTitle("コイシテイク・プラネット");
|
assertTitle("コイシテイク・プラネット");
|
||||||
@ -82,7 +162,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
createSection();
|
createSection();
|
||||||
|
|
||||||
assertArtist("*なみりん");
|
assertArtistTextBox("*なみりん");
|
||||||
assertRomanisedArtist("*namirin", true);
|
assertRomanisedArtist("*namirin", true);
|
||||||
|
|
||||||
assertTitle("コイシテイク・プラネット");
|
assertTitle("コイシテイク・プラネット");
|
||||||
@ -104,11 +184,11 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
createSection();
|
createSection();
|
||||||
|
|
||||||
AddStep("set romanised artist name", () => metadataSection.ArtistTextBox.Current.Value = "*namirin");
|
AddStep("set romanised artist name", () => metadataSection.ArtistTextBox.Current.Value = "*namirin");
|
||||||
assertArtist("*namirin");
|
assertArtistTextBox("*namirin");
|
||||||
assertRomanisedArtist("*namirin", false);
|
assertRomanisedArtist("*namirin", false);
|
||||||
|
|
||||||
AddStep("set native artist name", () => metadataSection.ArtistTextBox.Current.Value = "*なみりん");
|
AddStep("set native artist name", () => metadataSection.ArtistTextBox.Current.Value = "*なみりん");
|
||||||
assertArtist("*なみりん");
|
assertArtistTextBox("*なみりん");
|
||||||
assertRomanisedArtist("*namirin", true);
|
assertRomanisedArtist("*namirin", true);
|
||||||
|
|
||||||
AddStep("set romanised title", () => metadataSection.TitleTextBox.Current.Value = "Hitokoto no kyori");
|
AddStep("set romanised title", () => metadataSection.TitleTextBox.Current.Value = "Hitokoto no kyori");
|
||||||
@ -123,21 +203,24 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
private void createSection()
|
private void createSection()
|
||||||
=> AddStep("create metadata section", () => Child = metadataSection = new TestMetadataSection());
|
=> AddStep("create metadata section", () => Child = metadataSection = new TestMetadataSection());
|
||||||
|
|
||||||
private void assertArtist(string expected)
|
private void assertArtistMetadata(string expected)
|
||||||
=> AddAssert($"artist is {expected}", () => metadataSection.ArtistTextBox.Current.Value == expected);
|
=> AddAssert($"artist metadata is {expected}", () => editorBeatmap.Metadata.Artist, () => Is.EqualTo(expected));
|
||||||
|
|
||||||
|
private void assertArtistTextBox(string expected)
|
||||||
|
=> AddAssert($"artist textbox is {expected}", () => metadataSection.ArtistTextBox.Current.Value, () => Is.EqualTo(expected));
|
||||||
|
|
||||||
private void assertRomanisedArtist(string expected, bool editable)
|
private void assertRomanisedArtist(string expected, bool editable)
|
||||||
{
|
{
|
||||||
AddAssert($"romanised artist is {expected}", () => metadataSection.RomanisedArtistTextBox.Current.Value == expected);
|
AddAssert($"romanised artist is {expected}", () => metadataSection.RomanisedArtistTextBox.Current.Value, () => Is.EqualTo(expected));
|
||||||
AddAssert($"romanised artist is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedArtistTextBox.ReadOnly == !editable);
|
AddAssert($"romanised artist is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedArtistTextBox.ReadOnly == !editable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertTitle(string expected)
|
private void assertTitle(string expected)
|
||||||
=> AddAssert($"title is {expected}", () => metadataSection.TitleTextBox.Current.Value == expected);
|
=> AddAssert($"title is {expected}", () => metadataSection.TitleTextBox.Current.Value, () => Is.EqualTo(expected));
|
||||||
|
|
||||||
private void assertRomanisedTitle(string expected, bool editable)
|
private void assertRomanisedTitle(string expected, bool editable)
|
||||||
{
|
{
|
||||||
AddAssert($"romanised title is {expected}", () => metadataSection.RomanisedTitleTextBox.Current.Value == expected);
|
AddAssert($"romanised title is {expected}", () => metadataSection.RomanisedTitleTextBox.Current.Value, () => Is.EqualTo(expected));
|
||||||
AddAssert($"romanised title is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedTitleTextBox.ReadOnly == !editable);
|
AddAssert($"romanised title is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedTitleTextBox.ReadOnly == !editable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ using osu.Framework.Platform;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Drawables;
|
using osu.Game.Beatmaps.Drawables;
|
||||||
|
using osu.Game.Beatmaps.Drawables.Cards;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Cursor;
|
using osu.Game.Graphics.Cursor;
|
||||||
@ -317,13 +318,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
p.RequestResults = _ => resultsRequested = true;
|
p.RequestResults = _ => resultsRequested = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for load", () => playlist.ChildrenOfType<DrawableLinkCompiler>().Any() && playlist.ChildrenOfType<BeatmapCardThumbnail>().First().DrawWidth > 0);
|
||||||
AddStep("move mouse to first item title", () =>
|
AddStep("move mouse to first item title", () =>
|
||||||
{
|
{
|
||||||
var drawQuad = playlist.ChildrenOfType<LinkFlowContainer>().First().ScreenSpaceDrawQuad;
|
var drawQuad = playlist.ChildrenOfType<LinkFlowContainer>().First().ScreenSpaceDrawQuad;
|
||||||
var location = (drawQuad.TopLeft + drawQuad.BottomLeft) / 2 + new Vector2(drawQuad.Width * 0.2f, 0);
|
var location = (drawQuad.TopLeft + drawQuad.BottomLeft) / 2 + new Vector2(drawQuad.Width * 0.2f, 0);
|
||||||
InputManager.MoveMouseTo(location);
|
InputManager.MoveMouseTo(location);
|
||||||
});
|
});
|
||||||
AddUntilStep("wait for text load", () => playlist.ChildrenOfType<DrawableLinkCompiler>().Any());
|
|
||||||
AddAssert("first item title not hovered", () => playlist.ChildrenOfType<DrawableLinkCompiler>().First().IsHovered, () => Is.False);
|
AddAssert("first item title not hovered", () => playlist.ChildrenOfType<DrawableLinkCompiler>().First().IsHovered, () => Is.False);
|
||||||
AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
|
AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
|
||||||
AddUntilStep("first item selected", () => playlist.ChildrenOfType<DrawableRoomPlaylistItem>().First().IsSelectedItem, () => Is.True);
|
AddUntilStep("first item selected", () => playlist.ChildrenOfType<DrawableRoomPlaylistItem>().First().IsSelectedItem, () => Is.True);
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Extensions;
|
|||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -17,6 +18,7 @@ using osu.Game.Rulesets.Mania;
|
|||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.GameplayTest;
|
using osu.Game.Screens.Edit.GameplayTest;
|
||||||
|
using osu.Game.Screens.Edit.Setup;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osu.Game.Screens.Select.Filter;
|
using osu.Game.Screens.Select.Filter;
|
||||||
@ -27,6 +29,59 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
{
|
{
|
||||||
public partial class TestSceneBeatmapEditorNavigation : OsuGameTestScene
|
public partial class TestSceneBeatmapEditorNavigation : OsuGameTestScene
|
||||||
{
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestChangeMetadataExitWhileTextboxFocusedPromptsSave()
|
||||||
|
{
|
||||||
|
BeatmapSetInfo beatmapSet = null!;
|
||||||
|
|
||||||
|
AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely());
|
||||||
|
AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach());
|
||||||
|
|
||||||
|
AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet));
|
||||||
|
AddUntilStep("wait for song select",
|
||||||
|
() => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
||||||
|
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
||||||
|
&& songSelect.IsLoaded);
|
||||||
|
AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo);
|
||||||
|
|
||||||
|
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
||||||
|
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
||||||
|
|
||||||
|
AddStep("change to song setup", () => InputManager.Key(Key.F4));
|
||||||
|
|
||||||
|
TextBox textbox = null!;
|
||||||
|
|
||||||
|
AddUntilStep("wait for metadata section", () =>
|
||||||
|
{
|
||||||
|
var t = Game.ChildrenOfType<MetadataSection>().SingleOrDefault().ChildrenOfType<TextBox>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (t == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
textbox = t;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("focus textbox", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(textbox);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("simulate changing textbox", () =>
|
||||||
|
{
|
||||||
|
// Can't simulate text input but this should work.
|
||||||
|
InputManager.Keys(PlatformAction.SelectAll);
|
||||||
|
InputManager.Keys(PlatformAction.Copy);
|
||||||
|
InputManager.Keys(PlatformAction.Paste);
|
||||||
|
InputManager.Keys(PlatformAction.Paste);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("exit", () => Game.ChildrenOfType<Editor>().Single().Exit());
|
||||||
|
|
||||||
|
AddUntilStep("save dialog displayed", () => Game.ChildrenOfType<DialogOverlay>().SingleOrDefault()?.CurrentDialog is PromptForSaveDialog);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestEditorGameplayTestAlwaysUsesOriginalRuleset()
|
public void TestEditorGameplayTestAlwaysUsesOriginalRuleset()
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -26,7 +24,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
{
|
{
|
||||||
public partial class TestScenePresentScore : OsuGameTestScene
|
public partial class TestScenePresentScore : OsuGameTestScene
|
||||||
{
|
{
|
||||||
private BeatmapSetInfo beatmap;
|
private BeatmapSetInfo beatmap = null!;
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public new void SetUpSteps()
|
public new void SetUpSteps()
|
||||||
@ -64,7 +62,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
Ruleset = new OsuRuleset().RulesetInfo
|
Ruleset = new OsuRuleset().RulesetInfo
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})?.Value;
|
})!.Value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +156,27 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
presentAndConfirm(secondImport, type);
|
presentAndConfirm(secondImport, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScoreRefetchIgnoresEmptyHash()
|
||||||
|
{
|
||||||
|
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo?.Invoke());
|
||||||
|
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||||
|
|
||||||
|
importScore(-1, hash: string.Empty);
|
||||||
|
importScore(3, hash: @"deadbeef");
|
||||||
|
|
||||||
|
// oftentimes a `PresentScore()` call will be given a `ScoreInfo` which is converted from an online score,
|
||||||
|
// in which cases the hash will generally not be available.
|
||||||
|
AddStep("present score", () => Game.PresentScore(new ScoreInfo { OnlineID = 3, Hash = string.Empty }));
|
||||||
|
|
||||||
|
AddUntilStep("wait for results", () => lastWaitedScreen != Game.ScreenStack.CurrentScreen && Game.ScreenStack.CurrentScreen is ResultsScreen);
|
||||||
|
AddUntilStep("correct score displayed", () =>
|
||||||
|
{
|
||||||
|
var score = ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score!;
|
||||||
|
return score.OnlineID == 3 && score.Hash == "deadbeef";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void returnToMenu()
|
private void returnToMenu()
|
||||||
{
|
{
|
||||||
// if we don't pause, there's a chance the track may change at the main menu out of our control (due to reaching the end of the track).
|
// if we don't pause, there's a chance the track may change at the main menu out of our control (due to reaching the end of the track).
|
||||||
@ -171,14 +190,14 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
AddUntilStep("wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
AddUntilStep("wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Func<ScoreInfo> importScore(int i, RulesetInfo ruleset = null)
|
private Func<ScoreInfo> importScore(int i, RulesetInfo? ruleset = null, string? hash = null)
|
||||||
{
|
{
|
||||||
ScoreInfo imported = null;
|
ScoreInfo? imported = null;
|
||||||
AddStep($"import score {i}", () =>
|
AddStep($"import score {i}", () =>
|
||||||
{
|
{
|
||||||
imported = Game.ScoreManager.Import(new ScoreInfo
|
imported = Game.ScoreManager.Import(new ScoreInfo
|
||||||
{
|
{
|
||||||
Hash = Guid.NewGuid().ToString(),
|
Hash = hash ?? Guid.NewGuid().ToString(),
|
||||||
OnlineID = i,
|
OnlineID = i,
|
||||||
BeatmapInfo = beatmap.Beatmaps.First(),
|
BeatmapInfo = beatmap.Beatmaps.First(),
|
||||||
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo,
|
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo,
|
||||||
@ -188,14 +207,14 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
AddAssert($"import {i} succeeded", () => imported != null);
|
AddAssert($"import {i} succeeded", () => imported != null);
|
||||||
|
|
||||||
return () => imported;
|
return () => imported!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Some tests test waiting for a particular screen twice in a row, but expect a new instance each time.
|
/// Some tests test waiting for a particular screen twice in a row, but expect a new instance each time.
|
||||||
/// There's a case where they may succeed incorrectly if we don't compare against the previous instance.
|
/// There's a case where they may succeed incorrectly if we don't compare against the previous instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private IScreen lastWaitedScreen;
|
private IScreen lastWaitedScreen = null!;
|
||||||
|
|
||||||
private void presentAndConfirm(Func<ScoreInfo> getImport, ScorePresentType type)
|
private void presentAndConfirm(Func<ScoreInfo> getImport, ScorePresentType type)
|
||||||
{
|
{
|
||||||
|
@ -87,6 +87,105 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddStep("delete all beatmaps", () => manager.Delete());
|
AddStep("delete all beatmaps", () => manager.Delete());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSpeedChange()
|
||||||
|
{
|
||||||
|
createSongSelect();
|
||||||
|
changeMods();
|
||||||
|
|
||||||
|
decreaseModSpeed();
|
||||||
|
AddAssert("half time activated at 0.95x", () => songSelect!.Mods.Value.OfType<ModHalfTime>().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005));
|
||||||
|
|
||||||
|
decreaseModSpeed();
|
||||||
|
AddAssert("half time speed changed to 0.9x", () => songSelect!.Mods.Value.OfType<ModHalfTime>().Single().SpeedChange.Value, () => Is.EqualTo(0.9).Within(0.005));
|
||||||
|
|
||||||
|
increaseModSpeed();
|
||||||
|
AddAssert("half time speed changed to 0.95x", () => songSelect!.Mods.Value.OfType<ModHalfTime>().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005));
|
||||||
|
|
||||||
|
increaseModSpeed();
|
||||||
|
AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0);
|
||||||
|
|
||||||
|
increaseModSpeed();
|
||||||
|
AddAssert("double time activated at 1.05x", () => songSelect!.Mods.Value.OfType<ModDoubleTime>().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005));
|
||||||
|
|
||||||
|
increaseModSpeed();
|
||||||
|
AddAssert("double time speed changed to 1.1x", () => songSelect!.Mods.Value.OfType<ModDoubleTime>().Single().SpeedChange.Value, () => Is.EqualTo(1.1).Within(0.005));
|
||||||
|
|
||||||
|
decreaseModSpeed();
|
||||||
|
AddAssert("double time speed changed to 1.05x", () => songSelect!.Mods.Value.OfType<ModDoubleTime>().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005));
|
||||||
|
|
||||||
|
OsuModNightcore nc = new OsuModNightcore
|
||||||
|
{
|
||||||
|
SpeedChange = { Value = 1.05 }
|
||||||
|
};
|
||||||
|
changeMods(nc);
|
||||||
|
|
||||||
|
increaseModSpeed();
|
||||||
|
AddAssert("nightcore speed changed to 1.1x", () => songSelect!.Mods.Value.OfType<ModNightcore>().Single().SpeedChange.Value, () => Is.EqualTo(1.1).Within(0.005));
|
||||||
|
|
||||||
|
decreaseModSpeed();
|
||||||
|
AddAssert("nightcore speed changed to 1.05x", () => songSelect!.Mods.Value.OfType<ModNightcore>().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005));
|
||||||
|
|
||||||
|
decreaseModSpeed();
|
||||||
|
AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0);
|
||||||
|
|
||||||
|
decreaseModSpeed();
|
||||||
|
AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType<ModDaycore>().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005));
|
||||||
|
|
||||||
|
decreaseModSpeed();
|
||||||
|
AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType<ModDaycore>().Single().SpeedChange.Value, () => Is.EqualTo(0.9).Within(0.005));
|
||||||
|
|
||||||
|
increaseModSpeed();
|
||||||
|
AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType<ModDaycore>().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005));
|
||||||
|
|
||||||
|
OsuModDoubleTime dt = new OsuModDoubleTime
|
||||||
|
{
|
||||||
|
SpeedChange = { Value = 1.02 },
|
||||||
|
AdjustPitch = { Value = true },
|
||||||
|
};
|
||||||
|
changeMods(dt);
|
||||||
|
|
||||||
|
decreaseModSpeed();
|
||||||
|
AddAssert("half time activated at 0.97x", () => songSelect!.Mods.Value.OfType<ModHalfTime>().Single().SpeedChange.Value, () => Is.EqualTo(0.97).Within(0.005));
|
||||||
|
AddAssert("adjust pitch preserved", () => songSelect!.Mods.Value.OfType<ModHalfTime>().Single().AdjustPitch.Value, () => Is.True);
|
||||||
|
|
||||||
|
OsuModHalfTime ht = new OsuModHalfTime
|
||||||
|
{
|
||||||
|
SpeedChange = { Value = 0.97 },
|
||||||
|
AdjustPitch = { Value = true },
|
||||||
|
};
|
||||||
|
Mod[] modlist = { ht, new OsuModHardRock(), new OsuModHidden() };
|
||||||
|
changeMods(modlist);
|
||||||
|
|
||||||
|
increaseModSpeed();
|
||||||
|
AddAssert("double time activated at 1.02x", () => songSelect!.Mods.Value.OfType<ModDoubleTime>().Single().SpeedChange.Value, () => Is.EqualTo(1.02).Within(0.005));
|
||||||
|
AddAssert("double time activated at 1.02x", () => songSelect!.Mods.Value.OfType<ModDoubleTime>().Single().AdjustPitch.Value, () => Is.True);
|
||||||
|
AddAssert("HD still enabled", () => songSelect!.Mods.Value.OfType<ModHidden>().SingleOrDefault(), () => Is.Not.Null);
|
||||||
|
AddAssert("HR still enabled", () => songSelect!.Mods.Value.OfType<ModHardRock>().SingleOrDefault(), () => Is.Not.Null);
|
||||||
|
|
||||||
|
changeMods(new ModWindUp());
|
||||||
|
increaseModSpeed();
|
||||||
|
AddAssert("windup still active", () => songSelect!.Mods.Value.First() is ModWindUp);
|
||||||
|
|
||||||
|
changeMods(new ModAdaptiveSpeed());
|
||||||
|
increaseModSpeed();
|
||||||
|
AddAssert("adaptive speed still active", () => songSelect!.Mods.Value.First() is ModAdaptiveSpeed);
|
||||||
|
|
||||||
|
void increaseModSpeed() => AddStep("increase mod speed", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Key(Key.Up);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
void decreaseModSpeed() => AddStep("decrease mod speed", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Key(Key.Down);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPlaceholderBeatmapPresence()
|
public void TestPlaceholderBeatmapPresence()
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
// 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.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.Metadata;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Screens.Menu;
|
||||||
|
using osuTK.Input;
|
||||||
|
using Color4 = osuTK.Graphics.Color4;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public partial class TestSceneMainMenuButton : OsuTestScene
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private MetadataClient metadataClient { get; set; } = null!;
|
||||||
|
|
||||||
|
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStandardButton()
|
||||||
|
{
|
||||||
|
AddStep("add button", () => Child = new MainMenuButton(
|
||||||
|
ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), _ => { }, 0, Key.P)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
ButtonSystemState = ButtonSystemState.TopLevel,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDailyChallengeButton()
|
||||||
|
{
|
||||||
|
AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null));
|
||||||
|
|
||||||
|
AddStep("set up API", () => dummyAPI.HandleRequest = req =>
|
||||||
|
{
|
||||||
|
switch (req)
|
||||||
|
{
|
||||||
|
case GetRoomRequest getRoomRequest:
|
||||||
|
if (getRoomRequest.RoomId != 1234)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var beatmap = CreateAPIBeatmap();
|
||||||
|
beatmap.OnlineID = 1001;
|
||||||
|
getRoomRequest.TriggerSuccess(new Room
|
||||||
|
{
|
||||||
|
RoomID = { Value = 1234 },
|
||||||
|
Playlist =
|
||||||
|
{
|
||||||
|
new PlaylistItem(beatmap)
|
||||||
|
},
|
||||||
|
EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) }
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("add button", () => Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
ButtonSystemState = ButtonSystemState.TopLevel,
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo
|
||||||
|
{
|
||||||
|
RoomID = 1234,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Extensions.ObjectExtensions;
|
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.Input;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
@ -623,7 +624,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
||||||
AddAssert("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus);
|
AddAssert("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus);
|
||||||
|
|
||||||
AddStep("unfocus search text box externally", () => InputManager.ChangeFocus(null));
|
AddStep("unfocus search text box externally", () => ((IFocusManager)InputManager).ChangeFocus(null));
|
||||||
|
|
||||||
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
||||||
AddAssert("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus);
|
AddAssert("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus);
|
||||||
|
@ -15,15 +15,16 @@ using osu.Game.Overlays.Mods;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Screens.Select.FooterV2;
|
using osu.Game.Screens.Footer;
|
||||||
|
using osu.Game.Screens.SelectV2.Footer;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.SongSelect
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
public partial class TestSceneSongSelectFooterV2 : OsuManualInputManagerTestScene
|
public partial class TestSceneScreenFooter : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private FooterButtonRandomV2 randomButton = null!;
|
private ScreenFooterButtonRandom randomButton = null!;
|
||||||
private FooterButtonModsV2 modsButton = null!;
|
private ScreenFooterButtonMods modsButton = null!;
|
||||||
|
|
||||||
private bool nextRandomCalled;
|
private bool nextRandomCalled;
|
||||||
private bool previousRandomCalled;
|
private bool previousRandomCalled;
|
||||||
@ -39,25 +40,25 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
nextRandomCalled = false;
|
nextRandomCalled = false;
|
||||||
previousRandomCalled = false;
|
previousRandomCalled = false;
|
||||||
|
|
||||||
FooterV2 footer;
|
ScreenFooter footer;
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new PopoverContainer
|
new PopoverContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = footer = new FooterV2(),
|
Child = footer = new ScreenFooter(),
|
||||||
},
|
},
|
||||||
overlay = new DummyOverlay()
|
overlay = new DummyOverlay()
|
||||||
};
|
};
|
||||||
|
|
||||||
footer.AddButton(modsButton = new FooterButtonModsV2 { Current = SelectedMods }, overlay);
|
footer.AddButton(modsButton = new ScreenFooterButtonMods { Current = SelectedMods }, overlay);
|
||||||
footer.AddButton(randomButton = new FooterButtonRandomV2
|
footer.AddButton(randomButton = new ScreenFooterButtonRandom
|
||||||
{
|
{
|
||||||
NextRandom = () => nextRandomCalled = true,
|
NextRandom = () => nextRandomCalled = true,
|
||||||
PreviousRandom = () => previousRandomCalled = true
|
PreviousRandom = () => previousRandomCalled = true
|
||||||
});
|
});
|
||||||
footer.AddButton(new FooterButtonOptionsV2());
|
footer.AddButton(new ScreenFooterButtonOptions());
|
||||||
|
|
||||||
overlay.Hide();
|
overlay.Hide();
|
||||||
});
|
});
|
||||||
@ -98,7 +99,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
AddStep("enable options", () =>
|
AddStep("enable options", () =>
|
||||||
{
|
{
|
||||||
var optionsButton = this.ChildrenOfType<FooterButtonV2>().Last();
|
var optionsButton = this.ChildrenOfType<ScreenFooterButton>().Last();
|
||||||
|
|
||||||
optionsButton.Enabled.Value = true;
|
optionsButton.Enabled.Value = true;
|
||||||
optionsButton.TriggerClick();
|
optionsButton.TriggerClick();
|
||||||
@ -108,7 +109,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestState()
|
public void TestState()
|
||||||
{
|
{
|
||||||
AddToggleStep("set options enabled state", state => this.ChildrenOfType<FooterButtonV2>().Last().Enabled.Value = state);
|
AddToggleStep("set options enabled state", state => this.ChildrenOfType<ScreenFooterButton>().Last().Enabled.Value = state);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
@ -12,21 +12,21 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Screens.Select.FooterV2;
|
using osu.Game.Screens.SelectV2.Footer;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
public partial class TestSceneFooterButtonModsV2 : OsuTestScene
|
public partial class TestSceneScreenFooterButtonMods : OsuTestScene
|
||||||
{
|
{
|
||||||
private readonly TestFooterButtonModsV2 footerButtonMods;
|
private readonly TestScreenFooterButtonMods footerButtonMods;
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||||
|
|
||||||
public TestSceneFooterButtonModsV2()
|
public TestSceneScreenFooterButtonMods()
|
||||||
{
|
{
|
||||||
Add(footerButtonMods = new TestFooterButtonModsV2
|
Add(footerButtonMods = new TestScreenFooterButtonMods
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
@ -97,9 +97,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
public void TestUnrankedBadge()
|
public void TestUnrankedBadge()
|
||||||
{
|
{
|
||||||
AddStep(@"Add unranked mod", () => changeMods(new[] { new OsuModDeflate() }));
|
AddStep(@"Add unranked mod", () => changeMods(new[] { new OsuModDeflate() }));
|
||||||
AddUntilStep("Unranked badge shown", () => footerButtonMods.ChildrenOfType<FooterButtonModsV2.UnrankedBadge>().Single().Alpha == 1);
|
AddUntilStep("Unranked badge shown", () => footerButtonMods.ChildrenOfType<ScreenFooterButtonMods.UnrankedBadge>().Single().Alpha == 1);
|
||||||
AddStep(@"Clear selected mod", () => changeMods(Array.Empty<Mod>()));
|
AddStep(@"Clear selected mod", () => changeMods(Array.Empty<Mod>()));
|
||||||
AddUntilStep("Unranked badge not shown", () => footerButtonMods.ChildrenOfType<FooterButtonModsV2.UnrankedBadge>().Single().Alpha == 0);
|
AddUntilStep("Unranked badge not shown", () => footerButtonMods.ChildrenOfType<ScreenFooterButtonMods.UnrankedBadge>().Single().Alpha == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void changeMods(IReadOnlyList<Mod> mods) => footerButtonMods.Current.Value = mods;
|
private void changeMods(IReadOnlyList<Mod> mods) => footerButtonMods.Current.Value = mods;
|
||||||
@ -112,7 +112,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
return expectedValue == footerButtonMods.MultiplierText.Current.Value;
|
return expectedValue == footerButtonMods.MultiplierText.Current.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class TestFooterButtonModsV2 : FooterButtonModsV2
|
private partial class TestScreenFooterButtonMods : ScreenFooterButtonMods
|
||||||
{
|
{
|
||||||
public new OsuSpriteText MultiplierText => base.MultiplierText;
|
public new OsuSpriteText MultiplierText => base.MultiplierText;
|
||||||
}
|
}
|
@ -7,11 +7,13 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
@ -35,7 +37,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
if (bigButton)
|
if (bigButton)
|
||||||
{
|
{
|
||||||
Child = button = new ShearedButton(400)
|
Child = button = new ShearedButton(400, 80)
|
||||||
{
|
{
|
||||||
LighterColour = Colour4.FromHex("#FFFFFF"),
|
LighterColour = Colour4.FromHex("#FFFFFF"),
|
||||||
DarkerColour = Colour4.FromHex("#FFCC22"),
|
DarkerColour = Colour4.FromHex("#FFCC22"),
|
||||||
@ -44,13 +46,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Text = "Let's GO!",
|
Text = "Let's GO!",
|
||||||
Height = 80,
|
|
||||||
Action = () => actionFired = true,
|
Action = () => actionFired = true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Child = button = new ShearedButton(200)
|
Child = button = new ShearedButton(200, 80)
|
||||||
{
|
{
|
||||||
LighterColour = Colour4.FromHex("#FF86DD"),
|
LighterColour = Colour4.FromHex("#FF86DD"),
|
||||||
DarkerColour = Colour4.FromHex("#DE31AE"),
|
DarkerColour = Colour4.FromHex("#DE31AE"),
|
||||||
@ -58,7 +59,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Text = "Press me",
|
Text = "Press me",
|
||||||
Height = 80,
|
|
||||||
Action = () => actionFired = true,
|
Action = () => actionFired = true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -171,5 +171,48 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
void setToggleDisabledState(bool disabled) => AddStep($"{(disabled ? "disable" : "enable")} toggle", () => button.Active.Disabled = disabled);
|
void setToggleDisabledState(bool disabled) => AddStep($"{(disabled ? "disable" : "enable")} toggle", () => button.Active.Disabled = disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestButtons()
|
||||||
|
{
|
||||||
|
AddStep("create buttons", () => Children = new[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Scale = new Vector2(2.5f),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new ShearedButton(120)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Text = "Test",
|
||||||
|
Action = () => { },
|
||||||
|
Padding = new MarginPadding(),
|
||||||
|
},
|
||||||
|
new ShearedButton(120, 40)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Text = "Test",
|
||||||
|
Action = () => { },
|
||||||
|
Padding = new MarginPadding { Left = -1f },
|
||||||
|
},
|
||||||
|
new ShearedButton(120, 70)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Text = "Test",
|
||||||
|
Action = () => { },
|
||||||
|
Padding = new MarginPadding { Left = 3f },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
@ -42,7 +43,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
AddStep("set instantaneous to false", () => sliderWithTextBoxInput.Instantaneous = false);
|
AddStep("set instantaneous to false", () => sliderWithTextBoxInput.Instantaneous = false);
|
||||||
|
|
||||||
AddStep("focus textbox", () => InputManager.ChangeFocus(textBox));
|
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||||
AddStep("change text", () => textBox.Text = "3");
|
AddStep("change text", () => textBox.Text = "3");
|
||||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.Zero);
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.Zero);
|
||||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.Zero);
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.Zero);
|
||||||
@ -61,7 +62,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("textbox changed", () => textBox.Current.Value, () => Is.EqualTo("-5"));
|
AddAssert("textbox changed", () => textBox.Current.Value, () => Is.EqualTo("-5"));
|
||||||
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
|
||||||
AddStep("focus textbox", () => InputManager.ChangeFocus(textBox));
|
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||||
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
||||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
@ -71,12 +72,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
|
||||||
AddStep("focus textbox", () => InputManager.ChangeFocus(textBox));
|
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||||
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
||||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
|
||||||
AddStep("lose focus", () => InputManager.ChangeFocus(null));
|
AddStep("lose focus", () => ((IFocusManager)InputManager).ChangeFocus(null));
|
||||||
AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5"));
|
AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5"));
|
||||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
@ -87,7 +88,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
AddStep("set instantaneous to true", () => sliderWithTextBoxInput.Instantaneous = true);
|
AddStep("set instantaneous to true", () => sliderWithTextBoxInput.Instantaneous = true);
|
||||||
|
|
||||||
AddStep("focus textbox", () => InputManager.ChangeFocus(textBox));
|
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||||
AddStep("change text", () => textBox.Text = "3");
|
AddStep("change text", () => textBox.Text = "3");
|
||||||
AddAssert("slider moved", () => slider.Current.Value, () => Is.EqualTo(3));
|
AddAssert("slider moved", () => slider.Current.Value, () => Is.EqualTo(3));
|
||||||
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3));
|
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3));
|
||||||
@ -106,7 +107,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("textbox not changed", () => textBox.Current.Value, () => Is.EqualTo("-5"));
|
AddAssert("textbox not changed", () => textBox.Current.Value, () => Is.EqualTo("-5"));
|
||||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
|
||||||
AddStep("focus textbox", () => InputManager.ChangeFocus(textBox));
|
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||||
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
||||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
@ -116,12 +117,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
|
||||||
AddStep("focus textbox", () => InputManager.ChangeFocus(textBox));
|
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||||
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
||||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
|
||||||
AddStep("lose focus", () => InputManager.ChangeFocus(null));
|
AddStep("lose focus", () => ((IFocusManager)InputManager).ChangeFocus(null));
|
||||||
AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5"));
|
AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5"));
|
||||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
|
|||||||
editorInfo.Selected.ValueChanged += selection =>
|
editorInfo.Selected.ValueChanged += selection =>
|
||||||
{
|
{
|
||||||
// ensure any ongoing edits are committed out to the *current* selection before changing to a new one.
|
// ensure any ongoing edits are committed out to the *current* selection before changing to a new one.
|
||||||
GetContainingInputManager().TriggerFocusContention(null);
|
GetContainingFocusManager().TriggerFocusContention(null);
|
||||||
|
|
||||||
// Required to avoid cyclic failure in BindableWithCurrent (TriggerChange called during the Current_Set process).
|
// Required to avoid cyclic failure in BindableWithCurrent (TriggerChange called during the Current_Set process).
|
||||||
// Arguable a framework issue but since we haven't hit it anywhere else a local workaround seems best.
|
// Arguable a framework issue but since we haven't hit it anywhere else a local workaround seems best.
|
||||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
thumbnail = new BeatmapCardThumbnail(BeatmapSet)
|
thumbnail = new BeatmapCardThumbnail(BeatmapSet, BeatmapSet)
|
||||||
{
|
{
|
||||||
Name = @"Left (icon) area",
|
Name = @"Left (icon) area",
|
||||||
Size = new Vector2(height),
|
Size = new Vector2(height),
|
||||||
|
@ -62,7 +62,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
thumbnail = new BeatmapCardThumbnail(BeatmapSet)
|
thumbnail = new BeatmapCardThumbnail(BeatmapSet, BeatmapSet)
|
||||||
{
|
{
|
||||||
Name = @"Left (icon) area",
|
Name = @"Left (icon) area",
|
||||||
Size = new Vector2(height),
|
Size = new Vector2(height),
|
||||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Beatmaps.Drawables.Cards.Buttons;
|
using osu.Game.Beatmaps.Drawables.Cards.Buttons;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -36,14 +35,14 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
public BeatmapCardThumbnail(APIBeatmapSet beatmapSetInfo)
|
public BeatmapCardThumbnail(IBeatmapSetInfo beatmapSetInfo, IBeatmapSetOnlineInfo onlineInfo)
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List)
|
new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
OnlineInfo = beatmapSetInfo
|
OnlineInfo = onlineInfo
|
||||||
},
|
},
|
||||||
background = new Box
|
background = new Box
|
||||||
{
|
{
|
||||||
@ -62,7 +61,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(50),
|
|
||||||
InnerRadius = 0.2f
|
InnerRadius = 0.2f
|
||||||
},
|
},
|
||||||
content = new Container
|
content = new Container
|
||||||
@ -93,6 +91,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
progress.Progress = playButton.Progress.Value;
|
progress.Progress = playButton.Progress.Value;
|
||||||
|
|
||||||
|
playButton.Scale = new Vector2(DrawWidth / 100);
|
||||||
|
progress.Size = new Vector2(50 * DrawWidth / 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
|
@ -27,8 +27,17 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
set => base.Masking = value;
|
set => base.Masking = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType coverType = BeatmapSetCoverType.Cover)
|
protected override double LoadDelay { get; }
|
||||||
|
|
||||||
|
private readonly double timeBeforeUnload;
|
||||||
|
|
||||||
|
protected override double TransformDuration => 400;
|
||||||
|
|
||||||
|
public UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType coverType = BeatmapSetCoverType.Cover, double timeBeforeLoad = 500, double timeBeforeUnload = 1000)
|
||||||
{
|
{
|
||||||
|
LoadDelay = timeBeforeLoad;
|
||||||
|
this.timeBeforeUnload = timeBeforeUnload;
|
||||||
|
|
||||||
this.coverType = coverType;
|
this.coverType = coverType;
|
||||||
|
|
||||||
InternalChild = new Box
|
InternalChild = new Box
|
||||||
@ -38,12 +47,12 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double LoadDelay => 500;
|
|
||||||
|
|
||||||
protected override double TransformDuration => 400;
|
|
||||||
|
|
||||||
protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func<Drawable> createContentFunc, double timeBeforeLoad)
|
protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func<Drawable> createContentFunc, double timeBeforeLoad)
|
||||||
=> new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad);
|
=> new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad, timeBeforeUnload)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
};
|
||||||
|
|
||||||
protected override Drawable CreateDrawable(IBeatmapSetOnlineInfo model)
|
protected override Drawable CreateDrawable(IBeatmapSetOnlineInfo model)
|
||||||
{
|
{
|
||||||
|
@ -85,6 +85,8 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
base.ParseStreamInto(stream, beatmap);
|
base.ParseStreamInto(stream, beatmap);
|
||||||
|
|
||||||
|
applyDifficultyRestrictions(beatmap.Difficulty, beatmap);
|
||||||
|
|
||||||
flushPendingPoints();
|
flushPendingPoints();
|
||||||
|
|
||||||
// Objects may be out of order *only* if a user has manually edited an .osu file.
|
// Objects may be out of order *only* if a user has manually edited an .osu file.
|
||||||
@ -102,10 +104,30 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that all <see cref="BeatmapDifficulty"/> settings are within the allowed ranges.
|
||||||
|
/// See also: https://github.com/peppy/osu-stable-reference/blob/0e425c0d525ef21353c8293c235cc0621d28338b/osu!/GameplayElements/Beatmaps/Beatmap.cs#L567-L614
|
||||||
|
/// </summary>
|
||||||
|
private static void applyDifficultyRestrictions(BeatmapDifficulty difficulty, Beatmap beatmap)
|
||||||
|
{
|
||||||
|
difficulty.DrainRate = Math.Clamp(difficulty.DrainRate, 0, 10);
|
||||||
|
|
||||||
|
// mania uses "circle size" for key count, thus different allowable range
|
||||||
|
difficulty.CircleSize = beatmap.BeatmapInfo.Ruleset.OnlineID != 3
|
||||||
|
? Math.Clamp(difficulty.CircleSize, 0, 10)
|
||||||
|
: Math.Clamp(difficulty.CircleSize, 1, 18);
|
||||||
|
|
||||||
|
difficulty.OverallDifficulty = Math.Clamp(difficulty.OverallDifficulty, 0, 10);
|
||||||
|
difficulty.ApproachRate = Math.Clamp(difficulty.ApproachRate, 0, 10);
|
||||||
|
|
||||||
|
difficulty.SliderMultiplier = Math.Clamp(difficulty.SliderMultiplier, 0.4, 3.6);
|
||||||
|
difficulty.SliderTickRate = Math.Clamp(difficulty.SliderTickRate, 0.5, 8);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes the beatmap such that a new combo is started the first hitobject following each break.
|
/// Processes the beatmap such that a new combo is started the first hitobject following each break.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void postProcessBreaks(Beatmap beatmap)
|
private static void postProcessBreaks(Beatmap beatmap)
|
||||||
{
|
{
|
||||||
int currentBreak = 0;
|
int currentBreak = 0;
|
||||||
bool forceNewCombo = false;
|
bool forceNewCombo = false;
|
||||||
@ -161,7 +183,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
/// This method's intention is to restore those legacy defaults.
|
/// This method's intention is to restore those legacy defaults.
|
||||||
/// See also: https://osu.ppy.sh/wiki/en/Client/File_formats/Osu_%28file_format%29
|
/// See also: https://osu.ppy.sh/wiki/en/Client/File_formats/Osu_%28file_format%29
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void applyLegacyDefaults(BeatmapInfo beatmapInfo)
|
private static void applyLegacyDefaults(BeatmapInfo beatmapInfo)
|
||||||
{
|
{
|
||||||
beatmapInfo.WidescreenStoryboard = false;
|
beatmapInfo.WidescreenStoryboard = false;
|
||||||
beatmapInfo.SamplesMatchPlaybackRate = false;
|
beatmapInfo.SamplesMatchPlaybackRate = false;
|
||||||
@ -402,11 +424,11 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case @"SliderMultiplier":
|
case @"SliderMultiplier":
|
||||||
difficulty.SliderMultiplier = Math.Clamp(Parsing.ParseDouble(pair.Value), 0.4, 3.6);
|
difficulty.SliderMultiplier = Parsing.ParseDouble(pair.Value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case @"SliderTickRate":
|
case @"SliderTickRate":
|
||||||
difficulty.SliderTickRate = Math.Clamp(Parsing.ParseDouble(pair.Value), 0.5, 8);
|
difficulty.SliderTickRate = Parsing.ParseDouble(pair.Value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,7 @@ namespace osu.Game.Collections
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
AddInternal(addOrRemoveButton = new IconButton
|
AddInternal(addOrRemoveButton = new NoFocusChangeIconButton
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
@ -271,6 +271,11 @@ namespace osu.Game.Collections
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override Drawable CreateContent() => (Content)base.CreateContent();
|
protected override Drawable CreateContent() => (Content)base.CreateContent();
|
||||||
|
|
||||||
|
private partial class NoFocusChangeIconButton : IconButton
|
||||||
|
{
|
||||||
|
public override bool ChangeFocusOnClick => false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,7 @@ namespace osu.Game.Collections
|
|||||||
this.ScaleTo(0.9f, exit_duration);
|
this.ScaleTo(0.9f, exit_duration);
|
||||||
|
|
||||||
// Ensure that textboxes commit
|
// Ensure that textboxes commit
|
||||||
GetContainingInputManager()?.TriggerFocusContention(this);
|
GetContainingFocusManager()?.TriggerFocusContention(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1134,7 +1134,17 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
case 41:
|
case 41:
|
||||||
foreach (var score in migration.NewRealm.All<ScoreInfo>())
|
foreach (var score in migration.NewRealm.All<ScoreInfo>())
|
||||||
LegacyScoreDecoder.PopulateTotalScoreWithoutMods(score);
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// this can fail e.g. if a user has a score set on a ruleset that can no longer be loaded.
|
||||||
|
LegacyScoreDecoder.PopulateTotalScoreWithoutMods(score);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Log($@"Failed to populate total score without mods for score {score.ID}: {ex}", LoggingTarget.Database);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Scoring.Legacy;
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Scoring.Legacy;
|
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
{
|
{
|
||||||
@ -248,8 +247,7 @@ namespace osu.Game.Database
|
|||||||
// warning: ordering is important here - both total score and ranks are dependent on accuracy!
|
// warning: ordering is important here - both total score and ranks are dependent on accuracy!
|
||||||
score.Accuracy = computeAccuracy(score, scoreProcessor);
|
score.Accuracy = computeAccuracy(score, scoreProcessor);
|
||||||
score.Rank = computeRank(score, scoreProcessor);
|
score.Rank = computeRank(score, scoreProcessor);
|
||||||
score.TotalScore = convertFromLegacyTotalScore(score, ruleset, beatmap);
|
(score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, beatmap);
|
||||||
LegacyScoreDecoder.PopulateTotalScoreWithoutMods(score);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -273,7 +271,7 @@ namespace osu.Game.Database
|
|||||||
// warning: ordering is important here - both total score and ranks are dependent on accuracy!
|
// warning: ordering is important here - both total score and ranks are dependent on accuracy!
|
||||||
score.Accuracy = computeAccuracy(score, scoreProcessor);
|
score.Accuracy = computeAccuracy(score, scoreProcessor);
|
||||||
score.Rank = computeRank(score, scoreProcessor);
|
score.Rank = computeRank(score, scoreProcessor);
|
||||||
score.TotalScore = convertFromLegacyTotalScore(score, ruleset, difficulty, attributes);
|
(score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, difficulty, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -283,17 +281,13 @@ namespace osu.Game.Database
|
|||||||
/// <param name="ruleset">The <see cref="Ruleset"/> in which the score was set.</param>
|
/// <param name="ruleset">The <see cref="Ruleset"/> in which the score was set.</param>
|
||||||
/// <param name="beatmap">The <see cref="WorkingBeatmap"/> applicable for this score.</param>
|
/// <param name="beatmap">The <see cref="WorkingBeatmap"/> applicable for this score.</param>
|
||||||
/// <returns>The standardised total score.</returns>
|
/// <returns>The standardised total score.</returns>
|
||||||
private static long convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, WorkingBeatmap beatmap)
|
private static (long withoutMods, long withMods) convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, WorkingBeatmap beatmap)
|
||||||
{
|
{
|
||||||
if (!score.IsLegacyScore)
|
if (!score.IsLegacyScore)
|
||||||
return score.TotalScore;
|
return (score.TotalScoreWithoutMods, score.TotalScore);
|
||||||
|
|
||||||
if (ruleset is not ILegacyRuleset legacyRuleset)
|
if (ruleset is not ILegacyRuleset legacyRuleset)
|
||||||
return score.TotalScore;
|
return (score.TotalScoreWithoutMods, score.TotalScore);
|
||||||
|
|
||||||
var mods = score.Mods;
|
|
||||||
if (mods.Any(mod => mod is ModScoreV2))
|
|
||||||
return score.TotalScore;
|
|
||||||
|
|
||||||
var playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, score.Mods);
|
var playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, score.Mods);
|
||||||
|
|
||||||
@ -302,8 +296,13 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator();
|
ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator();
|
||||||
LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap);
|
LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap);
|
||||||
|
var legacyBeatmapConversionDifficultyInfo = LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap);
|
||||||
|
|
||||||
return convertFromLegacyTotalScore(score, ruleset, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes);
|
var mods = score.Mods;
|
||||||
|
if (mods.Any(mod => mod is ModScoreV2))
|
||||||
|
return ((long)Math.Round(score.TotalScore / sv1Simulator.GetLegacyScoreMultiplier(mods, legacyBeatmapConversionDifficultyInfo)), score.TotalScore);
|
||||||
|
|
||||||
|
return convertFromLegacyTotalScore(score, ruleset, legacyBeatmapConversionDifficultyInfo, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -314,15 +313,15 @@ namespace osu.Game.Database
|
|||||||
/// <param name="difficulty">The beatmap difficulty.</param>
|
/// <param name="difficulty">The beatmap difficulty.</param>
|
||||||
/// <param name="attributes">The legacy scoring attributes for the beatmap which the score was set on.</param>
|
/// <param name="attributes">The legacy scoring attributes for the beatmap which the score was set on.</param>
|
||||||
/// <returns>The standardised total score.</returns>
|
/// <returns>The standardised total score.</returns>
|
||||||
private static long convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
|
private static (long withoutMods, long withMods) convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
|
||||||
{
|
{
|
||||||
if (!score.IsLegacyScore)
|
if (!score.IsLegacyScore)
|
||||||
return score.TotalScore;
|
return (score.TotalScoreWithoutMods, score.TotalScore);
|
||||||
|
|
||||||
Debug.Assert(score.LegacyTotalScore != null);
|
Debug.Assert(score.LegacyTotalScore != null);
|
||||||
|
|
||||||
if (ruleset is not ILegacyRuleset legacyRuleset)
|
if (ruleset is not ILegacyRuleset legacyRuleset)
|
||||||
return score.TotalScore;
|
return (score.TotalScoreWithoutMods, score.TotalScore);
|
||||||
|
|
||||||
double legacyModMultiplier = legacyRuleset.CreateLegacyScoreSimulator().GetLegacyScoreMultiplier(score.Mods, difficulty);
|
double legacyModMultiplier = legacyRuleset.CreateLegacyScoreSimulator().GetLegacyScoreMultiplier(score.Mods, difficulty);
|
||||||
int maximumLegacyAccuracyScore = attributes.AccuracyScore;
|
int maximumLegacyAccuracyScore = attributes.AccuracyScore;
|
||||||
@ -354,17 +353,18 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
double modMultiplier = score.Mods.Select(m => m.ScoreMultiplier).Aggregate(1.0, (c, n) => c * n);
|
double modMultiplier = score.Mods.Select(m => m.ScoreMultiplier).Aggregate(1.0, (c, n) => c * n);
|
||||||
|
|
||||||
long convertedTotalScore;
|
long convertedTotalScoreWithoutMods;
|
||||||
|
|
||||||
switch (score.Ruleset.OnlineID)
|
switch (score.Ruleset.OnlineID)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
if (score.MaxCombo == 0 || score.Accuracy == 0)
|
if (score.MaxCombo == 0 || score.Accuracy == 0)
|
||||||
{
|
{
|
||||||
return (long)Math.Round((
|
convertedTotalScoreWithoutMods = (long)Math.Round(
|
||||||
0
|
0
|
||||||
+ 500000 * Math.Pow(score.Accuracy, 5)
|
+ 500000 * Math.Pow(score.Accuracy, 5)
|
||||||
+ bonusProportion) * modMultiplier);
|
+ bonusProportion);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// see similar check above.
|
// see similar check above.
|
||||||
@ -372,10 +372,11 @@ namespace osu.Game.Database
|
|||||||
// are either pointless or wildly wrong.
|
// are either pointless or wildly wrong.
|
||||||
if (maximumLegacyComboScore + maximumLegacyBonusScore == 0)
|
if (maximumLegacyComboScore + maximumLegacyBonusScore == 0)
|
||||||
{
|
{
|
||||||
return (long)Math.Round((
|
convertedTotalScoreWithoutMods = (long)Math.Round(
|
||||||
500000 * comboProportion // as above, zero if mods result in zero multiplier, one otherwise
|
500000 * comboProportion // as above, zero if mods result in zero multiplier, one otherwise
|
||||||
+ 500000 * Math.Pow(score.Accuracy, 5)
|
+ 500000 * Math.Pow(score.Accuracy, 5)
|
||||||
+ bonusProportion) * modMultiplier);
|
+ bonusProportion);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assumptions:
|
// Assumptions:
|
||||||
@ -472,17 +473,17 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
double newComboScoreProportion = estimatedComboPortionInStandardisedScore / maximumAchievableComboPortionInStandardisedScore;
|
double newComboScoreProportion = estimatedComboPortionInStandardisedScore / maximumAchievableComboPortionInStandardisedScore;
|
||||||
|
|
||||||
convertedTotalScore = (long)Math.Round((
|
convertedTotalScoreWithoutMods = (long)Math.Round(
|
||||||
500000 * newComboScoreProportion * score.Accuracy
|
500000 * newComboScoreProportion * score.Accuracy
|
||||||
+ 500000 * Math.Pow(score.Accuracy, 5)
|
+ 500000 * Math.Pow(score.Accuracy, 5)
|
||||||
+ bonusProportion) * modMultiplier);
|
+ bonusProportion);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
convertedTotalScore = (long)Math.Round((
|
convertedTotalScoreWithoutMods = (long)Math.Round(
|
||||||
250000 * comboProportion
|
250000 * comboProportion
|
||||||
+ 750000 * Math.Pow(score.Accuracy, 3.6)
|
+ 750000 * Math.Pow(score.Accuracy, 3.6)
|
||||||
+ bonusProportion) * modMultiplier);
|
+ bonusProportion);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
@ -507,28 +508,28 @@ namespace osu.Game.Database
|
|||||||
? 0
|
? 0
|
||||||
: (double)score.Statistics.GetValueOrDefault(HitResult.SmallTickHit) / score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit);
|
: (double)score.Statistics.GetValueOrDefault(HitResult.SmallTickHit) / score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit);
|
||||||
|
|
||||||
convertedTotalScore = (long)Math.Round((
|
convertedTotalScoreWithoutMods = (long)Math.Round(
|
||||||
comboPortion * estimateComboProportionForCatch(attributes.MaxCombo, score.MaxCombo, score.Statistics.GetValueOrDefault(HitResult.Miss))
|
comboPortion * estimateComboProportionForCatch(attributes.MaxCombo, score.MaxCombo, score.Statistics.GetValueOrDefault(HitResult.Miss))
|
||||||
+ dropletsPortion * dropletsHit
|
+ dropletsPortion * dropletsHit
|
||||||
+ bonusProportion) * modMultiplier);
|
+ bonusProportion);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
convertedTotalScore = (long)Math.Round((
|
convertedTotalScoreWithoutMods = (long)Math.Round(
|
||||||
850000 * comboProportion
|
850000 * comboProportion
|
||||||
+ 150000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy)
|
+ 150000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy)
|
||||||
+ bonusProportion) * modMultiplier);
|
+ bonusProportion);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
convertedTotalScore = score.TotalScore;
|
return (score.TotalScoreWithoutMods, score.TotalScore);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (convertedTotalScore < 0)
|
if (convertedTotalScoreWithoutMods < 0)
|
||||||
throw new InvalidOperationException($"Total score conversion operation returned invalid total of {convertedTotalScore}");
|
throw new InvalidOperationException($"Total score conversion operation returned invalid total of {convertedTotalScoreWithoutMods}");
|
||||||
|
|
||||||
return convertedTotalScore;
|
long convertedTotalScore = (long)Math.Round(convertedTotalScoreWithoutMods * modMultiplier);
|
||||||
|
return (convertedTotalScoreWithoutMods, convertedTotalScore);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -120,6 +120,7 @@ namespace osu.Game.Graphics
|
|||||||
public static IconUsage Cross => get(OsuIconMapping.Cross);
|
public static IconUsage Cross => get(OsuIconMapping.Cross);
|
||||||
public static IconUsage CrossCircle => get(OsuIconMapping.CrossCircle);
|
public static IconUsage CrossCircle => get(OsuIconMapping.CrossCircle);
|
||||||
public static IconUsage Crown => get(OsuIconMapping.Crown);
|
public static IconUsage Crown => get(OsuIconMapping.Crown);
|
||||||
|
public static IconUsage DailyChallenge => get(OsuIconMapping.DailyChallenge);
|
||||||
public static IconUsage Debug => get(OsuIconMapping.Debug);
|
public static IconUsage Debug => get(OsuIconMapping.Debug);
|
||||||
public static IconUsage Delete => get(OsuIconMapping.Delete);
|
public static IconUsage Delete => get(OsuIconMapping.Delete);
|
||||||
public static IconUsage Details => get(OsuIconMapping.Details);
|
public static IconUsage Details => get(OsuIconMapping.Details);
|
||||||
@ -218,6 +219,9 @@ namespace osu.Game.Graphics
|
|||||||
[Description(@"crown")]
|
[Description(@"crown")]
|
||||||
Crown,
|
Crown,
|
||||||
|
|
||||||
|
[Description(@"daily-challenge")]
|
||||||
|
DailyChallenge,
|
||||||
|
|
||||||
[Description(@"debug")]
|
[Description(@"debug")]
|
||||||
Debug,
|
Debug,
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
if (!allowImmediateFocus)
|
if (!allowImmediateFocus)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Scheduler.Add(() => GetContainingInputManager().ChangeFocus(this));
|
Scheduler.Add(() => GetContainingFocusManager().ChangeFocus(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public new void KillFocus() => base.KillFocus();
|
public new void KillFocus() => base.KillFocus();
|
||||||
|
@ -52,8 +52,6 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
const float nub_padding = 5;
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
LabelTextFlowContainer = new OsuTextFlowContainer(ApplyLabelParameters)
|
LabelTextFlowContainer = new OsuTextFlowContainer(ApplyLabelParameters)
|
||||||
@ -69,15 +67,13 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
Nub.Anchor = Anchor.CentreRight;
|
Nub.Anchor = Anchor.CentreRight;
|
||||||
Nub.Origin = Anchor.CentreRight;
|
Nub.Origin = Anchor.CentreRight;
|
||||||
Nub.Margin = new MarginPadding { Right = nub_padding };
|
LabelTextFlowContainer.Padding = new MarginPadding { Right = Nub.DEFAULT_EXPANDED_SIZE + 10f };
|
||||||
LabelTextFlowContainer.Padding = new MarginPadding { Right = Nub.DEFAULT_EXPANDED_SIZE + nub_padding * 2 };
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Nub.Anchor = Anchor.CentreLeft;
|
Nub.Anchor = Anchor.CentreLeft;
|
||||||
Nub.Origin = Anchor.CentreLeft;
|
Nub.Origin = Anchor.CentreLeft;
|
||||||
Nub.Margin = new MarginPadding { Left = nub_padding };
|
LabelTextFlowContainer.Padding = new MarginPadding { Left = Nub.DEFAULT_EXPANDED_SIZE + 10f };
|
||||||
LabelTextFlowContainer.Padding = new MarginPadding { Left = Nub.DEFAULT_EXPANDED_SIZE + nub_padding * 2 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Nub.Current.BindTo(Current);
|
Nub.Current.BindTo(Current);
|
||||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
public partial class ShearedButton : OsuClickableContainer
|
public partial class ShearedButton : OsuClickableContainer
|
||||||
{
|
{
|
||||||
public const float HEIGHT = 50;
|
public const float DEFAULT_HEIGHT = 50;
|
||||||
public const float CORNER_RADIUS = 7;
|
public const float CORNER_RADIUS = 7;
|
||||||
public const float BORDER_THICKNESS = 2;
|
public const float BORDER_THICKNESS = 2;
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
private readonly Box background;
|
private readonly Box background;
|
||||||
private readonly OsuSpriteText text;
|
private readonly OsuSpriteText text;
|
||||||
|
|
||||||
private const float shear = 0.2f;
|
private const float shear = OsuGame.SHEAR;
|
||||||
|
|
||||||
private Colour4? darkerColour;
|
private Colour4? darkerColour;
|
||||||
private Colour4? lighterColour;
|
private Colour4? lighterColour;
|
||||||
@ -75,6 +75,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
private readonly Container backgroundLayer;
|
private readonly Container backgroundLayer;
|
||||||
private readonly Box flashLayer;
|
private readonly Box flashLayer;
|
||||||
|
|
||||||
|
protected readonly Container ButtonContent;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="ShearedToggleButton"/>
|
/// Creates a new <see cref="ShearedToggleButton"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -85,10 +87,11 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
/// <item>If a <see langword="null"/> value is provided (or the argument is omitted entirely), the button will autosize in width to fit the text.</item>
|
/// <item>If a <see langword="null"/> value is provided (or the argument is omitted entirely), the button will autosize in width to fit the text.</item>
|
||||||
/// </list>
|
/// </list>
|
||||||
/// </param>
|
/// </param>
|
||||||
public ShearedButton(float? width = null)
|
/// <param name="height">The height of the button.</param>
|
||||||
|
public ShearedButton(float? width = null, float height = DEFAULT_HEIGHT)
|
||||||
{
|
{
|
||||||
Height = HEIGHT;
|
Height = height;
|
||||||
Padding = new MarginPadding { Horizontal = shear * 50 };
|
Padding = new MarginPadding { Horizontal = shear * height };
|
||||||
|
|
||||||
Content.CornerRadius = CORNER_RADIUS;
|
Content.CornerRadius = CORNER_RADIUS;
|
||||||
Content.Shear = new Vector2(shear, 0);
|
Content.Shear = new Vector2(shear, 0);
|
||||||
@ -109,12 +112,16 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
text = new OsuSpriteText
|
ButtonContent = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Font = OsuFont.TorusAlternate.With(size: 17),
|
AutoSizeAxes = Axes.Both,
|
||||||
Shear = new Vector2(-shear, 0)
|
Shear = new Vector2(-shear, 0),
|
||||||
|
Child = text = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.TorusAlternate.With(size: 17),
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -188,7 +195,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
var colourDark = darkerColour ?? ColourProvider.Background3;
|
var colourDark = darkerColour ?? ColourProvider.Background3;
|
||||||
var colourLight = lighterColour ?? ColourProvider.Background1;
|
var colourLight = lighterColour ?? ColourProvider.Background1;
|
||||||
var colourText = textColour ?? ColourProvider.Content1;
|
var colourContent = textColour ?? ColourProvider.Content1;
|
||||||
|
|
||||||
if (!Enabled.Value)
|
if (!Enabled.Value)
|
||||||
{
|
{
|
||||||
@ -205,9 +212,9 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
backgroundLayer.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(colourDark, colourLight), 150, Easing.OutQuint);
|
backgroundLayer.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(colourDark, colourLight), 150, Easing.OutQuint);
|
||||||
|
|
||||||
if (!Enabled.Value)
|
if (!Enabled.Value)
|
||||||
colourText = colourText.Opacity(0.6f);
|
colourContent = colourContent.Opacity(0.6f);
|
||||||
|
|
||||||
text.FadeColour(colourText, 150, Easing.OutQuint);
|
ButtonContent.FadeColour(colourContent, 150, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ using osu.Framework.Graphics.UserInterface;
|
|||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Mods;
|
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -53,7 +52,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
public ShearedSearchTextBox()
|
public ShearedSearchTextBox()
|
||||||
{
|
{
|
||||||
Height = 42;
|
Height = 42;
|
||||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0);
|
Shear = new Vector2(OsuGame.SHEAR, 0);
|
||||||
Masking = true;
|
Masking = true;
|
||||||
CornerRadius = corner_radius;
|
CornerRadius = corner_radius;
|
||||||
|
|
||||||
@ -116,7 +115,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
PlaceholderText = CommonStrings.InputSearch;
|
PlaceholderText = CommonStrings.InputSearch;
|
||||||
|
|
||||||
CornerRadius = corner_radius;
|
CornerRadius = corner_radius;
|
||||||
TextContainer.Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0);
|
TextContainer.Shear = new Vector2(-OsuGame.SHEAR, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SpriteText CreatePlaceholder() => new SearchPlaceholder();
|
protected override SpriteText CreatePlaceholder() => new SearchPlaceholder();
|
||||||
|
@ -57,7 +57,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
protected override void OnFocus(FocusEvent e)
|
protected override void OnFocus(FocusEvent e)
|
||||||
{
|
{
|
||||||
base.OnFocus(e);
|
base.OnFocus(e);
|
||||||
GetContainingInputManager().ChangeFocus(Component);
|
GetContainingFocusManager().ChangeFocus(Component);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override OsuTextBox CreateComponent() => CreateTextBox().With(t =>
|
protected override OsuTextBox CreateComponent() => CreateTextBox().With(t =>
|
||||||
|
@ -85,7 +85,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
Current.BindValueChanged(updateTextBoxFromSlider, true);
|
Current.BindValueChanged(updateTextBoxFromSlider, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TakeFocus() => GetContainingInputManager().ChangeFocus(textBox);
|
public bool TakeFocus() => GetContainingFocusManager().ChangeFocus(textBox);
|
||||||
|
|
||||||
private bool updatingFromTextBox;
|
private bool updatingFromTextBox;
|
||||||
|
|
||||||
|
@ -182,6 +182,8 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom),
|
new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom),
|
||||||
new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions),
|
new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions),
|
||||||
new KeyBinding(InputKey.BackSpace, GlobalAction.DeselectAllMods),
|
new KeyBinding(InputKey.BackSpace, GlobalAction.DeselectAllMods),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.Up }, GlobalAction.IncreaseModSpeed),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.Down }, GlobalAction.DecreaseModSpeed),
|
||||||
};
|
};
|
||||||
|
|
||||||
private static IEnumerable<KeyBinding> audioControlKeyBindings => new[]
|
private static IEnumerable<KeyBinding> audioControlKeyBindings => new[]
|
||||||
@ -420,6 +422,12 @@ namespace osu.Game.Input.Bindings
|
|||||||
|
|
||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.StepReplayBackward))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.StepReplayBackward))]
|
||||||
StepReplayBackward,
|
StepReplayBackward,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseModSpeed))]
|
||||||
|
IncreaseModSpeed,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseModSpeed))]
|
||||||
|
DecreaseModSpeed,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum GlobalActionCategory
|
public enum GlobalActionCategory
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// 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.Localisation;
|
using osu.Framework.Localisation;
|
||||||
@ -54,6 +54,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString Exit => new TranslatableString(getKey(@"exit"), @"exit");
|
public static LocalisableString Exit => new TranslatableString(getKey(@"exit"), @"exit");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "daily challenge"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString DailyChallenge => new TranslatableString(getKey(@"daily_challenge"), @"daily challenge");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Localisation
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Are you sure you want to delete all scores? This cannot be undone!"
|
/// "Are you sure you want to delete all scores? This cannot be undone!"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString Scores => new TranslatableString(getKey(@"collections"), @"Are you sure you want to delete all scores? This cannot be undone!");
|
public static LocalisableString Scores => new TranslatableString(getKey(@"scores"), @"Are you sure you want to delete all scores? This cannot be undone!");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Are you sure you want to delete all mod presets?"
|
/// "Are you sure you want to delete all mod presets?"
|
||||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Localisation
|
|||||||
public static LocalisableString Calculating => new TranslatableString(getKey(@"calculating"), @"calculating...");
|
public static LocalisableString Calculating => new TranslatableString(getKey(@"calculating"), @"calculating...");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "{0} items"
|
/// "{0} item(s)"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString Items(int arg0) => new TranslatableString(getKey(@"items"), @"{0} item(s)", arg0);
|
public static LocalisableString Items(int arg0) => new TranslatableString(getKey(@"items"), @"{0} item(s)", arg0);
|
||||||
|
|
||||||
|
@ -369,6 +369,16 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString EditorToggleRotateControl => new TranslatableString(getKey(@"editor_toggle_rotate_control"), @"Toggle rotate control");
|
public static LocalisableString EditorToggleRotateControl => new TranslatableString(getKey(@"editor_toggle_rotate_control"), @"Toggle rotate control");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Increase mod speed"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString IncreaseModSpeed => new TranslatableString(getKey(@"increase_mod_speed"), @"Increase mod speed");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Decrease mod speed"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString DecreaseModSpeed => new TranslatableString(getKey(@"decrease_mod_speed"), @"Decrease mod speed");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ Please try changing your audio device to a working setting.");
|
|||||||
public static LocalisableString MismatchingBeatmapForReplay => new TranslatableString(getKey(@"mismatching_beatmap_for_replay"), @"Your local copy of the beatmap for this replay appears to be different than expected. You may need to update or re-download it.");
|
public static LocalisableString MismatchingBeatmapForReplay => new TranslatableString(getKey(@"mismatching_beatmap_for_replay"), @"Your local copy of the beatmap for this replay appears to be different than expected. You may need to update or re-download it.");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "You are now running osu! {version}.
|
/// "You are now running osu! {0}.
|
||||||
/// Click to see what's new!"
|
/// Click to see what's new!"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString GameVersionAfterUpdate(string version) => new TranslatableString(getKey(@"game_version_after_update"), @"You are now running osu! {0}.
|
public static LocalisableString GameVersionAfterUpdate(string version) => new TranslatableString(getKey(@"game_version_after_update"), @"You are now running osu! {0}.
|
||||||
|
@ -49,6 +49,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString UrlCopied => new TranslatableString(getKey(@"url_copied"), @"URL copied");
|
public static LocalisableString UrlCopied => new TranslatableString(getKey(@"url_copied"), @"URL copied");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Speed changed to {0:N2}x"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString SpeedChangedTo(double speed) => new TranslatableString(getKey(@"speed_changed"), @"Speed changed to {0:N2}x", speed);
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
osu.Game/Online/Metadata/DailyChallengeInfo.cs
Normal file
16
osu.Game/Online/Metadata/DailyChallengeInfo.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// 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 MessagePack;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Metadata
|
||||||
|
{
|
||||||
|
[MessagePackObject]
|
||||||
|
[Serializable]
|
||||||
|
public struct DailyChallengeInfo
|
||||||
|
{
|
||||||
|
[Key(0)]
|
||||||
|
public long RoomID { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -20,5 +20,11 @@ namespace osu.Game.Online.Metadata
|
|||||||
/// Delivers an update of the <see cref="UserPresence"/> of the user with the supplied <paramref name="userId"/>.
|
/// Delivers an update of the <see cref="UserPresence"/> of the user with the supplied <paramref name="userId"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task UserPresenceUpdated(int userId, UserPresence? status);
|
Task UserPresenceUpdated(int userId, UserPresence? status);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delivers an update of the current "daily challenge" status.
|
||||||
|
/// Null value means there is no "daily challenge" currently active.
|
||||||
|
/// </summary>
|
||||||
|
Task DailyChallengeUpdated(DailyChallengeInfo? info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,12 @@ using osu.Game.Users;
|
|||||||
namespace osu.Game.Online.Metadata
|
namespace osu.Game.Online.Metadata
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Metadata server is responsible for keeping the osu! client up-to-date with any changes.
|
/// Metadata server is responsible for keeping the osu! client up-to-date with various real-time happenings, such as:
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>beatmap updates via BSS,</item>
|
||||||
|
/// <item>online user activity/status updates,</item>
|
||||||
|
/// <item>other real-time happenings, such as current "daily challenge" status.</item>
|
||||||
|
/// </list>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IMetadataServer
|
public interface IMetadataServer
|
||||||
{
|
{
|
||||||
|
@ -59,6 +59,15 @@ namespace osu.Game.Online.Metadata
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Daily Challenge
|
||||||
|
|
||||||
|
public abstract IBindable<DailyChallengeInfo?> DailyChallengeInfo { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public abstract Task DailyChallengeUpdated(DailyChallengeInfo? info);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Disconnection handling
|
#region Disconnection handling
|
||||||
|
|
||||||
public event Action? Disconnecting;
|
public event Action? Disconnecting;
|
||||||
|
@ -26,6 +26,9 @@ namespace osu.Game.Online.Metadata
|
|||||||
public override IBindableDictionary<int, UserPresence> UserStates => userStates;
|
public override IBindableDictionary<int, UserPresence> UserStates => userStates;
|
||||||
private readonly BindableDictionary<int, UserPresence> userStates = new BindableDictionary<int, UserPresence>();
|
private readonly BindableDictionary<int, UserPresence> userStates = new BindableDictionary<int, UserPresence>();
|
||||||
|
|
||||||
|
public override IBindable<DailyChallengeInfo?> DailyChallengeInfo => dailyChallengeInfo;
|
||||||
|
private readonly Bindable<DailyChallengeInfo?> dailyChallengeInfo = new Bindable<DailyChallengeInfo?>();
|
||||||
|
|
||||||
private readonly string endpoint;
|
private readonly string endpoint;
|
||||||
|
|
||||||
private IHubClientConnector? connector;
|
private IHubClientConnector? connector;
|
||||||
@ -58,6 +61,7 @@ namespace osu.Game.Online.Metadata
|
|||||||
// https://github.com/dotnet/aspnetcore/issues/15198
|
// https://github.com/dotnet/aspnetcore/issues/15198
|
||||||
connection.On<BeatmapUpdates>(nameof(IMetadataClient.BeatmapSetsUpdated), ((IMetadataClient)this).BeatmapSetsUpdated);
|
connection.On<BeatmapUpdates>(nameof(IMetadataClient.BeatmapSetsUpdated), ((IMetadataClient)this).BeatmapSetsUpdated);
|
||||||
connection.On<int, UserPresence?>(nameof(IMetadataClient.UserPresenceUpdated), ((IMetadataClient)this).UserPresenceUpdated);
|
connection.On<int, UserPresence?>(nameof(IMetadataClient.UserPresenceUpdated), ((IMetadataClient)this).UserPresenceUpdated);
|
||||||
|
connection.On<DailyChallengeInfo?>(nameof(IMetadataClient.DailyChallengeUpdated), ((IMetadataClient)this).DailyChallengeUpdated);
|
||||||
connection.On(nameof(IStatefulUserHubClient.DisconnectRequested), ((IMetadataClient)this).DisconnectRequested);
|
connection.On(nameof(IStatefulUserHubClient.DisconnectRequested), ((IMetadataClient)this).DisconnectRequested);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -101,6 +105,7 @@ namespace osu.Game.Online.Metadata
|
|||||||
{
|
{
|
||||||
isWatchingUserPresence.Value = false;
|
isWatchingUserPresence.Value = false;
|
||||||
userStates.Clear();
|
userStates.Clear();
|
||||||
|
dailyChallengeInfo.Value = null;
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -229,6 +234,12 @@ namespace osu.Game.Online.Metadata
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Task DailyChallengeUpdated(DailyChallengeInfo? info)
|
||||||
|
{
|
||||||
|
Schedule(() => dailyChallengeInfo.Value = info);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task DisconnectRequested()
|
public override async Task DisconnectRequested()
|
||||||
{
|
{
|
||||||
await base.DisconnectRequested().ConfigureAwait(false);
|
await base.DisconnectRequested().ConfigureAwait(false);
|
||||||
|
@ -13,5 +13,8 @@ namespace osu.Game.Online.Rooms
|
|||||||
|
|
||||||
[Description("Featured Artist")]
|
[Description("Featured Artist")]
|
||||||
FeaturedArtist,
|
FeaturedArtist,
|
||||||
|
|
||||||
|
[Description("Daily Challenge")]
|
||||||
|
DailyChallenge,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,11 @@ namespace osu.Game
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected const float SIDE_OVERLAY_OFFSET_RATIO = 0.05f;
|
protected const float SIDE_OVERLAY_OFFSET_RATIO = 0.05f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A common shear factor applied to most components of the game.
|
||||||
|
/// </summary>
|
||||||
|
public const float SHEAR = 0.2f;
|
||||||
|
|
||||||
public Toolbar Toolbar { get; private set; }
|
public Toolbar Toolbar { get; private set; }
|
||||||
|
|
||||||
private ChatOverlay chatOverlay;
|
private ChatOverlay chatOverlay;
|
||||||
|
@ -578,17 +578,17 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
case ITabletHandler th:
|
case ITabletHandler th:
|
||||||
return new TabletSettings(th);
|
return new TabletSettings(th);
|
||||||
|
|
||||||
case MouseHandler mh:
|
|
||||||
return new MouseSettings(mh);
|
|
||||||
|
|
||||||
case JoystickHandler jh:
|
|
||||||
return new JoystickSettings(jh);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (handler)
|
switch (handler)
|
||||||
{
|
{
|
||||||
|
case MouseHandler mh:
|
||||||
|
return new MouseSettings(mh);
|
||||||
|
|
||||||
|
case JoystickHandler jh:
|
||||||
|
return new JoystickSettings(jh);
|
||||||
|
|
||||||
case TouchHandler th:
|
case TouchHandler th:
|
||||||
return new TouchSettings(th);
|
return new TouchSettings(th);
|
||||||
|
|
||||||
|
@ -243,7 +243,7 @@ namespace osu.Game.Overlays.AccountCreation
|
|||||||
|
|
||||||
if (nextTextBox != null)
|
if (nextTextBox != null)
|
||||||
{
|
{
|
||||||
Schedule(() => GetContainingInputManager().ChangeFocus(nextTextBox));
|
Schedule(() => GetContainingFocusManager().ChangeFocus(nextTextBox));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -26,9 +25,9 @@ namespace osu.Game.Overlays.BeatmapSet
|
|||||||
{
|
{
|
||||||
private readonly Statistic length, bpm, circleCount, sliderCount;
|
private readonly Statistic length, bpm, circleCount, sliderCount;
|
||||||
|
|
||||||
private APIBeatmapSet beatmapSet;
|
private IBeatmapSetInfo beatmapSet;
|
||||||
|
|
||||||
public APIBeatmapSet BeatmapSet
|
public IBeatmapSetInfo BeatmapSet
|
||||||
{
|
{
|
||||||
get => beatmapSet;
|
get => beatmapSet;
|
||||||
set
|
set
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
@ -28,9 +29,9 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
|
|||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
public PreviewTrack Preview { get; private set; }
|
public PreviewTrack Preview { get; private set; }
|
||||||
|
|
||||||
private APIBeatmapSet beatmapSet;
|
private IBeatmapSetInfo beatmapSet;
|
||||||
|
|
||||||
public APIBeatmapSet BeatmapSet
|
public IBeatmapSetInfo BeatmapSet
|
||||||
{
|
{
|
||||||
get => beatmapSet;
|
get => beatmapSet;
|
||||||
set
|
set
|
||||||
|
@ -8,9 +8,9 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.BeatmapSet.Buttons
|
namespace osu.Game.Overlays.BeatmapSet.Buttons
|
||||||
@ -24,7 +24,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
|
|||||||
|
|
||||||
public IBindable<bool> Playing => playButton.Playing;
|
public IBindable<bool> Playing => playButton.Playing;
|
||||||
|
|
||||||
public APIBeatmapSet BeatmapSet
|
public IBeatmapSetInfo BeatmapSet
|
||||||
{
|
{
|
||||||
get => playButton.BeatmapSet;
|
get => playButton.BeatmapSet;
|
||||||
set => playButton.BeatmapSet = value;
|
set => playButton.BeatmapSet = value;
|
||||||
@ -32,8 +32,6 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
|
|||||||
|
|
||||||
public PreviewButton()
|
public PreviewButton()
|
||||||
{
|
{
|
||||||
Height = 42;
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
background = new Box
|
background = new Box
|
||||||
|
@ -68,6 +68,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
|||||||
preview = new PreviewButton
|
preview = new PreviewButton
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 42,
|
||||||
},
|
},
|
||||||
new DetailBox
|
new DetailBox
|
||||||
{
|
{
|
||||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Comments
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
if (!TextBox.ReadOnly)
|
if (!TextBox.ReadOnly)
|
||||||
GetContainingInputManager().ChangeFocus(TextBox);
|
GetContainingFocusManager().ChangeFocus(TextBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnCommit(string text)
|
protected override void OnCommit(string text)
|
||||||
|
@ -150,7 +150,7 @@ namespace osu.Game.Overlays.Login
|
|||||||
|
|
||||||
protected override void OnFocus(FocusEvent e)
|
protected override void OnFocus(FocusEvent e)
|
||||||
{
|
{
|
||||||
Schedule(() => { GetContainingInputManager().ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); });
|
Schedule(() => { GetContainingFocusManager().ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ namespace osu.Game.Overlays.Login
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (form != null)
|
if (form != null)
|
||||||
ScheduleAfterChildren(() => GetContainingInputManager()?.ChangeFocus(form));
|
ScheduleAfterChildren(() => GetContainingFocusManager()?.ChangeFocus(form));
|
||||||
});
|
});
|
||||||
|
|
||||||
private void updateDropdownCurrent(UserStatus? status)
|
private void updateDropdownCurrent(UserStatus? status)
|
||||||
@ -216,7 +216,7 @@ namespace osu.Game.Overlays.Login
|
|||||||
|
|
||||||
protected override void OnFocus(FocusEvent e)
|
protected override void OnFocus(FocusEvent e)
|
||||||
{
|
{
|
||||||
if (form != null) GetContainingInputManager().ChangeFocus(form);
|
if (form != null) GetContainingFocusManager().ChangeFocus(form);
|
||||||
base.OnFocus(e);
|
base.OnFocus(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,7 @@ namespace osu.Game.Overlays.Login
|
|||||||
|
|
||||||
protected override void OnFocus(FocusEvent e)
|
protected override void OnFocus(FocusEvent e)
|
||||||
{
|
{
|
||||||
Schedule(() => { GetContainingInputManager().ChangeFocus(codeTextBox); });
|
Schedule(() => { GetContainingFocusManager().ChangeFocus(codeTextBox); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ namespace osu.Game.Overlays
|
|||||||
this.FadeIn(transition_time, Easing.OutQuint);
|
this.FadeIn(transition_time, Easing.OutQuint);
|
||||||
FadeEdgeEffectTo(WaveContainer.SHADOW_OPACITY, WaveContainer.APPEAR_DURATION, Easing.Out);
|
FadeEdgeEffectTo(WaveContainer.SHADOW_OPACITY, WaveContainer.APPEAR_DURATION, Easing.Out);
|
||||||
|
|
||||||
ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(panel));
|
ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(panel));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PopOut()
|
protected override void PopOut()
|
||||||
|
@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(nameTextBox));
|
ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(nameTextBox));
|
||||||
|
|
||||||
nameTextBox.Current.BindValueChanged(s =>
|
nameTextBox.Current.BindValueChanged(s =>
|
||||||
{
|
{
|
||||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
const float shear = ShearedOverlayContainer.SHEAR;
|
const float shear = OsuGame.SHEAR;
|
||||||
|
|
||||||
LeftContent.AddRange(new Drawable[]
|
LeftContent.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
|
@ -136,7 +136,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(nameTextBox));
|
ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(nameTextBox));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
|
@ -106,7 +106,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Scale = new Vector2(0.8f),
|
Scale = new Vector2(0.8f),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)
|
Shear = new Vector2(-OsuGame.SHEAR, 0)
|
||||||
});
|
});
|
||||||
ItemsFlow.Padding = new MarginPadding
|
ItemsFlow.Padding = new MarginPadding
|
||||||
{
|
{
|
||||||
|
@ -36,8 +36,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Origin = Anchor.BottomRight,
|
Origin = Anchor.BottomRight,
|
||||||
Anchor = Anchor.BottomRight,
|
Anchor = Anchor.BottomRight,
|
||||||
AutoSizeAxes = Axes.X,
|
AutoSizeAxes = Axes.X,
|
||||||
Height = ShearedButton.HEIGHT,
|
Height = ShearedButton.DEFAULT_HEIGHT,
|
||||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(OsuGame.SHEAR, 0),
|
||||||
CornerRadius = ShearedButton.CORNER_RADIUS,
|
CornerRadius = ShearedButton.CORNER_RADIUS,
|
||||||
BorderThickness = ShearedButton.BORDER_THICKNESS,
|
BorderThickness = ShearedButton.BORDER_THICKNESS,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Active = { BindTarget = Active },
|
Active = { BindTarget = Active },
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||||
Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE)
|
Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
Width = WIDTH;
|
Width = WIDTH;
|
||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0);
|
Shear = new Vector2(OsuGame.SHEAR, 0);
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = header_height,
|
Height = header_height,
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||||
Velocity = 0.7f,
|
Velocity = 0.7f,
|
||||||
ClampAxes = Axes.Y
|
ClampAxes = Axes.Y
|
||||||
},
|
},
|
||||||
@ -111,7 +111,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||||
Padding = new MarginPadding
|
Padding = new MarginPadding
|
||||||
{
|
{
|
||||||
Horizontal = 17,
|
Horizontal = 17,
|
||||||
|
@ -186,7 +186,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.BottomCentre,
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
Height = 0
|
Height = 0
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
MainAreaContent.AddRange(new Drawable[]
|
MainAreaContent.AddRange(new Drawable[]
|
||||||
@ -227,7 +227,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
Direction = FillDirection.Horizontal,
|
Direction = FillDirection.Horizontal,
|
||||||
Shear = new Vector2(SHEAR, 0),
|
Shear = new Vector2(OsuGame.SHEAR, 0),
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
AutoSizeAxes = Axes.X,
|
AutoSizeAxes = Axes.X,
|
||||||
Margin = new MarginPadding { Horizontal = 70 },
|
Margin = new MarginPadding { Horizontal = 70 },
|
||||||
@ -847,7 +847,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
// DrawWidth/DrawPosition do not include shear effects, and we want to know the full extents of the columns post-shear,
|
// DrawWidth/DrawPosition do not include shear effects, and we want to know the full extents of the columns post-shear,
|
||||||
// so we have to manually compensate.
|
// so we have to manually compensate.
|
||||||
var topLeft = column.ToSpaceOfOtherDrawable(Vector2.Zero, ScrollContent);
|
var topLeft = column.ToSpaceOfOtherDrawable(Vector2.Zero, ScrollContent);
|
||||||
var bottomRight = column.ToSpaceOfOtherDrawable(new Vector2(column.DrawWidth - column.DrawHeight * SHEAR, 0), ScrollContent);
|
var bottomRight = column.ToSpaceOfOtherDrawable(new Vector2(column.DrawWidth - column.DrawHeight * OsuGame.SHEAR, 0), ScrollContent);
|
||||||
|
|
||||||
bool isCurrentlyVisible = Precision.AlmostBigger(topLeft.X, leftVisibleBound)
|
bool isCurrentlyVisible = Precision.AlmostBigger(topLeft.X, leftVisibleBound)
|
||||||
&& Precision.DefinitelyBigger(rightVisibleBound, bottomRight.X);
|
&& Precision.DefinitelyBigger(rightVisibleBound, bottomRight.X);
|
||||||
@ -949,7 +949,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
RequestScroll?.Invoke(this);
|
RequestScroll?.Invoke(this);
|
||||||
|
|
||||||
// Killing focus is done here because it's the only feasible place on ModSelectOverlay you can click on without triggering any action.
|
// Killing focus is done here because it's the only feasible place on ModSelectOverlay you can click on without triggering any action.
|
||||||
Scheduler.Add(() => GetContainingInputManager().ChangeFocus(null));
|
Scheduler.Add(() => GetContainingFocusManager().ChangeFocus(null));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Content.CornerRadius = CORNER_RADIUS;
|
Content.CornerRadius = CORNER_RADIUS;
|
||||||
Content.BorderThickness = 2;
|
Content.BorderThickness = 2;
|
||||||
|
|
||||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0);
|
Shear = new Vector2(OsuGame.SHEAR, 0);
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -128,10 +128,10 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold),
|
Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||||
Margin = new MarginPadding
|
Margin = new MarginPadding
|
||||||
{
|
{
|
||||||
Left = -18 * ShearedOverlayContainer.SHEAR
|
Left = -18 * OsuGame.SHEAR
|
||||||
},
|
},
|
||||||
ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`.
|
ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`.
|
||||||
},
|
},
|
||||||
@ -139,7 +139,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
Font = OsuFont.Default.With(size: 12),
|
Font = OsuFont.Default.With(size: 12),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||||
ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`.
|
ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Anchor = Anchor.BottomRight,
|
Anchor = Anchor.BottomRight,
|
||||||
Origin = Anchor.BottomRight,
|
Origin = Anchor.BottomRight,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(OsuGame.SHEAR, 0),
|
||||||
CornerRadius = ShearedButton.CORNER_RADIUS,
|
CornerRadius = ShearedButton.CORNER_RADIUS,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||||
Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold)
|
Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,7 +94,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Child = counter = new EffectCounter
|
Child = counter = new EffectCounter
|
||||||
{
|
{
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-OsuGame.SHEAR, 0),
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Current = { BindTarget = ModMultiplier }
|
Current = { BindTarget = ModMultiplier }
|
||||||
|
@ -22,8 +22,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
protected const float PADDING = 14;
|
protected const float PADDING = 14;
|
||||||
|
|
||||||
public const float SHEAR = 0.2f;
|
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
protected readonly OverlayColourProvider ColourProvider;
|
protected readonly OverlayColourProvider ColourProvider;
|
||||||
|
|
||||||
|
@ -3,18 +3,30 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
namespace osu.Game.Overlays.Mods
|
||||||
{
|
{
|
||||||
public partial class UserModSelectOverlay : ModSelectOverlay
|
public partial class UserModSelectOverlay : ModSelectOverlay
|
||||||
{
|
{
|
||||||
|
private ModSpeedHotkeyHandler modSpeedHotkeyHandler = null!;
|
||||||
|
|
||||||
public UserModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green)
|
public UserModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green)
|
||||||
: base(colourScheme)
|
: base(colourScheme)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Add(modSpeedHotkeyHandler = new ModSpeedHotkeyHandler());
|
||||||
|
}
|
||||||
|
|
||||||
protected override ModColumn CreateModColumn(ModType modType) => new UserModColumn(modType, false);
|
protected override ModColumn CreateModColumn(ModType modType) => new UserModColumn(modType, false);
|
||||||
|
|
||||||
protected override IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection)
|
protected override IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection)
|
||||||
@ -38,6 +50,20 @@ namespace osu.Game.Overlays.Mods
|
|||||||
return modsAfterRemoval.ToList();
|
return modsAfterRemoval.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case GlobalAction.IncreaseModSpeed:
|
||||||
|
return modSpeedHotkeyHandler.ChangeSpeed(0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod));
|
||||||
|
|
||||||
|
case GlobalAction.DecreaseModSpeed:
|
||||||
|
return modSpeedHotkeyHandler.ChangeSpeed(-0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod));
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnPressed(e);
|
||||||
|
}
|
||||||
|
|
||||||
private partial class UserModColumn : ModColumn
|
private partial class UserModColumn : ModColumn
|
||||||
{
|
{
|
||||||
public UserModColumn(ModType modType, bool allowIncompatibleSelection)
|
public UserModColumn(ModType modType, bool allowIncompatibleSelection)
|
||||||
|
17
osu.Game/Overlays/OSD/SpeedChangeToast.cs
Normal file
17
osu.Game/Overlays/OSD/SpeedChangeToast.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.OSD
|
||||||
|
{
|
||||||
|
public partial class SpeedChangeToast : Toast
|
||||||
|
{
|
||||||
|
public SpeedChangeToast(OsuConfigManager config, double newSpeed)
|
||||||
|
: base(ModSelectOverlayStrings.ModCustomisation, ToastStrings.SpeedChangedTo(newSpeed), config.LookupKeyBindings(GlobalAction.IncreaseModSpeed) + " / " + config.LookupKeyBindings(GlobalAction.DecreaseModSpeed))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -465,7 +465,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (HasFocus)
|
if (HasFocus)
|
||||||
GetContainingInputManager().ChangeFocus(null);
|
GetContainingFocusManager().ChangeFocus(null);
|
||||||
|
|
||||||
cancelAndClearButtons.FadeOut(300, Easing.OutQuint);
|
cancelAndClearButtons.FadeOut(300, Easing.OutQuint);
|
||||||
cancelAndClearButtons.BypassAutoSizeAxes |= Axes.Y;
|
cancelAndClearButtons.BypassAutoSizeAxes |= Axes.Y;
|
||||||
|
@ -106,7 +106,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
{
|
{
|
||||||
var next = Children.SkipWhile(c => c != sender).Skip(1).FirstOrDefault();
|
var next = Children.SkipWhile(c => c != sender).Skip(1).FirstOrDefault();
|
||||||
if (next != null)
|
if (next != null)
|
||||||
GetContainingInputManager().ChangeFocus(next);
|
GetContainingFocusManager().ChangeFocus(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,12 +105,17 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
|
|
||||||
highPrecisionMouse.Current.BindValueChanged(highPrecision =>
|
highPrecisionMouse.Current.BindValueChanged(highPrecision =>
|
||||||
{
|
{
|
||||||
if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows)
|
switch (RuntimeInfo.OS)
|
||||||
{
|
{
|
||||||
if (highPrecision.NewValue)
|
case RuntimeInfo.Platform.Linux:
|
||||||
highPrecisionMouse.SetNoticeText(MouseSettingsStrings.HighPrecisionPlatformWarning, true);
|
case RuntimeInfo.Platform.macOS:
|
||||||
else
|
case RuntimeInfo.Platform.iOS:
|
||||||
highPrecisionMouse.ClearNoticeText();
|
if (highPrecision.NewValue)
|
||||||
|
highPrecisionMouse.SetNoticeText(MouseSettingsStrings.HighPrecisionPlatformWarning, true);
|
||||||
|
else
|
||||||
|
highPrecisionMouse.ClearNoticeText();
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
searchTextBox.HoldFocus = false;
|
searchTextBox.HoldFocus = false;
|
||||||
if (searchTextBox.HasFocus)
|
if (searchTextBox.HasFocus)
|
||||||
GetContainingInputManager().ChangeFocus(null);
|
GetContainingFocusManager().ChangeFocus(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool AcceptsFocus => true;
|
public override bool AcceptsFocus => true;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user