1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-27 10:23:03 +08:00

Merge branch 'master' into master

This commit is contained in:
Fabian Bech Persson 2024-08-30 11:06:54 +02:00 committed by GitHub
commit 363fc0d5da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 399 additions and 74 deletions

View File

@ -254,5 +254,7 @@ namespace osu.Game.Rulesets.Catch
return adjustedDifficulty; return adjustedDifficulty;
} }
public override bool EditorShowScrollSpeed => false;
} }
} }

View File

@ -359,5 +359,7 @@ namespace osu.Game.Rulesets.Osu
return adjustedDifficulty; return adjustedDifficulty;
} }
public override bool EditorShowScrollSpeed => false;
} }
} }

View File

@ -537,7 +537,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
[TestCaseSource(nameof(correct_date_query_examples))] [TestCaseSource(nameof(correct_date_query_examples))]
public void TestValidDateQueries(string dateQuery) public void TestValidDateQueries(string dateQuery)
{ {
string query = $"played<{dateQuery} time"; string query = $"lastplayed<{dateQuery} time";
var filterCriteria = new FilterCriteria(); var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query); FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter); Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
@ -571,7 +571,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
[Test] [Test]
public void TestGreaterDateQuery() public void TestGreaterDateQuery()
{ {
const string query = "played>50"; const string query = "lastplayed>50";
var filterCriteria = new FilterCriteria(); var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query); FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null); Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null);
@ -584,7 +584,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
[Test] [Test]
public void TestLowerDateQuery() public void TestLowerDateQuery()
{ {
const string query = "played<50"; const string query = "lastplayed<50";
var filterCriteria = new FilterCriteria(); var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query); FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.That(filterCriteria.LastPlayed.Max, Is.Null); Assert.That(filterCriteria.LastPlayed.Max, Is.Null);
@ -597,7 +597,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
[Test] [Test]
public void TestBothSidesDateQuery() public void TestBothSidesDateQuery()
{ {
const string query = "played>3M played<1y6M"; const string query = "lastplayed>3M lastplayed<1y6M";
var filterCriteria = new FilterCriteria(); var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query); FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.That(filterCriteria.LastPlayed.Min, Is.Not.Null); Assert.That(filterCriteria.LastPlayed.Min, Is.Not.Null);
@ -611,7 +611,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
[Test] [Test]
public void TestEqualDateQuery() public void TestEqualDateQuery()
{ {
const string query = "played=50"; const string query = "lastplayed=50";
var filterCriteria = new FilterCriteria(); var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query); FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter); Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter);
@ -620,11 +620,34 @@ namespace osu.Game.Tests.NonVisual.Filtering
[Test] [Test]
public void TestOutOfRangeDateQuery() public void TestOutOfRangeDateQuery()
{ {
const string query = "played<10000y"; const string query = "lastplayed<10000y";
var filterCriteria = new FilterCriteria(); var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query); FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter); Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
Assert.AreEqual(DateTimeOffset.MinValue.AddMilliseconds(1), filterCriteria.LastPlayed.Min); Assert.AreEqual(DateTimeOffset.MinValue.AddMilliseconds(1), filterCriteria.LastPlayed.Min);
} }
private static readonly object[] played_query_tests =
{
new object[] { "0", DateTimeOffset.MinValue, true },
new object[] { "0", DateTimeOffset.Now, false },
new object[] { "false", DateTimeOffset.MinValue, true },
new object[] { "false", DateTimeOffset.Now, false },
new object[] { "1", DateTimeOffset.MinValue, false },
new object[] { "1", DateTimeOffset.Now, true },
new object[] { "true", DateTimeOffset.MinValue, false },
new object[] { "true", DateTimeOffset.Now, true },
};
[Test]
[TestCaseSource(nameof(played_query_tests))]
public void TestPlayedQuery(string query, DateTimeOffset reference, bool matched)
{
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, $"played={query}");
Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
Assert.AreEqual(matched, filterCriteria.LastPlayed.IsInRange(reference));
}
} }
} }

