mirror of
https://github.com/ppy/osu.git
synced 2024-11-15 11:47:24 +08:00
Merge branch 'master' into bat-mods
This commit is contained in:
commit
1b5d1347aa
@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1009.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1025.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||||
|
@ -5,7 +5,6 @@ using Android.Content.PM;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game;
|
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
namespace osu.Android
|
namespace osu.Android
|
||||||
@ -28,7 +27,7 @@ namespace osu.Android
|
|||||||
{
|
{
|
||||||
gameActivity.RunOnUiThread(() =>
|
gameActivity.RunOnUiThread(() =>
|
||||||
{
|
{
|
||||||
gameActivity.RequestedOrientation = userPlaying.NewValue != LocalUserPlayingState.NotPlaying ? ScreenOrientation.Locked : gameActivity.DefaultOrientation;
|
gameActivity.RequestedOrientation = userPlaying.NewValue == LocalUserPlayingState.Playing ? ScreenOrientation.Locked : gameActivity.DefaultOrientation;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default)
|
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default, Optional<bool> newEditorAutoBank = default)
|
||||||
=> new BananaHitSampleInfo(newVolume.GetOr(Volume));
|
=> new BananaHitSampleInfo(newVolume.GetOr(Volume));
|
||||||
|
|
||||||
public bool Equals(BananaHitSampleInfo? other)
|
public bool Equals(BananaHitSampleInfo? other)
|
||||||
|
@ -118,5 +118,45 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
AddAssert("all objects in last column", () => EditorBeatmap.HitObjects.All(ho => ((ManiaHitObject)ho).Column == 3));
|
AddAssert("all objects in last column", () => EditorBeatmap.HitObjects.All(ho => ((ManiaHitObject)ho).Column == 3));
|
||||||
AddAssert("all objects remain selected", () => EditorBeatmap.SelectedHitObjects.SequenceEqual(EditorBeatmap.HitObjects));
|
AddAssert("all objects remain selected", () => EditorBeatmap.SelectedHitObjects.SequenceEqual(EditorBeatmap.HitObjects));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOffScreenObjectsRemainSelectedOnHorizontalFlip()
|
||||||
|
{
|
||||||
|
AddStep("create objects", () =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 20; ++i)
|
||||||
|
EditorBeatmap.Add(new Note { StartTime = 1000 * i, Column = i % 4 });
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
|
AddStep("flip", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Key(Key.H);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("all objects remain selected", () => EditorBeatmap.SelectedHitObjects.SequenceEqual(EditorBeatmap.HitObjects));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOffScreenObjectsRemainSelectedOnVerticalFlip()
|
||||||
|
{
|
||||||
|
AddStep("create objects", () =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 20; ++i)
|
||||||
|
EditorBeatmap.Add(new Note { StartTime = 1000 * i, Column = i % 4 });
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
|
AddStep("flip", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Key(Key.J);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("all objects remain selected", () => EditorBeatmap.SelectedHitObjects.SequenceEqual(EditorBeatmap.HitObjects.Reverse()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,9 +54,8 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
int firstColumn = flipOverOrigin ? 0 : selectedObjects.Min(ho => ho.Column);
|
int firstColumn = flipOverOrigin ? 0 : selectedObjects.Min(ho => ho.Column);
|
||||||
int lastColumn = flipOverOrigin ? (int)EditorBeatmap.BeatmapInfo.Difficulty.CircleSize - 1 : selectedObjects.Max(ho => ho.Column);
|
int lastColumn = flipOverOrigin ? (int)EditorBeatmap.BeatmapInfo.Difficulty.CircleSize - 1 : selectedObjects.Max(ho => ho.Column);
|
||||||
|
|
||||||
EditorBeatmap.PerformOnSelection(hitObject =>
|
performOnSelection(maniaObject =>
|
||||||
{
|
{
|
||||||
var maniaObject = (ManiaHitObject)hitObject;
|
|
||||||
maniaPlayfield.Remove(maniaObject);
|
maniaPlayfield.Remove(maniaObject);
|
||||||
maniaObject.Column = firstColumn + (lastColumn - maniaObject.Column);
|
maniaObject.Column = firstColumn + (lastColumn - maniaObject.Column);
|
||||||
maniaPlayfield.Add(maniaObject);
|
maniaPlayfield.Add(maniaObject);
|
||||||
@ -71,7 +70,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
double selectionStartTime = selectedObjects.Min(ho => ho.StartTime);
|
double selectionStartTime = selectedObjects.Min(ho => ho.StartTime);
|
||||||
double selectionEndTime = selectedObjects.Max(ho => ho.GetEndTime());
|
double selectionEndTime = selectedObjects.Max(ho => ho.GetEndTime());
|
||||||
|
|
||||||
EditorBeatmap.PerformOnSelection(hitObject =>
|
performOnSelection(hitObject =>
|
||||||
{
|
{
|
||||||
hitObject.StartTime = selectionStartTime + (selectionEndTime - hitObject.GetEndTime());
|
hitObject.StartTime = selectionStartTime + (selectionEndTime - hitObject.GetEndTime());
|
||||||
});
|
});
|
||||||
@ -117,14 +116,21 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
|
|
||||||
columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn);
|
columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn);
|
||||||
|
|
||||||
EditorBeatmap.PerformOnSelection(h =>
|
performOnSelection(h =>
|
||||||
{
|
{
|
||||||
maniaPlayfield.Remove(h);
|
maniaPlayfield.Remove(h);
|
||||||
((ManiaHitObject)h).Column += columnDelta;
|
h.Column += columnDelta;
|
||||||
maniaPlayfield.Add(h);
|
maniaPlayfield.Add(h);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// `HitObjectUsageEventBuffer`'s usage transferal flows and the playfield's `SetKeepAlive()` functionality do not combine well with this operation's usage pattern,
|
private void performOnSelection(Action<ManiaHitObject> action)
|
||||||
|
{
|
||||||
|
var selectedObjects = EditorBeatmap.SelectedHitObjects.OfType<ManiaHitObject>().ToArray();
|
||||||
|
|
||||||
|
EditorBeatmap.PerformOnSelection(h => action.Invoke((ManiaHitObject)h));
|
||||||
|
|
||||||
|
// `HitObjectUsageEventBuffer`'s usage transferal flows and the playfield's `SetKeepAlive()` functionality do not combine well with mania's usage patterns,
|
||||||
// leading to selections being sometimes partially dropped if some of the objects being moved are off screen
|
// leading to selections being sometimes partially dropped if some of the objects being moved are off screen
|
||||||
// (check blame for detailed explanation).
|
// (check blame for detailed explanation).
|
||||||
// thus, ensure that selection is preserved manually.
|
// thus, ensure that selection is preserved manually.
|
||||||
|
87
osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderDrawing.cs
Normal file
87
osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderDrawing.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Edit.Components.RadioButtons;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public partial class TestSceneSliderDrawing : TestSceneOsuEditor
|
||||||
|
{
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTouchInputAfterTouchingComposeArea()
|
||||||
|
{
|
||||||
|
AddStep("tap circle", () => tap(this.ChildrenOfType<EditorRadioButton>().Single(b => b.Button.Label == "HitCircle")));
|
||||||
|
|
||||||
|
// this input is just for interacting with compose area
|
||||||
|
AddStep("tap playfield", () => tap(this.ChildrenOfType<Playfield>().Single()));
|
||||||
|
|
||||||
|
AddStep("move current time", () => InputManager.Key(Key.Right));
|
||||||
|
|
||||||
|
AddStep("tap to place circle", () => tap(this.ChildrenOfType<Playfield>().Single().ToScreenSpace(new Vector2(10, 10))));
|
||||||
|
AddAssert("circle placed correctly", () =>
|
||||||
|
{
|
||||||
|
var circle = (HitCircle)EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate);
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(circle.Position.X, Is.EqualTo(10f).Within(0.01f));
|
||||||
|
Assert.That(circle.Position.Y, Is.EqualTo(10f).Within(0.01f));
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("tap slider", () => tap(this.ChildrenOfType<EditorRadioButton>().Single(b => b.Button.Label == "Slider")));
|
||||||
|
|
||||||
|
// this input is just for interacting with compose area
|
||||||
|
AddStep("tap playfield", () => tap(this.ChildrenOfType<Playfield>().Single()));
|
||||||
|
|
||||||
|
AddStep("move current time", () => InputManager.Key(Key.Right));
|
||||||
|
|
||||||
|
AddStep("hold to draw slider", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, this.ChildrenOfType<Playfield>().Single().ToScreenSpace(new Vector2(50, 20)))));
|
||||||
|
AddStep("drag to draw", () => InputManager.MoveTouchTo(new Touch(TouchSource.Touch1, this.ChildrenOfType<Playfield>().Single().ToScreenSpace(new Vector2(200, 50)))));
|
||||||
|
AddAssert("selection not initiated", () => this.ChildrenOfType<DragBox>().All(d => d.State == Visibility.Hidden));
|
||||||
|
AddStep("end", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, InputManager.CurrentState.Touch.GetTouchPosition(TouchSource.Touch1)!.Value)));
|
||||||
|
AddAssert("slider placed correctly", () =>
|
||||||
|
{
|
||||||
|
var slider = (Slider)EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate);
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(slider.Position.X, Is.EqualTo(50f).Within(0.01f));
|
||||||
|
Assert.That(slider.Position.Y, Is.EqualTo(20f).Within(0.01f));
|
||||||
|
Assert.That(slider.Path.ControlPoints.Count, Is.EqualTo(2));
|
||||||
|
Assert.That(slider.Path.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
|
||||||
|
|
||||||
|
// the final position may be slightly off from the mouse position when drawing, account for that.
|
||||||
|
Assert.That(slider.Path.ControlPoints[1].Position.X, Is.EqualTo(150).Within(5));
|
||||||
|
Assert.That(slider.Path.ControlPoints[1].Position.Y, Is.EqualTo(30).Within(5));
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tap(Drawable drawable) => tap(drawable.ScreenSpaceDrawQuad.Centre);
|
||||||
|
|
||||||
|
private void tap(Vector2 position)
|
||||||
|
{
|
||||||
|
InputManager.BeginTouch(new Touch(TouchSource.Touch1, position));
|
||||||
|
InputManager.EndTouch(new Touch(TouchSource.Touch1, position));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
@ -392,6 +393,29 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
assertFinalControlPointType(3, null);
|
assertFinalControlPointType(3, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderDrawingViaTouch()
|
||||||
|
{
|
||||||
|
Vector2 startPoint = new Vector2(200);
|
||||||
|
|
||||||
|
AddStep("move mouse to a random point", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(Vector2.Zero)));
|
||||||
|
AddStep("begin touch at start point", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, InputManager.ToScreenSpace(startPoint))));
|
||||||
|
|
||||||
|
for (int i = 1; i < 20; i++)
|
||||||
|
addTouchMovementStep(startPoint + new Vector2(i * 40, MathF.Sin(i * MathF.PI / 5) * 50));
|
||||||
|
|
||||||
|
AddStep("release touch at end point", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, InputManager.CurrentState.Touch.GetTouchPosition(TouchSource.Touch1)!.Value)));
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertLength(808, tolerance: 10);
|
||||||
|
assertControlPointCount(5);
|
||||||
|
assertFinalControlPointType(0, PathType.BSpline(4));
|
||||||
|
assertFinalControlPointType(1, null);
|
||||||
|
assertFinalControlPointType(2, null);
|
||||||
|
assertFinalControlPointType(3, null);
|
||||||
|
assertFinalControlPointType(4, null);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPlacePerfectCurveSegmentAlmostLinearlyExterior()
|
public void TestPlacePerfectCurveSegmentAlmostLinearlyExterior()
|
||||||
{
|
{
|
||||||
@ -492,6 +516,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position)));
|
private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position)));
|
||||||
|
|
||||||
|
private void addTouchMovementStep(Vector2 position) => AddStep($"move touch1 to {position}", () => InputManager.MoveTouchTo(new Touch(TouchSource.Touch1, InputManager.ToScreenSpace(position))));
|
||||||
|
|
||||||
private void addClickStep(MouseButton button)
|
private void addClickStep(MouseButton button)
|
||||||
{
|
{
|
||||||
AddStep($"click {button}", () => InputManager.Click(button));
|
AddStep($"click {button}", () => InputManager.Click(button));
|
||||||
|
@ -180,7 +180,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
{
|
{
|
||||||
if (slider == null) return;
|
if (slider == null) return;
|
||||||
|
|
||||||
sample = new HitSampleInfo("hitwhistle", HitSampleInfo.BANK_SOFT, volume: 70);
|
sample = new HitSampleInfo("hitwhistle", HitSampleInfo.BANK_SOFT, volume: 70, editorAutoBank: false);
|
||||||
slider.Samples.Add(sample.With());
|
slider.Samples.Add(sample.With());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
55
osu.Game.Rulesets.Osu.Tests/Editor/TestSceneToolSwitching.cs
Normal file
55
osu.Game.Rulesets.Osu.Tests/Editor/TestSceneToolSwitching.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// 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.Testing;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||||
|
{
|
||||||
|
public partial class TestSceneToolSwitching : EditorTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderAnchorMoveOperationEndsOnSwitchingTool()
|
||||||
|
{
|
||||||
|
var initialPosition = Vector2.Zero;
|
||||||
|
|
||||||
|
AddStep("store original anchor position", () => initialPosition = EditorBeatmap.HitObjects.OfType<Slider>().First().Path.ControlPoints.ElementAt(1).Position);
|
||||||
|
AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.OfType<Slider>().First()));
|
||||||
|
AddStep("move to second anchor", () => InputManager.MoveMouseTo(this.ChildrenOfType<PathControlPointPiece<Slider>>().ElementAt(1)));
|
||||||
|
AddStep("start dragging", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
AddStep("drag away", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(0, -200)));
|
||||||
|
AddStep("switch tool", () => InputManager.PressButton(MouseButton.Button1));
|
||||||
|
AddStep("undo", () => Editor.Undo());
|
||||||
|
AddAssert("anchor back at original position",
|
||||||
|
() => EditorBeatmap.HitObjects.OfType<Slider>().First().Path.ControlPoints.ElementAt(1).Position,
|
||||||
|
() => Is.EqualTo(initialPosition));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderAnchorCreationOperationEndsOnSwitchingTool()
|
||||||
|
{
|
||||||
|
AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.OfType<Slider>().First()));
|
||||||
|
AddStep("move to second anchor", () => InputManager.MoveMouseTo(this.ChildrenOfType<PathControlPointPiece<Slider>>().ElementAt(1), new Vector2(-50, 0)));
|
||||||
|
AddStep("quick-create anchor", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
AddStep("drag away", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(0, -200)));
|
||||||
|
AddStep("switch tool", () => InputManager.PressKey(Key.Number3));
|
||||||
|
AddStep("drag away further", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(0, -200)));
|
||||||
|
AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.OfType<Slider>().First()));
|
||||||
|
AddStep("undo", () => Editor.Undo());
|
||||||
|
AddAssert("slider has three anchors again", () => EditorBeatmap.HitObjects.OfType<Slider>().First().Path.ControlPoints, () => Has.Count.EqualTo(3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
private int countMeh;
|
private int countMeh;
|
||||||
private int countMiss;
|
private int countMiss;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Missed slider ticks that includes missed reverse arrows. Will only be correct on non-classic scores
|
||||||
|
/// </summary>
|
||||||
|
private int countSliderTickMiss;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Amount of missed slider tails that don't break combo. Will only be correct on non-classic scores
|
||||||
|
/// </summary>
|
||||||
|
private int countSliderEndsDropped;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Estimated total amount of combo breaks
|
||||||
|
/// </summary>
|
||||||
private double effectiveMissCount;
|
private double effectiveMissCount;
|
||||||
|
|
||||||
public OsuPerformanceCalculator()
|
public OsuPerformanceCalculator()
|
||||||
@ -44,7 +57,37 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
|
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
|
||||||
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
|
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
|
||||||
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||||
effectiveMissCount = calculateEffectiveMissCount(osuAttributes);
|
countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit);
|
||||||
|
countSliderTickMiss = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss);
|
||||||
|
|
||||||
|
if (osuAttributes.SliderCount > 0)
|
||||||
|
{
|
||||||
|
if (usingClassicSliderAccuracy)
|
||||||
|
{
|
||||||
|
// Consider that full combo is maximum combo minus dropped slider tails since they don't contribute to combo but also don't break it
|
||||||
|
// In classic scores we can't know the amount of dropped sliders so we estimate to 10% of all sliders on the map
|
||||||
|
double fullComboThreshold = attributes.MaxCombo - 0.1 * osuAttributes.SliderCount;
|
||||||
|
|
||||||
|
if (scoreMaxCombo < fullComboThreshold)
|
||||||
|
effectiveMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo);
|
||||||
|
|
||||||
|
// In classic scores there can't be more misses than a sum of all non-perfect judgements
|
||||||
|
effectiveMissCount = Math.Min(effectiveMissCount, totalImperfectHits);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double fullComboThreshold = attributes.MaxCombo - countSliderEndsDropped;
|
||||||
|
|
||||||
|
if (scoreMaxCombo < fullComboThreshold)
|
||||||
|
effectiveMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo);
|
||||||
|
|
||||||
|
// Combine regular misses with tick misses since tick misses break combo as well
|
||||||
|
effectiveMissCount = Math.Min(effectiveMissCount, countSliderTickMiss + countMiss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
effectiveMissCount = Math.Max(countMiss, effectiveMissCount);
|
||||||
|
effectiveMissCount = Math.Min(totalHits, effectiveMissCount);
|
||||||
|
|
||||||
double multiplier = PERFORMANCE_BASE_MULTIPLIER;
|
double multiplier = PERFORMANCE_BASE_MULTIPLIER;
|
||||||
|
|
||||||
@ -124,8 +167,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
if (attributes.SliderCount > 0)
|
if (attributes.SliderCount > 0)
|
||||||
{
|
{
|
||||||
double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders);
|
double estimateImproperlyFollowedDifficultSliders;
|
||||||
double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor;
|
|
||||||
|
if (usingClassicSliderAccuracy)
|
||||||
|
{
|
||||||
|
// When the score is considered classic (regardless if it was made on old client or not) we consider all missing combo to be dropped difficult sliders
|
||||||
|
int maximumPossibleDroppedSliders = totalImperfectHits;
|
||||||
|
estimateImproperlyFollowedDifficultSliders = Math.Clamp(Math.Min(maximumPossibleDroppedSliders, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We add tick misses here since they too mean that the player didn't follow the slider properly
|
||||||
|
// We however aren't adding misses here because missing slider heads has a harsh penalty by itself and doesn't mean that the rest of the slider wasn't followed properly
|
||||||
|
estimateImproperlyFollowedDifficultSliders = Math.Min(countSliderEndsDropped + countSliderTickMiss, estimateDifficultSliders);
|
||||||
|
}
|
||||||
|
|
||||||
|
double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateImproperlyFollowedDifficultSliders / estimateDifficultSliders, 3) + attributes.SliderFactor;
|
||||||
aimValue *= sliderNerfFactor;
|
aimValue *= sliderNerfFactor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,29 +304,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
return flashlightValue;
|
return flashlightValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes)
|
|
||||||
{
|
|
||||||
// Guess the number of misses + slider breaks from combo
|
|
||||||
double comboBasedMissCount = 0.0;
|
|
||||||
|
|
||||||
if (attributes.SliderCount > 0)
|
|
||||||
{
|
|
||||||
double fullComboThreshold = attributes.MaxCombo - 0.1 * attributes.SliderCount;
|
|
||||||
if (scoreMaxCombo < fullComboThreshold)
|
|
||||||
comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clamp miss count to maximum amount of possible breaks
|
|
||||||
comboBasedMissCount = Math.Min(comboBasedMissCount, countOk + countMeh + countMiss);
|
|
||||||
|
|
||||||
return Math.Max(countMiss, comboBasedMissCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Miss penalty assumes that a player will miss on the hardest parts of a map,
|
// Miss penalty assumes that a player will miss on the hardest parts of a map,
|
||||||
// so we use the amount of relatively difficult sections to adjust miss penalty
|
// so we use the amount of relatively difficult sections to adjust miss penalty
|
||||||
// to make it more punishing on maps with lower amount of hard sections.
|
// to make it more punishing on maps with lower amount of hard sections.
|
||||||
private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.96 / ((missCount / (4 * Math.Pow(Math.Log(difficultStrainCount), 0.94))) + 1);
|
private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.96 / ((missCount / (4 * Math.Pow(Math.Log(difficultStrainCount), 0.94))) + 1);
|
||||||
private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0);
|
private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0);
|
||||||
private int totalHits => countGreat + countOk + countMeh + countMiss;
|
private int totalHits => countGreat + countOk + countMeh + countMiss;
|
||||||
|
private int totalImperfectHits => countOk + countMeh + countMiss;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,6 +333,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
foreach (var p in Pieces)
|
foreach (var p in Pieces)
|
||||||
p.ControlPoint.Changed -= controlPointChanged;
|
p.ControlPoint.Changed -= controlPointChanged;
|
||||||
|
|
||||||
|
if (draggedControlPointIndex >= 0)
|
||||||
|
DragEnded();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectionRequested(PathControlPointPiece<T> piece, MouseButtonEvent e)
|
private void selectionRequested(PathControlPointPiece<T> piece, MouseButtonEvent e)
|
||||||
@ -392,7 +395,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
private Vector2[] dragStartPositions;
|
private Vector2[] dragStartPositions;
|
||||||
private PathType?[] dragPathTypes;
|
private PathType?[] dragPathTypes;
|
||||||
private int draggedControlPointIndex;
|
private int draggedControlPointIndex = -1;
|
||||||
private HashSet<PathControlPoint> selectedControlPoints;
|
private HashSet<PathControlPoint> selectedControlPoints;
|
||||||
|
|
||||||
private List<MenuItem> curveTypeItems;
|
private List<MenuItem> curveTypeItems;
|
||||||
@ -473,7 +476,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
EnsureValidPathTypes();
|
EnsureValidPathTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DragEnded() => changeHandler?.EndChange();
|
public void DragEnded()
|
||||||
|
{
|
||||||
|
changeHandler?.EndChange();
|
||||||
|
draggedControlPointIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -178,6 +178,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
base.OnDeselected();
|
base.OnDeselected();
|
||||||
|
|
||||||
|
if (placementControlPoint != null)
|
||||||
|
endControlPointPlacement();
|
||||||
|
|
||||||
updateVisualDefinition();
|
updateVisualDefinition();
|
||||||
BodyPiece.RecyclePath();
|
BodyPiece.RecyclePath();
|
||||||
}
|
}
|
||||||
@ -377,13 +380,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
protected override void OnMouseUp(MouseUpEvent e)
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
{
|
{
|
||||||
if (placementControlPoint != null)
|
if (placementControlPoint != null)
|
||||||
{
|
endControlPointPlacement();
|
||||||
if (IsDragged)
|
}
|
||||||
ControlPointVisualiser?.DragEnded();
|
|
||||||
|
|
||||||
placementControlPoint = null;
|
private void endControlPointPlacement()
|
||||||
changeHandler?.EndChange();
|
{
|
||||||
}
|
if (IsDragged)
|
||||||
|
ControlPointVisualiser?.DragEnded();
|
||||||
|
|
||||||
|
placementControlPoint = null;
|
||||||
|
changeHandler?.EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
@ -53,6 +53,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight };
|
||||||
|
|
||||||
Child = new FillFlowContainer
|
Child = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Width = 220,
|
Width = 220,
|
||||||
|
@ -5,10 +5,13 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Screens.Edit.Components.RadioButtons;
|
using osu.Game.Screens.Edit.Components.RadioButtons;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
@ -55,6 +58,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
MaxValue = 360,
|
MaxValue = 360,
|
||||||
Precision = 1
|
Precision = 1
|
||||||
},
|
},
|
||||||
|
KeyboardStep = 1f,
|
||||||
Instantaneous = true
|
Instantaneous = true
|
||||||
},
|
},
|
||||||
rotationOrigin = new EditorRadioButtonCollection
|
rotationOrigin = new EditorRadioButtonCollection
|
||||||
@ -126,6 +130,17 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
if (IsLoaded)
|
if (IsLoaded)
|
||||||
rotationHandler.Commit();
|
rotationHandler.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
if (e.Action == GlobalAction.Select && !e.Repeat)
|
||||||
|
{
|
||||||
|
this.HidePopover();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnPressed(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum RotationOrigin
|
public enum RotationOrigin
|
||||||
|
@ -5,11 +5,14 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
@ -70,6 +73,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
Value = 1,
|
Value = 1,
|
||||||
Default = 1,
|
Default = 1,
|
||||||
},
|
},
|
||||||
|
KeyboardStep = 0.01f,
|
||||||
Instantaneous = true
|
Instantaneous = true
|
||||||
},
|
},
|
||||||
scaleOrigin = new EditorRadioButtonCollection
|
scaleOrigin = new EditorRadioButtonCollection
|
||||||
@ -282,6 +286,17 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
if (IsLoaded) scaleHandler.Commit();
|
if (IsLoaded) scaleHandler.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
if (e.Action == GlobalAction.Select && !e.Repeat)
|
||||||
|
{
|
||||||
|
this.HidePopover();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnPressed(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ScaleOrigin
|
public enum ScaleOrigin
|
||||||
|
@ -2,10 +2,13 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
@ -25,5 +28,14 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Update(Playfield playfield)
|
||||||
|
{
|
||||||
|
base.Update(playfield);
|
||||||
|
OsuPlayfield osuPlayfield = (OsuPlayfield)playfield;
|
||||||
|
Debug.Assert(osuPlayfield.Cursor != null);
|
||||||
|
|
||||||
|
osuPlayfield.Cursor.ActiveCursor.Rotation = -CurrentRotation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests.Editor
|
||||||
|
{
|
||||||
|
public partial class TestSceneEditorPlacement : EditorTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new TaikoRuleset();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlacementBlueprintDoesNotCauseCrashes()
|
||||||
|
{
|
||||||
|
AddStep("clear objects", () => EditorBeatmap.Clear());
|
||||||
|
AddStep("add two objects", () =>
|
||||||
|
{
|
||||||
|
EditorBeatmap.Add(new Hit { StartTime = 1818 });
|
||||||
|
EditorBeatmap.Add(new Hit { StartTime = 1584 });
|
||||||
|
});
|
||||||
|
AddStep("seek back", () => EditorClock.Seek(1584));
|
||||||
|
AddStep("choose hit placement tool", () => InputManager.Key(Key.Number2));
|
||||||
|
AddStep("hover over first hit", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<DrawableHit>().ElementAt(1)));
|
||||||
|
AddStep("hover over second hit", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<DrawableHit>().ElementAt(0)));
|
||||||
|
AddStep("right click", () => InputManager.Click(MouseButton.Right));
|
||||||
|
AddUntilStep("context menu open", () => Editor.ChildrenOfType<OsuContextMenu>().Any(menu => menu.State == MenuState.Open));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -43,6 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
|
|||||||
AddStep("load player", () =>
|
AddStep("load player", () =>
|
||||||
{
|
{
|
||||||
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
||||||
|
Ruleset.Value = new TaikoRuleset().RulesetInfo;
|
||||||
SelectedMods.Value = mods ?? Array.Empty<Mod>();
|
SelectedMods.Value = mods ?? Array.Empty<Mod>();
|
||||||
|
|
||||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||||
|
@ -7,6 +7,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.UI;
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Edit
|
namespace osu.Game.Rulesets.Taiko.Edit
|
||||||
@ -20,6 +21,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override Playfield CreatePlayfield() => new TaikoEditorPlayfield();
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
25
osu.Game.Rulesets.Taiko/Edit/TaikoEditorPlayfield.cs
Normal file
25
osu.Game.Rulesets.Taiko/Edit/TaikoEditorPlayfield.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Edit
|
||||||
|
{
|
||||||
|
public partial class TaikoEditorPlayfield : TaikoPlayfield
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
// This is the simplest way to extend the taiko playfield beyond the left of the drum area.
|
||||||
|
// Required in the editor to not look weird underneath left toolbox area.
|
||||||
|
AddInternal(new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.PlayfieldBackgroundRight), _ => new PlayfieldBackgroundRight())
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -241,8 +241,8 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
|
|
||||||
metadataLookup.Update(beatmapSet, preferOnlineFetch);
|
metadataLookup.Update(beatmapSet, preferOnlineFetch);
|
||||||
|
|
||||||
Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None));
|
Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked));
|
||||||
Assert.That(beatmap.OnlineID, Is.EqualTo(-1));
|
Assert.That(beatmap.OnlineID, Is.EqualTo(654321));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -273,34 +273,6 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
Assert.That(beatmap.OnlineID, Is.EqualTo(654321));
|
Assert.That(beatmap.OnlineID, Is.EqualTo(654321));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestMetadataLookupForBeatmapWithoutPopulatedIDAndIncorrectHash([Values] bool preferOnlineFetch)
|
|
||||||
{
|
|
||||||
var lookupResult = new OnlineBeatmapMetadata
|
|
||||||
{
|
|
||||||
BeatmapID = 654321,
|
|
||||||
BeatmapStatus = BeatmapOnlineStatus.Ranked,
|
|
||||||
MD5Hash = @"cafebabe",
|
|
||||||
};
|
|
||||||
|
|
||||||
var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock;
|
|
||||||
targetMock.Setup(src => src.Available).Returns(true);
|
|
||||||
targetMock.Setup(src => src.TryLookup(It.IsAny<BeatmapInfo>(), out lookupResult))
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
var beatmap = new BeatmapInfo
|
|
||||||
{
|
|
||||||
MD5Hash = @"deadbeef"
|
|
||||||
};
|
|
||||||
var beatmapSet = new BeatmapSetInfo(beatmap.Yield());
|
|
||||||
beatmap.BeatmapSet = beatmapSet;
|
|
||||||
|
|
||||||
metadataLookup.Update(beatmapSet, preferOnlineFetch);
|
|
||||||
|
|
||||||
Assert.That(beatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None));
|
|
||||||
Assert.That(beatmap.OnlineID, Is.EqualTo(-1));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestReturnedMetadataHasDifferentHash([Values] bool preferOnlineFetch)
|
public void TestReturnedMetadataHasDifferentHash([Values] bool preferOnlineFetch)
|
||||||
{
|
{
|
||||||
@ -383,58 +355,5 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
|
|
||||||
Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None));
|
Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestPartiallyMaliciousSet([Values] bool preferOnlineFetch)
|
|
||||||
{
|
|
||||||
var firstResult = new OnlineBeatmapMetadata
|
|
||||||
{
|
|
||||||
BeatmapID = 654321,
|
|
||||||
BeatmapStatus = BeatmapOnlineStatus.Ranked,
|
|
||||||
BeatmapSetStatus = BeatmapOnlineStatus.Ranked,
|
|
||||||
MD5Hash = @"cafebabe"
|
|
||||||
};
|
|
||||||
var secondResult = new OnlineBeatmapMetadata
|
|
||||||
{
|
|
||||||
BeatmapStatus = BeatmapOnlineStatus.Ranked,
|
|
||||||
BeatmapSetStatus = BeatmapOnlineStatus.Ranked,
|
|
||||||
MD5Hash = @"dededede"
|
|
||||||
};
|
|
||||||
|
|
||||||
var targetMock = preferOnlineFetch ? apiMetadataSourceMock : localCachedMetadataSourceMock;
|
|
||||||
targetMock.Setup(src => src.Available).Returns(true);
|
|
||||||
targetMock.Setup(src => src.TryLookup(It.Is<BeatmapInfo>(bi => bi.OnlineID == 654321), out firstResult))
|
|
||||||
.Returns(true);
|
|
||||||
targetMock.Setup(src => src.TryLookup(It.Is<BeatmapInfo>(bi => bi.OnlineID == 666666), out secondResult))
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
var firstBeatmap = new BeatmapInfo
|
|
||||||
{
|
|
||||||
OnlineID = 654321,
|
|
||||||
MD5Hash = @"cafebabe",
|
|
||||||
};
|
|
||||||
var secondBeatmap = new BeatmapInfo
|
|
||||||
{
|
|
||||||
OnlineID = 666666,
|
|
||||||
MD5Hash = @"deadbeef"
|
|
||||||
};
|
|
||||||
var beatmapSet = new BeatmapSetInfo(new[]
|
|
||||||
{
|
|
||||||
firstBeatmap,
|
|
||||||
secondBeatmap
|
|
||||||
});
|
|
||||||
firstBeatmap.BeatmapSet = beatmapSet;
|
|
||||||
secondBeatmap.BeatmapSet = beatmapSet;
|
|
||||||
|
|
||||||
metadataLookup.Update(beatmapSet, preferOnlineFetch);
|
|
||||||
|
|
||||||
Assert.That(firstBeatmap.Status, Is.EqualTo(BeatmapOnlineStatus.Ranked));
|
|
||||||
Assert.That(firstBeatmap.OnlineID, Is.EqualTo(654321));
|
|
||||||
|
|
||||||
Assert.That(secondBeatmap.Status, Is.EqualTo(BeatmapOnlineStatus.None));
|
|
||||||
Assert.That(secondBeatmap.OnlineID, Is.EqualTo(-1));
|
|
||||||
|
|
||||||
Assert.That(beatmapSet.Status, Is.EqualTo(BeatmapOnlineStatus.None));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
|
|
||||||
public override IAdjustableAudioComponent Audio { get; }
|
public override IAdjustableAudioComponent Audio { get; }
|
||||||
public override Playfield Playfield { get; }
|
public override Playfield Playfield { get; }
|
||||||
|
public override PlayfieldAdjustmentContainer PlayfieldAdjustmentContainer { get; }
|
||||||
public override Container Overlays { get; }
|
public override Container Overlays { get; }
|
||||||
public override Container FrameStableComponents { get; }
|
public override Container FrameStableComponents { get; }
|
||||||
public override IFrameStableClock FrameStableClock { get; }
|
public override IFrameStableClock FrameStableClock { get; }
|
||||||
|
@ -362,6 +362,12 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddStep("add whistle addition", () =>
|
||||||
|
{
|
||||||
|
foreach (var h in EditorBeatmap.HitObjects)
|
||||||
|
h.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, HitSampleInfo.BANK_SOFT));
|
||||||
|
});
|
||||||
|
|
||||||
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
|
|
||||||
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT);
|
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT);
|
||||||
@ -374,8 +380,10 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||||
});
|
});
|
||||||
|
|
||||||
hitObjectHasSampleBank(0, HitSampleInfo.BANK_NORMAL);
|
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
|
||||||
hitObjectHasSampleBank(1, HitSampleInfo.BANK_NORMAL);
|
hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_NORMAL);
|
||||||
|
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT);
|
||||||
|
|
||||||
AddStep("Press drum bank shortcut", () =>
|
AddStep("Press drum bank shortcut", () =>
|
||||||
{
|
{
|
||||||
@ -384,8 +392,10 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||||
});
|
});
|
||||||
|
|
||||||
hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM);
|
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
|
||||||
hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM);
|
hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT);
|
||||||
|
|
||||||
AddStep("Press auto bank shortcut", () =>
|
AddStep("Press auto bank shortcut", () =>
|
||||||
{
|
{
|
||||||
@ -395,8 +405,47 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Should be a noop.
|
// Should be a noop.
|
||||||
hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM);
|
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
|
||||||
hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM);
|
hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT);
|
||||||
|
|
||||||
|
AddStep("Press addition normal bank shortcut", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.AltLeft);
|
||||||
|
InputManager.Key(Key.W);
|
||||||
|
InputManager.ReleaseKey(Key.AltLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
|
||||||
|
hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_NORMAL);
|
||||||
|
|
||||||
|
AddStep("Press addition drum bank shortcut", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.AltLeft);
|
||||||
|
InputManager.Key(Key.R);
|
||||||
|
InputManager.ReleaseKey(Key.AltLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_DRUM);
|
||||||
|
|
||||||
|
AddStep("Press auto bank shortcut", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.AltLeft);
|
||||||
|
InputManager.Key(Key.Q);
|
||||||
|
InputManager.ReleaseKey(Key.AltLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should be a noop.
|
||||||
|
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_DRUM);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -414,7 +463,21 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||||
});
|
});
|
||||||
|
|
||||||
checkPlacementSample(HitSampleInfo.BANK_NORMAL);
|
AddStep("Press soft addition bank shortcut", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.AltLeft);
|
||||||
|
InputManager.Key(Key.E);
|
||||||
|
InputManager.ReleaseKey(Key.AltLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL);
|
||||||
|
|
||||||
|
AddStep("Press finish sample shortcut", () =>
|
||||||
|
{
|
||||||
|
InputManager.Key(Key.E);
|
||||||
|
});
|
||||||
|
|
||||||
|
checkPlacementSampleAdditionBank(HitSampleInfo.BANK_SOFT);
|
||||||
|
|
||||||
AddStep("Press drum bank shortcut", () =>
|
AddStep("Press drum bank shortcut", () =>
|
||||||
{
|
{
|
||||||
@ -423,7 +486,18 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||||
});
|
});
|
||||||
|
|
||||||
checkPlacementSample(HitSampleInfo.BANK_DRUM);
|
checkPlacementSampleBank(HitSampleInfo.BANK_DRUM);
|
||||||
|
checkPlacementSampleAdditionBank(HitSampleInfo.BANK_SOFT);
|
||||||
|
|
||||||
|
AddStep("Press drum addition bank shortcut", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.AltLeft);
|
||||||
|
InputManager.Key(Key.R);
|
||||||
|
InputManager.ReleaseKey(Key.AltLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
checkPlacementSampleBank(HitSampleInfo.BANK_DRUM);
|
||||||
|
checkPlacementSampleAdditionBank(HitSampleInfo.BANK_DRUM);
|
||||||
|
|
||||||
AddStep("Press auto bank shortcut", () =>
|
AddStep("Press auto bank shortcut", () =>
|
||||||
{
|
{
|
||||||
@ -432,15 +506,29 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||||
});
|
});
|
||||||
|
|
||||||
checkPlacementSample(HitSampleInfo.BANK_NORMAL);
|
checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL);
|
||||||
|
checkPlacementSampleAdditionBank(HitSampleInfo.BANK_DRUM);
|
||||||
|
|
||||||
|
AddStep("Press auto addition bank shortcut", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.AltLeft);
|
||||||
|
InputManager.Key(Key.Q);
|
||||||
|
InputManager.ReleaseKey(Key.AltLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL);
|
||||||
|
checkPlacementSampleAdditionBank(HitSampleInfo.BANK_NORMAL);
|
||||||
|
|
||||||
AddStep("Move after second object", () => EditorClock.Seek(750));
|
AddStep("Move after second object", () => EditorClock.Seek(750));
|
||||||
checkPlacementSample(HitSampleInfo.BANK_SOFT);
|
checkPlacementSampleBank(HitSampleInfo.BANK_SOFT);
|
||||||
|
checkPlacementSampleAdditionBank(HitSampleInfo.BANK_SOFT);
|
||||||
|
|
||||||
AddStep("Move to first object", () => EditorClock.Seek(0));
|
AddStep("Move to first object", () => EditorClock.Seek(0));
|
||||||
checkPlacementSample(HitSampleInfo.BANK_NORMAL);
|
checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL);
|
||||||
|
checkPlacementSampleAdditionBank(HitSampleInfo.BANK_NORMAL);
|
||||||
|
|
||||||
void checkPlacementSample(string expected) => AddAssert($"Placement sample is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First().Bank, () => Is.EqualTo(expected));
|
void checkPlacementSampleBank(string expected) => AddAssert($"Placement sample is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected));
|
||||||
|
void checkPlacementSampleAdditionBank(string expected) => AddAssert($"Placement sample addition is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -585,7 +673,29 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL);
|
hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL);
|
||||||
hitObjectNodeHasSampleBank(2, 0, HitSampleInfo.BANK_DRUM);
|
hitObjectNodeHasSampleBank(2, 0, HitSampleInfo.BANK_DRUM);
|
||||||
hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL);
|
hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL);
|
||||||
hitObjectNodeHasSampleBank(2, 1, HitSampleInfo.BANK_DRUM);
|
hitObjectNodeHasSampleNormalBank(2, 1, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectNodeHasSampleAdditionBank(2, 1, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
|
|
||||||
|
AddStep("set normal addition bank", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.LAlt);
|
||||||
|
InputManager.Key(Key.W);
|
||||||
|
InputManager.ReleaseKey(Key.LAlt);
|
||||||
|
});
|
||||||
|
|
||||||
|
hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
|
||||||
|
|
||||||
|
hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL);
|
||||||
|
|
||||||
|
hitObjectHasSampleBank(2, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL);
|
||||||
|
hitObjectNodeHasSampleBank(2, 0, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL);
|
||||||
|
hitObjectNodeHasSampleNormalBank(2, 1, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectNodeHasSampleAdditionBank(2, 1, HitSampleInfo.BANK_NORMAL);
|
||||||
hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -629,20 +739,37 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
InputManager.ReleaseKey(Key.LShift);
|
InputManager.ReleaseKey(Key.LShift);
|
||||||
});
|
});
|
||||||
|
|
||||||
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT);
|
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT);
|
||||||
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_SOFT);
|
hitObjectNodeHasSampleNormalBank(0, 0, HitSampleInfo.BANK_SOFT);
|
||||||
hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP);
|
hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP);
|
||||||
hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT);
|
hitObjectNodeHasSampleNormalBank(0, 1, HitSampleInfo.BANK_SOFT);
|
||||||
hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
|
|
||||||
AddStep("unify whistle addition", () => InputManager.Key(Key.W));
|
AddStep("unify whistle addition", () => InputManager.Key(Key.W));
|
||||||
|
|
||||||
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT);
|
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT);
|
||||||
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_SOFT);
|
hitObjectNodeHasSampleNormalBank(0, 0, HitSampleInfo.BANK_SOFT);
|
||||||
hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE);
|
hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE);
|
||||||
hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT);
|
hitObjectNodeHasSampleNormalBank(0, 1, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
|
|
||||||
|
AddStep("set drum addition bank", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.LAlt);
|
||||||
|
InputManager.Key(Key.R);
|
||||||
|
InputManager.ReleaseKey(Key.LAlt);
|
||||||
|
});
|
||||||
|
|
||||||
|
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
|
hitObjectNodeHasSampleNormalBank(0, 0, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectNodeHasSampleAdditionBank(0, 0, HitSampleInfo.BANK_DRUM);
|
||||||
|
hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE);
|
||||||
|
hitObjectNodeHasSampleNormalBank(0, 1, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectNodeHasSampleAdditionBank(0, 1, HitSampleInfo.BANK_DRUM);
|
||||||
hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +165,9 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("enable automatic bank assignment", () =>
|
AddStep("enable automatic bank assignment", () =>
|
||||||
{
|
{
|
||||||
InputManager.PressKey(Key.LShift);
|
InputManager.PressKey(Key.LShift);
|
||||||
|
InputManager.PressKey(Key.LAlt);
|
||||||
InputManager.Key(Key.Q);
|
InputManager.Key(Key.Q);
|
||||||
|
InputManager.ReleaseKey(Key.LAlt);
|
||||||
InputManager.ReleaseKey(Key.LShift);
|
InputManager.ReleaseKey(Key.LShift);
|
||||||
});
|
});
|
||||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||||
@ -228,7 +230,9 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("select drum bank", () =>
|
AddStep("select drum bank", () =>
|
||||||
{
|
{
|
||||||
InputManager.PressKey(Key.LShift);
|
InputManager.PressKey(Key.LShift);
|
||||||
|
InputManager.PressKey(Key.LAlt);
|
||||||
InputManager.Key(Key.R);
|
InputManager.Key(Key.R);
|
||||||
|
InputManager.ReleaseKey(Key.LAlt);
|
||||||
InputManager.ReleaseKey(Key.LShift);
|
InputManager.ReleaseKey(Key.LShift);
|
||||||
});
|
});
|
||||||
AddStep("enable clap addition", () => InputManager.Key(Key.R));
|
AddStep("enable clap addition", () => InputManager.Key(Key.R));
|
||||||
|
@ -284,6 +284,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
public override IAdjustableAudioComponent Audio { get; }
|
public override IAdjustableAudioComponent Audio { get; }
|
||||||
public override Playfield Playfield { get; }
|
public override Playfield Playfield { get; }
|
||||||
|
public override PlayfieldAdjustmentContainer PlayfieldAdjustmentContainer { get; }
|
||||||
public override Container Overlays { get; }
|
public override Container Overlays { get; }
|
||||||
public override Container FrameStableComponents { get; }
|
public override Container FrameStableComponents { get; }
|
||||||
public override IFrameStableClock FrameStableClock { get; }
|
public override IFrameStableClock FrameStableClock { get; }
|
||||||
|
@ -165,11 +165,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
AddUntilStep("wait for room join", () => RoomJoined);
|
AddUntilStep("wait for room join", () => RoomJoined);
|
||||||
|
|
||||||
AddStep("join other user (ready)", () =>
|
AddStep("join other user", void () => MultiplayerClient.AddUser(new APIUser { Id = PLAYER_1_ID }));
|
||||||
{
|
AddUntilStep("wait for user populated", () => MultiplayerClient.ClientRoom!.Users.Single(u => u.UserID == PLAYER_1_ID).User, () => Is.Not.Null);
|
||||||
MultiplayerClient.AddUser(new APIUser { Id = PLAYER_1_ID });
|
AddStep("other user ready", () => MultiplayerClient.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready));
|
||||||
MultiplayerClient.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready);
|
|
||||||
});
|
|
||||||
|
|
||||||
ClickButtonWhenEnabled<MultiplayerSpectateButton>();
|
ClickButtonWhenEnabled<MultiplayerSpectateButton>();
|
||||||
|
|
||||||
|
@ -648,6 +648,34 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddUntilStep("Info message displayed", () => channelManager.CurrentChannel.Value.Messages.Last(), () => Is.InstanceOf(typeof(InfoMessage)));
|
AddUntilStep("Info message displayed", () => channelManager.CurrentChannel.Value.Messages.Last(), () => Is.InstanceOf(typeof(InfoMessage)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFiltering()
|
||||||
|
{
|
||||||
|
AddStep("Show overlay", () => chatOverlay.Show());
|
||||||
|
joinTestChannel(1);
|
||||||
|
joinTestChannel(3);
|
||||||
|
joinTestChannel(5);
|
||||||
|
joinChannel(new Channel(new APIUser { Id = 2001, Username = "alice" }));
|
||||||
|
joinChannel(new Channel(new APIUser { Id = 2002, Username = "bob" }));
|
||||||
|
joinChannel(new Channel(new APIUser { Id = 2003, Username = "charley the plant" }));
|
||||||
|
|
||||||
|
AddStep("filter to \"c\"", () => chatOverlay.ChildrenOfType<SearchTextBox>().Single().Text = "c");
|
||||||
|
AddUntilStep("bob filtered out", () => chatOverlay.ChildrenOfType<ChannelListItem>().Count(i => i.Alpha > 0), () => Is.EqualTo(5));
|
||||||
|
|
||||||
|
AddStep("filter to \"channel\"", () => chatOverlay.ChildrenOfType<SearchTextBox>().Single().Text = "channel");
|
||||||
|
AddUntilStep("only public channels left", () => chatOverlay.ChildrenOfType<ChannelListItem>().Count(i => i.Alpha > 0), () => Is.EqualTo(3));
|
||||||
|
|
||||||
|
AddStep("commit textbox", () =>
|
||||||
|
{
|
||||||
|
chatOverlay.ChildrenOfType<SearchTextBox>().Single().TakeFocus();
|
||||||
|
Schedule(() => InputManager.PressKey(Key.Enter));
|
||||||
|
});
|
||||||
|
AddUntilStep("#channel-2 active", () => channelManager.CurrentChannel.Value.Name, () => Is.EqualTo("#channel-2"));
|
||||||
|
|
||||||
|
AddStep("filter to \"channel-3\"", () => chatOverlay.ChildrenOfType<SearchTextBox>().Single().Text = "channel-3");
|
||||||
|
AddUntilStep("no channels left", () => chatOverlay.ChildrenOfType<ChannelListItem>().Count(i => i.Alpha > 0), () => Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
private void joinTestChannel(int i)
|
private void joinTestChannel(int i)
|
||||||
{
|
{
|
||||||
AddStep($"Join test channel {i}", () => channelManager.JoinChannel(testChannels[i]));
|
AddStep($"Join test channel {i}", () => channelManager.JoinChannel(testChannels[i]));
|
||||||
|
@ -147,6 +147,18 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType<ScoreTableRowBackground>().Any());
|
AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType<ScoreTableRowBackground>().Any());
|
||||||
AddAssert("best score displayed", () => scoresContainer.ChildrenOfType<DrawableTopScore>().Count() == 2);
|
AddAssert("best score displayed", () => scoresContainer.ChildrenOfType<DrawableTopScore>().Count() == 2);
|
||||||
|
|
||||||
|
AddStep("Load scores with personal best FC", () =>
|
||||||
|
{
|
||||||
|
var allScores = createScores();
|
||||||
|
allScores.UserScore = createUserBest();
|
||||||
|
allScores.UserScore.Score.Accuracy = 1;
|
||||||
|
scoresContainer.Beatmap.Value.MaxCombo = allScores.UserScore.Score.MaxCombo = 1337;
|
||||||
|
scoresContainer.Scores = allScores;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for scores displayed", () => scoresContainer.ChildrenOfType<ScoreTableRowBackground>().Any());
|
||||||
|
AddAssert("best score displayed", () => scoresContainer.ChildrenOfType<DrawableTopScore>().Count() == 2);
|
||||||
|
|
||||||
AddStep("Load scores with personal best (null position)", () =>
|
AddStep("Load scores with personal best (null position)", () =>
|
||||||
{
|
{
|
||||||
var allScores = createScores();
|
var allScores = createScores();
|
||||||
|
@ -60,12 +60,18 @@ namespace osu.Game.Audio
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int Volume { get; }
|
public int Volume { get; }
|
||||||
|
|
||||||
public HitSampleInfo(string name, string bank = SampleControlPoint.DEFAULT_BANK, string? suffix = null, int volume = 100)
|
/// <summary>
|
||||||
|
/// Whether this sample should automatically assign the bank of the normal sample whenever it is set in the editor.
|
||||||
|
/// </summary>
|
||||||
|
public bool EditorAutoBank { get; }
|
||||||
|
|
||||||
|
public HitSampleInfo(string name, string bank = SampleControlPoint.DEFAULT_BANK, string? suffix = null, int volume = 100, bool editorAutoBank = true)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
Bank = bank;
|
Bank = bank;
|
||||||
Suffix = suffix;
|
Suffix = suffix;
|
||||||
Volume = volume;
|
Volume = volume;
|
||||||
|
EditorAutoBank = editorAutoBank;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -92,9 +98,10 @@ namespace osu.Game.Audio
|
|||||||
/// <param name="newBank">An optional new sample bank.</param>
|
/// <param name="newBank">An optional new sample bank.</param>
|
||||||
/// <param name="newSuffix">An optional new lookup suffix.</param>
|
/// <param name="newSuffix">An optional new lookup suffix.</param>
|
||||||
/// <param name="newVolume">An optional new volume.</param>
|
/// <param name="newVolume">An optional new volume.</param>
|
||||||
|
/// <param name="newEditorAutoBank">An optional new editor auto bank flag.</param>
|
||||||
/// <returns>The new <see cref="HitSampleInfo"/>.</returns>
|
/// <returns>The new <see cref="HitSampleInfo"/>.</returns>
|
||||||
public virtual HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default)
|
public virtual HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default, Optional<bool> newEditorAutoBank = default)
|
||||||
=> new HitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newSuffix.GetOr(Suffix), newVolume.GetOr(Volume));
|
=> new HitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newSuffix.GetOr(Suffix), newVolume.GetOr(Volume), newEditorAutoBank.GetOr(EditorAutoBank));
|
||||||
|
|
||||||
public virtual bool Equals(HitSampleInfo? other)
|
public virtual bool Equals(HitSampleInfo? other)
|
||||||
=> other != null && Name == other.Name && Bank == other.Bank && Suffix == other.Suffix;
|
=> other != null && Name == other.Name && Bank == other.Bank && Suffix == other.Suffix;
|
||||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
Debug.Assert(beatmapInfo.BeatmapSet != null);
|
Debug.Assert(beatmapInfo.BeatmapSet != null);
|
||||||
|
|
||||||
var req = new GetBeatmapRequest(beatmapInfo);
|
var req = new GetBeatmapRequest(md5Hash: beatmapInfo.MD5Hash, filename: beatmapInfo.Path);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Logging;
|
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
|
|
||||||
@ -44,10 +43,19 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
foreach (var beatmapInfo in beatmapSet.Beatmaps)
|
foreach (var beatmapInfo in beatmapSet.Beatmaps)
|
||||||
{
|
{
|
||||||
|
// note that these lookups DO NOT ACTUALLY FULLY GUARANTEE that the beatmap is what it claims it is,
|
||||||
|
// i.e. the correctness of this lookup should be treated as APPROXIMATE AT WORST.
|
||||||
|
// this is because the beatmap filename is used as a fallback in some scenarios where the MD5 of the beatmap may mismatch.
|
||||||
|
// this is considered to be an acceptable casualty so that things can continue to work as expected for users in some rare scenarios
|
||||||
|
// (stale beatmap files in beatmap packs, beatmap mirror desyncs).
|
||||||
|
// however, all this means that other places such as score submission ARE EXPECTED TO VERIFY THE MD5 OF THE BEATMAP AGAINST THE ONLINE ONE EXPLICITLY AGAIN.
|
||||||
|
//
|
||||||
|
// additionally note that the online ID stored to the map is EXPLICITLY NOT USED because some users in a silly attempt to "fix" things for themselves on stable
|
||||||
|
// would reuse online IDs of already submitted beatmaps, which means that information is pretty much expected to be bogus in a nonzero number of beatmapsets.
|
||||||
if (!tryLookup(beatmapInfo, preferOnlineFetch, out var res))
|
if (!tryLookup(beatmapInfo, preferOnlineFetch, out var res))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (res == null || shouldDiscardLookupResult(res, beatmapInfo))
|
if (res == null)
|
||||||
{
|
{
|
||||||
beatmapInfo.ResetOnlineInfo();
|
beatmapInfo.ResetOnlineInfo();
|
||||||
lookupResults.Add(null); // mark lookup failure
|
lookupResults.Add(null); // mark lookup failure
|
||||||
@ -83,23 +91,6 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool shouldDiscardLookupResult(OnlineBeatmapMetadata result, BeatmapInfo beatmapInfo)
|
|
||||||
{
|
|
||||||
if (beatmapInfo.OnlineID > 0 && result.BeatmapID != beatmapInfo.OnlineID)
|
|
||||||
{
|
|
||||||
Logger.Log($"Discarding metadata lookup result due to mismatching online ID (expected: {beatmapInfo.OnlineID} actual: {result.BeatmapID})", LoggingTarget.Database);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (beatmapInfo.OnlineID == -1 && result.MD5Hash != beatmapInfo.MD5Hash)
|
|
||||||
{
|
|
||||||
Logger.Log($"Discarding metadata lookup result due to mismatching hash (expected: {beatmapInfo.MD5Hash} actual: {result.MD5Hash})", LoggingTarget.Database);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to retrieve the <see cref="OnlineBeatmapMetadata"/> for the given <paramref name="beatmapInfo"/>.
|
/// Attempts to retrieve the <see cref="OnlineBeatmapMetadata"/> for the given <paramref name="beatmapInfo"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -539,7 +539,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
private string getSampleBank(IList<HitSampleInfo> samples, bool banksOnly = false)
|
private string getSampleBank(IList<HitSampleInfo> samples, bool banksOnly = false)
|
||||||
{
|
{
|
||||||
LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank);
|
LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank);
|
||||||
LegacySampleBank addBank = toLegacySampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name) && s.Name != HitSampleInfo.HIT_NORMAL)?.Bank);
|
LegacySampleBank addBank = toLegacySampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name) && s.Name != HitSampleInfo.HIT_NORMAL && !s.EditorAutoBank)?.Bank);
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
@ -90,8 +90,7 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(beatmapInfo.MD5Hash)
|
if (string.IsNullOrEmpty(beatmapInfo.MD5Hash)
|
||||||
&& string.IsNullOrEmpty(beatmapInfo.Path)
|
&& string.IsNullOrEmpty(beatmapInfo.Path))
|
||||||
&& beatmapInfo.OnlineID <= 0)
|
|
||||||
{
|
{
|
||||||
onlineMetadata = null;
|
onlineMetadata = null;
|
||||||
return false;
|
return false;
|
||||||
@ -240,10 +239,9 @@ namespace osu.Game.Beatmaps
|
|||||||
using var cmd = db.CreateCommand();
|
using var cmd = db.CreateCommand();
|
||||||
|
|
||||||
cmd.CommandText =
|
cmd.CommandText =
|
||||||
@"SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path";
|
@"SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR filename = @Path";
|
||||||
|
|
||||||
cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash));
|
cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash));
|
||||||
cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID));
|
|
||||||
cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path));
|
cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path));
|
||||||
|
|
||||||
using var reader = cmd.ExecuteReader();
|
using var reader = cmd.ExecuteReader();
|
||||||
@ -281,11 +279,10 @@ namespace osu.Game.Beatmaps
|
|||||||
SELECT `b`.`beatmapset_id`, `b`.`beatmap_id`, `b`.`approved`, `b`.`user_id`, `b`.`checksum`, `b`.`last_update`, `s`.`submit_date`, `s`.`approved_date`
|
SELECT `b`.`beatmapset_id`, `b`.`beatmap_id`, `b`.`approved`, `b`.`user_id`, `b`.`checksum`, `b`.`last_update`, `s`.`submit_date`, `s`.`approved_date`
|
||||||
FROM `osu_beatmaps` AS `b`
|
FROM `osu_beatmaps` AS `b`
|
||||||
JOIN `osu_beatmapsets` AS `s` ON `s`.`beatmapset_id` = `b`.`beatmapset_id`
|
JOIN `osu_beatmapsets` AS `s` ON `s`.`beatmapset_id` = `b`.`beatmapset_id`
|
||||||
WHERE `b`.`checksum` = @MD5Hash OR `b`.`beatmap_id` = @OnlineID OR `b`.`filename` = @Path
|
WHERE `b`.`checksum` = @MD5Hash OR `b`.`filename` = @Path
|
||||||
""";
|
""";
|
||||||
|
|
||||||
cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash));
|
cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash));
|
||||||
cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID));
|
|
||||||
cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path));
|
cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path));
|
||||||
|
|
||||||
using var reader = cmd.ExecuteReader();
|
using var reader = cmd.ExecuteReader();
|
||||||
|
@ -206,6 +206,8 @@ namespace osu.Game.Configuration
|
|||||||
SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true);
|
SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true);
|
||||||
SetDefault(OsuSetting.EditorTimelineShowTicks, true);
|
SetDefault(OsuSetting.EditorTimelineShowTicks, true);
|
||||||
|
|
||||||
|
SetDefault(OsuSetting.EditorContractSidebars, false);
|
||||||
|
|
||||||
SetDefault(OsuSetting.AlwaysShowHoldForMenuButton, false);
|
SetDefault(OsuSetting.AlwaysShowHoldForMenuButton, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,6 +433,7 @@ namespace osu.Game.Configuration
|
|||||||
HideCountryFlags,
|
HideCountryFlags,
|
||||||
EditorTimelineShowTimingChanges,
|
EditorTimelineShowTimingChanges,
|
||||||
EditorTimelineShowTicks,
|
EditorTimelineShowTicks,
|
||||||
AlwaysShowHoldForMenuButton
|
AlwaysShowHoldForMenuButton,
|
||||||
|
EditorContractSidebars
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -376,10 +376,6 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
foreach (var beatmap in beatmapSet.Beatmaps)
|
foreach (var beatmap in beatmapSet.Beatmaps)
|
||||||
{
|
{
|
||||||
// Cascade delete related scores, else they will have a null beatmap against the model's spec.
|
|
||||||
foreach (var score in beatmap.Scores)
|
|
||||||
realm.Remove(score);
|
|
||||||
|
|
||||||
realm.Remove(beatmap.Metadata);
|
realm.Remove(beatmap.Metadata);
|
||||||
realm.Remove(beatmap);
|
realm.Remove(beatmap);
|
||||||
}
|
}
|
||||||
|
@ -528,7 +528,7 @@ namespace osu.Game.Database
|
|||||||
/// <param name="model">The new model proposed for import.</param>
|
/// <param name="model">The new model proposed for import.</param>
|
||||||
/// <param name="realm">The current realm context.</param>
|
/// <param name="realm">The current realm context.</param>
|
||||||
/// <returns>An existing model which matches the criteria to skip importing, else null.</returns>
|
/// <returns>An existing model which matches the criteria to skip importing, else null.</returns>
|
||||||
protected TModel? CheckForExisting(TModel model, Realm realm) => string.IsNullOrEmpty(model.Hash) ? null : realm.All<TModel>().FirstOrDefault(b => b.Hash == model.Hash);
|
protected TModel? CheckForExisting(TModel model, Realm realm) => string.IsNullOrEmpty(model.Hash) ? null : realm.All<TModel>().OrderBy(b => b.DeletePending).FirstOrDefault(b => b.Hash == model.Hash);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether import can be skipped after finding an existing import early in the process.
|
/// Whether import can be skipped after finding an existing import early in the process.
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Online.API.Requests.Responses;
|
|||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
{
|
{
|
||||||
public partial class UserLookupCache : OnlineLookupCache<int, APIUser, GetUsersRequest>
|
public partial class UserLookupCache : OnlineLookupCache<int, APIUser, LookupUsersRequest>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform an API lookup on the specified user, populating a <see cref="APIUser"/> model.
|
/// Perform an API lookup on the specified user, populating a <see cref="APIUser"/> model.
|
||||||
@ -28,8 +28,8 @@ namespace osu.Game.Database
|
|||||||
/// <returns>The populated users. May include null results for failed retrievals.</returns>
|
/// <returns>The populated users. May include null results for failed retrievals.</returns>
|
||||||
public Task<APIUser?[]> GetUsersAsync(int[] userIds, CancellationToken token = default) => LookupAsync(userIds, token);
|
public Task<APIUser?[]> GetUsersAsync(int[] userIds, CancellationToken token = default) => LookupAsync(userIds, token);
|
||||||
|
|
||||||
protected override GetUsersRequest CreateRequest(IEnumerable<int> ids) => new GetUsersRequest(ids.ToArray());
|
protected override LookupUsersRequest CreateRequest(IEnumerable<int> ids) => new LookupUsersRequest(ids.ToArray());
|
||||||
|
|
||||||
protected override IEnumerable<APIUser>? RetrieveResults(GetUsersRequest request) => request.Response?.Users;
|
protected override IEnumerable<APIUser>? RetrieveResults(LookupUsersRequest request) => request.Response?.Users;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
const float vertical_offset = 13;
|
const float vertical_offset = 13;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
label = new OsuSpriteText
|
label = new OsuSpriteText
|
||||||
{
|
{
|
||||||
@ -115,7 +115,9 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
KeyboardStep = 0.1f,
|
KeyboardStep = 0.1f,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Y = vertical_offset,
|
Y = vertical_offset,
|
||||||
}
|
},
|
||||||
|
upperBound.Nub.CreateProxy(),
|
||||||
|
lowerBound.Nub.CreateProxy(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,6 +162,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
protected partial class BoundSlider : RoundedSliderBar<double>
|
protected partial class BoundSlider : RoundedSliderBar<double>
|
||||||
{
|
{
|
||||||
|
public new Nub Nub => base.Nub;
|
||||||
|
|
||||||
public string? DefaultString;
|
public string? DefaultString;
|
||||||
public LocalisableString? DefaultTooltip;
|
public LocalisableString? DefaultTooltip;
|
||||||
public string? TooltipSuffix;
|
public string? TooltipSuffix;
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -25,6 +26,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
private readonly HoverClickSounds hoverClickSounds;
|
private readonly HoverClickSounds hoverClickSounds;
|
||||||
|
|
||||||
|
private readonly Container mainContent;
|
||||||
|
|
||||||
private Color4 accentColour;
|
private Color4 accentColour;
|
||||||
|
|
||||||
public Color4 AccentColour
|
public Color4 AccentColour
|
||||||
@ -62,7 +65,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Padding = new MarginPadding { Horizontal = 2 },
|
Padding = new MarginPadding { Horizontal = 2 },
|
||||||
Child = new CircularContainer
|
Child = mainContent = new CircularContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
@ -135,6 +138,26 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnFocus(FocusEvent e)
|
||||||
|
{
|
||||||
|
base.OnFocus(e);
|
||||||
|
|
||||||
|
mainContent.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = AccentColour.Darken(1),
|
||||||
|
Hollow = true,
|
||||||
|
Radius = 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFocusLost(FocusLostEvent e)
|
||||||
|
{
|
||||||
|
base.OnFocusLost(e);
|
||||||
|
|
||||||
|
mainContent.EdgeEffect = default;
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
updateGlow();
|
updateGlow();
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -26,6 +27,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
private readonly HoverClickSounds hoverClickSounds;
|
private readonly HoverClickSounds hoverClickSounds;
|
||||||
|
|
||||||
|
private readonly Container mainContent;
|
||||||
|
|
||||||
private Color4 accentColour;
|
private Color4 accentColour;
|
||||||
|
|
||||||
public Color4 AccentColour
|
public Color4 AccentColour
|
||||||
@ -60,12 +63,13 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
RangePadding = EXPANDED_SIZE / 2;
|
RangePadding = EXPANDED_SIZE / 2;
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Container
|
mainContent = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 5,
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Padding = new MarginPadding { Horizontal = 2 },
|
|
||||||
Child = new Container
|
Child = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -138,6 +142,26 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnFocus(FocusEvent e)
|
||||||
|
{
|
||||||
|
base.OnFocus(e);
|
||||||
|
|
||||||
|
mainContent.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = AccentColour.Darken(1),
|
||||||
|
Hollow = true,
|
||||||
|
Radius = 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFocusLost(FocusLostEvent e)
|
||||||
|
{
|
||||||
|
base.OnFocusLost(e);
|
||||||
|
|
||||||
|
mainContent.EdgeEffect = default;
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
updateGlow();
|
updateGlow();
|
||||||
@ -167,8 +191,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
{
|
{
|
||||||
base.UpdateAfterChildren();
|
base.UpdateAfterChildren();
|
||||||
LeftBox.Scale = new Vector2(Math.Clamp(RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2.15f, 0, Math.Max(0, DrawWidth)), 1);
|
LeftBox.Scale = new Vector2(Math.Clamp(RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2.3f, 0, Math.Max(0, DrawWidth)), 1);
|
||||||
RightBox.Scale = new Vector2(Math.Clamp(DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2.15f, 0, Math.Max(0, DrawWidth)), 1);
|
RightBox.Scale = new Vector2(Math.Clamp(DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2.3f, 0, Math.Max(0, DrawWidth)), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateValue(float value)
|
protected override void UpdateValue(float value)
|
||||||
|
@ -71,7 +71,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
private Box background = null!;
|
private Box background = null!;
|
||||||
private Box flashLayer = null!;
|
private Box flashLayer = null!;
|
||||||
private FormTextBox.InnerTextBox textBox = null!;
|
private FormTextBox.InnerTextBox textBox = null!;
|
||||||
private Slider slider = null!;
|
private InnerSlider slider = null!;
|
||||||
private FormFieldCaption caption = null!;
|
private FormFieldCaption caption = null!;
|
||||||
private IFocusManager focusManager = null!;
|
private IFocusManager focusManager = null!;
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
},
|
},
|
||||||
TabbableContentContainer = tabbableContentContainer,
|
TabbableContentContainer = tabbableContentContainer,
|
||||||
},
|
},
|
||||||
slider = new Slider
|
slider = new InnerSlider
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
@ -163,6 +163,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
textBox.Current.BindValueChanged(textChanged);
|
textBox.Current.BindValueChanged(textChanged);
|
||||||
|
|
||||||
slider.IsDragging.BindValueChanged(_ => updateState());
|
slider.IsDragging.BindValueChanged(_ => updateState());
|
||||||
|
slider.Focused.BindValueChanged(_ => updateState());
|
||||||
|
|
||||||
current.ValueChanged += e => currentNumberInstantaneous.Value = e.NewValue;
|
current.ValueChanged += e => currentNumberInstantaneous.Value = e.NewValue;
|
||||||
current.MinValueChanged += v => currentNumberInstantaneous.MinValue = v;
|
current.MinValueChanged += v => currentNumberInstantaneous.MinValue = v;
|
||||||
@ -259,16 +260,18 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
|
bool childHasFocus = slider.Focused.Value || textBox.Focused.Value;
|
||||||
|
|
||||||
textBox.Alpha = 1;
|
textBox.Alpha = 1;
|
||||||
|
|
||||||
background.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Background4 : colourProvider.Background5;
|
background.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Background4 : colourProvider.Background5;
|
||||||
caption.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content2;
|
caption.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content2;
|
||||||
textBox.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content1;
|
textBox.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content1;
|
||||||
|
|
||||||
BorderThickness = IsHovered || textBox.Focused.Value || slider.IsDragging.Value ? 2 : 0;
|
BorderThickness = childHasFocus || IsHovered || slider.IsDragging.Value ? 2 : 0;
|
||||||
BorderColour = textBox.Focused.Value ? colourProvider.Highlight1 : colourProvider.Light4;
|
BorderColour = childHasFocus ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||||
|
|
||||||
if (textBox.Focused.Value)
|
if (childHasFocus)
|
||||||
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3);
|
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3);
|
||||||
else if (IsHovered || slider.IsDragging.Value)
|
else if (IsHovered || slider.IsDragging.Value)
|
||||||
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4);
|
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4);
|
||||||
@ -283,8 +286,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
textBox.Text = slider.GetDisplayableValue(currentNumberInstantaneous.Value).ToString();
|
textBox.Text = slider.GetDisplayableValue(currentNumberInstantaneous.Value).ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class Slider : OsuSliderBar<T>
|
private partial class InnerSlider : OsuSliderBar<T>
|
||||||
{
|
{
|
||||||
|
public BindableBool Focused { get; } = new BindableBool();
|
||||||
|
|
||||||
public BindableBool IsDragging { get; set; } = new BindableBool();
|
public BindableBool IsDragging { get; set; } = new BindableBool();
|
||||||
public Action? OnCommit { get; set; }
|
public Action? OnCommit { get; set; }
|
||||||
|
|
||||||
@ -344,7 +349,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
updateState();
|
updateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,11 +386,25 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
base.OnHoverLost(e);
|
base.OnHoverLost(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnFocus(FocusEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
Focused.Value = true;
|
||||||
|
base.OnFocus(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFocusLost(FocusLostEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
Focused.Value = false;
|
||||||
|
base.OnFocusLost(e);
|
||||||
|
}
|
||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
rightBox.Colour = colourProvider.Background6;
|
rightBox.Colour = colourProvider.Background6;
|
||||||
leftBox.Colour = IsHovered || IsDragged ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Dark2;
|
leftBox.Colour = HasFocus || IsHovered || IsDragged ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Dark2;
|
||||||
nub.Colour = IsHovered || IsDragged ? colourProvider.Highlight1 : colourProvider.Light4;
|
nub.Colour = HasFocus || IsHovered || IsDragged ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateValue(float value)
|
protected override void UpdateValue(float value)
|
||||||
|
@ -114,6 +114,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString LimitedDistanceSnap => new TranslatableString(getKey(@"limited_distance_snap_grid"), @"Limit distance snap placement to current time");
|
public static LocalisableString LimitedDistanceSnap => new TranslatableString(getKey(@"limited_distance_snap_grid"), @"Limit distance snap placement to current time");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Contract sidebars when not hovered"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ContractSidebars => new TranslatableString(getKey(@"contract_sidebars"), @"Contract sidebars when not hovered");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Must be in edit mode to handle editor links"
|
/// "Must be in edit mode to handle editor links"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -79,6 +79,11 @@ namespace osu.Game.Localisation.SkinComponents
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString TextColourDescription => new TranslatableString(getKey(@"text_colour_description"), @"The colour of the text.");
|
public static LocalisableString TextColourDescription => new TranslatableString(getKey(@"text_colour_description"), @"The colour of the text.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Use relative size"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString UseRelativeSize => new TranslatableString(getKey(@"use_relative_size"), @"Use relative size");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Globalization;
|
||||||
using osu.Framework.IO.Network;
|
using osu.Framework.IO.Network;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
@ -9,23 +10,30 @@ namespace osu.Game.Online.API.Requests
|
|||||||
{
|
{
|
||||||
public class GetBeatmapRequest : APIRequest<APIBeatmap>
|
public class GetBeatmapRequest : APIRequest<APIBeatmap>
|
||||||
{
|
{
|
||||||
public readonly IBeatmapInfo BeatmapInfo;
|
public readonly int OnlineID;
|
||||||
public readonly string Filename;
|
public readonly string? MD5Hash;
|
||||||
|
public readonly string? Filename;
|
||||||
|
|
||||||
public GetBeatmapRequest(IBeatmapInfo beatmapInfo)
|
public GetBeatmapRequest(IBeatmapInfo beatmapInfo)
|
||||||
|
: this(onlineId: beatmapInfo.OnlineID, md5Hash: beatmapInfo.MD5Hash, filename: (beatmapInfo as BeatmapInfo)?.Path)
|
||||||
{
|
{
|
||||||
BeatmapInfo = beatmapInfo;
|
}
|
||||||
Filename = (beatmapInfo as BeatmapInfo)?.Path ?? string.Empty;
|
|
||||||
|
public GetBeatmapRequest(int onlineId = -1, string? md5Hash = null, string? filename = null)
|
||||||
|
{
|
||||||
|
OnlineID = onlineId;
|
||||||
|
MD5Hash = md5Hash;
|
||||||
|
Filename = filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override WebRequest CreateWebRequest()
|
protected override WebRequest CreateWebRequest()
|
||||||
{
|
{
|
||||||
var request = base.CreateWebRequest();
|
var request = base.CreateWebRequest();
|
||||||
|
|
||||||
if (BeatmapInfo.OnlineID > 0)
|
if (OnlineID > 0)
|
||||||
request.AddParameter(@"id", BeatmapInfo.OnlineID.ToString());
|
request.AddParameter(@"id", OnlineID.ToString(CultureInfo.InvariantCulture));
|
||||||
if (!string.IsNullOrEmpty(BeatmapInfo.MD5Hash))
|
if (!string.IsNullOrEmpty(MD5Hash))
|
||||||
request.AddParameter(@"checksum", BeatmapInfo.MD5Hash);
|
request.AddParameter(@"checksum", MD5Hash);
|
||||||
if (!string.IsNullOrEmpty(Filename))
|
if (!string.IsNullOrEmpty(Filename))
|
||||||
request.AddParameter(@"filename", Filename);
|
request.AddParameter(@"filename", Filename);
|
||||||
|
|
||||||
|
@ -2,9 +2,15 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
|
||||||
namespace osu.Game.Online.API.Requests
|
namespace osu.Game.Online.API.Requests
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up users with the given <see cref="UserIds"/>.
|
||||||
|
/// In comparison to <see cref="LookupUsersRequest"/>, the response here contains <see cref="APIUser.RulesetsStatistics"/>,
|
||||||
|
/// but in exchange is subject to more stringent rate limiting.
|
||||||
|
/// </summary>
|
||||||
public class GetUsersRequest : APIRequest<GetUsersResponse>
|
public class GetUsersRequest : APIRequest<GetUsersResponse>
|
||||||
{
|
{
|
||||||
public readonly int[] UserIds;
|
public readonly int[] UserIds;
|
||||||
|
30
osu.Game/Online/API/Requests/LookupUsersRequest.cs
Normal file
30
osu.Game/Online/API/Requests/LookupUsersRequest.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// 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.Online.API.Requests.Responses;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API.Requests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up users with the given <see cref="UserIds"/>.
|
||||||
|
/// In comparison to <see cref="GetUsersRequest"/>, the response here does not contain <see cref="APIUser.RulesetsStatistics"/>,
|
||||||
|
/// but in exchange is subject to less stringent rate limiting, making it suitable for mass user listings.
|
||||||
|
/// </summary>
|
||||||
|
public class LookupUsersRequest : APIRequest<GetUsersResponse>
|
||||||
|
{
|
||||||
|
public readonly int[] UserIds;
|
||||||
|
|
||||||
|
private const int max_ids_per_request = 50;
|
||||||
|
|
||||||
|
public LookupUsersRequest(int[] userIds)
|
||||||
|
{
|
||||||
|
if (userIds.Length > max_ids_per_request)
|
||||||
|
throw new ArgumentException($"{nameof(LookupUsersRequest)} calls only support up to {max_ids_per_request} IDs at once");
|
||||||
|
|
||||||
|
UserIds = userIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string Target => @"users/lookup/?ids[]=" + string.Join(@"&ids[]=", UserIds);
|
||||||
|
}
|
||||||
|
}
|
@ -261,7 +261,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
public APIUserHistoryCount[] ReplaysWatchedCounts;
|
public APIUserHistoryCount[] ReplaysWatchedCounts;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All user statistics per ruleset's short name (in the case of a <see cref="GetUsersRequest"/> response).
|
/// All user statistics per ruleset's short name (in the case of a <see cref="GetUsersRequest"/> or <see cref="GetMeRequest"/> response).
|
||||||
/// Otherwise empty. Can be altered for testing purposes.
|
/// Otherwise empty. Can be altered for testing purposes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// todo: this should likely be moved to a separate UserCompact class at some point.
|
// todo: this should likely be moved to a separate UserCompact class at some point.
|
||||||
|
@ -15,6 +15,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Multiplayer.Countdown;
|
using osu.Game.Online.Multiplayer.Countdown;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
@ -188,7 +189,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
|
|
||||||
// Populate users.
|
// Populate users.
|
||||||
Debug.Assert(joinedRoom.Users != null);
|
Debug.Assert(joinedRoom.Users != null);
|
||||||
await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)).ConfigureAwait(false);
|
await PopulateUsers(joinedRoom.Users).ConfigureAwait(false);
|
||||||
|
|
||||||
// Update the stored room (must be done on update thread for thread-safety).
|
// Update the stored room (must be done on update thread for thread-safety).
|
||||||
await runOnUpdateThreadAsync(() =>
|
await runOnUpdateThreadAsync(() =>
|
||||||
@ -416,7 +417,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
|
|
||||||
async Task IMultiplayerClient.UserJoined(MultiplayerRoomUser user)
|
async Task IMultiplayerClient.UserJoined(MultiplayerRoomUser user)
|
||||||
{
|
{
|
||||||
await PopulateUser(user).ConfigureAwait(false);
|
await PopulateUsers([user]).ConfigureAwait(false);
|
||||||
|
|
||||||
Scheduler.Add(() =>
|
Scheduler.Add(() =>
|
||||||
{
|
{
|
||||||
@ -803,10 +804,26 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Populates the <see cref="APIUser"/> for a given <see cref="MultiplayerRoomUser"/>.
|
/// Populates the <see cref="APIUser"/> for a given collection of <see cref="MultiplayerRoomUser"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="multiplayerUser">The <see cref="MultiplayerRoomUser"/> to populate.</param>
|
/// <param name="multiplayerUsers">The <see cref="MultiplayerRoomUser"/>s to populate.</param>
|
||||||
protected async Task PopulateUser(MultiplayerRoomUser multiplayerUser) => multiplayerUser.User ??= await userLookupCache.GetUserAsync(multiplayerUser.UserID).ConfigureAwait(false);
|
protected async Task PopulateUsers(IEnumerable<MultiplayerRoomUser> multiplayerUsers)
|
||||||
|
{
|
||||||
|
var request = new GetUsersRequest(multiplayerUsers.Select(u => u.UserID).Distinct().ToArray());
|
||||||
|
|
||||||
|
await API.PerformAsync(request).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (request.Response == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Dictionary<int, APIUser> users = request.Response.Users.ToDictionary(user => user.Id);
|
||||||
|
|
||||||
|
foreach (var multiplayerUser in multiplayerUsers)
|
||||||
|
{
|
||||||
|
if (users.TryGetValue(multiplayerUser.UserID, out var user))
|
||||||
|
multiplayerUser.User = user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the local room settings with the given <see cref="MultiplayerRoomSettings"/>.
|
/// Updates the local room settings with the given <see cref="MultiplayerRoomSettings"/>.
|
||||||
|
@ -17,19 +17,21 @@ namespace osu.Game.Online.Placeholders
|
|||||||
|
|
||||||
public ClickablePlaceholder(LocalisableString actionMessage, IconUsage icon)
|
public ClickablePlaceholder(LocalisableString actionMessage, IconUsage icon)
|
||||||
{
|
{
|
||||||
|
OsuAnimatedButton button;
|
||||||
OsuTextFlowContainer textFlow;
|
OsuTextFlowContainer textFlow;
|
||||||
|
|
||||||
AddArbitraryDrawable(new OsuAnimatedButton
|
AddArbitraryDrawable(button = new OsuAnimatedButton
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Framework.Graphics.Axes.Both,
|
AutoSizeAxes = Framework.Graphics.Axes.Both,
|
||||||
Child = textFlow = new OsuTextFlowContainer(cp => cp.Font = cp.Font.With(size: TEXT_SIZE))
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Framework.Graphics.Axes.Both,
|
|
||||||
Margin = new Framework.Graphics.MarginPadding(5)
|
|
||||||
},
|
|
||||||
Action = () => Action?.Invoke()
|
Action = () => Action?.Invoke()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
button.Add(textFlow = new OsuTextFlowContainer(cp => cp.Font = cp.Font.With(size: TEXT_SIZE))
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Framework.Graphics.Axes.Both,
|
||||||
|
Margin = new Framework.Graphics.MarginPadding(5)
|
||||||
|
});
|
||||||
|
|
||||||
textFlow.AddIcon(icon, i =>
|
textFlow.AddIcon(icon, i =>
|
||||||
{
|
{
|
||||||
i.Padding = new Framework.Graphics.MarginPadding { Right = 10 };
|
i.Padding = new Framework.Graphics.MarginPadding { Right = 10 };
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using MessagePack;
|
using MessagePack;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
@ -56,6 +58,17 @@ namespace osu.Game.Online.Spectator
|
|||||||
[Key(6)]
|
[Key(6)]
|
||||||
public DateTimeOffset ReceivedTime { get; set; }
|
public DateTimeOffset ReceivedTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The set of mods currently active.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Nullable for backwards compatibility with older clients
|
||||||
|
/// (these structures are also used server-side, and <see langword="null"/> will be used as marker that the data isn't there).
|
||||||
|
/// can be made non-nullable 20250407
|
||||||
|
/// </remarks>
|
||||||
|
[Key(7)]
|
||||||
|
public APIMod[]? Mods { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Construct header summary information from a point-in-time reference to a score which is actively being played.
|
/// Construct header summary information from a point-in-time reference to a score which is actively being played.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -69,6 +82,7 @@ namespace osu.Game.Online.Spectator
|
|||||||
MaxCombo = score.MaxCombo;
|
MaxCombo = score.MaxCombo;
|
||||||
// copy for safety
|
// copy for safety
|
||||||
Statistics = new Dictionary<HitResult, int>(score.Statistics);
|
Statistics = new Dictionary<HitResult, int>(score.Statistics);
|
||||||
|
Mods = score.APIMods.ToArray();
|
||||||
|
|
||||||
ScoreProcessorStatistics = statistics;
|
ScoreProcessorStatistics = statistics;
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,9 @@ namespace osu.Game.Overlays.BeatmapSet
|
|||||||
protected override void AddMetadata(string metadata, LinkFlowContainer loaded)
|
protected override void AddMetadata(string metadata, LinkFlowContainer loaded)
|
||||||
{
|
{
|
||||||
if (SearchAction != null)
|
if (SearchAction != null)
|
||||||
loaded.AddLink(metadata, () => SearchAction(metadata));
|
loaded.AddLink(metadata, () => SearchAction($@"source=""""{metadata}"""""));
|
||||||
else
|
else
|
||||||
loaded.AddLink(metadata, LinkAction.SearchBeatmapSet, metadata);
|
loaded.AddLink(metadata, LinkAction.SearchBeatmapSet, $@"source=""""{metadata}""""");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,10 +96,17 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
if (score != null)
|
if (score != null)
|
||||||
|
{
|
||||||
totalScoreColumn.Current = scoreManager.GetBindableTotalScoreString(score);
|
totalScoreColumn.Current = scoreManager.GetBindableTotalScoreString(score);
|
||||||
|
|
||||||
|
if (score.Accuracy == 1.0) accuracyColumn.TextColour = colours.GreenLight;
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
if (score.MaxCombo == score.BeatmapInfo!.MaxCombo) maxComboColumn.TextColour = colours.GreenLight;
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScoreInfo score;
|
private ScoreInfo score;
|
||||||
@ -228,6 +235,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
set => text.Text = value;
|
set => text.Text = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Colour4 TextColour
|
||||||
|
{
|
||||||
|
set => text.Colour = value;
|
||||||
|
}
|
||||||
|
|
||||||
public Drawable Drawable
|
public Drawable Drawable
|
||||||
{
|
{
|
||||||
set
|
set
|
||||||
|
@ -9,13 +9,17 @@ using osu.Framework.Extensions.LocalisationExtensions;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
using osu.Game.Overlays.Chat.Listing;
|
using osu.Game.Overlays.Chat.Listing;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Chat.ChannelList
|
namespace osu.Game.Overlays.Chat.ChannelList
|
||||||
{
|
{
|
||||||
@ -34,11 +38,12 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
private readonly Dictionary<Channel, ChannelListItem> channelMap = new Dictionary<Channel, ChannelListItem>();
|
private readonly Dictionary<Channel, ChannelListItem> channelMap = new Dictionary<Channel, ChannelListItem>();
|
||||||
|
|
||||||
private OsuScrollContainer scroll = null!;
|
private OsuScrollContainer scroll = null!;
|
||||||
private FillFlowContainer groupFlow = null!;
|
private SearchContainer groupFlow = null!;
|
||||||
private ChannelGroup announceChannelGroup = null!;
|
private ChannelGroup announceChannelGroup = null!;
|
||||||
private ChannelGroup publicChannelGroup = null!;
|
private ChannelGroup publicChannelGroup = null!;
|
||||||
private ChannelGroup privateChannelGroup = null!;
|
private ChannelGroup privateChannelGroup = null!;
|
||||||
private ChannelListItem selector = null!;
|
private ChannelListItem selector = null!;
|
||||||
|
private TextBox searchTextBox = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OverlayColourProvider colourProvider)
|
private void load(OverlayColourProvider colourProvider)
|
||||||
@ -55,13 +60,23 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
ScrollbarAnchor = Anchor.TopRight,
|
ScrollbarAnchor = Anchor.TopRight,
|
||||||
ScrollDistance = 35f,
|
ScrollDistance = 35f,
|
||||||
Child = groupFlow = new FillFlowContainer
|
Child = groupFlow = new SearchContainer
|
||||||
{
|
{
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding { Horizontal = 10, Top = 8 },
|
||||||
|
Child = searchTextBox = new ChannelSearchTextBox
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
}
|
||||||
|
},
|
||||||
announceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper()),
|
announceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper()),
|
||||||
publicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper()),
|
publicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper()),
|
||||||
selector = new ChannelListItem(ChannelListingChannel),
|
selector = new ChannelListItem(ChannelListingChannel),
|
||||||
@ -71,6 +86,19 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
searchTextBox.Current.BindValueChanged(_ => groupFlow.SearchTerm = searchTextBox.Current.Value, true);
|
||||||
|
searchTextBox.OnCommit += (_, _) =>
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(searchTextBox.Current.Value))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var firstMatchingItem = this.ChildrenOfType<ChannelListItem>().FirstOrDefault(item => item.MatchingFilter);
|
||||||
|
if (firstMatchingItem == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
OnRequestSelect?.Invoke(firstMatchingItem.Channel);
|
||||||
|
};
|
||||||
|
|
||||||
selector.OnRequestSelect += chan => OnRequestSelect?.Invoke(chan);
|
selector.OnRequestSelect += chan => OnRequestSelect?.Invoke(chan);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,5 +196,17 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private partial class ChannelSearchTextBox : BasicSearchTextBox
|
||||||
|
{
|
||||||
|
protected override bool AllowCommit => true;
|
||||||
|
|
||||||
|
public ChannelSearchTextBox()
|
||||||
|
{
|
||||||
|
const float scale_factor = 0.8f;
|
||||||
|
Scale = new Vector2(scale_factor);
|
||||||
|
Width = 1 / scale_factor;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -9,6 +10,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -19,7 +21,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Overlays.Chat.ChannelList
|
namespace osu.Game.Overlays.Chat.ChannelList
|
||||||
{
|
{
|
||||||
public partial class ChannelListItem : OsuClickableContainer
|
public partial class ChannelListItem : OsuClickableContainer, IFilterable
|
||||||
{
|
{
|
||||||
public event Action<Channel>? OnRequestSelect;
|
public event Action<Channel>? OnRequestSelect;
|
||||||
public event Action<Channel>? OnRequestLeave;
|
public event Action<Channel>? OnRequestLeave;
|
||||||
@ -186,5 +188,28 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
}
|
}
|
||||||
|
|
||||||
private bool isSelector => Channel is ChannelListing.ChannelListingChannel;
|
private bool isSelector => Channel is ChannelListing.ChannelListingChannel;
|
||||||
|
|
||||||
|
#region Filtering support
|
||||||
|
|
||||||
|
public IEnumerable<LocalisableString> FilterTerms => isSelector ? Enumerable.Empty<LocalisableString>() : [Channel.Name];
|
||||||
|
|
||||||
|
private bool matchingFilter = true;
|
||||||
|
|
||||||
|
public bool MatchingFilter
|
||||||
|
{
|
||||||
|
get => matchingFilter;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (matchingFilter == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
matchingFilter = value;
|
||||||
|
Alpha = matchingFilter ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FilteringActive { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -58,6 +59,21 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
modState.ValidForSelection.BindValueChanged(_ => updateFilterState());
|
modState.ValidForSelection.BindValueChanged(_ => updateFilterState());
|
||||||
modState.MatchingTextFilter.BindValueChanged(_ => updateFilterState(), true);
|
modState.MatchingTextFilter.BindValueChanged(_ => updateFilterState(), true);
|
||||||
|
modState.Preselected.BindValueChanged(b =>
|
||||||
|
{
|
||||||
|
if (b.NewValue)
|
||||||
|
{
|
||||||
|
Content.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = AccentColour,
|
||||||
|
Hollow = true,
|
||||||
|
Radius = 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Content.EdgeEffect = default;
|
||||||
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Select()
|
protected override void Select()
|
||||||
|
@ -243,6 +243,9 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
foreach (var column in columnFlow.Columns)
|
foreach (var column in columnFlow.Columns)
|
||||||
column.SearchTerm = query.NewValue;
|
column.SearchTerm = query.NewValue;
|
||||||
|
|
||||||
|
if (SearchTextBox.HasFocus)
|
||||||
|
preselectMod();
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
// Start scrolling from the end, to give the user a sense that
|
// Start scrolling from the end, to give the user a sense that
|
||||||
@ -254,6 +257,26 @@ namespace osu.Game.Overlays.Mods
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void preselectMod()
|
||||||
|
{
|
||||||
|
var visibleMods = columnFlow.Columns.OfType<ModColumn>().Where(c => c.IsPresent).SelectMany(c => c.AvailableMods.Where(m => m.Visible));
|
||||||
|
|
||||||
|
// Search for an exact acronym or name match, or otherwise default to the first visible mod.
|
||||||
|
ModState? matchingMod =
|
||||||
|
visibleMods.FirstOrDefault(m => m.Mod.Acronym.Equals(SearchTerm, StringComparison.OrdinalIgnoreCase) || m.Mod.Name.Equals(SearchTerm, StringComparison.OrdinalIgnoreCase))
|
||||||
|
?? visibleMods.FirstOrDefault();
|
||||||
|
var preselectedMod = matchingMod;
|
||||||
|
|
||||||
|
foreach (var mod in AllAvailableMods)
|
||||||
|
mod.Preselected.Value = mod == preselectedMod && SearchTextBox.Current.Value.Length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearPreselection()
|
||||||
|
{
|
||||||
|
foreach (var mod in AllAvailableMods)
|
||||||
|
mod.Preselected.Value = false;
|
||||||
|
}
|
||||||
|
|
||||||
public new ModSelectFooterContent? DisplayedFooterContent => base.DisplayedFooterContent as ModSelectFooterContent;
|
public new ModSelectFooterContent? DisplayedFooterContent => base.DisplayedFooterContent as ModSelectFooterContent;
|
||||||
|
|
||||||
public override VisibilityContainer CreateFooterContent() => new ModSelectFooterContent(this)
|
public override VisibilityContainer CreateFooterContent() => new ModSelectFooterContent(this)
|
||||||
@ -383,7 +406,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
columnScroll.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint);
|
columnScroll.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint);
|
||||||
SearchTextBox.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint);
|
SearchTextBox.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint);
|
||||||
SearchTextBox.KillFocus();
|
setTextBoxFocus(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -590,11 +613,11 @@ namespace osu.Game.Overlays.Mods
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModState? firstMod = columnFlow.Columns.OfType<ModColumn>().FirstOrDefault(m => m.IsPresent)?.AvailableMods.FirstOrDefault(x => x.Visible);
|
var matchingMod = AllAvailableMods.SingleOrDefault(m => m.Preselected.Value);
|
||||||
|
|
||||||
if (firstMod is not null)
|
if (matchingMod is not null)
|
||||||
{
|
{
|
||||||
firstMod.Active.Value = !firstMod.Active.Value;
|
matchingMod.Active.Value = !matchingMod.Active.Value;
|
||||||
SearchTextBox.SelectAll();
|
SearchTextBox.SelectAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -648,9 +671,15 @@ namespace osu.Game.Overlays.Mods
|
|||||||
private void setTextBoxFocus(bool focus)
|
private void setTextBoxFocus(bool focus)
|
||||||
{
|
{
|
||||||
if (focus)
|
if (focus)
|
||||||
|
{
|
||||||
SearchTextBox.TakeFocus();
|
SearchTextBox.TakeFocus();
|
||||||
|
preselectMod();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
SearchTextBox.KillFocus();
|
SearchTextBox.KillFocus();
|
||||||
|
clearPreselection();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -22,6 +22,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public BindableBool Active { get; } = new BindableBool();
|
public BindableBool Active { get; } = new BindableBool();
|
||||||
|
|
||||||
|
public BindableBool Preselected { get; } = new BindableBool();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the mod requires further customisation.
|
/// Whether the mod requires further customisation.
|
||||||
/// This flag is read by the <see cref="ModSelectOverlay"/> to determine if the customisation panel should be opened after a mod change
|
/// This flag is read by the <see cref="ModSelectOverlay"/> to determine if the customisation panel should be opened after a mod change
|
||||||
|
34
osu.Game/Rulesets/Edit/ExpandableSpriteText.cs
Normal file
34
osu.Game/Rulesets/Edit/ExpandableSpriteText.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit
|
||||||
|
{
|
||||||
|
internal partial class ExpandableSpriteText : OsuSpriteText, IExpandable
|
||||||
|
{
|
||||||
|
public BindableBool Expanded { get; } = new BindableBool();
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private IExpandingContainer? expandingContainer { get; set; }
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
expandingContainer?.Expanded.BindValueChanged(containerExpanded =>
|
||||||
|
{
|
||||||
|
Expanded.Value = containerExpanded.NewValue;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
Expanded.BindValueChanged(expanded =>
|
||||||
|
{
|
||||||
|
this.FadeTo(expanded.NewValue ? 1 : 0, 150, Easing.OutQuint);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,13 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Edit
|
namespace osu.Game.Rulesets.Edit
|
||||||
@ -12,6 +16,15 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
{
|
{
|
||||||
protected override double HoverExpansionDelay => 250;
|
protected override double HoverExpansionDelay => 250;
|
||||||
|
|
||||||
|
protected override bool ExpandOnHover => expandOnHover;
|
||||||
|
|
||||||
|
private readonly Bindable<bool> contractSidebars = new Bindable<bool>();
|
||||||
|
|
||||||
|
private bool expandOnHover;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Editor? editor { get; set; }
|
||||||
|
|
||||||
public ExpandingToolboxContainer(float contractedWidth, float expandedWidth)
|
public ExpandingToolboxContainer(float contractedWidth, float expandedWidth)
|
||||||
: base(contractedWidth, expandedWidth)
|
: base(contractedWidth, expandedWidth)
|
||||||
{
|
{
|
||||||
@ -19,6 +32,27 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
FillFlow.Spacing = new Vector2(5);
|
FillFlow.Spacing = new Vector2(5);
|
||||||
FillFlow.Padding = new MarginPadding { Vertical = 5 };
|
FillFlow.Padding = new MarginPadding { Vertical = 5 };
|
||||||
|
|
||||||
|
Expanded.Value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
config.BindWith(OsuSetting.EditorContractSidebars, contractSidebars);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
bool requireContracting = contractSidebars.Value || editor?.DrawSize.X / editor?.DrawSize.Y < 1.7f;
|
||||||
|
|
||||||
|
if (expandOnHover != requireContracting)
|
||||||
|
{
|
||||||
|
expandOnHover = requireContracting;
|
||||||
|
Expanded.Value = !expandOnHover;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && anyToolboxHovered(screenSpacePos);
|
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && anyToolboxHovered(screenSpacePos);
|
||||||
|
@ -18,6 +18,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets.Configuration;
|
using osu.Game.Rulesets.Configuration;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
@ -178,16 +179,56 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
Spacing = new Vector2(0, 5),
|
Spacing = new Vector2(0, 5),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
new EditorToolboxGroup("bank (Shift-Q~R)")
|
new EditorToolboxGroup("bank (Shift/Alt-Q~R)")
|
||||||
{
|
{
|
||||||
Child = sampleBankTogglesCollection = new FillFlowContainer
|
Child = new FillFlowContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Spacing = new Vector2(0, 5),
|
Spacing = new Vector2(0, 5),
|
||||||
},
|
Children = new Drawable[]
|
||||||
}
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new ExpandableSpriteText
|
||||||
|
{
|
||||||
|
Text = "Normal",
|
||||||
|
AlwaysPresent = true,
|
||||||
|
AllowMultiline = false,
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
X = 0.25f,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 17),
|
||||||
|
},
|
||||||
|
new ExpandableSpriteText
|
||||||
|
{
|
||||||
|
Text = "Addition",
|
||||||
|
AlwaysPresent = true,
|
||||||
|
AllowMultiline = false,
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
X = 0.75f,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 17),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sampleBankTogglesCollection = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(0, 5),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -231,7 +272,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
TernaryStates = CreateTernaryButtons().ToArray();
|
TernaryStates = CreateTernaryButtons().ToArray();
|
||||||
togglesCollection.AddRange(TernaryStates.Select(b => new DrawableTernaryButton(b)));
|
togglesCollection.AddRange(TernaryStates.Select(b => new DrawableTernaryButton(b)));
|
||||||
|
|
||||||
sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Select(b => new DrawableTernaryButton(b)));
|
sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Zip(BlueprintContainer.SampleAdditionBankTernaryStates).Select(b => new SampleBankTernaryButton(b.First, b.Second)));
|
||||||
|
|
||||||
SetSelectTool();
|
SetSelectTool();
|
||||||
|
|
||||||
@ -303,8 +344,8 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
PlayfieldContentContainer.Anchor = Anchor.CentreLeft;
|
PlayfieldContentContainer.Anchor = Anchor.CentreLeft;
|
||||||
PlayfieldContentContainer.Origin = Anchor.CentreLeft;
|
PlayfieldContentContainer.Origin = Anchor.CentreLeft;
|
||||||
|
|
||||||
PlayfieldContentContainer.Width = Math.Max(1024, DrawWidth) - (TOOLBOX_CONTRACTED_SIZE_LEFT + TOOLBOX_CONTRACTED_SIZE_RIGHT);
|
PlayfieldContentContainer.Width = Math.Max(1024, DrawWidth);
|
||||||
PlayfieldContentContainer.X = TOOLBOX_CONTRACTED_SIZE_LEFT;
|
PlayfieldContentContainer.X = LeftToolbox.DrawWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
composerFocusMode.Value = PlayfieldContentContainer.Contains(InputManager.CurrentState.Mouse.Position)
|
composerFocusMode.Value = PlayfieldContentContainer.Contains(InputManager.CurrentState.Mouse.Position)
|
||||||
@ -362,7 +403,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
{
|
{
|
||||||
if (e.ControlPressed || e.AltPressed || e.SuperPressed)
|
if (e.ControlPressed || e.SuperPressed)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (checkToolboxMappingFromKey(e.Key, out int leftIndex))
|
if (checkToolboxMappingFromKey(e.Key, out int leftIndex))
|
||||||
@ -379,14 +420,26 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
if (checkToggleMappingFromKey(e.Key, out int rightIndex))
|
if (checkToggleMappingFromKey(e.Key, out int rightIndex))
|
||||||
{
|
{
|
||||||
var item = e.ShiftPressed
|
if (e.ShiftPressed || e.AltPressed)
|
||||||
? sampleBankTogglesCollection.ElementAtOrDefault(rightIndex)
|
|
||||||
: togglesCollection.ElementAtOrDefault(rightIndex);
|
|
||||||
|
|
||||||
if (item is DrawableTernaryButton button)
|
|
||||||
{
|
{
|
||||||
button.Button.Toggle();
|
if (sampleBankTogglesCollection.ElementAtOrDefault(rightIndex) is SampleBankTernaryButton sampleBankTernaryButton)
|
||||||
return true;
|
{
|
||||||
|
if (e.ShiftPressed)
|
||||||
|
sampleBankTernaryButton.NormalButton.Toggle();
|
||||||
|
|
||||||
|
if (e.AltPressed)
|
||||||
|
sampleBankTernaryButton.AdditionsButton.Toggle();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (togglesCollection.ElementAtOrDefault(rightIndex) is DrawableTernaryButton button)
|
||||||
|
{
|
||||||
|
button.Button.Toggle();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,11 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AutomaticBankAssignment { get; set; }
|
public bool AutomaticBankAssignment { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the sample addition bank should be taken from the previous hit objects.
|
||||||
|
/// </summary>
|
||||||
|
public bool AutomaticAdditionBankAssignment { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="HitObject"/> that is being placed.
|
/// The <see cref="HitObject"/> that is being placed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -73,7 +78,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the time and position of this <see cref="HitObjectPlacementBlueprint"/> based on the provided snap information.
|
/// Updates the time and position of this <see cref="PlacementBlueprint"/> based on the provided snap information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="result">The snap result information.</param>
|
/// <param name="result">The snap result information.</param>
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
public override void UpdateTimeAndPosition(SnapResult result)
|
||||||
@ -87,26 +92,26 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
}
|
}
|
||||||
|
|
||||||
var lastHitObject = getPreviousHitObject();
|
var lastHitObject = getPreviousHitObject();
|
||||||
|
var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
|
||||||
|
|
||||||
if (AutomaticBankAssignment)
|
if (AutomaticAdditionBankAssignment)
|
||||||
{
|
{
|
||||||
// Create samples based on the sample settings of the previous hit object
|
// Inherit the addition bank from the previous hit object
|
||||||
if (lastHitObject != null)
|
// If there is no previous addition, inherit from the normal sample
|
||||||
{
|
var lastAddition = lastHitObject?.Samples?.FirstOrDefault(o => o.Name != HitSampleInfo.HIT_NORMAL) ?? lastHitNormal;
|
||||||
for (int i = 0; i < HitObject.Samples.Count; i++)
|
|
||||||
HitObject.Samples[i] = lastHitObject.CreateHitSampleInfo(HitObject.Samples[i].Name);
|
if (lastAddition != null)
|
||||||
}
|
HitObject.Samples = HitObject.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: lastAddition.Bank) : s).ToList();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
|
|
||||||
|
|
||||||
if (lastHitNormal != null)
|
if (lastHitNormal != null)
|
||||||
{
|
{
|
||||||
// Only inherit the volume from the previous hit object
|
if (AutomaticBankAssignment)
|
||||||
for (int i = 0; i < HitObject.Samples.Count; i++)
|
// Inherit the bank from the previous hit object
|
||||||
HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume);
|
HitObject.Samples = HitObject.Samples.Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: lastHitNormal.Bank) : s).ToList();
|
||||||
}
|
|
||||||
|
// Inherit the volume from the previous hit object
|
||||||
|
HitObject.Samples = HitObject.Samples.Select(s => s.With(newVolume: lastHitNormal.Volume)).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HitObject is IHasRepeats hasRepeats)
|
if (HitObject is IHasRepeats hasRepeats)
|
||||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
private PlayfieldAdjustmentContainer playfieldAdjustmentContainer = null!;
|
private PlayfieldAdjustmentContainer playfieldAdjustmentContainer = null!;
|
||||||
|
|
||||||
public void Update(Playfield playfield)
|
public virtual void Update(Playfield playfield)
|
||||||
{
|
{
|
||||||
playfieldAdjustmentContainer.Rotation = CurrentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value);
|
playfieldAdjustmentContainer.Rotation = CurrentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value);
|
||||||
}
|
}
|
||||||
|
@ -233,7 +233,7 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
|
|
||||||
// Fall back to using the normal sample bank otherwise.
|
// Fall back to using the normal sample bank otherwise.
|
||||||
if (Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) is HitSampleInfo existingNormal)
|
if (Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) is HitSampleInfo existingNormal)
|
||||||
return existingNormal.With(newName: sampleName);
|
return existingNormal.With(newName: sampleName, newEditorAutoBank: true);
|
||||||
|
|
||||||
return new HitSampleInfo(sampleName);
|
return new HitSampleInfo(sampleName);
|
||||||
}
|
}
|
||||||
|
@ -204,8 +204,14 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
if (stringBank == @"none")
|
if (stringBank == @"none")
|
||||||
stringBank = null;
|
stringBank = null;
|
||||||
string stringAddBank = addBank.ToString().ToLowerInvariant();
|
string stringAddBank = addBank.ToString().ToLowerInvariant();
|
||||||
|
|
||||||
if (stringAddBank == @"none")
|
if (stringAddBank == @"none")
|
||||||
|
{
|
||||||
|
bankInfo.EditorAutoBank = true;
|
||||||
stringAddBank = null;
|
stringAddBank = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
bankInfo.EditorAutoBank = false;
|
||||||
|
|
||||||
bankInfo.BankForNormal = stringBank;
|
bankInfo.BankForNormal = stringBank;
|
||||||
bankInfo.BankForAdditions = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank;
|
bankInfo.BankForAdditions = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank;
|
||||||
@ -477,7 +483,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
|
|
||||||
if (string.IsNullOrEmpty(bankInfo.Filename))
|
if (string.IsNullOrEmpty(bankInfo.Filename))
|
||||||
{
|
{
|
||||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.BankForNormal, bankInfo.Volume, bankInfo.CustomSampleBank,
|
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.BankForNormal, bankInfo.Volume, true, bankInfo.CustomSampleBank,
|
||||||
// if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample.
|
// if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample.
|
||||||
// None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds
|
// None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds
|
||||||
type != LegacyHitSoundType.None && !type.HasFlag(LegacyHitSoundType.Normal)));
|
type != LegacyHitSoundType.None && !type.HasFlag(LegacyHitSoundType.Normal)));
|
||||||
@ -489,13 +495,13 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type.HasFlag(LegacyHitSoundType.Finish))
|
if (type.HasFlag(LegacyHitSoundType.Finish))
|
||||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.EditorAutoBank, bankInfo.CustomSampleBank));
|
||||||
|
|
||||||
if (type.HasFlag(LegacyHitSoundType.Whistle))
|
if (type.HasFlag(LegacyHitSoundType.Whistle))
|
||||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.EditorAutoBank, bankInfo.CustomSampleBank));
|
||||||
|
|
||||||
if (type.HasFlag(LegacyHitSoundType.Clap))
|
if (type.HasFlag(LegacyHitSoundType.Clap))
|
||||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.EditorAutoBank, bankInfo.CustomSampleBank));
|
||||||
|
|
||||||
return soundTypes;
|
return soundTypes;
|
||||||
}
|
}
|
||||||
@ -534,6 +540,11 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int CustomSampleBank;
|
public int CustomSampleBank;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the bank for additions should be inherited from the normal sample in edit.
|
||||||
|
/// </summary>
|
||||||
|
public bool EditorAutoBank = true;
|
||||||
|
|
||||||
public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
|
public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -558,21 +569,21 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool BankSpecified;
|
public bool BankSpecified;
|
||||||
|
|
||||||
public LegacyHitSampleInfo(string name, string? bank = null, int volume = 0, int customSampleBank = 0, bool isLayered = false)
|
public LegacyHitSampleInfo(string name, string? bank = null, int volume = 0, bool editorAutoBank = false, int customSampleBank = 0, bool isLayered = false)
|
||||||
: base(name, bank ?? SampleControlPoint.DEFAULT_BANK, customSampleBank >= 2 ? customSampleBank.ToString() : null, volume)
|
: base(name, bank ?? SampleControlPoint.DEFAULT_BANK, customSampleBank >= 2 ? customSampleBank.ToString() : null, volume, editorAutoBank)
|
||||||
{
|
{
|
||||||
CustomSampleBank = customSampleBank;
|
CustomSampleBank = customSampleBank;
|
||||||
BankSpecified = !string.IsNullOrEmpty(bank);
|
BankSpecified = !string.IsNullOrEmpty(bank);
|
||||||
IsLayered = isLayered;
|
IsLayered = isLayered;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default)
|
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default, Optional<bool> newEditorAutoBank = default)
|
||||||
=> With(newName, newBank, newVolume);
|
=> With(newName, newBank, newVolume, newEditorAutoBank);
|
||||||
|
|
||||||
public virtual LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default,
|
public virtual LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default, Optional<bool> newEditorAutoBank = default,
|
||||||
Optional<int> newCustomSampleBank = default,
|
Optional<int> newCustomSampleBank = default,
|
||||||
Optional<bool> newIsLayered = default)
|
Optional<bool> newIsLayered = default)
|
||||||
=> new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered));
|
=> new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newEditorAutoBank.GetOr(EditorAutoBank), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered));
|
||||||
|
|
||||||
public bool Equals(LegacyHitSampleInfo? other)
|
public bool Equals(LegacyHitSampleInfo? other)
|
||||||
// The additions to equality checks here are *required* to ensure that pooling works correctly.
|
// The additions to equality checks here are *required* to ensure that pooling works correctly.
|
||||||
@ -604,7 +615,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
Path.ChangeExtension(Filename, null)
|
Path.ChangeExtension(Filename, null)
|
||||||
};
|
};
|
||||||
|
|
||||||
public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default,
|
public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default, Optional<bool> newEditorAutoBank = default,
|
||||||
Optional<int> newCustomSampleBank = default,
|
Optional<int> newCustomSampleBank = default,
|
||||||
Optional<bool> newIsLayered = default)
|
Optional<bool> newIsLayered = default)
|
||||||
=> new FileHitSampleInfo(Filename, newVolume.GetOr(Volume));
|
=> new FileHitSampleInfo(Filename, newVolume.GetOr(Volume));
|
||||||
|
@ -101,7 +101,7 @@ namespace osu.Game.Rulesets
|
|||||||
/// <param name="acronym">The acronym to query for .</param>
|
/// <param name="acronym">The acronym to query for .</param>
|
||||||
public Mod? CreateModFromAcronym(string acronym)
|
public Mod? CreateModFromAcronym(string acronym)
|
||||||
{
|
{
|
||||||
return AllMods.FirstOrDefault(m => m.Acronym == acronym)?.CreateInstance();
|
return AllMods.FirstOrDefault(m => string.Equals(m.Acronym, acronym, StringComparison.OrdinalIgnoreCase))?.CreateInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -65,22 +65,20 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public override Playfield Playfield => playfield.Value;
|
public override Playfield Playfield => playfield.Value;
|
||||||
|
|
||||||
|
public override PlayfieldAdjustmentContainer PlayfieldAdjustmentContainer => playfieldAdjustmentContainer;
|
||||||
|
|
||||||
public override Container Overlays { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
public override Container Overlays { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
public override IAdjustableAudioComponent Audio => audioContainer;
|
public override IAdjustableAudioComponent Audio => audioContainer;
|
||||||
|
|
||||||
private readonly AudioContainer audioContainer = new AudioContainer { RelativeSizeAxes = Axes.Both };
|
private readonly AudioContainer audioContainer = new AudioContainer { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A container which encapsulates the <see cref="Playfield"/> and provides any adjustments to
|
|
||||||
/// ensure correct scale and position.
|
|
||||||
/// </summary>
|
|
||||||
public virtual PlayfieldAdjustmentContainer PlayfieldAdjustmentContainer { get; private set; }
|
|
||||||
|
|
||||||
public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
public override IFrameStableClock FrameStableClock => frameStabilityContainer;
|
public override IFrameStableClock FrameStableClock => frameStabilityContainer;
|
||||||
|
|
||||||
|
private readonly PlayfieldAdjustmentContainer playfieldAdjustmentContainer;
|
||||||
|
|
||||||
private bool allowBackwardsSeeks;
|
private bool allowBackwardsSeeks;
|
||||||
|
|
||||||
public override bool AllowBackwardsSeeks
|
public override bool AllowBackwardsSeeks
|
||||||
@ -146,6 +144,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
KeyBindingInputManager = CreateInputManager();
|
KeyBindingInputManager = CreateInputManager();
|
||||||
|
playfieldAdjustmentContainer = CreatePlayfieldAdjustmentContainer();
|
||||||
playfield = new Lazy<Playfield>(() => CreatePlayfield().With(p =>
|
playfield = new Lazy<Playfield>(() => CreatePlayfield().With(p =>
|
||||||
{
|
{
|
||||||
p.NewResult += (_, r) => NewResult?.Invoke(r);
|
p.NewResult += (_, r) => NewResult?.Invoke(r);
|
||||||
@ -197,8 +196,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
audioContainer.WithChild(KeyBindingInputManager
|
audioContainer.WithChild(KeyBindingInputManager
|
||||||
.WithChildren(new Drawable[]
|
.WithChildren(new Drawable[]
|
||||||
{
|
{
|
||||||
PlayfieldAdjustmentContainer = CreatePlayfieldAdjustmentContainer()
|
playfieldAdjustmentContainer.WithChild(Playfield),
|
||||||
.WithChild(Playfield),
|
|
||||||
Overlays
|
Overlays
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
@ -456,6 +454,12 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract Playfield Playfield { get; }
|
public abstract Playfield Playfield { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A container which encapsulates the <see cref="Playfield"/> and provides any adjustments to
|
||||||
|
/// ensure correct scale and position.
|
||||||
|
/// </summary>
|
||||||
|
public abstract PlayfieldAdjustmentContainer PlayfieldAdjustmentContainer { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Content to be placed above hitobjects. Will be affected by frame stability and adjustments applied to <see cref="Audio"/>.
|
/// Content to be placed above hitobjects. Will be affected by frame stability and adjustments applied to <see cref="Audio"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -4,8 +4,10 @@
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -14,14 +16,14 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
||||||
{
|
{
|
||||||
public partial class DrawableTernaryButton : OsuButton
|
public partial class DrawableTernaryButton : OsuButton, IHasTooltip
|
||||||
{
|
{
|
||||||
private Color4 defaultBackgroundColour;
|
private Color4 defaultBackgroundColour;
|
||||||
private Color4 defaultIconColour;
|
private Color4 defaultIconColour;
|
||||||
private Color4 selectedBackgroundColour;
|
private Color4 selectedBackgroundColour;
|
||||||
private Color4 selectedIconColour;
|
private Color4 selectedIconColour;
|
||||||
|
|
||||||
private Drawable icon = null!;
|
protected Drawable Icon { get; private set; } = null!;
|
||||||
|
|
||||||
public readonly TernaryButton Button;
|
public readonly TernaryButton Button;
|
||||||
|
|
||||||
@ -43,7 +45,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
|||||||
defaultIconColour = defaultBackgroundColour.Darken(0.5f);
|
defaultIconColour = defaultBackgroundColour.Darken(0.5f);
|
||||||
selectedIconColour = selectedBackgroundColour.Lighten(0.5f);
|
selectedIconColour = selectedBackgroundColour.Lighten(0.5f);
|
||||||
|
|
||||||
Add(icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b =>
|
Add(Icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b =>
|
||||||
{
|
{
|
||||||
b.Blending = BlendingParameters.Additive;
|
b.Blending = BlendingParameters.Additive;
|
||||||
b.Anchor = Anchor.CentreLeft;
|
b.Anchor = Anchor.CentreLeft;
|
||||||
@ -58,12 +60,16 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
Button.Bindable.BindValueChanged(_ => updateSelectionState(), true);
|
Button.Bindable.BindValueChanged(_ => updateSelectionState(), true);
|
||||||
|
Button.Enabled.BindTo(Enabled);
|
||||||
|
|
||||||
Action = onAction;
|
Action = onAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onAction()
|
private void onAction()
|
||||||
{
|
{
|
||||||
|
if (!Button.Enabled.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
Button.Toggle();
|
Button.Toggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,17 +81,17 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
|||||||
switch (Button.Bindable.Value)
|
switch (Button.Bindable.Value)
|
||||||
{
|
{
|
||||||
case TernaryState.Indeterminate:
|
case TernaryState.Indeterminate:
|
||||||
icon.Colour = selectedIconColour.Darken(0.5f);
|
Icon.Colour = selectedIconColour.Darken(0.5f);
|
||||||
BackgroundColour = selectedBackgroundColour.Darken(0.5f);
|
BackgroundColour = selectedBackgroundColour.Darken(0.5f);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TernaryState.False:
|
case TernaryState.False:
|
||||||
icon.Colour = defaultIconColour;
|
Icon.Colour = defaultIconColour;
|
||||||
BackgroundColour = defaultBackgroundColour;
|
BackgroundColour = defaultBackgroundColour;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TernaryState.True:
|
case TernaryState.True:
|
||||||
icon.Colour = selectedIconColour;
|
Icon.Colour = selectedIconColour;
|
||||||
BackgroundColour = selectedBackgroundColour;
|
BackgroundColour = selectedBackgroundColour;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -98,5 +104,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
|||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
X = 40f
|
X = 40f
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public LocalisableString TooltipText => Button.Tooltip;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
||||||
|
{
|
||||||
|
public partial class SampleBankTernaryButton : CompositeDrawable
|
||||||
|
{
|
||||||
|
public readonly TernaryButton NormalButton;
|
||||||
|
public readonly TernaryButton AdditionsButton;
|
||||||
|
|
||||||
|
public SampleBankTernaryButton(TernaryButton normalButton, TernaryButton additionsButton)
|
||||||
|
{
|
||||||
|
NormalButton = normalButton;
|
||||||
|
AdditionsButton = additionsButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = 5;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Width = 0.5f,
|
||||||
|
Padding = new MarginPadding { Right = 1 },
|
||||||
|
Child = new InlineDrawableTernaryButton(NormalButton),
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Width = 0.5f,
|
||||||
|
Padding = new MarginPadding { Left = 1 },
|
||||||
|
Child = new InlineDrawableTernaryButton(AdditionsButton),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class InlineDrawableTernaryButton : DrawableTernaryButton
|
||||||
|
{
|
||||||
|
public InlineDrawableTernaryButton(TernaryButton button)
|
||||||
|
: base(button)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Content.Masking = false;
|
||||||
|
Content.CornerRadius = 0;
|
||||||
|
Icon.X = 4.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SpriteText CreateText() => new ExpandableSpriteText
|
||||||
|
{
|
||||||
|
Depth = -1,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
X = 31f
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,8 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
|||||||
{
|
{
|
||||||
public readonly Bindable<TernaryState> Bindable;
|
public readonly Bindable<TernaryState> Bindable;
|
||||||
|
|
||||||
|
public readonly Bindable<bool> Enabled = new Bindable<bool>(true);
|
||||||
|
|
||||||
public readonly string Description;
|
public readonly string Description;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -19,6 +21,8 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly Func<Drawable>? CreateIcon;
|
public readonly Func<Drawable>? CreateIcon;
|
||||||
|
|
||||||
|
public string Tooltip { get; set; } = string.Empty;
|
||||||
|
|
||||||
public TernaryButton(Bindable<TernaryState> bindable, string description, Func<Drawable>? createIcon = null)
|
public TernaryButton(Bindable<TernaryState> bindable, string description, Func<Drawable>? createIcon = null)
|
||||||
{
|
{
|
||||||
Bindable = bindable;
|
Bindable = bindable;
|
||||||
|
@ -32,7 +32,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
protected DragBox DragBox { get; private set; }
|
protected DragBox DragBox { get; private set; }
|
||||||
|
|
||||||
public Container<SelectionBlueprint<T>> SelectionBlueprints { get; private set; }
|
public SelectionBlueprintContainer SelectionBlueprints { get; private set; }
|
||||||
|
|
||||||
|
public partial class SelectionBlueprintContainer : Container<SelectionBlueprint<T>>
|
||||||
|
{
|
||||||
|
public new virtual void ChangeChildDepth(SelectionBlueprint<T> child, float newDepth) => base.ChangeChildDepth(child, newDepth);
|
||||||
|
}
|
||||||
|
|
||||||
public SelectionHandler<T> SelectionHandler { get; private set; }
|
public SelectionHandler<T> SelectionHandler { get; private set; }
|
||||||
|
|
||||||
@ -95,7 +100,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Container<SelectionBlueprint<T>> CreateSelectionBlueprintContainer() => new Container<SelectionBlueprint<T>> { RelativeSizeAxes = Axes.Both };
|
protected virtual SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a <see cref="Components.SelectionHandler{T}"/> which outlines items and handles movement of selections.
|
/// Creates a <see cref="Components.SelectionHandler{T}"/> which outlines items and handles movement of selections.
|
||||||
|
@ -65,7 +65,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
MainTernaryStates = CreateTernaryButtons().ToArray();
|
MainTernaryStates = CreateTernaryButtons().ToArray();
|
||||||
SampleBankTernaryStates = createSampleBankTernaryButtons().ToArray();
|
SampleBankTernaryStates = createSampleBankTernaryButtons(SelectionHandler.SelectionBankStates).ToArray();
|
||||||
|
SampleAdditionBankTernaryStates = createSampleBankTernaryButtons(SelectionHandler.SelectionAdditionBankStates).ToArray();
|
||||||
|
|
||||||
|
SelectionHandler.AutoSelectionBankEnabled.BindValueChanged(_ => updateAutoBankTernaryButtonTooltip(), true);
|
||||||
|
SelectionHandler.SelectionAdditionBanksEnabled.BindValueChanged(_ => updateAdditionBankTernaryButtonTooltips(), true);
|
||||||
|
|
||||||
AddInternal(new DrawableRulesetDependenciesProvidingContainer(Composer.Ruleset)
|
AddInternal(new DrawableRulesetDependenciesProvidingContainer(Composer.Ruleset)
|
||||||
{
|
{
|
||||||
@ -91,6 +95,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
foreach (var kvp in SelectionHandler.SelectionBankStates)
|
foreach (var kvp in SelectionHandler.SelectionBankStates)
|
||||||
kvp.Value.BindValueChanged(_ => updatePlacementSamples());
|
kvp.Value.BindValueChanged(_ => updatePlacementSamples());
|
||||||
|
|
||||||
|
foreach (var kvp in SelectionHandler.SelectionAdditionBankStates)
|
||||||
|
kvp.Value.BindValueChanged(_ => updatePlacementSamples());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject)
|
protected override void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject)
|
||||||
@ -179,6 +186,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
foreach (var kvp in SelectionHandler.SelectionBankStates)
|
foreach (var kvp in SelectionHandler.SelectionBankStates)
|
||||||
bankChanged(kvp.Key, kvp.Value.Value);
|
bankChanged(kvp.Key, kvp.Value.Value);
|
||||||
|
|
||||||
|
foreach (var kvp in SelectionHandler.SelectionAdditionBankStates)
|
||||||
|
additionBankChanged(kvp.Key, kvp.Value.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sampleChanged(string sampleName, TernaryState state)
|
private void sampleChanged(string sampleName, TernaryState state)
|
||||||
@ -210,7 +220,17 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
if (bankName == EditorSelectionHandler.HIT_BANK_AUTO)
|
if (bankName == EditorSelectionHandler.HIT_BANK_AUTO)
|
||||||
CurrentHitObjectPlacement.AutomaticBankAssignment = state == TernaryState.True;
|
CurrentHitObjectPlacement.AutomaticBankAssignment = state == TernaryState.True;
|
||||||
else if (state == TernaryState.True)
|
else if (state == TernaryState.True)
|
||||||
CurrentHitObjectPlacement.HitObject.Samples = CurrentHitObjectPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList();
|
CurrentHitObjectPlacement.HitObject.Samples = CurrentHitObjectPlacement.HitObject.Samples.Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void additionBankChanged(string bankName, TernaryState state)
|
||||||
|
{
|
||||||
|
if (CurrentHitObjectPlacement == null) return;
|
||||||
|
|
||||||
|
if (bankName == EditorSelectionHandler.HIT_BANK_AUTO)
|
||||||
|
CurrentHitObjectPlacement.AutomaticAdditionBankAssignment = state == TernaryState.True;
|
||||||
|
else if (state == TernaryState.True)
|
||||||
|
CurrentHitObjectPlacement.HitObject.Samples = CurrentHitObjectPlacement.HitObject.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly Bindable<TernaryState> NewCombo = new Bindable<TernaryState> { Description = "New Combo" };
|
public readonly Bindable<TernaryState> NewCombo = new Bindable<TernaryState> { Description = "New Combo" };
|
||||||
@ -222,6 +242,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
public TernaryButton[] SampleBankTernaryStates { get; private set; }
|
public TernaryButton[] SampleBankTernaryStates { get; private set; }
|
||||||
|
|
||||||
|
public TernaryButton[] SampleAdditionBankTernaryStates { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create all ternary states required to be displayed to the user.
|
/// Create all ternary states required to be displayed to the user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -234,36 +256,21 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
yield return new TernaryButton(kvp.Value, kvp.Key.Replace("hit", string.Empty).Titleize(), () => GetIconForSample(kvp.Key));
|
yield return new TernaryButton(kvp.Value, kvp.Key.Replace("hit", string.Empty).Titleize(), () => GetIconForSample(kvp.Key));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<TernaryButton> createSampleBankTernaryButtons()
|
private IEnumerable<TernaryButton> createSampleBankTernaryButtons(Dictionary<string, Bindable<TernaryState>> sampleBankStates)
|
||||||
{
|
{
|
||||||
foreach (var kvp in SelectionHandler.SelectionBankStates)
|
foreach (var kvp in sampleBankStates)
|
||||||
yield return new TernaryButton(kvp.Value, kvp.Key.Titleize(), () => getIconForBank(kvp.Key));
|
yield return new TernaryButton(kvp.Value, kvp.Key.Titleize(), () => getIconForBank(kvp.Key));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable getIconForBank(string sampleName)
|
private Drawable getIconForBank(string sampleName)
|
||||||
{
|
{
|
||||||
return new Container
|
return new OsuSpriteText
|
||||||
{
|
{
|
||||||
Size = new Vector2(30, 20),
|
Anchor = Anchor.Centre,
|
||||||
Children = new Drawable[]
|
Origin = Anchor.Centre,
|
||||||
{
|
Y = -1,
|
||||||
new SpriteIcon
|
Font = OsuFont.Default.With(weight: FontWeight.Bold, size: 20),
|
||||||
{
|
Text = $"{char.ToUpperInvariant(sampleName.First())}"
|
||||||
Size = new Vector2(8),
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Icon = FontAwesome.Solid.VolumeOff
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
X = 10,
|
|
||||||
Y = -1,
|
|
||||||
Font = OsuFont.Default.With(weight: FontWeight.Bold, size: 20),
|
|
||||||
Text = $"{char.ToUpperInvariant(sampleName.First())}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,6 +291,26 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateAutoBankTernaryButtonTooltip()
|
||||||
|
{
|
||||||
|
bool enabled = SelectionHandler.AutoSelectionBankEnabled.Value;
|
||||||
|
|
||||||
|
var autoBankButton = SampleBankTernaryStates.Single(t => t.Bindable == SelectionHandler.SelectionBankStates[EditorSelectionHandler.HIT_BANK_AUTO]);
|
||||||
|
autoBankButton.Enabled.Value = enabled;
|
||||||
|
autoBankButton.Tooltip = !enabled ? "Auto normal bank can only be used during hit object placement" : string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAdditionBankTernaryButtonTooltips()
|
||||||
|
{
|
||||||
|
bool enabled = SelectionHandler.SelectionAdditionBanksEnabled.Value;
|
||||||
|
|
||||||
|
foreach (var ternaryButton in SampleAdditionBankTernaryStates)
|
||||||
|
{
|
||||||
|
ternaryButton.Enabled.Value = enabled;
|
||||||
|
ternaryButton.Tooltip = !enabled ? "Add an addition sample first to be able to set a bank" : string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#region Placement
|
#region Placement
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -295,7 +322,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
ensurePlacementCreated();
|
ensurePlacementCreated();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePlacementPosition()
|
private void updatePlacementTimeAndPosition()
|
||||||
{
|
{
|
||||||
var snapResult = Composer.FindSnappedPositionAndTime(InputManager.CurrentState.Mouse.Position, CurrentPlacement.SnapType);
|
var snapResult = Composer.FindSnappedPositionAndTime(InputManager.CurrentState.Mouse.Position, CurrentPlacement.SnapType);
|
||||||
|
|
||||||
@ -329,8 +356,18 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
if (Composer.CursorInPlacementArea)
|
if (Composer.CursorInPlacementArea)
|
||||||
ensurePlacementCreated();
|
ensurePlacementCreated();
|
||||||
|
|
||||||
|
// updates the placement with the latest editor clock time.
|
||||||
if (CurrentPlacement != null)
|
if (CurrentPlacement != null)
|
||||||
updatePlacementPosition();
|
updatePlacementTimeAndPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
|
{
|
||||||
|
// updates the placement with the latest mouse position.
|
||||||
|
if (CurrentPlacement != null)
|
||||||
|
updatePlacementTimeAndPosition();
|
||||||
|
|
||||||
|
return base.OnMouseMove(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected sealed override SelectionBlueprint<HitObject> CreateBlueprintFor(HitObject item)
|
protected sealed override SelectionBlueprint<HitObject> CreateBlueprintFor(HitObject item)
|
||||||
@ -367,7 +404,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
placementBlueprintContainer.Child = CurrentPlacement = blueprint;
|
placementBlueprintContainer.Child = CurrentPlacement = blueprint;
|
||||||
|
|
||||||
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
|
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
|
||||||
updatePlacementPosition();
|
updatePlacementTimeAndPosition();
|
||||||
|
|
||||||
updatePlacementSamples();
|
updatePlacementSamples();
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ using System.Collections.Specialized;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
@ -136,7 +135,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
base.ApplySelectionOrder(blueprints)
|
base.ApplySelectionOrder(blueprints)
|
||||||
.OrderBy(b => Math.Min(Math.Abs(EditorClock.CurrentTime - b.Item.GetEndTime()), Math.Abs(EditorClock.CurrentTime - b.Item.StartTime)));
|
.OrderBy(b => Math.Min(Math.Abs(EditorClock.CurrentTime - b.Item.GetEndTime()), Math.Abs(EditorClock.CurrentTime - b.Item.StartTime)));
|
||||||
|
|
||||||
protected override Container<SelectionBlueprint<HitObject>> CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both };
|
protected override SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new EditorSelectionHandler();
|
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new EditorSelectionHandler();
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -10,7 +11,6 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -37,7 +37,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
// bring in updates from selection changes
|
// bring in updates from selection changes
|
||||||
EditorBeatmap.HitObjectUpdated += _ => Scheduler.AddOnce(UpdateTernaryStates);
|
EditorBeatmap.HitObjectUpdated += _ => Scheduler.AddOnce(UpdateTernaryStates);
|
||||||
|
|
||||||
SelectedItems.CollectionChanged += (_, _) => Scheduler.AddOnce(UpdateTernaryStates);
|
SelectedItems.CollectionChanged += onSelectedItemsChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DeleteItems(IEnumerable<HitObject> items) => EditorBeatmap.RemoveRange(items);
|
protected override void DeleteItems(IEnumerable<HitObject> items) => EditorBeatmap.RemoveRange(items);
|
||||||
@ -59,6 +59,21 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly Dictionary<string, Bindable<TernaryState>> SelectionBankStates = new Dictionary<string, Bindable<TernaryState>>();
|
public readonly Dictionary<string, Bindable<TernaryState>> SelectionBankStates = new Dictionary<string, Bindable<TernaryState>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The state of each sample addition bank type for all selected hitobjects.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Dictionary<string, Bindable<TernaryState>> SelectionAdditionBankStates = new Dictionary<string, Bindable<TernaryState>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether there is no selection and the auto <see cref="SelectionBankStates"/> can be used.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Bindable<bool> AutoSelectionBankEnabled = new Bindable<bool>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the selection contains any addition samples and the <see cref="SelectionAdditionBankStates"/> can be used.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Bindable<bool> SelectionAdditionBanksEnabled = new Bindable<bool>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions)
|
/// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -91,7 +106,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
// Never remove a sample bank.
|
// Never remove a sample bank.
|
||||||
// These are basically radio buttons, not toggles.
|
// These are basically radio buttons, not toggles.
|
||||||
if (SelectedItems.All(h => h.Samples.All(s => s.Bank == bankName)))
|
if (SelectedItems.All(h => h.Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName)))
|
||||||
bindable.Value = TernaryState.True;
|
bindable.Value = TernaryState.True;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,8 +143,78 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
SelectionBankStates[bankName] = bindable;
|
SelectionBankStates[bankName] = bindable;
|
||||||
}
|
}
|
||||||
|
|
||||||
// start with normal selected.
|
foreach (string bankName in HitSampleInfo.AllBanks.Prepend(HIT_BANK_AUTO))
|
||||||
SelectionBankStates[SampleControlPoint.DEFAULT_BANK].Value = TernaryState.True;
|
{
|
||||||
|
var bindable = new Bindable<TernaryState>
|
||||||
|
{
|
||||||
|
Description = bankName.Titleize()
|
||||||
|
};
|
||||||
|
|
||||||
|
bindable.ValueChanged += state =>
|
||||||
|
{
|
||||||
|
switch (state.NewValue)
|
||||||
|
{
|
||||||
|
case TernaryState.False:
|
||||||
|
if (SelectedItems.Count == 0)
|
||||||
|
{
|
||||||
|
// Ensure that if this is the last selected bank, it should remain selected.
|
||||||
|
if (SelectionAdditionBankStates.Values.All(b => b.Value == TernaryState.False))
|
||||||
|
bindable.Value = TernaryState.True;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Completely empty selections should be allowed in the case that none of the selected objects have any addition samples.
|
||||||
|
// This is also required to stop a bindable feedback loop when a HitObject has zero addition samples (and LINQ `All` below becomes true).
|
||||||
|
if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.All(o => o.Name == HitSampleInfo.HIT_NORMAL)))
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Never remove a sample bank.
|
||||||
|
// These are basically radio buttons, not toggles.
|
||||||
|
if (bankName == HIT_BANK_AUTO)
|
||||||
|
{
|
||||||
|
if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.EditorAutoBank)))
|
||||||
|
bindable.Value = TernaryState.True;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName && !s.EditorAutoBank)))
|
||||||
|
bindable.Value = TernaryState.True;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TernaryState.True:
|
||||||
|
if (SelectedItems.Count == 0)
|
||||||
|
{
|
||||||
|
// Ensure the user can't stack multiple bank selections when there's no hitobject selection.
|
||||||
|
// Note that in normal scenarios this is sorted out by the feedback from applying the bank to the selected objects.
|
||||||
|
foreach (var other in SelectionAdditionBankStates.Values)
|
||||||
|
{
|
||||||
|
if (other != bindable)
|
||||||
|
other.Value = TernaryState.False;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If none of the selected objects have any addition samples, we should not apply the addition bank.
|
||||||
|
if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.All(o => o.Name == HitSampleInfo.HIT_NORMAL)))
|
||||||
|
{
|
||||||
|
bindable.Value = TernaryState.False;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetSampleAdditionBank(bankName);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SelectionAdditionBankStates[bankName] = bindable;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetTernaryStates();
|
||||||
|
|
||||||
foreach (string sampleName in HitSampleInfo.AllAdditions)
|
foreach (string sampleName in HitSampleInfo.AllAdditions)
|
||||||
{
|
{
|
||||||
@ -171,12 +256,21 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void resetTernaryStates()
|
||||||
|
{
|
||||||
|
AutoSelectionBankEnabled.Value = true;
|
||||||
|
SelectionAdditionBanksEnabled.Value = true;
|
||||||
|
SelectionBankStates[HIT_BANK_AUTO].Value = TernaryState.True;
|
||||||
|
SelectionAdditionBankStates[HIT_BANK_AUTO].Value = TernaryState.True;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when context menu ternary states may need to be recalculated (selection changed or hitobject updated).
|
/// Called when context menu ternary states may need to be recalculated (selection changed or hitobject updated).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void UpdateTernaryStates()
|
protected virtual void UpdateTernaryStates()
|
||||||
{
|
{
|
||||||
SelectionNewComboState.Value = GetStateFromSelection(SelectedItems.OfType<IHasComboInformation>(), h => h.NewCombo);
|
SelectionNewComboState.Value = GetStateFromSelection(SelectedItems.OfType<IHasComboInformation>(), h => h.NewCombo);
|
||||||
|
AutoSelectionBankEnabled.Value = SelectedItems.Count == 0;
|
||||||
|
|
||||||
var samplesInSelection = SelectedItems.SelectMany(enumerateAllSamples).ToArray();
|
var samplesInSelection = SelectedItems.SelectMany(enumerateAllSamples).ToArray();
|
||||||
|
|
||||||
@ -187,18 +281,34 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
foreach ((string bankName, var bindable) in SelectionBankStates)
|
foreach ((string bankName, var bindable) in SelectionBankStates)
|
||||||
{
|
{
|
||||||
bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s), h => h.Bank == bankName);
|
bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name == HitSampleInfo.HIT_NORMAL), h => h.Bank == bankName);
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerable<IList<HitSampleInfo>> enumerateAllSamples(HitObject hitObject)
|
SelectionAdditionBanksEnabled.Value = samplesInSelection.SelectMany(s => s).Any(o => o.Name != HitSampleInfo.HIT_NORMAL);
|
||||||
{
|
|
||||||
yield return hitObject.Samples;
|
|
||||||
|
|
||||||
if (hitObject is IHasRepeats withRepeats)
|
foreach ((string bankName, var bindable) in SelectionAdditionBankStates)
|
||||||
{
|
{
|
||||||
foreach (var node in withRepeats.NodeSamples)
|
bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name != HitSampleInfo.HIT_NORMAL), h => (bankName != HIT_BANK_AUTO && h.Bank == bankName && !h.EditorAutoBank) || (bankName == HIT_BANK_AUTO && h.EditorAutoBank));
|
||||||
yield return node;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onSelectedItemsChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
// Reset the ternary states when the selection is cleared.
|
||||||
|
if (e.OldStartingIndex >= 0 && e.NewStartingIndex < 0)
|
||||||
|
Scheduler.AddOnce(resetTernaryStates);
|
||||||
|
else
|
||||||
|
Scheduler.AddOnce(UpdateTernaryStates);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<IList<HitSampleInfo>> enumerateAllSamples(HitObject hitObject)
|
||||||
|
{
|
||||||
|
yield return hitObject.Samples;
|
||||||
|
|
||||||
|
if (hitObject is IHasRepeats withRepeats)
|
||||||
|
{
|
||||||
|
foreach (var node in withRepeats.NodeSamples)
|
||||||
|
yield return node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,12 +324,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
bool hasRelevantBank(HitObject hitObject)
|
bool hasRelevantBank(HitObject hitObject)
|
||||||
{
|
{
|
||||||
bool result = hitObject.Samples.All(s => s.Bank == bankName);
|
bool result = hitObject.Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName);
|
||||||
|
|
||||||
if (hitObject is IHasRepeats hasRepeats)
|
if (hitObject is IHasRepeats hasRepeats)
|
||||||
{
|
{
|
||||||
foreach (var node in hasRepeats.NodeSamples)
|
foreach (var node in hasRepeats.NodeSamples)
|
||||||
result &= node.All(s => s.Bank == bankName);
|
result &= node.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -233,12 +343,47 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
if (hasRelevantBank(h))
|
if (hasRelevantBank(h))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
h.Samples = h.Samples.Select(s => s.With(newBank: bankName)).ToList();
|
h.Samples = h.Samples.Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList();
|
||||||
|
|
||||||
if (h is IHasRepeats hasRepeats)
|
if (h is IHasRepeats hasRepeats)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i)
|
for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i)
|
||||||
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.With(newBank: bankName)).ToList();
|
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorBeatmap.Update(h);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the sample addition bank for all selected <see cref="HitObject"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bankName">The name of the sample bank.</param>
|
||||||
|
public void SetSampleAdditionBank(string bankName)
|
||||||
|
{
|
||||||
|
bool hasRelevantBank(HitObject hitObject) =>
|
||||||
|
bankName == HIT_BANK_AUTO
|
||||||
|
? enumerateAllSamples(hitObject).SelectMany(o => o).Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.EditorAutoBank)
|
||||||
|
: enumerateAllSamples(hitObject).SelectMany(o => o).Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName && !s.EditorAutoBank);
|
||||||
|
|
||||||
|
if (SelectedItems.All(hasRelevantBank))
|
||||||
|
return;
|
||||||
|
|
||||||
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
|
{
|
||||||
|
if (hasRelevantBank(h))
|
||||||
|
return;
|
||||||
|
|
||||||
|
string normalBank = h.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank ?? HitSampleInfo.BANK_SOFT;
|
||||||
|
h.Samples = h.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? bankName == HIT_BANK_AUTO ? s.With(newBank: normalBank, newEditorAutoBank: true) : s.With(newBank: bankName, newEditorAutoBank: false) : s).ToList();
|
||||||
|
|
||||||
|
if (h is IHasRepeats hasRepeats)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i)
|
||||||
|
{
|
||||||
|
normalBank = hasRepeats.NodeSamples[i].FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank ?? HitSampleInfo.BANK_SOFT;
|
||||||
|
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? bankName == HIT_BANK_AUTO ? s.With(newBank: normalBank, newEditorAutoBank: true) : s.With(newBank: bankName, newEditorAutoBank: false) : s).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EditorBeatmap.Update(h);
|
EditorBeatmap.Update(h);
|
||||||
@ -282,9 +427,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
var hitSample = h.CreateHitSampleInfo(sampleName);
|
var hitSample = h.CreateHitSampleInfo(sampleName);
|
||||||
|
|
||||||
string? existingAdditionBank = node.FirstOrDefault(s => s.Name != HitSampleInfo.HIT_NORMAL)?.Bank;
|
HitSampleInfo? existingAddition = node.FirstOrDefault(s => s.Name != HitSampleInfo.HIT_NORMAL);
|
||||||
if (existingAdditionBank != null)
|
if (existingAddition != null)
|
||||||
hitSample = hitSample.With(newBank: existingAdditionBank);
|
hitSample = hitSample.With(newBank: existingAddition.Bank, newEditorAutoBank: existingAddition.EditorAutoBank);
|
||||||
|
|
||||||
node.Add(hitSample);
|
node.Add(hitSample);
|
||||||
}
|
}
|
||||||
@ -415,6 +560,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
State = { BindTarget = drum },
|
State = { BindTarget = drum },
|
||||||
Hotkey = new Hotkey(new KeyCombination(InputKey.Shift, InputKey.R))
|
Hotkey = new Hotkey(new KeyCombination(InputKey.Shift, InputKey.R))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
yield return new OsuMenuItem("Addition bank")
|
||||||
|
{
|
||||||
|
Items = SelectionAdditionBankStates.Select(kvp =>
|
||||||
|
new TernaryStateToggleMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
@ -14,7 +13,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A container for <see cref="SelectionBlueprint{HitObject}"/> ordered by their <see cref="HitObject"/> start times.
|
/// A container for <see cref="SelectionBlueprint{HitObject}"/> ordered by their <see cref="HitObject"/> start times.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class HitObjectOrderedSelectionContainer : Container<SelectionBlueprint<HitObject>>
|
public sealed partial class HitObjectOrderedSelectionContainer : BlueprintContainer<HitObject>.SelectionBlueprintContainer
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||||
@ -28,16 +27,18 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
public override void Add(SelectionBlueprint<HitObject> drawable)
|
public override void Add(SelectionBlueprint<HitObject> drawable)
|
||||||
{
|
{
|
||||||
SortInternal();
|
Sort();
|
||||||
base.Add(drawable);
|
base.Add(drawable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Remove(SelectionBlueprint<HitObject> drawable, bool disposeImmediately)
|
public override bool Remove(SelectionBlueprint<HitObject> drawable, bool disposeImmediately)
|
||||||
{
|
{
|
||||||
SortInternal();
|
Sort();
|
||||||
return base.Remove(drawable, disposeImmediately);
|
return base.Remove(drawable, disposeImmediately);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void Sort() => SortInternal();
|
||||||
|
|
||||||
protected override int Compare(Drawable x, Drawable y)
|
protected override int Compare(Drawable x, Drawable y)
|
||||||
{
|
{
|
||||||
var xObj = ((SelectionBlueprint<HitObject>)x).Item;
|
var xObj = ((SelectionBlueprint<HitObject>)x).Item;
|
||||||
|
@ -107,7 +107,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
public static string? GetAdditionBankValue(IEnumerable<HitSampleInfo> samples)
|
public static string? GetAdditionBankValue(IEnumerable<HitSampleInfo> samples)
|
||||||
{
|
{
|
||||||
return samples.FirstOrDefault(o => o.Name != HitSampleInfo.HIT_NORMAL)?.Bank;
|
var firstAddition = samples.FirstOrDefault(o => o.Name != HitSampleInfo.HIT_NORMAL);
|
||||||
|
if (firstAddition == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return firstAddition.EditorAutoBank ? EditorSelectionHandler.HIT_BANK_AUTO : firstAddition.Bank;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int GetVolumeValue(ICollection<HitSampleInfo> samples)
|
public static int GetVolumeValue(ICollection<HitSampleInfo> samples)
|
||||||
@ -320,7 +324,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < relevantSamples.Count; i++)
|
for (int i = 0; i < relevantSamples.Count; i++)
|
||||||
{
|
{
|
||||||
if (relevantSamples[i].Name != HitSampleInfo.HIT_NORMAL) continue;
|
if (relevantSamples[i].Name != HitSampleInfo.HIT_NORMAL && !relevantSamples[i].EditorAutoBank) continue;
|
||||||
|
|
||||||
relevantSamples[i] = relevantSamples[i].With(newBank: newBank);
|
relevantSamples[i] = relevantSamples[i].With(newBank: newBank);
|
||||||
}
|
}
|
||||||
@ -331,11 +335,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
{
|
{
|
||||||
updateAllRelevantSamples((_, relevantSamples) =>
|
updateAllRelevantSamples((_, relevantSamples) =>
|
||||||
{
|
{
|
||||||
|
string normalBank = relevantSamples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank ?? HitSampleInfo.BANK_SOFT;
|
||||||
|
|
||||||
for (int i = 0; i < relevantSamples.Count; i++)
|
for (int i = 0; i < relevantSamples.Count; i++)
|
||||||
{
|
{
|
||||||
if (relevantSamples[i].Name == HitSampleInfo.HIT_NORMAL) continue;
|
if (relevantSamples[i].Name == HitSampleInfo.HIT_NORMAL)
|
||||||
|
continue;
|
||||||
|
|
||||||
relevantSamples[i] = relevantSamples[i].With(newBank: newBank);
|
// Addition samples with bank set to auto should inherit the bank of the normal sample
|
||||||
|
if (newBank == EditorSelectionHandler.HIT_BANK_AUTO)
|
||||||
|
{
|
||||||
|
relevantSamples[i] = relevantSamples[i].With(newBank: normalBank, newEditorAutoBank: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
relevantSamples[i] = relevantSamples[i].With(newBank: newBank, newEditorAutoBank: false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -383,7 +396,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
selectionSampleStates[sampleName] = bindable;
|
selectionSampleStates[sampleName] = bindable;
|
||||||
}
|
}
|
||||||
|
|
||||||
banks.AddRange(HitSampleInfo.AllBanks);
|
banks.AddRange(HitSampleInfo.AllBanks.Prepend(EditorSelectionHandler.HIT_BANK_AUTO));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTernaryStates()
|
private void updateTernaryStates()
|
||||||
@ -438,24 +451,31 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
{
|
{
|
||||||
if (e.ControlPressed || e.AltPressed || e.SuperPressed || !checkRightToggleFromKey(e.Key, out int rightIndex))
|
if (e.ControlPressed || e.SuperPressed || !checkRightToggleFromKey(e.Key, out int rightIndex))
|
||||||
return base.OnKeyDown(e);
|
return base.OnKeyDown(e);
|
||||||
|
|
||||||
if (e.ShiftPressed)
|
if (e.ShiftPressed || e.AltPressed)
|
||||||
{
|
{
|
||||||
string? newBank = banks.ElementAtOrDefault(rightIndex);
|
string? newBank = banks.ElementAtOrDefault(rightIndex);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(newBank))
|
if (string.IsNullOrEmpty(newBank))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
setBank(newBank);
|
if (e.ShiftPressed && newBank != EditorSelectionHandler.HIT_BANK_AUTO)
|
||||||
updatePrimaryBankState();
|
{
|
||||||
setAdditionBank(newBank);
|
setBank(newBank);
|
||||||
updateAdditionBankState();
|
updatePrimaryBankState();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.AltPressed)
|
||||||
|
{
|
||||||
|
setAdditionBank(newBank);
|
||||||
|
updateAdditionBankState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var item = togglesCollection.ElementAtOrDefault(rightIndex);
|
var item = togglesCollection.ElementAtOrDefault(rightIndex - 1);
|
||||||
|
|
||||||
if (item is not DrawableTernaryButton button) return base.OnKeyDown(e);
|
if (item is not DrawableTernaryButton button) return base.OnKeyDown(e);
|
||||||
|
|
||||||
@ -469,18 +489,22 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
{
|
{
|
||||||
switch (key)
|
switch (key)
|
||||||
{
|
{
|
||||||
case Key.W:
|
case Key.Q:
|
||||||
index = 0;
|
index = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Key.E:
|
case Key.W:
|
||||||
index = 1;
|
index = 1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Key.R:
|
case Key.E:
|
||||||
index = 2;
|
index = 2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Key.R:
|
||||||
|
index = 3;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
index = -1;
|
index = -1;
|
||||||
break;
|
break;
|
||||||
|
@ -91,7 +91,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Container<SelectionBlueprint<HitObject>> CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both };
|
protected override SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
protected override bool OnDragStart(DragStartEvent e)
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
{
|
{
|
||||||
@ -287,14 +287,27 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected partial class TimelineSelectionBlueprintContainer : Container<SelectionBlueprint<HitObject>>
|
protected partial class TimelineSelectionBlueprintContainer : SelectionBlueprintContainer
|
||||||
{
|
{
|
||||||
protected override Container<SelectionBlueprint<HitObject>> Content { get; }
|
protected override HitObjectOrderedSelectionContainer Content { get; }
|
||||||
|
|
||||||
public TimelineSelectionBlueprintContainer()
|
public TimelineSelectionBlueprintContainer()
|
||||||
{
|
{
|
||||||
AddInternal(new TimelinePart<SelectionBlueprint<HitObject>>(Content = new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both });
|
AddInternal(new TimelinePart<SelectionBlueprint<HitObject>>(Content = new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void ChangeChildDepth(SelectionBlueprint<HitObject> child, float newDepth)
|
||||||
|
{
|
||||||
|
// timeline blueprint container also contains a blueprint for current placement, if present
|
||||||
|
// (see `placementChanged()` callback above).
|
||||||
|
// because the current placement hitobject is generally going to be mutated during the placement,
|
||||||
|
// it is possible for `Content`'s children to become unsorted when the user moves the placement around,
|
||||||
|
// which can culminate in a critical failure when attempting to binary-search children here
|
||||||
|
// using `HitObjectOrderedSelectionContainer`'s custom comparer.
|
||||||
|
// thus, always force a re-sort of objects before attempting to change child depth to avoid this scenario.
|
||||||
|
Content.Sort();
|
||||||
|
base.ChangeChildDepth(child, newDepth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -215,6 +215,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
private Bindable<bool> editorLimitedDistanceSnap;
|
private Bindable<bool> editorLimitedDistanceSnap;
|
||||||
private Bindable<bool> editorTimelineShowTimingChanges;
|
private Bindable<bool> editorTimelineShowTimingChanges;
|
||||||
private Bindable<bool> editorTimelineShowTicks;
|
private Bindable<bool> editorTimelineShowTicks;
|
||||||
|
private Bindable<bool> editorContractSidebars;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This controls the opacity of components like the timelines, sidebars, etc.
|
/// This controls the opacity of components like the timelines, sidebars, etc.
|
||||||
@ -323,6 +324,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
editorLimitedDistanceSnap = config.GetBindable<bool>(OsuSetting.EditorLimitedDistanceSnap);
|
editorLimitedDistanceSnap = config.GetBindable<bool>(OsuSetting.EditorLimitedDistanceSnap);
|
||||||
editorTimelineShowTimingChanges = config.GetBindable<bool>(OsuSetting.EditorTimelineShowTimingChanges);
|
editorTimelineShowTimingChanges = config.GetBindable<bool>(OsuSetting.EditorTimelineShowTimingChanges);
|
||||||
editorTimelineShowTicks = config.GetBindable<bool>(OsuSetting.EditorTimelineShowTicks);
|
editorTimelineShowTicks = config.GetBindable<bool>(OsuSetting.EditorTimelineShowTicks);
|
||||||
|
editorContractSidebars = config.GetBindable<bool>(OsuSetting.EditorContractSidebars);
|
||||||
|
|
||||||
AddInternal(new OsuContextMenuContainer
|
AddInternal(new OsuContextMenuContainer
|
||||||
{
|
{
|
||||||
@ -402,7 +404,11 @@ namespace osu.Game.Screens.Edit
|
|||||||
new ToggleMenuItem(EditorStrings.LimitedDistanceSnap)
|
new ToggleMenuItem(EditorStrings.LimitedDistanceSnap)
|
||||||
{
|
{
|
||||||
State = { BindTarget = editorLimitedDistanceSnap },
|
State = { BindTarget = editorLimitedDistanceSnap },
|
||||||
}
|
},
|
||||||
|
new ToggleMenuItem(EditorStrings.ContractSidebars)
|
||||||
|
{
|
||||||
|
State = { BindTarget = editorContractSidebars }
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new MenuItem(EditorStrings.Timing)
|
new MenuItem(EditorStrings.Timing)
|
||||||
@ -1098,8 +1104,12 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
private void seekControlPoint(int direction)
|
private void seekControlPoint(int direction)
|
||||||
{
|
{
|
||||||
var found = direction < 1
|
// In the case of a backwards seek while playing, it can be hard to jump before a timing point.
|
||||||
? editorBeatmap.ControlPointInfo.AllControlPoints.LastOrDefault(p => p.Time < clock.CurrentTime)
|
// Adding some lenience here makes it more user-friendly.
|
||||||
|
double seekLenience = clock.IsRunning ? 1000 * ((IAdjustableClock)clock).Rate : 0;
|
||||||
|
|
||||||
|
ControlPoint found = direction < 1
|
||||||
|
? editorBeatmap.ControlPointInfo.AllControlPoints.LastOrDefault(p => p.Time < clock.CurrentTime - seekLenience)
|
||||||
: editorBeatmap.ControlPointInfo.AllControlPoints.FirstOrDefault(p => p.Time > clock.CurrentTime);
|
: editorBeatmap.ControlPointInfo.AllControlPoints.FirstOrDefault(p => p.Time > clock.CurrentTime);
|
||||||
|
|
||||||
if (found != null)
|
if (found != null)
|
||||||
|
@ -13,6 +13,7 @@ using osu.Framework.Layout;
|
|||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Localisation.SkinComponents;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts;
|
using osu.Game.Screens.Play.HUD.ArgonHealthDisplayParts;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -33,7 +34,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
Precision = 1
|
Precision = 1
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Use relative size")]
|
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.UseRelativeSize))]
|
||||||
public BindableBool UseRelativeSize { get; } = new BindableBool(true);
|
public BindableBool UseRelativeSize { get; } = new BindableBool(true);
|
||||||
|
|
||||||
private ArgonHealthDisplayBar mainBar = null!;
|
private ArgonHealthDisplayBar mainBar = null!;
|
||||||
|
@ -30,6 +30,9 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
[SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))]
|
[SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))]
|
||||||
public Bindable<bool> ShowTime { get; } = new BindableBool(true);
|
public Bindable<bool> ShowTime { get; } = new BindableBool(true);
|
||||||
|
|
||||||
|
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.UseRelativeSize))]
|
||||||
|
public BindableBool UseRelativeSize { get; } = new BindableBool(true);
|
||||||
|
|
||||||
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))]
|
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))]
|
||||||
public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White);
|
public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White);
|
||||||
|
|
||||||
@ -99,6 +102,11 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true);
|
ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true);
|
||||||
ShowTime.BindValueChanged(_ => info.FadeTo(ShowTime.Value ? 1 : 0, 200, Easing.In), true);
|
ShowTime.BindValueChanged(_ => info.FadeTo(ShowTime.Value ? 1 : 0, 200, Easing.In), true);
|
||||||
AccentColour.BindValueChanged(_ => Colour = AccentColour.Value, true);
|
AccentColour.BindValueChanged(_ => Colour = AccentColour.Value, true);
|
||||||
|
|
||||||
|
// see comment in ArgonHealthDisplay.cs regarding RelativeSizeAxes
|
||||||
|
float previousWidth = Width;
|
||||||
|
UseRelativeSize.BindValueChanged(v => RelativeSizeAxes = v.NewValue ? Axes.X : Axes.None, true);
|
||||||
|
Width = previousWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateObjects(IEnumerable<HitObject> objects)
|
protected override void UpdateObjects(IEnumerable<HitObject> objects)
|
||||||
|
@ -37,6 +37,9 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
[SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))]
|
[SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))]
|
||||||
public Bindable<bool> ShowTime { get; } = new BindableBool(true);
|
public Bindable<bool> ShowTime { get; } = new BindableBool(true);
|
||||||
|
|
||||||
|
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.UseRelativeSize))]
|
||||||
|
public BindableBool UseRelativeSize { get; } = new BindableBool(true);
|
||||||
|
|
||||||
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))]
|
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.Colour), nameof(SkinnableComponentStrings.ColourDescription))]
|
||||||
public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White);
|
public BindableColour4 AccentColour { get; } = new BindableColour4(Colour4.White);
|
||||||
|
|
||||||
@ -83,6 +86,11 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
graph.FillColour = bar.FillColour = colours.BlueLighter;
|
graph.FillColour = bar.FillColour = colours.BlueLighter;
|
||||||
|
|
||||||
|
// see comment in ArgonHealthDisplay.cs regarding RelativeSizeAxes
|
||||||
|
float previousWidth = Width;
|
||||||
|
UseRelativeSize.BindValueChanged(v => RelativeSizeAxes = v.NewValue ? Axes.X : Axes.None, true);
|
||||||
|
Width = previousWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
|
@ -252,7 +252,7 @@ namespace osu.Game.Screens.Play
|
|||||||
PlayfieldSkinLayer.Position = ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft);
|
PlayfieldSkinLayer.Position = ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft);
|
||||||
PlayfieldSkinLayer.Width = (ToLocalSpace(playfieldScreenSpaceDrawQuad.TopRight) - ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft)).Length;
|
PlayfieldSkinLayer.Width = (ToLocalSpace(playfieldScreenSpaceDrawQuad.TopRight) - ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft)).Length;
|
||||||
PlayfieldSkinLayer.Height = (ToLocalSpace(playfieldScreenSpaceDrawQuad.BottomLeft) - ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft)).Length;
|
PlayfieldSkinLayer.Height = (ToLocalSpace(playfieldScreenSpaceDrawQuad.BottomLeft) - ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft)).Length;
|
||||||
PlayfieldSkinLayer.Rotation = drawableRuleset.Playfield.Rotation;
|
PlayfieldSkinLayer.Rotation = drawableRuleset.PlayfieldAdjustmentContainer.Rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
float? lowestTopScreenSpaceLeft = null;
|
float? lowestTopScreenSpaceLeft = null;
|
||||||
|
@ -562,11 +562,8 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
catch (BeatmapInvalidForRulesetException)
|
catch (BeatmapInvalidForRulesetException)
|
||||||
{
|
{
|
||||||
// A playable beatmap may not be creatable with the user's preferred ruleset, so try using the beatmap's default ruleset
|
Logger.Log($"The current beatmap is not playable in {ruleset.RulesetInfo.Name}!", level: LogLevel.Important);
|
||||||
rulesetInfo = Beatmap.Value.BeatmapInfo.Ruleset;
|
return null;
|
||||||
ruleset = rulesetInfo.CreateInstance();
|
|
||||||
|
|
||||||
playable = Beatmap.Value.GetPlayableBeatmap(rulesetInfo, gameplayMods, cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playable.HitObjects.Count == 0)
|
if (playable.HitObjects.Count == 0)
|
||||||
|
@ -322,6 +322,11 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// To handle the beatmap update flow, attempt to track selection changes across delete-insert transactions.
|
||||||
|
// When an update occurs, the previous beatmap set is either soft or hard deleted.
|
||||||
|
// Check if the current selection was potentially deleted by re-querying its validity.
|
||||||
|
bool selectedSetMarkedDeleted = SelectedBeatmapSet != null && fetchFromID(SelectedBeatmapSet.ID)?.DeletePending != false;
|
||||||
|
|
||||||
foreach (var set in setsRequiringRemoval) removeBeatmapSet(set.ID);
|
foreach (var set in setsRequiringRemoval) removeBeatmapSet(set.ID);
|
||||||
|
|
||||||
foreach (var set in setsRequiringUpdate) updateBeatmapSet(set);
|
foreach (var set in setsRequiringUpdate) updateBeatmapSet(set);
|
||||||
@ -331,11 +336,6 @@ namespace osu.Game.Screens.Select
|
|||||||
// If SelectedBeatmapInfo is non-null, the set should also be non-null.
|
// If SelectedBeatmapInfo is non-null, the set should also be non-null.
|
||||||
Debug.Assert(SelectedBeatmapSet != null);
|
Debug.Assert(SelectedBeatmapSet != null);
|
||||||
|
|
||||||
// To handle the beatmap update flow, attempt to track selection changes across delete-insert transactions.
|
|
||||||
// When an update occurs, the previous beatmap set is either soft or hard deleted.
|
|
||||||
// Check if the current selection was potentially deleted by re-querying its validity.
|
|
||||||
bool selectedSetMarkedDeleted = fetchFromID(SelectedBeatmapSet.ID)?.DeletePending != false;
|
|
||||||
|
|
||||||
if (selectedSetMarkedDeleted && setsRequiringUpdate.Any())
|
if (selectedSetMarkedDeleted && setsRequiringUpdate.Any())
|
||||||
{
|
{
|
||||||
// If it is no longer valid, make the bold assumption that an updated version will be available in the modified/inserted indices.
|
// If it is no longer valid, make the bold assumption that an updated version will be available in the modified/inserted indices.
|
||||||
|
@ -154,6 +154,9 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
bool wasPlaying = IsPlaying;
|
bool wasPlaying = IsPlaying;
|
||||||
|
|
||||||
|
if (wasPlaying && Looping)
|
||||||
|
Stop();
|
||||||
|
|
||||||
// Remove all pooled samples (return them to the pool), and dispose the rest.
|
// Remove all pooled samples (return them to the pool), and dispose the rest.
|
||||||
samplesContainer.RemoveAll(s => s.IsInPool, false);
|
samplesContainer.RemoveAll(s => s.IsInPool, false);
|
||||||
samplesContainer.Clear();
|
samplesContainer.Clear();
|
||||||
|
@ -188,7 +188,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
|||||||
|
|
||||||
case GetBeatmapRequest getBeatmapRequest:
|
case GetBeatmapRequest getBeatmapRequest:
|
||||||
{
|
{
|
||||||
getBeatmapRequest.TriggerSuccess(createResponseBeatmaps(getBeatmapRequest.BeatmapInfo.OnlineID).Single());
|
getBeatmapRequest.TriggerSuccess(createResponseBeatmaps(getBeatmapRequest.OnlineID).Single());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,6 +214,22 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
|||||||
getBeatmapSetRequest.TriggerSuccess(OsuTestScene.CreateAPIBeatmapSet(baseBeatmap));
|
getBeatmapSetRequest.TriggerSuccess(OsuTestScene.CreateAPIBeatmapSet(baseBeatmap));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case GetUsersRequest getUsersRequest:
|
||||||
|
{
|
||||||
|
getUsersRequest.TriggerSuccess(new GetUsersResponse
|
||||||
|
{
|
||||||
|
Users = getUsersRequest.UserIds.Select(id => id == TestUserLookupCache.UNRESOLVED_USER_ID
|
||||||
|
? null
|
||||||
|
: new APIUser
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Username = $"User {id}"
|
||||||
|
})
|
||||||
|
.Where(u => u != null).ToList(),
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<APIBeatmap> createResponseBeatmaps(params int[] beatmapIds)
|
List<APIBeatmap> createResponseBeatmaps(params int[] beatmapIds)
|
||||||
|
@ -3,9 +3,11 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
@ -24,6 +26,10 @@ namespace osu.Game.Tests.Visual
|
|||||||
protected PlacementBlueprintTestScene()
|
protected PlacementBlueprintTestScene()
|
||||||
{
|
{
|
||||||
base.Content.Add(HitObjectContainer = CreateHitObjectContainer().With(c => c.Clock = new FramedClock(new StopwatchClock())));
|
base.Content.Add(HitObjectContainer = CreateHitObjectContainer().With(c => c.Clock = new FramedClock(new StopwatchClock())));
|
||||||
|
base.Content.Add(new MouseMovementInterceptor
|
||||||
|
{
|
||||||
|
MouseMoved = updatePlacementTimeAndPosition,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
@ -83,10 +89,11 @@ namespace osu.Game.Tests.Visual
|
|||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
updatePlacementTimeAndPosition();
|
||||||
CurrentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(CurrentBlueprint));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updatePlacementTimeAndPosition() => CurrentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(CurrentBlueprint));
|
||||||
|
|
||||||
protected virtual SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint) =>
|
protected virtual SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint) =>
|
||||||
new SnapResult(InputManager.CurrentState.Mouse.Position, null);
|
new SnapResult(InputManager.CurrentState.Mouse.Position, null);
|
||||||
|
|
||||||
@ -107,5 +114,22 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
protected abstract DrawableHitObject CreateHitObject(HitObject hitObject);
|
protected abstract DrawableHitObject CreateHitObject(HitObject hitObject);
|
||||||
protected abstract HitObjectPlacementBlueprint CreateBlueprint();
|
protected abstract HitObjectPlacementBlueprint CreateBlueprint();
|
||||||
|
|
||||||
|
private partial class MouseMovementInterceptor : Drawable
|
||||||
|
{
|
||||||
|
public Action MouseMoved;
|
||||||
|
|
||||||
|
public MouseMovementInterceptor()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Depth = float.MinValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
|
{
|
||||||
|
MouseMoved?.Invoke();
|
||||||
|
return base.OnMouseMove(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="11.5.0" />
|
<PackageReference Include="Realm" Version="11.5.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2024.1009.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2024.1025.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.1003.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.1003.0" />
|
||||||
<PackageReference Include="Sentry" Version="4.12.1" />
|
<PackageReference Include="Sentry" Version="4.12.1" />
|
||||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||||
|
@ -17,6 +17,6 @@
|
|||||||
<MtouchInterpreter>-all</MtouchInterpreter>
|
<MtouchInterpreter>-all</MtouchInterpreter>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.1009.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.1025.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -69,7 +69,7 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertConditionalTernaryExpressionToSwitchExpression/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertConditionalTernaryExpressionToSwitchExpression/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertConstructorToMemberInitializers/@EntryIndexedValue">HINT</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertConstructorToMemberInitializers/@EntryIndexedValue">HINT</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfDoToWhile/@EntryIndexedValue">WARNING</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfDoToWhile/@EntryIndexedValue">WARNING</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToConditionalTernaryExpression/@EntryIndexedValue">WARNING</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToConditionalTernaryExpression/@EntryIndexedValue">HINT</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToNullCoalescingAssignment/@EntryIndexedValue">WARNING</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToNullCoalescingAssignment/@EntryIndexedValue">WARNING</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToNullCoalescingExpression/@EntryIndexedValue">WARNING</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToNullCoalescingExpression/@EntryIndexedValue">WARNING</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToSwitchExpression/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToSwitchExpression/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||||
|
Loading…
Reference in New Issue
Block a user