mirror of
https://github.com/ppy/osu.git
synced 2024-11-13 18:47:27 +08:00
Merge branch 'master' into fix-old-beatmap-crash-on-load
This commit is contained in:
commit
27055919a3
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
@ -13,17 +13,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
# FIXME: Tools won't run in .NET 6.0 unless you install 3.1.x LTS side by side.
|
# FIXME: Tools won't run in .NET 6.0 unless you install 3.1.x LTS side by side.
|
||||||
# https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e
|
# https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e
|
||||||
- name: Install .NET 3.1.x LTS
|
- name: Install .NET 3.1.x LTS
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "3.1.x"
|
dotnet-version: "3.1.x"
|
||||||
|
|
||||||
- name: Install .NET 6.0.x
|
- name: Install .NET 6.0.x
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "6.0.x"
|
||||||
|
|
||||||
@ -77,10 +77,10 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install .NET 6.0.x
|
- name: Install .NET 6.0.x
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "6.0.x"
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ jobs:
|
|||||||
# Attempt to upload results even if test fails.
|
# Attempt to upload results even if test fails.
|
||||||
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always
|
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
with:
|
with:
|
||||||
name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
||||||
@ -106,10 +106,10 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install .NET 6.0.x
|
- name: Install .NET 6.0.x
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "6.0.x"
|
||||||
|
|
||||||
@ -125,10 +125,10 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install .NET 6.0.x
|
- name: Install .NET 6.0.x
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "6.0.x"
|
||||||
|
|
||||||
|
18
.github/workflows/diffcalc.yml
vendored
18
.github/workflows/diffcalc.yml
vendored
@ -48,8 +48,8 @@ jobs:
|
|||||||
CONTINUE="no"
|
CONTINUE="no"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "::set-output name=continue::${CONTINUE}"
|
echo "continue=${CONTINUE}" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=matrix::${MATRIX_JSON}"
|
echo "matrix=${MATRIX_JSON}" >> $GITHUB_OUTPUT
|
||||||
diffcalc:
|
diffcalc:
|
||||||
name: Run
|
name: Run
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
@ -80,34 +80,34 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=branchname::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')"
|
echo "branchname=$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=repo::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.repo.full_name' | sed 's/\"//g')"
|
echo "repo=$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.repo.full_name' | sed 's/\"//g')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
# Checkout osu
|
# Checkout osu
|
||||||
- name: Checkout osu (master)
|
- name: Checkout osu (master)
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: 'master/osu'
|
path: 'master/osu'
|
||||||
- name: Checkout osu (pr)
|
- name: Checkout osu (pr)
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: 'pr/osu'
|
path: 'pr/osu'
|
||||||
repository: ${{ steps.upstreambranch.outputs.repo }}
|
repository: ${{ steps.upstreambranch.outputs.repo }}
|
||||||
ref: ${{ steps.upstreambranch.outputs.branchname }}
|
ref: ${{ steps.upstreambranch.outputs.branchname }}
|
||||||
|
|
||||||
- name: Checkout osu-difficulty-calculator (master)
|
- name: Checkout osu-difficulty-calculator (master)
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: ppy/osu-difficulty-calculator
|
repository: ppy/osu-difficulty-calculator
|
||||||
path: 'master/osu-difficulty-calculator'
|
path: 'master/osu-difficulty-calculator'
|
||||||
- name: Checkout osu-difficulty-calculator (pr)
|
- name: Checkout osu-difficulty-calculator (pr)
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: ppy/osu-difficulty-calculator
|
repository: ppy/osu-difficulty-calculator
|
||||||
path: 'pr/osu-difficulty-calculator'
|
path: 'pr/osu-difficulty-calculator'
|
||||||
|
|
||||||
- name: Install .NET 5.0.x
|
- name: Install .NET 5.0.x
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "5.0.x"
|
dotnet-version: "5.0.x"
|
||||||
|
|
||||||
|
2
.github/workflows/sentry-release.yml
vendored
2
.github/workflows/sentry-release.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<!-- Contains required properties for osu!framework projects. -->
|
<!-- Contains required properties for osu!framework projects. -->
|
||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup Label="C#">
|
<PropertyGroup Label="C#">
|
||||||
<LangVersion>9.0</LangVersion>
|
<LangVersion>10.0</LangVersion>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
@ -105,7 +105,7 @@ When it comes to contributing to the project, the two main things you can do to
|
|||||||
|
|
||||||
If you wish to help with localisation efforts, head over to [crowdin](https://crowdin.com/project/osu-web).
|
If you wish to help with localisation efforts, head over to [crowdin](https://crowdin.com/project/osu-web).
|
||||||
|
|
||||||
For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via PayPal or osu!supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project.
|
We love to reward quality contributions. If you have made a large contribution, or are a regular contributor, you are welcome to [submit an expense via opencollective](https://opencollective.com/ppy/expenses/new). If you have any questions, feel free to [reach out to peppy](mailto:pe@ppy.sh) before doing so.
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
|
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.228.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.403.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
|
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -139,7 +138,17 @@ namespace osu.Desktop
|
|||||||
|
|
||||||
desktopWindow.CursorState |= CursorState.Hidden;
|
desktopWindow.CursorState |= CursorState.Hidden;
|
||||||
desktopWindow.Title = Name;
|
desktopWindow.Title = Name;
|
||||||
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
desktopWindow.DragDrop += f =>
|
||||||
|
{
|
||||||
|
// on macOS, URL associations are handled via SDL_DROPFILE events.
|
||||||
|
if (f.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
HandleLink(f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileDrop(new[] { f });
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
|
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
|
||||||
@ -151,10 +160,6 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
lock (importableFiles)
|
lock (importableFiles)
|
||||||
{
|
{
|
||||||
string firstExtension = Path.GetExtension(filePaths.First());
|
|
||||||
|
|
||||||
if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
|
|
||||||
|
|
||||||
importableFiles.AddRange(filePaths);
|
importableFiles.AddRange(filePaths);
|
||||||
|
|
||||||
Logger.Log($"Adding {filePaths.Length} files for import");
|
Logger.Log($"Adding {filePaths.Length} files for import");
|
||||||
|
@ -1,17 +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.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Foundation;
|
|
||||||
using osu.Framework.iOS;
|
|
||||||
using osu.Game.Tests;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests.iOS
|
|
||||||
{
|
|
||||||
[Register("AppDelegate")]
|
|
||||||
public class AppDelegate : GameAppDelegate
|
|
||||||
{
|
|
||||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using osu.Framework.iOS;
|
||||||
|
using osu.Game.Tests;
|
||||||
using UIKit;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests.iOS
|
namespace osu.Game.Rulesets.Catch.Tests.iOS
|
||||||
{
|
{
|
||||||
@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Catch.Tests.iOS
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
GameApplication.Main(new OsuTestBrowser());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
|
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
|
||||||
{
|
{
|
||||||
Precision = 0.1f,
|
Precision = 0.1f,
|
||||||
MinValue = 1,
|
MinValue = 0,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
ExtendedMaxValue = 11,
|
ExtendedMaxValue = 11,
|
||||||
ReadCurrentFromDifficulty = diff => diff.CircleSize,
|
ReadCurrentFromDifficulty = diff => diff.CircleSize,
|
||||||
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
public DifficultyBindable ApproachRate { get; } = new DifficultyBindable
|
public DifficultyBindable ApproachRate { get; } = new DifficultyBindable
|
||||||
{
|
{
|
||||||
Precision = 0.1f,
|
Precision = 0.1f,
|
||||||
MinValue = 1,
|
MinValue = 0,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
ExtendedMaxValue = 11,
|
ExtendedMaxValue = 11,
|
||||||
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
|
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
|
||||||
|
@ -136,6 +136,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
Origin = Anchor.TopCentre;
|
Origin = Anchor.TopCentre;
|
||||||
|
|
||||||
Size = new Vector2(BASE_SIZE);
|
Size = new Vector2(BASE_SIZE);
|
||||||
|
|
||||||
if (difficulty != null)
|
if (difficulty != null)
|
||||||
Scale = calculateScale(difficulty);
|
Scale = calculateScale(difficulty);
|
||||||
|
|
||||||
@ -333,8 +334,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
var scaleFromDirection = new Vector2((int)VisualDirection, 1);
|
var scaleFromDirection = new Vector2((int)VisualDirection, 1);
|
||||||
|
|
||||||
body.Scale = scaleFromDirection;
|
body.Scale = scaleFromDirection;
|
||||||
caughtObjectContainer.Scale = hitExplosionContainer.Scale = flipCatcherPlate ? scaleFromDirection : Vector2.One;
|
// Inverse of catcher scale is applied here, as catcher gets scaled by circle size and so do the incoming fruit.
|
||||||
|
caughtObjectContainer.Scale = (1 / Scale.X) * (flipCatcherPlate ? scaleFromDirection : Vector2.One);
|
||||||
|
hitExplosionContainer.Scale = flipCatcherPlate ? scaleFromDirection : Vector2.One;
|
||||||
|
|
||||||
// Correct overshooting.
|
// Correct overshooting.
|
||||||
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
|
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
|
||||||
|
@ -1,17 +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.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Foundation;
|
|
||||||
using osu.Framework.iOS;
|
|
||||||
using osu.Game.Tests;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.iOS
|
|
||||||
{
|
|
||||||
[Register("AppDelegate")]
|
|
||||||
public class AppDelegate : GameAppDelegate
|
|
||||||
{
|
|
||||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using osu.Framework.iOS;
|
||||||
|
using osu.Game.Tests;
|
||||||
using UIKit;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.iOS
|
namespace osu.Game.Rulesets.Mania.Tests.iOS
|
||||||
{
|
{
|
||||||
@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Mania.Tests.iOS
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
GameApplication.Main(new OsuTestBrowser());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Game.Rulesets.Mania.Configuration;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
@ -10,5 +13,19 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
public partial class TestSceneManiaPlayer : PlayerTestScene
|
public partial class TestSceneManiaPlayer : PlayerTestScene
|
||||||
{
|
{
|
||||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddStep("change direction to down", () => changeDirectionTo(ManiaScrollingDirection.Down));
|
||||||
|
AddStep("change direction to up", () => changeDirectionTo(ManiaScrollingDirection.Up));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changeDirectionTo(ManiaScrollingDirection direction)
|
||||||
|
{
|
||||||
|
var rulesetConfig = (ManiaRulesetConfigManager)RulesetConfigs.GetConfigFor(new ManiaRuleset()).AsNonNull();
|
||||||
|
rulesetConfig.SetValue(ManiaRulesetSetting.ScrollDirection, direction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,6 +236,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Position and resize the body to lie half-way under the head and the tail notes.
|
// Position and resize the body to lie half-way under the head and the tail notes.
|
||||||
|
// The rationale for this is account for heads/tails with corner radius.
|
||||||
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2;
|
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2;
|
||||||
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
|
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
{
|
{
|
||||||
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
|
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
|
||||||
|
|
||||||
protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
|
protected internal DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
|
||||||
|
|
||||||
public DrawableHoldNoteTail()
|
public DrawableHoldNoteTail()
|
||||||
: this(null)
|
: this(null)
|
||||||
|
@ -43,9 +43,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
{
|
{
|
||||||
largeFaint = new Container
|
largeFaint = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||||
Blending = BlendingParameters.Additive,
|
Blending = BlendingParameters.Additive,
|
||||||
@ -80,11 +79,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
if (direction.NewValue == ScrollingDirection.Up)
|
if (direction.NewValue == ScrollingDirection.Up)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre;
|
Anchor = Anchor.TopCentre;
|
||||||
|
largeFaint.Anchor = Anchor.TopCentre;
|
||||||
|
largeFaint.Origin = Anchor.TopCentre;
|
||||||
Y = ArgonNotePiece.NOTE_HEIGHT / 2;
|
Y = ArgonNotePiece.NOTE_HEIGHT / 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomCentre;
|
Anchor = Anchor.BottomCentre;
|
||||||
|
largeFaint.Anchor = Anchor.BottomCentre;
|
||||||
|
largeFaint.Origin = Anchor.BottomCentre;
|
||||||
Y = -ArgonNotePiece.NOTE_HEIGHT / 2;
|
Y = -ArgonNotePiece.NOTE_HEIGHT / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
private void load(IScrollingInfo scrollingInfo)
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = ArgonNotePiece.NOTE_HEIGHT;
|
Height = ArgonNotePiece.NOTE_HEIGHT * ArgonNotePiece.NOTE_ACCENT_RATIO;
|
||||||
|
|
||||||
Masking = true;
|
Masking = true;
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
||||||
|
@ -20,10 +20,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
public partial class ArgonHoldBodyPiece : CompositeDrawable, IHoldNoteBody
|
public partial class ArgonHoldBodyPiece : CompositeDrawable, IHoldNoteBody
|
||||||
{
|
{
|
||||||
protected readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
protected readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||||
protected readonly IBindable<bool> IsHitting = new Bindable<bool>();
|
|
||||||
|
|
||||||
private Drawable background = null!;
|
private Drawable background = null!;
|
||||||
private Box foreground = null!;
|
private ArgonHoldNoteHittingLayer hittingLayer = null!;
|
||||||
|
|
||||||
public ArgonHoldBodyPiece()
|
public ArgonHoldBodyPiece()
|
||||||
{
|
{
|
||||||
@ -32,7 +31,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
// Without this, the width of the body will be slightly larger than the head/tail.
|
// Without this, the width of the body will be slightly larger than the head/tail.
|
||||||
Masking = true;
|
Masking = true;
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
||||||
Blending = BlendingParameters.Additive;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
@ -41,12 +39,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
background = new Box { RelativeSizeAxes = Axes.Both },
|
background = new Box { RelativeSizeAxes = Axes.Both },
|
||||||
foreground = new Box
|
hittingLayer = new ArgonHoldNoteHittingLayer()
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
Alpha = 0,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (drawableObject != null)
|
if (drawableObject != null)
|
||||||
@ -54,44 +47,19 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
var holdNote = (DrawableHoldNote)drawableObject;
|
var holdNote = (DrawableHoldNote)drawableObject;
|
||||||
|
|
||||||
AccentColour.BindTo(holdNote.AccentColour);
|
AccentColour.BindTo(holdNote.AccentColour);
|
||||||
IsHitting.BindTo(holdNote.IsHitting);
|
hittingLayer.AccentColour.BindTo(holdNote.AccentColour);
|
||||||
|
((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNote.IsHitting);
|
||||||
}
|
}
|
||||||
|
|
||||||
AccentColour.BindValueChanged(colour =>
|
AccentColour.BindValueChanged(colour =>
|
||||||
{
|
{
|
||||||
background.Colour = colour.NewValue.Darken(1.2f);
|
background.Colour = colour.NewValue.Darken(0.6f);
|
||||||
foreground.Colour = colour.NewValue.Opacity(0.2f);
|
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
IsHitting.BindValueChanged(hitting =>
|
|
||||||
{
|
|
||||||
const float animation_length = 50;
|
|
||||||
|
|
||||||
foreground.ClearTransforms();
|
|
||||||
|
|
||||||
if (hitting.NewValue)
|
|
||||||
{
|
|
||||||
// wait for the next sync point
|
|
||||||
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
|
|
||||||
|
|
||||||
using (foreground.BeginDelayedSequence(synchronisedOffset))
|
|
||||||
{
|
|
||||||
foreground.FadeTo(1, animation_length).Then()
|
|
||||||
.FadeTo(0.5f, animation_length)
|
|
||||||
.Loop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreground.FadeOut(animation_length);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Recycle()
|
public void Recycle()
|
||||||
{
|
{
|
||||||
foreground.ClearTransforms();
|
hittingLayer.Recycle();
|
||||||
foreground.Alpha = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
internal partial class ArgonHoldNoteHeadPiece : ArgonNotePiece
|
||||||
|
{
|
||||||
|
protected override Drawable CreateIcon() => new Circle
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Y = 2,
|
||||||
|
Size = new Vector2(20, 5),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using Box = osu.Framework.Graphics.Shapes.Box;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
public partial class ArgonHoldNoteHittingLayer : Box
|
||||||
|
{
|
||||||
|
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||||
|
public readonly Bindable<bool> IsHitting = new Bindable<bool>();
|
||||||
|
|
||||||
|
public ArgonHoldNoteHittingLayer()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
Alpha = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
AccentColour.BindValueChanged(colour =>
|
||||||
|
{
|
||||||
|
Colour = colour.NewValue.Lighten(0.2f).Opacity(0.3f);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
IsHitting.BindValueChanged(hitting =>
|
||||||
|
{
|
||||||
|
const float animation_length = 80;
|
||||||
|
|
||||||
|
ClearTransforms();
|
||||||
|
|
||||||
|
if (hitting.NewValue)
|
||||||
|
{
|
||||||
|
// wait for the next sync point
|
||||||
|
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(synchronisedOffset))
|
||||||
|
{
|
||||||
|
this.FadeTo(1, animation_length, Easing.OutSine).Then()
|
||||||
|
.FadeTo(0.5f, animation_length, Easing.InSine)
|
||||||
|
.Loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.FadeOut(animation_length);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Recycle()
|
||||||
|
{
|
||||||
|
ClearTransforms();
|
||||||
|
Alpha = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,8 +5,10 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -16,47 +18,68 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
{
|
{
|
||||||
internal partial class ArgonHoldNoteTailPiece : CompositeDrawable
|
internal partial class ArgonHoldNoteTailPiece : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
[Resolved]
|
||||||
|
private DrawableHitObject? drawableObject { get; set; }
|
||||||
|
|
||||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||||
|
|
||||||
private readonly Box shadeBackground;
|
private readonly Box foreground;
|
||||||
private readonly Box shadeForeground;
|
private readonly ArgonHoldNoteHittingLayer hittingLayer;
|
||||||
|
private readonly Box foregroundAdditive;
|
||||||
|
|
||||||
public ArgonHoldNoteTailPiece()
|
public ArgonHoldNoteTailPiece()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = ArgonNotePiece.NOTE_HEIGHT;
|
Height = ArgonNotePiece.NOTE_HEIGHT;
|
||||||
|
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
|
||||||
Masking = true;
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
shadeBackground = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
|
Height = ArgonNotePiece.NOTE_HEIGHT,
|
||||||
Anchor = Anchor.BottomCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
shadeForeground = new Box
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Colour4.Black),
|
||||||
|
// Avoid ugly single pixel overlap.
|
||||||
|
Height = 0.9f,
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
|
||||||
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||||
|
Masking = true,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
foreground = new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
|
hittingLayer = new ArgonHoldNoteHittingLayer(),
|
||||||
|
foregroundAdditive = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Height = 0.5f,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
{
|
{
|
||||||
direction.BindTo(scrollingInfo.Direction);
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
direction.BindValueChanged(onDirectionChanged, true);
|
direction.BindValueChanged(onDirectionChanged, true);
|
||||||
@ -65,9 +88,24 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
{
|
{
|
||||||
accentColour.BindTo(drawableObject.AccentColour);
|
accentColour.BindTo(drawableObject.AccentColour);
|
||||||
accentColour.BindValueChanged(onAccentChanged, true);
|
accentColour.BindValueChanged(onAccentChanged, true);
|
||||||
|
|
||||||
|
drawableObject.HitObjectApplied += hitObjectApplied;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void hitObjectApplied(DrawableHitObject drawableHitObject)
|
||||||
|
{
|
||||||
|
var holdNoteTail = (DrawableHoldNoteTail)drawableHitObject;
|
||||||
|
|
||||||
|
hittingLayer.Recycle();
|
||||||
|
|
||||||
|
hittingLayer.AccentColour.UnbindBindings();
|
||||||
|
hittingLayer.AccentColour.BindTo(holdNoteTail.HoldNote.AccentColour);
|
||||||
|
|
||||||
|
hittingLayer.IsHitting.UnbindBindings();
|
||||||
|
((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNoteTail.HoldNote.IsHitting);
|
||||||
|
}
|
||||||
|
|
||||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
{
|
{
|
||||||
Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Up ? -1 : 1);
|
Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Up ? -1 : 1);
|
||||||
@ -75,8 +113,20 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
|
|
||||||
private void onAccentChanged(ValueChangedEvent<Color4> accent)
|
private void onAccentChanged(ValueChangedEvent<Color4> accent)
|
||||||
{
|
{
|
||||||
shadeBackground.Colour = accent.NewValue.Darken(1.7f);
|
foreground.Colour = accent.NewValue.Darken(0.6f); // matches body
|
||||||
shadeForeground.Colour = accent.NewValue.Darken(1.1f);
|
|
||||||
|
foregroundAdditive.Colour = ColourInfo.GradientVertical(
|
||||||
|
accent.NewValue.Opacity(0.4f),
|
||||||
|
accent.NewValue.Opacity(0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (drawableObject != null)
|
||||||
|
drawableObject.HitObjectApplied -= hitObjectApplied;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||||
|
|
||||||
private readonly Box colouredBox;
|
private readonly Box colouredBox;
|
||||||
private readonly Box shadow;
|
|
||||||
|
|
||||||
public ArgonNotePiece()
|
public ArgonNotePiece()
|
||||||
{
|
{
|
||||||
@ -36,11 +35,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
CornerRadius = CORNER_RADIUS;
|
CornerRadius = CORNER_RADIUS;
|
||||||
Masking = true;
|
Masking = true;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
shadow = new Box
|
new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Colour4.Black)
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
@ -65,17 +65,21 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = CORNER_RADIUS * 2,
|
Height = CORNER_RADIUS * 2,
|
||||||
},
|
},
|
||||||
new SpriteIcon
|
CreateIcon(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual Drawable CreateIcon() => new SpriteIcon
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Y = 4,
|
Y = 4,
|
||||||
|
// TODO: replace with a non-squashed version.
|
||||||
|
// The 0.7f height scale should be removed.
|
||||||
Icon = FontAwesome.Solid.AngleDown,
|
Icon = FontAwesome.Solid.AngleDown,
|
||||||
Size = new Vector2(20),
|
Size = new Vector2(20),
|
||||||
Scale = new Vector2(1, 0.7f)
|
Scale = new Vector2(1, 0.7f)
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
|
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
|
||||||
@ -105,8 +109,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
accent.NewValue.Lighten(0.1f),
|
accent.NewValue.Lighten(0.1f),
|
||||||
accent.NewValue
|
accent.NewValue
|
||||||
);
|
);
|
||||||
|
|
||||||
shadow.Colour = accent.NewValue.Darken(0.5f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
return new ArgonHoldNoteTailPiece();
|
return new ArgonHoldNoteTailPiece();
|
||||||
|
|
||||||
case ManiaSkinComponents.HoldNoteHead:
|
case ManiaSkinComponents.HoldNoteHead:
|
||||||
|
return new ArgonHoldNoteHeadPiece();
|
||||||
|
|
||||||
case ManiaSkinComponents.Note:
|
case ManiaSkinComponents.Note:
|
||||||
return new ArgonNotePiece();
|
return new ArgonNotePiece();
|
||||||
|
|
||||||
@ -69,12 +71,23 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
return base.GetDrawableComponent(lookup);
|
return base.GetDrawableComponent(lookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly Color4 colour_special_column = new Color4(169, 106, 255, 255);
|
||||||
|
|
||||||
|
private const int total_colours = 6;
|
||||||
|
|
||||||
|
private static readonly Color4 colour_yellow = new Color4(255, 197, 40, 255);
|
||||||
|
private static readonly Color4 colour_orange = new Color4(252, 109, 1, 255);
|
||||||
|
private static readonly Color4 colour_pink = new Color4(213, 35, 90, 255);
|
||||||
|
private static readonly Color4 colour_purple = new Color4(203, 60, 236, 255);
|
||||||
|
private static readonly Color4 colour_cyan = new Color4(72, 198, 255, 255);
|
||||||
|
private static readonly Color4 colour_green = new Color4(100, 192, 92, 255);
|
||||||
|
|
||||||
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
{
|
{
|
||||||
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
|
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
|
||||||
{
|
{
|
||||||
int column = maniaLookup.ColumnIndex ?? 0;
|
int columnIndex = maniaLookup.ColumnIndex ?? 0;
|
||||||
var stage = beatmap.GetStageForColumnIndex(column);
|
var stage = beatmap.GetStageForColumnIndex(columnIndex);
|
||||||
|
|
||||||
switch (maniaLookup.Lookup)
|
switch (maniaLookup.Lookup)
|
||||||
{
|
{
|
||||||
@ -87,53 +100,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
|
|
||||||
case LegacyManiaSkinConfigurationLookups.ColumnWidth:
|
case LegacyManiaSkinConfigurationLookups.ColumnWidth:
|
||||||
return SkinUtils.As<TValue>(new Bindable<float>(
|
return SkinUtils.As<TValue>(new Bindable<float>(
|
||||||
stage.IsSpecialColumn(column) ? 120 : 60
|
stage.IsSpecialColumn(columnIndex) ? 120 : 60
|
||||||
));
|
));
|
||||||
|
|
||||||
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
|
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
|
||||||
|
|
||||||
Color4 colour;
|
var colour = getColourForLayout(columnIndex, stage);
|
||||||
|
|
||||||
const int total_colours = 7;
|
|
||||||
|
|
||||||
if (stage.IsSpecialColumn(column))
|
|
||||||
colour = new Color4(159, 101, 255, 255);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
switch (column % total_colours)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
colour = new Color4(240, 216, 0, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
colour = new Color4(240, 101, 0, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
colour = new Color4(240, 0, 130, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
colour = new Color4(192, 0, 240, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 4:
|
|
||||||
colour = new Color4(0, 96, 240, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 5:
|
|
||||||
colour = new Color4(0, 226, 240, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 6:
|
|
||||||
colour = new Color4(0, 240, 96, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SkinUtils.As<TValue>(new Bindable<Color4>(colour));
|
return SkinUtils.As<TValue>(new Bindable<Color4>(colour));
|
||||||
}
|
}
|
||||||
@ -141,5 +113,203 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
|
|
||||||
return base.GetConfig<TLookup, TValue>(lookup);
|
return base.GetConfig<TLookup, TValue>(lookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Color4 getColourForLayout(int columnIndex, StageDefinition stage)
|
||||||
|
{
|
||||||
|
// Account for cases like dual-stage (assume that all stages have the same column count for now).
|
||||||
|
columnIndex %= stage.Columns;
|
||||||
|
|
||||||
|
// For now, these are defined per column count as per https://user-images.githubusercontent.com/50823728/218038463-b450f46c-ef21-4551-b133-f866be59970c.png
|
||||||
|
// See https://github.com/ppy/osu/discussions/21996 for discussion.
|
||||||
|
switch (stage.Columns)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
return colour_yellow;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_green;
|
||||||
|
|
||||||
|
case 1: return colour_cyan;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_pink;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_yellow;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_yellow;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_pink;
|
||||||
|
|
||||||
|
case 3: return colour_purple;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_pink;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_yellow;
|
||||||
|
|
||||||
|
case 3: return colour_green;
|
||||||
|
|
||||||
|
case 4: return colour_cyan;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_pink;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_yellow;
|
||||||
|
|
||||||
|
case 3: return colour_cyan;
|
||||||
|
|
||||||
|
case 4: return colour_purple;
|
||||||
|
|
||||||
|
case 5: return colour_pink;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 7:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_pink;
|
||||||
|
|
||||||
|
case 1: return colour_cyan;
|
||||||
|
|
||||||
|
case 2: return colour_pink;
|
||||||
|
|
||||||
|
case 3: return colour_special_column;
|
||||||
|
|
||||||
|
case 4: return colour_green;
|
||||||
|
|
||||||
|
case 5: return colour_cyan;
|
||||||
|
|
||||||
|
case 6: return colour_green;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_purple;
|
||||||
|
|
||||||
|
case 1: return colour_pink;
|
||||||
|
|
||||||
|
case 2: return colour_orange;
|
||||||
|
|
||||||
|
case 3: return colour_yellow;
|
||||||
|
|
||||||
|
case 4: return colour_yellow;
|
||||||
|
|
||||||
|
case 5: return colour_orange;
|
||||||
|
|
||||||
|
case 6: return colour_pink;
|
||||||
|
|
||||||
|
case 7: return colour_purple;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 9:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_purple;
|
||||||
|
|
||||||
|
case 1: return colour_pink;
|
||||||
|
|
||||||
|
case 2: return colour_orange;
|
||||||
|
|
||||||
|
case 3: return colour_yellow;
|
||||||
|
|
||||||
|
case 4: return colour_special_column;
|
||||||
|
|
||||||
|
case 5: return colour_yellow;
|
||||||
|
|
||||||
|
case 6: return colour_orange;
|
||||||
|
|
||||||
|
case 7: return colour_pink;
|
||||||
|
|
||||||
|
case 8: return colour_purple;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 10:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_purple;
|
||||||
|
|
||||||
|
case 1: return colour_pink;
|
||||||
|
|
||||||
|
case 2: return colour_orange;
|
||||||
|
|
||||||
|
case 3: return colour_yellow;
|
||||||
|
|
||||||
|
case 4: return colour_cyan;
|
||||||
|
|
||||||
|
case 5: return colour_green;
|
||||||
|
|
||||||
|
case 6: return colour_yellow;
|
||||||
|
|
||||||
|
case 7: return colour_orange;
|
||||||
|
|
||||||
|
case 8: return colour_pink;
|
||||||
|
|
||||||
|
case 9: return colour_purple;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback for unhandled scenarios
|
||||||
|
|
||||||
|
if (stage.IsSpecialColumn(columnIndex))
|
||||||
|
return colour_special_column;
|
||||||
|
|
||||||
|
switch (columnIndex % total_colours)
|
||||||
|
{
|
||||||
|
case 0: return colour_yellow;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_pink;
|
||||||
|
|
||||||
|
case 3: return colour_purple;
|
||||||
|
|
||||||
|
case 4: return colour_cyan;
|
||||||
|
|
||||||
|
case 5: return colour_green;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +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.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Foundation;
|
|
||||||
using osu.Framework.iOS;
|
|
||||||
using osu.Game.Tests;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests.iOS
|
|
||||||
{
|
|
||||||
[Register("AppDelegate")]
|
|
||||||
public class AppDelegate : GameAppDelegate
|
|
||||||
{
|
|
||||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using osu.Framework.iOS;
|
||||||
|
using osu.Game.Tests;
|
||||||
using UIKit;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests.iOS
|
namespace osu.Game.Rulesets.Osu.Tests.iOS
|
||||||
{
|
{
|
||||||
@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Tests.iOS
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
GameApplication.Main(new OsuTestBrowser());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ using osu.Game.Configuration;
|
|||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -34,9 +34,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; } = null!;
|
private OsuConfigManager config { get; set; } = null!;
|
||||||
|
|
||||||
private TestActionKeyCounter leftKeyCounter = null!;
|
private DefaultKeyCounter leftKeyCounter = null!;
|
||||||
|
|
||||||
private TestActionKeyCounter rightKeyCounter = null!;
|
private DefaultKeyCounter rightKeyCounter = null!;
|
||||||
|
|
||||||
private OsuInputManager osuInputManager = null!;
|
private OsuInputManager osuInputManager = null!;
|
||||||
|
|
||||||
@ -59,14 +59,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
leftKeyCounter = new TestActionKeyCounter(OsuAction.LeftButton)
|
leftKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.LeftButton))
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
Depth = float.MinValue,
|
Depth = float.MinValue,
|
||||||
X = -100,
|
X = -100,
|
||||||
},
|
},
|
||||||
rightKeyCounter = new TestActionKeyCounter(OsuAction.RightButton)
|
rightKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.RightButton))
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
@ -150,6 +150,42 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
assertKeyCounter(1, 1);
|
assertKeyCounter(1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPositionalTrackingAfterLongDistanceTravelled()
|
||||||
|
{
|
||||||
|
// When a single touch has already travelled enough distance on screen, it should remain as the positional
|
||||||
|
// tracking touch until released (unless a direct touch occurs).
|
||||||
|
|
||||||
|
beginTouch(TouchSource.Touch1);
|
||||||
|
|
||||||
|
assertKeyCounter(1, 0);
|
||||||
|
checkPressed(OsuAction.LeftButton);
|
||||||
|
checkPosition(TouchSource.Touch1);
|
||||||
|
|
||||||
|
// cover some distance
|
||||||
|
beginTouch(TouchSource.Touch1, new Vector2(0));
|
||||||
|
beginTouch(TouchSource.Touch1, new Vector2(9999));
|
||||||
|
beginTouch(TouchSource.Touch1, new Vector2(0));
|
||||||
|
beginTouch(TouchSource.Touch1, new Vector2(9999));
|
||||||
|
beginTouch(TouchSource.Touch1);
|
||||||
|
|
||||||
|
beginTouch(TouchSource.Touch2);
|
||||||
|
|
||||||
|
assertKeyCounter(1, 1);
|
||||||
|
checkNotPressed(OsuAction.LeftButton);
|
||||||
|
checkPressed(OsuAction.RightButton);
|
||||||
|
// in this case, touch 2 should not become the positional tracking touch.
|
||||||
|
checkPosition(TouchSource.Touch1);
|
||||||
|
|
||||||
|
// even if the second touch moves on the screen, the original tracking touch is retained.
|
||||||
|
beginTouch(TouchSource.Touch2, new Vector2(0));
|
||||||
|
beginTouch(TouchSource.Touch2, new Vector2(9999));
|
||||||
|
beginTouch(TouchSource.Touch2, new Vector2(0));
|
||||||
|
beginTouch(TouchSource.Touch2, new Vector2(9999));
|
||||||
|
|
||||||
|
checkPosition(TouchSource.Touch1);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPositionalInputUpdatesOnlyFromMostRecentTouch()
|
public void TestPositionalInputUpdatesOnlyFromMostRecentTouch()
|
||||||
{
|
{
|
||||||
@ -562,8 +598,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
private void assertKeyCounter(int left, int right)
|
private void assertKeyCounter(int left, int right)
|
||||||
{
|
{
|
||||||
AddAssert($"The left key was pressed {left} times", () => leftKeyCounter.CountPresses, () => Is.EqualTo(left));
|
AddAssert($"The left key was pressed {left} times", () => leftKeyCounter.CountPresses.Value, () => Is.EqualTo(left));
|
||||||
AddAssert($"The right key was pressed {right} times", () => rightKeyCounter.CountPresses, () => Is.EqualTo(right));
|
AddAssert($"The right key was pressed {right} times", () => rightKeyCounter.CountPresses.Value, () => Is.EqualTo(right));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void releaseAllTouches()
|
private void releaseAllTouches()
|
||||||
@ -579,11 +615,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
private void checkNotPressed(OsuAction action) => AddAssert($"Not pressing {action}", () => !osuInputManager.PressedActions.Contains(action));
|
private void checkNotPressed(OsuAction action) => AddAssert($"Not pressing {action}", () => !osuInputManager.PressedActions.Contains(action));
|
||||||
private void checkPressed(OsuAction action) => AddAssert($"Is pressing {action}", () => osuInputManager.PressedActions.Contains(action));
|
private void checkPressed(OsuAction action) => AddAssert($"Is pressing {action}", () => osuInputManager.PressedActions.Contains(action));
|
||||||
|
|
||||||
public partial class TestActionKeyCounter : KeyCounter, IKeyBindingHandler<OsuAction>
|
public partial class TestActionKeyCounterTrigger : InputTrigger, IKeyBindingHandler<OsuAction>
|
||||||
{
|
{
|
||||||
public OsuAction Action { get; }
|
public OsuAction Action { get; }
|
||||||
|
|
||||||
public TestActionKeyCounter(OsuAction action)
|
public TestActionKeyCounterTrigger(OsuAction action)
|
||||||
: base(action.ToString())
|
: base(action.ToString())
|
||||||
{
|
{
|
||||||
Action = action;
|
Action = action;
|
||||||
@ -593,8 +629,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
if (e.Action == Action)
|
if (e.Action == Action)
|
||||||
{
|
{
|
||||||
IsLit = true;
|
Activate();
|
||||||
Increment();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -602,7 +637,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||||
{
|
{
|
||||||
if (e.Action == Action) IsLit = false;
|
if (e.Action == Action)
|
||||||
|
Deactivate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
|||||||
protected override bool AlwaysShowWhenSelected => true;
|
protected override bool AlwaysShowWhenSelected => true;
|
||||||
|
|
||||||
protected override bool ShouldBeAlive => base.ShouldBeAlive
|
protected override bool ShouldBeAlive => base.ShouldBeAlive
|
||||||
|| (ShowHitMarkers.Value && editorClock.CurrentTime >= Item.StartTime && editorClock.CurrentTime - Item.GetEndTime() < HitCircleOverlapMarker.FADE_OUT_EXTENSION);
|
|| (DrawableObject is not DrawableSpinner && ShowHitMarkers.Value && editorClock.CurrentTime >= Item.StartTime && editorClock.CurrentTime - Item.GetEndTime() < HitCircleOverlapMarker.FADE_OUT_EXTENSION);
|
||||||
|
|
||||||
protected OsuSelectionBlueprint(T hitObject)
|
protected OsuSelectionBlueprint(T hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
|
@ -252,13 +252,14 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
renderer.SetBlend(BlendingParameters.Additive);
|
renderer.SetBlend(BlendingParameters.Additive);
|
||||||
renderer.PushLocalMatrix(DrawInfo.Matrix);
|
renderer.PushLocalMatrix(DrawInfo.Matrix);
|
||||||
|
|
||||||
TextureShader.Bind();
|
BindTextureShader(renderer);
|
||||||
|
|
||||||
texture.Bind();
|
texture.Bind();
|
||||||
|
|
||||||
for (int i = 0; i < points.Count; i++)
|
for (int i = 0; i < points.Count; i++)
|
||||||
drawPointQuad(points[i], textureRect, i + firstVisiblePointIndex);
|
drawPointQuad(points[i], textureRect, i + firstVisiblePointIndex);
|
||||||
|
|
||||||
TextureShader.Unbind();
|
UnbindTextureShader(renderer);
|
||||||
renderer.PopLocalMatrix();
|
renderer.PopLocalMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics.Primitives;
|
|||||||
using osu.Framework.Graphics.Rendering;
|
using osu.Framework.Graphics.Rendering;
|
||||||
using osu.Framework.Graphics.Rendering.Vertices;
|
using osu.Framework.Graphics.Rendering.Vertices;
|
||||||
using osu.Framework.Graphics.Shaders;
|
using osu.Framework.Graphics.Shaders;
|
||||||
|
using osu.Framework.Graphics.Shaders.Types;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
@ -255,15 +256,23 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
Source.parts.CopyTo(parts, 0);
|
Source.parts.CopyTo(parts, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IUniformBuffer<CursorTrailParameters> cursorTrailParameters;
|
||||||
|
|
||||||
public override void Draw(IRenderer renderer)
|
public override void Draw(IRenderer renderer)
|
||||||
{
|
{
|
||||||
base.Draw(renderer);
|
base.Draw(renderer);
|
||||||
|
|
||||||
vertexBatch ??= renderer.CreateQuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
vertexBatch ??= renderer.CreateQuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
||||||
|
|
||||||
|
cursorTrailParameters ??= renderer.CreateUniformBuffer<CursorTrailParameters>();
|
||||||
|
cursorTrailParameters.Data = cursorTrailParameters.Data with
|
||||||
|
{
|
||||||
|
FadeClock = time,
|
||||||
|
FadeExponent = fadeExponent
|
||||||
|
};
|
||||||
|
|
||||||
shader.Bind();
|
shader.Bind();
|
||||||
shader.GetUniform<float>("g_FadeClock").UpdateValue(ref time);
|
shader.BindUniformBlock("m_CursorTrailParameters", cursorTrailParameters);
|
||||||
shader.GetUniform<float>("g_FadeExponent").UpdateValue(ref fadeExponent);
|
|
||||||
|
|
||||||
texture.Bind();
|
texture.Bind();
|
||||||
|
|
||||||
@ -323,6 +332,15 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
vertexBatch?.Dispose();
|
vertexBatch?.Dispose();
|
||||||
|
cursorTrailParameters?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
private record struct CursorTrailParameters
|
||||||
|
{
|
||||||
|
public UniformFloat FadeClock;
|
||||||
|
public UniformFloat FadeExponent;
|
||||||
|
private readonly UniformPadding8 pad1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,13 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly List<TrackedTouch> trackedTouches = new List<TrackedTouch>();
|
private readonly List<TrackedTouch> trackedTouches = new List<TrackedTouch>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The distance (in local pixels) that a touch must move before being considered a permanent tracking touch.
|
||||||
|
/// After this distance is covered, any extra touches on the screen will be considered as button inputs, unless
|
||||||
|
/// a new touch directly interacts with a hit circle.
|
||||||
|
/// </summary>
|
||||||
|
private const float distance_before_position_tracking_lock_in = 100;
|
||||||
|
|
||||||
private TrackedTouch? positionTrackingTouch;
|
private TrackedTouch? positionTrackingTouch;
|
||||||
|
|
||||||
private readonly OsuInputManager osuInputManager;
|
private readonly OsuInputManager osuInputManager;
|
||||||
@ -97,26 +104,32 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ..or if the current position tracking touch was not a direct touch (this one is debatable and may be change in the future, but it's the simplest way to handle)
|
// ..or if the current position tracking touch was not a direct touch (and didn't travel across the screen too far).
|
||||||
if (!positionTrackingTouch.DirectTouch)
|
if (!positionTrackingTouch.DirectTouch && positionTrackingTouch.DistanceTravelled < distance_before_position_tracking_lock_in)
|
||||||
{
|
{
|
||||||
positionTrackingTouch = newTouch;
|
positionTrackingTouch = newTouch;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the case the new touch was not used for position tracking, we should also check the previous position tracking touch.
|
// In the case the new touch was not used for position tracking, we should also check the previous position tracking touch.
|
||||||
// If it was a direct touch and still has its action pressed, that action should be released.
|
// If it still has its action pressed, that action should be released.
|
||||||
//
|
//
|
||||||
// This is done to allow tracking with the initial touch while still having both Left/Right actions available for alternating with two more touches.
|
// This is done to allow tracking with the initial touch while still having both Left/Right actions available for alternating with two more touches.
|
||||||
if (positionTrackingTouch.DirectTouch && positionTrackingTouch.Action is OsuAction directTouchAction)
|
if (positionTrackingTouch.Action is OsuAction touchAction)
|
||||||
{
|
{
|
||||||
osuInputManager.KeyBindingContainer.TriggerReleased(directTouchAction);
|
osuInputManager.KeyBindingContainer.TriggerReleased(touchAction);
|
||||||
positionTrackingTouch.Action = null;
|
positionTrackingTouch.Action = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleTouchMovement(TouchEvent touchEvent)
|
private void handleTouchMovement(TouchEvent touchEvent)
|
||||||
{
|
{
|
||||||
|
if (touchEvent is TouchMoveEvent moveEvent)
|
||||||
|
{
|
||||||
|
var trackedTouch = trackedTouches.Single(t => t.Source == touchEvent.Touch.Source);
|
||||||
|
trackedTouch.DistanceTravelled += moveEvent.Delta.Length;
|
||||||
|
}
|
||||||
|
|
||||||
// Movement should only be tracked for the most recent touch.
|
// Movement should only be tracked for the most recent touch.
|
||||||
if (touchEvent.Touch.Source != positionTrackingTouch?.Source)
|
if (touchEvent.Touch.Source != positionTrackingTouch?.Source)
|
||||||
return;
|
return;
|
||||||
@ -148,8 +161,16 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
public OsuAction? Action;
|
public OsuAction? Action;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the touch was on a hit circle receptor.
|
||||||
|
/// </summary>
|
||||||
public readonly bool DirectTouch;
|
public readonly bool DirectTouch;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total distance on screen travelled by this touch (in local pixels).
|
||||||
|
/// </summary>
|
||||||
|
public float DistanceTravelled;
|
||||||
|
|
||||||
public TrackedTouch(TouchSource source, OsuAction? action, bool directTouch)
|
public TrackedTouch(TouchSource source, OsuAction? action, bool directTouch)
|
||||||
{
|
{
|
||||||
Source = source;
|
Source = source;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Description>click the circles. to the beat.</Description>
|
<Description>click the circles. to the beat.</Description>
|
||||||
|
<LangVersion>10</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Label="Nuget">
|
<PropertyGroup Label="Nuget">
|
||||||
|
@ -1,17 +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.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Foundation;
|
|
||||||
using osu.Framework.iOS;
|
|
||||||
using osu.Game.Tests;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests.iOS
|
|
||||||
{
|
|
||||||
[Register("AppDelegate")]
|
|
||||||
public class AppDelegate : GameAppDelegate
|
|
||||||
{
|
|
||||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using osu.Framework.iOS;
|
||||||
|
using osu.Game.Tests;
|
||||||
using UIKit;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests.iOS
|
namespace osu.Game.Rulesets.Taiko.Tests.iOS
|
||||||
{
|
{
|
||||||
@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.iOS
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
GameApplication.Main(new OsuTestBrowser());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
@ -35,20 +33,11 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
|||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
{
|
{
|
||||||
switch (e.Button)
|
if (e.Button != MouseButton.Left)
|
||||||
{
|
|
||||||
case MouseButton.Left:
|
|
||||||
HitObject.Type = HitType.Centre;
|
|
||||||
EndPlacement(true);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MouseButton.Right:
|
|
||||||
HitObject.Type = HitType.Rim;
|
|
||||||
EndPlacement(true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
EndPlacement(true);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
public override void UpdateTimeAndPosition(SnapResult result)
|
||||||
|
@ -1,16 +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.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Foundation;
|
|
||||||
using osu.Framework.iOS;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.iOS
|
|
||||||
{
|
|
||||||
[Register("AppDelegate")]
|
|
||||||
public class AppDelegate : GameAppDelegate
|
|
||||||
{
|
|
||||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using osu.Framework.iOS;
|
||||||
|
|
||||||
using UIKit;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.iOS
|
namespace osu.Game.Tests.iOS
|
||||||
{
|
{
|
||||||
@ -11,7 +9,7 @@ namespace osu.Game.Tests.iOS
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
GameApplication.Main(new OsuTestBrowser());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,6 +176,7 @@ namespace osu.Game.Tests.Resources
|
|||||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||||
},
|
},
|
||||||
BeatmapInfo = beatmap,
|
BeatmapInfo = beatmap,
|
||||||
|
BeatmapHash = beatmap.Hash,
|
||||||
Ruleset = beatmap.Ruleset,
|
Ruleset = beatmap.Ruleset,
|
||||||
Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() },
|
Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() },
|
||||||
TotalScore = 2845370,
|
TotalScore = 2845370,
|
||||||
|
@ -80,7 +80,7 @@ namespace osu.Game.Tests.Rulesets
|
|||||||
|
|
||||||
dependencies.CacheAs<TextureStore>(ParentTextureStore = new TestTextureStore(parent.Get<GameHost>().Renderer));
|
dependencies.CacheAs<TextureStore>(ParentTextureStore = new TestTextureStore(parent.Get<GameHost>().Renderer));
|
||||||
dependencies.CacheAs<ISampleStore>(ParentSampleStore = new TestSampleStore());
|
dependencies.CacheAs<ISampleStore>(ParentSampleStore = new TestSampleStore());
|
||||||
dependencies.CacheAs<ShaderManager>(ParentShaderManager = new TestShaderManager(parent.Get<GameHost>().Renderer));
|
dependencies.CacheAs<ShaderManager>(ParentShaderManager = new TestShaderManager(parent.Get<GameHost>().Renderer, parent.Get<ShaderManager>()));
|
||||||
|
|
||||||
return new DrawableRulesetDependencies(new OsuRuleset(), dependencies);
|
return new DrawableRulesetDependencies(new OsuRuleset(), dependencies);
|
||||||
}
|
}
|
||||||
@ -156,12 +156,15 @@ namespace osu.Game.Tests.Rulesets
|
|||||||
|
|
||||||
private class TestShaderManager : ShaderManager
|
private class TestShaderManager : ShaderManager
|
||||||
{
|
{
|
||||||
public TestShaderManager(IRenderer renderer)
|
private readonly ShaderManager parentManager;
|
||||||
|
|
||||||
|
public TestShaderManager(IRenderer renderer, ShaderManager parentManager)
|
||||||
: base(renderer, new ResourceStore<byte[]>())
|
: base(renderer, new ResourceStore<byte[]>())
|
||||||
{
|
{
|
||||||
|
this.parentManager = parentManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override byte[] LoadRaw(string name) => null;
|
public override byte[] LoadRaw(string name) => parentManager.LoadRaw(name);
|
||||||
|
|
||||||
public bool IsDisposed { get; private set; }
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
|
@ -133,6 +133,25 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
assertImportedOnce(import1, import2);
|
assertImportedOnce(import1, import2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public Task TestImportExportedNonAsciiSkinFilename() => runSkinTest(async osu =>
|
||||||
|
{
|
||||||
|
MemoryStream exportStream = new MemoryStream();
|
||||||
|
|
||||||
|
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 『1』", "author 1"), "custom.osk"));
|
||||||
|
assertCorrectMetadata(import1, "name 『1』 [custom]", "author 1", osu);
|
||||||
|
|
||||||
|
import1.PerformRead(s =>
|
||||||
|
{
|
||||||
|
new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportModelTo(s, exportStream);
|
||||||
|
});
|
||||||
|
|
||||||
|
string exportFilename = import1.GetDisplayString().GetValidFilename();
|
||||||
|
|
||||||
|
var import2 = await loadSkinIntoOsu(osu, new ImportTask(exportStream, $"{exportFilename}.osk"));
|
||||||
|
assertCorrectMetadata(import2, "name 『1』 [custom]", "author 1", osu);
|
||||||
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public Task TestSameMetadataNameSameFolderName([Values] bool batchImport) => runSkinTest(async osu =>
|
public Task TestSameMetadataNameSameFolderName([Values] bool batchImport) => runSkinTest(async osu =>
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -51,9 +49,11 @@ namespace osu.Game.Tests.Testing
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestRetrieveShader()
|
public void TestRetrieveShader()
|
||||||
{
|
{
|
||||||
AddAssert("ruleset shaders retrieved", () =>
|
AddStep("ruleset shaders retrieved without error", () =>
|
||||||
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestVertex.vs") != null &&
|
{
|
||||||
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestFragment.fs") != null);
|
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestVertex.vs");
|
||||||
|
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestFragment.fs");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -76,12 +76,12 @@ namespace osu.Game.Tests.Testing
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override IResourceStore<byte[]> CreateResourceStore() => new NamespacedResourceStore<byte[]>(TestResources.GetStore(), @"Resources");
|
public override IResourceStore<byte[]> CreateResourceStore() => new NamespacedResourceStore<byte[]>(TestResources.GetStore(), @"Resources");
|
||||||
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new TestRulesetConfigManager();
|
public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new TestRulesetConfigManager();
|
||||||
|
|
||||||
public override IEnumerable<Mod> GetModsFor(ModType type) => Array.Empty<Mod>();
|
public override IEnumerable<Mod> GetModsFor(ModType type) => Array.Empty<Mod>();
|
||||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => null;
|
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod>? mods = null) => null!;
|
||||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null;
|
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null!;
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null;
|
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestRulesetConfigManager : IRulesetConfigManager
|
private class TestRulesetConfigManager : IRulesetConfigManager
|
||||||
|
@ -9,6 +9,7 @@ using osuTK;
|
|||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Rendering;
|
using osu.Framework.Graphics.Rendering;
|
||||||
|
using osu.Game.Graphics.Backgrounds;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Background
|
namespace osu.Game.Tests.Visual.Background
|
||||||
{
|
{
|
||||||
@ -97,15 +98,29 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
texelSize = Source.texelSize;
|
texelSize = Source.texelSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Draw(IRenderer renderer)
|
private IUniformBuffer<TriangleBorderData>? borderDataBuffer;
|
||||||
{
|
|
||||||
TextureShader.GetUniform<float>("thickness").UpdateValue(ref thickness);
|
|
||||||
TextureShader.GetUniform<float>("texelSize").UpdateValue(ref texelSize);
|
|
||||||
|
|
||||||
base.Draw(renderer);
|
protected override void BindUniformResources(IShader shader, IRenderer renderer)
|
||||||
|
{
|
||||||
|
base.BindUniformResources(shader, renderer);
|
||||||
|
|
||||||
|
borderDataBuffer ??= renderer.CreateUniformBuffer<TriangleBorderData>();
|
||||||
|
borderDataBuffer.Data = borderDataBuffer.Data with
|
||||||
|
{
|
||||||
|
Thickness = thickness,
|
||||||
|
TexelSize = texelSize
|
||||||
|
};
|
||||||
|
|
||||||
|
shader.BindUniformBlock("m_BorderData", borderDataBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CanDrawOpaqueInterior => false;
|
protected override bool CanDrawOpaqueInterior => false;
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
borderDataBuffer?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,14 +35,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
|
var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
|
||||||
|
|
||||||
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||||
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2));
|
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Counters.Any(kc => kc.CountPresses.Value > 2));
|
||||||
|
|
||||||
seekTo(referenceBeatmap.Breaks[0].StartTime);
|
seekTo(referenceBeatmap.Breaks[0].StartTime);
|
||||||
AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting);
|
AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting.Value);
|
||||||
AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType<BreakInfo>().Single().AccuracyDisplay.Current.Value == 1);
|
AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType<BreakInfo>().Single().AccuracyDisplay.Current.Value == 1);
|
||||||
|
|
||||||
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
|
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
|
||||||
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
|
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Counters.All(kc => kc.CountPresses.Value == 0));
|
||||||
|
|
||||||
seekTo(referenceBeatmap.HitObjects[^1].GetEndTime());
|
seekTo(referenceBeatmap.HitObjects[^1].GetEndTime());
|
||||||
AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true);
|
AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true);
|
||||||
|
@ -31,11 +31,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||||
addSeekStep(3000);
|
addSeekStep(3000);
|
||||||
AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
|
AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
|
||||||
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Select(kc => kc.CountPresses).Sum() == 15);
|
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Counters.Select(kc => kc.CountPresses.Value).Sum() == 15);
|
||||||
AddStep("clear results", () => Player.Results.Clear());
|
AddStep("clear results", () => Player.Results.Clear());
|
||||||
addSeekStep(0);
|
addSeekStep(0);
|
||||||
AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
|
AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
|
||||||
AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
|
AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Counters.All(kc => kc.CountPresses.Value == 0));
|
||||||
AddAssert("no results triggered", () => Player.Results.Count == 0);
|
AddAssert("no results triggered", () => Player.Results.Count == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
// best way to check without exposing.
|
// best way to check without exposing.
|
||||||
private Drawable hideTarget => hudOverlay.KeyCounter;
|
private Drawable hideTarget => hudOverlay.KeyCounter;
|
||||||
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
|
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<DefaultKeyCounter>>().Single();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
@ -267,7 +267,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
||||||
|
|
||||||
// Add any key just to display the key counter visually.
|
// Add any key just to display the key counter visually.
|
||||||
hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
|
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||||
|
|
||||||
scoreProcessor.Combo.Value = 1;
|
scoreProcessor.Combo.Value = 1;
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
@ -17,28 +17,29 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
public TestSceneKeyCounter()
|
public TestSceneKeyCounter()
|
||||||
{
|
{
|
||||||
KeyCounterKeyboard testCounter;
|
KeyCounterDisplay kc = new DefaultKeyCounterDisplay
|
||||||
|
|
||||||
KeyCounterDisplay kc = new KeyCounterDisplay
|
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Children = new KeyCounter[]
|
|
||||||
{
|
|
||||||
testCounter = new KeyCounterKeyboard(Key.X),
|
|
||||||
new KeyCounterKeyboard(Key.X),
|
|
||||||
new KeyCounterMouse(MouseButton.Left),
|
|
||||||
new KeyCounterMouse(MouseButton.Right),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
kc.AddRange(new InputTrigger[]
|
||||||
|
{
|
||||||
|
new KeyCounterKeyboardTrigger(Key.X),
|
||||||
|
new KeyCounterKeyboardTrigger(Key.X),
|
||||||
|
new KeyCounterMouseTrigger(MouseButton.Left),
|
||||||
|
new KeyCounterMouseTrigger(MouseButton.Right),
|
||||||
|
});
|
||||||
|
|
||||||
|
var testCounter = (DefaultKeyCounter)kc.Counters.First();
|
||||||
|
|
||||||
AddStep("Add random", () =>
|
AddStep("Add random", () =>
|
||||||
{
|
{
|
||||||
Key key = (Key)((int)Key.A + RNG.Next(26));
|
Key key = (Key)((int)Key.A + RNG.Next(26));
|
||||||
kc.Add(new KeyCounterKeyboard(key));
|
kc.Add(new KeyCounterKeyboardTrigger(key));
|
||||||
});
|
});
|
||||||
|
|
||||||
Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key;
|
Key testKey = ((KeyCounterKeyboardTrigger)kc.Counters.First().Trigger).Key;
|
||||||
|
|
||||||
void addPressKeyStep()
|
void addPressKeyStep()
|
||||||
{
|
{
|
||||||
@ -46,12 +47,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
|
|
||||||
addPressKeyStep();
|
addPressKeyStep();
|
||||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 1);
|
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 1);
|
||||||
addPressKeyStep();
|
addPressKeyStep();
|
||||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 2);
|
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 2);
|
||||||
AddStep("Disable counting", () => testCounter.IsCounting = false);
|
AddStep("Disable counting", () => testCounter.IsCounting.Value = false);
|
||||||
addPressKeyStep();
|
addPressKeyStep();
|
||||||
AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses == 2);
|
AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses.Value == 2);
|
||||||
|
|
||||||
Add(kc);
|
Add(kc);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
protected override void AddCheckSteps()
|
protected override void AddCheckSteps()
|
||||||
{
|
{
|
||||||
AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0);
|
AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0);
|
||||||
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0));
|
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Counters.Any(kc => kc.CountPresses.Value > 0));
|
||||||
AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail);
|
AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -45,6 +46,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("Time still stopped", () => lastTime == Player.GameplayClockContainer.CurrentTime);
|
AddAssert("Time still stopped", () => lastTime == Player.GameplayClockContainer.CurrentTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDoesNotFailOnExit()
|
||||||
|
{
|
||||||
|
loadPlayerWithBeatmap();
|
||||||
|
|
||||||
|
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||||
|
AddAssert("ensure rank is not fail", () => Player.ScoreProcessor.Rank.Value, () => Is.Not.EqualTo(ScoreRank.F));
|
||||||
|
AddStep("exit player", () => Player.Exit());
|
||||||
|
AddUntilStep("wait for exit", () => Player.Parent == null);
|
||||||
|
AddAssert("ensure rank is not fail", () => Player.ScoreProcessor.Rank.Value, () => Is.Not.EqualTo(ScoreRank.F));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPauseViaSpaceWithSkip()
|
public void TestPauseViaSpaceWithSkip()
|
||||||
{
|
{
|
||||||
|
@ -17,6 +17,8 @@ using osu.Game.Rulesets.Osu;
|
|||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Skinning.Components;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
@ -52,6 +54,134 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("wait for loaded", () => skinEditor.IsLoaded);
|
AddUntilStep("wait for loaded", () => skinEditor.IsLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDragSelection()
|
||||||
|
{
|
||||||
|
BigBlackBox box1 = null!;
|
||||||
|
BigBlackBox box2 = null!;
|
||||||
|
BigBlackBox box3 = null!;
|
||||||
|
|
||||||
|
AddStep("Add big black boxes", () =>
|
||||||
|
{
|
||||||
|
var target = Player.ChildrenOfType<SkinComponentsContainer>().First();
|
||||||
|
target.Add(box1 = new BigBlackBox
|
||||||
|
{
|
||||||
|
Position = new Vector2(-90),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
});
|
||||||
|
target.Add(box2 = new BigBlackBox
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
});
|
||||||
|
target.Add(box3 = new BigBlackBox
|
||||||
|
{
|
||||||
|
Position = new Vector2(90),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// This step is specifically added to reproduce an edge case which was found during cyclic selection development.
|
||||||
|
// If everything is working as expected it should not affect the subsequent drag selections.
|
||||||
|
AddRepeatStep("Select top left", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.TopLeft + new Vector2(box1.ScreenSpaceDrawQuad.Width / 8));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
}, 2);
|
||||||
|
|
||||||
|
AddStep("Begin drag top left", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.TopLeft - new Vector2(box1.ScreenSpaceDrawQuad.Width / 4));
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Drag to bottom right", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.TopRight + new Vector2(-box3.ScreenSpaceDrawQuad.Width / 8, box3.ScreenSpaceDrawQuad.Height / 4));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Release button", () =>
|
||||||
|
{
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("First two boxes selected", () => skinEditor.SelectedComponents, () => Is.EqualTo(new[] { box1, box2 }));
|
||||||
|
|
||||||
|
AddStep("Begin drag bottom right", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.BottomRight + new Vector2(box3.ScreenSpaceDrawQuad.Width / 4));
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Drag to top left", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(box2.ScreenSpaceDrawQuad.Centre - new Vector2(box2.ScreenSpaceDrawQuad.Width / 4));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Release button", () =>
|
||||||
|
{
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Last two boxes selected", () => skinEditor.SelectedComponents, () => Is.EqualTo(new[] { box2, box3 }));
|
||||||
|
|
||||||
|
// Test cyclic selection doesn't trigger in this state.
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("Last two boxes still selected", () => skinEditor.SelectedComponents, () => Is.EqualTo(new[] { box2, box3 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCyclicSelection()
|
||||||
|
{
|
||||||
|
SkinBlueprint[] blueprints = null!;
|
||||||
|
|
||||||
|
AddStep("Add big black boxes", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(skinEditor.ChildrenOfType<BigBlackBox>().First());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Three black boxes added", () => targetContainer.Components.OfType<BigBlackBox>().Count(), () => Is.EqualTo(3));
|
||||||
|
|
||||||
|
AddStep("Store black box blueprints", () =>
|
||||||
|
{
|
||||||
|
blueprints = skinEditor.ChildrenOfType<SkinBlueprint>().Where(b => b.Item is BigBlackBox).ToArray();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Selection is black box 1", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[0].Item));
|
||||||
|
|
||||||
|
AddStep("move cursor to black box", () =>
|
||||||
|
{
|
||||||
|
// Slightly offset from centre to avoid random failures (see https://github.com/ppy/osu-framework/issues/5669).
|
||||||
|
InputManager.MoveMouseTo(((Drawable)blueprints[0].Item).ScreenSpaceDrawQuad.Centre + new Vector2(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("Selection is black box 2", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[1].Item));
|
||||||
|
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("Selection is black box 3", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[2].Item));
|
||||||
|
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("Selection is black box 1", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[0].Item));
|
||||||
|
|
||||||
|
AddStep("select all boxes", () =>
|
||||||
|
{
|
||||||
|
skinEditor.SelectedComponents.Clear();
|
||||||
|
skinEditor.SelectedComponents.AddRange(targetContainer.Components.OfType<BigBlackBox>().Skip(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("all boxes selected", () => skinEditor.SelectedComponents, () => Has.Count.EqualTo(2));
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("all boxes still selected", () => skinEditor.SelectedComponents, () => Has.Count.EqualTo(2));
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
public void TestBringToFront(bool alterSelectionOrder)
|
public void TestBringToFront(bool alterSelectionOrder)
|
||||||
|
@ -14,6 +14,7 @@ using osu.Game.Rulesets.Osu;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Tests.Gameplay;
|
using osu.Game.Tests.Gameplay;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -57,7 +58,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Add any key just to display the key counter visually.
|
// Add any key just to display the key counter visually.
|
||||||
hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
|
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||||
scoreProcessor.Combo.Value = 1;
|
scoreProcessor.Combo.Value = 1;
|
||||||
|
|
||||||
return new Container
|
return new Container
|
||||||
|
@ -18,6 +18,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Tests.Gameplay;
|
using osu.Game.Tests.Gameplay;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
// best way to check without exposing.
|
// best way to check without exposing.
|
||||||
private Drawable hideTarget => hudOverlay.KeyCounter;
|
private Drawable hideTarget => hudOverlay.KeyCounter;
|
||||||
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
|
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<DefaultKeyCounter>>().Single();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestComboCounterIncrementing()
|
public void TestComboCounterIncrementing()
|
||||||
@ -88,7 +89,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
||||||
|
|
||||||
// Add any key just to display the key counter visually.
|
// Add any key just to display the key counter visually.
|
||||||
hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
|
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||||
|
|
||||||
action?.Invoke(hudOverlay);
|
action?.Invoke(hudOverlay);
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ using osu.Framework.Screens;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
@ -106,6 +107,26 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
|
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSaveFailedReplayWithStoryboardEndedDoesNotProgress()
|
||||||
|
{
|
||||||
|
CreateTest(() =>
|
||||||
|
{
|
||||||
|
AddStep("fail on first judgement", () => currentFailConditions = (_, _) => true);
|
||||||
|
AddStep("set storyboard duration to 0s", () => currentStoryboardDuration = 0);
|
||||||
|
});
|
||||||
|
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
|
||||||
|
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||||
|
|
||||||
|
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
|
||||||
|
AddUntilStep("wait for button clickable", () => Player.ChildrenOfType<SaveFailedScoreButton>().First().ChildrenOfType<OsuClickableContainer>().First().Enabled.Value);
|
||||||
|
AddStep("click save button", () => Player.ChildrenOfType<SaveFailedScoreButton>().First().ChildrenOfType<OsuClickableContainer>().First().TriggerClick());
|
||||||
|
|
||||||
|
// Test a regression where importing the fail replay would cause progression to results screen in a failed state.
|
||||||
|
AddWaitStep("wait some", 10);
|
||||||
|
AddAssert("player is still current screen", () => Player.IsCurrentScreen());
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestShowResultsFalse()
|
public void TestShowResultsFalse()
|
||||||
{
|
{
|
||||||
|
@ -114,6 +114,19 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(OverlayActivation.All)]
|
||||||
|
[TestCase(OverlayActivation.Disabled)]
|
||||||
|
public void TestButtonKeyboardInputRespectsOverlayActivation(OverlayActivation mode)
|
||||||
|
{
|
||||||
|
AddStep($"set activation mode to {mode}", () => toolbar.OverlayActivationMode.Value = mode);
|
||||||
|
AddStep("hide toolbar", () => toolbar.Hide());
|
||||||
|
|
||||||
|
if (mode == OverlayActivation.Disabled)
|
||||||
|
AddAssert("check buttons not accepting input", () => InputManager.NonPositionalInputQueue.OfType<ToolbarButton>().Count(), () => Is.Zero);
|
||||||
|
else
|
||||||
|
AddAssert("check buttons accepting input", () => InputManager.NonPositionalInputQueue.OfType<ToolbarButton>().Count(), () => Is.Not.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase(OverlayActivation.All)]
|
[TestCase(OverlayActivation.All)]
|
||||||
[TestCase(OverlayActivation.Disabled)]
|
[TestCase(OverlayActivation.Disabled)]
|
||||||
public void TestRespectsOverlayActivation(OverlayActivation mode)
|
public void TestRespectsOverlayActivation(OverlayActivation mode)
|
||||||
|
@ -14,6 +14,7 @@ using osu.Game.Graphics.UserInterface;
|
|||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Overlays.Settings.Sections.Input;
|
using osu.Game.Overlays.Settings.Sections.Input;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osu.Game.Tests.Beatmaps.IO;
|
using osu.Game.Tests.Beatmaps.IO;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -69,10 +70,10 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
AddUntilStep("wait for gameplay", () => player?.IsBreakTime.Value == false);
|
AddUntilStep("wait for gameplay", () => player?.IsBreakTime.Value == false);
|
||||||
|
|
||||||
AddStep("press 'z'", () => InputManager.Key(Key.Z));
|
AddStep("press 'z'", () => InputManager.Key(Key.Z));
|
||||||
AddAssert("key counter didn't increase", () => keyCounter.CountPresses == 0);
|
AddAssert("key counter didn't increase", () => keyCounter.CountPresses.Value == 0);
|
||||||
|
|
||||||
AddStep("press 's'", () => InputManager.Key(Key.S));
|
AddStep("press 's'", () => InputManager.Key(Key.S));
|
||||||
AddAssert("key counter did increase", () => keyCounter.CountPresses == 1);
|
AddAssert("key counter did increase", () => keyCounter.CountPresses.Value == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyBindingsSubsection osuBindingSubsection => keyBindingPanel
|
private KeyBindingsSubsection osuBindingSubsection => keyBindingPanel
|
||||||
|
@ -126,6 +126,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
Id = 13926,
|
Id = 13926,
|
||||||
TournamentId = 35,
|
TournamentId = 35,
|
||||||
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2022/profile/winner_US.jpg",
|
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2022/profile/winner_US.jpg",
|
||||||
|
Image = "https://assets.ppy.sh/tournament-banners/official/owc2022/profile/winner_US@2x.jpg",
|
||||||
},
|
},
|
||||||
Badges = new[]
|
Badges = new[]
|
||||||
{
|
{
|
||||||
|
@ -84,16 +84,80 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
});
|
});
|
||||||
|
|
||||||
clearScores();
|
clearScores();
|
||||||
checkCount(0);
|
checkDisplayedCount(0);
|
||||||
|
|
||||||
loadMoreScores(() => beatmapInfo);
|
importMoreScores(() => beatmapInfo);
|
||||||
checkCount(10);
|
checkDisplayedCount(10);
|
||||||
|
|
||||||
loadMoreScores(() => beatmapInfo);
|
importMoreScores(() => beatmapInfo);
|
||||||
checkCount(20);
|
checkDisplayedCount(20);
|
||||||
|
|
||||||
clearScores();
|
clearScores();
|
||||||
checkCount(0);
|
checkDisplayedCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLocalScoresDisplayOnBeatmapEdit()
|
||||||
|
{
|
||||||
|
BeatmapInfo beatmapInfo = null!;
|
||||||
|
string originalHash = string.Empty;
|
||||||
|
|
||||||
|
AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Local);
|
||||||
|
|
||||||
|
AddStep(@"Import beatmap", () =>
|
||||||
|
{
|
||||||
|
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||||
|
beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
|
||||||
|
|
||||||
|
leaderboard.BeatmapInfo = beatmapInfo;
|
||||||
|
});
|
||||||
|
|
||||||
|
clearScores();
|
||||||
|
checkDisplayedCount(0);
|
||||||
|
|
||||||
|
AddStep(@"Perform initial save to guarantee stable hash", () =>
|
||||||
|
{
|
||||||
|
IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap;
|
||||||
|
beatmapManager.Save(beatmapInfo, beatmap);
|
||||||
|
|
||||||
|
originalHash = beatmapInfo.Hash;
|
||||||
|
});
|
||||||
|
|
||||||
|
importMoreScores(() => beatmapInfo);
|
||||||
|
|
||||||
|
checkDisplayedCount(10);
|
||||||
|
checkStoredCount(10);
|
||||||
|
|
||||||
|
AddStep(@"Save with changes", () =>
|
||||||
|
{
|
||||||
|
IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap;
|
||||||
|
beatmap.Difficulty.ApproachRate = 12;
|
||||||
|
beatmapManager.Save(beatmapInfo, beatmap);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Hash changed", () => beatmapInfo.Hash, () => Is.Not.EqualTo(originalHash));
|
||||||
|
checkDisplayedCount(0);
|
||||||
|
checkStoredCount(10);
|
||||||
|
|
||||||
|
importMoreScores(() => beatmapInfo);
|
||||||
|
importMoreScores(() => beatmapInfo);
|
||||||
|
checkDisplayedCount(20);
|
||||||
|
checkStoredCount(30);
|
||||||
|
|
||||||
|
AddStep(@"Revert changes", () =>
|
||||||
|
{
|
||||||
|
IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap;
|
||||||
|
beatmap.Difficulty.ApproachRate = 8;
|
||||||
|
beatmapManager.Save(beatmapInfo, beatmap);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Hash restored", () => beatmapInfo.Hash, () => Is.EqualTo(originalHash));
|
||||||
|
checkDisplayedCount(10);
|
||||||
|
checkStoredCount(30);
|
||||||
|
|
||||||
|
clearScores();
|
||||||
|
checkDisplayedCount(0);
|
||||||
|
checkStoredCount(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -162,9 +226,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadMoreScores(Func<BeatmapInfo> beatmapInfo)
|
private void importMoreScores(Func<BeatmapInfo> beatmapInfo)
|
||||||
{
|
{
|
||||||
AddStep(@"Load new scores via manager", () =>
|
AddStep(@"Import new scores", () =>
|
||||||
{
|
{
|
||||||
foreach (var score in generateSampleScores(beatmapInfo()))
|
foreach (var score in generateSampleScores(beatmapInfo()))
|
||||||
scoreManager.Import(score);
|
scoreManager.Import(score);
|
||||||
@ -176,8 +240,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddStep("Clear all scores", () => scoreManager.Delete());
|
AddStep("Clear all scores", () => scoreManager.Delete());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkCount(int expected) =>
|
private void checkDisplayedCount(int expected) =>
|
||||||
AddUntilStep("Correct count displayed", () => leaderboard.ChildrenOfType<LeaderboardScore>().Count() == expected);
|
AddUntilStep($"{expected} scores displayed", () => leaderboard.ChildrenOfType<LeaderboardScore>().Count(), () => Is.EqualTo(expected));
|
||||||
|
|
||||||
|
private void checkStoredCount(int expected) =>
|
||||||
|
AddUntilStep($"Total scores stored is {expected}", () => Realm.Run(r => r.All<ScoreInfo>().Count(s => !s.DeletePending)), () => Is.EqualTo(expected));
|
||||||
|
|
||||||
private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
|
private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
|
||||||
{
|
{
|
||||||
@ -210,6 +277,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
},
|
},
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
{
|
{
|
||||||
Id = 6602580,
|
Id = 6602580,
|
||||||
@ -226,6 +294,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddSeconds(-30),
|
Date = DateTime.Now.AddSeconds(-30),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
{
|
{
|
||||||
@ -243,6 +312,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddSeconds(-70),
|
Date = DateTime.Now.AddSeconds(-70),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -261,6 +331,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddMinutes(-40),
|
Date = DateTime.Now.AddMinutes(-40),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -279,6 +350,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddHours(-2),
|
Date = DateTime.Now.AddHours(-2),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -297,6 +369,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddHours(-25),
|
Date = DateTime.Now.AddHours(-25),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -315,6 +388,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddHours(-50),
|
Date = DateTime.Now.AddHours(-50),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -333,6 +407,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddHours(-72),
|
Date = DateTime.Now.AddHours(-72),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -351,6 +426,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddMonths(-3),
|
Date = DateTime.Now.AddMonths(-3),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -369,6 +445,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddYears(-2),
|
Date = DateTime.Now.AddYears(-2),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
|
@ -14,10 +14,11 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.BeatmapListing;
|
using osu.Game.Overlays.BeatmapListing;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
public partial class TestSceneBeatmapListingSortTabControl : OsuTestScene
|
public partial class TestSceneBeatmapListingSortTabControl : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private readonly BeatmapListingSortTabControl control;
|
private readonly BeatmapListingSortTabControl control;
|
||||||
|
|
||||||
@ -111,6 +112,29 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
resetUsesCriteriaOnCategory(SortCriteria.Updated, SearchCategory.Mine);
|
resetUsesCriteriaOnCategory(SortCriteria.Updated, SearchCategory.Mine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSortDirectionOnCriteriaChange()
|
||||||
|
{
|
||||||
|
AddStep("set category to leaderboard", () => control.Reset(SearchCategory.Leaderboard, false));
|
||||||
|
AddAssert("sort direction is descending", () => control.SortDirection.Value == SortDirection.Descending);
|
||||||
|
|
||||||
|
AddStep("click ranked sort button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(control.TabControl.ChildrenOfType<BeatmapListingSortTabControl.BeatmapTabButton>().Single(s => s.Active.Value));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("sort direction is ascending", () => control.SortDirection.Value == SortDirection.Ascending);
|
||||||
|
|
||||||
|
AddStep("click first inactive sort button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(control.TabControl.ChildrenOfType<BeatmapListingSortTabControl.BeatmapTabButton>().First(s => !s.Active.Value));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("sort direction is descending", () => control.SortDirection.Value == SortDirection.Descending);
|
||||||
|
}
|
||||||
|
|
||||||
private void criteriaShowsOnCategory(bool expected, SortCriteria criteria, SearchCategory category)
|
private void criteriaShowsOnCategory(bool expected, SortCriteria criteria, SearchCategory category)
|
||||||
{
|
{
|
||||||
AddAssert($"{criteria.ToString().ToLowerInvariant()} {(expected ? "shown" : "not shown")} on {category.ToString().ToLowerInvariant()}", () =>
|
AddAssert($"{criteria.ToString().ToLowerInvariant()} {(expected ? "shown" : "not shown")} on {category.ToString().ToLowerInvariant()}", () =>
|
||||||
|
@ -94,6 +94,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
OnlineID = i,
|
OnlineID = i,
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Accuracy = RNG.NextDouble(),
|
Accuracy = RNG.NextDouble(),
|
||||||
TotalScore = RNG.Next(1, 1000000),
|
TotalScore = RNG.Next(1, 1000000),
|
||||||
MaxCombo = RNG.Next(1, 1000),
|
MaxCombo = RNG.Next(1, 1000),
|
||||||
|
@ -332,13 +332,6 @@ namespace osu.Game.Tournament
|
|||||||
|
|
||||||
private void saveChanges()
|
private void saveChanges()
|
||||||
{
|
{
|
||||||
foreach (var r in ladder.Rounds)
|
|
||||||
r.Matches = ladder.Matches.Where(p => p.Round.Value == r).Select(p => p.ID).ToList();
|
|
||||||
|
|
||||||
ladder.Progressions = ladder.Matches.Where(p => p.Progression.Value != null).Select(p => new TournamentProgression(p.ID, p.Progression.Value.ID)).Concat(
|
|
||||||
ladder.Matches.Where(p => p.LosersProgression.Value != null).Select(p => new TournamentProgression(p.ID, p.LosersProgression.Value.ID, true)))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Serialise before opening stream for writing, so if there's a failure it will leave the file in the previous state.
|
// Serialise before opening stream for writing, so if there's a failure it will leave the file in the previous state.
|
||||||
string serialisedLadder = GetSerialisedLadder();
|
string serialisedLadder = GetSerialisedLadder();
|
||||||
|
|
||||||
@ -349,6 +342,13 @@ namespace osu.Game.Tournament
|
|||||||
|
|
||||||
public string GetSerialisedLadder()
|
public string GetSerialisedLadder()
|
||||||
{
|
{
|
||||||
|
foreach (var r in ladder.Rounds)
|
||||||
|
r.Matches = ladder.Matches.Where(p => p.Round.Value == r).Select(p => p.ID).ToList();
|
||||||
|
|
||||||
|
ladder.Progressions = ladder.Matches.Where(p => p.Progression.Value != null).Select(p => new TournamentProgression(p.ID, p.Progression.Value.ID)).Concat(
|
||||||
|
ladder.Matches.Where(p => p.LosersProgression.Value != null).Select(p => new TournamentProgression(p.ID, p.LosersProgression.Value.ID, true)))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
return JsonConvert.SerializeObject(ladder,
|
return JsonConvert.SerializeObject(ladder,
|
||||||
new JsonSerializerSettings
|
new JsonSerializerSettings
|
||||||
{
|
{
|
||||||
|
@ -70,8 +70,9 @@ namespace osu.Game.Database
|
|||||||
/// 23 2022-08-01 Added LastLocalUpdate to BeatmapInfo.
|
/// 23 2022-08-01 Added LastLocalUpdate to BeatmapInfo.
|
||||||
/// 24 2022-08-22 Added MaximumStatistics to ScoreInfo.
|
/// 24 2022-08-22 Added MaximumStatistics to ScoreInfo.
|
||||||
/// 25 2022-09-18 Remove skins to add with new naming.
|
/// 25 2022-09-18 Remove skins to add with new naming.
|
||||||
|
/// 26 2023-02-05 Added BeatmapHash to ScoreInfo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int schema_version = 25;
|
private const int schema_version = 26;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||||
@ -866,6 +867,15 @@ namespace osu.Game.Database
|
|||||||
// Remove the default skins so they can be added back by SkinManager with updated naming.
|
// Remove the default skins so they can be added back by SkinManager with updated naming.
|
||||||
migration.NewRealm.RemoveRange(migration.NewRealm.All<SkinInfo>().Where(s => s.Protected));
|
migration.NewRealm.RemoveRange(migration.NewRealm.All<SkinInfo>().Where(s => s.Protected));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 26:
|
||||||
|
// Add ScoreInfo.BeatmapHash property to ensure scores correspond to the correct version of beatmap.
|
||||||
|
var scores = migration.NewRealm.All<ScoreInfo>();
|
||||||
|
|
||||||
|
foreach (var score in scores)
|
||||||
|
score.BeatmapHash = score.BeatmapInfo.Hash;
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
osu.Game/Graphics/Backgrounds/TriangleBorderData.cs
Normal file
16
osu.Game/Graphics/Backgrounds/TriangleBorderData.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using osu.Framework.Graphics.Shaders.Types;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.Backgrounds
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public record struct TriangleBorderData
|
||||||
|
{
|
||||||
|
public UniformFloat Thickness;
|
||||||
|
public UniformFloat TexelSize;
|
||||||
|
private readonly UniformPadding8 pad1;
|
||||||
|
}
|
||||||
|
}
|
@ -252,7 +252,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
|
|
||||||
private class TrianglesDrawNode : DrawNode
|
private class TrianglesDrawNode : DrawNode
|
||||||
{
|
{
|
||||||
private float fill = 1f;
|
private const float fill = 1f;
|
||||||
|
|
||||||
protected new Triangles Source => (Triangles)base.Source;
|
protected new Triangles Source => (Triangles)base.Source;
|
||||||
|
|
||||||
@ -284,6 +284,8 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
parts.AddRange(Source.parts);
|
parts.AddRange(Source.parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IUniformBuffer<TriangleBorderData> borderDataBuffer;
|
||||||
|
|
||||||
public override void Draw(IRenderer renderer)
|
public override void Draw(IRenderer renderer)
|
||||||
{
|
{
|
||||||
base.Draw(renderer);
|
base.Draw(renderer);
|
||||||
@ -294,14 +296,17 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
vertexBatch = renderer.CreateQuadBatch<TexturedVertex2D>(Source.AimCount, 1);
|
vertexBatch = renderer.CreateQuadBatch<TexturedVertex2D>(Source.AimCount, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Due to triangles having various sizes we would need to set a different "texelSize" value for each of them, which is insanely expensive, thus we should use one single value.
|
borderDataBuffer ??= renderer.CreateUniformBuffer<TriangleBorderData>();
|
||||||
// texelSize computed for an average triangle (size 100) will result in big triangles becoming blurry, so we may just use 0 for all of them.
|
borderDataBuffer.Data = borderDataBuffer.Data with
|
||||||
// But we still need to specify at least something, because otherwise other shader usages will override this value.
|
{
|
||||||
float texelSize = 0f;
|
Thickness = fill,
|
||||||
|
// Due to triangles having various sizes we would need to set a different "TexelSize" value for each of them, which is insanely expensive, thus we should use one single value.
|
||||||
|
// TexelSize computed for an average triangle (size 100) will result in big triangles becoming blurry, so we may just use 0 for all of them.
|
||||||
|
TexelSize = 0
|
||||||
|
};
|
||||||
|
|
||||||
shader.Bind();
|
shader.Bind();
|
||||||
shader.GetUniform<float>("thickness").UpdateValue(ref fill);
|
shader.BindUniformBlock(@"m_BorderData", borderDataBuffer);
|
||||||
shader.GetUniform<float>("texelSize").UpdateValue(ref texelSize);
|
|
||||||
|
|
||||||
foreach (TriangleParticle particle in parts)
|
foreach (TriangleParticle particle in parts)
|
||||||
{
|
{
|
||||||
@ -352,6 +357,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
vertexBatch?.Dispose();
|
vertexBatch?.Dispose();
|
||||||
|
borderDataBuffer?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,6 +226,8 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
parts.AddRange(Source.parts);
|
parts.AddRange(Source.parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IUniformBuffer<TriangleBorderData>? borderDataBuffer;
|
||||||
|
|
||||||
public override void Draw(IRenderer renderer)
|
public override void Draw(IRenderer renderer)
|
||||||
{
|
{
|
||||||
base.Draw(renderer);
|
base.Draw(renderer);
|
||||||
@ -239,9 +241,15 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
vertexBatch = renderer.CreateQuadBatch<TexturedVertex2D>(Source.AimCount, 1);
|
vertexBatch = renderer.CreateQuadBatch<TexturedVertex2D>(Source.AimCount, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
borderDataBuffer ??= renderer.CreateUniformBuffer<TriangleBorderData>();
|
||||||
|
borderDataBuffer.Data = borderDataBuffer.Data with
|
||||||
|
{
|
||||||
|
Thickness = thickness,
|
||||||
|
TexelSize = texelSize
|
||||||
|
};
|
||||||
|
|
||||||
shader.Bind();
|
shader.Bind();
|
||||||
shader.GetUniform<float>("thickness").UpdateValue(ref thickness);
|
shader.BindUniformBlock(@"m_BorderData", borderDataBuffer);
|
||||||
shader.GetUniform<float>("texelSize").UpdateValue(ref texelSize);
|
|
||||||
|
|
||||||
Vector2 relativeSize = Vector2.Divide(triangleSize, size);
|
Vector2 relativeSize = Vector2.Divide(triangleSize, size);
|
||||||
|
|
||||||
@ -289,6 +297,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
vertexBatch?.Dispose();
|
vertexBatch?.Dispose();
|
||||||
|
borderDataBuffer?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,11 +3,18 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Rendering;
|
using osu.Framework.Graphics.Rendering;
|
||||||
|
using osu.Framework.Graphics.Rendering.Vertices;
|
||||||
using osu.Framework.Graphics.Shaders;
|
using osu.Framework.Graphics.Shaders;
|
||||||
|
using osu.Framework.Graphics.Shaders.Types;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osuTK.Graphics.ES30;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Sprites
|
namespace osu.Game.Graphics.Sprites
|
||||||
{
|
{
|
||||||
@ -16,7 +23,7 @@ namespace osu.Game.Graphics.Sprites
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ShaderManager shaders)
|
private void load(ShaderManager shaders)
|
||||||
{
|
{
|
||||||
TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"LogoAnimation");
|
TextureShader = shaders.Load(@"LogoAnimation", @"LogoAnimation");
|
||||||
}
|
}
|
||||||
|
|
||||||
private float animationProgress;
|
private float animationProgress;
|
||||||
@ -41,11 +48,22 @@ namespace osu.Game.Graphics.Sprites
|
|||||||
{
|
{
|
||||||
private LogoAnimation source => (LogoAnimation)Source;
|
private LogoAnimation source => (LogoAnimation)Source;
|
||||||
|
|
||||||
|
private readonly Action<TexturedVertex2D> addVertexAction;
|
||||||
|
|
||||||
private float progress;
|
private float progress;
|
||||||
|
|
||||||
public LogoAnimationDrawNode(LogoAnimation source)
|
public LogoAnimationDrawNode(LogoAnimation source)
|
||||||
: base(source)
|
: base(source)
|
||||||
{
|
{
|
||||||
|
addVertexAction = v =>
|
||||||
|
{
|
||||||
|
animationVertexBatch!.Add(new LogoAnimationVertex
|
||||||
|
{
|
||||||
|
Position = v.Position,
|
||||||
|
Colour = v.Colour,
|
||||||
|
TexturePosition = v.TexturePosition,
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ApplyState()
|
public override void ApplyState()
|
||||||
@ -55,14 +73,69 @@ namespace osu.Game.Graphics.Sprites
|
|||||||
progress = source.animationProgress;
|
progress = source.animationProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IUniformBuffer<AnimationData> animationDataBuffer;
|
||||||
|
private IVertexBatch<LogoAnimationVertex> animationVertexBatch;
|
||||||
|
|
||||||
|
protected override void BindUniformResources(IShader shader, IRenderer renderer)
|
||||||
|
{
|
||||||
|
base.BindUniformResources(shader, renderer);
|
||||||
|
|
||||||
|
animationDataBuffer ??= renderer.CreateUniformBuffer<AnimationData>();
|
||||||
|
animationVertexBatch ??= renderer.CreateQuadBatch<LogoAnimationVertex>(1, 2);
|
||||||
|
|
||||||
|
animationDataBuffer.Data = animationDataBuffer.Data with { Progress = progress };
|
||||||
|
|
||||||
|
shader.BindUniformBlock(@"m_AnimationData", animationDataBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Blit(IRenderer renderer)
|
protected override void Blit(IRenderer renderer)
|
||||||
{
|
{
|
||||||
TextureShader.GetUniform<float>("progress").UpdateValue(ref progress);
|
if (DrawRectangle.Width == 0 || DrawRectangle.Height == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
base.Blit(renderer);
|
base.Blit(renderer);
|
||||||
|
|
||||||
|
renderer.DrawQuad(
|
||||||
|
Texture,
|
||||||
|
ScreenSpaceDrawQuad,
|
||||||
|
DrawColourInfo.Colour,
|
||||||
|
inflationPercentage: new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / DrawRectangle.Height),
|
||||||
|
textureCoords: TextureCoords,
|
||||||
|
vertexAction: addVertexAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CanDrawOpaqueInterior => false;
|
protected override bool CanDrawOpaqueInterior => false;
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
animationDataBuffer?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
private record struct AnimationData
|
||||||
|
{
|
||||||
|
public UniformFloat Progress;
|
||||||
|
private readonly UniformPadding12 pad1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct LogoAnimationVertex : IEquatable<LogoAnimationVertex>, IVertex
|
||||||
|
{
|
||||||
|
[VertexMember(2, VertexAttribPointerType.Float)]
|
||||||
|
public Vector2 Position;
|
||||||
|
|
||||||
|
[VertexMember(4, VertexAttribPointerType.Float)]
|
||||||
|
public Color4 Colour;
|
||||||
|
|
||||||
|
[VertexMember(2, VertexAttribPointerType.Float)]
|
||||||
|
public Vector2 TexturePosition;
|
||||||
|
|
||||||
|
public readonly bool Equals(LogoAnimationVertex other) =>
|
||||||
|
Position.Equals(other.Position)
|
||||||
|
&& TexturePosition.Equals(other.TexturePosition)
|
||||||
|
&& Colour.Equals(other.Colour);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString RendererHeader => new TranslatableString(getKey(@"renderer_header"), @"Renderer");
|
public static LocalisableString RendererHeader => new TranslatableString(getKey(@"renderer_header"), @"Renderer");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Renderer"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString Renderer => new TranslatableString(getKey(@"renderer"), @"Renderer");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Frame limiter"
|
/// "Frame limiter"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -144,6 +149,12 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString Png => new TranslatableString(getKey(@"png_lossless"), @"PNG (lossless)");
|
public static LocalisableString Png => new TranslatableString(getKey(@"png_lossless"), @"PNG (lossless)");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "In order to change the renderer, the game will close. Please open it again."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ChangeRendererConfirmation =>
|
||||||
|
new TranslatableString(getKey(@"change_renderer_configuration"), @"In order to change the renderer, the game will close. Please open it again.");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,16 +50,18 @@ namespace osu.Game.Localisation
|
|||||||
public static LocalisableString NoAutoplayMod => new TranslatableString(getKey(@"no_autoplay_mod"), @"The current ruleset doesn't have an autoplay mod available!");
|
public static LocalisableString NoAutoplayMod => new TranslatableString(getKey(@"no_autoplay_mod"), @"The current ruleset doesn't have an autoplay mod available!");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "osu! doesn't seem to be able to play audio correctly.\n\nPlease try changing your audio device to a working setting."
|
/// "osu! doesn't seem to be able to play audio correctly.
|
||||||
|
///
|
||||||
|
/// Please try changing your audio device to a working setting."
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString AudioPlaybackIssue => new TranslatableString(getKey(@"audio_playback_issue"),
|
public static LocalisableString AudioPlaybackIssue => new TranslatableString(getKey(@"audio_playback_issue"), @"osu! doesn't seem to be able to play audio correctly.
|
||||||
@"osu! doesn't seem to be able to play audio correctly.\n\nPlease try changing your audio device to a working setting.");
|
|
||||||
|
Please try changing your audio device to a working setting.");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "The score overlay is currently disabled. You can toggle this by pressing {0}."
|
/// "The score overlay is currently disabled. You can toggle this by pressing {0}."
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ScoreOverlayDisabled(LocalisableString arg0) => new TranslatableString(getKey(@"score_overlay_disabled"),
|
public static LocalisableString ScoreOverlayDisabled(LocalisableString arg0) => new TranslatableString(getKey(@"score_overlay_disabled"), @"The score overlay is currently disabled. You can toggle this by pressing {0}.", arg0);
|
||||||
@"The score overlay is currently disabled. You can toggle this by pressing {0}.", arg0);
|
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,11 @@ namespace osu.Game.Localisation
|
|||||||
if (manager == null)
|
if (manager == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
// When using the English culture, prefer the fallbacks rather than osu-resources baked strings.
|
||||||
|
// They are guaranteed to be up-to-date, and is also what a developer expects to see when making changes to `xxxStrings.cs` files.
|
||||||
|
if (EffectiveCulture.Name == @"en")
|
||||||
|
return null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return manager.GetString(key, EffectiveCulture);
|
return manager.GetString(key, EffectiveCulture);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
@ -209,7 +209,7 @@ namespace osu.Game.Overlays.AccountCreation
|
|||||||
if (!string.IsNullOrEmpty(errors.Message))
|
if (!string.IsNullOrEmpty(errors.Message))
|
||||||
passwordDescription.AddErrors(new[] { errors.Message });
|
passwordDescription.AddErrors(new[] { errors.Message });
|
||||||
|
|
||||||
game.OpenUrlExternally($"{errors.Redirect}?username={usernameTextBox.Text}&email={emailTextBox.Text}");
|
game.OpenUrlExternally($"{errors.Redirect}?username={usernameTextBox.Text}&email={emailTextBox.Text}", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
@ -10,8 +11,12 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
{
|
{
|
||||||
public partial class BeatmapListingHeader : OverlayHeader
|
public partial class BeatmapListingHeader : OverlayHeader
|
||||||
{
|
{
|
||||||
|
public BeatmapListingFilterControl FilterControl { get; private set; }
|
||||||
|
|
||||||
protected override OverlayTitle CreateTitle() => new BeatmapListingTitle();
|
protected override OverlayTitle CreateTitle() => new BeatmapListingTitle();
|
||||||
|
|
||||||
|
protected override Drawable CreateContent() => FilterControl = new BeatmapListingFilterControl();
|
||||||
|
|
||||||
private partial class BeatmapListingTitle : OverlayTitle
|
private partial class BeatmapListingTitle : OverlayTitle
|
||||||
{
|
{
|
||||||
public BeatmapListingTitle()
|
public BeatmapListingTitle()
|
||||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
|
|
||||||
if (currentParameters == null)
|
if (currentParameters == null)
|
||||||
Reset(SearchCategory.Leaderboard, false);
|
Reset(SearchCategory.Leaderboard, false);
|
||||||
|
|
||||||
|
Current.BindValueChanged(_ => SortDirection.Value = Overlays.SortDirection.Descending);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset(SearchCategory category, bool hasQuery)
|
public void Reset(SearchCategory category, bool hasQuery)
|
||||||
@ -102,7 +104,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class BeatmapTabButton : TabButton
|
public partial class BeatmapTabButton : TabButton
|
||||||
{
|
{
|
||||||
public readonly Bindable<SortDirection> SortDirection = new Bindable<SortDirection>();
|
public readonly Bindable<SortDirection> SortDirection = new Bindable<SortDirection>();
|
||||||
|
|
||||||
@ -136,7 +138,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
|
|
||||||
SortDirection.BindValueChanged(direction =>
|
SortDirection.BindValueChanged(direction =>
|
||||||
{
|
{
|
||||||
icon.Icon = direction.NewValue == Overlays.SortDirection.Ascending ? FontAwesome.Solid.CaretUp : FontAwesome.Solid.CaretDown;
|
icon.Icon = direction.NewValue == Overlays.SortDirection.Ascending && Active.Value ? FontAwesome.Solid.CaretUp : FontAwesome.Solid.CaretDown;
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,8 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private Container panelTarget;
|
private Container panelTarget;
|
||||||
private FillFlowContainer<BeatmapCard> foundContent;
|
private FillFlowContainer<BeatmapCard> foundContent;
|
||||||
private BeatmapListingFilterControl filterControl;
|
|
||||||
|
private BeatmapListingFilterControl filterControl => Header.FilterControl;
|
||||||
|
|
||||||
public BeatmapListingOverlay()
|
public BeatmapListingOverlay()
|
||||||
: base(OverlayColourScheme.Blue)
|
: base(OverlayColourScheme.Blue)
|
||||||
@ -60,12 +61,6 @@ namespace osu.Game.Overlays
|
|||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
filterControl = new BeatmapListingFilterControl
|
|
||||||
{
|
|
||||||
TypingStarted = onTypingStarted,
|
|
||||||
SearchStarted = onSearchStarted,
|
|
||||||
SearchFinished = onSearchFinished,
|
|
||||||
},
|
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
@ -88,6 +83,10 @@ namespace osu.Game.Overlays
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
filterControl.TypingStarted = onTypingStarted;
|
||||||
|
filterControl.SearchStarted = onSearchStarted;
|
||||||
|
filterControl.SearchFinished = onSearchFinished;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
|
@ -132,11 +132,10 @@ namespace osu.Game.Overlays.Comments
|
|||||||
},
|
},
|
||||||
sideNumber = new OsuSpriteText
|
sideNumber = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.BottomCentre,
|
||||||
Text = "+1",
|
Text = "+1",
|
||||||
Font = OsuFont.GetFont(size: 14),
|
Font = OsuFont.GetFont(size: 14),
|
||||||
Margin = new MarginPadding { Right = 3 },
|
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
},
|
},
|
||||||
votesCounter = new OsuSpriteText
|
votesCounter = new OsuSpriteText
|
||||||
@ -189,7 +188,7 @@ namespace osu.Game.Overlays.Comments
|
|||||||
else
|
else
|
||||||
sideNumber.FadeTo(IsHovered ? 1 : 0);
|
sideNumber.FadeTo(IsHovered ? 1 : 0);
|
||||||
|
|
||||||
borderContainer.BorderThickness = IsHovered ? 3 : 0;
|
borderContainer.BorderThickness = IsHovered ? 2 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onHoverAction()
|
private void onHoverAction()
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -22,6 +23,7 @@ namespace osu.Game.Overlays
|
|||||||
protected readonly OverlayScrollContainer ScrollFlow;
|
protected readonly OverlayScrollContainer ScrollFlow;
|
||||||
|
|
||||||
protected readonly LoadingLayer Loading;
|
protected readonly LoadingLayer Loading;
|
||||||
|
private readonly Container loadingContainer;
|
||||||
private readonly Container content;
|
private readonly Container content;
|
||||||
|
|
||||||
protected OnlineOverlay(OverlayColourScheme colourScheme, bool requiresSignIn = true)
|
protected OnlineOverlay(OverlayColourScheme colourScheme, bool requiresSignIn = true)
|
||||||
@ -65,10 +67,22 @@ namespace osu.Game.Overlays
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Loading = new LoadingLayer(true)
|
loadingContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = Loading = new LoadingLayer(true),
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
base.Content.Add(mainContent);
|
base.Content.Add(mainContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void UpdateAfterChildren()
|
||||||
|
{
|
||||||
|
base.UpdateAfterChildren();
|
||||||
|
|
||||||
|
// don't block header by applying padding equal to the visible header height
|
||||||
|
loadingContainer.Padding = new MarginPadding { Top = Math.Max(0, Header.Height - ScrollFlow.Current) };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
|
||||||
namespace osu.Game.Overlays
|
namespace osu.Game.Overlays
|
||||||
@ -39,7 +40,7 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Right = -3 }, // Compensate for scrollbar margin
|
Padding = new MarginPadding { Right = -3 }, // Compensate for scrollbar margin
|
||||||
Child = new OsuScrollContainer
|
Child = new SidebarScrollContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = new Container
|
Child = new Container
|
||||||
@ -74,5 +75,30 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
[NotNull]
|
[NotNull]
|
||||||
protected virtual Drawable CreateContent() => Empty();
|
protected virtual Drawable CreateContent() => Empty();
|
||||||
|
|
||||||
|
private partial class SidebarScrollContainer : OsuScrollContainer
|
||||||
|
{
|
||||||
|
protected override bool OnScroll(ScrollEvent e)
|
||||||
|
{
|
||||||
|
if (e.ScrollDelta.Y > 0 && IsScrolledToStart())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (e.ScrollDelta.Y < 0 && IsScrolledToEnd())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return base.OnScroll(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
|
{
|
||||||
|
if (e.Delta.Y > 0 && IsScrolledToStart())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (e.Delta.Y < 0 && IsScrolledToEnd())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return base.OnDragStart(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ namespace osu.Game.Overlays
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected partial class TabButton : HeaderButton
|
public partial class TabButton : HeaderButton
|
||||||
{
|
{
|
||||||
public readonly BindableBool Active = new BindableBool();
|
public readonly BindableBool Active = new BindableBool();
|
||||||
|
|
||||||
|
@ -2,15 +2,12 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osuTK;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Extensions.LocalisationExtensions;
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
@ -52,36 +49,24 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
|
|||||||
{
|
{
|
||||||
private readonly OsuSpriteText valueText;
|
private readonly OsuSpriteText valueText;
|
||||||
protected readonly LinkFlowContainer DescriptionText;
|
protected readonly LinkFlowContainer DescriptionText;
|
||||||
private readonly Box lineBackground;
|
|
||||||
|
|
||||||
public new int Count
|
public new int Count
|
||||||
{
|
{
|
||||||
set => valueText.Text = value.ToLocalisableString("N0");
|
set => valueText.Text = value.ToLocalisableString("N0");
|
||||||
}
|
}
|
||||||
|
|
||||||
public CountSection(LocalisableString header)
|
protected CountSection(LocalisableString header)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
Padding = new MarginPadding { Top = 10, Bottom = 20 };
|
Padding = new MarginPadding { Bottom = 20 };
|
||||||
Child = new FillFlowContainer
|
Child = new FillFlowContainer
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Spacing = new Vector2(0, 5),
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new CircularContainer
|
|
||||||
{
|
|
||||||
Masking = true,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 2,
|
|
||||||
Child = lineBackground = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = header,
|
Text = header,
|
||||||
@ -91,7 +76,6 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
|
|||||||
{
|
{
|
||||||
Text = "0",
|
Text = "0",
|
||||||
Font = OsuFont.GetFont(size: 40, weight: FontWeight.Light),
|
Font = OsuFont.GetFont(size: 40, weight: FontWeight.Light),
|
||||||
UseFullGlyphHeight = false,
|
|
||||||
},
|
},
|
||||||
DescriptionText = new LinkFlowContainer(t => t.Font = t.Font.With(size: 14))
|
DescriptionText = new LinkFlowContainer(t => t.Font = t.Font.With(size: 14))
|
||||||
{
|
{
|
||||||
@ -101,12 +85,6 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OverlayColourProvider colourProvider)
|
|
||||||
{
|
|
||||||
lineBackground.Colour = colourProvider.Highlight1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
private FillFlowContainer<SettingsSlider<float>> scalingSettings = null!;
|
private FillFlowContainer<SettingsSlider<float>> scalingSettings = null!;
|
||||||
|
|
||||||
private readonly Bindable<Display> currentDisplay = new Bindable<Display>();
|
private readonly Bindable<Display> currentDisplay = new Bindable<Display>();
|
||||||
private readonly IBindableList<WindowMode> windowModes = new BindableList<WindowMode>();
|
|
||||||
|
|
||||||
private Bindable<ScalingMode> scalingMode = null!;
|
private Bindable<ScalingMode> scalingMode = null!;
|
||||||
private Bindable<Size> sizeFullscreen = null!;
|
private Bindable<Size> sizeFullscreen = null!;
|
||||||
@ -75,7 +74,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
if (window != null)
|
if (window != null)
|
||||||
{
|
{
|
||||||
currentDisplay.BindTo(window.CurrentDisplayBindable);
|
currentDisplay.BindTo(window.CurrentDisplayBindable);
|
||||||
windowModes.BindTo(window.SupportedWindowModes);
|
|
||||||
window.DisplaysChanged += onDisplaysChanged;
|
window.DisplaysChanged += onDisplaysChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +85,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
windowModeDropdown = new SettingsDropdown<WindowMode>
|
windowModeDropdown = new SettingsDropdown<WindowMode>
|
||||||
{
|
{
|
||||||
LabelText = GraphicsSettingsStrings.ScreenMode,
|
LabelText = GraphicsSettingsStrings.ScreenMode,
|
||||||
ItemSource = windowModes,
|
Items = window?.SupportedWindowModes,
|
||||||
|
CanBeShown = { Value = window?.SupportedWindowModes.Count() > 1 },
|
||||||
Current = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode),
|
Current = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode),
|
||||||
},
|
},
|
||||||
displayDropdown = new DisplaySettingsDropdown
|
displayDropdown = new DisplaySettingsDropdown
|
||||||
@ -181,8 +180,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
updateScreenModeWarning();
|
updateScreenModeWarning();
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
windowModes.BindCollectionChanged((_, _) => updateDisplaySettingsVisibility());
|
|
||||||
|
|
||||||
currentDisplay.BindValueChanged(display => Schedule(() =>
|
currentDisplay.BindValueChanged(display => Schedule(() =>
|
||||||
{
|
{
|
||||||
resolutions.RemoveRange(1, resolutions.Count - 1);
|
resolutions.RemoveRange(1, resolutions.Count - 1);
|
||||||
@ -236,7 +233,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
|
|
||||||
private void updateDisplaySettingsVisibility()
|
private void updateDisplaySettingsVisibility()
|
||||||
{
|
{
|
||||||
windowModeDropdown.CanBeShown.Value = windowModes.Count > 1;
|
|
||||||
resolutionDropdown.CanBeShown.Value = resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen;
|
resolutionDropdown.CanBeShown.Value = resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen;
|
||||||
displayDropdown.CanBeShown.Value = displayDropdown.Items.Count() > 1;
|
displayDropdown.CanBeShown.Value = displayDropdown.Items.Count() > 1;
|
||||||
safeAreaConsiderationsCheckbox.CanBeShown.Value = host.Window?.SafeAreaPadding.Value.Total != Vector2.Zero;
|
safeAreaConsiderationsCheckbox.CanBeShown.Value = host.Window?.SafeAreaPadding.Value.Total != Vector2.Zero;
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using System.Linq;
|
||||||
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Overlays.Dialog;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.Graphics
|
namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||||
{
|
{
|
||||||
@ -17,12 +20,25 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
{
|
{
|
||||||
protected override LocalisableString Header => GraphicsSettingsStrings.RendererHeader;
|
protected override LocalisableString Header => GraphicsSettingsStrings.RendererHeader;
|
||||||
|
|
||||||
|
private bool automaticRendererInUse;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(FrameworkConfigManager config, OsuConfigManager osuConfig)
|
private void load(FrameworkConfigManager config, OsuConfigManager osuConfig, IDialogOverlay? dialogOverlay, OsuGame? game, GameHost host)
|
||||||
{
|
{
|
||||||
// NOTE: Compatability mode omitted
|
var renderer = config.GetBindable<RendererType>(FrameworkSetting.Renderer);
|
||||||
|
automaticRendererInUse = renderer.Value == RendererType.Automatic;
|
||||||
|
|
||||||
|
SettingsEnumDropdown<RendererType> rendererDropdown;
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
rendererDropdown = new RendererSettingsDropdown
|
||||||
|
{
|
||||||
|
LabelText = GraphicsSettingsStrings.Renderer,
|
||||||
|
Current = renderer,
|
||||||
|
Items = host.GetPreferredRenderersForCurrentPlatform().OrderBy(t => t).Where(t => t != RendererType.Vulkan),
|
||||||
|
Keywords = new[] { @"compatibility", @"directx" },
|
||||||
|
},
|
||||||
// TODO: this needs to be a custom dropdown at some point
|
// TODO: this needs to be a custom dropdown at some point
|
||||||
new SettingsEnumDropdown<FrameSync>
|
new SettingsEnumDropdown<FrameSync>
|
||||||
{
|
{
|
||||||
@ -41,6 +57,55 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
Current = osuConfig.GetBindable<bool>(OsuSetting.ShowFpsDisplay)
|
Current = osuConfig.GetBindable<bool>(OsuSetting.ShowFpsDisplay)
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderer.BindValueChanged(r =>
|
||||||
|
{
|
||||||
|
if (r.NewValue == host.ResolvedRenderer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Need to check startup renderer for the "automatic" case, as ResolvedRenderer above will track the final resolved renderer instead.
|
||||||
|
if (r.NewValue == RendererType.Automatic && automaticRendererInUse)
|
||||||
|
return;
|
||||||
|
|
||||||
|
dialogOverlay?.Push(new ConfirmDialog(GraphicsSettingsStrings.ChangeRendererConfirmation, () => game?.AttemptExit(), () =>
|
||||||
|
{
|
||||||
|
renderer.Value = automaticRendererInUse ? RendererType.Automatic : host.ResolvedRenderer;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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>
|
||||||
|
{
|
||||||
|
protected override OsuDropdown<RendererType> CreateDropdown() => new RendererDropdown();
|
||||||
|
|
||||||
|
protected partial class RendererDropdown : DropdownControl
|
||||||
|
{
|
||||||
|
private RendererType hostResolvedRenderer;
|
||||||
|
private bool automaticRendererInUse;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(FrameworkConfigManager config, GameHost host)
|
||||||
|
{
|
||||||
|
var renderer = config.GetBindable<RendererType>(FrameworkSetting.Renderer);
|
||||||
|
automaticRendererInUse = renderer.Value == RendererType.Automatic;
|
||||||
|
hostResolvedRenderer = host.ResolvedRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override LocalisableString GenerateItemText(RendererType item)
|
||||||
|
{
|
||||||
|
if (item == RendererType.Automatic && automaticRendererInUse)
|
||||||
|
return LocalisableString.Interpolate($"{base.GenerateItemText(item)} ({hostResolvedRenderer.GetDescription()})");
|
||||||
|
|
||||||
|
return base.GenerateItemText(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private SkinEditor editor { get; set; } = null!;
|
private SkinEditor editor { get; set; } = null!;
|
||||||
|
|
||||||
|
protected override bool AllowCyclicSelection => true;
|
||||||
|
|
||||||
public SkinBlueprintContainer(ISerialisableDrawableContainer targetContainer)
|
public SkinBlueprintContainer(ISerialisableDrawableContainer targetContainer)
|
||||||
{
|
{
|
||||||
this.targetContainer = targetContainer;
|
this.targetContainer = targetContainer;
|
||||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
protected readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
|
protected readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
|
||||||
|
|
||||||
// Toolbar and its components need keyboard input even when hidden.
|
// Toolbar and its components need keyboard input even when hidden.
|
||||||
public override bool PropagateNonPositionalInputSubTree => true;
|
public override bool PropagateNonPositionalInputSubTree => OverlayActivationMode.Value != OverlayActivation.Disabled;
|
||||||
|
|
||||||
public Toolbar()
|
public Toolbar()
|
||||||
{
|
{
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Wiki.Markdown
|
namespace osu.Game.Overlays.Wiki.Markdown
|
||||||
{
|
{
|
||||||
@ -19,24 +20,30 @@ namespace osu.Game.Overlays.Wiki.Markdown
|
|||||||
{
|
{
|
||||||
private readonly bool isOutdated;
|
private readonly bool isOutdated;
|
||||||
private readonly bool needsCleanup;
|
private readonly bool needsCleanup;
|
||||||
|
private readonly bool isStub;
|
||||||
|
|
||||||
public WikiNoticeContainer(YamlFrontMatterBlock yamlFrontMatterBlock)
|
public WikiNoticeContainer(YamlFrontMatterBlock yamlFrontMatterBlock)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
Direction = FillDirection.Vertical;
|
Direction = FillDirection.Vertical;
|
||||||
|
Spacing = new Vector2(10);
|
||||||
|
|
||||||
foreach (object line in yamlFrontMatterBlock.Lines)
|
foreach (object line in yamlFrontMatterBlock.Lines)
|
||||||
{
|
{
|
||||||
switch (line.ToString())
|
switch (line.ToString())
|
||||||
{
|
{
|
||||||
case "outdated: true":
|
case @"outdated: true":
|
||||||
isOutdated = true;
|
isOutdated = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "needs_cleanup: true":
|
case @"needs_cleanup: true":
|
||||||
needsCleanup = true;
|
needsCleanup = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case @"stub: true":
|
||||||
|
isStub = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,6 +67,14 @@ namespace osu.Game.Overlays.Wiki.Markdown
|
|||||||
Text = WikiStrings.ShowNeedsCleanupOrRewrite,
|
Text = WikiStrings.ShowNeedsCleanupOrRewrite,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isStub)
|
||||||
|
{
|
||||||
|
Add(new NoticeBox
|
||||||
|
{
|
||||||
|
Text = WikiStrings.ShowStub,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class NoticeBox : Container
|
private partial class NoticeBox : Container
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -9,6 +10,7 @@ using osu.Framework.Graphics.Primitives;
|
|||||||
using osu.Framework.Graphics.Rendering;
|
using osu.Framework.Graphics.Rendering;
|
||||||
using osu.Framework.Graphics.Rendering.Vertices;
|
using osu.Framework.Graphics.Rendering.Vertices;
|
||||||
using osu.Framework.Graphics.Shaders;
|
using osu.Framework.Graphics.Shaders;
|
||||||
|
using osu.Framework.Graphics.Shaders.Types;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -245,6 +247,8 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
flashlightSmoothness = Source.flashlightSmoothness;
|
flashlightSmoothness = Source.flashlightSmoothness;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IUniformBuffer<FlashlightParameters>? flashlightParametersBuffer;
|
||||||
|
|
||||||
public override void Draw(IRenderer renderer)
|
public override void Draw(IRenderer renderer)
|
||||||
{
|
{
|
||||||
base.Draw(renderer);
|
base.Draw(renderer);
|
||||||
@ -259,12 +263,17 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
shader.Bind();
|
flashlightParametersBuffer ??= renderer.CreateUniformBuffer<FlashlightParameters>();
|
||||||
|
flashlightParametersBuffer.Data = flashlightParametersBuffer.Data with
|
||||||
|
{
|
||||||
|
Position = flashlightPosition,
|
||||||
|
Size = flashlightSize,
|
||||||
|
Dim = flashlightDim,
|
||||||
|
Smoothness = flashlightSmoothness
|
||||||
|
};
|
||||||
|
|
||||||
shader.GetUniform<Vector2>("flashlightPos").UpdateValue(ref flashlightPosition);
|
shader.Bind();
|
||||||
shader.GetUniform<Vector2>("flashlightSize").UpdateValue(ref flashlightSize);
|
shader.BindUniformBlock(@"m_FlashlightParameters", flashlightParametersBuffer);
|
||||||
shader.GetUniform<float>("flashlightDim").UpdateValue(ref flashlightDim);
|
|
||||||
shader.GetUniform<float>("flashlightSmoothness").UpdateValue(ref flashlightSmoothness);
|
|
||||||
|
|
||||||
renderer.DrawQuad(renderer.WhitePixel, screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: addAction);
|
renderer.DrawQuad(renderer.WhitePixel, screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: addAction);
|
||||||
|
|
||||||
@ -275,6 +284,17 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
quadBatch?.Dispose();
|
quadBatch?.Dispose();
|
||||||
|
flashlightParametersBuffer?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
private record struct FlashlightParameters
|
||||||
|
{
|
||||||
|
public UniformVector2 Position;
|
||||||
|
public UniformVector2 Size;
|
||||||
|
public UniformFloat Dim;
|
||||||
|
public UniformFloat Smoothness;
|
||||||
|
private readonly UniformPadding8 pad1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
|
@ -25,21 +25,28 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The texture store to be used for the ruleset.
|
/// The texture store to be used for the ruleset.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Reads textures from the "Textures" folder in ruleset resources.
|
||||||
|
/// If not available locally, lookups will fallback to the global texture store.
|
||||||
|
/// </remarks>
|
||||||
public TextureStore TextureStore { get; }
|
public TextureStore TextureStore { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The sample store to be used for the ruleset.
|
/// The sample store to be used for the ruleset.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is the local sample store pointing to the ruleset sample resources,
|
/// Reads samples from the "Samples" folder in ruleset resources.
|
||||||
/// the cached sample store (<see cref="FallbackSampleStore"/>) retrieves from
|
/// If not available locally, lookups will fallback to the global sample store.
|
||||||
/// this store and falls back to the parent store if this store doesn't have the requested sample.
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public ISampleStore SampleStore { get; }
|
public ISampleStore SampleStore { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The shader manager to be used for the ruleset.
|
/// The shader manager to be used for the ruleset.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Reads shaders from the "Shaders" folder in ruleset resources.
|
||||||
|
/// If not available locally, lookups will fallback to the global shader manager.
|
||||||
|
/// </remarks>
|
||||||
public ShaderManager ShaderManager { get; }
|
public ShaderManager ShaderManager { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -61,8 +68,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
SampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY;
|
SampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY;
|
||||||
CacheAs(SampleStore = new FallbackSampleStore(SampleStore, parent.Get<ISampleStore>()));
|
CacheAs(SampleStore = new FallbackSampleStore(SampleStore, parent.Get<ISampleStore>()));
|
||||||
|
|
||||||
ShaderManager = new ShaderManager(host.Renderer, new NamespacedResourceStore<byte[]>(resources, @"Shaders"));
|
CacheAs(ShaderManager = new RulesetShaderManager(host.Renderer, new NamespacedResourceStore<byte[]>(resources, @"Shaders"), parent.Get<ShaderManager>()));
|
||||||
CacheAs(ShaderManager = new FallbackShaderManager(host.Renderer, ShaderManager, parent.Get<ShaderManager>()));
|
|
||||||
|
|
||||||
RulesetConfigManager = parent.Get<IRulesetConfigCache>().GetConfigFor(ruleset);
|
RulesetConfigManager = parent.Get<IRulesetConfigCache>().GetConfigFor(ruleset);
|
||||||
if (RulesetConfigManager != null)
|
if (RulesetConfigManager != null)
|
||||||
@ -190,24 +196,36 @@ namespace osu.Game.Rulesets.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FallbackShaderManager : ShaderManager
|
private class RulesetShaderManager : ShaderManager
|
||||||
{
|
{
|
||||||
private readonly ShaderManager primary;
|
private readonly ShaderManager parent;
|
||||||
private readonly ShaderManager fallback;
|
|
||||||
|
|
||||||
public FallbackShaderManager(IRenderer renderer, ShaderManager primary, ShaderManager fallback)
|
public RulesetShaderManager(IRenderer renderer, NamespacedResourceStore<byte[]> rulesetResources, ShaderManager parent)
|
||||||
: base(renderer, new ResourceStore<byte[]>())
|
: base(renderer, rulesetResources)
|
||||||
{
|
{
|
||||||
this.primary = primary;
|
this.parent = parent;
|
||||||
this.fallback = fallback;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override byte[]? LoadRaw(string name) => primary.LoadRaw(name) ?? fallback.LoadRaw(name);
|
// When the debugger is attached, exceptions are expensive.
|
||||||
|
// Manually work around this by caching failed lookups and falling back straight to parent.
|
||||||
|
private readonly HashSet<(string, string)> failedLookups = new HashSet<(string, string)>();
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
public override IShader Load(string vertex, string fragment)
|
||||||
{
|
{
|
||||||
base.Dispose(disposing);
|
if (!failedLookups.Contains((vertex, fragment)))
|
||||||
if (primary.IsNotNull()) primary.Dispose();
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return base.Load(vertex, fragment);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Shader lookup is very non-standard. Rather than returning null on missing shaders, exceptions are thrown.
|
||||||
|
failedLookups.Add((vertex, fragment));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent.Load(vertex, fragment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ using osu.Game.Input;
|
|||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Input.Handlers;
|
using osu.Game.Input.Handlers;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
||||||
using static osu.Game.Input.Handlers.ReplayInputHandler;
|
using static osu.Game.Input.Handlers.ReplayInputHandler;
|
||||||
|
|
||||||
@ -39,6 +39,9 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
|
if (value == recorder)
|
||||||
|
return;
|
||||||
|
|
||||||
if (value != null && recorder != null)
|
if (value != null && recorder != null)
|
||||||
throw new InvalidOperationException("Cannot attach more than one recorder");
|
throw new InvalidOperationException("Cannot attach more than one recorder");
|
||||||
|
|
||||||
@ -166,7 +169,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
.Select(b => b.GetAction<T>())
|
.Select(b => b.GetAction<T>())
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.OrderBy(action => action)
|
.OrderBy(action => action)
|
||||||
.Select(action => new KeyCounterAction<T>(action)));
|
.Select(action => new KeyCounterActionTrigger<T>(action)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler<T>
|
private partial class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler<T>
|
||||||
@ -176,11 +179,14 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnPressed(KeyBindingPressEvent<T> e) => Target.Children.OfType<KeyCounterAction<T>>().Any(c => c.OnPressed(e.Action, Clock.Rate >= 0));
|
public bool OnPressed(KeyBindingPressEvent<T> e) => Target.Counters.Where(c => c.Trigger is KeyCounterActionTrigger<T>)
|
||||||
|
.Select(c => (KeyCounterActionTrigger<T>)c.Trigger)
|
||||||
|
.Any(c => c.OnPressed(e.Action, Clock.Rate >= 0));
|
||||||
|
|
||||||
public void OnReleased(KeyBindingReleaseEvent<T> e)
|
public void OnReleased(KeyBindingReleaseEvent<T> e)
|
||||||
{
|
{
|
||||||
foreach (var c in Target.Children.OfType<KeyCounterAction<T>>())
|
foreach (var c
|
||||||
|
in Target.Counters.Where(c => c.Trigger is KeyCounterActionTrigger<T>).Select(c => (KeyCounterActionTrigger<T>)c.Trigger))
|
||||||
c.OnReleased(e.Action, Clock.Rate >= 0);
|
c.OnReleased(e.Action, Clock.Rate >= 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,6 +123,7 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
// before returning for database import, we must restore the database-sourced BeatmapInfo.
|
// before returning for database import, we must restore the database-sourced BeatmapInfo.
|
||||||
// if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception.
|
// if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception.
|
||||||
score.ScoreInfo.BeatmapInfo = workingBeatmap.BeatmapInfo;
|
score.ScoreInfo.BeatmapInfo = workingBeatmap.BeatmapInfo;
|
||||||
|
score.ScoreInfo.BeatmapHash = workingBeatmap.BeatmapInfo.Hash;
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,9 @@ using Realms;
|
|||||||
|
|
||||||
namespace osu.Game.Scoring
|
namespace osu.Game.Scoring
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A realm model containing metadata for a single score.
|
||||||
|
/// </summary>
|
||||||
[ExcludeFromDynamicCompile]
|
[ExcludeFromDynamicCompile]
|
||||||
[MapTo("Score")]
|
[MapTo("Score")]
|
||||||
public class ScoreInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftDelete, IEquatable<ScoreInfo>, IScoreInfo
|
public class ScoreInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftDelete, IEquatable<ScoreInfo>, IScoreInfo
|
||||||
@ -29,8 +32,19 @@ namespace osu.Game.Scoring
|
|||||||
[PrimaryKey]
|
[PrimaryKey]
|
||||||
public Guid ID { get; set; }
|
public Guid ID { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="BeatmapInfo"/> this score was made against.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// When setting this, make sure to also set <see cref="BeatmapHash"/> to allow relational consistency when a beatmap is potentially changed.
|
||||||
|
/// </remarks>
|
||||||
public BeatmapInfo BeatmapInfo { get; set; } = null!;
|
public BeatmapInfo BeatmapInfo { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="osu.Game.Beatmaps.BeatmapInfo.Hash"/> at the point in time when the score was set.
|
||||||
|
/// </summary>
|
||||||
|
public string BeatmapHash { get; set; } = string.Empty;
|
||||||
|
|
||||||
public RulesetInfo Ruleset { get; set; } = null!;
|
public RulesetInfo Ruleset { get; set; } = null!;
|
||||||
|
|
||||||
public IList<RealmNamedFileUsage> Files { get; } = null!;
|
public IList<RealmNamedFileUsage> Files { get; } = null!;
|
||||||
|
@ -45,6 +45,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
protected readonly BindableList<T> SelectedItems = new BindableList<T>();
|
protected readonly BindableList<T> SelectedItems = new BindableList<T>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to allow cyclic selection on clicking multiple times.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Disabled by default as it does not work well with editors that support double-clicking or other advanced interactions.
|
||||||
|
/// Can probably be made to work with more thought.
|
||||||
|
/// </remarks>
|
||||||
|
protected virtual bool AllowCyclicSelection => false;
|
||||||
|
|
||||||
protected BlueprintContainer()
|
protected BlueprintContainer()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
@ -167,8 +176,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
endClickSelection(e);
|
endClickSelection(e);
|
||||||
clickSelectionBegan = false;
|
clickSelectionHandled = false;
|
||||||
isDraggingBlueprint = false;
|
isDraggingBlueprint = false;
|
||||||
|
wasDragStarted = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
finishSelectionMovement();
|
finishSelectionMovement();
|
||||||
@ -182,6 +192,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
lastDragEvent = e;
|
lastDragEvent = e;
|
||||||
|
wasDragStarted = true;
|
||||||
|
|
||||||
if (movementBlueprints != null)
|
if (movementBlueprints != null)
|
||||||
{
|
{
|
||||||
@ -339,7 +350,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether a blueprint was selected by a previous click event.
|
/// Whether a blueprint was selected by a previous click event.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool clickSelectionBegan;
|
private bool clickSelectionHandled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the selected blueprint(s) were already selected on mouse down. Generally used to perform selection cycling on mouse up in such a case.
|
||||||
|
/// </summary>
|
||||||
|
private bool selectedBlueprintAlreadySelectedOnMouseDown;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to select any hovered blueprints.
|
/// Attempts to select any hovered blueprints.
|
||||||
@ -354,7 +370,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
if (!blueprint.IsHovered) continue;
|
if (!blueprint.IsHovered) continue;
|
||||||
|
|
||||||
return clickSelectionBegan = SelectionHandler.MouseDownSelectionRequested(blueprint, e);
|
selectedBlueprintAlreadySelectedOnMouseDown = blueprint.State == SelectionState.Selected;
|
||||||
|
return clickSelectionHandled = SelectionHandler.MouseDownSelectionRequested(blueprint, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -367,25 +384,48 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <returns>Whether a click selection was active.</returns>
|
/// <returns>Whether a click selection was active.</returns>
|
||||||
private bool endClickSelection(MouseButtonEvent e)
|
private bool endClickSelection(MouseButtonEvent e)
|
||||||
{
|
{
|
||||||
if (!clickSelectionBegan && !isDraggingBlueprint)
|
// If already handled a selection or drag, we don't want to perform a mouse up / click action.
|
||||||
|
if (clickSelectionHandled || isDraggingBlueprint) return true;
|
||||||
|
|
||||||
|
if (e.Button != MouseButton.Left) return false;
|
||||||
|
|
||||||
|
if (e.ControlPressed)
|
||||||
{
|
{
|
||||||
// if a selection didn't occur, we may want to trigger a deselection.
|
// if a selection didn't occur, we may want to trigger a deselection.
|
||||||
if (e.ControlPressed && e.Button == MouseButton.Left)
|
|
||||||
{
|
|
||||||
// Iterate from the top of the input stack (blueprints closest to the front of the screen first).
|
// Iterate from the top of the input stack (blueprints closest to the front of the screen first).
|
||||||
// Priority is given to already-selected blueprints.
|
// Priority is given to already-selected blueprints.
|
||||||
foreach (SelectionBlueprint<T> blueprint in SelectionBlueprints.AliveChildren.Reverse().OrderByDescending(b => b.IsSelected))
|
foreach (SelectionBlueprint<T> blueprint in SelectionBlueprints.AliveChildren.Where(b => b.IsHovered).OrderByDescending(b => b.IsSelected))
|
||||||
{
|
return clickSelectionHandled = SelectionHandler.MouseUpSelectionRequested(blueprint, e);
|
||||||
if (!blueprint.IsHovered) continue;
|
|
||||||
|
|
||||||
return clickSelectionBegan = SelectionHandler.MouseUpSelectionRequested(blueprint, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
if (!wasDragStarted && selectedBlueprintAlreadySelectedOnMouseDown && SelectedItems.Count == 1 && AllowCyclicSelection)
|
||||||
|
{
|
||||||
|
// If a click occurred and was handled by the currently selected blueprint but didn't result in a drag,
|
||||||
|
// cycle between other blueprints which are also under the cursor.
|
||||||
|
|
||||||
|
// The depth of blueprints is constantly changing (see above where selected blueprints are brought to the front).
|
||||||
|
// For this logic, we want a stable sort order so we can correctly cycle, thus using the blueprintMap instead.
|
||||||
|
IEnumerable<SelectionBlueprint<T>> cyclingSelectionBlueprints = blueprintMap.Values;
|
||||||
|
|
||||||
|
// If there's already a selection, let's start from the blueprint after the selection.
|
||||||
|
cyclingSelectionBlueprints = cyclingSelectionBlueprints.SkipWhile(b => !b.IsSelected).Skip(1);
|
||||||
|
|
||||||
|
// Add the blueprints from before the selection to the end of the enumerable to allow for cyclic selection.
|
||||||
|
cyclingSelectionBlueprints = cyclingSelectionBlueprints.Concat(blueprintMap.Values.TakeWhile(b => !b.IsSelected));
|
||||||
|
|
||||||
|
foreach (SelectionBlueprint<T> blueprint in cyclingSelectionBlueprints)
|
||||||
|
{
|
||||||
|
if (!blueprint.IsHovered) continue;
|
||||||
|
|
||||||
|
// We are performing a mouse up, but selection handlers perform selection on mouse down, so we need to call that instead.
|
||||||
|
return clickSelectionHandled = SelectionHandler.MouseDownSelectionRequested(blueprint, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -441,8 +481,17 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
private Vector2[][] movementBlueprintsOriginalPositions;
|
private Vector2[][] movementBlueprintsOriginalPositions;
|
||||||
private SelectionBlueprint<T>[] movementBlueprints;
|
private SelectionBlueprint<T>[] movementBlueprints;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether a blueprint is currently being dragged.
|
||||||
|
/// </summary>
|
||||||
private bool isDraggingBlueprint;
|
private bool isDraggingBlueprint;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether a drag operation was started at all.
|
||||||
|
/// </summary>
|
||||||
|
private bool wasDragStarted;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to begin the movement of any selected blueprints.
|
/// Attempts to begin the movement of any selected blueprints.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -454,7 +503,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
// Any selected blueprint that is hovered can begin the movement of the group, however only the first item (according to SortForMovement) is used for movement.
|
// Any selected blueprint that is hovered can begin the movement of the group, however only the first item (according to SortForMovement) is used for movement.
|
||||||
// A special case is added for when a click selection occurred before the drag
|
// A special case is added for when a click selection occurred before the drag
|
||||||
if (!clickSelectionBegan && !SelectionHandler.SelectedBlueprints.Any(b => b.IsHovered))
|
if (!clickSelectionHandled && !SelectionHandler.SelectedBlueprints.Any(b => b.IsHovered))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Movement is tracked from the blueprint of the earliest item, since it only makes sense to distance snap from that item
|
// Movement is tracked from the blueprint of the earliest item, since it only makes sense to distance snap from that item
|
||||||
|
@ -130,6 +130,8 @@ namespace osu.Game.Screens
|
|||||||
|
|
||||||
loadTargets.Add(manager.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE));
|
loadTargets.Add(manager.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE));
|
||||||
|
|
||||||
|
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, "TriangleBorder"));
|
||||||
|
|
||||||
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE));
|
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,11 +73,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
private OsuSpriteText typeLabel = null!;
|
private OsuSpriteText typeLabel = null!;
|
||||||
private LoadingLayer loadingLayer = null!;
|
private LoadingLayer loadingLayer = null!;
|
||||||
|
|
||||||
public void SelectBeatmap()
|
public void SelectBeatmap() => selectBeatmapButton.TriggerClick();
|
||||||
{
|
|
||||||
if (matchSubScreen.IsCurrentScreen())
|
|
||||||
matchSubScreen.Push(new MultiplayerMatchSongSelect(matchSubScreen.Room));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private MultiplayerMatchSubScreen matchSubScreen { get; set; } = null!;
|
private MultiplayerMatchSubScreen matchSubScreen { get; set; } = null!;
|
||||||
@ -97,6 +93,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
private IDisposable? applyingSettingsOperation;
|
private IDisposable? applyingSettingsOperation;
|
||||||
private Drawable playlistContainer = null!;
|
private Drawable playlistContainer = null!;
|
||||||
private DrawableRoomPlaylist drawablePlaylist = null!;
|
private DrawableRoomPlaylist drawablePlaylist = null!;
|
||||||
|
private RoundedButton selectBeatmapButton = null!;
|
||||||
|
|
||||||
public MatchSettings(Room room)
|
public MatchSettings(Room room)
|
||||||
{
|
{
|
||||||
@ -275,12 +272,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = DrawableRoomPlaylistItem.HEIGHT
|
Height = DrawableRoomPlaylistItem.HEIGHT
|
||||||
},
|
},
|
||||||
new RoundedButton
|
selectBeatmapButton = new RoundedButton
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = 40,
|
Height = 40,
|
||||||
Text = "Select beatmap",
|
Text = "Select beatmap",
|
||||||
Action = SelectBeatmap
|
Action = () =>
|
||||||
|
{
|
||||||
|
if (matchSubScreen.IsCurrentScreen())
|
||||||
|
matchSubScreen.Push(new MultiplayerMatchSongSelect(matchSubScreen.Room));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ namespace osu.Game.Screens.Play
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
FillMode = FillMode.Fill,
|
FillMode = FillMode.Fill,
|
||||||
},
|
},
|
||||||
loading = new LoadingLayer(true)
|
loading = new LoadingLayer(dimBackground: true, blockInput: false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
versionFlow = new FillFlowContainer
|
versionFlow = new FillFlowContainer
|
||||||
|
@ -242,7 +242,6 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
{
|
{
|
||||||
length = value;
|
length = value;
|
||||||
mask.Width = value * DrawWidth;
|
mask.Width = value * DrawWidth;
|
||||||
fill.Width = value * DrawWidth;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -13,70 +11,23 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play.HUD
|
||||||
{
|
{
|
||||||
public abstract partial class KeyCounter : Container
|
public partial class DefaultKeyCounter : KeyCounter
|
||||||
{
|
{
|
||||||
private Sprite buttonSprite;
|
private Sprite buttonSprite = null!;
|
||||||
private Sprite glowSprite;
|
private Sprite glowSprite = null!;
|
||||||
private Container textLayer;
|
private Container textLayer = null!;
|
||||||
private SpriteText countSpriteText;
|
private SpriteText countSpriteText = null!;
|
||||||
|
|
||||||
public bool IsCounting { get; set; } = true;
|
|
||||||
private int countPresses;
|
|
||||||
|
|
||||||
public int CountPresses
|
|
||||||
{
|
|
||||||
get => countPresses;
|
|
||||||
private set
|
|
||||||
{
|
|
||||||
if (countPresses != value)
|
|
||||||
{
|
|
||||||
countPresses = value;
|
|
||||||
countSpriteText.Text = value.ToString(@"#,0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool isLit;
|
|
||||||
|
|
||||||
public bool IsLit
|
|
||||||
{
|
|
||||||
get => isLit;
|
|
||||||
protected set
|
|
||||||
{
|
|
||||||
if (isLit != value)
|
|
||||||
{
|
|
||||||
isLit = value;
|
|
||||||
updateGlowSprite(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Increment()
|
|
||||||
{
|
|
||||||
if (!IsCounting)
|
|
||||||
return;
|
|
||||||
|
|
||||||
CountPresses++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Decrement()
|
|
||||||
{
|
|
||||||
if (!IsCounting)
|
|
||||||
return;
|
|
||||||
|
|
||||||
CountPresses--;
|
|
||||||
}
|
|
||||||
|
|
||||||
//further: change default values here and in KeyCounterCollection if needed, instead of passing them in every constructor
|
//further: change default values here and in KeyCounterCollection if needed, instead of passing them in every constructor
|
||||||
public Color4 KeyDownTextColor { get; set; } = Color4.DarkGray;
|
public Color4 KeyDownTextColor { get; set; } = Color4.DarkGray;
|
||||||
public Color4 KeyUpTextColor { get; set; } = Color4.White;
|
public Color4 KeyUpTextColor { get; set; } = Color4.White;
|
||||||
public double FadeTime { get; set; }
|
public double FadeTime { get; set; }
|
||||||
|
|
||||||
protected KeyCounter(string name)
|
public DefaultKeyCounter(InputTrigger trigger)
|
||||||
|
: base(trigger)
|
||||||
{
|
{
|
||||||
Name = name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
@ -116,7 +67,7 @@ namespace osu.Game.Screens.Play
|
|||||||
},
|
},
|
||||||
countSpriteText = new OsuSpriteText
|
countSpriteText = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = CountPresses.ToString(@"#,0"),
|
Text = CountPresses.Value.ToString(@"#,0"),
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativePositionAxes = Axes.Both,
|
RelativePositionAxes = Axes.Both,
|
||||||
@ -130,6 +81,9 @@ namespace osu.Game.Screens.Play
|
|||||||
// so the size can be changing between buttonSprite and glowSprite.
|
// so the size can be changing between buttonSprite and glowSprite.
|
||||||
Height = buttonSprite.DrawHeight;
|
Height = buttonSprite.DrawHeight;
|
||||||
Width = buttonSprite.DrawWidth;
|
Width = buttonSprite.DrawWidth;
|
||||||
|
|
||||||
|
IsActive.BindValueChanged(e => updateGlowSprite(e.NewValue), true);
|
||||||
|
CountPresses.BindValueChanged(e => countSpriteText.Text = e.NewValue.ToString(@"#,0"), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateGlowSprite(bool show)
|
private void updateGlowSprite(bool show)
|
83
osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs
Normal file
83
osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play.HUD
|
||||||
|
{
|
||||||
|
public partial class DefaultKeyCounterDisplay : KeyCounterDisplay
|
||||||
|
{
|
||||||
|
private const int duration = 100;
|
||||||
|
private const double key_fade_time = 80;
|
||||||
|
|
||||||
|
private readonly FillFlowContainer<DefaultKeyCounter> keyFlow;
|
||||||
|
|
||||||
|
public override IEnumerable<KeyCounter> Counters => keyFlow;
|
||||||
|
|
||||||
|
public DefaultKeyCounterDisplay()
|
||||||
|
{
|
||||||
|
InternalChild = keyFlow = new FillFlowContainer<DefaultKeyCounter>
|
||||||
|
{
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
// Don't use autosize as it will shrink to zero when KeyFlow is hidden.
|
||||||
|
// In turn this can cause the display to be masked off screen and never become visible again.
|
||||||
|
Size = keyFlow.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Add(InputTrigger trigger) =>
|
||||||
|
keyFlow.Add(new DefaultKeyCounter(trigger)
|
||||||
|
{
|
||||||
|
FadeTime = key_fade_time,
|
||||||
|
KeyDownTextColor = KeyDownTextColor,
|
||||||
|
KeyUpTextColor = KeyUpTextColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
protected override void UpdateVisibility() =>
|
||||||
|
// Isolate changing visibility of the key counters from fading this component.
|
||||||
|
keyFlow.FadeTo(AlwaysVisible.Value || ConfigVisibility.Value ? 1 : 0, duration);
|
||||||
|
|
||||||
|
private Color4 keyDownTextColor = Color4.DarkGray;
|
||||||
|
|
||||||
|
public Color4 KeyDownTextColor
|
||||||
|
{
|
||||||
|
get => keyDownTextColor;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value != keyDownTextColor)
|
||||||
|
{
|
||||||
|
keyDownTextColor = value;
|
||||||
|
foreach (var child in keyFlow)
|
||||||
|
child.KeyDownTextColor = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color4 keyUpTextColor = Color4.White;
|
||||||
|
|
||||||
|
public Color4 KeyUpTextColor
|
||||||
|
{
|
||||||
|
get => keyUpTextColor;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value != keyUpTextColor)
|
||||||
|
{
|
||||||
|
keyUpTextColor = value;
|
||||||
|
foreach (var child in keyFlow)
|
||||||
|
child.KeyUpTextColor = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
osu.Game/Screens/Play/HUD/InputTrigger.cs
Normal file
37
osu.Game/Screens/Play/HUD/InputTrigger.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play.HUD
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An event trigger which can be used with <see cref="KeyCounter"/> to create visual tracking of button/key presses.
|
||||||
|
/// </summary>
|
||||||
|
public abstract partial class InputTrigger : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Callback to invoke when the associated input has been activated.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="forwardPlayback">Whether gameplay is progressing in the forward direction time-wise.</param>
|
||||||
|
public delegate void OnActivateCallback(bool forwardPlayback);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback to invoke when the associated input has been deactivated.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="forwardPlayback">Whether gameplay is progressing in the forward direction time-wise.</param>
|
||||||
|
public delegate void OnDeactivateCallback(bool forwardPlayback);
|
||||||
|
|
||||||
|
public event OnActivateCallback? OnActivate;
|
||||||
|
public event OnDeactivateCallback? OnDeactivate;
|
||||||
|
|
||||||
|
protected InputTrigger(string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Activate(bool forwardPlayback = true) => OnActivate?.Invoke(forwardPlayback);
|
||||||
|
|
||||||
|
protected void Deactivate(bool forwardPlayback = true) => OnDeactivate?.Invoke(forwardPlayback);
|
||||||
|
}
|
||||||
|
}
|
98
osu.Game/Screens/Play/HUD/KeyCounter.cs
Normal file
98
osu.Game/Screens/Play/HUD/KeyCounter.cs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play.HUD
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An individual key display which is intended to be displayed within a <see cref="KeyCounterDisplay"/>.
|
||||||
|
/// </summary>
|
||||||
|
public abstract partial class KeyCounter : Container
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="InputTrigger"/> which activates and deactivates this <see cref="KeyCounter"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly InputTrigger Trigger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the actions reported by <see cref="Trigger"/> should be counted.
|
||||||
|
/// </summary>
|
||||||
|
public Bindable<bool> IsCounting { get; } = new BindableBool(true);
|
||||||
|
|
||||||
|
private readonly Bindable<int> countPresses = new BindableInt
|
||||||
|
{
|
||||||
|
MinValue = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current count of registered key presses.
|
||||||
|
/// </summary>
|
||||||
|
public IBindable<int> CountPresses => countPresses;
|
||||||
|
|
||||||
|
private readonly Container content;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => content;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this <see cref="KeyCounter"/> is currently in the "activated" state because the associated key is currently pressed.
|
||||||
|
/// </summary>
|
||||||
|
protected readonly Bindable<bool> IsActive = new BindableBool();
|
||||||
|
|
||||||
|
protected KeyCounter(InputTrigger trigger)
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
content = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
Trigger = trigger,
|
||||||
|
};
|
||||||
|
|
||||||
|
Trigger.OnActivate += Activate;
|
||||||
|
Trigger.OnDeactivate += Deactivate;
|
||||||
|
|
||||||
|
Name = trigger.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void increment()
|
||||||
|
{
|
||||||
|
if (!IsCounting.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
countPresses.Value++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decrement()
|
||||||
|
{
|
||||||
|
if (!IsCounting.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
countPresses.Value--;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Activate(bool forwardPlayback = true)
|
||||||
|
{
|
||||||
|
IsActive.Value = true;
|
||||||
|
if (forwardPlayback)
|
||||||
|
increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Deactivate(bool forwardPlayback = true)
|
||||||
|
{
|
||||||
|
IsActive.Value = false;
|
||||||
|
if (!forwardPlayback)
|
||||||
|
decrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
Trigger.OnActivate -= Activate;
|
||||||
|
Trigger.OnDeactivate -= Deactivate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,16 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play.HUD
|
||||||
{
|
{
|
||||||
public partial class KeyCounterAction<T> : KeyCounter
|
public partial class KeyCounterActionTrigger<T> : InputTrigger
|
||||||
where T : struct
|
where T : struct
|
||||||
{
|
{
|
||||||
public T Action { get; }
|
public T Action { get; }
|
||||||
|
|
||||||
public KeyCounterAction(T action)
|
public KeyCounterActionTrigger(T action)
|
||||||
: base($"B{(int)(object)action + 1}")
|
: base($"B{(int)(object)action + 1}")
|
||||||
{
|
{
|
||||||
Action = action;
|
Action = action;
|
||||||
@ -23,9 +21,7 @@ namespace osu.Game.Screens.Play
|
|||||||
if (!EqualityComparer<T>.Default.Equals(action, Action))
|
if (!EqualityComparer<T>.Default.Equals(action, Action))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
IsLit = true;
|
Activate(forwards);
|
||||||
if (forwards)
|
|
||||||
Increment();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,9 +30,7 @@ namespace osu.Game.Screens.Play
|
|||||||
if (!EqualityComparer<T>.Default.Equals(action, Action))
|
if (!EqualityComparer<T>.Default.Equals(action, Action))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
IsLit = false;
|
Deactivate(forwards);
|
||||||
if (!forwards)
|
|
||||||
Decrement();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
109
osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs
Normal file
109
osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play.HUD
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A flowing display of all gameplay keys. Individual keys can be added using <see cref="InputTrigger"/> implementations.
|
||||||
|
/// </summary>
|
||||||
|
public abstract partial class KeyCounterDisplay : CompositeDrawable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the key counter should be visible regardless of the configuration value.
|
||||||
|
/// This is true by default, but can be changed.
|
||||||
|
/// </summary>
|
||||||
|
public Bindable<bool> AlwaysVisible { get; } = new Bindable<bool>(true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="KeyCounter"/>s contained in this <see cref="KeyCounterDisplay"/>.
|
||||||
|
/// </summary>
|
||||||
|
public abstract IEnumerable<KeyCounter> Counters { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the actions reported by all <see cref="InputTrigger"/>s within this <see cref="KeyCounterDisplay"/> should be counted.
|
||||||
|
/// </summary>
|
||||||
|
public Bindable<bool> IsCounting { get; } = new BindableBool(true);
|
||||||
|
|
||||||
|
protected readonly Bindable<bool> ConfigVisibility = new Bindable<bool>();
|
||||||
|
|
||||||
|
protected abstract void UpdateVisibility();
|
||||||
|
|
||||||
|
private Receptor? receptor;
|
||||||
|
|
||||||
|
public void SetReceptor(Receptor receptor)
|
||||||
|
{
|
||||||
|
if (this.receptor != null)
|
||||||
|
throw new InvalidOperationException("Cannot set a new receptor when one is already active");
|
||||||
|
|
||||||
|
this.receptor = receptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a <see cref="InputTrigger"/> to this display.
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Add(InputTrigger trigger);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a range of <see cref="InputTrigger"/> to this display.
|
||||||
|
/// </summary>
|
||||||
|
public void AddRange(IEnumerable<InputTrigger> triggers) => triggers.ForEach(Add);
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
config.BindWith(OsuSetting.KeyOverlay, ConfigVisibility);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
AlwaysVisible.BindValueChanged(_ => UpdateVisibility());
|
||||||
|
ConfigVisibility.BindValueChanged(_ => UpdateVisibility(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool HandleNonPositionalInput => receptor == null;
|
||||||
|
|
||||||
|
public override bool HandlePositionalInput => receptor == null;
|
||||||
|
|
||||||
|
public partial class Receptor : Drawable
|
||||||
|
{
|
||||||
|
protected readonly KeyCounterDisplay Target;
|
||||||
|
|
||||||
|
public Receptor(KeyCounterDisplay target)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Depth = float.MinValue;
|
||||||
|
Target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||||
|
|
||||||
|
protected override bool Handle(UIEvent e)
|
||||||
|
{
|
||||||
|
switch (e)
|
||||||
|
{
|
||||||
|
case KeyDownEvent:
|
||||||
|
case KeyUpEvent:
|
||||||
|
case MouseDownEvent:
|
||||||
|
case MouseUpEvent:
|
||||||
|
return Target.InternalChildren.Any(c => c.TriggerEvent(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Handle(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,16 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play.HUD
|
||||||
{
|
{
|
||||||
public partial class KeyCounterKeyboard : KeyCounter
|
public partial class KeyCounterKeyboardTrigger : InputTrigger
|
||||||
{
|
{
|
||||||
public Key Key { get; }
|
public Key Key { get; }
|
||||||
|
|
||||||
public KeyCounterKeyboard(Key key)
|
public KeyCounterKeyboardTrigger(Key key)
|
||||||
: base(key.ToString())
|
: base(key.ToString())
|
||||||
{
|
{
|
||||||
Key = key;
|
Key = key;
|
||||||
@ -22,8 +20,7 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
if (e.Key == Key)
|
if (e.Key == Key)
|
||||||
{
|
{
|
||||||
IsLit = true;
|
Activate();
|
||||||
Increment();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.OnKeyDown(e);
|
return base.OnKeyDown(e);
|
||||||
@ -31,7 +28,9 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
protected override void OnKeyUp(KeyUpEvent e)
|
protected override void OnKeyUp(KeyUpEvent e)
|
||||||
{
|
{
|
||||||
if (e.Key == Key) IsLit = false;
|
if (e.Key == Key)
|
||||||
|
Deactivate();
|
||||||
|
|
||||||
base.OnKeyUp(e);
|
base.OnKeyUp(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user