View File

@ -7,6 +7,7 @@ using Humanizer;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
@ -307,6 +308,46 @@ namespace osu.Game.Tests.Visual.Editing
hitObjectNodeHasSampleVolume(0, 1, 10); hitObjectNodeHasSampleVolume(0, 1, 10);
} }
[Test]
public void TestSamplePointSeek()
{
AddStep("add slider", () =>
{
EditorBeatmap.Clear();
EditorBeatmap.Add(new Slider
{
Position = new Vector2(256, 256),
StartTime = 0,
Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }),
Samples =
{
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
},
NodeSamples =
{
new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) },
new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) },
},
RepeatCount = 1
});
});
seekSamplePiece(-1);
editorTimeIs(0);
samplePopoverIsOpen();
seekSamplePiece(-1);
editorTimeIs(0);
samplePopoverIsOpen();
seekSamplePiece(1);
editorTimeIs(406);
seekSamplePiece(1);
editorTimeIs(813);
seekSamplePiece(1);
editorTimeIs(1627);
seekSamplePiece(1);
editorTimeIs(1627);
}
[Test] [Test]
public void TestHotkeysMultipleSelectionWithSameSampleBank() public void TestHotkeysMultipleSelectionWithSameSampleBank()
{ {
@ -548,6 +589,63 @@ namespace osu.Game.Tests.Visual.Editing
hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
} }
[Test]
public void TestHotkeysUnifySliderSamplesAndNodeSamples()
{
AddStep("add slider", () =>
{
EditorBeatmap.Clear();
EditorBeatmap.Add(new Slider
{
Position = new Vector2(256, 256),
StartTime = 1000,
Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }),
Samples =
{
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT),
new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_DRUM),
},
NodeSamples = new List<IList<HitSampleInfo>>
{
new List<HitSampleInfo>
{
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_DRUM),
new HitSampleInfo(HitSampleInfo.HIT_CLAP, bank: HitSampleInfo.BANK_DRUM),
},
new List<HitSampleInfo>
{
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT),
new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_SOFT),
},
}
});
});
AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
AddStep("set soft bank", () =>
{
InputManager.PressKey(Key.LShift);
InputManager.Key(Key.E);
InputManager.ReleaseKey(Key.LShift);
});
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT);
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_SOFT);
hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP);
hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT);
hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
AddStep("unify whistle addition", () => InputManager.Key(Key.W));
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT);
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_SOFT);
hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE);
hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT);
hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
}
[Test] [Test]
public void TestSelectingObjectDoesNotMutateSamples() public void TestSelectingObjectDoesNotMutateSamples()
{ {
@ -569,7 +667,7 @@ namespace osu.Game.Tests.Visual.Editing
private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} sample piece", () => private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} sample piece", () =>
{ {
var samplePiece = this.ChildrenOfType<SamplePointPiece>().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); var samplePiece = this.ChildrenOfType<SamplePointPiece>().Single(piece => piece is not NodeSamplePointPiece && piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex));
InputManager.MoveMouseTo(samplePiece); InputManager.MoveMouseTo(samplePiece);
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
@ -583,6 +681,21 @@ namespace osu.Game.Tests.Visual.Editing
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });
private void seekSamplePiece(int direction) => AddStep($"seek sample piece {direction}", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.PressKey(Key.ShiftLeft);
InputManager.Key(direction < 1 ? Key.Left : Key.Right);
InputManager.ReleaseKey(Key.ShiftLeft);
InputManager.ReleaseKey(Key.ControlLeft);
});
private void samplePopoverIsOpen() => AddUntilStep("sample popover is open", () =>
{
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault(o => o.IsPresent);
return popover != null;
});
private void samplePopoverHasNoFocus() => AddUntilStep("sample popover textbox not focused", () => private void samplePopoverHasNoFocus() => AddUntilStep("sample popover textbox not focused", () =>
{ {
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault(); var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
@ -727,5 +840,7 @@ namespace osu.Game.Tests.Visual.Editing
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats; var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank); return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank);
}); });
private void editorTimeIs(double time) => AddAssert($"editor time is {time}", () => Precision.AlmostEquals(EditorClock.CurrentTimeAccurate, time, 1));
} }
} }

