mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 08:27:49 +08:00
Merge branch 'master' into taiko-beat-snap-grid
This commit is contained in:
commit
52c2eb93de
69
.github/workflows/diffcalc.yml
vendored
69
.github/workflows/diffcalc.yml
vendored
@ -14,8 +14,8 @@
|
||||
#
|
||||
# The workflow can be run in two ways:
|
||||
# 1. Via workflow dispatch.
|
||||
# 2. By an owner of the repository posting a pull request or issue comment containing `!diffcalc`.
|
||||
# For pull requests, the workflow will assume the pull request as the target to compare against (i.e. the `OSU_B` variable).
|
||||
# 2. By an owner of the repository posting a pull request or issue comment containing `!diffcalc`.
|
||||
# For pull requests, the workflow will assume the pull request as the target to compare against (i.e. the `OSU_B` variable).
|
||||
# Any lines in the comment of the form `KEY=VALUE` are treated as variables for the generator.
|
||||
#
|
||||
# ## Google Service Account
|
||||
@ -104,24 +104,25 @@ env:
|
||||
COMMENT_TAG: execution-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
|
||||
|
||||
jobs:
|
||||
wait-for-queue:
|
||||
name: "Wait for previous workflows"
|
||||
check-permissions:
|
||||
name: Check permissions
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !cancelled() && (github.event_name == 'workflow_dispatch' || contains(github.event.comment.body, '!diffcalc') && github.event.comment.author_association == 'OWNER') }}
|
||||
timeout-minutes: 50400 # 35 days, the maximum for jobs.
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || contains(github.event.comment.body, '!diffcalc') }}
|
||||
steps:
|
||||
- uses: ahmadnassri/action-workflow-queue@v1
|
||||
- name: Check permissions
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
uses: actions-cool/check-user-permission@a0668c9aec87f3875fc56170b6452a453e9dd819 # v2.2.0
|
||||
with:
|
||||
timeout: 2147483647 # Around 24 days, maximum supported.
|
||||
delay: 120000 # Poll every 2 minutes. API seems fairly low on this one.
|
||||
require: 'write'
|
||||
|
||||
create-comment:
|
||||
name: Create PR comment
|
||||
needs: check-permissions
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '!diffcalc') && github.event.comment.author_association == 'OWNER' }}
|
||||
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }}
|
||||
steps:
|
||||
- name: Create comment
|
||||
uses: thollander/actions-comment-pull-request@v2
|
||||
uses: thollander/actions-comment-pull-request@363c6f6eae92cc5c3a66e95ba016fc771bb38943 # v2.4.2
|
||||
with:
|
||||
comment_tag: ${{ env.COMMENT_TAG }}
|
||||
message: |
|
||||
@ -129,11 +130,21 @@ jobs:
|
||||
|
||||
*This comment will update on completion*
|
||||
|
||||
wait-for-queue:
|
||||
name: "Wait for previous workflows"
|
||||
needs: check-permissions
|
||||
runs-on: self-hosted
|
||||
timeout-minutes: 50400 # 35 days, the maximum for jobs on self-hosted runners.
|
||||
steps:
|
||||
- uses: ahmadnassri/action-workflow-queue@f547ac848c16a9bb1b2ed4a850e0cc5098af2cf8 # v1.1.5
|
||||
with:
|
||||
timeout: 2147483647 # Around 24 days - the maximum supported by JS setTimeout().
|
||||
delay: 120000 # Poll every 2 minutes - the API limit seems fairly low on this one.
|
||||
|
||||
directory:
|
||||
name: Prepare directory
|
||||
needs: wait-for-queue
|
||||
runs-on: self-hosted
|
||||
if: ${{ !cancelled() && (github.event_name == 'workflow_dispatch' || contains(github.event.comment.body, '!diffcalc') && github.event.comment.author_association == 'OWNER') }}
|
||||
outputs:
|
||||
GENERATOR_DIR: ${{ steps.set-outputs.outputs.GENERATOR_DIR }}
|
||||
GENERATOR_ENV: ${{ steps.set-outputs.outputs.GENERATOR_ENV }}
|
||||
@ -159,7 +170,6 @@ jobs:
|
||||
name: Setup environment
|
||||
needs: directory
|
||||
runs-on: self-hosted
|
||||
if: ${{ !cancelled() && needs.directory.result == 'success' }}
|
||||
env:
|
||||
VARS_JSON: ${{ toJSON(vars) }}
|
||||
steps:
|
||||
@ -185,7 +195,7 @@ jobs:
|
||||
- name: Add pull-request environment
|
||||
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }}
|
||||
run: |
|
||||
sed -i "s;^OSU_B=.*$;OSU_B=${{ github.event.issue.pull_request.url }};" "${{ needs.directory.outputs.GENERATOR_ENV }}"
|
||||
sed -i "s;^OSU_B=.*$;OSU_B=${{ github.event.issue.pull_request.html_url }};" "${{ needs.directory.outputs.GENERATOR_ENV }}"
|
||||
|
||||
- name: Add comment environment
|
||||
if: ${{ github.event_name == 'issue_comment' }}
|
||||
@ -239,7 +249,6 @@ jobs:
|
||||
name: Setup scores
|
||||
needs: [ directory, environment ]
|
||||
runs-on: self-hosted
|
||||
if: ${{ !cancelled() && needs.environment.result == 'success' }}
|
||||
steps:
|
||||
- name: Query latest data
|
||||
id: query
|
||||
@ -252,7 +261,7 @@ jobs:
|
||||
|
||||
- name: Restore cache
|
||||
id: restore-cache
|
||||
uses: maxnowack/local-cache@v1
|
||||
uses: maxnowack/local-cache@038cc090b52e4f205fbc468bf5b0756df6f68775 # v1
|
||||
with:
|
||||
path: ${{ steps.query.outputs.DATA_NAME }}.tar.bz2
|
||||
key: ${{ steps.query.outputs.DATA_NAME }}
|
||||
@ -272,7 +281,6 @@ jobs:
|
||||
name: Setup beatmaps
|
||||
needs: directory
|
||||
runs-on: self-hosted
|
||||
if: ${{ !cancelled() && needs.directory.result == 'success' }}
|
||||
steps:
|
||||
- name: Query latest data
|
||||
id: query
|
||||
@ -284,7 +292,7 @@ jobs:
|
||||
|
||||
- name: Restore cache
|
||||
id: restore-cache
|
||||
uses: maxnowack/local-cache@v1
|
||||
uses: maxnowack/local-cache@038cc090b52e4f205fbc468bf5b0756df6f68775 # v1
|
||||
with:
|
||||
path: ${{ steps.query.outputs.DATA_NAME }}.tar.bz2
|
||||
key: ${{ steps.query.outputs.DATA_NAME }}
|
||||
@ -305,7 +313,6 @@ jobs:
|
||||
needs: [ directory, environment, scores, beatmaps ]
|
||||
runs-on: self-hosted
|
||||
timeout-minutes: 720
|
||||
if: ${{ !cancelled() && needs.scores.result == 'success' && needs.beatmaps.result == 'success' }}
|
||||
outputs:
|
||||
TARGET: ${{ steps.run.outputs.TARGET }}
|
||||
SPREADSHEET_LINK: ${{ steps.run.outputs.SPREADSHEET_LINK }}
|
||||
@ -331,21 +338,25 @@ jobs:
|
||||
cd "${{ needs.directory.outputs.GENERATOR_DIR }}"
|
||||
docker-compose down
|
||||
|
||||
output-cli:
|
||||
name: Output info
|
||||
needs: generator
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Output info
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
echo "Target: ${{ steps.run.outputs.TARGET }}"
|
||||
echo "Spreadsheet: ${{ steps.run.outputs.SPREADSHEET_LINK }}"
|
||||
echo "Target: ${{ needs.generator.outputs.TARGET }}"
|
||||
echo "Spreadsheet: ${{ needs.generator.outputs.SPREADSHEET_LINK }}"
|
||||
|
||||
update-comment:
|
||||
name: Update PR comment
|
||||
needs: [ create-comment, generator ]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '!diffcalc') && github.event.comment.author_association == 'OWNER' }}
|
||||
if: ${{ always() && needs.create-comment.result == 'success' }}
|
||||
steps:
|
||||
- name: Update comment on success
|
||||
if: ${{ needs.generator.result == 'success' }}
|
||||
uses: thollander/actions-comment-pull-request@v2
|
||||
uses: thollander/actions-comment-pull-request@363c6f6eae92cc5c3a66e95ba016fc771bb38943 # v2.4.2
|
||||
with:
|
||||
comment_tag: ${{ env.COMMENT_TAG }}
|
||||
mode: upsert
|
||||
@ -356,10 +367,18 @@ jobs:
|
||||
|
||||
- name: Update comment on failure
|
||||
if: ${{ needs.generator.result == 'failure' }}
|
||||
uses: thollander/actions-comment-pull-request@v2
|
||||
uses: thollander/actions-comment-pull-request@363c6f6eae92cc5c3a66e95ba016fc771bb38943 # v2.4.2
|
||||
with:
|
||||
comment_tag: ${{ env.COMMENT_TAG }}
|
||||
mode: upsert
|
||||
create_if_not_exists: false
|
||||
message: |
|
||||
Difficulty calculation failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
- name: Update comment on cancellation
|
||||
if: ${{ needs.generator.result == 'cancelled' }}
|
||||
uses: thollander/actions-comment-pull-request@363c6f6eae92cc5c3a66e95ba016fc771bb38943 # v2.4.2
|
||||
with:
|
||||
comment_tag: ${{ env.COMMENT_TAG }}
|
||||
mode: delete
|
||||
message: '.' # Appears to be required by this action for non-error status code.
|
||||
|
@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
private float getCaughtObjectPosition(Fruit fruit)
|
||||
{
|
||||
var caughtObject = catcher.ChildrenOfType<CaughtObject>().Single(c => c.HitObject == fruit);
|
||||
return caughtObject.Parent.ToSpaceOfOtherDrawable(caughtObject.Position, catcher).X;
|
||||
return caughtObject.Parent!.ToSpaceOfOtherDrawable(caughtObject.Position, catcher).X;
|
||||
}
|
||||
|
||||
private void catchFruit(Fruit fruit, float x)
|
||||
|
@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Catch
|
||||
new KeyBinding(InputKey.X, CatchAction.MoveRight),
|
||||
new KeyBinding(InputKey.Right, CatchAction.MoveRight),
|
||||
new KeyBinding(InputKey.Shift, CatchAction.Dash),
|
||||
new KeyBinding(InputKey.Shift, CatchAction.Dash),
|
||||
new KeyBinding(InputKey.MouseLeft, CatchAction.Dash),
|
||||
};
|
||||
|
||||
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
||||
|
26
osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs
Normal file
26
osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Edit
|
||||
{
|
||||
public partial class CatchDistanceSnapProvider : ComposerDistanceSnapProvider
|
||||
{
|
||||
protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
|
||||
{
|
||||
// osu!catch's distance snap implementation is limited, in that a custom spacing cannot be specified.
|
||||
// Therefore this functionality is not currently used.
|
||||
//
|
||||
// The implementation below is probably correct but should be checked if/when exposed via controls.
|
||||
|
||||
float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
|
||||
float actualDistance = Math.Abs(((CatchHitObject)before).EffectiveX - ((CatchHitObject)after).EffectiveX);
|
||||
|
||||
return actualDistance / expectedDistance;
|
||||
}
|
||||
}
|
||||
}
|
@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
{
|
||||
base.Update();
|
||||
|
||||
Scale = new Vector2(Math.Min(Parent.ChildSize.X / CatchPlayfield.WIDTH, Parent.ChildSize.Y / CatchPlayfield.HEIGHT));
|
||||
Scale = new Vector2(Math.Min(Parent!.ChildSize.X / CatchPlayfield.WIDTH, Parent!.ChildSize.Y / CatchPlayfield.HEIGHT));
|
||||
Height = 1 / Scale.Y;
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@ -19,13 +19,13 @@ using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Edit
|
||||
{
|
||||
// we're also a ScrollingHitObjectComposer candidate, but can't be everything can we?
|
||||
public partial class CatchHitObjectComposer : DistancedHitObjectComposer<CatchHitObject>
|
||||
public partial class CatchHitObjectComposer : ScrollingHitObjectComposer<CatchHitObject>, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
private const float distance_snap_radius = 50;
|
||||
|
||||
@ -37,6 +37,11 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
MaxValue = 10,
|
||||
};
|
||||
|
||||
[Cached(typeof(IDistanceSnapProvider))]
|
||||
protected readonly CatchDistanceSnapProvider DistanceSnapProvider = new CatchDistanceSnapProvider();
|
||||
|
||||
private BeatSnapGrid beatSnapGrid = null!;
|
||||
|
||||
public CatchHitObjectComposer(CatchRuleset ruleset)
|
||||
: base(ruleset)
|
||||
{
|
||||
@ -45,8 +50,11 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddInternal(DistanceSnapProvider);
|
||||
DistanceSnapProvider.AttachToToolbox(RightToolbox);
|
||||
|
||||
// todo: enable distance spacing once catch supports applying it to its existing distance snap grid implementation.
|
||||
DistanceSpacingMultiplier.Disabled = true;
|
||||
DistanceSnapProvider.DistanceSpacingMultiplier.Disabled = true;
|
||||
|
||||
LayerBelowRuleset.Add(new PlayfieldBorder
|
||||
{
|
||||
@ -63,19 +71,35 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
Catcher.BASE_DASH_SPEED, -Catcher.BASE_DASH_SPEED,
|
||||
Catcher.BASE_WALK_SPEED, -Catcher.BASE_WALK_SPEED,
|
||||
}));
|
||||
|
||||
AddInternal(beatSnapGrid = new CatchBeatSnapGrid());
|
||||
}
|
||||
|
||||
protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
|
||||
protected override IEnumerable<TernaryButton> CreateTernaryButtons()
|
||||
=> base.CreateTernaryButtons()
|
||||
.Concat(DistanceSnapProvider.CreateTernaryButtons());
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
// osu!catch's distance snap implementation is limited, in that a custom spacing cannot be specified.
|
||||
// Therefore this functionality is not currently used.
|
||||
//
|
||||
// The implementation below is probably correct but should be checked if/when exposed via controls.
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
|
||||
float actualDistance = Math.Abs(((CatchHitObject)before).EffectiveX - ((CatchHitObject)after).EffectiveX);
|
||||
|
||||
return actualDistance / expectedDistance;
|
||||
if (BlueprintContainer.CurrentTool is SelectTool)
|
||||
{
|
||||
if (EditorBeatmap.SelectedHitObjects.Any())
|
||||
{
|
||||
beatSnapGrid.SelectionTimeRange = (EditorBeatmap.SelectedHitObjects.Min(h => h.StartTime), EditorBeatmap.SelectedHitObjects.Max(h => h.GetEndTime()));
|
||||
}
|
||||
else
|
||||
beatSnapGrid.SelectionTimeRange = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = FindSnappedPositionAndTime(InputManager.CurrentState.Mouse.Position);
|
||||
if (result.Time is double time)
|
||||
beatSnapGrid.SelectionTimeRange = (time, time);
|
||||
else
|
||||
beatSnapGrid.SelectionTimeRange = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -85,7 +109,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
updateDistanceSnapGrid();
|
||||
}
|
||||
|
||||
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
@ -94,14 +118,18 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
// May be worth considering standardising "zoom" behaviour with what the timeline uses (ie. alt-wheel) but that may cause new conflicts.
|
||||
case GlobalAction.IncreaseScrollSpeed:
|
||||
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value - 1, 200, Easing.OutQuint);
|
||||
break;
|
||||
return true;
|
||||
|
||||
case GlobalAction.DecreaseScrollSpeed:
|
||||
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value + 1, 200, Easing.OutQuint);
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnPressed(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||
{
|
||||
}
|
||||
|
||||
protected override DrawableRuleset<CatchHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods) =>
|
||||
@ -189,7 +217,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
|
||||
private void updateDistanceSnapGrid()
|
||||
{
|
||||
if (DistanceSnapToggle.Value != TernaryState.True)
|
||||
if (DistanceSnapProvider.DistanceSnapToggle.Value != TernaryState.True)
|
||||
{
|
||||
distanceSnapGrid.Hide();
|
||||
return;
|
||||
|
@ -44,8 +44,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
|
||||
if (Column != null)
|
||||
{
|
||||
headPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.StartTime)).Y;
|
||||
tailPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.EndTime)).Y;
|
||||
headPiece.Y = Parent!.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.StartTime)).Y;
|
||||
tailPiece.Y = Parent!.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.EndTime)).Y;
|
||||
|
||||
switch (scrollingInfo.Direction.Value)
|
||||
{
|
||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
foreach (var child in InternalChildren)
|
||||
child.Anchor = child.Origin = anchor;
|
||||
|
||||
Position = Parent.ToLocalSpace(HitObjectContainer.ScreenSpacePositionAtTime(HitObject.StartTime)) - AnchorPosition;
|
||||
Position = Parent!.ToLocalSpace(HitObjectContainer.ScreenSpacePositionAtTime(HitObject.StartTime)) - AnchorPosition;
|
||||
Width = HitObjectContainer.DrawWidth;
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
foreach (Column column in maniaPlayfield.Stages.SelectMany(stage => stage.Columns))
|
||||
{
|
||||
HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer;
|
||||
Container hocParent = (Container)hoc.Parent;
|
||||
Container hocParent = (Container)hoc.Parent!;
|
||||
|
||||
hocParent.Remove(hoc, false);
|
||||
hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c =>
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
});
|
||||
|
||||
moveMouseToHitObject(1);
|
||||
AddAssert("merge option available", () => selectionHandler.ContextMenuItems.Any(o => o.Text.Value == "Merge selection"));
|
||||
AddAssert("merge option available", () => selectionHandler.ContextMenuItems?.Any(o => o.Text.Value == "Merge selection") == true);
|
||||
|
||||
mergeSelection();
|
||||
|
||||
@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
});
|
||||
|
||||
moveMouseToHitObject(1);
|
||||
AddAssert("merge option not available", () => selectionHandler.ContextMenuItems.Length > 0 && selectionHandler.ContextMenuItems.All(o => o.Text.Value != "Merge selection"));
|
||||
AddAssert("merge option not available", () => selectionHandler.ContextMenuItems?.Length > 0 && selectionHandler.ContextMenuItems.All(o => o.Text.Value != "Merge selection"));
|
||||
mergeSelection();
|
||||
AddAssert("circles not merged", () => circle1 is not null && circle2 is not null
|
||||
&& EditorBeatmap.HitObjects.Contains(circle1) && EditorBeatmap.HitObjects.Contains(circle2));
|
||||
@ -222,7 +222,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
});
|
||||
|
||||
moveMouseToHitObject(1);
|
||||
AddAssert("merge option available", () => selectionHandler.ContextMenuItems.Any(o => o.Text.Value == "Merge selection"));
|
||||
AddAssert("merge option available", () => selectionHandler.ContextMenuItems?.Any(o => o.Text.Value == "Merge selection") == true);
|
||||
|
||||
mergeSelection();
|
||||
|
||||
|
@ -47,8 +47,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
[Cached]
|
||||
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
|
||||
|
||||
[Cached(typeof(IDistanceSnapProvider))]
|
||||
private readonly OsuHitObjectComposer snapProvider = new OsuHitObjectComposer(new OsuRuleset())
|
||||
private readonly TestHitObjectComposer composer = new TestHitObjectComposer
|
||||
{
|
||||
// Just used for the snap implementation, so let's hide from vision.
|
||||
AlwaysPresent = true,
|
||||
@ -71,11 +70,18 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
base.Content.Children = new Drawable[]
|
||||
{
|
||||
editorClock = new EditorClock(editorBeatmap),
|
||||
new PopoverContainer { Child = snapProvider },
|
||||
new PopoverContainer { Child = composer },
|
||||
Content
|
||||
};
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
dependencies.CacheAs(composer.DistanceSnapProvider);
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
protected override Container<Drawable> Content { get; } = new PopoverContainer { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
[SetUp]
|
||||
@ -84,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
editorBeatmap.Difficulty.SliderMultiplier = 1;
|
||||
editorBeatmap.ControlPointInfo.Clear();
|
||||
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
|
||||
snapProvider.DistanceSpacingMultiplier.Value = 1;
|
||||
composer.DistanceSnapProvider.DistanceSpacingMultiplier.Value = 1;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -116,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
[TestCase(0.5f)]
|
||||
public void TestDistanceSpacing(float multiplier)
|
||||
{
|
||||
AddStep($"set distance spacing = {multiplier}", () => snapProvider.DistanceSpacingMultiplier.Value = multiplier);
|
||||
AddStep($"set distance spacing = {multiplier}", () => composer.DistanceSnapProvider.DistanceSpacingMultiplier.Value = multiplier);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -153,7 +159,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
[TestCase(2f, beat_length * 2)]
|
||||
public void TestDistanceSpacingAdjust(float multiplier, float expectedDistance)
|
||||
{
|
||||
AddStep($"Set distance spacing to {multiplier}", () => snapProvider.DistanceSpacingMultiplier.Value = multiplier);
|
||||
AddStep($"Set distance spacing to {multiplier}", () => composer.DistanceSnapProvider.DistanceSpacingMultiplier.Value = multiplier);
|
||||
AddStep("move mouse to point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2(beat_length, 0) * 2)));
|
||||
|
||||
assertSnappedDistance(expectedDistance);
|
||||
@ -266,5 +272,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
cursor.Position = LastSnappedPosition = GetSnapPosition.Invoke(inputManager.CurrentState.Mouse.Position);
|
||||
}
|
||||
}
|
||||
|
||||
private partial class TestHitObjectComposer : OsuHitObjectComposer
|
||||
{
|
||||
public new IDistanceSnapProvider DistanceSnapProvider => base.DistanceSnapProvider;
|
||||
|
||||
public TestHitObjectComposer()
|
||||
: base(new OsuRuleset())
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddStep($"move mouse to control point {index}", () =>
|
||||
{
|
||||
Vector2 position = slider.Path.ControlPoints[index].Position;
|
||||
InputManager.MoveMouseTo(visualiser.Pieces[0].Parent.ToScreenSpace(position));
|
||||
InputManager.MoveMouseTo(visualiser.Pieces[0].Parent!.ToScreenSpace(position));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddStep($"move mouse to {relativePosition}", () =>
|
||||
{
|
||||
Vector2 position = slider.Position + relativePosition;
|
||||
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
||||
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
|
||||
});
|
||||
|
||||
[Test]
|
||||
@ -331,7 +331,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddStep($"move mouse to {relativePosition}", () =>
|
||||
{
|
||||
Vector2 position = slider.Position + relativePosition;
|
||||
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
||||
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
|
||||
});
|
||||
}
|
||||
|
||||
@ -340,7 +340,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddStep($"move mouse to control point {index}", () =>
|
||||
{
|
||||
Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position;
|
||||
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
||||
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -187,7 +187,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddStep($"move mouse to control point {index}", () =>
|
||||
{
|
||||
Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position;
|
||||
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
||||
InputManager.MoveMouseTo(drawableObject.Parent!.ToScreenSpace(position));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
{
|
||||
var firstPiece = this.ChildrenOfType<PathControlPointPiece<Slider>>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]);
|
||||
var pos = slider.Path.PositionAt(0.25d) + slider.Position;
|
||||
InputManager.MoveMouseTo(firstPiece.Parent.ToScreenSpace(pos));
|
||||
InputManager.MoveMouseTo(firstPiece.Parent!.ToScreenSpace(pos));
|
||||
});
|
||||
AddStep("move slider end", () =>
|
||||
{
|
||||
|
@ -231,7 +231,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
if (slider is null || visualiser is null) return;
|
||||
|
||||
Vector2 position = slider.Path.ControlPoints[index].Position + slider.Position;
|
||||
InputManager.MoveMouseTo(visualiser.Pieces[0].Parent.ToScreenSpace(position));
|
||||
InputManager.MoveMouseTo(visualiser.Pieces[0].Parent!.ToScreenSpace(position));
|
||||
});
|
||||
}
|
||||
|
||||
@ -241,7 +241,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
{
|
||||
if (visualiser is null) return;
|
||||
|
||||
MenuItem? item = visualiser.ContextMenuItems.FirstOrDefault(menuItem => menuItem.Text.Value == contextMenuText);
|
||||
MenuItem? item = visualiser.ContextMenuItems?.FirstOrDefault(menuItem => menuItem.Text.Value == contextMenuText);
|
||||
|
||||
item?.Action.Value?.Invoke();
|
||||
});
|
||||
|
@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
pool = pools[poolIndex];
|
||||
|
||||
// We need to make sure neither the pool nor the judgement get disposed when new content is set, and they both share the same parent.
|
||||
((Container)pool.Parent).Clear(false);
|
||||
((Container)pool.Parent!).Clear(false);
|
||||
}
|
||||
|
||||
var container = new Container
|
||||
|
@ -27,6 +27,7 @@ using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
@ -50,6 +51,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
snakingOut.Value = !v;
|
||||
});
|
||||
|
||||
AddToggleStep("toggle hidden", hiddenActive => SelectedMods.Value = hiddenActive ? new[] { new OsuModHidden() } : Array.Empty<Mod>());
|
||||
|
||||
AddSliderStep("hit at", 0f, 1f, 0f, v =>
|
||||
{
|
||||
progressToHit = v;
|
||||
|
@ -47,7 +47,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
public Action<List<PathControlPoint>> SplitControlPointsRequested;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IDistanceSnapProvider snapProvider { get; set; }
|
||||
private IPositionSnapProvider positionSnapProvider { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IDistanceSnapProvider distanceSnapProvider { get; set; }
|
||||
|
||||
public PathControlPointVisualiser(T hitObject, bool allowSelection)
|
||||
{
|
||||
@ -288,10 +291,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
if (selectedControlPoints.Contains(hitObject.Path.ControlPoints[0]))
|
||||
{
|
||||
// Special handling for selections containing head control point - the position of the hit object changes which means the snapped position and time have to be taken into account
|
||||
Vector2 newHeadPosition = Parent.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex]));
|
||||
var result = snapProvider?.FindSnappedPositionAndTime(newHeadPosition);
|
||||
Vector2 newHeadPosition = Parent!.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex]));
|
||||
var result = positionSnapProvider?.FindSnappedPositionAndTime(newHeadPosition);
|
||||
|
||||
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - hitObject.Position;
|
||||
Vector2 movementDelta = Parent!.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - hitObject.Position;
|
||||
|
||||
hitObject.Position += movementDelta;
|
||||
hitObject.StartTime = result?.Time ?? hitObject.StartTime;
|
||||
@ -309,9 +312,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = snapProvider?.FindSnappedPositionAndTime(Parent.ToScreenSpace(e.MousePosition), SnapType.GlobalGrids);
|
||||
var result = positionSnapProvider?.FindSnappedPositionAndTime(Parent!.ToScreenSpace(e.MousePosition), SnapType.GlobalGrids);
|
||||
|
||||
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? Parent.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - hitObject.Position;
|
||||
Vector2 movementDelta = Parent!.ToLocalSpace(result?.ScreenSpacePosition ?? Parent!.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - hitObject.Position;
|
||||
|
||||
for (int i = 0; i < controlPoints.Count; ++i)
|
||||
{
|
||||
@ -322,7 +325,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
}
|
||||
|
||||
// Snap the path to the current beat divisor before checking length validity.
|
||||
hitObject.SnapTo(snapProvider);
|
||||
hitObject.SnapTo(distanceSnapProvider);
|
||||
|
||||
if (!hitObject.Path.HasValidLength)
|
||||
{
|
||||
@ -332,7 +335,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
hitObject.Position = oldPosition;
|
||||
hitObject.StartTime = oldStartTime;
|
||||
// Snap the path length again to undo the invalid length.
|
||||
hitObject.SnapTo(snapProvider);
|
||||
hitObject.SnapTo(distanceSnapProvider);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
private int currentSegmentLength;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IDistanceSnapProvider snapProvider { get; set; }
|
||||
private IPositionSnapProvider positionSnapProvider { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IDistanceSnapProvider distanceSnapProvider { get; set; }
|
||||
|
||||
protected override bool IsValidForPlacement => HitObject.Path.HasValidLength;
|
||||
|
||||
@ -198,7 +201,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
}
|
||||
|
||||
// Update the cursor position.
|
||||
var result = snapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.Body ? SnapType.GlobalGrids : SnapType.All);
|
||||
var result = positionSnapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.Body ? SnapType.GlobalGrids : SnapType.All);
|
||||
cursor.Position = ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
||||
}
|
||||
else if (cursor != null)
|
||||
@ -230,7 +233,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
private void updateSlider()
|
||||
{
|
||||
HitObject.Path.ExpectedDistance.Value = snapProvider?.FindSnappedDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
||||
HitObject.Path.ExpectedDistance.Value = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
||||
|
||||
bodyPiece.UpdateFrom(HitObject);
|
||||
headCirclePiece.UpdateFrom(HitObject.HeadCircle);
|
||||
|
@ -40,7 +40,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
protected PathControlPointVisualiser<Slider> ControlPointVisualiser { get; private set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IDistanceSnapProvider snapProvider { get; set; }
|
||||
private IPositionSnapProvider positionSnapProvider { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IDistanceSnapProvider distanceSnapProvider { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IPlacementHandler placementHandler { get; set; }
|
||||
@ -194,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
{
|
||||
if (placementControlPoint != null)
|
||||
{
|
||||
var result = snapProvider?.FindSnappedPositionAndTime(ToScreenSpace(e.MousePosition));
|
||||
var result = positionSnapProvider?.FindSnappedPositionAndTime(ToScreenSpace(e.MousePosition));
|
||||
placementControlPoint.Position = ToLocalSpace(result?.ScreenSpacePosition ?? ToScreenSpace(e.MousePosition)) - HitObject.Position;
|
||||
}
|
||||
}
|
||||
@ -245,7 +248,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
// Move the control points from the insertion index onwards to make room for the insertion
|
||||
controlPoints.Insert(insertionIndex, pathControlPoint);
|
||||
|
||||
HitObject.SnapTo(snapProvider);
|
||||
HitObject.SnapTo(distanceSnapProvider);
|
||||
|
||||
return pathControlPoint;
|
||||
}
|
||||
@ -267,7 +270,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
}
|
||||
|
||||
// Snap the slider to the current beat divisor before checking length validity.
|
||||
HitObject.SnapTo(snapProvider);
|
||||
HitObject.SnapTo(distanceSnapProvider);
|
||||
|
||||
// If there are 0 or 1 remaining control points, or the slider has an invalid length, it is in a degenerate form and should be deleted
|
||||
if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLength)
|
||||
|
31
osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs
Normal file
31
osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public partial class OsuDistanceSnapProvider : ComposerDistanceSnapProvider
|
||||
{
|
||||
protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
|
||||
{
|
||||
float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
|
||||
float actualDistance = Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).Position);
|
||||
|
||||
return actualDistance / expectedDistance;
|
||||
}
|
||||
|
||||
protected override bool AdjustDistanceSpacing(GlobalAction action, float amount)
|
||||
{
|
||||
// To allow better visualisation, ensure that the spacing grid is visible before adjusting.
|
||||
DistanceSnapToggle.Value = TernaryState.True;
|
||||
|
||||
return base.AdjustDistanceSpacing(action, amount);
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,6 @@ using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -30,7 +29,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public partial class OsuHitObjectComposer : DistancedHitObjectComposer<OsuHitObject>
|
||||
public partial class OsuHitObjectComposer : HitObjectComposer<OsuHitObject>
|
||||
{
|
||||
public OsuHitObjectComposer(Ruleset ruleset)
|
||||
: base(ruleset)
|
||||
@ -49,18 +48,27 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
private readonly Bindable<TernaryState> rectangularGridSnapToggle = new Bindable<TernaryState>();
|
||||
|
||||
protected override IEnumerable<TernaryButton> CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
|
||||
{
|
||||
new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Th })
|
||||
});
|
||||
protected override IEnumerable<TernaryButton> CreateTernaryButtons()
|
||||
=> base.CreateTernaryButtons()
|
||||
.Concat(DistanceSnapProvider.CreateTernaryButtons())
|
||||
.Concat(new[]
|
||||
{
|
||||
new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Th })
|
||||
});
|
||||
|
||||
private BindableList<HitObject> selectedHitObjects;
|
||||
|
||||
private Bindable<HitObject> placementObject;
|
||||
|
||||
[Cached(typeof(IDistanceSnapProvider))]
|
||||
protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddInternal(DistanceSnapProvider);
|
||||
DistanceSnapProvider.AttachToToolbox(RightToolbox);
|
||||
|
||||
// Give a bit of breathing room around the playfield content.
|
||||
PlayfieldContentContainer.Padding = new MarginPadding(10);
|
||||
|
||||
@ -81,7 +89,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
placementObject = EditorBeatmap.PlacementObject.GetBoundCopy();
|
||||
placementObject.ValueChanged += _ => updateDistanceSnapGrid();
|
||||
DistanceSnapToggle.ValueChanged += _ => updateDistanceSnapGrid();
|
||||
DistanceSnapProvider.DistanceSnapToggle.ValueChanged += _ => updateDistanceSnapGrid();
|
||||
|
||||
// we may be entering the screen with a selection already active
|
||||
updateDistanceSnapGrid();
|
||||
@ -106,14 +114,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
private RectangularPositionSnapGrid rectangularPositionSnapGrid;
|
||||
|
||||
protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
|
||||
{
|
||||
float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
|
||||
float actualDistance = Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).Position);
|
||||
|
||||
return actualDistance / expectedDistance;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
// We want to ensure that in this particular case, the time-snapping component of distance snap is still applied.
|
||||
// The easiest way to ensure this is to attempt application of distance snap after a nearby object is found, and copy over
|
||||
// the time value if the proposed positions are roughly the same.
|
||||
if (snapType.HasFlagFast(SnapType.RelativeGrids) && DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||
if (snapType.HasFlagFast(SnapType.RelativeGrids) && DistanceSnapProvider.DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||
{
|
||||
(Vector2 distanceSnappedPosition, double distanceSnappedTime) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(snapResult.ScreenSpacePosition));
|
||||
if (Precision.AlmostEquals(distanceSnapGrid.ToScreenSpace(distanceSnappedPosition), snapResult.ScreenSpacePosition, 1))
|
||||
@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
if (snapType.HasFlagFast(SnapType.RelativeGrids))
|
||||
{
|
||||
if (DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||
if (DistanceSnapProvider.DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||
{
|
||||
(Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition));
|
||||
|
||||
@ -220,7 +220,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
distanceSnapGridCache.Invalidate();
|
||||
distanceSnapGrid = null;
|
||||
|
||||
if (DistanceSnapToggle.Value != TernaryState.True)
|
||||
if (DistanceSnapProvider.DistanceSnapToggle.Value != TernaryState.True)
|
||||
return;
|
||||
|
||||
switch (BlueprintContainer.CurrentTool)
|
||||
@ -262,14 +262,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
base.OnKeyUp(e);
|
||||
}
|
||||
|
||||
protected override bool AdjustDistanceSpacing(GlobalAction action, float amount)
|
||||
{
|
||||
// To allow better visualisation, ensure that the spacing grid is visible before adjusting.
|
||||
DistanceSnapToggle.Value = TernaryState.True;
|
||||
|
||||
return base.AdjustDistanceSpacing(action, amount);
|
||||
}
|
||||
|
||||
private bool gridSnapMomentary;
|
||||
|
||||
private void handleToggleViaKey(KeyboardEvent key)
|
||||
|
@ -139,12 +139,12 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
if (Precision.AlmostEquals(restrictTo.Rotation, 0))
|
||||
{
|
||||
start = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopLeft).X;
|
||||
end = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopRight).X;
|
||||
start = Parent!.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopLeft).X;
|
||||
end = Parent!.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopRight).X;
|
||||
}
|
||||
else
|
||||
{
|
||||
float center = restrictTo.ToSpaceOfOtherDrawable(restrictTo.OriginPosition, Parent).X;
|
||||
float center = restrictTo.ToSpaceOfOtherDrawable(restrictTo.OriginPosition, Parent!).X;
|
||||
float halfDiagonal = (restrictTo.DrawSize / 2).LengthFast;
|
||||
|
||||
start = center - halfDiagonal;
|
||||
|
@ -98,6 +98,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
// only apply to circle piece – reverse arrow is not affected by hidden.
|
||||
sliderRepeat.CirclePiece.FadeOut(fadeDuration);
|
||||
|
||||
using (drawableObject.BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||
sliderRepeat.FadeOut();
|
||||
|
||||
break;
|
||||
|
||||
case DrawableHitCircle circle:
|
||||
|
@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
/// </summary>
|
||||
public void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult);
|
||||
|
||||
private RectangleF parentScreenSpaceRectangle => ((DrawableOsuHitObject)ParentHitObject)?.parentScreenSpaceRectangle ?? Parent.ScreenSpaceDrawQuad.AABBFloat;
|
||||
private RectangleF parentScreenSpaceRectangle => ((DrawableOsuHitObject)ParentHitObject)?.parentScreenSpaceRectangle ?? Parent!.ScreenSpaceDrawQuad.AABBFloat;
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the position of the given <paramref name="drawable"/> relative to the playfield area.
|
||||
|
@ -118,11 +118,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
case ArmedState.Hit:
|
||||
this.FadeOut(animDuration, Easing.Out);
|
||||
|
||||
const float final_scale = 1.5f;
|
||||
|
||||
Arrow.ScaleTo(Scale * final_scale, animDuration, Easing.Out);
|
||||
CirclePiece.ScaleTo(Scale * final_scale, animDuration, Easing.Out);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
@ -12,6 +13,7 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -19,8 +21,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
{
|
||||
public partial class ArgonReverseArrow : CompositeDrawable
|
||||
{
|
||||
[Resolved]
|
||||
private DrawableHitObject drawableObject { get; set; } = null!;
|
||||
private DrawableSliderRepeat drawableRepeat { get; set; } = null!;
|
||||
|
||||
private Bindable<Color4> accentColour = null!;
|
||||
|
||||
@ -29,8 +30,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
private Sprite side = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(TextureStore textures)
|
||||
private void load(DrawableHitObject drawableObject, TextureStore textures)
|
||||
{
|
||||
drawableRepeat = (DrawableSliderRepeat)drawableObject;
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
@ -70,10 +73,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
}
|
||||
};
|
||||
|
||||
accentColour = drawableObject.AccentColour.GetBoundCopy();
|
||||
accentColour = drawableRepeat.AccentColour.GetBoundCopy();
|
||||
accentColour.BindValueChanged(accent => icon.Colour = accent.NewValue.Darken(4), true);
|
||||
|
||||
drawableObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||
drawableRepeat.ApplyCustomUpdateState += updateStateTransforms;
|
||||
}
|
||||
|
||||
private void updateStateTransforms(DrawableHitObject hitObject, ArmedState state)
|
||||
@ -96,6 +99,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
.MoveToX(0, move_in_duration, Easing.Out)
|
||||
.Loop(total - (move_in_duration + move_out_duration));
|
||||
break;
|
||||
|
||||
case ArmedState.Hit:
|
||||
double animDuration = Math.Min(300, drawableRepeat.HitObject.SpanDuration);
|
||||
this.ScaleTo(1.5f, animDuration, Easing.Out);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,8 +111,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (drawableObject.IsNotNull())
|
||||
drawableObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||
if (drawableRepeat.IsNotNull())
|
||||
drawableRepeat.ApplyCustomUpdateState -= updateStateTransforms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
base.Update();
|
||||
|
||||
//undo rotation on layers which should not be rotated.
|
||||
float appliedRotation = Parent.Rotation;
|
||||
float appliedRotation = Parent!.Rotation;
|
||||
|
||||
fill.Rotation = -appliedRotation;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
@ -8,14 +9,14 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
public partial class DefaultReverseArrow : CompositeDrawable
|
||||
{
|
||||
[Resolved]
|
||||
private DrawableHitObject drawableObject { get; set; } = null!;
|
||||
private DrawableSliderRepeat drawableRepeat { get; set; } = null!;
|
||||
|
||||
public DefaultReverseArrow()
|
||||
{
|
||||
@ -36,9 +37,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(DrawableHitObject drawableObject)
|
||||
{
|
||||
drawableObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||
drawableRepeat = (DrawableSliderRepeat)drawableObject;
|
||||
drawableRepeat.ApplyCustomUpdateState += updateStateTransforms;
|
||||
}
|
||||
|
||||
private void updateStateTransforms(DrawableHitObject hitObject, ArmedState state)
|
||||
@ -55,6 +57,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
.ScaleTo(1f, move_in_duration, Easing.Out)
|
||||
.Loop(total - (move_in_duration + move_out_duration));
|
||||
break;
|
||||
|
||||
case ArmedState.Hit:
|
||||
double animDuration = Math.Min(300, drawableRepeat.HitObject.SpanDuration);
|
||||
InternalChild.ScaleTo(1.5f, animDuration, Easing.Out);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,8 +69,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (drawableObject.IsNotNull())
|
||||
drawableObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||
if (drawableRepeat.IsNotNull())
|
||||
drawableRepeat.ApplyCustomUpdateState -= updateStateTransforms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
{
|
||||
mousePosition = Parent.ToLocalSpace(e.ScreenSpaceMousePosition);
|
||||
mousePosition = Parent!.ToLocalSpace(e.ScreenSpaceMousePosition);
|
||||
return base.OnMouseMove(e);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -17,8 +18,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
public partial class LegacyReverseArrow : CompositeDrawable
|
||||
{
|
||||
[Resolved]
|
||||
private DrawableHitObject drawableObject { get; set; } = null!;
|
||||
private DrawableSliderRepeat drawableRepeat { get; set; } = null!;
|
||||
|
||||
private Drawable proxy = null!;
|
||||
|
||||
@ -31,8 +31,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
private bool shouldRotate;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skinSource)
|
||||
private void load(DrawableHitObject drawableObject, ISkinSource skinSource)
|
||||
{
|
||||
drawableRepeat = (DrawableSliderRepeat)drawableObject;
|
||||
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
string lookupName = new OsuSkinComponentLookup(OsuSkinComponents.ReverseArrow).LookupName;
|
||||
@ -58,10 +60,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
proxy = CreateProxy();
|
||||
|
||||
drawableObject.HitObjectApplied += onHitObjectApplied;
|
||||
onHitObjectApplied(drawableObject);
|
||||
drawableRepeat.HitObjectApplied += onHitObjectApplied;
|
||||
onHitObjectApplied(drawableRepeat);
|
||||
|
||||
accentColour = drawableObject.AccentColour.GetBoundCopy();
|
||||
accentColour = drawableRepeat.AccentColour.GetBoundCopy();
|
||||
accentColour.BindValueChanged(c =>
|
||||
{
|
||||
arrow.Colour = textureIsDefaultSkin && c.NewValue.R + c.NewValue.G + c.NewValue.B > (600 / 255f) ? Color4.Black : Color4.White;
|
||||
@ -73,8 +75,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
Debug.Assert(proxy.Parent == null);
|
||||
|
||||
// see logic in LegacySliderHeadHitCircle.
|
||||
(drawableObject as DrawableSliderRepeat)?.DrawableSlider
|
||||
.OverlayElementContainer.Add(proxy);
|
||||
drawableRepeat.DrawableSlider.OverlayElementContainer.Add(proxy);
|
||||
}
|
||||
|
||||
private void updateStateTransforms(DrawableHitObject hitObject, ArmedState state)
|
||||
@ -102,6 +103,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ArmedState.Hit:
|
||||
double animDuration = Math.Min(300, drawableRepeat.HitObject.SpanDuration);
|
||||
InternalChild.ScaleTo(1.4f, animDuration, Easing.Out);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,10 +115,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (drawableObject.IsNotNull())
|
||||
if (drawableRepeat.IsNotNull())
|
||||
{
|
||||
drawableObject.HitObjectApplied -= onHitObjectApplied;
|
||||
drawableObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||
drawableRepeat.HitObjectApplied -= onHitObjectApplied;
|
||||
drawableRepeat.ApplyCustomUpdateState -= updateStateTransforms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
//undo rotation on layers which should not be rotated.
|
||||
float appliedRotation = Parent.Rotation;
|
||||
float appliedRotation = Parent!.Rotation;
|
||||
|
||||
layerNd.Rotation = -appliedRotation;
|
||||
layerSpec.Rotation = -appliedRotation;
|
||||
|
@ -60,12 +60,12 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
// game_size = DrawSizePreservingFillContainer.TargetSize = new Vector2(1024, 768)
|
||||
//
|
||||
// Parent is a 4:3 aspect enforced, using height as the constricting dimension
|
||||
// Parent.ChildSize.X = min(game_size.X, game_size.Y * (4 / 3)) * playfield_size_adjust
|
||||
// Parent.ChildSize.X = 819.2
|
||||
// Parent!.ChildSize.X = min(game_size.X, game_size.Y * (4 / 3)) * playfield_size_adjust
|
||||
// Parent!.ChildSize.X = 819.2
|
||||
//
|
||||
// Scale = 819.2 / 512
|
||||
// Scale = 1.6
|
||||
Scale = new Vector2(Parent.ChildSize.X / OsuPlayfield.BASE_SIZE.X);
|
||||
Scale = new Vector2(Parent!.ChildSize.X / OsuPlayfield.BASE_SIZE.X);
|
||||
Position = new Vector2(0, (PlayfieldShift ? 8f : 0f) * Scale.X);
|
||||
// Size = 0.625
|
||||
Size = Vector2.Divide(Vector2.One, Scale);
|
||||
|
@ -30,8 +30,8 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||
var topLeft = new Vector2(float.MaxValue, float.MaxValue);
|
||||
var bottomRight = new Vector2(float.MinValue, float.MinValue);
|
||||
|
||||
topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.TopLeft));
|
||||
bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.BottomRight));
|
||||
topLeft = Vector2.ComponentMin(topLeft, Parent!.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.TopLeft));
|
||||
bottomRight = Vector2.ComponentMax(bottomRight, Parent!.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.BottomRight));
|
||||
|
||||
Size = bottomRight - topLeft;
|
||||
Position = topLeft;
|
||||
|
@ -261,7 +261,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
base.Update();
|
||||
|
||||
Size = BaseSize * Parent.RelativeChildSize;
|
||||
Size = BaseSize * Parent!.RelativeChildSize;
|
||||
|
||||
// Make the swell stop at the hit target
|
||||
X = Math.Max(0, X);
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
Width = Parent.DrawSize.X + DrawHeight;
|
||||
Width = Parent!.DrawSize.X + DrawHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
Width = Parent.DrawSize.X + DrawHeight;
|
||||
Width = Parent!.DrawSize.X + DrawHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
// This is still a bit weird, because readability changes with window size, but it is what it is.
|
||||
if (LockPlayfieldAspectRange.Value)
|
||||
{
|
||||
float currentAspect = Parent.ChildSize.X / Parent.ChildSize.Y;
|
||||
float currentAspect = Parent!.ChildSize.X / Parent!.ChildSize.Y;
|
||||
|
||||
if (currentAspect > MAXIMUM_ASPECT)
|
||||
height *= currentAspect / MAXIMUM_ASPECT;
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
@ -230,25 +229,25 @@ namespace osu.Game.Tests.Editing
|
||||
}
|
||||
|
||||
private void assertSnapDistance(float expectedDistance, HitObject? referenceObject, bool includeSliderVelocity)
|
||||
=> AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(referenceObject ?? new HitObject(), includeSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
||||
=> AddAssert($"distance is {expectedDistance}", () => composer.DistanceSnapProvider.GetBeatSnapDistanceAt(referenceObject ?? new HitObject(), includeSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
||||
|
||||
private void assertDurationToDistance(double duration, float expectedDistance, HitObject? referenceObject = null)
|
||||
=> AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(referenceObject ?? new HitObject(), duration), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
||||
=> AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DistanceSnapProvider.DurationToDistance(referenceObject ?? new HitObject(), duration), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
||||
|
||||
private void assertDistanceToDuration(float distance, double expectedDuration, HitObject? referenceObject = null)
|
||||
=> AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceToDuration(referenceObject ?? new HitObject(), distance), () => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON));
|
||||
=> AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceSnapProvider.DistanceToDuration(referenceObject ?? new HitObject(), distance), () => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON));
|
||||
|
||||
private void assertSnappedDuration(float distance, double expectedDuration, HitObject? referenceObject = null)
|
||||
=> AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.FindSnappedDuration(referenceObject ?? new HitObject(), distance), () => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON));
|
||||
=> AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.DistanceSnapProvider.FindSnappedDuration(referenceObject ?? new HitObject(), distance), () => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON));
|
||||
|
||||
private void assertSnappedDistance(float distance, float expectedDistance, HitObject? referenceObject = null)
|
||||
=> AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.FindSnappedDistance(referenceObject ?? new HitObject(), distance), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
||||
=> AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.DistanceSnapProvider.FindSnappedDistance(referenceObject ?? new HitObject(), distance), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
||||
|
||||
private partial class TestHitObjectComposer : OsuHitObjectComposer
|
||||
{
|
||||
public new EditorBeatmap EditorBeatmap => base.EditorBeatmap;
|
||||
|
||||
public new Bindable<double> DistanceSpacingMultiplier => base.DistanceSpacingMultiplier;
|
||||
public new IDistanceSnapProvider DistanceSnapProvider => base.DistanceSnapProvider;
|
||||
|
||||
public TestHitObjectComposer()
|
||||
: base(new OsuRuleset())
|
||||
|
117
osu.Game.Tests/Input/RealmKeyBindingStoreTest.cs
Normal file
117
osu.Game.Tests/Input/RealmKeyBindingStoreTest.cs
Normal file
@ -0,0 +1,117 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Input;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Input
|
||||
{
|
||||
[TestFixture]
|
||||
public class RealmKeyBindingStoreTest
|
||||
{
|
||||
[Test]
|
||||
public void TestBindingsWithoutDuplicatesAreNotModified()
|
||||
{
|
||||
var bindings = new List<RealmKeyBinding>
|
||||
{
|
||||
new RealmKeyBinding(GlobalAction.Back, KeyCombination.FromKey(Key.Escape)),
|
||||
new RealmKeyBinding(GlobalAction.Back, KeyCombination.FromMouseButton(MouseButton.Button1)),
|
||||
new RealmKeyBinding(GlobalAction.MusicPrev, KeyCombination.FromKey(Key.F1)),
|
||||
new RealmKeyBinding(GlobalAction.MusicNext, KeyCombination.FromKey(Key.F5))
|
||||
};
|
||||
|
||||
int countCleared = RealmKeyBindingStore.ClearDuplicateBindings(bindings);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(countCleared, Is.Zero);
|
||||
|
||||
Assert.That(bindings[0].Action, Is.EqualTo((int)GlobalAction.Back));
|
||||
Assert.That(bindings[0].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.Escape)));
|
||||
|
||||
Assert.That(bindings[1].Action, Is.EqualTo((int)GlobalAction.Back));
|
||||
Assert.That(bindings[1].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.ExtraMouseButton1)));
|
||||
|
||||
Assert.That(bindings[2].Action, Is.EqualTo((int)GlobalAction.MusicPrev));
|
||||
Assert.That(bindings[2].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.F1)));
|
||||
|
||||
Assert.That(bindings[3].Action, Is.EqualTo((int)GlobalAction.MusicNext));
|
||||
Assert.That(bindings[3].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.F5)));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDuplicateBindingsAreCleared()
|
||||
{
|
||||
var bindings = new List<RealmKeyBinding>
|
||||
{
|
||||
new RealmKeyBinding(GlobalAction.Back, KeyCombination.FromKey(Key.Escape)),
|
||||
new RealmKeyBinding(GlobalAction.Back, KeyCombination.FromMouseButton(MouseButton.Button1)),
|
||||
new RealmKeyBinding(GlobalAction.MusicPrev, KeyCombination.FromKey(Key.F1)),
|
||||
new RealmKeyBinding(GlobalAction.IncreaseVolume, KeyCombination.FromKey(Key.Escape)),
|
||||
new RealmKeyBinding(GlobalAction.MusicNext, KeyCombination.FromKey(Key.F5)),
|
||||
new RealmKeyBinding(GlobalAction.ExportReplay, KeyCombination.FromKey(Key.F1)),
|
||||
new RealmKeyBinding(GlobalAction.TakeScreenshot, KeyCombination.FromKey(Key.PrintScreen)),
|
||||
};
|
||||
|
||||
int countCleared = RealmKeyBindingStore.ClearDuplicateBindings(bindings);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(countCleared, Is.EqualTo(4));
|
||||
|
||||
Assert.That(bindings[0].Action, Is.EqualTo((int)GlobalAction.Back));
|
||||
Assert.That(bindings[0].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.None)));
|
||||
|
||||
Assert.That(bindings[1].Action, Is.EqualTo((int)GlobalAction.Back));
|
||||
Assert.That(bindings[1].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.ExtraMouseButton1)));
|
||||
|
||||
Assert.That(bindings[2].Action, Is.EqualTo((int)GlobalAction.MusicPrev));
|
||||
Assert.That(bindings[2].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.None)));
|
||||
|
||||
Assert.That(bindings[3].Action, Is.EqualTo((int)GlobalAction.IncreaseVolume));
|
||||
Assert.That(bindings[3].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.None)));
|
||||
|
||||
Assert.That(bindings[4].Action, Is.EqualTo((int)GlobalAction.MusicNext));
|
||||
Assert.That(bindings[4].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.F5)));
|
||||
|
||||
Assert.That(bindings[5].Action, Is.EqualTo((int)GlobalAction.ExportReplay));
|
||||
Assert.That(bindings[5].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.None)));
|
||||
|
||||
Assert.That(bindings[6].Action, Is.EqualTo((int)GlobalAction.TakeScreenshot));
|
||||
Assert.That(bindings[6].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.PrintScreen)));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDuplicateBindingsAllowedIfBoundToSameAction()
|
||||
{
|
||||
var bindings = new List<RealmKeyBinding>
|
||||
{
|
||||
new RealmKeyBinding(GlobalAction.Back, KeyCombination.FromKey(Key.Escape)),
|
||||
new RealmKeyBinding(GlobalAction.Back, KeyCombination.FromKey(Key.Escape)),
|
||||
new RealmKeyBinding(GlobalAction.MusicPrev, KeyCombination.FromKey(Key.F1)),
|
||||
};
|
||||
|
||||
int countCleared = RealmKeyBindingStore.ClearDuplicateBindings(bindings);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(countCleared, Is.EqualTo(0));
|
||||
|
||||
Assert.That(bindings[0].Action, Is.EqualTo((int)GlobalAction.Back));
|
||||
Assert.That(bindings[0].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.Escape)));
|
||||
|
||||
Assert.That(bindings[1].Action, Is.EqualTo((int)GlobalAction.Back));
|
||||
Assert.That(bindings[1].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.Escape)));
|
||||
|
||||
Assert.That(bindings[2].Action, Is.EqualTo((int)GlobalAction.MusicPrev));
|
||||
Assert.That(bindings[2].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.F1)));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -187,11 +187,9 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
private class SnapProvider : IDistanceSnapProvider
|
||||
{
|
||||
public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.AllGrids) => new SnapResult(screenSpacePosition, 0);
|
||||
|
||||
public Bindable<double> DistanceSpacingMultiplier { get; } = new BindableDouble(1);
|
||||
|
||||
IBindable<double> IDistanceSnapProvider.DistanceSpacingMultiplier => DistanceSpacingMultiplier;
|
||||
Bindable<double> IDistanceSnapProvider.DistanceSpacingMultiplier => DistanceSpacingMultiplier;
|
||||
|
||||
public float GetBeatSnapDistanceAt(HitObject referenceObject, bool useReferenceSliderVelocity = true) => beat_snap_distance;
|
||||
|
||||
|
@ -182,7 +182,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
if (sameRuleset)
|
||||
{
|
||||
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog);
|
||||
AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog.PerformOkAction());
|
||||
AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction());
|
||||
}
|
||||
|
||||
AddUntilStep("wait for created", () =>
|
||||
@ -269,7 +269,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo));
|
||||
|
||||
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog);
|
||||
AddStep("confirm creation as a copy", () => DialogOverlay.CurrentDialog.Buttons.ElementAt(1).TriggerClick());
|
||||
AddStep("confirm creation as a copy", () => DialogOverlay.CurrentDialog!.Buttons.ElementAt(1).TriggerClick());
|
||||
|
||||
AddUntilStep("wait for created", () =>
|
||||
{
|
||||
@ -342,7 +342,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo));
|
||||
|
||||
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog);
|
||||
AddStep("confirm creation as a copy", () => DialogOverlay.CurrentDialog.Buttons.ElementAt(1).TriggerClick());
|
||||
AddStep("confirm creation as a copy", () => DialogOverlay.CurrentDialog!.Buttons.ElementAt(1).TriggerClick());
|
||||
|
||||
AddUntilStep("wait for created", () =>
|
||||
{
|
||||
@ -380,7 +380,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
AddStep("try to create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo));
|
||||
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog);
|
||||
AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog.PerformOkAction());
|
||||
AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction());
|
||||
|
||||
AddUntilStep("wait for created", () =>
|
||||
{
|
||||
@ -415,7 +415,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
if (sameRuleset)
|
||||
{
|
||||
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog);
|
||||
AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog.PerformOkAction());
|
||||
AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction());
|
||||
}
|
||||
|
||||
AddUntilStep("wait for created", () =>
|
||||
@ -476,7 +476,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
AddStep("exit", () => Editor.Exit());
|
||||
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is PromptForSaveDialog);
|
||||
AddStep("attempt to save", () => DialogOverlay.CurrentDialog.PerformOkAction());
|
||||
AddStep("attempt to save", () => DialogOverlay.CurrentDialog!.PerformOkAction());
|
||||
AddAssert("editor is still current", () => Editor.IsCurrentScreen());
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
public void TestWidthUpdatesOnDrawSizeChanges()
|
||||
{
|
||||
AddStep("Shrink scroll container", () => scrollContainer.Width = 0.5f);
|
||||
AddAssert("Scroll container width shrunk", () => scrollContainer.DrawWidth == scrollContainer.Parent.DrawWidth / 2);
|
||||
AddAssert("Scroll container width shrunk", () => scrollContainer.DrawWidth == scrollContainer.Parent!.DrawWidth / 2);
|
||||
AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
var expectedComponentsAdjustmentContainer = new DependencyProvidingContainer
|
||||
{
|
||||
Position = actualComponentsContainer.Parent.ToSpaceOfOtherDrawable(actualComponentsContainer.DrawPosition, Content),
|
||||
Position = actualComponentsContainer.Parent!.ToSpaceOfOtherDrawable(actualComponentsContainer.DrawPosition, Content),
|
||||
Size = actualComponentsContainer.DrawSize,
|
||||
Child = expectedComponentsContainer,
|
||||
// proxy the same required dependencies that `actualComponentsContainer` is using.
|
||||
|
@ -220,7 +220,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
public partial class TestInputConsumer : CompositeDrawable, IKeyBindingHandler<TestAction>
|
||||
{
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos);
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent!.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
private readonly Box box;
|
||||
|
||||
|
@ -259,7 +259,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
public partial class TestInputConsumer : CompositeDrawable, IKeyBindingHandler<TestAction>
|
||||
{
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos);
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent!.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
private readonly Box box;
|
||||
|
||||
|
@ -188,7 +188,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddUntilStep("placeholder shown", () =>
|
||||
{
|
||||
var notFoundDrawable = overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault();
|
||||
return notFoundDrawable != null && notFoundDrawable.IsPresent && notFoundDrawable.Parent.DrawHeight > 0;
|
||||
return notFoundDrawable != null && notFoundDrawable.IsPresent && notFoundDrawable.Parent!.DrawHeight > 0;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("Progress details are hidden", () => match.ChildrenOfType<RoomLocalUserInfo>().FirstOrDefault()?.Parent.Alpha == 0);
|
||||
AddUntilStep("Progress details are hidden", () => match.ChildrenOfType<RoomLocalUserInfo>().FirstOrDefault()?.Parent!.Alpha == 0);
|
||||
|
||||
AddUntilStep("Leaderboard shows two aggregate scores", () => match.ChildrenOfType<MatchLeaderboardScore>().Count(s => s.ScoreText.Text != "0") == 2);
|
||||
|
||||
@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("Progress details are visible", () => match.ChildrenOfType<RoomLocalUserInfo>().FirstOrDefault()?.Parent.Alpha == 1);
|
||||
AddUntilStep("Progress details are visible", () => match.ChildrenOfType<RoomLocalUserInfo>().FirstOrDefault()?.Parent!.Alpha == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -66,20 +66,20 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
});
|
||||
|
||||
AddStep("set non-default value", () => revertToDefaultButton.Current.Value = "non-default");
|
||||
AddAssert("default value button centre aligned to control size", () => Precision.AlmostEquals(revertToDefaultButton.Parent.DrawHeight, control.DrawHeight, 1));
|
||||
AddAssert("default value button centre aligned to control size", () => Precision.AlmostEquals(revertToDefaultButton.Parent!.DrawHeight, control.DrawHeight, 1));
|
||||
|
||||
AddStep("set label", () => textBox.LabelText = "label text");
|
||||
AddAssert("default value button centre aligned to label size", () =>
|
||||
{
|
||||
var label = textBox.ChildrenOfType<OsuSpriteText>().Single(spriteText => spriteText.Text == "label text");
|
||||
return Precision.AlmostEquals(revertToDefaultButton.Parent.DrawHeight, label.DrawHeight, 1);
|
||||
return Precision.AlmostEquals(revertToDefaultButton.Parent!.DrawHeight, label.DrawHeight, 1);
|
||||
});
|
||||
|
||||
AddStep("clear label", () => textBox.LabelText = default);
|
||||
AddAssert("default value button centre aligned to control size", () => Precision.AlmostEquals(revertToDefaultButton.Parent.DrawHeight, control.DrawHeight, 1));
|
||||
AddAssert("default value button centre aligned to control size", () => Precision.AlmostEquals(revertToDefaultButton.Parent!.DrawHeight, control.DrawHeight, 1));
|
||||
|
||||
AddStep("set warning text", () => textBox.SetNoticeText("This is some very important warning text! Hopefully it doesn't break the alignment of the default value indicator...", true));
|
||||
AddAssert("default value button centre aligned to control size", () => Precision.AlmostEquals(revertToDefaultButton.Parent.DrawHeight, control.DrawHeight, 1));
|
||||
AddAssert("default value button centre aligned to control size", () => Precision.AlmostEquals(revertToDefaultButton.Parent!.DrawHeight, control.DrawHeight, 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -269,8 +269,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
if (!logoFacade.Transforms.Any() && !transferContainer.Transforms.Any())
|
||||
{
|
||||
Random random = new Random();
|
||||
trackingContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300);
|
||||
transferContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300);
|
||||
trackingContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent!.DrawWidth), random.Next(0, (int)logo.Parent!.DrawHeight)), 300);
|
||||
transferContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent!.DrawWidth), random.Next(0, (int)logo.Parent!.DrawHeight)), 300);
|
||||
}
|
||||
|
||||
if (randomPositions)
|
||||
|
@ -0,0 +1,59 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneRoundedSliderBar : OsuManualInputManagerTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
|
||||
private readonly BindableDouble current = new BindableDouble(5)
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 0,
|
||||
MaxValue = 15
|
||||
};
|
||||
|
||||
private RoundedSliderBar<double> slider = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Child = slider = new RoundedSliderBar<double>
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Current = current,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.4f
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNubDoubleClickRevertToDefault()
|
||||
{
|
||||
AddStep("set slider to 1", () => slider.Current.Value = 1);
|
||||
|
||||
AddStep("move mouse to nub", () => InputManager.MoveMouseTo(slider.ChildrenOfType<Nub>().Single()));
|
||||
|
||||
AddStep("double click nub", () =>
|
||||
{
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("slider is default", () => slider.Current.IsDefault);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +1,19 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneShearedSliderBar : OsuTestScene
|
||||
public partial class TestSceneShearedSliderBar : OsuManualInputManagerTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
@ -21,10 +25,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
MaxValue = 15
|
||||
};
|
||||
|
||||
private ShearedSliderBar<double> slider = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Child = new ShearedSliderBar<double>
|
||||
Child = slider = new ShearedSliderBar<double>
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -33,5 +39,21 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Width = 0.4f
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNubDoubleClickRevertToDefault()
|
||||
{
|
||||
AddStep("set slider to 1", () => slider.Current.Value = 1);
|
||||
|
||||
AddStep("move mouse to nub", () => InputManager.MoveMouseTo(slider.ChildrenOfType<ShearedNub>().Single()));
|
||||
|
||||
AddStep("double click nub", () =>
|
||||
{
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("slider is default", () => slider.Current.IsDefault);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Tournament.Components
|
||||
public new Bindable<DateTimeOffset>? Current
|
||||
{
|
||||
get => current;
|
||||
set => current.Current = value;
|
||||
set => current.Current = value!;
|
||||
}
|
||||
|
||||
public DateTextBox()
|
||||
|
@ -61,8 +61,8 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
|
||||
var topLeft = new Vector2(minX, minY);
|
||||
|
||||
OriginPosition = new Vector2(PathRadius);
|
||||
Position = Parent.ToLocalSpace(topLeft);
|
||||
Vertices = points.Select(p => Parent.ToLocalSpace(p) - Parent.ToLocalSpace(topLeft)).ToList();
|
||||
Position = Parent!.ToLocalSpace(topLeft);
|
||||
Vertices = points.Select(p => Parent!.ToLocalSpace(p) - Parent!.ToLocalSpace(topLeft)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,9 +70,9 @@ namespace osu.Game.Beatmaps
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new DummyBeatmapConverter { Beatmap = beatmap };
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new DummyBeatmapConverter(beatmap);
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null;
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException();
|
||||
|
||||
public override string Description => "dummy";
|
||||
|
||||
@ -80,9 +80,15 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
private class DummyBeatmapConverter : IBeatmapConverter
|
||||
{
|
||||
public event Action<HitObject, IEnumerable<HitObject>> ObjectConverted;
|
||||
public IBeatmap Beatmap { get; }
|
||||
|
||||
public IBeatmap Beatmap { get; set; }
|
||||
public DummyBeatmapConverter(IBeatmap beatmap)
|
||||
{
|
||||
Beatmap = beatmap;
|
||||
}
|
||||
|
||||
[CanBeNull]
|
||||
public event Action<HitObject, IEnumerable<HitObject>> ObjectConverted;
|
||||
|
||||
public bool CanConvert() => true;
|
||||
|
||||
|
@ -86,7 +86,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public event Action<WorkingBeatmap> OnInvalidated;
|
||||
|
||||
public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo)
|
||||
public virtual WorkingBeatmap GetWorkingBeatmap([CanBeNull] BeatmapInfo beatmapInfo)
|
||||
{
|
||||
if (beatmapInfo?.BeatmapSet == null)
|
||||
return DefaultBeatmap;
|
||||
|
@ -184,6 +184,7 @@ namespace osu.Game.Configuration
|
||||
SetDefault(OsuSetting.EditorShowHitMarkers, true);
|
||||
SetDefault(OsuSetting.EditorAutoSeekOnPlacement, true);
|
||||
SetDefault(OsuSetting.EditorLimitedDistanceSnap, false);
|
||||
SetDefault(OsuSetting.EditorShowSpeedChanges, false);
|
||||
|
||||
SetDefault(OsuSetting.LastProcessedMetadataId, -1);
|
||||
|
||||
@ -407,5 +408,6 @@ namespace osu.Game.Configuration
|
||||
EditorLimitedDistanceSnap,
|
||||
ReplaySettingsOverlay,
|
||||
AutomaticallyDownloadMissingBeatmaps,
|
||||
EditorShowSpeedChanges
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Input;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Online.API;
|
||||
@ -34,6 +35,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Scoring.Legacy;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Input;
|
||||
using Realms;
|
||||
using Realms.Exceptions;
|
||||
|
||||
@ -84,8 +86,9 @@ namespace osu.Game.Database
|
||||
/// 32 2023-07-09 Populate legacy scores with the ScoreV2 mod (and restore TotalScore to the legacy total for such scores) using replay files.
|
||||
/// 33 2023-08-16 Reset default chat toggle key binding to avoid conflict with newly added leaderboard toggle key binding.
|
||||
/// 34 2023-08-21 Add BackgroundReprocessingFailed flag to ScoreInfo to track upgrade failures.
|
||||
/// 35 2023-10-16 Clear key combinations of keybindings that are assigned to more than one action in a given settings section.
|
||||
/// </summary>
|
||||
private const int schema_version = 34;
|
||||
private const int schema_version = 35;
|
||||
|
||||
/// <summary>
|
||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||
@ -1031,6 +1034,47 @@ namespace osu.Game.Database
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 35:
|
||||
{
|
||||
// catch used `Shift` twice as a default key combination for dash, which generally was bothersome and causes issues elsewhere.
|
||||
// the duplicate binding logic below had to account for it, it could also break keybinding conflict resolution on revert-to-default.
|
||||
// as such, detect this situation and fix it before proceeding further.
|
||||
var catchDashBindings = migration.NewRealm.All<RealmKeyBinding>()
|
||||
.Where(kb => kb.RulesetName == @"fruits" && kb.ActionInt == 2)
|
||||
.ToList();
|
||||
|
||||
if (catchDashBindings.All(kb => kb.KeyCombination.Equals(new KeyCombination(InputKey.Shift))))
|
||||
{
|
||||
Debug.Assert(catchDashBindings.Count == 2);
|
||||
catchDashBindings.Last().KeyCombination = KeyCombination.FromMouseButton(MouseButton.Left);
|
||||
}
|
||||
|
||||
// with the catch case dealt with, de-duplicate the remaining bindings.
|
||||
int countCleared = 0;
|
||||
|
||||
var globalBindings = migration.NewRealm.All<RealmKeyBinding>().Where(kb => kb.RulesetName == null).ToList();
|
||||
|
||||
foreach (var category in Enum.GetValues<GlobalActionCategory>())
|
||||
{
|
||||
var categoryActions = GlobalActionContainer.GetGlobalActionsFor(category).Cast<int>().ToHashSet();
|
||||
var categoryBindings = globalBindings.Where(kb => categoryActions.Contains(kb.ActionInt));
|
||||
countCleared += RealmKeyBindingStore.ClearDuplicateBindings(categoryBindings);
|
||||
}
|
||||
|
||||
var rulesetBindings = migration.NewRealm.All<RealmKeyBinding>().Where(kb => kb.RulesetName != null).ToList();
|
||||
|
||||
foreach (var variantGroup in rulesetBindings.GroupBy(kb => (kb.RulesetName, kb.Variant)))
|
||||
countCleared += RealmKeyBindingStore.ClearDuplicateBindings(variantGroup);
|
||||
|
||||
if (countCleared > 0)
|
||||
{
|
||||
Logger.Log($"{countCleared} of your keybinding(s) have been cleared due to being bound to multiple actions. "
|
||||
+ "Please choose new unique ones in the settings panel.", level: LogLevel.Important);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms");
|
||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Extensions
|
||||
/// <param name="delta">A delta in screen-space coordinates.</param>
|
||||
/// <returns>The delta vector in Parent's coordinates.</returns>
|
||||
public static Vector2 ScreenSpaceDeltaToParentSpace(this Drawable drawable, Vector2 delta) =>
|
||||
drawable.Parent.ToLocalSpace(drawable.Parent.ToScreenSpace(Vector2.Zero) + delta);
|
||||
drawable.Parent!.ToLocalSpace(drawable.Parent!.ToScreenSpace(Vector2.Zero) + delta);
|
||||
|
||||
/// <summary>
|
||||
/// Some elements don't handle rewind correctly and fixing them is non-trivial.
|
||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
Sprite.Texture = skin.GetTexture("menu-background") ?? Sprite.Texture;
|
||||
}
|
||||
|
||||
public override bool Equals(Background other)
|
||||
public override bool Equals(Background? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
|
@ -76,10 +76,10 @@ namespace osu.Game.Graphics.Containers
|
||||
/// <remarks>Will only be correct if the logo's <see cref="Drawable.RelativePositionAxes"/> are set to Axes.Both</remarks>
|
||||
protected Vector2 ComputeLogoTrackingPosition()
|
||||
{
|
||||
var absolutePos = Logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre);
|
||||
var absolutePos = Logo.Parent!.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre);
|
||||
|
||||
return new Vector2(absolutePos.X / Logo.Parent.RelativeToAbsoluteFactor.X,
|
||||
absolutePos.Y / Logo.Parent.RelativeToAbsoluteFactor.Y);
|
||||
return new Vector2(absolutePos.X / Logo.Parent!.RelativeToAbsoluteFactor.X,
|
||||
absolutePos.Y / Logo.Parent!.RelativeToAbsoluteFactor.Y);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
@ -119,11 +119,11 @@ namespace osu.Game.Graphics.Containers
|
||||
headerBackgroundContainer.Clear();
|
||||
headerBackground = value;
|
||||
|
||||
if (value == null) return;
|
||||
|
||||
headerBackgroundContainer.Add(headerBackground);
|
||||
|
||||
lastKnownScroll = null;
|
||||
if (headerBackground != null)
|
||||
{
|
||||
headerBackgroundContainer.Add(headerBackground);
|
||||
lastKnownScroll = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Graphics.Containers
|
||||
private void keepUprightAndUnstretched()
|
||||
{
|
||||
// Decomposes the inverse of the parent DrawInfo.Matrix into rotation, shear and scale.
|
||||
var parentMatrix = Parent.DrawInfo.Matrix;
|
||||
var parentMatrix = Parent!.DrawInfo.Matrix;
|
||||
|
||||
// Remove Translation.>
|
||||
parentMatrix.M31 = 0.0f;
|
||||
|
@ -178,7 +178,7 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
// We can not use RelativeSizeAxes for Height, because the height
|
||||
// of our parent diminishes as the content moves up.
|
||||
Height = Parent.Parent.DrawSize.Y * 1.5f;
|
||||
Height = Parent!.Parent!.DrawSize.Y * 1.5f;
|
||||
}
|
||||
|
||||
protected override void PopIn() => Schedule(() => this.MoveToY(FinalPosition, APPEAR_DURATION, easing_show));
|
||||
@ -188,7 +188,7 @@ namespace osu.Game.Graphics.Containers
|
||||
double duration = IsLoaded ? DISAPPEAR_DURATION : 0;
|
||||
|
||||
// scheduling is required as parent may not be present at the time this is called.
|
||||
Schedule(() => this.MoveToY(Parent.Parent.DrawSize.Y, duration, easing_hide));
|
||||
Schedule(() => this.MoveToY(Parent!.Parent!.DrawSize.Y, duration, easing_hide));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
if (!PlaySamplesOnAdjust)
|
||||
return;
|
||||
|
||||
if (Clock == null || Clock.CurrentTime - lastSampleTime <= 30)
|
||||
if (Clock.CurrentTime - lastSampleTime <= 30)
|
||||
return;
|
||||
|
||||
if (value.Equals(lastSampleValue))
|
||||
|
@ -93,11 +93,12 @@ namespace osu.Game.Graphics.UserInterface
|
||||
nubContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = Nub = new Nub
|
||||
Child = Nub = new SliderNub
|
||||
{
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativePositionAxes = Axes.X,
|
||||
Current = { Value = true }
|
||||
Current = { Value = true },
|
||||
OnDoubleClicked = () => Current.SetDefault(),
|
||||
},
|
||||
},
|
||||
hoverClickSounds = new HoverClickSounds()
|
||||
@ -166,5 +167,18 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
Nub.MoveToX(value, 250, Easing.OutQuint);
|
||||
}
|
||||
|
||||
public partial class SliderNub : Nub
|
||||
{
|
||||
public Action? OnDoubleClicked { get; init; }
|
||||
|
||||
protected override bool OnClick(ClickEvent e) => true;
|
||||
|
||||
protected override bool OnDoubleClick(DoubleClickEvent e)
|
||||
{
|
||||
OnDoubleClicked?.Invoke();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@ -18,6 +19,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public partial class ShearedNub : Container, IHasCurrentValue<bool>, IHasAccentColour
|
||||
{
|
||||
public Action? OnDoubleClicked { get; init; }
|
||||
|
||||
protected const float BORDER_WIDTH = 3;
|
||||
|
||||
public const int HEIGHT = 30;
|
||||
@ -179,5 +182,13 @@ namespace osu.Game.Graphics.UserInterface
|
||||
main.TransformTo(nameof(BorderThickness), BORDER_WIDTH, duration, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e) => true;
|
||||
|
||||
protected override bool OnDoubleClick(DoubleClickEvent e)
|
||||
{
|
||||
OnDoubleClicked?.Invoke();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
X = -SHEAR.X * HEIGHT / 2f,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativePositionAxes = Axes.X,
|
||||
Current = { Value = true }
|
||||
Current = { Value = true },
|
||||
OnDoubleClicked = () => Current.SetDefault(),
|
||||
},
|
||||
},
|
||||
hoverClickSounds = new HoverClickSounds()
|
||||
|
@ -13,7 +13,7 @@ namespace osu.Game.IO
|
||||
|
||||
public bool IsManaged => ID > 0;
|
||||
|
||||
public string Hash { get; set; }
|
||||
public string Hash { get; set; } = string.Empty;
|
||||
|
||||
public int ReferenceCount { get; set; }
|
||||
}
|
||||
|
@ -130,5 +130,31 @@ namespace osu.Game.Input
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all <see cref="RealmKeyBinding.KeyCombination"/>s from the provided <paramref name="keyBindings"/>
|
||||
/// which are assigned to more than one binding.
|
||||
/// </summary>
|
||||
/// <param name="keyBindings">The <see cref="RealmKeyBinding"/>s to de-duplicate.</param>
|
||||
/// <returns>Number of bindings cleared.</returns>
|
||||
public static int ClearDuplicateBindings(IEnumerable<IKeyBinding> keyBindings)
|
||||
{
|
||||
int countRemoved = 0;
|
||||
|
||||
var lookup = keyBindings.ToLookup(kb => kb.KeyCombination);
|
||||
|
||||
foreach (var group in lookup)
|
||||
{
|
||||
if (group.Select(kb => kb.Action).Distinct().Count() <= 1)
|
||||
continue;
|
||||
|
||||
foreach (var binding in group)
|
||||
binding.KeyCombination = new KeyCombination(InputKey.None);
|
||||
|
||||
countRemoved += group.Count();
|
||||
}
|
||||
|
||||
return countRemoved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
public DateTimeOffset JoinDate;
|
||||
|
||||
[JsonProperty(@"username")]
|
||||
public string Username { get; set; }
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty(@"previous_usernames")]
|
||||
public string[] PreviousUsernames;
|
||||
|
@ -22,6 +22,6 @@ namespace osu.Game.Online.Placeholders
|
||||
AddText(this.message = message);
|
||||
}
|
||||
|
||||
public override bool Equals(Placeholder other) => (other as MessagePlaceholder)?.message == message;
|
||||
public override bool Equals(Placeholder? other) => (other as MessagePlaceholder)?.message == message;
|
||||
}
|
||||
}
|
||||
|
@ -267,7 +267,7 @@ namespace osu.Game.Overlays
|
||||
if (!isDraggingTopBar)
|
||||
return;
|
||||
|
||||
float targetChatHeight = dragStartChatHeight - (e.MousePosition.Y - e.MouseDownPosition.Y) / Parent.DrawSize.Y;
|
||||
float targetChatHeight = dragStartChatHeight - (e.MousePosition.Y - e.MouseDownPosition.Y) / Parent!.DrawSize.Y;
|
||||
chatHeight.Value = targetChatHeight;
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Overlays
|
||||
public abstract partial class FullscreenOverlay<T> : WaveOverlayContainer, INamedOverlayComponent
|
||||
where T : OverlayHeader
|
||||
{
|
||||
public virtual string IconTexture => Header.Title.IconTexture ?? string.Empty;
|
||||
public virtual string IconTexture => Header.Title.IconTexture;
|
||||
public virtual LocalisableString Title => Header.Title.Title;
|
||||
public virtual LocalisableString Description => Header.Title.Description;
|
||||
|
||||
|
@ -604,7 +604,7 @@ namespace osu.Game.Overlays.Mods
|
||||
if (columnNumber > 5 && !column.Active.Value) return;
|
||||
|
||||
// use X position of the column on screen as a basis for panning the sample
|
||||
float balance = column.Parent.BoundingBox.Centre.X / RelativeToAbsoluteFactor.X;
|
||||
float balance = column.Parent!.BoundingBox.Centre.X / RelativeToAbsoluteFactor.X;
|
||||
|
||||
// dip frequency and ramp volume of sample over the first 5 displayed columns
|
||||
float progress = Math.Min(1, columnNumber / 5f);
|
||||
|
@ -248,7 +248,7 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
playlistContainer.Height = MathF.Min(Parent.DrawHeight - margin * 3 - player_height, PlaylistOverlay.PLAYLIST_HEIGHT);
|
||||
playlistContainer.Height = MathF.Min(Parent!.DrawHeight - margin * 3 - player_height, PlaylistOverlay.PLAYLIST_HEIGHT);
|
||||
|
||||
float height = player_height;
|
||||
|
||||
|
@ -241,7 +241,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
protected override void OnDrag(DragEvent e)
|
||||
{
|
||||
var newPos = Position + e.Delta;
|
||||
this.MoveTo(Vector2.Clamp(newPos, Vector2.Zero, Parent.Size));
|
||||
this.MoveTo(Vector2.Clamp(newPos, Vector2.Zero, Parent!.Size));
|
||||
}
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
|
@ -202,7 +202,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
if (drawable.Parent == null)
|
||||
return;
|
||||
|
||||
var newAnchor = drawable.Parent.ToSpaceOfOtherDrawable(drawable.AnchorPosition, this);
|
||||
var newAnchor = drawable.Parent!.ToSpaceOfOtherDrawable(drawable.AnchorPosition, this);
|
||||
anchorPosition = tweenPosition(anchorPosition ?? newAnchor, newAnchor);
|
||||
anchorBox.Position = anchorPosition.Value;
|
||||
|
||||
|
@ -135,7 +135,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
var drawableItem = (Drawable)b.Item;
|
||||
|
||||
var flippedPosition = GeometryUtils.GetFlippedPosition(direction, flipOverOrigin ? drawableItem.Parent.ScreenSpaceDrawQuad : selectionQuad, b.ScreenSpaceSelectionPoint);
|
||||
var flippedPosition = GeometryUtils.GetFlippedPosition(direction, flipOverOrigin ? drawableItem.Parent!.ScreenSpaceDrawQuad : selectionQuad, b.ScreenSpaceSelectionPoint);
|
||||
|
||||
updateDrawablePosition(drawableItem, flippedPosition);
|
||||
|
||||
@ -242,7 +242,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
private static void updateDrawablePosition(Drawable drawable, Vector2 screenSpacePosition)
|
||||
{
|
||||
drawable.Position =
|
||||
drawable.Parent.ToLocalSpace(screenSpacePosition) - drawable.AnchorPosition;
|
||||
drawable.Parent!.ToLocalSpace(screenSpacePosition) - drawable.AnchorPosition;
|
||||
}
|
||||
|
||||
private void applyOrigins(Anchor origin)
|
||||
|
@ -63,7 +63,7 @@ namespace osu.Game.Overlays.Toolbar
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
Parent.TriggerClick();
|
||||
Parent!.TriggerClick();
|
||||
return base.OnClick(e);
|
||||
}
|
||||
}
|
||||
|
@ -81,10 +81,10 @@ namespace osu.Game.Overlays.Wiki.Markdown
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (Width > Parent.DrawWidth)
|
||||
if (Width > Parent!.DrawWidth)
|
||||
{
|
||||
float ratio = Height / Width;
|
||||
Width = Parent.DrawWidth;
|
||||
Width = Parent!.DrawWidth;
|
||||
Height = ratio * Width;
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Wiki
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
Height = Math.Max(panelContainer.Height, Parent.DrawHeight);
|
||||
Height = Math.Max(panelContainer.Height, Parent!.DrawHeight);
|
||||
}
|
||||
|
||||
private partial class WikiPanelMarkdownContainer : WikiMarkdownContainer
|
||||
|
@ -1,8 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
@ -24,16 +23,13 @@ using osu.Game.Overlays.OSD;
|
||||
using osu.Game.Overlays.Settings.Sections;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a <see cref="HitObjectComposer{TObject}"/> for rulesets with the concept of distances between objects.
|
||||
/// </summary>
|
||||
/// <typeparam name="TObject">The base type of supported objects.</typeparam>
|
||||
public abstract partial class DistancedHitObjectComposer<TObject> : HitObjectComposer<TObject>, IDistanceSnapProvider, IScrollBindingHandler<GlobalAction>
|
||||
where TObject : HitObject
|
||||
public abstract partial class ComposerDistanceSnapProvider : Component, IDistanceSnapProvider, IScrollBindingHandler<GlobalAction>
|
||||
{
|
||||
private const float adjust_step = 0.1f;
|
||||
|
||||
@ -44,27 +40,38 @@ namespace osu.Game.Rulesets.Edit
|
||||
Precision = 0.01,
|
||||
};
|
||||
|
||||
IBindable<double> IDistanceSnapProvider.DistanceSpacingMultiplier => DistanceSpacingMultiplier;
|
||||
Bindable<double> IDistanceSnapProvider.DistanceSpacingMultiplier => DistanceSpacingMultiplier;
|
||||
|
||||
private ExpandableSlider<double, SizeSlider<double>> distanceSpacingSlider;
|
||||
private ExpandableButton currentDistanceSpacingButton;
|
||||
private ExpandableSlider<double, SizeSlider<double>> distanceSpacingSlider = null!;
|
||||
private ExpandableButton currentDistanceSpacingButton = null!;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private OnScreenDisplay onScreenDisplay { get; set; }
|
||||
[Resolved]
|
||||
private Playfield playfield { get; set; } = null!;
|
||||
|
||||
protected readonly Bindable<TernaryState> DistanceSnapToggle = new Bindable<TernaryState>();
|
||||
[Resolved]
|
||||
private EditorClock editorClock { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBeatSnapProvider beatSnapProvider { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OnScreenDisplay? onScreenDisplay { get; set; }
|
||||
|
||||
public readonly Bindable<TernaryState> DistanceSnapToggle = new Bindable<TernaryState>();
|
||||
|
||||
private bool distanceSnapMomentary;
|
||||
|
||||
protected DistancedHitObjectComposer(Ruleset ruleset)
|
||||
: base(ruleset)
|
||||
{
|
||||
}
|
||||
private EditorToolboxGroup? toolboxGroup;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
public void AttachToToolbox(ExpandingToolboxContainer toolboxContainer)
|
||||
{
|
||||
RightToolbox.Add(new EditorToolboxGroup("snapping")
|
||||
if (toolboxGroup != null)
|
||||
throw new InvalidOperationException($"{nameof(AttachToToolbox)} may be called only once for a single {nameof(ComposerDistanceSnapProvider)} instance.");
|
||||
|
||||
toolboxContainer.Add(toolboxGroup = new EditorToolboxGroup("snapping")
|
||||
{
|
||||
Alpha = DistanceSpacingMultiplier.Disabled ? 0 : 1,
|
||||
Children = new Drawable[]
|
||||
@ -90,16 +97,42 @@ namespace osu.Game.Rulesets.Edit
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (DistanceSpacingMultiplier.Disabled)
|
||||
{
|
||||
distanceSpacingSlider.Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
DistanceSpacingMultiplier.Value = editorBeatmap.BeatmapInfo.DistanceSpacing;
|
||||
DistanceSpacingMultiplier.BindValueChanged(multiplier =>
|
||||
{
|
||||
distanceSpacingSlider.ContractedLabelText = $"D. S. ({multiplier.NewValue:0.##x})";
|
||||
distanceSpacingSlider.ExpandedLabelText = $"Distance Spacing ({multiplier.NewValue:0.##x})";
|
||||
|
||||
if (multiplier.NewValue != multiplier.OldValue)
|
||||
onScreenDisplay?.Display(new DistanceSpacingToast(multiplier.NewValue.ToLocalisableString(@"0.##x"), multiplier));
|
||||
|
||||
editorBeatmap.BeatmapInfo.DistanceSpacing = multiplier.NewValue;
|
||||
}, true);
|
||||
|
||||
// Manual binding to handle enabling distance spacing when the slider is interacted with.
|
||||
distanceSpacingSlider.Current.BindValueChanged(spacing =>
|
||||
{
|
||||
DistanceSpacingMultiplier.Value = spacing.NewValue;
|
||||
DistanceSnapToggle.Value = TernaryState.True;
|
||||
});
|
||||
DistanceSpacingMultiplier.BindValueChanged(spacing => distanceSpacingSlider.Current.Value = spacing.NewValue);
|
||||
}
|
||||
|
||||
private (HitObject before, HitObject after)? getObjectsOnEitherSideOfCurrentTime()
|
||||
{
|
||||
HitObject lastBefore = Playfield.HitObjectContainer.AliveObjects.LastOrDefault(h => h.HitObject.StartTime < EditorClock.CurrentTime)?.HitObject;
|
||||
HitObject? lastBefore = playfield.HitObjectContainer.AliveObjects.LastOrDefault(h => h.HitObject.StartTime < editorClock.CurrentTime)?.HitObject;
|
||||
|
||||
if (lastBefore == null)
|
||||
return null;
|
||||
|
||||
HitObject firstAfter = Playfield.HitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime >= EditorClock.CurrentTime)?.HitObject;
|
||||
HitObject? firstAfter = playfield.HitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime >= editorClock.CurrentTime)?.HitObject;
|
||||
|
||||
if (firstAfter == null)
|
||||
return null;
|
||||
@ -138,41 +171,10 @@ namespace osu.Game.Rulesets.Edit
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (DistanceSpacingMultiplier.Disabled)
|
||||
{
|
||||
distanceSpacingSlider.Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
DistanceSpacingMultiplier.Value = EditorBeatmap.BeatmapInfo.DistanceSpacing;
|
||||
DistanceSpacingMultiplier.BindValueChanged(multiplier =>
|
||||
{
|
||||
distanceSpacingSlider.ContractedLabelText = $"D. S. ({multiplier.NewValue:0.##x})";
|
||||
distanceSpacingSlider.ExpandedLabelText = $"Distance Spacing ({multiplier.NewValue:0.##x})";
|
||||
|
||||
if (multiplier.NewValue != multiplier.OldValue)
|
||||
onScreenDisplay?.Display(new DistanceSpacingToast(multiplier.NewValue.ToLocalisableString(@"0.##x"), multiplier));
|
||||
|
||||
EditorBeatmap.BeatmapInfo.DistanceSpacing = multiplier.NewValue;
|
||||
}, true);
|
||||
|
||||
// Manual binding to handle enabling distance spacing when the slider is interacted with.
|
||||
distanceSpacingSlider.Current.BindValueChanged(spacing =>
|
||||
{
|
||||
DistanceSpacingMultiplier.Value = spacing.NewValue;
|
||||
DistanceSnapToggle.Value = TernaryState.True;
|
||||
});
|
||||
DistanceSpacingMultiplier.BindValueChanged(spacing => distanceSpacingSlider.Current.Value = spacing.NewValue);
|
||||
}
|
||||
|
||||
protected override IEnumerable<TernaryButton> CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
|
||||
public IEnumerable<TernaryButton> CreateTernaryButtons() => new[]
|
||||
{
|
||||
new TernaryButton(DistanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler })
|
||||
});
|
||||
};
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
@ -242,26 +244,28 @@ namespace osu.Game.Rulesets.Edit
|
||||
return true;
|
||||
}
|
||||
|
||||
#region IDistanceSnapProvider
|
||||
|
||||
public virtual float GetBeatSnapDistanceAt(HitObject referenceObject, bool useReferenceSliderVelocity = true)
|
||||
{
|
||||
return (float)(100 * (useReferenceSliderVelocity && referenceObject is IHasSliderVelocity hasSliderVelocity ? hasSliderVelocity.SliderVelocityMultiplier : 1) * EditorBeatmap.Difficulty.SliderMultiplier * 1
|
||||
/ BeatSnapProvider.BeatDivisor);
|
||||
return (float)(100 * (useReferenceSliderVelocity && referenceObject is IHasSliderVelocity hasSliderVelocity ? hasSliderVelocity.SliderVelocityMultiplier : 1) * editorBeatmap.Difficulty.SliderMultiplier * 1
|
||||
/ beatSnapProvider.BeatDivisor);
|
||||
}
|
||||
|
||||
public virtual float DurationToDistance(HitObject referenceObject, double duration)
|
||||
{
|
||||
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime);
|
||||
double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime);
|
||||
return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceObject));
|
||||
}
|
||||
|
||||
public virtual double DistanceToDuration(HitObject referenceObject, float distance)
|
||||
{
|
||||
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime);
|
||||
double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime);
|
||||
return distance / GetBeatSnapDistanceAt(referenceObject) * beatLength;
|
||||
}
|
||||
|
||||
public virtual double FindSnappedDuration(HitObject referenceObject, float distance)
|
||||
=> BeatSnapProvider.SnapTime(referenceObject.StartTime + DistanceToDuration(referenceObject, distance), referenceObject.StartTime) - referenceObject.StartTime;
|
||||
=> beatSnapProvider.SnapTime(referenceObject.StartTime + DistanceToDuration(referenceObject, distance), referenceObject.StartTime) - referenceObject.StartTime;
|
||||
|
||||
public virtual float FindSnappedDistance(HitObject referenceObject, float distance)
|
||||
{
|
||||
@ -269,9 +273,9 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
double actualDuration = startTime + DistanceToDuration(referenceObject, distance);
|
||||
|
||||
double snappedEndTime = BeatSnapProvider.SnapTime(actualDuration, startTime);
|
||||
double snappedEndTime = beatSnapProvider.SnapTime(actualDuration, startTime);
|
||||
|
||||
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(startTime);
|
||||
double beatLength = beatSnapProvider.GetBeatLengthAtTime(startTime);
|
||||
|
||||
// we don't want to exceed the actual duration and snap to a point in the future.
|
||||
// as we are snapping to beat length via SnapTime (which will round-to-nearest), check for snapping in the forward direction and reverse it.
|
||||
@ -281,6 +285,8 @@ namespace osu.Game.Rulesets.Edit
|
||||
return DurationToDistance(referenceObject, snappedEndTime - startTime);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private partial class DistanceSpacingToast : Toast
|
||||
{
|
||||
private readonly ValueChangedEvent<double> change;
|
@ -12,14 +12,14 @@ namespace osu.Game.Rulesets.Edit
|
||||
/// A snap provider which given a reference hit object and proposed distance from it, offers a more correct duration or distance value.
|
||||
/// </summary>
|
||||
[Cached]
|
||||
public interface IDistanceSnapProvider : IPositionSnapProvider
|
||||
public interface IDistanceSnapProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// A multiplier which changes the ratio of distance travelled per time unit.
|
||||
/// Importantly, this is provided for manual usage, and not multiplied into any of the methods exposed by this interface.
|
||||
/// </summary>
|
||||
/// <seealso cref="BeatmapInfo.DistanceSpacing"/>
|
||||
IBindable<double> DistanceSpacingMultiplier { get; }
|
||||
Bindable<double> DistanceSpacingMultiplier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the distance between two points within a timing point that are one beat length apart.
|
||||
|
@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
@ -18,6 +19,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
where TObject : HitObject
|
||||
{
|
||||
private readonly Bindable<TernaryState> showSpeedChanges = new Bindable<TernaryState>();
|
||||
private Bindable<bool> configShowSpeedChanges = null!;
|
||||
|
||||
protected ScrollingHitObjectComposer(Ruleset ruleset)
|
||||
: base(ruleset)
|
||||
@ -25,7 +27,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
if (DrawableRuleset is ISupportConstantAlgorithmToggle toggleRuleset)
|
||||
{
|
||||
@ -44,7 +46,16 @@ namespace osu.Game.Rulesets.Edit
|
||||
},
|
||||
});
|
||||
|
||||
showSpeedChanges.BindValueChanged(state => toggleRuleset.ShowSpeedChanges.Value = state.NewValue == TernaryState.True, true);
|
||||
configShowSpeedChanges = config.GetBindable<bool>(OsuSetting.EditorShowSpeedChanges);
|
||||
configShowSpeedChanges.BindValueChanged(enabled => showSpeedChanges.Value = enabled.NewValue ? TernaryState.True : TernaryState.False, true);
|
||||
|
||||
showSpeedChanges.BindValueChanged(state =>
|
||||
{
|
||||
bool enabled = state.NewValue == TernaryState.True;
|
||||
|
||||
toggleRuleset.ShowSpeedChanges.Value = enabled;
|
||||
configShowSpeedChanges.Value = enabled;
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +71,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
// Apply a fixed rate change when missing, allowing the player to catch up when the rate is too fast.
|
||||
private const double rate_change_on_miss = 0.95d;
|
||||
|
||||
private IAdjustableAudioComponent? track;
|
||||
private double targetRate = 1d;
|
||||
|
||||
/// <summary>
|
||||
@ -123,24 +122,27 @@ namespace osu.Game.Rulesets.Mods
|
||||
/// </summary>
|
||||
private readonly Dictionary<HitObject, double> ratesForRewinding = new Dictionary<HitObject, double>();
|
||||
|
||||
private readonly RateAdjustModHelper rateAdjustHelper;
|
||||
|
||||
public ModAdaptiveSpeed()
|
||||
{
|
||||
rateAdjustHelper = new RateAdjustModHelper(SpeedChange);
|
||||
rateAdjustHelper.HandleAudioAdjustments(AdjustPitch);
|
||||
|
||||
InitialRate.BindValueChanged(val =>
|
||||
{
|
||||
SpeedChange.Value = val.NewValue;
|
||||
targetRate = val.NewValue;
|
||||
});
|
||||
AdjustPitch.BindValueChanged(adjustPitchChanged);
|
||||
}
|
||||
|
||||
public void ApplyToTrack(IAdjustableAudioComponent track)
|
||||
{
|
||||
this.track = track;
|
||||
|
||||
InitialRate.TriggerChange();
|
||||
AdjustPitch.TriggerChange();
|
||||
recentRates.Clear();
|
||||
recentRates.AddRange(Enumerable.Repeat(InitialRate.Value, recent_rate_count));
|
||||
|
||||
rateAdjustHelper.ApplyToTrack(track);
|
||||
}
|
||||
|
||||
public void ApplyToSample(IAdjustableAudioComponent sample)
|
||||
@ -199,15 +201,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustPitchChanged(ValueChangedEvent<bool> adjustPitchSetting)
|
||||
{
|
||||
track?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange);
|
||||
track?.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange);
|
||||
}
|
||||
|
||||
private AdjustableProperty adjustmentForPitchSetting(bool adjustPitchSettingValue)
|
||||
=> adjustPitchSettingValue ? AdjustableProperty.Frequency : AdjustableProperty.Tempo;
|
||||
|
||||
private IEnumerable<HitObject> getAllApplicableHitObjects(IEnumerable<HitObject> hitObjects)
|
||||
{
|
||||
foreach (var hitObject in hitObjects)
|
||||
|
@ -5,21 +5,36 @@ using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
public abstract class ModDaycore : ModHalfTime
|
||||
public abstract class ModDaycore : ModRateAdjust
|
||||
{
|
||||
public override string Name => "Daycore";
|
||||
public override string Acronym => "DC";
|
||||
public override IconUsage? Icon => null;
|
||||
public override ModType Type => ModType.DifficultyReduction;
|
||||
public override LocalisableString Description => "Whoaaaaa...";
|
||||
|
||||
[SettingSource("Speed decrease", "The actual decrease to apply")]
|
||||
public override BindableNumber<double> SpeedChange { get; } = new BindableDouble(0.75)
|
||||
{
|
||||
MinValue = 0.5,
|
||||
MaxValue = 0.99,
|
||||
Precision = 0.01,
|
||||
};
|
||||
|
||||
private readonly BindableNumber<double> tempoAdjust = new BindableDouble(1);
|
||||
private readonly BindableNumber<double> freqAdjust = new BindableDouble(1);
|
||||
private readonly RateAdjustModHelper rateAdjustHelper;
|
||||
|
||||
protected ModDaycore()
|
||||
{
|
||||
rateAdjustHelper = new RateAdjustModHelper(SpeedChange);
|
||||
|
||||
// intentionally not deferring the speed change handling to `RateAdjustModHelper`
|
||||
// as the expected result of operation is not the same (daycore should preserve constant pitch).
|
||||
SpeedChange.BindValueChanged(val =>
|
||||
{
|
||||
freqAdjust.Value = SpeedChange.Default;
|
||||
@ -29,9 +44,10 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public override void ApplyToTrack(IAdjustableAudioComponent track)
|
||||
{
|
||||
// base.ApplyToTrack() intentionally not called (different tempo adjustment is applied)
|
||||
track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust);
|
||||
track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust);
|
||||
}
|
||||
|
||||
public override double ScoreMultiplier => rateAdjustHelper.ScoreMultiplier;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
@ -26,21 +27,22 @@ namespace osu.Game.Rulesets.Mods
|
||||
Precision = 0.01,
|
||||
};
|
||||
|
||||
public override double ScoreMultiplier
|
||||
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
|
||||
public virtual BindableBool AdjustPitch { get; } = new BindableBool();
|
||||
|
||||
private readonly RateAdjustModHelper rateAdjustHelper;
|
||||
|
||||
protected ModDoubleTime()
|
||||
{
|
||||
get
|
||||
{
|
||||
// Round to the nearest multiple of 0.1.
|
||||
double value = (int)(SpeedChange.Value * 10) / 10.0;
|
||||
|
||||
// Offset back to 0.
|
||||
value -= 1;
|
||||
|
||||
// Each 0.1 multiple changes score multiplier by 0.02.
|
||||
value /= 5;
|
||||
|
||||
return 1 + value;
|
||||
}
|
||||
rateAdjustHelper = new RateAdjustModHelper(SpeedChange);
|
||||
rateAdjustHelper.HandleAudioAdjustments(AdjustPitch);
|
||||
}
|
||||
|
||||
public override void ApplyToTrack(IAdjustableAudioComponent track)
|
||||
{
|
||||
rateAdjustHelper.ApplyToTrack(track);
|
||||
}
|
||||
|
||||
public override double ScoreMultiplier => rateAdjustHelper.ScoreMultiplier;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
@ -26,18 +27,22 @@ namespace osu.Game.Rulesets.Mods
|
||||
Precision = 0.01,
|
||||
};
|
||||
|
||||
public override double ScoreMultiplier
|
||||
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
|
||||
public virtual BindableBool AdjustPitch { get; } = new BindableBool();
|
||||
|
||||
private readonly RateAdjustModHelper rateAdjustHelper;
|
||||
|
||||
protected ModHalfTime()
|
||||
{
|
||||
get
|
||||
{
|
||||
// Round to the nearest multiple of 0.1.
|
||||
double value = (int)(SpeedChange.Value * 10) / 10.0;
|
||||
|
||||
// Offset back to 0.
|
||||
value -= 1;
|
||||
|
||||
return 1 + value;
|
||||
}
|
||||
rateAdjustHelper = new RateAdjustModHelper(SpeedChange);
|
||||
rateAdjustHelper.HandleAudioAdjustments(AdjustPitch);
|
||||
}
|
||||
|
||||
public override void ApplyToTrack(IAdjustableAudioComponent track)
|
||||
{
|
||||
rateAdjustHelper.ApplyToTrack(track);
|
||||
}
|
||||
|
||||
public override double ScoreMultiplier => rateAdjustHelper.ScoreMultiplier;
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ using osu.Framework.Localisation;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -19,22 +20,33 @@ using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
public abstract class ModNightcore : ModDoubleTime
|
||||
public abstract class ModNightcore : ModRateAdjust
|
||||
{
|
||||
public override string Name => "Nightcore";
|
||||
public override string Acronym => "NC";
|
||||
public override IconUsage? Icon => OsuIcon.ModNightcore;
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
public override LocalisableString Description => "Uguuuuuuuu...";
|
||||
}
|
||||
|
||||
public abstract partial class ModNightcore<TObject> : ModNightcore, IApplicableToDrawableRuleset<TObject>
|
||||
where TObject : HitObject
|
||||
{
|
||||
[SettingSource("Speed increase", "The actual increase to apply")]
|
||||
public override BindableNumber<double> SpeedChange { get; } = new BindableDouble(1.5)
|
||||
{
|
||||
MinValue = 1.01,
|
||||
MaxValue = 2,
|
||||
Precision = 0.01,
|
||||
};
|
||||
|
||||
private readonly BindableNumber<double> tempoAdjust = new BindableDouble(1);
|
||||
private readonly BindableNumber<double> freqAdjust = new BindableDouble(1);
|
||||
|
||||
private readonly RateAdjustModHelper rateAdjustHelper;
|
||||
|
||||
protected ModNightcore()
|
||||
{
|
||||
rateAdjustHelper = new RateAdjustModHelper(SpeedChange);
|
||||
|
||||
// intentionally not deferring the speed change handling to `RateAdjustModHelper`
|
||||
// as the expected result of operation is not the same (nightcore should preserve constant pitch).
|
||||
SpeedChange.BindValueChanged(val =>
|
||||
{
|
||||
freqAdjust.Value = SpeedChange.Default;
|
||||
@ -44,11 +56,16 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public override void ApplyToTrack(IAdjustableAudioComponent track)
|
||||
{
|
||||
// base.ApplyToTrack() intentionally not called (different tempo adjustment is applied)
|
||||
track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust);
|
||||
track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust);
|
||||
}
|
||||
|
||||
public override double ScoreMultiplier => rateAdjustHelper.ScoreMultiplier;
|
||||
}
|
||||
|
||||
public abstract partial class ModNightcore<TObject> : ModNightcore, IApplicableToDrawableRuleset<TObject>
|
||||
where TObject : HitObject
|
||||
{
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<TObject> drawableRuleset)
|
||||
{
|
||||
drawableRuleset.Overlays.Add(new NightcoreBeatContainer());
|
||||
|
@ -13,10 +13,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public abstract BindableNumber<double> SpeedChange { get; }
|
||||
|
||||
public virtual void ApplyToTrack(IAdjustableAudioComponent track)
|
||||
{
|
||||
track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange);
|
||||
}
|
||||
public abstract void ApplyToTrack(IAdjustableAudioComponent track);
|
||||
|
||||
public virtual void ApplyToSample(IAdjustableAudioComponent sample)
|
||||
{
|
||||
|
@ -44,21 +44,21 @@ namespace osu.Game.Rulesets.Mods
|
||||
Precision = 0.01,
|
||||
};
|
||||
|
||||
private IAdjustableAudioComponent? track;
|
||||
private readonly RateAdjustModHelper rateAdjustHelper;
|
||||
|
||||
protected ModTimeRamp()
|
||||
{
|
||||
rateAdjustHelper = new RateAdjustModHelper(SpeedChange);
|
||||
rateAdjustHelper.HandleAudioAdjustments(AdjustPitch);
|
||||
|
||||
// for preview purpose at song select. eventually we'll want to be able to update every frame.
|
||||
FinalRate.BindValueChanged(_ => applyRateAdjustment(double.PositiveInfinity), true);
|
||||
AdjustPitch.BindValueChanged(applyPitchAdjustment);
|
||||
}
|
||||
|
||||
public void ApplyToTrack(IAdjustableAudioComponent track)
|
||||
{
|
||||
this.track = track;
|
||||
|
||||
rateAdjustHelper.ApplyToTrack(track);
|
||||
FinalRate.TriggerChange();
|
||||
AdjustPitch.TriggerChange();
|
||||
}
|
||||
|
||||
public void ApplyToSample(IAdjustableAudioComponent sample)
|
||||
@ -95,16 +95,5 @@ namespace osu.Game.Rulesets.Mods
|
||||
/// Adjust the rate along the specified ramp.
|
||||
/// </summary>
|
||||
private void applyRateAdjustment(double time) => SpeedChange.Value = ApplyToRate(time);
|
||||
|
||||
private void applyPitchAdjustment(ValueChangedEvent<bool> adjustPitchSetting)
|
||||
{
|
||||
// remove existing old adjustment
|
||||
track?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange);
|
||||
|
||||
track?.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange);
|
||||
}
|
||||
|
||||
private AdjustableProperty adjustmentForPitchSetting(bool adjustPitchSettingValue)
|
||||
=> adjustPitchSettingValue ? AdjustableProperty.Frequency : AdjustableProperty.Tempo;
|
||||
}
|
||||
}
|
||||
|
84
osu.Game/Rulesets/Mods/RateAdjustModHelper.cs
Normal file
84
osu.Game/Rulesets/Mods/RateAdjustModHelper.cs
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides common functionality shared across various rate adjust mods.
|
||||
/// </summary>
|
||||
public class RateAdjustModHelper : IApplicableToTrack
|
||||
{
|
||||
public readonly IBindableNumber<double> SpeedChange;
|
||||
|
||||
private IAdjustableAudioComponent? track;
|
||||
|
||||
private BindableBool? adjustPitch;
|
||||
|
||||
/// <summary>
|
||||
/// The score multiplier for the current <see cref="SpeedChange"/>.
|
||||
/// </summary>
|
||||
public double ScoreMultiplier
|
||||
{
|
||||
get
|
||||
{
|
||||
// Round to the nearest multiple of 0.1.
|
||||
double value = (int)(SpeedChange.Value * 10) / 10.0;
|
||||
|
||||
// Offset back to 0.
|
||||
value -= 1;
|
||||
|
||||
if (SpeedChange.Value >= 1)
|
||||
value /= 5;
|
||||
|
||||
return 1 + value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new <see cref="RateAdjustModHelper"/>.
|
||||
/// </summary>
|
||||
/// <param name="speedChange">The main speed adjust parameter which is exposed to the user.</param>
|
||||
public RateAdjustModHelper(IBindableNumber<double> speedChange)
|
||||
{
|
||||
SpeedChange = speedChange;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup audio track adjustments for a rate adjust mod.
|
||||
/// Importantly, <see cref="ApplyToTrack"/> must be called when a track is obtained/changed for this to work.
|
||||
/// </summary>
|
||||
/// <param name="adjustPitch">The "adjust pitch" setting as exposed to the user.</param>
|
||||
public void HandleAudioAdjustments(BindableBool adjustPitch)
|
||||
{
|
||||
this.adjustPitch = adjustPitch;
|
||||
|
||||
// When switching between pitch adjust, we need to update adjustments to time-shift or frequency-scale.
|
||||
adjustPitch.BindValueChanged(adjustPitchSetting =>
|
||||
{
|
||||
track?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange);
|
||||
track?.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange);
|
||||
|
||||
AdjustableProperty adjustmentForPitchSetting(bool adjustPitchSettingValue)
|
||||
=> adjustPitchSettingValue ? AdjustableProperty.Frequency : AdjustableProperty.Tempo;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should be invoked when a track is obtained / changed.
|
||||
/// </summary>
|
||||
/// <param name="track">The new track.</param>
|
||||
/// <exception cref="InvalidOperationException">If this method is called before <see cref="HandleAudioAdjustments"/>.</exception>
|
||||
public void ApplyToTrack(IAdjustableAudioComponent track)
|
||||
{
|
||||
if (adjustPitch == null)
|
||||
throw new InvalidOperationException($"Must call {nameof(HandleAudioAdjustments)} first");
|
||||
|
||||
this.track = track;
|
||||
adjustPitch.TriggerChange();
|
||||
}
|
||||
}
|
||||
}
|
@ -23,11 +23,12 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
/// <summary>
|
||||
/// <see cref="ConvertSlider"/>s don't need a curve since they're converted to ruleset-specific hitobjects.
|
||||
/// </summary>
|
||||
public SliderPath Path { get; set; }
|
||||
public SliderPath Path { get; set; } = null!;
|
||||
|
||||
public double Distance => Path.Distance;
|
||||
|
||||
public IList<IList<HitSampleInfo>> NodeSamples { get; set; }
|
||||
public IList<IList<HitSampleInfo>> NodeSamples { get; set; } = null!;
|
||||
|
||||
public int RepeatCount { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
|
@ -186,7 +186,7 @@ namespace osu.Game.Rulesets.UI
|
||||
this.fallback = fallback;
|
||||
}
|
||||
|
||||
public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||
public override Texture? Get(string name, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||
=> primary.Get(name, wrapModeS, wrapModeT) ?? fallback.Get(name, wrapModeS, wrapModeT);
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
|
@ -218,6 +218,7 @@ namespace osu.Game.Rulesets.UI
|
||||
base.ReloadMappings(realmKeyBindings);
|
||||
|
||||
KeyBindings = KeyBindings.Where(b => RealmKeyBindingStore.CheckValidForGameplay(b.KeyCombination)).ToList();
|
||||
RealmKeyBindingStore.ClearDuplicateBindings(KeyBindings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Screens.Backgrounds
|
||||
AddInternal(new Background(textureName));
|
||||
}
|
||||
|
||||
public override bool Equals(BackgroundScreen other)
|
||||
public override bool Equals(BackgroundScreen? other)
|
||||
{
|
||||
if (other is BackgroundScreenCustom backgroundScreenCustom)
|
||||
return base.Equals(other) && textureName == backgroundScreenCustom.textureName;
|
||||
|
@ -383,7 +383,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
// Shrink the parent quad to give a bit of padding so the buttons don't stick *right* on the border.
|
||||
// AABBFloat assumes no rotation. one would hope the whole editor is not being rotated.
|
||||
var parentQuad = Parent.ScreenSpaceDrawQuad.AABBFloat.Shrink(ToLocalSpace(thisQuad.TopLeft + new Vector2(button_padding * 2)));
|
||||
var parentQuad = Parent!.ScreenSpaceDrawQuad.AABBFloat.Shrink(ToLocalSpace(thisQuad.TopLeft + new Vector2(button_padding * 2)));
|
||||
|
||||
float topExcess = thisQuad.TopLeft.Y - parentQuad.TopLeft.Y;
|
||||
float bottomExcess = parentQuad.BottomLeft.Y - thisQuad.BottomLeft.Y;
|
||||
@ -396,7 +396,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
buttons.Anchor = Anchor.BottomCentre;
|
||||
buttons.Origin = Anchor.BottomCentre;
|
||||
buttons.Y = Math.Min(0, ToLocalSpace(Parent.ScreenSpaceDrawQuad.BottomLeft).Y - DrawHeight);
|
||||
buttons.Y = Math.Min(0, ToLocalSpace(Parent!.ScreenSpaceDrawQuad.BottomLeft).Y - DrawHeight);
|
||||
}
|
||||
else if (topExcess > bottomExcess)
|
||||
{
|
||||
|
@ -95,11 +95,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
}
|
||||
|
||||
private Vector2 getFinalPosition() =>
|
||||
Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.Centre);
|
||||
Parent!.ToLocalSpace(facade.ScreenSpaceDrawQuad.Centre);
|
||||
|
||||
private Vector2 getFinalSize() =>
|
||||
Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.BottomRight)
|
||||
- Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.TopLeft);
|
||||
Parent!.ToLocalSpace(facade.ScreenSpaceDrawQuad.BottomRight)
|
||||
- Parent!.ToLocalSpace(facade.ScreenSpaceDrawQuad.TopLeft);
|
||||
|
||||
protected override bool OnClick(ClickEvent 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