mirror of
https://github.com/ppy/osu.git
synced 2025-02-20 00:52:56 +08:00
Merge branch 'master' into medals
This commit is contained in:
commit
fe59a3b9be
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
@ -13,10 +13,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install .NET 8.0.x
|
- name: Install .NET 8.0.x
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "8.0.x"
|
dotnet-version: "8.0.x"
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ jobs:
|
|||||||
run: dotnet restore osu.Desktop.slnf
|
run: dotnet restore osu.Desktop.slnf
|
||||||
|
|
||||||
- name: Restore inspectcode cache
|
- name: Restore inspectcode cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ github.workspace }}/inspectcode
|
path: ${{ github.workspace }}/inspectcode
|
||||||
key: inspectcode-${{ hashFiles('.config/dotnet-tools.json', '.github/workflows/ci.yml', 'osu.sln*', 'osu*.slnf', '.editorconfig', '.globalconfig', 'CodeAnalysis/*', '**/*.csproj', '**/*.props') }}
|
key: inspectcode-${{ hashFiles('.config/dotnet-tools.json', '.github/workflows/ci.yml', 'osu.sln*', 'osu*.slnf', '.editorconfig', '.globalconfig', 'CodeAnalysis/*', '**/*.csproj', '**/*.props') }}
|
||||||
@ -70,10 +70,10 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install .NET 8.0.x
|
- name: Install .NET 8.0.x
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "8.0.x"
|
dotnet-version: "8.0.x"
|
||||||
|
|
||||||
@ -99,16 +99,16 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup JDK 11
|
- name: Setup JDK 11
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: microsoft
|
distribution: microsoft
|
||||||
java-version: 11
|
java-version: 11
|
||||||
|
|
||||||
- name: Install .NET 8.0.x
|
- name: Install .NET 8.0.x
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "8.0.x"
|
dotnet-version: "8.0.x"
|
||||||
|
|
||||||
@ -126,10 +126,10 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install .NET 8.0.x
|
- name: Install .NET 8.0.x
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "8.0.x"
|
dotnet-version: "8.0.x"
|
||||||
|
|
||||||
|
2
.github/workflows/diffcalc.yml
vendored
2
.github/workflows/diffcalc.yml
vendored
@ -140,7 +140,7 @@ jobs:
|
|||||||
GOOGLE_CREDS_FILE: ${{ steps.set-outputs.outputs.GOOGLE_CREDS_FILE }}
|
GOOGLE_CREDS_FILE: ${{ steps.set-outputs.outputs.GOOGLE_CREDS_FILE }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout diffcalc-sheet-generator
|
- name: Checkout diffcalc-sheet-generator
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ env.EXECUTION_ID }}
|
path: ${{ env.EXECUTION_ID }}
|
||||||
repository: 'smoogipoo/diffcalc-sheet-generator'
|
repository: 'smoogipoo/diffcalc-sheet-generator'
|
||||||
|
2
.github/workflows/report-nunit.yml
vendored
2
.github/workflows/report-nunit.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
|||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
steps:
|
steps:
|
||||||
- name: Annotate CI run with test results
|
- name: Annotate CI run with test results
|
||||||
uses: dorny/test-reporter@v1.6.0
|
uses: dorny/test-reporter@v1.8.0
|
||||||
with:
|
with:
|
||||||
artifact: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
artifact: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
||||||
name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}})
|
name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}})
|
||||||
|
2
.github/workflows/sentry-release.yml
vendored
2
.github/workflows/sentry-release.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
10
.github/workflows/update-web-mod-definitions.yml
vendored
10
.github/workflows/update-web-mod-definitions.yml
vendored
@ -13,23 +13,23 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Install .NET 8.0.x
|
- name: Install .NET 8.0.x
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "8.0.x"
|
dotnet-version: "8.0.x"
|
||||||
|
|
||||||
- name: Checkout ppy/osu
|
- name: Checkout ppy/osu
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
path: osu
|
path: osu
|
||||||
|
|
||||||
- name: Checkout ppy/osu-tools
|
- name: Checkout ppy/osu-tools
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: ppy/osu-tools
|
repository: ppy/osu-tools
|
||||||
path: osu-tools
|
path: osu-tools
|
||||||
|
|
||||||
- name: Checkout ppy/osu-web
|
- name: Checkout ppy/osu-web
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: ppy/osu-web
|
repository: ppy/osu-web
|
||||||
path: osu-web
|
path: osu-web
|
||||||
@ -43,7 +43,7 @@ jobs:
|
|||||||
working-directory: ./osu-tools
|
working-directory: ./osu-tools
|
||||||
|
|
||||||
- name: Create pull request with changes
|
- name: Create pull request with changes
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
title: Update mod definitions
|
title: Update mod definitions
|
||||||
body: "This PR has been auto-generated to update the mod definitions to match ppy/osu@${{ github.ref_name }}."
|
body: "This PR has been auto-generated to update the mod definitions to match ppy/osu@${{ github.ref_name }}."
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.217.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.221.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.
|
||||||
|
48
osu.Game.Benchmarks/BenchmarkStringComparison.cs
Normal file
48
osu.Game.Benchmarks/BenchmarkStringComparison.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
|
namespace osu.Game.Benchmarks
|
||||||
|
{
|
||||||
|
public class BenchmarkStringComparison
|
||||||
|
{
|
||||||
|
private string[] strings = null!;
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void GlobalSetUp()
|
||||||
|
{
|
||||||
|
strings = new string[10000];
|
||||||
|
|
||||||
|
for (int i = 0; i < strings.Length; ++i)
|
||||||
|
strings[i] = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
for (int i = 0; i < strings.Length; ++i)
|
||||||
|
{
|
||||||
|
if (i % 2 == 0)
|
||||||
|
strings[i] = strings[i].ToUpperInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void OrdinalIgnoreCase() => compare(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void OrdinalSortByCase() => compare(OrdinalSortByCaseStringComparer.DEFAULT);
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void InvariantCulture() => compare(StringComparer.InvariantCulture);
|
||||||
|
|
||||||
|
private void compare(IComparer<string> comparer)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < strings.Length; ++i)
|
||||||
|
{
|
||||||
|
for (int j = i + 1; j < strings.Length; ++j)
|
||||||
|
_ = comparer.Compare(strings[i], strings[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
@ -17,9 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private Playfield playfield { get; set; } = null!;
|
private Playfield playfield { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private IScrollingInfo scrollingInfo { get; set; } = null!;
|
|
||||||
|
|
||||||
protected ScrollingHitObjectContainer HitObjectContainer => ((ManiaPlayfield)playfield).GetColumn(HitObject.Column).HitObjectContainer;
|
protected ScrollingHitObjectContainer HitObjectContainer => ((ManiaPlayfield)playfield).GetColumn(HitObject.Column).HitObjectContainer;
|
||||||
|
|
||||||
protected ManiaSelectionBlueprint(T hitObject)
|
protected ManiaSelectionBlueprint(T hitObject)
|
||||||
@ -28,14 +26,31 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
RelativeSizeAxes = Axes.None;
|
RelativeSizeAxes = Axes.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
private readonly IBindable<ScrollingDirection> directionBindable = new Bindable<ScrollingDirection>();
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
var anchor = scrollingInfo.Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
|
{
|
||||||
|
directionBindable.BindTo(scrollingInfo.Direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
directionBindable.BindValueChanged(onDirectionChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
|
{
|
||||||
|
var anchor = direction.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||||
Anchor = Origin = anchor;
|
Anchor = Origin = anchor;
|
||||||
foreach (var child in InternalChildren)
|
foreach (var child in InternalChildren)
|
||||||
child.Anchor = child.Origin = anchor;
|
child.Anchor = child.Origin = anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
Position = Parent!.ToLocalSpace(HitObjectContainer.ScreenSpacePositionAtTime(HitObject.StartTime)) - AnchorPosition;
|
Position = Parent!.ToLocalSpace(HitObjectContainer.ScreenSpacePositionAtTime(HitObject.StartTime)) - AnchorPosition;
|
||||||
Width = HitObjectContainer.DrawWidth;
|
Width = HitObjectContainer.DrawWidth;
|
||||||
|
@ -7,7 +7,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
@ -149,7 +148,18 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the total amount of columns across all stages in this playfield.
|
/// Retrieves the total amount of columns across all stages in this playfield.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int TotalColumns => stages.Sum(s => s.Columns.Length);
|
public int TotalColumns
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int sum = 0;
|
||||||
|
|
||||||
|
foreach (var stage in stages)
|
||||||
|
sum += stage.Columns.Length;
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Stage getStageByColumn(int column)
|
private Stage getStageByColumn(int column)
|
||||||
{
|
{
|
||||||
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
@ -32,6 +33,27 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
|
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlayfieldBasedSize()
|
||||||
|
{
|
||||||
|
ModFlashlight mod = new OsuModFlashlight();
|
||||||
|
CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = mod,
|
||||||
|
PassCondition = () =>
|
||||||
|
{
|
||||||
|
var flashlightOverlay = Player.DrawableRuleset.Overlays
|
||||||
|
.OfType<ModFlashlight<OsuHitObject>.Flashlight>()
|
||||||
|
.First();
|
||||||
|
|
||||||
|
return Precision.AlmostEquals(mod.DefaultFlashlightSize * .5f, flashlightOverlay.GetSize());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("adjust playfield scale", () =>
|
||||||
|
Player.DrawableRuleset.Playfield.Scale = new Vector2(.5f));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSliderDimsOnlyAfterStartTime()
|
public void TestSliderDimsOnlyAfterStartTime()
|
||||||
{
|
{
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// 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.Diagnostics;
|
using System.Diagnostics;
|
||||||
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;
|
||||||
|
@ -279,10 +279,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
if (HandleUserInput)
|
if (HandleUserInput)
|
||||||
{
|
{
|
||||||
bool isValidSpinningTime = Time.Current >= HitObject.StartTime && Time.Current <= HitObject.EndTime;
|
bool isValidSpinningTime = Time.Current >= HitObject.StartTime && Time.Current <= HitObject.EndTime;
|
||||||
bool correctButtonPressed = (OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
|
|
||||||
|
|
||||||
RotationTracker.Tracking = !Result.HasResult
|
RotationTracker.Tracking = !Result.HasResult
|
||||||
&& correctButtonPressed
|
&& correctButtonPressed()
|
||||||
&& isValidSpinningTime;
|
&& isValidSpinningTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,11 +291,34 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
// Ticks can theoretically be judged at any point in the spinner's duration.
|
// Ticks can theoretically be judged at any point in the spinner's duration.
|
||||||
// A tick must be alive to correctly play back samples,
|
// A tick must be alive to correctly play back samples,
|
||||||
// but for performance reasons, we only want to keep the next tick alive.
|
// but for performance reasons, we only want to keep the next tick alive.
|
||||||
var next = NestedHitObjects.FirstOrDefault(h => !h.Judged);
|
DrawableHitObject nextTick = null;
|
||||||
|
|
||||||
|
foreach (var nested in NestedHitObjects)
|
||||||
|
{
|
||||||
|
if (!nested.Judged)
|
||||||
|
{
|
||||||
|
nextTick = nested;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// See default `LifetimeStart` as set in `DrawableSpinnerTick`.
|
// See default `LifetimeStart` as set in `DrawableSpinnerTick`.
|
||||||
if (next?.LifetimeStart == double.MaxValue)
|
if (nextTick?.LifetimeStart == double.MaxValue)
|
||||||
next.LifetimeStart = HitObject.StartTime;
|
nextTick.LifetimeStart = HitObject.StartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool correctButtonPressed()
|
||||||
|
{
|
||||||
|
if (OsuActionInputManager == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (var action in OsuActionInputManager.PressedActions)
|
||||||
|
{
|
||||||
|
if (action == OsuAction.LeftButton || action == OsuAction.RightButton)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Lists;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
{
|
{
|
||||||
public partial class OsuInputManager : RulesetInputManager<OsuAction>
|
public partial class OsuInputManager : RulesetInputManager<OsuAction>
|
||||||
{
|
{
|
||||||
public IEnumerable<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
|
public SlimReadOnlyListWrapper<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether gameplay input buttons should be allowed.
|
/// Whether gameplay input buttons should be allowed.
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -111,42 +110,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
{
|
{
|
||||||
spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0");
|
spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0");
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
|
||||||
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
if (!spmContainer.IsPresent && drawableSpinner.Result?.TimeStarted != null)
|
updateSpmAlpha();
|
||||||
fadeCounterOnTimeStart();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
private void updateSpmAlpha()
|
||||||
{
|
|
||||||
if (!(drawableHitObject is DrawableSpinner))
|
|
||||||
return;
|
|
||||||
|
|
||||||
fadeCounterOnTimeStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fadeCounterOnTimeStart()
|
|
||||||
{
|
{
|
||||||
if (drawableSpinner.Result?.TimeStarted is double startTime)
|
if (drawableSpinner.Result?.TimeStarted is double startTime)
|
||||||
{
|
spmContainer.Alpha = (float)Math.Clamp((Clock.CurrentTime - startTime) / drawableSpinner.HitObject.TimeFadeIn, 0, 1);
|
||||||
using (BeginAbsoluteSequence(startTime))
|
else
|
||||||
spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn);
|
spmContainer.Alpha = 0;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
|
||||||
{
|
|
||||||
base.Dispose(isDisposing);
|
|
||||||
|
|
||||||
if (drawableSpinner.IsNotNull())
|
|
||||||
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
{
|
{
|
||||||
Texture = textures.Get(@"Gameplay/osu/approachcircle").WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2);
|
Texture = textures.Get(@"Gameplay/osu/approachcircle").WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2);
|
||||||
|
|
||||||
// account for the sprite being used for the default approach circle being taken from stable,
|
// In triangles and argon skins, we expanded hitcircles to take up the full 128 px which are clickable,
|
||||||
// when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
|
// but still use the old approach circle sprite. To make it feel correct (ie. disappear as it collides
|
||||||
|
// with the hitcircle, *not when it overlaps the border*) we need to expand it slightly.
|
||||||
Scale = new Vector2(128 / 118f);
|
Scale = new Vector2(128 / 118f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -117,42 +116,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
{
|
{
|
||||||
spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0");
|
spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0");
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
|
||||||
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
if (!spmContainer.IsPresent && drawableSpinner.Result?.TimeStarted != null)
|
updateSpmAlpha();
|
||||||
fadeCounterOnTimeStart();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
private void updateSpmAlpha()
|
||||||
{
|
|
||||||
if (!(drawableHitObject is DrawableSpinner))
|
|
||||||
return;
|
|
||||||
|
|
||||||
fadeCounterOnTimeStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fadeCounterOnTimeStart()
|
|
||||||
{
|
{
|
||||||
if (drawableSpinner.Result?.TimeStarted is double startTime)
|
if (drawableSpinner.Result?.TimeStarted is double startTime)
|
||||||
{
|
spmContainer.Alpha = (float)Math.Clamp((Clock.CurrentTime - startTime) / drawableSpinner.HitObject.TimeFadeIn, 0, 1);
|
||||||
using (BeginAbsoluteSequence(startTime))
|
else
|
||||||
spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn);
|
spmContainer.Alpha = 0;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
|
||||||
{
|
|
||||||
base.Dispose(isDisposing);
|
|
||||||
|
|
||||||
if (drawableSpinner.IsNotNull())
|
|
||||||
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
@ -33,14 +32,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
drawableSpinner.HitObjectApplied += resetState;
|
drawableSpinner.HitObjectApplied += resetState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RotationRecord lastRecord;
|
||||||
|
|
||||||
public void SetRotation(float currentRotation)
|
public void SetRotation(float currentRotation)
|
||||||
{
|
{
|
||||||
// If we've gone back in time, it's fine to work with a fresh set of records for now
|
// If we've gone back in time, it's fine to work with a fresh set of records for now
|
||||||
if (records.Count > 0 && Time.Current < records.Last().Time)
|
if (records.Count > 0 && Time.Current < lastRecord.Time)
|
||||||
records.Clear();
|
records.Clear();
|
||||||
|
|
||||||
// Never calculate SPM by same time of record to avoid 0 / 0 = NaN or X / 0 = Infinity result.
|
// Never calculate SPM by same time of record to avoid 0 / 0 = NaN or X / 0 = Infinity result.
|
||||||
if (records.Count > 0 && Precision.AlmostEquals(Time.Current, records.Last().Time))
|
if (records.Count > 0 && Precision.AlmostEquals(Time.Current, lastRecord.Time))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (records.Count > 0)
|
if (records.Count > 0)
|
||||||
@ -52,11 +53,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
result.Value = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360;
|
result.Value = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360;
|
||||||
}
|
}
|
||||||
|
|
||||||
records.Enqueue(new RotationRecord { Rotation = currentRotation, Time = Time.Current });
|
records.Enqueue(lastRecord = new RotationRecord { Rotation = currentRotation, Time = Time.Current });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetState(DrawableHitObject hitObject)
|
private void resetState(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
|
lastRecord = default;
|
||||||
result.Value = 0;
|
result.Value = 0;
|
||||||
records.Clear();
|
records.Clear();
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||||
@ -26,10 +25,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
var texture = skin.GetTexture(@"approachcircle");
|
var texture = skin.GetTexture(@"approachcircle");
|
||||||
Debug.Assert(texture != null);
|
Debug.Assert(texture != null);
|
||||||
Texture = texture.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2);
|
Texture = texture.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2);
|
||||||
|
|
||||||
// account for the sprite being used for the default approach circle being taken from stable,
|
|
||||||
// when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
|
|
||||||
Scale = new Vector2(128 / 118f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
// Note that the scale adjust here is 2 instead of DrawableSliderBall.FOLLOW_AREA to match legacy behaviour.
|
// Note that the scale adjust here is 2 instead of DrawableSliderBall.FOLLOW_AREA to match legacy behaviour.
|
||||||
// This means the actual tracking area for gameplay purposes is larger than the sprite (but skins may be accounting for this).
|
// This means the actual tracking area for gameplay purposes is larger than the sprite (but skins may be accounting for this).
|
||||||
this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out)
|
this.ScaleTo(1f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out)
|
||||||
.FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime));
|
.FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,8 +92,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||||
this.FadeOut();
|
this.FadeOut();
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn))
|
||||||
this.FadeInFromZero(spinner.TimeFadeIn / 2);
|
this.FadeInFromZero(spinner.TimeFadeIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -228,7 +227,9 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
int futurePointIndex = ~Source.SmokePoints.BinarySearch(new SmokePoint { Time = CurrentTime }, new SmokePoint.UpperBoundComparer());
|
int futurePointIndex = ~Source.SmokePoints.BinarySearch(new SmokePoint { Time = CurrentTime }, new SmokePoint.UpperBoundComparer());
|
||||||
|
|
||||||
points.Clear();
|
points.Clear();
|
||||||
points.AddRange(Source.SmokePoints.Skip(firstVisiblePointIndex).Take(futurePointIndex - firstVisiblePointIndex));
|
|
||||||
|
for (int i = firstVisiblePointIndex; i < futurePointIndex; i++)
|
||||||
|
points.Add(Source.SmokePoints[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected sealed override void Draw(IRenderer renderer)
|
protected sealed override void Draw(IRenderer renderer)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -17,6 +18,7 @@ using osu.Game.Rulesets.Osu.Edit;
|
|||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Editing
|
namespace osu.Game.Tests.Editing
|
||||||
{
|
{
|
||||||
@ -228,6 +230,28 @@ namespace osu.Game.Tests.Editing
|
|||||||
assertSnappedDistance(400, 400);
|
assertSnappedDistance(400, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUseCurrentSnap()
|
||||||
|
{
|
||||||
|
AddStep("add objects to beatmap", () =>
|
||||||
|
{
|
||||||
|
editorBeatmap.Add(new HitCircle { StartTime = 1000 });
|
||||||
|
editorBeatmap.Add(new HitCircle { Position = new Vector2(100), StartTime = 2000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("hover use current snap button", () => InputManager.MoveMouseTo(composer.ChildrenOfType<ExpandableButton>().Single()));
|
||||||
|
AddUntilStep("use current snap expanded", () => composer.ChildrenOfType<ExpandableButton>().Single().Expanded.Value, () => Is.True);
|
||||||
|
|
||||||
|
AddStep("seek before first object", () => EditorClock.Seek(0));
|
||||||
|
AddUntilStep("use current snap not available", () => composer.ChildrenOfType<ExpandableButton>().Single().Enabled.Value, () => Is.False);
|
||||||
|
|
||||||
|
AddStep("seek to between objects", () => EditorClock.Seek(1500));
|
||||||
|
AddUntilStep("use current snap available", () => composer.ChildrenOfType<ExpandableButton>().Single().Enabled.Value, () => Is.True);
|
||||||
|
|
||||||
|
AddStep("seek after last object", () => EditorClock.Seek(2500));
|
||||||
|
AddUntilStep("use current snap not available", () => composer.ChildrenOfType<ExpandableButton>().Single().Enabled.Value, () => Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
private void assertSnapDistance(float expectedDistance, HitObject? referenceObject, bool includeSliderVelocity)
|
private void assertSnapDistance(float expectedDistance, HitObject? referenceObject, bool includeSliderVelocity)
|
||||||
=> AddAssert($"distance is {expectedDistance}", () => composer.DistanceSnapProvider.GetBeatSnapDistanceAt(referenceObject ?? new HitObject(), includeSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
=> AddAssert($"distance is {expectedDistance}", () => composer.DistanceSnapProvider.GetBeatSnapDistanceAt(referenceObject ?? new HitObject(), includeSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
||||||
|
|
||||||
|
60
osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs
Normal file
60
osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Beatmaps
|
||||||
|
{
|
||||||
|
public partial class TestSceneDifficultyIcon : OsuTestScene
|
||||||
|
{
|
||||||
|
private FillFlowContainer<DifficultyIcon> fill = null!;
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Child = fill = new FillFlowContainer<DifficultyIcon>
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Width = 300,
|
||||||
|
Direction = FillDirection.Full,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CreateDifficultyIcon()
|
||||||
|
{
|
||||||
|
AddRepeatStep("create difficulty icon", () =>
|
||||||
|
{
|
||||||
|
var rulesetInfo = new OsuRuleset().RulesetInfo;
|
||||||
|
var beatmapInfo = new TestBeatmap(rulesetInfo).BeatmapInfo;
|
||||||
|
|
||||||
|
beatmapInfo.Difficulty.ApproachRate = RNG.Next(0, 10);
|
||||||
|
beatmapInfo.Difficulty.CircleSize = RNG.Next(0, 10);
|
||||||
|
beatmapInfo.Difficulty.OverallDifficulty = RNG.Next(0, 10);
|
||||||
|
beatmapInfo.Difficulty.DrainRate = RNG.Next(0, 10);
|
||||||
|
beatmapInfo.StarRating = RNG.NextSingle(0, 10);
|
||||||
|
beatmapInfo.BPM = RNG.Next(60, 300);
|
||||||
|
|
||||||
|
fill.Add(new DifficultyIcon(beatmapInfo, rulesetInfo)
|
||||||
|
{
|
||||||
|
Scale = new Vector2(2),
|
||||||
|
});
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
AddStep("no tooltip", () => fill.ForEach(icon => icon.TooltipType = DifficultyIconTooltipType.None));
|
||||||
|
AddStep("basic tooltip", () => fill.ForEach(icon => icon.TooltipType = DifficultyIconTooltipType.StarRating));
|
||||||
|
AddStep("extended tooltip", () => fill.ForEach(icon => icon.TooltipType = DifficultyIconTooltipType.Extended));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,11 +8,13 @@ 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.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Login;
|
using osu.Game.Overlays.Login;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Users.Drawables;
|
using osu.Game.Users.Drawables;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -25,6 +27,9 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
|
|
||||||
private LoginOverlay loginOverlay = null!;
|
private LoginOverlay loginOverlay = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager configManager { get; set; } = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
@ -156,5 +161,36 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
});
|
});
|
||||||
AddAssert("login overlay is hidden", () => loginOverlay.State.Value == Visibility.Hidden);
|
AddAssert("login overlay is hidden", () => loginOverlay.State.Value == Visibility.Hidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUncheckingRememberUsernameClearsIt()
|
||||||
|
{
|
||||||
|
AddStep("logout", () => API.Logout());
|
||||||
|
AddStep("set username", () => configManager.SetValue(OsuSetting.Username, "test_user"));
|
||||||
|
AddStep("set remember password", () => configManager.SetValue(OsuSetting.SavePassword, true));
|
||||||
|
AddStep("uncheck remember username", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(loginOverlay.ChildrenOfType<SettingsCheckbox>().First());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddAssert("remember username off", () => configManager.Get<bool>(OsuSetting.SaveUsername), () => Is.False);
|
||||||
|
AddAssert("remember password off", () => configManager.Get<bool>(OsuSetting.SavePassword), () => Is.False);
|
||||||
|
AddAssert("username cleared", () => configManager.Get<string>(OsuSetting.Username), () => Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUncheckingRememberPasswordClearsToken()
|
||||||
|
{
|
||||||
|
AddStep("logout", () => API.Logout());
|
||||||
|
AddStep("set token", () => configManager.SetValue(OsuSetting.Token, "test_token"));
|
||||||
|
AddStep("set remember password", () => configManager.SetValue(OsuSetting.SavePassword, true));
|
||||||
|
AddStep("uncheck remember token", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(loginOverlay.ChildrenOfType<SettingsCheckbox>().Last());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddAssert("remember password off", () => configManager.Get<bool>(OsuSetting.SavePassword), () => Is.False);
|
||||||
|
AddAssert("token cleared", () => configManager.Get<string>(OsuSetting.Token), () => Is.Empty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,16 +81,17 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Taken from https://github.com/ppy/osu/issues/13993#issuecomment-885994077
|
// Taken from https://github.com/ppy/osu/issues/13993#issuecomment-885994077
|
||||||
|
new[] { "Problematic", @"My tablet doesn't work :( It's a Huion 420 and it's apparently incompatible with OpenTablet Driver. The warning I get is: ""DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"" and it repeats 4 times on the notification before logging subsequent warnings. Checking the logs, it looks for other Huion tablets before sending the notification (e.g. ""2021-07-23 03:52:33 [verbose]: Detect: Searching for tablet 'Huion WH1409 V2' 20 2021-07-23 03:52:33 [error]: DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"") I use an Arch based installation of Linux and the tablet runs perfectly with Digimend kernel driver, with area configuration, pen pressure, etc. On osu!lazer the cursor disappears until I set it to ""Borderless"" instead of ""Fullscreen"" and even after it shows up, it goes to the bottom left corner as soon as a map starts. I have honestly 0 idea of whats going on at this point.", },
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
"Problematic", @"My tablet doesn't work :(
|
"Code Block", @"User not found! ;_;
|
||||||
It's a Huion 420 and it's apparently incompatible with OpenTablet Driver. The warning I get is: ""DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"" and it repeats 4 times on the notification before logging subsequent warnings.
|
|
||||||
Checking the logs, it looks for other Huion tablets before sending the notification (e.g.
|
There are a few possible reasons for this:
|
||||||
""2021-07-23 03:52:33 [verbose]: Detect: Searching for tablet 'Huion WH1409 V2'
|
|
||||||
20 2021-07-23 03:52:33 [error]: DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"")
|
They may have changed their username.
|
||||||
I use an Arch based installation of Linux and the tablet runs perfectly with Digimend kernel driver, with area configuration, pen pressure, etc. On osu!lazer the cursor disappears until I set it to ""Borderless"" instead of ""Fullscreen"" and even after it shows up, it goes to the bottom left corner as soon as a map starts.
|
The account may be temporarily unavailable due to security or abuse issues.
|
||||||
I have honestly 0 idea of whats going on at this point."
|
You may have made a typo!"
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,6 +137,11 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
@"top_ranks",
|
@"top_ranks",
|
||||||
@"medals"
|
@"medals"
|
||||||
},
|
},
|
||||||
|
RankHighest = new APIUser.UserRankHighest
|
||||||
|
{
|
||||||
|
Rank = 1,
|
||||||
|
UpdatedAt = DateTimeOffset.Now,
|
||||||
|
},
|
||||||
Statistics = new UserStatistics
|
Statistics = new UserStatistics
|
||||||
{
|
{
|
||||||
IsRanked = true,
|
IsRanked = true,
|
||||||
|
42
osu.Game.Tests/Visual/Ranking/TestSceneGradedCircles.cs
Normal file
42
osu.Game.Tests/Visual/Ranking/TestSceneGradedCircles.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Ranking.Expanded.Accuracy;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Ranking
|
||||||
|
{
|
||||||
|
public partial class TestSceneGradedCircles : OsuTestScene
|
||||||
|
{
|
||||||
|
private readonly GradedCircles ring;
|
||||||
|
|
||||||
|
public TestSceneGradedCircles()
|
||||||
|
{
|
||||||
|
ScoreProcessor scoreProcessor = new OsuRuleset().CreateScoreProcessor();
|
||||||
|
double accuracyX = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.X);
|
||||||
|
double accuracyS = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.S);
|
||||||
|
|
||||||
|
double accuracyA = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.A);
|
||||||
|
double accuracyB = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.B);
|
||||||
|
double accuracyC = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.C);
|
||||||
|
|
||||||
|
Add(ring = new GradedCircles(accuracyC, accuracyB, accuracyA, accuracyS, accuracyX)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(400)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
AddSliderStep("Progress", 0.0, 1.0, 1.0, p => ring.Progress = p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -629,7 +629,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
var sets = new List<BeatmapSetInfo>();
|
var sets = new List<BeatmapSetInfo>();
|
||||||
|
|
||||||
const string zzz_string = "zzzzz";
|
const string zzz_lowercase = "zzzzz";
|
||||||
|
const string zzz_uppercase = "ZZZZZ";
|
||||||
|
|
||||||
AddStep("Populuate beatmap sets", () =>
|
AddStep("Populuate beatmap sets", () =>
|
||||||
{
|
{
|
||||||
@ -640,10 +641,16 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
var set = TestResources.CreateTestBeatmapSetInfo();
|
var set = TestResources.CreateTestBeatmapSetInfo();
|
||||||
|
|
||||||
if (i == 4)
|
if (i == 4)
|
||||||
set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_string);
|
set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_uppercase);
|
||||||
|
|
||||||
|
if (i == 8)
|
||||||
|
set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_lowercase);
|
||||||
|
|
||||||
|
if (i == 12)
|
||||||
|
set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_uppercase);
|
||||||
|
|
||||||
if (i == 16)
|
if (i == 16)
|
||||||
set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_string);
|
set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_lowercase);
|
||||||
|
|
||||||
sets.Add(set);
|
sets.Add(set);
|
||||||
}
|
}
|
||||||
@ -652,9 +659,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
loadBeatmaps(sets);
|
loadBeatmaps(sets);
|
||||||
|
|
||||||
AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false));
|
AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false));
|
||||||
AddAssert($"Check {zzz_string} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Author.Username == zzz_string);
|
AddAssert($"Check {zzz_uppercase} is last", () => carousel.BeatmapSets.Last().Metadata.Author.Username == zzz_uppercase);
|
||||||
|
AddAssert($"Check {zzz_lowercase} is second last", () => carousel.BeatmapSets.SkipLast(1).Last().Metadata.Author.Username == zzz_lowercase);
|
||||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
||||||
AddAssert($"Check {zzz_string} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Artist == zzz_string);
|
AddAssert($"Check {zzz_uppercase} is last", () => carousel.BeatmapSets.Last().Metadata.Artist == zzz_uppercase);
|
||||||
|
AddAssert($"Check {zzz_lowercase} is second last", () => carousel.BeatmapSets.SkipLast(1).Last().Metadata.Artist == zzz_lowercase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -788,7 +788,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("all columns visible", () => this.ChildrenOfType<ModColumn>().All(col => col.IsPresent));
|
AddAssert("all columns visible", () => this.ChildrenOfType<ModColumn>().All(col => col.IsPresent));
|
||||||
|
|
||||||
AddStep("set search", () => modSelectOverlay.SearchTerm = "HD");
|
AddStep("set search", () => modSelectOverlay.SearchTerm = "HD");
|
||||||
AddAssert("one column visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 1);
|
AddAssert("two columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 2);
|
||||||
|
|
||||||
AddStep("filter out everything", () => modSelectOverlay.SearchTerm = "Some long search term with no matches");
|
AddStep("filter out everything", () => modSelectOverlay.SearchTerm = "Some long search term with no matches");
|
||||||
AddAssert("no columns visible", () => this.ChildrenOfType<ModColumn>().All(col => !col.IsPresent));
|
AddAssert("no columns visible", () => this.ChildrenOfType<ModColumn>().All(col => !col.IsPresent));
|
||||||
@ -812,7 +812,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("all columns visible", () => this.ChildrenOfType<ModColumn>().All(col => col.IsPresent));
|
AddAssert("all columns visible", () => this.ChildrenOfType<ModColumn>().All(col => col.IsPresent));
|
||||||
|
|
||||||
AddStep("set search", () => modSelectOverlay.SearchTerm = "fail");
|
AddStep("set search", () => modSelectOverlay.SearchTerm = "fail");
|
||||||
AddAssert("one column visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 2);
|
AddAssert("one column visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 1);
|
||||||
|
|
||||||
AddStep("hide", () => modSelectOverlay.Hide());
|
AddStep("hide", () => modSelectOverlay.Hide());
|
||||||
AddStep("show", () => modSelectOverlay.Show());
|
AddStep("show", () => modSelectOverlay.Show());
|
||||||
|
@ -361,13 +361,20 @@ namespace osu.Game.Beatmaps
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void DeleteVideos(List<BeatmapSetInfo> items, bool silent = false)
|
public void DeleteVideos(List<BeatmapSetInfo> items, bool silent = false)
|
||||||
{
|
{
|
||||||
if (items.Count == 0) return;
|
const string no_videos_message = "No videos found to delete!";
|
||||||
|
|
||||||
|
if (items.Count == 0)
|
||||||
|
{
|
||||||
|
if (!silent)
|
||||||
|
PostNotification?.Invoke(new ProgressCompletionNotification { Text = no_videos_message });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var notification = new ProgressNotification
|
var notification = new ProgressNotification
|
||||||
{
|
{
|
||||||
Progress = 0,
|
Progress = 0,
|
||||||
Text = $"Preparing to delete all {HumanisedModelName} videos...",
|
Text = $"Preparing to delete all {HumanisedModelName} videos...",
|
||||||
CompletionText = "No videos found to delete!",
|
CompletionText = no_videos_message,
|
||||||
State = ProgressNotificationState.Active,
|
State = ProgressNotificationState.Active,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -31,14 +32,16 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether to display a tooltip on hover. Only works if a beatmap was provided at construction time.
|
/// Which type of tooltip to show. Only works if a beatmap was provided at construction time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ShowTooltip { get; set; } = true;
|
public DifficultyIconTooltipType TooltipType { get; set; } = DifficultyIconTooltipType.StarRating;
|
||||||
|
|
||||||
private readonly IBeatmapInfo? beatmap;
|
private readonly IBeatmapInfo? beatmap;
|
||||||
|
|
||||||
private readonly IRulesetInfo ruleset;
|
private readonly IRulesetInfo ruleset;
|
||||||
|
|
||||||
|
private readonly Mod[]? mods;
|
||||||
|
|
||||||
private Drawable background = null!;
|
private Drawable background = null!;
|
||||||
|
|
||||||
private readonly Container iconContainer;
|
private readonly Container iconContainer;
|
||||||
@ -58,11 +61,14 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
/// Creates a new <see cref="DifficultyIcon"/>. Will use provided beatmap's <see cref="BeatmapInfo.StarRating"/> for initial value.
|
/// Creates a new <see cref="DifficultyIcon"/>. Will use provided beatmap's <see cref="BeatmapInfo.StarRating"/> for initial value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="beatmap">The beatmap to be displayed in the tooltip, and to be used for the initial star rating value.</param>
|
/// <param name="beatmap">The beatmap to be displayed in the tooltip, and to be used for the initial star rating value.</param>
|
||||||
|
/// <param name="mods">An array of mods to account for in the calculations</param>
|
||||||
/// <param name="ruleset">An optional ruleset to be used for the icon display, in place of the beatmap's ruleset.</param>
|
/// <param name="ruleset">An optional ruleset to be used for the icon display, in place of the beatmap's ruleset.</param>
|
||||||
public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null)
|
public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null, Mod[]? mods = null)
|
||||||
: this(ruleset ?? beatmap.Ruleset)
|
: this(ruleset ?? beatmap.Ruleset)
|
||||||
{
|
{
|
||||||
this.beatmap = beatmap;
|
this.beatmap = beatmap;
|
||||||
|
this.mods = mods;
|
||||||
|
|
||||||
Current.Value = new StarDifficulty(beatmap.StarRating, 0);
|
Current.Value = new StarDifficulty(beatmap.StarRating, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,6 +133,24 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
GetCustomTooltip() => new DifficultyIconTooltip();
|
GetCustomTooltip() => new DifficultyIconTooltip();
|
||||||
|
|
||||||
DifficultyIconTooltipContent IHasCustomTooltip<DifficultyIconTooltipContent>.
|
DifficultyIconTooltipContent IHasCustomTooltip<DifficultyIconTooltipContent>.
|
||||||
TooltipContent => (ShowTooltip && beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current) : null)!;
|
TooltipContent => (TooltipType != DifficultyIconTooltipType.None && beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current, ruleset, mods, TooltipType) : null)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DifficultyIconTooltipType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No tooltip.
|
||||||
|
/// </summary>
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Star rating only.
|
||||||
|
/// </summary>
|
||||||
|
StarRating,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Star rating, OD, HP, CS, AR, length, BPM, and max combo.
|
||||||
|
/// </summary>
|
||||||
|
Extended,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -11,14 +11,25 @@ using osu.Framework.Graphics.Cursor;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps.Drawables
|
namespace osu.Game.Beatmaps.Drawables
|
||||||
{
|
{
|
||||||
internal partial class DifficultyIconTooltip : VisibilityContainer, ITooltip<DifficultyIconTooltipContent>
|
internal partial class DifficultyIconTooltip : VisibilityContainer, ITooltip<DifficultyIconTooltipContent>
|
||||||
{
|
{
|
||||||
private OsuSpriteText difficultyName;
|
private OsuSpriteText difficultyName = null!;
|
||||||
private StarRatingDisplay starRating;
|
private StarRatingDisplay starRating = null!;
|
||||||
|
private OsuSpriteText overallDifficulty = null!;
|
||||||
|
private OsuSpriteText drainRate = null!;
|
||||||
|
private OsuSpriteText circleSize = null!;
|
||||||
|
private OsuSpriteText approachRate = null!;
|
||||||
|
private OsuSpriteText bpm = null!;
|
||||||
|
private OsuSpriteText length = null!;
|
||||||
|
|
||||||
|
private FillFlowContainer difficultyFillFlowContainer = null!;
|
||||||
|
private FillFlowContainer miscFillFlowContainer = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
@ -31,7 +42,6 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
Alpha = 0.9f,
|
|
||||||
Colour = colours.Gray3,
|
Colour = colours.Gray3,
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
@ -49,19 +59,49 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold),
|
Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold)
|
||||||
},
|
},
|
||||||
starRating = new StarRatingDisplay(default, StarRatingDisplaySize.Small)
|
starRating = new StarRatingDisplay(default, StarRatingDisplaySize.Small)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre
|
||||||
|
},
|
||||||
|
difficultyFillFlowContainer = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
Alpha = 0,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(5),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
circleSize = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) },
|
||||||
|
drainRate = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) },
|
||||||
|
overallDifficulty = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) },
|
||||||
|
approachRate = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
miscFillFlowContainer = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Alpha = 0,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(5),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
length = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) },
|
||||||
|
bpm = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) },
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private DifficultyIconTooltipContent displayedContent;
|
private DifficultyIconTooltipContent? displayedContent;
|
||||||
|
|
||||||
public void SetContent(DifficultyIconTooltipContent content)
|
public void SetContent(DifficultyIconTooltipContent content)
|
||||||
{
|
{
|
||||||
@ -72,6 +112,45 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
|
|
||||||
starRating.Current.BindTarget = displayedContent.Difficulty;
|
starRating.Current.BindTarget = displayedContent.Difficulty;
|
||||||
difficultyName.Text = displayedContent.BeatmapInfo.DifficultyName;
|
difficultyName.Text = displayedContent.BeatmapInfo.DifficultyName;
|
||||||
|
|
||||||
|
if (displayedContent.TooltipType == DifficultyIconTooltipType.StarRating)
|
||||||
|
{
|
||||||
|
difficultyFillFlowContainer.Hide();
|
||||||
|
miscFillFlowContainer.Hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
difficultyFillFlowContainer.Show();
|
||||||
|
miscFillFlowContainer.Show();
|
||||||
|
|
||||||
|
double rate = 1;
|
||||||
|
|
||||||
|
if (displayedContent.Mods != null)
|
||||||
|
{
|
||||||
|
foreach (var mod in displayedContent.Mods.OfType<IApplicableToRate>())
|
||||||
|
rate = mod.ApplyToRate(0, rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
double bpmAdjusted = displayedContent.BeatmapInfo.BPM * rate;
|
||||||
|
|
||||||
|
BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(displayedContent.BeatmapInfo.Difficulty);
|
||||||
|
|
||||||
|
if (displayedContent.Mods != null)
|
||||||
|
{
|
||||||
|
foreach (var mod in displayedContent.Mods.OfType<IApplicableToDifficulty>())
|
||||||
|
mod.ApplyToDifficulty(originalDifficulty);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ruleset ruleset = displayedContent.Ruleset.CreateInstance();
|
||||||
|
BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate);
|
||||||
|
|
||||||
|
circleSize.Text = @"CS: " + adjustedDifficulty.CircleSize.ToString(@"0.##");
|
||||||
|
drainRate.Text = @" HP: " + adjustedDifficulty.DrainRate.ToString(@"0.##");
|
||||||
|
approachRate.Text = @" AR: " + adjustedDifficulty.ApproachRate.ToString(@"0.##");
|
||||||
|
overallDifficulty.Text = @" OD: " + adjustedDifficulty.OverallDifficulty.ToString(@"0.##");
|
||||||
|
|
||||||
|
length.Text = "Length: " + TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate).ToString(@"mm\:ss");
|
||||||
|
bpm.Text = " BPM: " + Math.Round(bpmAdjusted, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Move(Vector2 pos) => Position = pos;
|
public void Move(Vector2 pos) => Position = pos;
|
||||||
@ -85,11 +164,20 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
{
|
{
|
||||||
public readonly IBeatmapInfo BeatmapInfo;
|
public readonly IBeatmapInfo BeatmapInfo;
|
||||||
public readonly IBindable<StarDifficulty> Difficulty;
|
public readonly IBindable<StarDifficulty> Difficulty;
|
||||||
|
public readonly IRulesetInfo Ruleset;
|
||||||
|
public readonly Mod[]? Mods;
|
||||||
|
public readonly DifficultyIconTooltipType TooltipType;
|
||||||
|
|
||||||
public DifficultyIconTooltipContent(IBeatmapInfo beatmapInfo, IBindable<StarDifficulty> difficulty)
|
public DifficultyIconTooltipContent(IBeatmapInfo beatmapInfo, IBindable<StarDifficulty> difficulty, IRulesetInfo rulesetInfo, Mod[]? mods, DifficultyIconTooltipType tooltipType)
|
||||||
{
|
{
|
||||||
|
if (tooltipType == DifficultyIconTooltipType.None)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(tooltipType), tooltipType, "Cannot instantiate a tooltip without a type");
|
||||||
|
|
||||||
BeatmapInfo = beatmapInfo;
|
BeatmapInfo = beatmapInfo;
|
||||||
Difficulty = difficulty;
|
Difficulty = difficulty;
|
||||||
|
Ruleset = rulesetInfo;
|
||||||
|
Mods = mods;
|
||||||
|
TooltipType = tooltipType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,12 +77,19 @@ namespace osu.Game.Configuration
|
|||||||
|
|
||||||
SetDefault(OsuSetting.SavePassword, false).ValueChanged += enabled =>
|
SetDefault(OsuSetting.SavePassword, false).ValueChanged += enabled =>
|
||||||
{
|
{
|
||||||
if (enabled.NewValue) SetValue(OsuSetting.SaveUsername, true);
|
if (enabled.NewValue)
|
||||||
|
SetValue(OsuSetting.SaveUsername, true);
|
||||||
|
else
|
||||||
|
GetBindable<string>(OsuSetting.Token).SetDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
SetDefault(OsuSetting.SaveUsername, true).ValueChanged += enabled =>
|
SetDefault(OsuSetting.SaveUsername, true).ValueChanged += enabled =>
|
||||||
{
|
{
|
||||||
if (!enabled.NewValue) SetValue(OsuSetting.SavePassword, false);
|
if (!enabled.NewValue)
|
||||||
|
{
|
||||||
|
GetBindable<string>(OsuSetting.Username).SetDefault();
|
||||||
|
SetValue(OsuSetting.SavePassword, false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
SetDefault(OsuSetting.ExternalLinkWarning, true);
|
SetDefault(OsuSetting.ExternalLinkWarning, true);
|
||||||
|
@ -105,7 +105,12 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Delete(List<TModel> items, bool silent = false)
|
public void Delete(List<TModel> items, bool silent = false)
|
||||||
{
|
{
|
||||||
if (items.Count == 0) return;
|
if (items.Count == 0)
|
||||||
|
{
|
||||||
|
if (!silent)
|
||||||
|
PostNotification?.Invoke(new ProgressCompletionNotification { Text = $"No {HumanisedModelName}s found to delete!" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var notification = new ProgressNotification
|
var notification = new ProgressNotification
|
||||||
{
|
{
|
||||||
@ -142,7 +147,12 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Undelete(List<TModel> items, bool silent = false)
|
public void Undelete(List<TModel> items, bool silent = false)
|
||||||
{
|
{
|
||||||
if (!items.Any()) return;
|
if (!items.Any())
|
||||||
|
{
|
||||||
|
if (!silent)
|
||||||
|
PostNotification?.Invoke(new ProgressCompletionNotification { Text = $"No {HumanisedModelName}s found to restore!" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var notification = new ProgressNotification
|
var notification = new ProgressNotification
|
||||||
{
|
{
|
||||||
|
@ -10,11 +10,11 @@ using osu.Game.Overlays;
|
|||||||
|
|
||||||
namespace osu.Game.Graphics.Containers.Markdown
|
namespace osu.Game.Graphics.Containers.Markdown
|
||||||
{
|
{
|
||||||
public partial class OsuMarkdownFencedCodeBlock : MarkdownFencedCodeBlock
|
public partial class OsuMarkdownCodeBlock : MarkdownCodeBlock
|
||||||
{
|
{
|
||||||
// TODO : change to monospace font for this component
|
// TODO : change to monospace font for this component
|
||||||
public OsuMarkdownFencedCodeBlock(FencedCodeBlock fencedCodeBlock)
|
public OsuMarkdownCodeBlock(CodeBlock codeBlock)
|
||||||
: base(fencedCodeBlock)
|
: base(codeBlock)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +67,7 @@ namespace osu.Game.Graphics.Containers.Markdown
|
|||||||
|
|
||||||
protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new OsuMarkdownHeading(headingBlock);
|
protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new OsuMarkdownHeading(headingBlock);
|
||||||
|
|
||||||
protected override MarkdownFencedCodeBlock CreateFencedCodeBlock(FencedCodeBlock fencedCodeBlock) => new OsuMarkdownFencedCodeBlock(fencedCodeBlock);
|
protected override MarkdownCodeBlock CreateCodeBlock(CodeBlock codeBlock) => new OsuMarkdownCodeBlock(codeBlock);
|
||||||
|
|
||||||
protected override MarkdownSeparator CreateSeparator(ThematicBreakBlock thematicBlock) => new OsuMarkdownSeparator();
|
protected override MarkdownSeparator CreateSeparator(ThematicBreakBlock thematicBlock) => new OsuMarkdownSeparator();
|
||||||
|
|
||||||
|
@ -104,16 +104,31 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString DeletedAllCollections => new TranslatableString(getKey(@"deleted_all_collections"), @"Deleted all collections!");
|
public static LocalisableString DeletedAllCollections => new TranslatableString(getKey(@"deleted_all_collections"), @"Deleted all collections!");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "No collections found to delete!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString NoCollectionsFoundToDelete => new TranslatableString(getKey(@"no_collections_found_to_delete"), @"No collections found to delete!");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Deleted all mod presets!"
|
/// "Deleted all mod presets!"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString DeletedAllModPresets => new TranslatableString(getKey(@"deleted_all_mod_presets"), @"Deleted all mod presets!");
|
public static LocalisableString DeletedAllModPresets => new TranslatableString(getKey(@"deleted_all_mod_presets"), @"Deleted all mod presets!");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "No mod presets found to delete!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString NoModPresetsFoundToDelete => new TranslatableString(getKey(@"no_mod_presets_found_to_delete"), @"No mod presets found to delete!");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Restored all deleted mod presets!"
|
/// "Restored all deleted mod presets!"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString RestoredAllDeletedModPresets => new TranslatableString(getKey(@"restored_all_deleted_mod_presets"), @"Restored all deleted mod presets!");
|
public static LocalisableString RestoredAllDeletedModPresets => new TranslatableString(getKey(@"restored_all_deleted_mod_presets"), @"Restored all deleted mod presets!");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "No mod presets found to restore!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString NoModPresetsFoundToRestore => new TranslatableString(getKey(@"no_mod_presets_found_to_restore"), @"No mod presets found to restore!");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Please select your osu!stable install location"
|
/// "Please select your osu!stable install location"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -34,6 +34,19 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
[JsonProperty(@"previous_usernames")]
|
[JsonProperty(@"previous_usernames")]
|
||||||
public string[] PreviousUsernames;
|
public string[] PreviousUsernames;
|
||||||
|
|
||||||
|
[JsonProperty(@"rank_highest")]
|
||||||
|
[CanBeNull]
|
||||||
|
public UserRankHighest RankHighest;
|
||||||
|
|
||||||
|
public class UserRankHighest
|
||||||
|
{
|
||||||
|
[JsonProperty(@"rank")]
|
||||||
|
public int Rank;
|
||||||
|
|
||||||
|
[JsonProperty(@"updated_at")]
|
||||||
|
public DateTimeOffset UpdatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
[JsonProperty(@"country_code")]
|
[JsonProperty(@"country_code")]
|
||||||
private string countryCodeString;
|
private string countryCodeString;
|
||||||
|
|
||||||
|
@ -297,7 +297,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
|||||||
},
|
},
|
||||||
icon = new DifficultyIcon(beatmapInfo, ruleset)
|
icon = new DifficultyIcon(beatmapInfo, ruleset)
|
||||||
{
|
{
|
||||||
ShowTooltip = false,
|
TooltipType = DifficultyIconTooltipType.None,
|
||||||
Current = { Value = new StarDifficulty(beatmapInfo.StarRating, 0) },
|
Current = { Value = new StarDifficulty(beatmapInfo.StarRating, 0) },
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -66,41 +66,37 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
Colour = colourProvider.Background4,
|
Colour = colourProvider.Background4,
|
||||||
Alpha = 0f,
|
Alpha = 0f,
|
||||||
},
|
},
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Left = 18, Right = 10 },
|
Padding = new MarginPadding { Left = 18, Right = 10 },
|
||||||
Child = new GridContainer
|
ColumnDimensions = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
ColumnDimensions = new[]
|
new Dimension(),
|
||||||
{
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
new Dimension(),
|
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
|
||||||
},
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new Drawable?[]
|
|
||||||
{
|
|
||||||
createIcon(),
|
|
||||||
text = new TruncatingSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Text = Channel.Name,
|
|
||||||
Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold),
|
|
||||||
Colour = colourProvider.Light3,
|
|
||||||
Margin = new MarginPadding { Bottom = 2 },
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
},
|
|
||||||
createMentionPill(),
|
|
||||||
close = createCloseButton(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable?[]
|
||||||
|
{
|
||||||
|
createIcon(),
|
||||||
|
text = new TruncatingSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Text = Channel.Name,
|
||||||
|
Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold),
|
||||||
|
Colour = colourProvider.Light3,
|
||||||
|
Margin = new MarginPadding { Bottom = 2 },
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
},
|
||||||
|
createMentionPill(),
|
||||||
|
close = createCloseButton(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Action = () => OnRequestSelect?.Invoke(Channel);
|
Action = () => OnRequestSelect?.Invoke(Channel);
|
||||||
|
@ -19,6 +19,7 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
namespace osu.Game.Overlays.Mods
|
||||||
@ -168,7 +169,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
foreach (var mod in mods.Value.OfType<IApplicableToRate>())
|
foreach (var mod in mods.Value.OfType<IApplicableToRate>())
|
||||||
rate = mod.ApplyToRate(0, rate);
|
rate = mod.ApplyToRate(0, rate);
|
||||||
|
|
||||||
bpmDisplay.Current.Value = BeatmapInfo.Value.BPM * rate;
|
bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate);
|
||||||
|
|
||||||
BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty);
|
BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty);
|
||||||
|
|
||||||
@ -194,11 +195,11 @@ namespace osu.Game.Overlays.Mods
|
|||||||
RightContent.FadeTo(Collapsed.Value && !IsHovered ? 0 : 1, transition_duration, Easing.OutQuint);
|
RightContent.FadeTo(Collapsed.Value && !IsHovered ? 0 : 1, transition_duration, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class BPMDisplay : RollingCounter<double>
|
private partial class BPMDisplay : RollingCounter<int>
|
||||||
{
|
{
|
||||||
protected override double RollingDuration => 250;
|
protected override double RollingDuration => 250;
|
||||||
|
|
||||||
protected override LocalisableString FormatCount(double count) => count.ToLocalisableString("0 BPM");
|
protected override LocalisableString FormatCount(int count) => count.ToLocalisableString("0 BPM");
|
||||||
|
|
||||||
protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText
|
protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText
|
||||||
{
|
{
|
||||||
|
@ -77,11 +77,11 @@ namespace osu.Game.Overlays.Mods
|
|||||||
/// <seealso cref="ModState.Visible"/>
|
/// <seealso cref="ModState.Visible"/>
|
||||||
public bool Visible => modState.Visible;
|
public bool Visible => modState.Visible;
|
||||||
|
|
||||||
public override IEnumerable<LocalisableString> FilterTerms => new[]
|
public override IEnumerable<LocalisableString> FilterTerms => new LocalisableString[]
|
||||||
{
|
{
|
||||||
Mod.Name,
|
Mod.Name,
|
||||||
|
Mod.Name.Replace(" ", string.Empty),
|
||||||
Mod.Acronym,
|
Mod.Acronym,
|
||||||
Mod.Description
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public override bool MatchingFilter
|
public override bool MatchingFilter
|
||||||
|
@ -143,6 +143,13 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0;
|
scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0;
|
||||||
|
|
||||||
detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||||
|
|
||||||
|
var rankHighest = user?.RankHighest;
|
||||||
|
|
||||||
|
detailGlobalRank.ContentTooltipText = rankHighest != null
|
||||||
|
? UsersStrings.ShowRankHighest(rankHighest.Rank.ToLocalisableString("\\##,##0"), rankHighest.UpdatedAt.ToLocalisableString(@"d MMM yyyy"))
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||||
|
|
||||||
rankGraph.Statistics.Value = user?.Statistics;
|
rankGraph.Statistics.Value = user?.Statistics;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
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.Cursor;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -13,7 +14,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
public partial class ProfileValueDisplay : CompositeDrawable
|
public partial class ProfileValueDisplay : CompositeDrawable
|
||||||
{
|
{
|
||||||
private readonly OsuSpriteText title;
|
private readonly OsuSpriteText title;
|
||||||
private readonly OsuSpriteText content;
|
private readonly ContentText content;
|
||||||
|
|
||||||
public LocalisableString Title
|
public LocalisableString Title
|
||||||
{
|
{
|
||||||
@ -25,6 +26,11 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
set => content.Text = value;
|
set => content.Text = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LocalisableString ContentTooltipText
|
||||||
|
{
|
||||||
|
set => content.TooltipText = value;
|
||||||
|
}
|
||||||
|
|
||||||
public ProfileValueDisplay(bool big = false, int minimumWidth = 60)
|
public ProfileValueDisplay(bool big = false, int minimumWidth = 60)
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
@ -38,9 +44,9 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
{
|
{
|
||||||
Font = OsuFont.GetFont(size: 12)
|
Font = OsuFont.GetFont(size: 12)
|
||||||
},
|
},
|
||||||
content = new OsuSpriteText
|
content = new ContentText
|
||||||
{
|
{
|
||||||
Font = OsuFont.GetFont(size: big ? 30 : 20, weight: FontWeight.Light)
|
Font = OsuFont.GetFont(size: big ? 30 : 20, weight: FontWeight.Light),
|
||||||
},
|
},
|
||||||
new Container // Add a minimum size to the FillFlowContainer
|
new Container // Add a minimum size to the FillFlowContainer
|
||||||
{
|
{
|
||||||
@ -56,5 +62,10 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
title.Colour = colourProvider.Content1;
|
title.Colour = colourProvider.Content1;
|
||||||
content.Colour = colourProvider.Content2;
|
content.Colour = colourProvider.Content2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private partial class ContentText : OsuSpriteText, IHasTooltip
|
||||||
|
{
|
||||||
|
public LocalisableString TooltipText { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,25 +5,19 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
|
||||||
using osu.Framework.Localisation;
|
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Profile.Header.Components
|
namespace osu.Game.Overlays.Profile.Header.Components
|
||||||
{
|
{
|
||||||
public partial class TotalPlayTime : CompositeDrawable, IHasTooltip
|
public partial class TotalPlayTime : CompositeDrawable
|
||||||
{
|
{
|
||||||
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
|
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
|
||||||
|
|
||||||
public LocalisableString TooltipText { get; set; }
|
|
||||||
|
|
||||||
private ProfileValueDisplay info = null!;
|
private ProfileValueDisplay info = null!;
|
||||||
|
|
||||||
public TotalPlayTime()
|
public TotalPlayTime()
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
TooltipText = "0 hours";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -32,6 +26,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
InternalChild = info = new ProfileValueDisplay(minimumWidth: 140)
|
InternalChild = info = new ProfileValueDisplay(minimumWidth: 140)
|
||||||
{
|
{
|
||||||
Title = UsersStrings.ShowStatsPlayTime,
|
Title = UsersStrings.ShowStatsPlayTime,
|
||||||
|
ContentTooltipText = "0 hours",
|
||||||
};
|
};
|
||||||
|
|
||||||
User.BindValueChanged(updateTime, true);
|
User.BindValueChanged(updateTime, true);
|
||||||
@ -40,7 +35,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
private void updateTime(ValueChangedEvent<UserProfileData?> user)
|
private void updateTime(ValueChangedEvent<UserProfileData?> user)
|
||||||
{
|
{
|
||||||
int? playTime = user.NewValue?.User.Statistics?.PlayTime;
|
int? playTime = user.NewValue?.User.Statistics?.PlayTime;
|
||||||
TooltipText = (playTime ?? 0) / 3600 + " hours";
|
info.ContentTooltipText = (playTime ?? 0) / 3600 + " hours";
|
||||||
info.Content = formatTime(playTime);
|
info.Content = formatTime(playTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,47 +26,42 @@ namespace osu.Game.Overlays.Profile.Header
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = colourProvider.Background5,
|
Colour = colourProvider.Background5,
|
||||||
},
|
},
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING, Vertical = 10 },
|
Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING, Vertical = 10 },
|
||||||
Child = new GridContainer
|
RowDimensions = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
AutoSizeAxes = Axes.Y,
|
},
|
||||||
RowDimensions = new[]
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
new MainDetails
|
||||||
},
|
|
||||||
ColumnDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(),
|
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
|
||||||
},
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new Drawable[]
|
|
||||||
{
|
{
|
||||||
new MainDetails
|
RelativeSizeAxes = Axes.X,
|
||||||
{
|
User = { BindTarget = User }
|
||||||
RelativeSizeAxes = Axes.X,
|
},
|
||||||
User = { BindTarget = User }
|
new Box
|
||||||
},
|
{
|
||||||
new Box
|
RelativeSizeAxes = Axes.Y,
|
||||||
{
|
Width = 2,
|
||||||
RelativeSizeAxes = Axes.Y,
|
Colour = colourProvider.Background6,
|
||||||
Width = 2,
|
Margin = new MarginPadding { Horizontal = 15 }
|
||||||
Colour = colourProvider.Background6,
|
},
|
||||||
Margin = new MarginPadding { Horizontal = 15 }
|
new ExtendedDetails
|
||||||
},
|
{
|
||||||
new ExtendedDetails
|
Anchor = Anchor.CentreLeft,
|
||||||
{
|
Origin = Anchor.CentreLeft,
|
||||||
Anchor = Anchor.CentreLeft,
|
User = { BindTarget = User }
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
User = { BindTarget = User }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
@ -28,15 +27,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
var renderer = config.GetBindable<RendererType>(FrameworkSetting.Renderer);
|
var renderer = config.GetBindable<RendererType>(FrameworkSetting.Renderer);
|
||||||
automaticRendererInUse = renderer.Value == RendererType.Automatic;
|
automaticRendererInUse = renderer.Value == RendererType.Automatic;
|
||||||
|
|
||||||
SettingsEnumDropdown<RendererType> rendererDropdown;
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
rendererDropdown = new RendererSettingsDropdown
|
new RendererSettingsDropdown
|
||||||
{
|
{
|
||||||
LabelText = GraphicsSettingsStrings.Renderer,
|
LabelText = GraphicsSettingsStrings.Renderer,
|
||||||
Current = renderer,
|
Current = renderer,
|
||||||
Items = host.GetPreferredRenderersForCurrentPlatform().Order().Where(t => t != RendererType.Vulkan),
|
Items = host.GetPreferredRenderersForCurrentPlatform().Order()
|
||||||
|
#pragma warning disable CS0612 // Type or member is obsolete
|
||||||
|
.Where(t => t != RendererType.Vulkan && t != RendererType.OpenGLLegacy),
|
||||||
|
#pragma warning restore CS0612 // Type or member is obsolete
|
||||||
Keywords = new[] { @"compatibility", @"directx" },
|
Keywords = new[] { @"compatibility", @"directx" },
|
||||||
},
|
},
|
||||||
// TODO: this needs to be a custom dropdown at some point
|
// TODO: this needs to be a custom dropdown at some point
|
||||||
@ -79,13 +79,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: remove this once we support SDL+android.
|
|
||||||
if (RuntimeInfo.OS == RuntimeInfo.Platform.Android)
|
|
||||||
{
|
|
||||||
rendererDropdown.Items = new[] { RendererType.Automatic, RendererType.OpenGLLegacy };
|
|
||||||
rendererDropdown.SetNoticeText("New renderer support for android is coming soon!", true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class RendererSettingsDropdown : SettingsEnumDropdown<RendererType>
|
private partial class RendererSettingsDropdown : SettingsEnumDropdown<RendererType>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Collections;
|
using osu.Game.Collections;
|
||||||
@ -35,8 +36,20 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
|
|
||||||
private void deleteAllCollections()
|
private void deleteAllCollections()
|
||||||
{
|
{
|
||||||
realm.Write(r => r.RemoveAll<BeatmapCollection>());
|
bool anyDeleted = realm.Write(r =>
|
||||||
notificationOverlay?.Post(new ProgressCompletionNotification { Text = MaintenanceSettingsStrings.DeletedAllCollections });
|
{
|
||||||
|
if (r.All<BeatmapCollection>().Any())
|
||||||
|
{
|
||||||
|
r.RemoveAll<BeatmapCollection>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
notificationOverlay?.Post(new ProgressCompletionNotification { Text = anyDeleted ? MaintenanceSettingsStrings.DeletedAllCollections : MaintenanceSettingsStrings.NoCollectionsFoundToDelete });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
@ -52,36 +53,50 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteAllModPresets() =>
|
private bool deleteAllModPresets() =>
|
||||||
realm.Write(r =>
|
realm.Write(r =>
|
||||||
{
|
{
|
||||||
|
bool anyDeleted = false;
|
||||||
|
|
||||||
foreach (var preset in r.All<ModPreset>())
|
foreach (var preset in r.All<ModPreset>())
|
||||||
|
{
|
||||||
|
anyDeleted |= !preset.DeletePending;
|
||||||
preset.DeletePending = true;
|
preset.DeletePending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return anyDeleted;
|
||||||
});
|
});
|
||||||
|
|
||||||
private void onAllModPresetsDeleted(Task deletionTask)
|
private void onAllModPresetsDeleted(Task<bool> deletionTask)
|
||||||
{
|
{
|
||||||
deleteAllButton.Enabled.Value = true;
|
deleteAllButton.Enabled.Value = true;
|
||||||
|
|
||||||
if (deletionTask.IsCompletedSuccessfully)
|
if (deletionTask.IsCompletedSuccessfully)
|
||||||
notificationOverlay?.Post(new ProgressCompletionNotification { Text = MaintenanceSettingsStrings.DeletedAllModPresets });
|
notificationOverlay?.Post(new ProgressCompletionNotification { Text = deletionTask.GetResultSafely() ? MaintenanceSettingsStrings.DeletedAllModPresets : MaintenanceSettingsStrings.NoModPresetsFoundToDelete });
|
||||||
else if (deletionTask.IsFaulted)
|
else if (deletionTask.IsFaulted)
|
||||||
Logger.Error(deletionTask.Exception, "Failed to delete all mod presets");
|
Logger.Error(deletionTask.Exception, "Failed to delete all mod presets");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void undeleteModPresets() =>
|
private bool undeleteModPresets() =>
|
||||||
realm.Write(r =>
|
realm.Write(r =>
|
||||||
{
|
{
|
||||||
|
bool anyRestored = false;
|
||||||
|
|
||||||
foreach (var preset in r.All<ModPreset>().Where(preset => preset.DeletePending))
|
foreach (var preset in r.All<ModPreset>().Where(preset => preset.DeletePending))
|
||||||
|
{
|
||||||
|
anyRestored |= preset.DeletePending;
|
||||||
preset.DeletePending = false;
|
preset.DeletePending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return anyRestored;
|
||||||
});
|
});
|
||||||
|
|
||||||
private void onModPresetsUndeleted(Task undeletionTask)
|
private void onModPresetsUndeleted(Task<bool> undeletionTask)
|
||||||
{
|
{
|
||||||
undeleteButton.Enabled.Value = true;
|
undeleteButton.Enabled.Value = true;
|
||||||
|
|
||||||
if (undeletionTask.IsCompletedSuccessfully)
|
if (undeletionTask.IsCompletedSuccessfully)
|
||||||
notificationOverlay?.Post(new ProgressCompletionNotification { Text = MaintenanceSettingsStrings.RestoredAllDeletedModPresets });
|
notificationOverlay?.Post(new ProgressCompletionNotification { Text = undeletionTask.GetResultSafely() ? MaintenanceSettingsStrings.RestoredAllDeletedModPresets : MaintenanceSettingsStrings.NoModPresetsFoundToRestore });
|
||||||
else if (undeletionTask.IsFaulted)
|
else if (undeletionTask.IsFaulted)
|
||||||
Logger.Error(undeletionTask.Exception, "Failed to restore mod presets");
|
Logger.Error(undeletionTask.Exception, "Failed to restore mod presets");
|
||||||
}
|
}
|
||||||
|
@ -103,13 +103,14 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
{
|
{
|
||||||
globallyDisableBeatmapSkinSetting();
|
globallyDisableBeatmapSkinSetting();
|
||||||
|
|
||||||
if (lastTargetScreen is MainMenu)
|
|
||||||
PresentGameplay();
|
|
||||||
|
|
||||||
if (skinEditor != null)
|
if (skinEditor != null)
|
||||||
{
|
{
|
||||||
disableNestedInputManagers();
|
disableNestedInputManagers();
|
||||||
skinEditor.Show();
|
skinEditor.Show();
|
||||||
|
|
||||||
|
if (lastTargetScreen is MainMenu)
|
||||||
|
PresentGameplay();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,6 +127,9 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
|
|
||||||
AddInternal(editor);
|
AddInternal(editor);
|
||||||
|
|
||||||
|
if (lastTargetScreen is MainMenu)
|
||||||
|
PresentGameplay();
|
||||||
|
|
||||||
Debug.Assert(lastTargetScreen != null);
|
Debug.Assert(lastTargetScreen != null);
|
||||||
|
|
||||||
SetTarget(lastTargetScreen);
|
SetTarget(lastTargetScreen);
|
||||||
@ -270,7 +274,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
|
|
||||||
Debug.Assert(skinEditor != null);
|
Debug.Assert(skinEditor != null);
|
||||||
|
|
||||||
if (!target.IsLoaded)
|
if (!target.IsLoaded || !skinEditor.IsLoaded)
|
||||||
{
|
{
|
||||||
Scheduler.AddOnce(setTarget, target);
|
Scheduler.AddOnce(setTarget, target);
|
||||||
return;
|
return;
|
||||||
@ -350,7 +354,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
if (!LoadedBeatmapSuccessfully)
|
if (!LoadedBeatmapSuccessfully)
|
||||||
Scheduler.AddDelayed(this.Exit, 3000);
|
Scheduler.AddDelayed(this.Exit, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
if (objTime >= editorClock.CurrentTime)
|
if (objTime >= editorClock.CurrentTime)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (objTime > lastBefore?.StartTime)
|
if (lastBefore == null || objTime > lastBefore.StartTime)
|
||||||
lastBefore = entry.Value.HitObject;
|
lastBefore = entry.Value.HitObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
if (objTime < editorClock.CurrentTime)
|
if (objTime < editorClock.CurrentTime)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (objTime < firstAfter?.StartTime)
|
if (firstAfter == null || objTime < firstAfter.StartTime)
|
||||||
firstAfter = entry.Value.HitObject;
|
firstAfter = entry.Value.HitObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -13,6 +14,7 @@ using osu.Framework.Graphics.Shaders;
|
|||||||
using osu.Framework.Graphics.Shaders.Types;
|
using osu.Framework.Graphics.Shaders.Types;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.OpenGL.Vertices;
|
using osu.Game.Graphics.OpenGL.Vertices;
|
||||||
@ -84,6 +86,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
flashlight.Depth = float.MinValue;
|
flashlight.Depth = float.MinValue;
|
||||||
|
|
||||||
flashlight.Combo.BindTo(Combo);
|
flashlight.Combo.BindTo(Combo);
|
||||||
|
flashlight.GetPlayfieldScale = () => drawableRuleset.Playfield.Scale;
|
||||||
|
|
||||||
drawableRuleset.Overlays.Add(flashlight);
|
drawableRuleset.Overlays.Add(flashlight);
|
||||||
}
|
}
|
||||||
@ -100,6 +103,8 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
public override bool RemoveCompletedTransforms => false;
|
public override bool RemoveCompletedTransforms => false;
|
||||||
|
|
||||||
|
internal Func<Vector2>? GetPlayfieldScale;
|
||||||
|
|
||||||
private readonly float defaultFlashlightSize;
|
private readonly float defaultFlashlightSize;
|
||||||
private readonly float sizeMultiplier;
|
private readonly float sizeMultiplier;
|
||||||
private readonly bool comboBasedSize;
|
private readonly bool comboBasedSize;
|
||||||
@ -139,10 +144,19 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
protected abstract string FragmentShader { get; }
|
protected abstract string FragmentShader { get; }
|
||||||
|
|
||||||
protected float GetSize()
|
public float GetSize()
|
||||||
{
|
{
|
||||||
float size = defaultFlashlightSize * sizeMultiplier;
|
float size = defaultFlashlightSize * sizeMultiplier;
|
||||||
|
|
||||||
|
if (GetPlayfieldScale != null)
|
||||||
|
{
|
||||||
|
Vector2 playfieldScale = GetPlayfieldScale();
|
||||||
|
|
||||||
|
Debug.Assert(Precision.AlmostEquals(Math.Abs(playfieldScale.X), Math.Abs(playfieldScale.Y)),
|
||||||
|
@"Playfield has non-proportional scaling. Flashlight implementations should be revisited with regard to balance.");
|
||||||
|
size *= Math.Abs(playfieldScale.X);
|
||||||
|
}
|
||||||
|
|
||||||
if (isBreakTime.Value)
|
if (isBreakTime.Value)
|
||||||
size *= 2.5f;
|
size *= 2.5f;
|
||||||
else if (comboBasedSize)
|
else if (comboBasedSize)
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public override LocalisableString Description => "Can you still feel the rhythm without music?";
|
public override LocalisableString Description => "Can you still feel the rhythm without music?";
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override bool Ranked => UsesDefaultConfiguration;
|
public override bool Ranked => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class ModMuted<TObject> : ModMuted, IApplicableToDrawableRuleset<TObject>, IApplicableToTrack, IApplicableToScoreProcessor
|
public abstract class ModMuted<TObject> : ModMuted, IApplicableToDrawableRuleset<TObject>, IApplicableToTrack, IApplicableToScoreProcessor
|
||||||
|
@ -11,10 +11,12 @@ using System.Linq;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ListExtensions;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Extensions.TypeExtensions;
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Framework.Lists;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
@ -65,7 +67,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
public virtual IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples;
|
public virtual IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples;
|
||||||
|
|
||||||
private readonly List<DrawableHitObject> nestedHitObjects = new List<DrawableHitObject>();
|
private readonly List<DrawableHitObject> nestedHitObjects = new List<DrawableHitObject>();
|
||||||
public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects;
|
public SlimReadOnlyListWrapper<DrawableHitObject> NestedHitObjects => nestedHitObjects.AsSlimReadOnly();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this object should handle any user input events.
|
/// Whether this object should handle any user input events.
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Performance;
|
using osu.Framework.Graphics.Performance;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
@ -41,7 +40,22 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether <see cref="HitObject"/> and all of its nested objects have been judged.
|
/// Whether <see cref="HitObject"/> and all of its nested objects have been judged.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AllJudged => Judged && NestedEntries.All(h => h.AllJudged);
|
public bool AllJudged
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!Judged)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (var entry in NestedEntries)
|
||||||
|
{
|
||||||
|
if (!entry.AllJudged)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly IBindable<double> startTimeBindable = new BindableDouble();
|
private readonly IBindable<double> startTimeBindable = new BindableDouble();
|
||||||
|
|
||||||
|
@ -47,35 +47,31 @@ namespace osu.Game.Screens.Edit
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = colourProvider.Background4,
|
Colour = colourProvider.Background4,
|
||||||
},
|
},
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = new GridContainer
|
ColumnDimensions = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new Dimension(GridSizeMode.Absolute, 170),
|
||||||
ColumnDimensions = new[]
|
new Dimension(),
|
||||||
{
|
new Dimension(GridSizeMode.Absolute, 220),
|
||||||
new Dimension(GridSizeMode.Absolute, 170),
|
new Dimension(GridSizeMode.Absolute, HitObjectComposer.TOOLBOX_CONTRACTED_SIZE_RIGHT),
|
||||||
new Dimension(),
|
|
||||||
new Dimension(GridSizeMode.Absolute, 220),
|
|
||||||
new Dimension(GridSizeMode.Absolute, HitObjectComposer.TOOLBOX_CONTRACTED_SIZE_RIGHT),
|
|
||||||
},
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
new TimeInfoContainer { RelativeSizeAxes = Axes.Both },
|
|
||||||
new SummaryTimeline { RelativeSizeAxes = Axes.Both },
|
|
||||||
new PlaybackControl { RelativeSizeAxes = Axes.Both },
|
|
||||||
TestGameplayButton = new TestGameplayButton
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Size = new Vector2(1),
|
|
||||||
Action = editor.TestGameplay,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new TimeInfoContainer { RelativeSizeAxes = Axes.Both },
|
||||||
|
new SummaryTimeline { RelativeSizeAxes = Axes.Both },
|
||||||
|
new PlaybackControl { RelativeSizeAxes = Axes.Both },
|
||||||
|
TestGameplayButton = new TestGameplayButton
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Size = new Vector2(1),
|
||||||
|
Action = editor.TestGameplay,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -97,11 +97,14 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
editorClock.Start();
|
editorClock.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly IconUsage play_icon = FontAwesome.Regular.PlayCircle;
|
||||||
|
private static readonly IconUsage pause_icon = FontAwesome.Regular.PauseCircle;
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
playButton.Icon = editorClock.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle;
|
playButton.Icon = editorClock.IsRunning ? pause_icon : play_icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class PlaybackTabControl : OsuTabControl<double>
|
private partial class PlaybackTabControl : OsuTabControl<double>
|
||||||
|
@ -47,11 +47,26 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private double? lastTime;
|
||||||
|
private double? lastBPM;
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
trackTimer.Text = editorClock.CurrentTime.ToEditorFormattedString();
|
|
||||||
bpm.Text = @$"{editorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime).BPM:0} BPM";
|
if (lastTime != editorClock.CurrentTime)
|
||||||
|
{
|
||||||
|
lastTime = editorClock.CurrentTime;
|
||||||
|
trackTimer.Text = editorClock.CurrentTime.ToEditorFormattedString();
|
||||||
|
}
|
||||||
|
|
||||||
|
double newBPM = editorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime).BPM;
|
||||||
|
|
||||||
|
if (lastBPM != newBPM)
|
||||||
|
{
|
||||||
|
lastBPM = newBPM;
|
||||||
|
bpm.Text = @$"{newBPM:0} BPM";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,35 +86,31 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = colourProvider.Background3
|
Colour = colourProvider.Background3
|
||||||
},
|
},
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = new GridContainer
|
Content = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new Drawable[]
|
||||||
Content = new[]
|
|
||||||
{
|
{
|
||||||
new Drawable[]
|
new ChevronButton
|
||||||
{
|
{
|
||||||
new ChevronButton
|
Icon = FontAwesome.Solid.ChevronLeft,
|
||||||
{
|
Action = beatDivisor.SelectPrevious
|
||||||
Icon = FontAwesome.Solid.ChevronLeft,
|
|
||||||
Action = beatDivisor.SelectPrevious
|
|
||||||
},
|
|
||||||
new DivisorDisplay { BeatDivisor = { BindTarget = beatDivisor } },
|
|
||||||
new ChevronButton
|
|
||||||
{
|
|
||||||
Icon = FontAwesome.Solid.ChevronRight,
|
|
||||||
Action = beatDivisor.SelectNext
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
new DivisorDisplay { BeatDivisor = { BindTarget = beatDivisor } },
|
||||||
|
new ChevronButton
|
||||||
|
{
|
||||||
|
Icon = FontAwesome.Solid.ChevronRight,
|
||||||
|
Action = beatDivisor.SelectNext
|
||||||
|
}
|
||||||
},
|
},
|
||||||
ColumnDimensions = new[]
|
},
|
||||||
{
|
ColumnDimensions = new[]
|
||||||
new Dimension(GridSizeMode.Absolute, 20),
|
{
|
||||||
new Dimension(),
|
new Dimension(GridSizeMode.Absolute, 20),
|
||||||
new Dimension(GridSizeMode.Absolute, 20)
|
new Dimension(),
|
||||||
}
|
new Dimension(GridSizeMode.Absolute, 20)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,42 +118,31 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
},
|
},
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Content = new[]
|
||||||
{
|
{
|
||||||
new Container
|
new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new ChevronButton
|
||||||
Child = new GridContainer
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
Icon = FontAwesome.Solid.ChevronLeft,
|
||||||
Content = new[]
|
Action = () => cycleDivisorType(-1)
|
||||||
{
|
},
|
||||||
new Drawable[]
|
new DivisorTypeText { BeatDivisor = { BindTarget = beatDivisor } },
|
||||||
{
|
new ChevronButton
|
||||||
new ChevronButton
|
{
|
||||||
{
|
Icon = FontAwesome.Solid.ChevronRight,
|
||||||
Icon = FontAwesome.Solid.ChevronLeft,
|
Action = () => cycleDivisorType(1)
|
||||||
Action = () => cycleDivisorType(-1)
|
|
||||||
},
|
|
||||||
new DivisorTypeText { BeatDivisor = { BindTarget = beatDivisor } },
|
|
||||||
new ChevronButton
|
|
||||||
{
|
|
||||||
Icon = FontAwesome.Solid.ChevronRight,
|
|
||||||
Action = () => cycleDivisorType(1)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ColumnDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(GridSizeMode.Absolute, 20),
|
|
||||||
new Dimension(),
|
|
||||||
new Dimension(GridSizeMode.Absolute, 20)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
},
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.Absolute, 20),
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.Absolute, 20)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -116,6 +116,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
updateStacking();
|
updateStacking();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly Stack<HitObject> currentConcurrentObjects = new Stack<HitObject>();
|
||||||
|
|
||||||
private void updateStacking()
|
private void updateStacking()
|
||||||
{
|
{
|
||||||
// because only blueprints of objects which are alive (via pooling) are displayed in the timeline, it's feasible to do this every-update.
|
// because only blueprints of objects which are alive (via pooling) are displayed in the timeline, it's feasible to do this every-update.
|
||||||
@ -125,10 +127,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
// after the stack gets this tall, we can presume there is space underneath to draw subsequent blueprints.
|
// after the stack gets this tall, we can presume there is space underneath to draw subsequent blueprints.
|
||||||
const int stack_reset_count = 3;
|
const int stack_reset_count = 3;
|
||||||
|
|
||||||
Stack<HitObject> currentConcurrentObjects = new Stack<HitObject>();
|
currentConcurrentObjects.Clear();
|
||||||
|
|
||||||
foreach (var b in SelectionBlueprints.Reverse())
|
for (int i = SelectionBlueprints.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
|
var b = SelectionBlueprints[i];
|
||||||
|
|
||||||
// remove objects from the stack as long as their end time is in the past.
|
// remove objects from the stack as long as their end time is in the past.
|
||||||
while (currentConcurrentObjects.TryPeek(out HitObject hitObject))
|
while (currentConcurrentObjects.TryPeek(out HitObject hitObject))
|
||||||
{
|
{
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -20,7 +18,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class EditorBeatmapSkin : ISkin
|
public class EditorBeatmapSkin : ISkin
|
||||||
{
|
{
|
||||||
public event Action BeatmapSkinChanged;
|
public event Action? BeatmapSkinChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The underlying beatmap skin.
|
/// The underlying beatmap skin.
|
||||||
@ -38,8 +36,17 @@ namespace osu.Game.Screens.Edit
|
|||||||
Skin = skin;
|
Skin = skin;
|
||||||
|
|
||||||
ComboColours = new BindableList<Colour4>();
|
ComboColours = new BindableList<Colour4>();
|
||||||
if (Skin.Configuration.ComboColours != null)
|
|
||||||
ComboColours.AddRange(Skin.Configuration.ComboColours.Select(c => (Colour4)c));
|
if (Skin.Configuration.ComboColours is IReadOnlyList<Color4> comboColours)
|
||||||
|
{
|
||||||
|
// due to the foibles of how `IHasComboInformation` / `ComboIndexWithOffsets` work,
|
||||||
|
// the actual effective first combo colour that will be used on the beatmap is the one with index 1, not 0.
|
||||||
|
// see also: `IHasComboInformation.UpdateComboInformation`,
|
||||||
|
// https://github.com/peppy/osu-stable-reference/blob/46cd3a10af7cc6cc96f4eba92ef1812dc8c3a27e/osu!/GameModes/Edit/Forms/SongSetup.cs#L233-L234.
|
||||||
|
for (int i = 0; i < comboColours.Count; ++i)
|
||||||
|
ComboColours.Add(comboColours[(i + 1) % comboColours.Count]);
|
||||||
|
}
|
||||||
|
|
||||||
ComboColours.BindCollectionChanged((_, _) => updateColours());
|
ComboColours.BindCollectionChanged((_, _) => updateColours());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,16 +54,23 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
private void updateColours()
|
private void updateColours()
|
||||||
{
|
{
|
||||||
Skin.Configuration.CustomComboColours = ComboColours.Select(c => (Color4)c).ToList();
|
// performs the inverse of the index rotation operation described in the ctor.
|
||||||
|
Skin.Configuration.CustomComboColours.Clear();
|
||||||
|
for (int i = 0; i < ComboColours.Count; ++i)
|
||||||
|
Skin.Configuration.CustomComboColours.Add(ComboColours[(ComboColours.Count + i - 1) % ComboColours.Count]);
|
||||||
invokeSkinChanged();
|
invokeSkinChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Delegated ISkin implementation
|
#region Delegated ISkin implementation
|
||||||
|
|
||||||
public Drawable GetDrawableComponent(ISkinComponentLookup lookup) => Skin.GetDrawableComponent(lookup);
|
public Drawable? GetDrawableComponent(ISkinComponentLookup lookup) => Skin.GetDrawableComponent(lookup);
|
||||||
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Skin.GetTexture(componentName, wrapModeS, wrapModeT);
|
public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Skin.GetTexture(componentName, wrapModeS, wrapModeT);
|
||||||
public ISample GetSample(ISampleInfo sampleInfo) => Skin.GetSample(sampleInfo);
|
public ISample? GetSample(ISampleInfo sampleInfo) => Skin.GetSample(sampleInfo);
|
||||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => Skin.GetConfig<TLookup, TValue>(lookup);
|
|
||||||
|
public IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
|
where TLookup : notnull
|
||||||
|
where TValue : notnull
|
||||||
|
=> Skin.GetConfig<TLookup, TValue>(lookup);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
@ -57,37 +57,32 @@ namespace osu.Game.Screens.Edit
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = colourProvider.Background4
|
Colour = colourProvider.Background4
|
||||||
},
|
},
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
Name = "Timeline content",
|
Name = "Timeline content",
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Padding = new MarginPadding { Horizontal = PADDING, Top = PADDING },
|
Padding = new MarginPadding { Horizontal = PADDING, Top = PADDING },
|
||||||
Child = new GridContainer
|
Content = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
new Drawable[]
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Content = new[]
|
|
||||||
{
|
{
|
||||||
new Drawable[]
|
TimelineContent = new Container
|
||||||
{
|
{
|
||||||
TimelineContent = new Container
|
RelativeSizeAxes = Axes.X,
|
||||||
{
|
AutoSizeAxes = Axes.Y,
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
RowDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
|
||||||
},
|
|
||||||
ColumnDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(),
|
|
||||||
new Dimension(GridSizeMode.Absolute, 90),
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
},
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.Absolute, 90),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -65,35 +65,28 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding(padding),
|
Padding = new MarginPadding(padding),
|
||||||
Children = new Drawable[]
|
ColumnDimensions = new[]
|
||||||
{
|
{
|
||||||
new GridContainer
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
new Dimension()
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
metronome = new MetronomeDisplay
|
||||||
ColumnDimensions = new[]
|
|
||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
Anchor = Anchor.CentreLeft,
|
||||||
new Dimension()
|
Origin = Anchor.CentreLeft,
|
||||||
},
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
metronome = new MetronomeDisplay
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
},
|
|
||||||
new WaveformComparisonDisplay()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
new WaveformComparisonDisplay()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
|
@ -34,25 +34,21 @@ namespace osu.Game.Screens.Edit.Verify
|
|||||||
InterpretedDifficulty.Default = StarDifficulty.GetDifficultyRating(EditorBeatmap.BeatmapInfo.StarRating);
|
InterpretedDifficulty.Default = StarDifficulty.GetDifficultyRating(EditorBeatmap.BeatmapInfo.StarRating);
|
||||||
InterpretedDifficulty.SetDefault();
|
InterpretedDifficulty.SetDefault();
|
||||||
|
|
||||||
Child = new Container
|
Child = new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = new GridContainer
|
ColumnDimensions = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new Dimension(),
|
||||||
ColumnDimensions = new[]
|
new Dimension(GridSizeMode.Absolute, 250),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new Dimension(),
|
IssueList = new IssueList(),
|
||||||
new Dimension(GridSizeMode.Absolute, 250),
|
new IssueSettings(),
|
||||||
},
|
},
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
IssueList = new IssueList(),
|
|
||||||
new IssueSettings(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
buttonArea.AddRange(new Drawable[]
|
buttonArea.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, OsuIcon.Settings, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O),
|
new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, OsuIcon.Settings, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O, Key.S),
|
||||||
backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.PrevCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel,
|
backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.PrevCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel,
|
||||||
-WEDGE_WIDTH)
|
-WEDGE_WIDTH)
|
||||||
{
|
{
|
||||||
@ -132,11 +132,11 @@ namespace osu.Game.Screens.Menu
|
|||||||
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Tournament, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L));
|
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Tournament, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L));
|
||||||
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
|
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
|
||||||
|
|
||||||
buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), () => OnEditBeatmap?.Invoke(), WEDGE_WIDTH, Key.B));
|
buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), () => OnEditBeatmap?.Invoke(), WEDGE_WIDTH, Key.B, Key.E));
|
||||||
buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", OsuIcon.SkinB, new Color4(220, 160, 0, 255), () => OnEditSkin?.Invoke(), 0, Key.S));
|
buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", OsuIcon.SkinB, new Color4(220, 160, 0, 255), () => OnEditSkin?.Invoke(), 0, Key.S));
|
||||||
buttonsEdit.ForEach(b => b.VisibleState = ButtonSystemState.Edit);
|
buttonsEdit.ForEach(b => b.VisibleState = ButtonSystemState.Edit);
|
||||||
|
|
||||||
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P));
|
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P, Key.M, Key.L));
|
||||||
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-play-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => State = ButtonSystemState.Edit, 0, Key.E));
|
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-play-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => State = ButtonSystemState.Edit, 0, Key.E));
|
||||||
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.Beatmap, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D));
|
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.Beatmap, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D));
|
||||||
|
|
||||||
|
@ -26,48 +26,44 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
|||||||
[Resolved(typeof(Room))]
|
[Resolved(typeof(Room))]
|
||||||
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
||||||
|
|
||||||
private readonly Drawable playlistArea;
|
private readonly GridContainer playlistArea;
|
||||||
private readonly DrawableRoomPlaylist playlist;
|
private readonly DrawableRoomPlaylist playlist;
|
||||||
|
|
||||||
public MatchBeatmapDetailArea()
|
public MatchBeatmapDetailArea()
|
||||||
{
|
{
|
||||||
Add(playlistArea = new Container
|
Add(playlistArea = new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Vertical = 10 },
|
Padding = new MarginPadding { Vertical = 10 },
|
||||||
Child = new GridContainer
|
Content = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new Drawable[]
|
||||||
Content = new[]
|
|
||||||
{
|
{
|
||||||
new Drawable[]
|
new Container
|
||||||
{
|
{
|
||||||
new Container
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding { Bottom = 10 },
|
||||||
|
Child = playlist = new PlaylistsRoomSettingsPlaylist
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both
|
||||||
Padding = new MarginPadding { Bottom = 10 },
|
|
||||||
Child = playlist = new PlaylistsRoomSettingsPlaylist
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
new RoundedButton
|
|
||||||
{
|
|
||||||
Text = "Add new playlist entry",
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Size = Vector2.One,
|
|
||||||
Action = () => CreateNewItem?.Invoke()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
RowDimensions = new[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new Dimension(),
|
new RoundedButton
|
||||||
new Dimension(GridSizeMode.Absolute, 50),
|
{
|
||||||
}
|
Text = "Add new playlist entry",
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Size = Vector2.One,
|
||||||
|
Action = () => CreateNewItem?.Invoke()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.Absolute, 50),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -281,7 +281,13 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (beatmap != null)
|
if (beatmap != null)
|
||||||
difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset) { Size = new Vector2(icon_height) };
|
{
|
||||||
|
difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset, requiredMods)
|
||||||
|
{
|
||||||
|
Size = new Vector2(icon_height),
|
||||||
|
TooltipType = DifficultyIconTooltipType.Extended,
|
||||||
|
};
|
||||||
|
}
|
||||||
else
|
else
|
||||||
difficultyIconContainer.Clear();
|
difficultyIconContainer.Clear();
|
||||||
|
|
||||||
|
@ -40,35 +40,31 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|||||||
Colour = Color4.Black,
|
Colour = Color4.Black,
|
||||||
Alpha = 0.5f
|
Alpha = 0.5f
|
||||||
},
|
},
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Horizontal = padding },
|
Padding = new MarginPadding { Horizontal = padding },
|
||||||
Child = new GridContainer
|
ColumnDimensions = new[]
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both,
|
new Dimension(GridSizeMode.AutoSize, minSize: 80 - 2 * padding)
|
||||||
ColumnDimensions = new[]
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new[]
|
||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.AutoSize, minSize: 80 - 2 * padding)
|
new Container
|
||||||
},
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new[]
|
|
||||||
{
|
{
|
||||||
new Container
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Padding = new MarginPadding { Bottom = 2 },
|
||||||
|
Child = content = new Container
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Padding = new MarginPadding { Bottom = 2 },
|
|
||||||
Child = content = new Container
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,38 +95,34 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
|||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
// Playlist items column
|
// Playlist items column
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Right = 5 },
|
Padding = new MarginPadding { Right = 5 },
|
||||||
Child = new GridContainer
|
Content = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new Drawable[] { new OverlinedPlaylistHeader(), },
|
||||||
Content = new[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new Drawable[] { new OverlinedPlaylistHeader(), },
|
new DrawableRoomPlaylist
|
||||||
new Drawable[]
|
|
||||||
{
|
{
|
||||||
new DrawableRoomPlaylist
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Items = { BindTarget = Room.Playlist },
|
||||||
|
SelectedItem = { BindTarget = SelectedItem },
|
||||||
|
AllowSelection = true,
|
||||||
|
AllowShowingResults = true,
|
||||||
|
RequestResults = item =>
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
Debug.Assert(RoomId.Value != null);
|
||||||
Items = { BindTarget = Room.Playlist },
|
ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false));
|
||||||
SelectedItem = { BindTarget = SelectedItem },
|
|
||||||
AllowSelection = true,
|
|
||||||
AllowShowingResults = true,
|
|
||||||
RequestResults = item =>
|
|
||||||
{
|
|
||||||
Debug.Assert(RoomId.Value != null);
|
|
||||||
ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
RowDimensions = new[]
|
},
|
||||||
{
|
RowDimensions = new[]
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
{
|
||||||
new Dimension(),
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
}
|
new Dimension(),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Spacer
|
// Spacer
|
||||||
|
@ -258,14 +258,10 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
Vector2? highestBottomScreenSpace = null;
|
Vector2? highestBottomScreenSpace = null;
|
||||||
|
|
||||||
foreach (var element in mainComponents.Components)
|
processDrawables(mainComponents);
|
||||||
processDrawable(element);
|
|
||||||
|
|
||||||
if (rulesetComponents != null)
|
if (rulesetComponents != null)
|
||||||
{
|
processDrawables(rulesetComponents);
|
||||||
foreach (var element in rulesetComponents.Components)
|
|
||||||
processDrawable(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lowestTopScreenSpaceRight.HasValue)
|
if (lowestTopScreenSpaceRight.HasValue)
|
||||||
topRightElements.Y = MathHelper.Clamp(ToLocalSpace(new Vector2(0, lowestTopScreenSpaceRight.Value)).Y, 0, DrawHeight - topRightElements.DrawHeight);
|
topRightElements.Y = MathHelper.Clamp(ToLocalSpace(new Vector2(0, lowestTopScreenSpaceRight.Value)).Y, 0, DrawHeight - topRightElements.DrawHeight);
|
||||||
@ -282,6 +278,14 @@ namespace osu.Game.Screens.Play
|
|||||||
else
|
else
|
||||||
bottomRightElements.Y = 0;
|
bottomRightElements.Y = 0;
|
||||||
|
|
||||||
|
void processDrawables(SkinComponentsContainer components)
|
||||||
|
{
|
||||||
|
// Avoid using foreach due to missing GetEnumerator implementation.
|
||||||
|
// See https://github.com/ppy/osu-framework/blob/e10051e6643731e393b09de40a3a3d209a545031/osu.Framework/Bindables/IBindableList.cs#L41-L44.
|
||||||
|
for (int i = 0; i < components.Components.Count; i++)
|
||||||
|
processDrawable(components.Components[i]);
|
||||||
|
}
|
||||||
|
|
||||||
void processDrawable(ISerialisableDrawable element)
|
void processDrawable(ISerialisableDrawable element)
|
||||||
{
|
{
|
||||||
// Cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes.
|
// Cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes.
|
||||||
|
@ -545,7 +545,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
if (playable.HitObjects.Count == 0)
|
if (playable.HitObjects.Count == 0)
|
||||||
{
|
{
|
||||||
Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Error);
|
Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Important);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,44 +150,40 @@ namespace osu.Game.Screens.Ranking.Contracted
|
|||||||
},
|
},
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Vertical = 5 },
|
Padding = new MarginPadding { Vertical = 5 },
|
||||||
Child = new GridContainer
|
Content = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new Drawable[]
|
||||||
Content = new[]
|
|
||||||
{
|
{
|
||||||
new Drawable[]
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
new OsuSpriteText
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Current = scoreManager.GetBindableTotalScoreString(score),
|
||||||
|
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true),
|
||||||
|
Spacing = new Vector2(-1, 0)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding { Top = 2 },
|
||||||
|
Child = new DrawableRank(score.Rank)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Current = scoreManager.GetBindableTotalScoreString(score),
|
|
||||||
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true),
|
|
||||||
Spacing = new Vector2(-1, 0)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Padding = new MarginPadding { Top = 2 },
|
|
||||||
Child = new DrawableRank(score.Rank)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
RowDimensions = new[]
|
},
|
||||||
{
|
RowDimensions = new[]
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
{
|
||||||
}
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Relative width of the rank circles.
|
/// Relative width of the rank circles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const float RANK_CIRCLE_RADIUS = 0.06f;
|
public const float RANK_CIRCLE_RADIUS = 0.05f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Relative width of the circle showing the accuracy.
|
/// Relative width of the circle showing the accuracy.
|
||||||
@ -79,12 +79,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// SS is displayed as a 1% region, otherwise it would be invisible.
|
/// SS is displayed as a 1% region, otherwise it would be invisible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const double virtual_ss_percentage = 0.01;
|
public const double VIRTUAL_SS_PERCENTAGE = 0.01;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The width of a <see cref="RankNotch"/> in terms of accuracy.
|
/// The width of spacing in terms of accuracy between the grade circles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const double NOTCH_WIDTH_PERCENTAGE = 1.0 / 360;
|
public const double GRADE_SPACING_PERCENTAGE = 2.0 / 360;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The easing for the circle filling transforms.
|
/// The easing for the circle filling transforms.
|
||||||
@ -94,7 +94,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
private readonly ScoreInfo score;
|
private readonly ScoreInfo score;
|
||||||
|
|
||||||
private CircularProgress accuracyCircle;
|
private CircularProgress accuracyCircle;
|
||||||
private CircularProgress innerMask;
|
private GradedCircles gradedCircles;
|
||||||
private Container<RankBadge> badges;
|
private Container<RankBadge> badges;
|
||||||
private RankText rankText;
|
private RankText rankText;
|
||||||
|
|
||||||
@ -163,82 +163,16 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#7CF6FF"), Color4Extensions.FromHex("#BAFFA9")),
|
Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#7CF6FF"), Color4Extensions.FromHex("#BAFFA9")),
|
||||||
InnerRadius = accuracy_circle_radius,
|
InnerRadius = accuracy_circle_radius,
|
||||||
},
|
},
|
||||||
new BufferedContainer
|
new Container
|
||||||
{
|
{
|
||||||
Name = "Graded circles",
|
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Size = new Vector2(0.8f),
|
Size = new Vector2(0.8f),
|
||||||
Padding = new MarginPadding(2),
|
Padding = new MarginPadding(2.5f),
|
||||||
Children = new Drawable[]
|
Child = gradedCircles = new GradedCircles(accuracyC, accuracyB, accuracyA, accuracyS, accuracyX)
|
||||||
{
|
{
|
||||||
new CircularProgress
|
RelativeSizeAxes = Axes.Both
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = OsuColour.ForRank(ScoreRank.X),
|
|
||||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
|
||||||
Current = { Value = accuracyX }
|
|
||||||
},
|
|
||||||
new CircularProgress
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = OsuColour.ForRank(ScoreRank.S),
|
|
||||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
|
||||||
Current = { Value = accuracyX - virtual_ss_percentage }
|
|
||||||
},
|
|
||||||
new CircularProgress
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = OsuColour.ForRank(ScoreRank.A),
|
|
||||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
|
||||||
Current = { Value = accuracyS }
|
|
||||||
},
|
|
||||||
new CircularProgress
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = OsuColour.ForRank(ScoreRank.B),
|
|
||||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
|
||||||
Current = { Value = accuracyA }
|
|
||||||
},
|
|
||||||
new CircularProgress
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = OsuColour.ForRank(ScoreRank.C),
|
|
||||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
|
||||||
Current = { Value = accuracyB }
|
|
||||||
},
|
|
||||||
new CircularProgress
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = OsuColour.ForRank(ScoreRank.D),
|
|
||||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
|
||||||
Current = { Value = accuracyC }
|
|
||||||
},
|
|
||||||
new RankNotch((float)accuracyX),
|
|
||||||
new RankNotch((float)(accuracyX - virtual_ss_percentage)),
|
|
||||||
new RankNotch((float)accuracyS),
|
|
||||||
new RankNotch((float)accuracyA),
|
|
||||||
new RankNotch((float)accuracyB),
|
|
||||||
new RankNotch((float)accuracyC),
|
|
||||||
new BufferedContainer
|
|
||||||
{
|
|
||||||
Name = "Graded circle mask",
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Padding = new MarginPadding(1),
|
|
||||||
Blending = new BlendingParameters
|
|
||||||
{
|
|
||||||
Source = BlendingType.DstColor,
|
|
||||||
Destination = BlendingType.OneMinusSrcColor,
|
|
||||||
SourceAlpha = BlendingType.One,
|
|
||||||
DestinationAlpha = BlendingType.SrcAlpha
|
|
||||||
},
|
|
||||||
Child = innerMask = new CircularProgress
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
InnerRadius = RANK_CIRCLE_RADIUS - 0.02f,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
badges = new Container<RankBadge>
|
badges = new Container<RankBadge>
|
||||||
@ -253,7 +187,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
new RankBadge(accuracyB, Interpolation.Lerp(accuracyB, accuracyA, 0.5), getRank(ScoreRank.B)),
|
new RankBadge(accuracyB, Interpolation.Lerp(accuracyB, accuracyA, 0.5), getRank(ScoreRank.B)),
|
||||||
// The S and A badges are moved down slightly to prevent collision with the SS badge.
|
// The S and A badges are moved down slightly to prevent collision with the SS badge.
|
||||||
new RankBadge(accuracyA, Interpolation.Lerp(accuracyA, accuracyS, 0.25), getRank(ScoreRank.A)),
|
new RankBadge(accuracyA, Interpolation.Lerp(accuracyA, accuracyS, 0.25), getRank(ScoreRank.A)),
|
||||||
new RankBadge(accuracyS, Interpolation.Lerp(accuracyS, (accuracyX - virtual_ss_percentage), 0.25), getRank(ScoreRank.S)),
|
new RankBadge(accuracyS, Interpolation.Lerp(accuracyS, (accuracyX - VIRTUAL_SS_PERCENTAGE), 0.25), getRank(ScoreRank.S)),
|
||||||
new RankBadge(accuracyX, accuracyX, getRank(ScoreRank.X)),
|
new RankBadge(accuracyX, accuracyX, getRank(ScoreRank.X)),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -301,7 +235,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
}
|
}
|
||||||
|
|
||||||
using (BeginDelayedSequence(RANK_CIRCLE_TRANSFORM_DELAY))
|
using (BeginDelayedSequence(RANK_CIRCLE_TRANSFORM_DELAY))
|
||||||
innerMask.FillTo(1f, RANK_CIRCLE_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
|
gradedCircles.TransformTo(nameof(GradedCircles.Progress), 1.0, RANK_CIRCLE_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
|
||||||
|
|
||||||
using (BeginDelayedSequence(ACCURACY_TRANSFORM_DELAY))
|
using (BeginDelayedSequence(ACCURACY_TRANSFORM_DELAY))
|
||||||
{
|
{
|
||||||
@ -318,10 +252,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
// to prevent ambiguity on what grade it's pointing at.
|
// to prevent ambiguity on what grade it's pointing at.
|
||||||
foreach (double p in notchPercentages)
|
foreach (double p in notchPercentages)
|
||||||
{
|
{
|
||||||
if (Precision.AlmostEquals(p, targetAccuracy, NOTCH_WIDTH_PERCENTAGE / 2))
|
if (Precision.AlmostEquals(p, targetAccuracy, GRADE_SPACING_PERCENTAGE / 2))
|
||||||
{
|
{
|
||||||
int tippingDirection = targetAccuracy - p >= 0 ? 1 : -1; // We "round up" here to match rank criteria
|
int tippingDirection = targetAccuracy - p >= 0 ? 1 : -1; // We "round up" here to match rank criteria
|
||||||
targetAccuracy = p + tippingDirection * (NOTCH_WIDTH_PERCENTAGE / 2);
|
targetAccuracy = p + tippingDirection * (GRADE_SPACING_PERCENTAGE / 2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -330,7 +264,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
if (score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH)
|
if (score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH)
|
||||||
targetAccuracy = 1;
|
targetAccuracy = 1;
|
||||||
else
|
else
|
||||||
targetAccuracy = Math.Min(accuracyX - virtual_ss_percentage - NOTCH_WIDTH_PERCENTAGE / 2, targetAccuracy);
|
targetAccuracy = Math.Min(accuracyX - VIRTUAL_SS_PERCENTAGE - GRADE_SPACING_PERCENTAGE / 2, targetAccuracy);
|
||||||
|
|
||||||
// The accuracy circle gauge visually fills up a bit too much.
|
// The accuracy circle gauge visually fills up a bit too much.
|
||||||
// This wouldn't normally matter but we want it to align properly with the inner graded circle in the above cases.
|
// This wouldn't normally matter but we want it to align properly with the inner graded circle in the above cases.
|
||||||
@ -370,7 +304,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
using (BeginDelayedSequence(
|
using (BeginDelayedSequence(
|
||||||
inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(accuracyX - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION))
|
inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(accuracyX - VIRTUAL_SS_PERCENTAGE, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION))
|
||||||
{
|
{
|
||||||
badge.Appear();
|
badge.Appear();
|
||||||
|
|
||||||
@ -430,7 +364,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
.FadeOut(800, Easing.Out);
|
.FadeOut(800, Easing.Out);
|
||||||
|
|
||||||
accuracyCircle
|
accuracyCircle
|
||||||
.FillTo(accuracyS - NOTCH_WIDTH_PERCENTAGE / 2 - visual_alignment_offset, 70, Easing.OutQuint);
|
.FillTo(accuracyS - GRADE_SPACING_PERCENTAGE / 2 - visual_alignment_offset, 70, Easing.OutQuint);
|
||||||
|
|
||||||
badges.Single(b => b.Rank == getRank(ScoreRank.S))
|
badges.Single(b => b.Rank == getRank(ScoreRank.S))
|
||||||
.FadeOut(70, Easing.OutQuint);
|
.FadeOut(70, Easing.OutQuint);
|
||||||
|
89
osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs
Normal file
89
osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||||
|
{
|
||||||
|
public partial class GradedCircles : CompositeDrawable
|
||||||
|
{
|
||||||
|
private double progress;
|
||||||
|
|
||||||
|
public double Progress
|
||||||
|
{
|
||||||
|
get => progress;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
progress = value;
|
||||||
|
|
||||||
|
foreach (var circle in circles)
|
||||||
|
circle.RevealProgress = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Container<GradedCircle> circles;
|
||||||
|
|
||||||
|
public GradedCircles(double accuracyC, double accuracyB, double accuracyA, double accuracyS, double accuracyX)
|
||||||
|
{
|
||||||
|
InternalChild = circles = new Container<GradedCircle>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
new GradedCircle(0.0, accuracyC)
|
||||||
|
{
|
||||||
|
Colour = OsuColour.ForRank(ScoreRank.D),
|
||||||
|
},
|
||||||
|
new GradedCircle(accuracyC, accuracyB)
|
||||||
|
{
|
||||||
|
Colour = OsuColour.ForRank(ScoreRank.C),
|
||||||
|
},
|
||||||
|
new GradedCircle(accuracyB, accuracyA)
|
||||||
|
{
|
||||||
|
Colour = OsuColour.ForRank(ScoreRank.B),
|
||||||
|
},
|
||||||
|
new GradedCircle(accuracyA, accuracyS)
|
||||||
|
{
|
||||||
|
Colour = OsuColour.ForRank(ScoreRank.A),
|
||||||
|
},
|
||||||
|
new GradedCircle(accuracyS, accuracyX - AccuracyCircle.VIRTUAL_SS_PERCENTAGE)
|
||||||
|
{
|
||||||
|
Colour = OsuColour.ForRank(ScoreRank.S),
|
||||||
|
},
|
||||||
|
new GradedCircle(accuracyX - AccuracyCircle.VIRTUAL_SS_PERCENTAGE, 1.0)
|
||||||
|
{
|
||||||
|
Colour = OsuColour.ForRank(ScoreRank.X)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class GradedCircle : CircularProgress
|
||||||
|
{
|
||||||
|
public double RevealProgress
|
||||||
|
{
|
||||||
|
set => Current.Value = Math.Clamp(value, startProgress, endProgress) - startProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly double startProgress;
|
||||||
|
private readonly double endProgress;
|
||||||
|
|
||||||
|
public GradedCircle(double startProgress, double endProgress)
|
||||||
|
{
|
||||||
|
this.startProgress = startProgress + AccuracyCircle.GRADE_SPACING_PERCENTAGE * 0.5;
|
||||||
|
this.endProgress = endProgress - AccuracyCircle.GRADE_SPACING_PERCENTAGE * 0.5;
|
||||||
|
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS;
|
||||||
|
Rotation = (float)this.startProgress * 360;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,49 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A solid "notch" of the <see cref="AccuracyCircle"/> that appears at the ends of the rank circles to add separation.
|
|
||||||
/// </summary>
|
|
||||||
public partial class RankNotch : CompositeDrawable
|
|
||||||
{
|
|
||||||
private readonly float position;
|
|
||||||
|
|
||||||
public RankNotch(float position)
|
|
||||||
{
|
|
||||||
this.position = position;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
InternalChild = new Container
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Rotation = position * 360f,
|
|
||||||
Child = new Box
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
|
||||||
Height = AccuracyCircle.RANK_CIRCLE_RADIUS,
|
|
||||||
Width = (float)AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 360f,
|
|
||||||
Colour = OsuColour.Gray(0.3f),
|
|
||||||
EdgeSmoothness = new Vector2(1f)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -75,99 +75,92 @@ namespace osu.Game.Screens.Select
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Colour4.Black.Opacity(0.3f),
|
Colour = Colour4.Black.Opacity(0.3f),
|
||||||
},
|
},
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Horizontal = spacing },
|
Padding = new MarginPadding { Horizontal = spacing },
|
||||||
Children = new Drawable[]
|
RowDimensions = new[]
|
||||||
{
|
{
|
||||||
new GridContainer
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
new Dimension()
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new FillFlowContainer
|
||||||
RowDimensions = new[]
|
|
||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
RelativeSizeAxes = Axes.X,
|
||||||
new Dimension()
|
AutoSizeAxes = Axes.Y,
|
||||||
},
|
Direction = FillDirection.Horizontal,
|
||||||
Content = new[]
|
Children = new Drawable[]
|
||||||
{
|
|
||||||
new Drawable[]
|
|
||||||
{
|
{
|
||||||
new FillFlowContainer
|
new FillFlowContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Direction = FillDirection.Horizontal,
|
Width = 0.5f,
|
||||||
Children = new Drawable[]
|
Spacing = new Vector2(spacing),
|
||||||
|
Padding = new MarginPadding { Right = spacing / 2 },
|
||||||
|
Children = new[]
|
||||||
{
|
{
|
||||||
new FillFlowContainer
|
new DetailBox().WithChild(new OnlineViewContainer(string.Empty)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
Height = 134,
|
||||||
Width = 0.5f,
|
Padding = new MarginPadding { Horizontal = spacing, Top = spacing },
|
||||||
Spacing = new Vector2(spacing),
|
Child = ratingsDisplay = new UserRatings
|
||||||
Padding = new MarginPadding { Right = spacing / 2 },
|
|
||||||
Children = new[]
|
|
||||||
{
|
{
|
||||||
new DetailBox().WithChild(new OnlineViewContainer(string.Empty)
|
RelativeSizeAxes = Axes.Both,
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 134,
|
|
||||||
Padding = new MarginPadding { Horizontal = spacing, Top = spacing },
|
|
||||||
Child = ratingsDisplay = new UserRatings
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
new OsuScrollContainer
|
},
|
||||||
|
},
|
||||||
|
new OsuScrollContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 250,
|
||||||
|
Width = 0.5f,
|
||||||
|
ScrollbarVisible = false,
|
||||||
|
Padding = new MarginPadding { Left = spacing / 2 },
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
LayoutDuration = transition_duration,
|
||||||
|
LayoutEasing = Easing.OutQuad,
|
||||||
|
Children = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
description = new MetadataSectionDescription(query => songSelect?.Search(query)),
|
||||||
Height = 250,
|
source = new MetadataSectionSource(query => songSelect?.Search(query)),
|
||||||
Width = 0.5f,
|
tags = new MetadataSectionTags(query => songSelect?.Search(query)),
|
||||||
ScrollbarVisible = false,
|
|
||||||
Padding = new MarginPadding { Left = spacing / 2 },
|
|
||||||
Child = new FillFlowContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
LayoutDuration = transition_duration,
|
|
||||||
LayoutEasing = Easing.OutQuad,
|
|
||||||
Children = new[]
|
|
||||||
{
|
|
||||||
description = new MetadataSectionDescription(query => songSelect?.Search(query)),
|
|
||||||
source = new MetadataSectionSource(query => songSelect?.Search(query)),
|
|
||||||
tags = new MetadataSectionTags(query => songSelect?.Search(query)),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
new Drawable[]
|
},
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
failRetryContainer = new OnlineViewContainer("Sign in to view more details")
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
failRetryContainer = new OnlineViewContainer("Sign in to view more details")
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = BeatmapsetsStrings.ShowInfoPointsOfFailure,
|
||||||
|
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14),
|
||||||
|
},
|
||||||
|
failRetryGraph = new FailRetryGraph
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Padding = new MarginPadding { Top = 14 + spacing / 2 },
|
||||||
{
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Text = BeatmapsetsStrings.ShowInfoPointsOfFailure,
|
|
||||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14),
|
|
||||||
},
|
|
||||||
failRetryGraph = new FailRetryGraph
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Padding = new MarginPadding { Top = 14 + spacing / 2 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
loading = new LoadingLayer(true)
|
loading = new LoadingLayer(true)
|
||||||
};
|
};
|
||||||
|
@ -31,6 +31,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Select
|
namespace osu.Game.Screens.Select
|
||||||
{
|
{
|
||||||
@ -405,9 +406,9 @@ namespace osu.Game.Screens.Select
|
|||||||
foreach (var mod in mods.Value.OfType<IApplicableToRate>())
|
foreach (var mod in mods.Value.OfType<IApplicableToRate>())
|
||||||
rate = mod.ApplyToRate(0, rate);
|
rate = mod.ApplyToRate(0, rate);
|
||||||
|
|
||||||
int bpmMax = (int)Math.Round(Math.Round(beatmap.ControlPointInfo.BPMMaximum) * rate);
|
int bpmMax = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMaximum, rate);
|
||||||
int bpmMin = (int)Math.Round(Math.Round(beatmap.ControlPointInfo.BPMMinimum) * rate);
|
int bpmMin = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMinimum, rate);
|
||||||
int mostCommonBPM = (int)Math.Round(Math.Round(60000 / beatmap.GetMostCommonBeatLength()) * rate);
|
int mostCommonBPM = FormatUtils.RoundBPM(60000 / beatmap.GetMostCommonBeatLength(), rate);
|
||||||
|
|
||||||
string labelText = bpmMin == bpmMax
|
string labelText = bpmMin == bpmMax
|
||||||
? $"{bpmMin}"
|
? $"{bpmMin}"
|
||||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Screens.Select.Filter;
|
using osu.Game.Screens.Select.Filter;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Select.Carousel
|
namespace osu.Game.Screens.Select.Carousel
|
||||||
{
|
{
|
||||||
@ -67,19 +68,19 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
case SortMode.Artist:
|
case SortMode.Artist:
|
||||||
comparison = string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.Ordinal);
|
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SortMode.Title:
|
case SortMode.Title:
|
||||||
comparison = string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.Ordinal);
|
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SortMode.Author:
|
case SortMode.Author:
|
||||||
comparison = string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.Ordinal);
|
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SortMode.Source:
|
case SortMode.Source:
|
||||||
comparison = string.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source, StringComparison.Ordinal);
|
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SortMode.DateAdded:
|
case SortMode.DateAdded:
|
||||||
|
@ -122,7 +122,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
{
|
{
|
||||||
difficultyIcon = new DifficultyIcon(beatmapInfo)
|
difficultyIcon = new DifficultyIcon(beatmapInfo)
|
||||||
{
|
{
|
||||||
ShowTooltip = false,
|
TooltipType = DifficultyIconTooltipType.None,
|
||||||
Scale = new Vector2(1.8f),
|
Scale = new Vector2(1.8f),
|
||||||
},
|
},
|
||||||
new FillFlowContainer
|
new FillFlowContainer
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -64,7 +65,7 @@ namespace osu.Game.Screens.Select
|
|||||||
Sort = sortMode.Value,
|
Sort = sortMode.Value,
|
||||||
AllowConvertedBeatmaps = showConverted.Value,
|
AllowConvertedBeatmaps = showConverted.Value,
|
||||||
Ruleset = ruleset.Value,
|
Ruleset = ruleset.Value,
|
||||||
CollectionBeatmapMD5Hashes = collectionDropdown.Current.Value?.Collection?.PerformRead(c => c.BeatmapMD5Hashes)
|
CollectionBeatmapMD5Hashes = collectionDropdown.Current.Value?.Collection?.PerformRead(c => c.BeatmapMD5Hashes).ToImmutableHashSet()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!minimumStars.IsDefault)
|
if (!minimumStars.IsDefault)
|
||||||
|
@ -166,6 +166,9 @@ namespace osu.Game.Users
|
|||||||
globalRankDisplay = new ProfileValueDisplay(true)
|
globalRankDisplay = new ProfileValueDisplay(true)
|
||||||
{
|
{
|
||||||
Title = UsersStrings.ShowRankGlobalSimple,
|
Title = UsersStrings.ShowRankGlobalSimple,
|
||||||
|
// TODO: implement highest rank tooltip
|
||||||
|
// `RankHighest` resides in `APIUser`, but `api.LocalUser` doesn't update
|
||||||
|
// maybe move to `UserStatistics` in api, so `SoloStatisticsWatcher` can update the value
|
||||||
},
|
},
|
||||||
countryRankDisplay = new ProfileValueDisplay(true)
|
countryRankDisplay = new ProfileValueDisplay(true)
|
||||||
{
|
{
|
||||||
|
@ -49,5 +49,15 @@ namespace osu.Game.Utils
|
|||||||
|
|
||||||
return precision;
|
return precision;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies rounding to the given BPM value.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Double-rounding is applied intentionally (see https://github.com/ppy/osu/pull/18345#issue-1243311382 for rationale).
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="baseBpm">The base BPM to round.</param>
|
||||||
|
/// <param name="rate">Rate adjustment, if applicable.</param>
|
||||||
|
public static int RoundBPM(double baseBpm, double rate = 1) => (int)Math.Round(Math.Round(baseBpm) * rate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
49
osu.Game/Utils/OrdinalSortByCaseStringComparer.cs
Normal file
49
osu.Game/Utils/OrdinalSortByCaseStringComparer.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace osu.Game.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This string comparer is something of a cross-over between <see cref="StringComparer.Ordinal"/> and <see cref="StringComparer.OrdinalIgnoreCase"/>.
|
||||||
|
/// <see cref="StringComparer.OrdinalIgnoreCase"/> is used first, but <see cref="StringComparer.Ordinal"/> is used as a tie-breaker.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This comparer's behaviour somewhat emulates <see cref="StringComparer.InvariantCulture"/>,
|
||||||
|
/// but non-ordinal comparers - both culture-aware and culture-invariant - have huge performance overheads due to i18n factors (up to 5x slower).
|
||||||
|
/// </remarks>
|
||||||
|
/// <example>
|
||||||
|
/// Given the following strings to sort: <c>[A, B, C, D, a, b, c, d, A]</c> and a stable sorting algorithm:
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>
|
||||||
|
/// <see cref="StringComparer.Ordinal"/> would return <c>[A, A, B, C, D, a, b, c, d]</c>.
|
||||||
|
/// This is undesirable as letters are interleaved.
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <see cref="StringComparer.OrdinalIgnoreCase"/> would return <c>[A, a, A, B, b, C, c, D, d]</c>.
|
||||||
|
/// Different letters are not interleaved, but because case is ignored, the As are left in arbitrary order.
|
||||||
|
/// </item>
|
||||||
|
/// </list>
|
||||||
|
/// <item>
|
||||||
|
/// <see cref="OrdinalSortByCaseStringComparer"/> would return <c>[a, A, A, b, B, c, C, d, D]</c>, which is the expected behaviour.
|
||||||
|
/// </item>
|
||||||
|
/// </example>
|
||||||
|
public class OrdinalSortByCaseStringComparer : IComparer<string>
|
||||||
|
{
|
||||||
|
public static readonly OrdinalSortByCaseStringComparer DEFAULT = new OrdinalSortByCaseStringComparer();
|
||||||
|
|
||||||
|
private OrdinalSortByCaseStringComparer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Compare(string? a, string? b)
|
||||||
|
{
|
||||||
|
int result = StringComparer.OrdinalIgnoreCase.Compare(a, b);
|
||||||
|
if (result == 0)
|
||||||
|
result = -StringComparer.Ordinal.Compare(a, b); // negative to place lowercase letters before uppercase.
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -36,7 +36,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="11.5.0" />
|
<PackageReference Include="Realm" Version="11.5.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2024.217.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2024.221.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.207.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.207.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.41.3" />
|
<PackageReference Include="Sentry" Version="3.41.3" />
|
||||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||||
|
@ -23,6 +23,6 @@
|
|||||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.217.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.221.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -102,6 +102,19 @@
|
|||||||
<string>osz</string>
|
<string>osz</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>sh.ppy.osu.items</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>sh.ppy.osu.olz</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<string>olz</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleDocumentTypes</key>
|
<key>CFBundleDocumentTypes</key>
|
||||||
<array>
|
<array>
|
||||||
|
Loading…
Reference in New Issue
Block a user