View File

@ -96,32 +96,6 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1)); AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1));
} }
[Test]
public void TestCommitPlacementViaGlobalAction()
{
Playfield playfield = null!;
AddStep("select slider placement tool", () => InputManager.Key(Key.Number3));
AddStep("move mouse to top left of playfield", () =>
{
playfield = this.ChildrenOfType<Playfield>().Single();
var location = (3 * playfield.ScreenSpaceDrawQuad.TopLeft + playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
InputManager.MoveMouseTo(location);
});
AddStep("begin placement", () => InputManager.Click(MouseButton.Left));
AddStep("move mouse to bottom right of playfield", () =>
{
var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
InputManager.MoveMouseTo(location);
});
AddStep("confirm via global action", () =>
{
globalActionContainer.TriggerPressed(GlobalAction.Select);
globalActionContainer.TriggerReleased(GlobalAction.Select);
});
AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1));
}
[Test] [Test]
public void TestAbortPlacementViaGlobalAction() public void TestAbortPlacementViaGlobalAction()
{ {
@ -272,11 +246,7 @@ namespace osu.Game.Tests.Visual.Editing
var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4; var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
InputManager.MoveMouseTo(location); InputManager.MoveMouseTo(location);
}); });
AddStep("confirm via global action", () => AddStep("confirm via right click", () => InputManager.Click(MouseButton.Right));
{
globalActionContainer.TriggerPressed(GlobalAction.Select);
globalActionContainer.TriggerReleased(GlobalAction.Select);
});
AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1)); AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1));
AddAssert("slider samples have drum bank", () => EditorBeatmap.HitObjects[0].Samples.All(s => s.Bank == HitSampleInfo.BANK_DRUM)); AddAssert("slider samples have drum bank", () => EditorBeatmap.HitObjects[0].Samples.All(s => s.Bank == HitSampleInfo.BANK_DRUM));

View File

@ -17,6 +17,7 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Comments; using osu.Game.Overlays.Comments;
using osu.Game.Overlays.Comments.Buttons;
namespace osu.Game.Tests.Visual.Online namespace osu.Game.Tests.Visual.Online
{ {
@ -58,6 +59,11 @@ namespace osu.Game.Tests.Visual.Online
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123)); AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
AddUntilStep("show more button hidden", AddUntilStep("show more button hidden",
() => commentsContainer.ChildrenOfType<CommentsShowMoreButton>().Single().Alpha == 0); () => commentsContainer.ChildrenOfType<CommentsShowMoreButton>().Single().Alpha == 0);
if (withPinned)
AddAssert("pinned comment replies collapsed", () => commentsContainer.ChildrenOfType<ShowRepliesButton>().First().Expanded.Value, () => Is.False);
else
AddAssert("first comment replies expanded", () => commentsContainer.ChildrenOfType<ShowRepliesButton>().First().Expanded.Value, () => Is.True);
} }
[TestCase(false)] [TestCase(false)]
@ -302,7 +308,7 @@ namespace osu.Game.Tests.Visual.Online
bundle.Comments.Add(new Comment bundle.Comments.Add(new Comment
{ {
Id = 20, Id = 20,
Message = "Reply to pinned comment", Message = "Reply to pinned comment initially hidden",
LegacyName = "AbandonedUser", LegacyName = "AbandonedUser",
CreatedAt = DateTimeOffset.Now, CreatedAt = DateTimeOffset.Now,
VotesCount = 0, VotesCount = 0,

View File

@ -147,6 +147,10 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor),
new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl), new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl),
new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl), new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl),
new KeyBinding(new[] { InputKey.Control, InputKey.Left }, GlobalAction.EditorSeekToPreviousHitObject),
new KeyBinding(new[] { InputKey.Control, InputKey.Right }, GlobalAction.EditorSeekToNextHitObject),
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.Left }, GlobalAction.EditorSeekToPreviousSamplePoint),
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.Right }, GlobalAction.EditorSeekToNextSamplePoint),
}; };
private static IEnumerable<KeyBinding> editorTestPlayKeyBindings => new[] private static IEnumerable<KeyBinding> editorTestPlayKeyBindings => new[]
@ -456,6 +460,18 @@ namespace osu.Game.Input.Bindings
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestPlayQuickExitToCurrentTime))] [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestPlayQuickExitToCurrentTime))]
EditorTestPlayQuickExitToCurrentTime, EditorTestPlayQuickExitToCurrentTime,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToPreviousHitObject))]
EditorSeekToPreviousHitObject,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToNextHitObject))]
EditorSeekToNextHitObject,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToPreviousSamplePoint))]
EditorSeekToPreviousSamplePoint,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToNextSamplePoint))]
EditorSeekToNextSamplePoint,
} }
public enum GlobalActionCategory public enum GlobalActionCategory

