mirror of
https://github.com/ppy/osu.git
synced 2025-02-15 15:23:14 +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
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
@ -27,7 +27,7 @@ jobs:
|
||||
run: dotnet restore osu.Desktop.slnf
|
||||
|
||||
- name: Restore inspectcode cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ github.workspace }}/inspectcode
|
||||
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
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
@ -99,16 +99,16 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: microsoft
|
||||
java-version: 11
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
@ -126,10 +126,10 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
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 }}
|
||||
steps:
|
||||
- name: Checkout diffcalc-sheet-generator
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: ${{ env.EXECUTION_ID }}
|
||||
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
|
||||
steps:
|
||||
- name: Annotate CI run with test results
|
||||
uses: dorny/test-reporter@v1.6.0
|
||||
uses: dorny/test-reporter@v1.8.0
|
||||
with:
|
||||
artifact: osu-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
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
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
|
||||
steps:
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
- name: Checkout ppy/osu
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: osu
|
||||
|
||||
- name: Checkout ppy/osu-tools
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ppy/osu-tools
|
||||
path: osu-tools
|
||||
|
||||
- name: Checkout ppy/osu-web
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ppy/osu-web
|
||||
path: osu-web
|
||||
@ -43,7 +43,7 @@ jobs:
|
||||
working-directory: ./osu-tools
|
||||
|
||||
- name: Create pull request with changes
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
title: Update mod definitions
|
||||
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>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.217.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.221.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- 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.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
@ -17,9 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
[Resolved]
|
||||
private Playfield playfield { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; } = null!;
|
||||
|
||||
protected ScrollingHitObjectContainer HitObjectContainer => ((ManiaPlayfield)playfield).GetColumn(HitObject.Column).HitObjectContainer;
|
||||
|
||||
protected ManiaSelectionBlueprint(T hitObject)
|
||||
@ -28,14 +26,31 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
RelativeSizeAxes = Axes.None;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
private readonly IBindable<ScrollingDirection> directionBindable = new Bindable<ScrollingDirection>();
|
||||
|
||||
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;
|
||||
foreach (var child in InternalChildren)
|
||||
child.Anchor = child.Origin = anchor;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
Position = Parent!.ToLocalSpace(HitObjectContainer.ScreenSpacePositionAtTime(HitObject.StartTime)) - AnchorPosition;
|
||||
Width = HitObjectContainer.DrawWidth;
|
||||
|
@ -7,7 +7,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
@ -149,7 +148,18 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
/// <summary>
|
||||
/// Retrieves the total amount of columns across all stages in this playfield.
|
||||
/// </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)
|
||||
{
|
||||
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
@ -32,6 +33,27 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
[Test]
|
||||
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]
|
||||
public void TestSliderDimsOnlyAfterStartTime()
|
||||
{
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
|
@ -279,10 +279,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
if (HandleUserInput)
|
||||
{
|
||||
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
|
||||
&& correctButtonPressed
|
||||
&& correctButtonPressed()
|
||||
&& 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.
|
||||
// A tick must be alive to correctly play back samples,
|
||||
// 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`.
|
||||
if (next?.LifetimeStart == double.MaxValue)
|
||||
next.LifetimeStart = HitObject.StartTime;
|
||||
if (nextTick?.LifetimeStart == double.MaxValue)
|
||||
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()
|
||||
|
@ -1,13 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
{
|
||||
public partial class OsuInputManager : RulesetInputManager<OsuAction>
|
||||
{
|
||||
public IEnumerable<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
|
||||
public SlimReadOnlyListWrapper<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
|
||||
|
||||
/// <summary>
|
||||
/// Whether gameplay input buttons should be allowed.
|
||||
|
@ -5,7 +5,6 @@ using System;
|
||||
using System.Globalization;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
@ -111,42 +110,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
{
|
||||
spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0");
|
||||
}, true);
|
||||
|
||||
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!spmContainer.IsPresent && drawableSpinner.Result?.TimeStarted != null)
|
||||
fadeCounterOnTimeStart();
|
||||
updateSpmAlpha();
|
||||
}
|
||||
|
||||
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||
{
|
||||
if (!(drawableHitObject is DrawableSpinner))
|
||||
return;
|
||||
|
||||
fadeCounterOnTimeStart();
|
||||
}
|
||||
|
||||
private void fadeCounterOnTimeStart()
|
||||
private void updateSpmAlpha()
|
||||
{
|
||||
if (drawableSpinner.Result?.TimeStarted is double startTime)
|
||||
{
|
||||
using (BeginAbsoluteSequence(startTime))
|
||||
spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (drawableSpinner.IsNotNull())
|
||||
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
|
||||
spmContainer.Alpha = (float)Math.Clamp((Clock.CurrentTime - startTime) / drawableSpinner.HitObject.TimeFadeIn, 0, 1);
|
||||
else
|
||||
spmContainer.Alpha = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,8 +25,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
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,
|
||||
// when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
|
||||
// In triangles and argon skins, we expanded hitcircles to take up the full 128 px which are clickable,
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ using System;
|
||||
using System.Globalization;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
@ -117,42 +116,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0");
|
||||
}, true);
|
||||
|
||||
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!spmContainer.IsPresent && drawableSpinner.Result?.TimeStarted != null)
|
||||
fadeCounterOnTimeStart();
|
||||
updateSpmAlpha();
|
||||
}
|
||||
|
||||
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||
{
|
||||
if (!(drawableHitObject is DrawableSpinner))
|
||||
return;
|
||||
|
||||
fadeCounterOnTimeStart();
|
||||
}
|
||||
|
||||
private void fadeCounterOnTimeStart()
|
||||
private void updateSpmAlpha()
|
||||
{
|
||||
if (drawableSpinner.Result?.TimeStarted is double startTime)
|
||||
{
|
||||
using (BeginAbsoluteSequence(startTime))
|
||||
spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (drawableSpinner.IsNotNull())
|
||||
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
|
||||
spmContainer.Alpha = (float)Math.Clamp((Clock.CurrentTime - startTime) / drawableSpinner.HitObject.TimeFadeIn, 0, 1);
|
||||
else
|
||||
spmContainer.Alpha = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
@ -33,14 +32,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
drawableSpinner.HitObjectApplied += resetState;
|
||||
}
|
||||
|
||||
private RotationRecord lastRecord;
|
||||
|
||||
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 (records.Count > 0 && Time.Current < records.Last().Time)
|
||||
if (records.Count > 0 && Time.Current < lastRecord.Time)
|
||||
records.Clear();
|
||||
|
||||
// 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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
records.Enqueue(new RotationRecord { Rotation = currentRotation, Time = Time.Current });
|
||||
records.Enqueue(lastRecord = new RotationRecord { Rotation = currentRotation, Time = Time.Current });
|
||||
}
|
||||
|
||||
private void resetState(DrawableHitObject hitObject)
|
||||
{
|
||||
lastRecord = default;
|
||||
result.Value = 0;
|
||||
records.Clear();
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
@ -26,10 +25,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
var texture = skin.GetTexture(@"approachcircle");
|
||||
Debug.Assert(texture != null);
|
||||
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()
|
||||
|
@ -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.
|
||||
// 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));
|
||||
}
|
||||
|
||||
|
@ -92,8 +92,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||
this.FadeOut();
|
||||
|
||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2))
|
||||
this.FadeInFromZero(spinner.TimeFadeIn / 2);
|
||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn))
|
||||
this.FadeInFromZero(spinner.TimeFadeIn);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
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());
|
||||
|
||||
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)
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@ -17,6 +18,7 @@ using osu.Game.Rulesets.Osu.Edit;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Editing
|
||||
{
|
||||
@ -228,6 +230,28 @@ namespace osu.Game.Tests.Editing
|
||||
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)
|
||||
=> 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.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Login;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Users.Drawables;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -25,6 +27,9 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
|
||||
private LoginOverlay loginOverlay = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager configManager { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@ -156,5 +161,36 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
});
|
||||
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
|
||||
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[]
|
||||
{
|
||||
"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."
|
||||
}
|
||||
"Code Block", @"User not found! ;_;
|
||||
|
||||
There are a few possible reasons for this:
|
||||
|
||||
They may have changed their username.
|
||||
The account may be temporarily unavailable due to security or abuse issues.
|
||||
You may have made a typo!"
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -137,6 +137,11 @@ namespace osu.Game.Tests.Visual.Online
|
||||
@"top_ranks",
|
||||
@"medals"
|
||||
},
|
||||
RankHighest = new APIUser.UserRankHighest
|
||||
{
|
||||
Rank = 1,
|
||||
UpdatedAt = DateTimeOffset.Now,
|
||||
},
|
||||
Statistics = new UserStatistics
|
||||
{
|
||||
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>();
|
||||
|
||||
const string zzz_string = "zzzzz";
|
||||
const string zzz_lowercase = "zzzzz";
|
||||
const string zzz_uppercase = "ZZZZZ";
|
||||
|
||||
AddStep("Populuate beatmap sets", () =>
|
||||
{
|
||||
@ -640,10 +641,16 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
var set = TestResources.CreateTestBeatmapSetInfo();
|
||||
|
||||
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)
|
||||
set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_string);
|
||||
set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_lowercase);
|
||||
|
||||
sets.Add(set);
|
||||
}
|
||||
@ -652,9 +659,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
loadBeatmaps(sets);
|
||||
|
||||
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));
|
||||
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>
|
||||
|
@ -788,7 +788,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddAssert("all columns visible", () => this.ChildrenOfType<ModColumn>().All(col => col.IsPresent));
|
||||
|
||||
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");
|
||||
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));
|
||||
|
||||
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("show", () => modSelectOverlay.Show());
|
||||
|
@ -361,13 +361,20 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
Progress = 0,
|
||||
Text = $"Preparing to delete all {HumanisedModelName} videos...",
|
||||
CompletionText = "No videos found to delete!",
|
||||
CompletionText = no_videos_message,
|
||||
State = ProgressNotificationState.Active,
|
||||
};
|
||||
|
||||
|
@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -31,14 +32,16 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public bool ShowTooltip { get; set; } = true;
|
||||
public DifficultyIconTooltipType TooltipType { get; set; } = DifficultyIconTooltipType.StarRating;
|
||||
|
||||
private readonly IBeatmapInfo? beatmap;
|
||||
|
||||
private readonly IRulesetInfo ruleset;
|
||||
|
||||
private readonly Mod[]? mods;
|
||||
|
||||
private Drawable background = null!;
|
||||
|
||||
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.
|
||||
/// </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="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>
|
||||
public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null)
|
||||
public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null, Mod[]? mods = null)
|
||||
: this(ruleset ?? beatmap.Ruleset)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
this.mods = mods;
|
||||
|
||||
Current.Value = new StarDifficulty(beatmap.StarRating, 0);
|
||||
}
|
||||
|
||||
@ -127,6 +133,24 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
GetCustomTooltip() => new DifficultyIconTooltip();
|
||||
|
||||
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.
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -11,14 +11,25 @@ using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
internal partial class DifficultyIconTooltip : VisibilityContainer, ITooltip<DifficultyIconTooltipContent>
|
||||
{
|
||||
private OsuSpriteText difficultyName;
|
||||
private StarRatingDisplay starRating;
|
||||
private OsuSpriteText difficultyName = null!;
|
||||
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]
|
||||
private void load(OsuColour colours)
|
||||
@ -31,7 +42,6 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Alpha = 0.9f,
|
||||
Colour = colours.Gray3,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
@ -49,19 +59,49 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
Anchor = 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)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
},
|
||||
difficultyFillFlowContainer = new FillFlowContainer
|
||||
{
|
||||
Anchor = 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)
|
||||
{
|
||||
@ -72,6 +112,45 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
starRating.Current.BindTarget = displayedContent.Difficulty;
|
||||
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;
|
||||
@ -85,11 +164,20 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
public readonly IBeatmapInfo BeatmapInfo;
|
||||
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;
|
||||
Difficulty = difficulty;
|
||||
Ruleset = rulesetInfo;
|
||||
Mods = mods;
|
||||
TooltipType = tooltipType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,12 +77,19 @@ namespace osu.Game.Configuration
|
||||
|
||||
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 =>
|
||||
{
|
||||
if (!enabled.NewValue) SetValue(OsuSetting.SavePassword, false);
|
||||
if (!enabled.NewValue)
|
||||
{
|
||||
GetBindable<string>(OsuSetting.Username).SetDefault();
|
||||
SetValue(OsuSetting.SavePassword, false);
|
||||
}
|
||||
};
|
||||
|
||||
SetDefault(OsuSetting.ExternalLinkWarning, true);
|
||||
|
@ -105,7 +105,12 @@ namespace osu.Game.Database
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
@ -142,7 +147,12 @@ namespace osu.Game.Database
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
|
@ -10,11 +10,11 @@ using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
public partial class OsuMarkdownFencedCodeBlock : MarkdownFencedCodeBlock
|
||||
public partial class OsuMarkdownCodeBlock : MarkdownCodeBlock
|
||||
{
|
||||
// TODO : change to monospace font for this component
|
||||
public OsuMarkdownFencedCodeBlock(FencedCodeBlock fencedCodeBlock)
|
||||
: base(fencedCodeBlock)
|
||||
public OsuMarkdownCodeBlock(CodeBlock codeBlock)
|
||||
: base(codeBlock)
|
||||
{
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Graphics.Containers.Markdown
|
||||
|
||||
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();
|
||||
|
||||
|
@ -104,16 +104,31 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
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>
|
||||
/// "Deleted all mod presets!"
|
||||
/// </summary>
|
||||
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>
|
||||
/// "Restored all deleted mod presets!"
|
||||
/// </summary>
|
||||
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>
|
||||
/// "Please select your osu!stable install location"
|
||||
/// </summary>
|
||||
|
@ -34,6 +34,19 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty(@"previous_usernames")]
|
||||
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")]
|
||||
private string countryCodeString;
|
||||
|
||||
|
@ -297,7 +297,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
},
|
||||
icon = new DifficultyIcon(beatmapInfo, ruleset)
|
||||
{
|
||||
ShowTooltip = false,
|
||||
TooltipType = DifficultyIconTooltipType.None,
|
||||
Current = { Value = new StarDifficulty(beatmapInfo.StarRating, 0) },
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -66,41 +66,37 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
Colour = colourProvider.Background4,
|
||||
Alpha = 0f,
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Left = 18, Right = 10 },
|
||||
Child = new GridContainer
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
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(),
|
||||
}
|
||||
},
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Action = () => OnRequestSelect?.Invoke(Channel);
|
||||
|
@ -19,6 +19,7 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
@ -168,7 +169,7 @@ namespace osu.Game.Overlays.Mods
|
||||
foreach (var mod in mods.Value.OfType<IApplicableToRate>())
|
||||
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);
|
||||
|
||||
@ -194,11 +195,11 @@ namespace osu.Game.Overlays.Mods
|
||||
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 LocalisableString FormatCount(double count) => count.ToLocalisableString("0 BPM");
|
||||
protected override LocalisableString FormatCount(int count) => count.ToLocalisableString("0 BPM");
|
||||
|
||||
protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText
|
||||
{
|
||||
|
@ -77,11 +77,11 @@ namespace osu.Game.Overlays.Mods
|
||||
/// <seealso cref="ModState.Visible"/>
|
||||
public bool Visible => modState.Visible;
|
||||
|
||||
public override IEnumerable<LocalisableString> FilterTerms => new[]
|
||||
public override IEnumerable<LocalisableString> FilterTerms => new LocalisableString[]
|
||||
{
|
||||
Mod.Name,
|
||||
Mod.Name.Replace(" ", string.Empty),
|
||||
Mod.Acronym,
|
||||
Mod.Description
|
||||
};
|
||||
|
||||
public override bool MatchingFilter
|
||||
|
@ -143,6 +143,13 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0;
|
||||
|
||||
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)"-";
|
||||
|
||||
rankGraph.Statistics.Value = user?.Statistics;
|
||||
|
@ -4,6 +4,7 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -13,7 +14,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
public partial class ProfileValueDisplay : CompositeDrawable
|
||||
{
|
||||
private readonly OsuSpriteText title;
|
||||
private readonly OsuSpriteText content;
|
||||
private readonly ContentText content;
|
||||
|
||||
public LocalisableString Title
|
||||
{
|
||||
@ -25,6 +26,11 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
set => content.Text = value;
|
||||
}
|
||||
|
||||
public LocalisableString ContentTooltipText
|
||||
{
|
||||
set => content.TooltipText = value;
|
||||
}
|
||||
|
||||
public ProfileValueDisplay(bool big = false, int minimumWidth = 60)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
@ -38,9 +44,9 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
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
|
||||
{
|
||||
@ -56,5 +62,10 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
title.Colour = colourProvider.Content1;
|
||||
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.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
|
||||
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 LocalisableString TooltipText { get; set; }
|
||||
|
||||
private ProfileValueDisplay info = null!;
|
||||
|
||||
public TotalPlayTime()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
TooltipText = "0 hours";
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -32,6 +26,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
InternalChild = info = new ProfileValueDisplay(minimumWidth: 140)
|
||||
{
|
||||
Title = UsersStrings.ShowStatsPlayTime,
|
||||
ContentTooltipText = "0 hours",
|
||||
};
|
||||
|
||||
User.BindValueChanged(updateTime, true);
|
||||
@ -40,7 +35,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
private void updateTime(ValueChangedEvent<UserProfileData?> user)
|
||||
{
|
||||
int? playTime = user.NewValue?.User.Statistics?.PlayTime;
|
||||
TooltipText = (playTime ?? 0) / 3600 + " hours";
|
||||
info.ContentTooltipText = (playTime ?? 0) / 3600 + " hours";
|
||||
info.Content = formatTime(playTime);
|
||||
}
|
||||
|
||||
|
@ -26,47 +26,42 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING, Vertical = 10 },
|
||||
Child = new GridContainer
|
||||
RowDimensions = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RowDimensions = new[]
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
new MainDetails
|
||||
{
|
||||
new MainDetails
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
User = { BindTarget = User }
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 2,
|
||||
Colour = colourProvider.Background6,
|
||||
Margin = new MarginPadding { Horizontal = 15 }
|
||||
},
|
||||
new ExtendedDetails
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
User = { BindTarget = User }
|
||||
}
|
||||
RelativeSizeAxes = Axes.X,
|
||||
User = { BindTarget = User }
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 2,
|
||||
Colour = colourProvider.Background6,
|
||||
Margin = new MarginPadding { Horizontal = 15 }
|
||||
},
|
||||
new ExtendedDetails
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
User = { BindTarget = User }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions;
|
||||
@ -28,15 +27,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
var renderer = config.GetBindable<RendererType>(FrameworkSetting.Renderer);
|
||||
automaticRendererInUse = renderer.Value == RendererType.Automatic;
|
||||
|
||||
SettingsEnumDropdown<RendererType> rendererDropdown;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
rendererDropdown = new RendererSettingsDropdown
|
||||
new RendererSettingsDropdown
|
||||
{
|
||||
LabelText = GraphicsSettingsStrings.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" },
|
||||
},
|
||||
// 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>
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Collections;
|
||||
@ -35,8 +36,20 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
|
||||
private void deleteAllCollections()
|
||||
{
|
||||
realm.Write(r => r.RemoveAll<BeatmapCollection>());
|
||||
notificationOverlay?.Post(new ProgressCompletionNotification { Text = MaintenanceSettingsStrings.DeletedAllCollections });
|
||||
bool anyDeleted = realm.Write(r =>
|
||||
{
|
||||
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.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Logging;
|
||||
@ -52,36 +53,50 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
});
|
||||
}
|
||||
|
||||
private void deleteAllModPresets() =>
|
||||
private bool deleteAllModPresets() =>
|
||||
realm.Write(r =>
|
||||
{
|
||||
bool anyDeleted = false;
|
||||
|
||||
foreach (var preset in r.All<ModPreset>())
|
||||
{
|
||||
anyDeleted |= !preset.DeletePending;
|
||||
preset.DeletePending = true;
|
||||
}
|
||||
|
||||
return anyDeleted;
|
||||
});
|
||||
|
||||
private void onAllModPresetsDeleted(Task deletionTask)
|
||||
private void onAllModPresetsDeleted(Task<bool> deletionTask)
|
||||
{
|
||||
deleteAllButton.Enabled.Value = true;
|
||||
|
||||
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)
|
||||
Logger.Error(deletionTask.Exception, "Failed to delete all mod presets");
|
||||
}
|
||||
|
||||
private void undeleteModPresets() =>
|
||||
private bool undeleteModPresets() =>
|
||||
realm.Write(r =>
|
||||
{
|
||||
bool anyRestored = false;
|
||||
|
||||
foreach (var preset in r.All<ModPreset>().Where(preset => preset.DeletePending))
|
||||
{
|
||||
anyRestored |= preset.DeletePending;
|
||||
preset.DeletePending = false;
|
||||
}
|
||||
|
||||
return anyRestored;
|
||||
});
|
||||
|
||||
private void onModPresetsUndeleted(Task undeletionTask)
|
||||
private void onModPresetsUndeleted(Task<bool> undeletionTask)
|
||||
{
|
||||
undeleteButton.Enabled.Value = true;
|
||||
|
||||
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)
|
||||
Logger.Error(undeletionTask.Exception, "Failed to restore mod presets");
|
||||
}
|
||||
|
@ -103,13 +103,14 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
globallyDisableBeatmapSkinSetting();
|
||||
|
||||
if (lastTargetScreen is MainMenu)
|
||||
PresentGameplay();
|
||||
|
||||
if (skinEditor != null)
|
||||
{
|
||||
disableNestedInputManagers();
|
||||
skinEditor.Show();
|
||||
|
||||
if (lastTargetScreen is MainMenu)
|
||||
PresentGameplay();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -126,6 +127,9 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
|
||||
AddInternal(editor);
|
||||
|
||||
if (lastTargetScreen is MainMenu)
|
||||
PresentGameplay();
|
||||
|
||||
Debug.Assert(lastTargetScreen != null);
|
||||
|
||||
SetTarget(lastTargetScreen);
|
||||
@ -270,7 +274,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
|
||||
Debug.Assert(skinEditor != null);
|
||||
|
||||
if (!target.IsLoaded)
|
||||
if (!target.IsLoaded || !skinEditor.IsLoaded)
|
||||
{
|
||||
Scheduler.AddOnce(setTarget, target);
|
||||
return;
|
||||
@ -350,7 +354,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
base.LoadComplete();
|
||||
|
||||
if (!LoadedBeatmapSuccessfully)
|
||||
Scheduler.AddDelayed(this.Exit, 3000);
|
||||
Scheduler.AddDelayed(this.Exit, 1000);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
if (objTime >= editorClock.CurrentTime)
|
||||
continue;
|
||||
|
||||
if (objTime > lastBefore?.StartTime)
|
||||
if (lastBefore == null || objTime > lastBefore.StartTime)
|
||||
lastBefore = entry.Value.HitObject;
|
||||
}
|
||||
|
||||
@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
if (objTime < editorClock.CurrentTime)
|
||||
continue;
|
||||
|
||||
if (objTime < firstAfter?.StartTime)
|
||||
if (firstAfter == null || objTime < firstAfter.StartTime)
|
||||
firstAfter = entry.Value.HitObject;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -13,6 +14,7 @@ using osu.Framework.Graphics.Shaders;
|
||||
using osu.Framework.Graphics.Shaders.Types;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.OpenGL.Vertices;
|
||||
@ -84,6 +86,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
flashlight.Depth = float.MinValue;
|
||||
|
||||
flashlight.Combo.BindTo(Combo);
|
||||
flashlight.GetPlayfieldScale = () => drawableRuleset.Playfield.Scale;
|
||||
|
||||
drawableRuleset.Overlays.Add(flashlight);
|
||||
}
|
||||
@ -100,6 +103,8 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public override bool RemoveCompletedTransforms => false;
|
||||
|
||||
internal Func<Vector2>? GetPlayfieldScale;
|
||||
|
||||
private readonly float defaultFlashlightSize;
|
||||
private readonly float sizeMultiplier;
|
||||
private readonly bool comboBasedSize;
|
||||
@ -139,10 +144,19 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
protected abstract string FragmentShader { get; }
|
||||
|
||||
protected float GetSize()
|
||||
public float GetSize()
|
||||
{
|
||||
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)
|
||||
size *= 2.5f;
|
||||
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 ModType Type => ModType.Fun;
|
||||
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
|
||||
|
@ -11,10 +11,12 @@ using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ListExtensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
@ -65,7 +67,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
public virtual IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples;
|
||||
|
||||
private readonly List<DrawableHitObject> nestedHitObjects = new List<DrawableHitObject>();
|
||||
public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects;
|
||||
public SlimReadOnlyListWrapper<DrawableHitObject> NestedHitObjects => nestedHitObjects.AsSlimReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Whether this object should handle any user input events.
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Performance;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
@ -41,7 +40,22 @@ namespace osu.Game.Rulesets.Objects
|
||||
/// <summary>
|
||||
/// Whether <see cref="HitObject"/> and all of its nested objects have been judged.
|
||||
/// </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();
|
||||
|
||||
|
@ -47,35 +47,31 @@ namespace osu.Game.Screens.Edit
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background4,
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new GridContainer
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.Absolute, 170),
|
||||
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,
|
||||
}
|
||||
},
|
||||
}
|
||||
new Dimension(GridSizeMode.Absolute, 170),
|
||||
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,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -97,11 +97,14 @@ namespace osu.Game.Screens.Edit.Components
|
||||
editorClock.Start();
|
||||
}
|
||||
|
||||
private static readonly IconUsage play_icon = FontAwesome.Regular.PlayCircle;
|
||||
private static readonly IconUsage pause_icon = FontAwesome.Regular.PauseCircle;
|
||||
|
||||
protected override void 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>
|
||||
|
@ -47,11 +47,26 @@ namespace osu.Game.Screens.Edit.Components
|
||||
};
|
||||
}
|
||||
|
||||
private double? lastTime;
|
||||
private double? lastBPM;
|
||||
|
||||
protected override void 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,
|
||||
Colour = colourProvider.Background3
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new GridContainer
|
||||
Content = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
new Drawable[]
|
||||
{
|
||||
new Drawable[]
|
||||
new ChevronButton
|
||||
{
|
||||
new ChevronButton
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronLeft,
|
||||
Action = beatDivisor.SelectPrevious
|
||||
},
|
||||
new DivisorDisplay { BeatDivisor = { BindTarget = beatDivisor } },
|
||||
new ChevronButton
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronRight,
|
||||
Action = beatDivisor.SelectNext
|
||||
}
|
||||
Icon = FontAwesome.Solid.ChevronLeft,
|
||||
Action = beatDivisor.SelectPrevious
|
||||
},
|
||||
new DivisorDisplay { BeatDivisor = { BindTarget = beatDivisor } },
|
||||
new ChevronButton
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronRight,
|
||||
Action = beatDivisor.SelectNext
|
||||
}
|
||||
},
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -122,42 +118,31 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
Content = new[]
|
||||
{
|
||||
new Container
|
||||
new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new GridContainer
|
||||
new ChevronButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new ChevronButton
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronLeft,
|
||||
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)
|
||||
}
|
||||
Icon = FontAwesome.Solid.ChevronLeft,
|
||||
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)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -116,6 +116,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
updateStacking();
|
||||
}
|
||||
|
||||
private readonly Stack<HitObject> currentConcurrentObjects = new Stack<HitObject>();
|
||||
|
||||
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.
|
||||
@ -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.
|
||||
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.
|
||||
while (currentConcurrentObjects.TryPeek(out HitObject hitObject))
|
||||
{
|
||||
|
@ -1,10 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -20,7 +18,7 @@ namespace osu.Game.Screens.Edit
|
||||
/// </summary>
|
||||
public class EditorBeatmapSkin : ISkin
|
||||
{
|
||||
public event Action BeatmapSkinChanged;
|
||||
public event Action? BeatmapSkinChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The underlying beatmap skin.
|
||||
@ -38,8 +36,17 @@ namespace osu.Game.Screens.Edit
|
||||
Skin = skin;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@ -47,16 +54,23 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
#region Delegated ISkin implementation
|
||||
|
||||
public Drawable GetDrawableComponent(ISkinComponentLookup lookup) => Skin.GetDrawableComponent(lookup);
|
||||
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Skin.GetTexture(componentName, wrapModeS, wrapModeT);
|
||||
public ISample GetSample(ISampleInfo sampleInfo) => Skin.GetSample(sampleInfo);
|
||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => Skin.GetConfig<TLookup, TValue>(lookup);
|
||||
public Drawable? GetDrawableComponent(ISkinComponentLookup lookup) => Skin.GetDrawableComponent(lookup);
|
||||
public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Skin.GetTexture(componentName, wrapModeS, wrapModeT);
|
||||
public ISample? GetSample(ISampleInfo sampleInfo) => Skin.GetSample(sampleInfo);
|
||||
|
||||
public IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||
where TLookup : notnull
|
||||
where TValue : notnull
|
||||
=> Skin.GetConfig<TLookup, TValue>(lookup);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
@ -57,37 +57,32 @@ namespace osu.Game.Screens.Edit
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background4
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
Name = "Timeline content",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Horizontal = PADDING, Top = PADDING },
|
||||
Child = new GridContainer
|
||||
Content = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Content = new[]
|
||||
new Drawable[]
|
||||
{
|
||||
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 Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(padding),
|
||||
Children = new Drawable[]
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new GridContainer
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension()
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
metronome = new MetronomeDisplay
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension()
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
metronome = new MetronomeDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
new WaveformComparisonDisplay()
|
||||
}
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
new WaveformComparisonDisplay()
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
|
@ -34,25 +34,21 @@ namespace osu.Game.Screens.Edit.Verify
|
||||
InterpretedDifficulty.Default = StarDifficulty.GetDifficultyRating(EditorBeatmap.BeatmapInfo.StarRating);
|
||||
InterpretedDifficulty.SetDefault();
|
||||
|
||||
Child = new Container
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new GridContainer
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 250),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 250),
|
||||
IssueList = new IssueList(),
|
||||
new IssueSettings(),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
IssueList = new IssueList(),
|
||||
new IssueSettings(),
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
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,
|
||||
-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.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.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.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))]
|
||||
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
||||
|
||||
private readonly Drawable playlistArea;
|
||||
private readonly GridContainer playlistArea;
|
||||
private readonly DrawableRoomPlaylist playlist;
|
||||
|
||||
public MatchBeatmapDetailArea()
|
||||
{
|
||||
Add(playlistArea = new Container
|
||||
Add(playlistArea = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Vertical = 10 },
|
||||
Child = new GridContainer
|
||||
Content = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
new Drawable[]
|
||||
{
|
||||
new Drawable[]
|
||||
new Container
|
||||
{
|
||||
new Container
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Bottom = 10 },
|
||||
Child = playlist = new PlaylistsRoomSettingsPlaylist
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Bottom = 10 },
|
||||
Child = playlist = new PlaylistsRoomSettingsPlaylist
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
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 Dimension(GridSizeMode.Absolute, 50),
|
||||
}
|
||||
new RoundedButton
|
||||
{
|
||||
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)
|
||||
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
|
||||
difficultyIconContainer.Clear();
|
||||
|
||||
|
@ -40,35 +40,31 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.5f
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Horizontal = padding },
|
||||
Child = new GridContainer
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
new Dimension(GridSizeMode.AutoSize, minSize: 80 - 2 * padding)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize, minSize: 80 - 2 * padding)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
new Container
|
||||
{
|
||||
new Container
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Padding = new MarginPadding { Bottom = 2 },
|
||||
Child = content = new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = 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[]
|
||||
{
|
||||
// Playlist items column
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Right = 5 },
|
||||
Child = new GridContainer
|
||||
Content = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
new Drawable[] { new OverlinedPlaylistHeader(), },
|
||||
new Drawable[]
|
||||
{
|
||||
new Drawable[] { new OverlinedPlaylistHeader(), },
|
||||
new Drawable[]
|
||||
new DrawableRoomPlaylist
|
||||
{
|
||||
new DrawableRoomPlaylist
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Items = { BindTarget = Room.Playlist },
|
||||
SelectedItem = { BindTarget = SelectedItem },
|
||||
AllowSelection = true,
|
||||
AllowShowingResults = true,
|
||||
RequestResults = item =>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Items = { BindTarget = Room.Playlist },
|
||||
SelectedItem = { BindTarget = SelectedItem },
|
||||
AllowSelection = true,
|
||||
AllowShowingResults = true,
|
||||
RequestResults = item =>
|
||||
{
|
||||
Debug.Assert(RoomId.Value != null);
|
||||
ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false));
|
||||
}
|
||||
Debug.Assert(RoomId.Value != null);
|
||||
ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false));
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
}
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
}
|
||||
},
|
||||
// Spacer
|
||||
|
@ -258,14 +258,10 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
Vector2? highestBottomScreenSpace = null;
|
||||
|
||||
foreach (var element in mainComponents.Components)
|
||||
processDrawable(element);
|
||||
processDrawables(mainComponents);
|
||||
|
||||
if (rulesetComponents != null)
|
||||
{
|
||||
foreach (var element in rulesetComponents.Components)
|
||||
processDrawable(element);
|
||||
}
|
||||
processDrawables(rulesetComponents);
|
||||
|
||||
if (lowestTopScreenSpaceRight.HasValue)
|
||||
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
|
||||
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)
|
||||
{
|
||||
// Cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes.
|
||||
|
@ -545,7 +545,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -150,44 +150,40 @@ namespace osu.Game.Screens.Ranking.Contracted
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Vertical = 5 },
|
||||
Child = new GridContainer
|
||||
Content = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
new Drawable[]
|
||||
{
|
||||
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,
|
||||
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[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
}
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
/// <summary>
|
||||
/// Relative width of the rank circles.
|
||||
/// </summary>
|
||||
public const float RANK_CIRCLE_RADIUS = 0.06f;
|
||||
public const float RANK_CIRCLE_RADIUS = 0.05f;
|
||||
|
||||
/// <summary>
|
||||
/// Relative width of the circle showing the accuracy.
|
||||
@ -79,12 +79,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
/// <summary>
|
||||
/// SS is displayed as a 1% region, otherwise it would be invisible.
|
||||
/// </summary>
|
||||
private const double virtual_ss_percentage = 0.01;
|
||||
public const double VIRTUAL_SS_PERCENTAGE = 0.01;
|
||||
|
||||
/// <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>
|
||||
public const double NOTCH_WIDTH_PERCENTAGE = 1.0 / 360;
|
||||
public const double GRADE_SPACING_PERCENTAGE = 2.0 / 360;
|
||||
|
||||
/// <summary>
|
||||
/// The easing for the circle filling transforms.
|
||||
@ -94,7 +94,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
private readonly ScoreInfo score;
|
||||
|
||||
private CircularProgress accuracyCircle;
|
||||
private CircularProgress innerMask;
|
||||
private GradedCircles gradedCircles;
|
||||
private Container<RankBadge> badges;
|
||||
private RankText rankText;
|
||||
|
||||
@ -163,82 +163,16 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#7CF6FF"), Color4Extensions.FromHex("#BAFFA9")),
|
||||
InnerRadius = accuracy_circle_radius,
|
||||
},
|
||||
new BufferedContainer
|
||||
new Container
|
||||
{
|
||||
Name = "Graded circles",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(0.8f),
|
||||
Padding = new MarginPadding(2),
|
||||
Children = new Drawable[]
|
||||
Padding = new MarginPadding(2.5f),
|
||||
Child = gradedCircles = new GradedCircles(accuracyC, accuracyB, accuracyA, accuracyS, accuracyX)
|
||||
{
|
||||
new CircularProgress
|
||||
{
|
||||
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,
|
||||
}
|
||||
}
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
},
|
||||
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)),
|
||||
// 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(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)),
|
||||
}
|
||||
},
|
||||
@ -301,7 +235,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
@ -318,10 +252,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
// to prevent ambiguity on what grade it's pointing at.
|
||||
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
|
||||
targetAccuracy = p + tippingDirection * (NOTCH_WIDTH_PERCENTAGE / 2);
|
||||
targetAccuracy = p + tippingDirection * (GRADE_SPACING_PERCENTAGE / 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -330,7 +264,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
if (score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH)
|
||||
targetAccuracy = 1;
|
||||
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.
|
||||
// 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;
|
||||
|
||||
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();
|
||||
|
||||
@ -430,7 +364,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
.FadeOut(800, Easing.Out);
|
||||
|
||||
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))
|
||||
.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,
|
||||
Colour = Colour4.Black.Opacity(0.3f),
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
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,
|
||||
RowDimensions = new[]
|
||||
new FillFlowContainer
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension()
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new Drawable[]
|
||||
Width = 0.5f,
|
||||
Spacing = new Vector2(spacing),
|
||||
Padding = new MarginPadding { Right = spacing / 2 },
|
||||
Children = new[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
new DetailBox().WithChild(new OnlineViewContainer(string.Empty)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 0.5f,
|
||||
Spacing = new Vector2(spacing),
|
||||
Padding = new MarginPadding { Right = spacing / 2 },
|
||||
Children = new[]
|
||||
Height = 134,
|
||||
Padding = new MarginPadding { Horizontal = spacing, Top = spacing },
|
||||
Child = ratingsDisplay = new UserRatings
|
||||
{
|
||||
new DetailBox().WithChild(new OnlineViewContainer(string.Empty)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 134,
|
||||
Padding = new MarginPadding { Horizontal = spacing, Top = spacing },
|
||||
Child = ratingsDisplay = new UserRatings
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
}),
|
||||
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,
|
||||
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[]
|
||||
{
|
||||
description = new MetadataSectionDescription(query => songSelect?.Search(query)),
|
||||
source = new MetadataSectionSource(query => songSelect?.Search(query)),
|
||||
tags = new MetadataSectionTags(query => songSelect?.Search(query)),
|
||||
},
|
||||
},
|
||||
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,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
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 },
|
||||
},
|
||||
},
|
||||
Padding = new MarginPadding { Top = 14 + spacing / 2 },
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
loading = new LoadingLayer(true)
|
||||
};
|
||||
|
@ -31,6 +31,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Screens.Select
|
||||
{
|
||||
@ -405,9 +406,9 @@ namespace osu.Game.Screens.Select
|
||||
foreach (var mod in mods.Value.OfType<IApplicableToRate>())
|
||||
rate = mod.ApplyToRate(0, rate);
|
||||
|
||||
int bpmMax = (int)Math.Round(Math.Round(beatmap.ControlPointInfo.BPMMaximum) * rate);
|
||||
int bpmMin = (int)Math.Round(Math.Round(beatmap.ControlPointInfo.BPMMinimum) * rate);
|
||||
int mostCommonBPM = (int)Math.Round(Math.Round(60000 / beatmap.GetMostCommonBeatLength()) * rate);
|
||||
int bpmMax = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMaximum, rate);
|
||||
int bpmMin = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMinimum, rate);
|
||||
int mostCommonBPM = FormatUtils.RoundBPM(60000 / beatmap.GetMostCommonBeatLength(), rate);
|
||||
|
||||
string labelText = bpmMin == bpmMax
|
||||
? $"{bpmMin}"
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
@ -67,19 +68,19 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
default:
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
case SortMode.DateAdded:
|
||||
|
@ -122,7 +122,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
difficultyIcon = new DifficultyIcon(beatmapInfo)
|
||||
{
|
||||
ShowTooltip = false,
|
||||
TooltipType = DifficultyIconTooltipType.None,
|
||||
Scale = new Vector2(1.8f),
|
||||
},
|
||||
new FillFlowContainer
|
||||
|
@ -4,6 +4,7 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -64,7 +65,7 @@ namespace osu.Game.Screens.Select
|
||||
Sort = sortMode.Value,
|
||||
AllowConvertedBeatmaps = showConverted.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)
|
||||
|
@ -166,6 +166,9 @@ namespace osu.Game.Users
|
||||
globalRankDisplay = new ProfileValueDisplay(true)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -49,5 +49,15 @@ namespace osu.Game.Utils
|
||||
|
||||
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>
|
||||
</PackageReference>
|
||||
<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="Sentry" Version="3.41.3" />
|
||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||
|
@ -23,6 +23,6 @@
|
||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.217.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.221.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -102,6 +102,19 @@
|
||||
<string>osz</string>
|
||||
</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>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
|
Loading…
Reference in New Issue
Block a user