View File

@ -404,6 +404,26 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString DecreaseModSpeed => new TranslatableString(getKey(@"decrease_mod_speed"), @"Decrease mod speed"); public static LocalisableString DecreaseModSpeed => new TranslatableString(getKey(@"decrease_mod_speed"), @"Decrease mod speed");
/// <summary>
/// "Seek to previous hit object"
/// </summary>
public static LocalisableString EditorSeekToPreviousHitObject => new TranslatableString(getKey(@"editor_seek_to_previous_hit_object"), @"Seek to previous hit object");
/// <summary>
/// "Seek to next hit object"
/// </summary>
public static LocalisableString EditorSeekToNextHitObject => new TranslatableString(getKey(@"editor_seek_to_next_hit_object"), @"Seek to next hit object");
/// <summary>
/// "Seek to previous sample point"
/// </summary>
public static LocalisableString EditorSeekToPreviousSamplePoint => new TranslatableString(getKey(@"editor_seek_to_previous_sample_point"), @"Seek to previous sample point");
/// <summary>
/// "Seek to next sample point"
/// </summary>
public static LocalisableString EditorSeekToNextSamplePoint => new TranslatableString(getKey(@"editor_seek_to_next_sample_point"), @"Seek to next sample point");
private static string getKey(string key) => $@"{prefix}:{key}"; private static string getKey(string key) => $@"{prefix}:{key}";
} }
} }

View File

@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Comments
public readonly BindableList<DrawableComment> Replies = new BindableList<DrawableComment>(); public readonly BindableList<DrawableComment> Replies = new BindableList<DrawableComment>();
private readonly BindableBool childrenExpanded = new BindableBool(true); private readonly BindableBool childrenExpanded;
private int currentPage; private int currentPage;
@ -92,6 +92,8 @@ namespace osu.Game.Overlays.Comments
{ {
Comment = comment; Comment = comment;
Meta = meta; Meta = meta;
childrenExpanded = new BindableBool(!comment.Pinned);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -115,7 +115,7 @@ namespace osu.Game.Overlays
seekDelegate?.Cancel(); seekDelegate?.Cancel();
seekDelegate = Schedule(() => seekDelegate = Schedule(() =>
{ {
if (beatmap.Disabled || !AllowTrackControl.Value) if (!AllowTrackControl.Value)
return; return;
CurrentTrack.Seek(position); CurrentTrack.Seek(position);

View File

@ -23,15 +23,15 @@ namespace osu.Game.Overlays.Volume
{ {
case GlobalAction.DecreaseVolume: case GlobalAction.DecreaseVolume:
case GlobalAction.IncreaseVolume: case GlobalAction.IncreaseVolume:
ActionRequested?.Invoke(e.Action); return ActionRequested?.Invoke(e.Action) == true;
return true;
case GlobalAction.ToggleMute: case GlobalAction.ToggleMute:
case GlobalAction.NextVolumeMeter: case GlobalAction.NextVolumeMeter:
case GlobalAction.PreviousVolumeMeter: case GlobalAction.PreviousVolumeMeter:
if (!e.Repeat) if (!e.Repeat)
ActionRequested?.Invoke(e.Action); return ActionRequested?.Invoke(e.Action) == true;
return true;
return false;
} }
return false; return false;

View File

@ -110,14 +110,18 @@ namespace osu.Game.Overlays
return true; return true;
case GlobalAction.NextVolumeMeter: case GlobalAction.NextVolumeMeter:
if (State.Value == Visibility.Visible) if (State.Value != Visibility.Visible)
volumeMeters.SelectNext(); return false;
volumeMeters.SelectNext();
Show(); Show();
return true; return true;
case GlobalAction.PreviousVolumeMeter: case GlobalAction.PreviousVolumeMeter:
if (State.Value == Visibility.Visible) if (State.Value != Visibility.Visible)
volumeMeters.SelectPrevious(); return false;
volumeMeters.SelectPrevious();
Show(); Show();
return true; return true;

View File

@ -125,10 +125,6 @@ namespace osu.Game.Rulesets.Edit
switch (e.Action) switch (e.Action)
{ {
case GlobalAction.Select:
EndPlacement(true);
return true;
case GlobalAction.Back: case GlobalAction.Back:
EndPlacement(false); EndPlacement(false);
return true; return true;

View File

@ -401,5 +401,10 @@ namespace osu.Game.Rulesets
new DifficultySection(), new DifficultySection(),
new ColoursSection(), new ColoursSection(),
]; ];
/// <summary>
/// Can be overridden to avoid showing scroll speed changes in the editor.
/// </summary>
public virtual bool EditorShowScrollSpeed => true;
} }
} }

View File

@ -64,8 +64,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
MaxValue = time_span_max MaxValue = time_span_max
}; };
ScrollVisualisationMethod IDrawableScrollingRuleset.VisualisationMethod => VisualisationMethod;
/// <summary> /// <summary>
/// Whether the player can change <see cref="TimeRange"/>. /// Whether the player can change <see cref="TimeRange"/>.
/// </summary> /// </summary>

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Configuration;
namespace osu.Game.Rulesets.UI.Scrolling namespace osu.Game.Rulesets.UI.Scrolling
{ {
/// <summary> /// <summary>
@ -10,8 +8,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
/// </summary> /// </summary>
public interface IDrawableScrollingRuleset public interface IDrawableScrollingRuleset
{ {
ScrollVisualisationMethod VisualisationMethod { get; }
IScrollingInfo ScrollingInfo { get; } IScrollingInfo ScrollingInfo { get; }
} }
} }

View File

@ -79,7 +79,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
{ {
ClearInternal(); ClearInternal();
AddInternal(new ControlPointVisualisation(effect)); if (beatmap.BeatmapInfo.Ruleset.CreateInstance().EditorShowScrollSpeed)
{
AddInternal(new ControlPointVisualisation(effect)
{
// importantly, override the x position being set since we do that in the GroupVisualisation parent drawable.
X = 0,
});
}
if (!kiai.Value) if (!kiai.Value)
return; return;

View File

@ -229,7 +229,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
EditorBeatmap.PerformOnSelection(h => EditorBeatmap.PerformOnSelection(h =>
{ {
if (h.Samples.All(s => s.Bank == bankName)) if (hasRelevantBank(h))
return; return;
h.Samples = h.Samples.Select(s => s.With(newBank: bankName)).ToList(); h.Samples = h.Samples.Select(s => s.With(newBank: bankName)).ToList();
@ -269,10 +269,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
EditorBeatmap.PerformOnSelection(h => EditorBeatmap.PerformOnSelection(h =>
{ {
// Make sure there isn't already an existing sample // Make sure there isn't already an existing sample
if (h.Samples.Any(s => s.Name == sampleName)) if (h.Samples.All(s => s.Name != sampleName))
return; h.Samples.Add(h.CreateHitSampleInfo(sampleName));
h.Samples.Add(h.CreateHitSampleInfo(sampleName));
if (h is IHasRepeats hasRepeats) if (h is IHasRepeats hasRepeats)
{ {

View File

@ -22,6 +22,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
NodeIndex = nodeIndex; NodeIndex = nodeIndex;
} }
protected override double GetTime()
{
var hasRepeats = (IHasRepeats)HitObject;
return HitObject.StartTime + hasRepeats.Duration * NodeIndex / hasRepeats.SpanCount();
}
protected override IList<HitSampleInfo> GetSamples() protected override IList<HitSampleInfo> GetSamples()
{ {
var hasRepeats = (IHasRepeats)HitObject; var hasRepeats = (IHasRepeats)HitObject;

View File

@ -14,6 +14,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
@ -33,6 +34,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
public readonly HitObject HitObject; public readonly HitObject HitObject;
[Resolved]
private EditorClock? editorClock { get; set; }
[Resolved]
private Editor? editor { get; set; }
public SamplePointPiece(HitObject hitObject) public SamplePointPiece(HitObject hitObject)
{ {
HitObject = hitObject; HitObject = hitObject;
@ -43,11 +50,32 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
protected override Color4 GetRepresentingColour(OsuColour colours) => AlternativeColor ? colours.Pink2 : colours.Pink1; protected override Color4 GetRepresentingColour(OsuColour colours) => AlternativeColor ? colours.Pink2 : colours.Pink1;
protected virtual double GetTime() => HitObject is IHasRepeats r ? HitObject.StartTime + r.Duration / r.SpanCount() / 2 : HitObject.StartTime;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
HitObject.DefaultsApplied += _ => updateText(); HitObject.DefaultsApplied += _ => updateText();
updateText(); updateText();
if (editor != null)
editor.ShowSampleEditPopoverRequested += onShowSampleEditPopoverRequested;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (editor != null)
editor.ShowSampleEditPopoverRequested -= onShowSampleEditPopoverRequested;
}
private void onShowSampleEditPopoverRequested(double time)
{
if (!Precision.AlmostEquals(time, GetTime())) return;
editorClock?.SeekSmoothlyTo(GetTime());
this.ShowPopover();
} }
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)

View File

@ -44,6 +44,7 @@ using osu.Game.Overlays.OSD;
using osu.Game.Rulesets; using osu.Game.Rulesets;
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.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Components.Menus;
using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.Compose.Components.Timeline;
@ -224,6 +225,9 @@ namespace osu.Game.Screens.Edit
/// </remarks> /// </remarks>
public Bindable<bool> ComposerFocusMode { get; } = new Bindable<bool>(); public Bindable<bool> ComposerFocusMode { get; } = new Bindable<bool>();
[CanBeNull]
public event Action<double> ShowSampleEditPopoverRequested;
public Editor(EditorLoader loader = null) public Editor(EditorLoader loader = null)
{ {
this.loader = loader; this.loader = loader;
@ -713,6 +717,26 @@ namespace osu.Game.Screens.Edit
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
// Repeatable actions
switch (e.Action)
{
case GlobalAction.EditorSeekToPreviousHitObject:
seekHitObject(-1);
return true;
case GlobalAction.EditorSeekToNextHitObject:
seekHitObject(1);
return true;
case GlobalAction.EditorSeekToPreviousSamplePoint:
seekSamplePoint(-1);
return true;
case GlobalAction.EditorSeekToNextSamplePoint:
seekSamplePoint(1);
return true;
}
if (e.Repeat) if (e.Repeat)
return false; return false;
@ -750,10 +774,9 @@ namespace osu.Game.Screens.Edit
case GlobalAction.EditorTestGameplay: case GlobalAction.EditorTestGameplay:
bottomBar.TestGameplayButton.TriggerClick(); bottomBar.TestGameplayButton.TriggerClick();
return true; return true;
default:
return false;
} }
return false;
} }
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e) public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
@ -1077,6 +1100,66 @@ namespace osu.Game.Screens.Edit
clock.Seek(found.Time); clock.Seek(found.Time);
} }
private void seekHitObject(int direction)
{
var found = direction < 1
? editorBeatmap.HitObjects.LastOrDefault(p => p.StartTime < clock.CurrentTimeAccurate)
: editorBeatmap.HitObjects.FirstOrDefault(p => p.StartTime > clock.CurrentTimeAccurate);
if (found != null)
clock.SeekSmoothlyTo(found.StartTime);
}
private void seekSamplePoint(int direction)
{
double currentTime = clock.CurrentTimeAccurate;
// Check if we are currently inside a hit object with node samples, if so seek to the next node sample point
var current = direction < 1
? editorBeatmap.HitObjects.LastOrDefault(p => p is IHasRepeats r && p.StartTime < currentTime && r.EndTime >= currentTime)
: editorBeatmap.HitObjects.LastOrDefault(p => p is IHasRepeats r && p.StartTime <= currentTime && r.EndTime > currentTime);
if (current != null)
{
// Find the next node sample point
var r = (IHasRepeats)current;
double[] nodeSamplePointTimes = new double[r.RepeatCount + 3];
nodeSamplePointTimes[0] = current.StartTime;
// The sample point for the main samples is sandwiched between the head and the first repeat
nodeSamplePointTimes[1] = current.StartTime + r.Duration / r.SpanCount() / 2;
for (int i = 0; i < r.SpanCount(); i++)
{
nodeSamplePointTimes[i + 2] = current.StartTime + r.Duration * (i + 1) / r.SpanCount();
}
double found = direction < 1
? nodeSamplePointTimes.Last(p => p < currentTime)
: nodeSamplePointTimes.First(p => p > currentTime);
clock.SeekSmoothlyTo(found);
}
else
{
if (direction < 1)
{
current = editorBeatmap.HitObjects.LastOrDefault(p => p.StartTime < currentTime);
if (current != null)
clock.SeekSmoothlyTo(current is IHasRepeats r ? r.EndTime : current.StartTime);
}
else
{
current = editorBeatmap.HitObjects.FirstOrDefault(p => p.StartTime > currentTime);
if (current != null)
clock.SeekSmoothlyTo(current.StartTime);
}
}
// Show the sample edit popover at the current time
ShowSampleEditPopoverRequested?.Invoke(clock.CurrentTimeAccurate);
}
private void seek(UIEvent e, int direction) private void seek(UIEvent e, int direction)
{ {
double amount = e.ShiftPressed ? 4 : 1; double amount = e.ShiftPressed ? 4 : 1;

View File

@ -5,9 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Screens.Edit.Timing namespace osu.Game.Screens.Edit.Timing
{ {
@ -38,8 +36,7 @@ namespace osu.Game.Screens.Edit.Timing
kiai.Current.BindValueChanged(_ => saveChanges()); kiai.Current.BindValueChanged(_ => saveChanges());
scrollSpeedSlider.Current.BindValueChanged(_ => saveChanges()); scrollSpeedSlider.Current.BindValueChanged(_ => saveChanges());
var drawableRuleset = Beatmap.BeatmapInfo.Ruleset.CreateInstance().CreateDrawableRulesetWith(Beatmap.PlayableBeatmap); if (!Beatmap.BeatmapInfo.Ruleset.CreateInstance().EditorShowScrollSpeed)
if (drawableRuleset is not IDrawableScrollingRuleset scrollingRuleset || scrollingRuleset.VisualisationMethod == ScrollVisualisationMethod.Constant)
scrollSpeedSlider.Hide(); scrollSpeedSlider.Hide();
void saveChanges() void saveChanges()

View File

@ -15,6 +15,10 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes
private AttributeText kiaiModeBubble = null!; private AttributeText kiaiModeBubble = null!;
private AttributeText text = null!; private AttributeText text = null!;
private AttributeProgressBar progressBar = null!;
[Resolved]
protected EditorBeatmap Beatmap { get; private set; } = null!;
public EffectRowAttribute(EffectControlPoint effect) public EffectRowAttribute(EffectControlPoint effect)
: base(effect, "effect") : base(effect, "effect")
@ -28,7 +32,7 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes
{ {
Content.AddRange(new Drawable[] Content.AddRange(new Drawable[]
{ {
new AttributeProgressBar(Point) progressBar = new AttributeProgressBar(Point)
{ {
Current = scrollSpeed, Current = scrollSpeed,
}, },
@ -36,6 +40,12 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes
kiaiModeBubble = new AttributeText(Point) { Text = "kiai" }, kiaiModeBubble = new AttributeText(Point) { Text = "kiai" },
}); });
if (!Beatmap.BeatmapInfo.Ruleset.CreateInstance().EditorShowScrollSpeed)
{
text.Hide();
progressBar.Hide();
}
kiaiMode.BindValueChanged(enabled => kiaiModeBubble.FadeTo(enabled.NewValue ? 1 : 0), true); kiaiMode.BindValueChanged(enabled => kiaiModeBubble.FadeTo(enabled.NewValue ? 1 : 0), true);
scrollSpeed.BindValueChanged(_ => updateText(), true); scrollSpeed.BindValueChanged(_ => updateText(), true);
} }

View File

@ -108,7 +108,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
base.Dispose(isDisposing); base.Dispose(isDisposing);
if (client.IsNotNull()) if (client.IsNotNull())
{
client.RoomUpdated -= onRoomUpdated; client.RoomUpdated -= onRoomUpdated;
client.GameplayAborted -= onGameplayAborted;
}
} }
} }
} }

View File

@ -62,10 +62,31 @@ namespace osu.Game.Screens.Select
case "length": case "length":
return tryUpdateLengthRange(criteria, op, value); return tryUpdateLengthRange(criteria, op, value);
case "played":
case "lastplayed": case "lastplayed":
return tryUpdateDateAgoRange(ref criteria.LastPlayed, op, value); return tryUpdateDateAgoRange(ref criteria.LastPlayed, op, value);
case "played":
if (!tryParseBool(value, out bool played))
return false;
// Unplayed beatmaps are filtered on DateTimeOffset.MinValue.
if (played)
{
criteria.LastPlayed.Min = DateTimeOffset.MinValue;
criteria.LastPlayed.Max = DateTimeOffset.MaxValue;
criteria.LastPlayed.IsLowerInclusive = false;
}
else
{
criteria.LastPlayed.Min = DateTimeOffset.MinValue;
criteria.LastPlayed.Max = DateTimeOffset.MinValue;
criteria.LastPlayed.IsLowerInclusive = true;
criteria.LastPlayed.IsUpperInclusive = true;
}
return true;
case "divisor": case "divisor":
return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt); return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt);
@ -133,6 +154,23 @@ namespace osu.Game.Screens.Select
private static bool tryParseInt(string value, out int result) => private static bool tryParseInt(string value, out int result) =>
int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result); int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result);
private static bool tryParseBool(string value, out bool result)
{
switch (value)
{
case "1":
result = true;
return true;
case "0":
result = false;
return true;
default:
return bool.TryParse(value, out result);
}
}
private static bool tryParseEnum<TEnum>(string value, out TEnum result) where TEnum : struct private static bool tryParseEnum<TEnum>(string value, out TEnum result) where TEnum : struct
{ {
// First try an exact match. // First try an exact match.