diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 65ac05261a..cbd0231fdb 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -21,7 +21,7 @@
]
},
"ppy.localisationanalyser.tools": {
- "version": "2022.417.0",
+ "version": "2022.607.0",
"commands": [
"localisation"
]
diff --git a/osu.Android.props b/osu.Android.props
index 116c7dbfcd..aad8cf10d0 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,10 +52,10 @@
-
+
-
+
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs
index 715614a201..a5bd126782 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs
@@ -14,11 +14,11 @@ namespace osu.Game.Rulesets.Mania.Tests
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
- [TestCase(2.3449735700206298d, 151, "diffcalc-test")]
+ [TestCase(2.3449735700206298d, 242, "diffcalc-test")]
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
=> base.Test(expectedStarRating, expectedMaxCombo, name);
- [TestCase(2.7879104989252959d, 151, "diffcalc-test")]
+ [TestCase(2.7879104989252959d, 242, "diffcalc-test")]
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
=> Test(expectedStarRating, expectedMaxCombo, name, new ManiaModDoubleTime());
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
index 5b7a460079..c35a3dcdc2 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
foreach (var v in base.ToDatabaseAttributes())
yield return v;
- // Todo: osu!mania doesn't output MaxCombo attribute for some reason.
+ yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier);
@@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
base.FromDatabaseAttributes(values);
+ MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
StarRating = values[ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER];
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index b17aa7fc4d..88f51bf961 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -52,10 +52,18 @@ namespace osu.Game.Rulesets.Mania.Difficulty
// This is done the way it is to introduce fractional differences in order to match osu-stable for the time being.
GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate),
ScoreMultiplier = getScoreMultiplier(mods),
- MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1),
+ MaxCombo = beatmap.HitObjects.Sum(maxComboForObject)
};
}
+ private static int maxComboForObject(HitObject hitObject)
+ {
+ if (hitObject is HoldNote hold)
+ return 1 + (int)((hold.EndTime - hold.StartTime) / 100);
+
+ return 1;
+ }
+
protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
{
var sortedObjects = beatmap.HitObjects.ToArray();
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index a904658a4c..fa095edafa 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -79,7 +79,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
Result = { BindTarget = SpinsPerMinute },
},
- ticks = new Container(),
+ ticks = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
new AspectContainer
{
Anchor = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs
index 726fbd3ea6..39239c8233 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Graphics;
+
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableSpinnerTick : DrawableOsuHitObject
@@ -10,13 +12,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected DrawableSpinner DrawableSpinner => (DrawableSpinner)ParentHitObject;
public DrawableSpinnerTick()
- : base(null)
+ : this(null)
{
}
public DrawableSpinnerTick(SpinnerTick spinnerTick)
: base(spinnerTick)
{
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
}
protected override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index ddee4d3ebd..1a130e96b3 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -69,8 +69,8 @@ namespace osu.Game.Rulesets.Osu.Objects
double startTime = StartTime + (float)(i + 1) / totalSpins * Duration;
AddNested(i < SpinsRequired
- ? new SpinnerTick { StartTime = startTime, Position = Position }
- : new SpinnerBonusTick { StartTime = startTime, Position = Position });
+ ? new SpinnerTick { StartTime = startTime }
+ : new SpinnerBonusTick { StartTime = startTime });
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs
index 1e170036e4..a58f62736b 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs
@@ -78,7 +78,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
}
});
- if (!(source.FindProvider(s => s.GetTexture("spinner-top") != null) is DefaultLegacySkin))
+ var topProvider = source.FindProvider(s => s.GetTexture("spinner-top") != null);
+
+ if (topProvider is LegacySkinTransformer transformer && !(transformer.Skin is DefaultLegacySkin))
{
AddInternal(ApproachCircle = new Sprite
{
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index 504b10e9bc..2dd332fc13 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -8,6 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
+using osu.Framework.Graphics.Primitives;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Judgements;
@@ -321,12 +322,14 @@ namespace osu.Game.Rulesets.Taiko.UI
private class ProxyContainer : LifetimeManagementContainer
{
- public new MarginPadding Padding
- {
- set => base.Padding = value;
- }
-
public void Add(Drawable proxy) => AddInternal(proxy);
+
+ public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds)
+ {
+ // DrawableHitObject disables masking.
+ // Hitobject content is proxied and unproxied based on hit status and the IsMaskedAway value could get stuck because of this.
+ return false;
+ }
}
}
}
diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs
index 00276955aa..d4956e97e0 100644
--- a/osu.Game.Tests/Database/BeatmapImporterTests.cs
+++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs
@@ -710,7 +710,7 @@ namespace osu.Game.Tests.Database
var imported = await LoadOszIntoStore(importer, realm.Realm);
- realm.Realm.Write(() =>
+ await realm.Realm.WriteAsync(() =>
{
foreach (var b in imported.Beatmaps)
b.OnlineID = -1;
diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs
index af4b002bc9..97be1dcfaa 100644
--- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs
@@ -59,12 +59,14 @@ namespace osu.Game.Tests.Gameplay
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TestJudgement(HitResult.Great)) { Type = HitResult.Great });
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
+ Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(1));
// No header shouldn't cause any change
scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame());
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
+ Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(1));
// Reset with a miss instead.
scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame
@@ -74,6 +76,7 @@ namespace osu.Game.Tests.Gameplay
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
+ Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0));
// Reset with no judged hit.
scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame
@@ -83,6 +86,7 @@ namespace osu.Game.Tests.Gameplay
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
Assert.That(scoreProcessor.JudgedHits, Is.Zero);
+ Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0));
}
private class TestJudgement : Judgement
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
index d3cacaa88c..d68398236a 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
@@ -59,11 +59,13 @@ namespace osu.Game.Tests.Skins
AddAssert("Check float parse lookup", () => requester.GetConfig("FloatTest")?.Value == 1.1f);
}
- [Test]
- public void TestBoolLookup()
+ [TestCase("0", false)]
+ [TestCase("1", true)]
+ [TestCase("2", true)] // https://github.com/ppy/osu/issues/18579
+ public void TestBoolLookup(string originalValue, bool expectedParsedValue)
{
- AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["BoolTest"] = "1");
- AddAssert("Check bool parse lookup", () => requester.GetConfig("BoolTest")?.Value == true);
+ AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["BoolTest"] = originalValue);
+ AddAssert("Check bool parse lookup", () => requester.GetConfig("BoolTest")?.Value == expectedParsedValue);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTapButton.cs b/osu.Game.Tests/Visual/Editing/TestSceneTapButton.cs
new file mode 100644
index 0000000000..d8141619ab
--- /dev/null
+++ b/osu.Game.Tests/Visual/Editing/TestSceneTapButton.cs
@@ -0,0 +1,48 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Overlays;
+using osu.Game.Screens.Edit.Timing;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Tests.Visual.Editing
+{
+ public class TestSceneTapButton : OsuManualInputManagerTestScene
+ {
+ private TapButton tapButton;
+
+ [Cached]
+ private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
+
+ [Test]
+ public void TestBasic()
+ {
+ AddStep("create button", () =>
+ {
+ Child = tapButton = new TapButton
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(4),
+ };
+ });
+
+ bool pressed = false;
+
+ AddRepeatStep("Press button", () =>
+ {
+ InputManager.MoveMouseTo(tapButton);
+ if (!pressed)
+ InputManager.PressButton(MouseButton.Left);
+ else
+ InputManager.ReleaseButton(MouseButton.Left);
+
+ pressed = !pressed;
+ }, 100);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs
index 8dd368f2a9..a1218aa3e7 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs
@@ -11,7 +11,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Sprites;
-using osu.Game.Graphics.UserInterfaceV2;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Timing;
@@ -77,34 +77,6 @@ namespace osu.Game.Tests.Visual.Editing
timingInfo.Text = $"offset: {selectedGroup.Value.Time:N2} bpm: {selectedGroup.Value.ControlPoints.OfType().First().BPM:N2}";
}
- [Test]
- public void TestNoop()
- {
- AddStep("do nothing", () => { });
- }
-
- [Test]
- public void TestTapThenReset()
- {
- AddStep("click tap button", () =>
- {
- control.ChildrenOfType()
- .Last()
- .TriggerClick();
- });
-
- AddUntilStep("wait for track playing", () => Clock.IsRunning);
-
- AddStep("click reset button", () =>
- {
- control.ChildrenOfType()
- .First()
- .TriggerClick();
- });
-
- AddUntilStep("wait for track stopped", () => !Clock.IsRunning);
- }
-
[Test]
public void TestBasic()
{
@@ -115,7 +87,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("click tap button", () =>
{
- control.ChildrenOfType()
+ control.ChildrenOfType()
.Last()
.TriggerClick();
});
@@ -129,6 +101,28 @@ namespace osu.Game.Tests.Visual.Editing
});
}
+ [Test]
+ public void TestTapThenReset()
+ {
+ AddStep("click tap button", () =>
+ {
+ control.ChildrenOfType()
+ .Last()
+ .TriggerClick();
+ });
+
+ AddUntilStep("wait for track playing", () => Clock.IsRunning);
+
+ AddStep("click reset button", () =>
+ {
+ control.ChildrenOfType()
+ .First()
+ .TriggerClick();
+ });
+
+ AddUntilStep("wait for track stopped", () => !Clock.IsRunning);
+ }
+
protected override void Dispose(bool isDisposing)
{
Beatmap.Disabled = false;
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs
new file mode 100644
index 0000000000..d726bd004e
--- /dev/null
+++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs
@@ -0,0 +1,48 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Utils;
+
+namespace osu.Game.Tests.Visual.Editing
+{
+ public class TestSceneTimelineZoom : TimelineTestScene
+ {
+ public override Drawable CreateTestComponent() => Empty();
+
+ [Test]
+ public void TestVisibleRangeUpdatesOnZoomChange()
+ {
+ double initialVisibleRange = 0;
+
+ AddStep("reset zoom", () => TimelineArea.Timeline.Zoom = 100);
+ AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
+
+ AddStep("scale zoom", () => TimelineArea.Timeline.Zoom = 200);
+ AddAssert("range halved", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange / 2, 1));
+ AddStep("descale zoom", () => TimelineArea.Timeline.Zoom = 50);
+ AddAssert("range doubled", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange * 2, 1));
+
+ AddStep("restore zoom", () => TimelineArea.Timeline.Zoom = 100);
+ AddAssert("range restored", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange, 1));
+ }
+
+ [Test]
+ public void TestVisibleRangeConstantOnSizeChange()
+ {
+ double initialVisibleRange = 0;
+
+ AddStep("reset timeline size", () => TimelineArea.Timeline.Width = 1);
+ AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
+
+ AddStep("scale timeline size", () => TimelineArea.Timeline.Width = 2);
+ AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange);
+ AddStep("descale timeline size", () => TimelineArea.Timeline.Width = 0.5f);
+ AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange);
+
+ AddStep("restore timeline size", () => TimelineArea.Timeline.Width = 1);
+ AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs
index 95d11d6909..2d056bafdd 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs
@@ -44,7 +44,12 @@ namespace osu.Game.Tests.Visual.Editing
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(30)
},
- scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both }
+ scrollContainer = new ZoomableScrollContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ }
}
},
new MenuCursor()
@@ -62,7 +67,15 @@ namespace osu.Game.Tests.Visual.Editing
[Test]
public void TestWidthInitialization()
{
- AddAssert("Inner container width was initialized", () => innerBox.DrawWidth > 0);
+ AddAssert("Inner container width was initialized", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
+ }
+
+ [Test]
+ public void TestWidthUpdatesOnDrawSizeChanges()
+ {
+ AddStep("Shrink scroll container", () => scrollContainer.Width = 0.5f);
+ AddAssert("Scroll container width shrunk", () => scrollContainer.DrawWidth == scrollContainer.Parent.DrawWidth / 2);
+ AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs
index 79f62a16e3..5f7c8b3c51 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -178,6 +179,36 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1);
}
+ ///
+ /// Ensures that handles channels which have not been or could not be resolved (i.e. = 0).
+ ///
+ [Test]
+ public void TestSendInUnresolvedChannel()
+ {
+ int i = 1;
+ Channel unresolved = null;
+
+ AddRepeatStep("join unresolved channels", () => testContainer.ChannelManager.JoinChannel(unresolved = new Channel(new APIUser
+ {
+ Id = 100 + i,
+ Username = $"Foreign #{i++}",
+ })), 5);
+
+ AddStep("send message in unresolved channel", () =>
+ {
+ Debug.Assert(unresolved.Id == 0);
+
+ unresolved.AddLocalEcho(new LocalEchoMessage
+ {
+ Sender = API.LocalUser.Value,
+ ChannelId = unresolved.Id,
+ Content = "Some message",
+ });
+ });
+
+ AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
+ }
+
private void receiveMessage(APIUser sender, Channel channel, string content) => channel.AddNewMessages(createMessage(sender, channel, content));
private Message createMessage(APIUser sender, Channel channel, string content) => new Message(messageIdCounter++)
diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs
index 860ef5d565..cb52f41c33 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs
@@ -128,11 +128,11 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("Ensure no adjacent day separators", () =>
{
- var indices = chatDisplay.FillFlow.OfType().Select(ds => chatDisplay.FillFlow.IndexOf(ds));
+ var indices = chatDisplay.FillFlow.OfType().Select(ds => chatDisplay.FillFlow.IndexOf(ds));
foreach (int i in indices)
{
- if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DrawableChannel.DaySeparator)
+ if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DaySeparator)
return false;
}
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs
index 8b646df362..2a31728f87 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs
@@ -96,6 +96,7 @@ namespace osu.Game.Tests.Visual.Ranking
beatmap.Metadata.Author = author;
beatmap.Metadata.Title = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap title";
beatmap.Metadata.Artist = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap artist";
+ beatmap.DifficultyName = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong difficulty name";
return beatmap;
}
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 5f7de0d762..dba457c81c 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -319,6 +319,15 @@ namespace osu.Game.Beatmaps
});
}
+ public void DeleteAllVideos()
+ {
+ realm.Write(r =>
+ {
+ var items = r.All().Where(s => !s.DeletePending && !s.Protected);
+ beatmapModelManager.DeleteVideos(items.ToList());
+ });
+ }
+
public void UndeleteAll()
{
realm.Run(r => beatmapModelManager.Undelete(r.All().Where(s => s.DeletePending).ToList()));
diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs
index 4c680bbcc9..277047348e 100644
--- a/osu.Game/Beatmaps/BeatmapModelManager.cs
+++ b/osu.Game/Beatmaps/BeatmapModelManager.cs
@@ -16,6 +16,7 @@ using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.Skinning;
using osu.Game.Stores;
+using osu.Game.Overlays.Notifications;
#nullable enable
@@ -33,6 +34,8 @@ namespace osu.Game.Beatmaps
protected override string[] HashableFileTypes => new[] { ".osu" };
+ public static readonly string[] VIDEO_EXTENSIONS = { ".mp4", ".mov", ".avi", ".flv" };
+
public BeatmapModelManager(RealmAccess realm, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null)
: base(realm, storage, onlineLookupQueue)
{
@@ -114,5 +117,50 @@ namespace osu.Game.Beatmaps
item.CopyChangesToRealm(existing);
});
}
+
+ ///
+ /// Delete videos from a list of beatmaps.
+ /// This will post notifications tracking progress.
+ ///
+ public void DeleteVideos(List items, bool silent = false)
+ {
+ if (items.Count == 0) return;
+
+ var notification = new ProgressNotification
+ {
+ Progress = 0,
+ Text = $"Preparing to delete all {HumanisedModelName} videos...",
+ CompletionText = "No videos found to delete!",
+ State = ProgressNotificationState.Active,
+ };
+
+ if (!silent)
+ PostNotification?.Invoke(notification);
+
+ int i = 0;
+ int deleted = 0;
+
+ foreach (var b in items)
+ {
+ if (notification.State == ProgressNotificationState.Cancelled)
+ // user requested abort
+ return;
+
+ var video = b.Files.FirstOrDefault(f => VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.Ordinal)));
+
+ if (video != null)
+ {
+ DeleteFile(b, video);
+ deleted++;
+ notification.CompletionText = $"Deleted {deleted} {HumanisedModelName} video(s)!";
+ }
+
+ notification.Text = $"Deleting videos from {HumanisedModelName}s ({deleted} deleted)";
+
+ notification.Progress = (float)++i / items.Count;
+ }
+
+ notification.State = ProgressNotificationState.Completed;
+ }
}
}
diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs
index 9a17d6dc9b..08aaa8da42 100644
--- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs
+++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
protected readonly BeatmapDownloadTracker DownloadTracker;
protected BeatmapCard(APIBeatmapSet beatmapSet, bool allowExpansion = true)
- : base(HoverSampleSet.Submit)
+ : base(HoverSampleSet.Button)
{
Expanded = new BindableBool { Disabled = !allowExpansion };
diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs
index 58c1ebee0f..7826d64296 100644
--- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs
+++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs
@@ -6,7 +6,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
using osu.Game.Graphics;
@@ -245,10 +244,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards
});
if (BeatmapSet.HasVideo)
- leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) });
+ leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) });
if (BeatmapSet.HasStoryboard)
- leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) });
+ leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) });
if (BeatmapSet.FeaturedInSpotlight)
{
diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs
index 3d7e81de21..c1ba521925 100644
--- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs
+++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs
@@ -7,7 +7,6 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
using osu.Game.Graphics;
@@ -226,10 +225,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards
});
if (BeatmapSet.HasVideo)
- leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) });
+ leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) });
if (BeatmapSet.HasStoryboard)
- leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) });
+ leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) });
if (BeatmapSet.FeaturedInSpotlight)
{
diff --git a/osu.Game/Beatmaps/Drawables/Cards/IconPill.cs b/osu.Game/Beatmaps/Drawables/Cards/IconPill.cs
index c55e9c0a28..1b2c5d3ffc 100644
--- a/osu.Game/Beatmaps/Drawables/Cards/IconPill.cs
+++ b/osu.Game/Beatmaps/Drawables/Cards/IconPill.cs
@@ -3,14 +3,16 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Beatmaps.Drawables.Cards
{
- public class IconPill : CircularContainer
+ public abstract class IconPill : CircularContainer, IHasTooltip
{
public Vector2 IconSize
{
@@ -20,7 +22,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
private readonly Container iconContainer;
- public IconPill(IconUsage icon)
+ protected IconPill(IconUsage icon)
{
AutoSizeAxes = Axes.Both;
Masking = true;
@@ -47,5 +49,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
},
};
}
+
+ public abstract LocalisableString TooltipText { get; }
}
}
diff --git a/osu.Game/Beatmaps/Drawables/Cards/StoryboardIconPill.cs b/osu.Game/Beatmaps/Drawables/Cards/StoryboardIconPill.cs
new file mode 100644
index 0000000000..2ebf9107f5
--- /dev/null
+++ b/osu.Game/Beatmaps/Drawables/Cards/StoryboardIconPill.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Resources.Localisation.Web;
+
+namespace osu.Game.Beatmaps.Drawables.Cards
+{
+ public class StoryboardIconPill : IconPill
+ {
+ public StoryboardIconPill()
+ : base(FontAwesome.Solid.Image)
+ {
+ }
+
+ public override LocalisableString TooltipText => BeatmapsetsStrings.ShowInfoStoryboard;
+ }
+}
diff --git a/osu.Game/Beatmaps/Drawables/Cards/VideoIconPill.cs b/osu.Game/Beatmaps/Drawables/Cards/VideoIconPill.cs
new file mode 100644
index 0000000000..b81e18b0dd
--- /dev/null
+++ b/osu.Game/Beatmaps/Drawables/Cards/VideoIconPill.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Resources.Localisation.Web;
+
+namespace osu.Game.Beatmaps.Drawables.Cards
+{
+ public class VideoIconPill : IconPill
+ {
+ public VideoIconPill()
+ : base(FontAwesome.Solid.Film)
+ {
+ }
+
+ public override LocalisableString TooltipText => BeatmapsetsStrings.ShowInfoVideo;
+ }
+}
diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs
index dbd3b96763..086ec52d80 100644
--- a/osu.Game/Database/RealmAccess.cs
+++ b/osu.Game/Database/RealmAccess.cs
@@ -392,7 +392,7 @@ namespace osu.Game.Database
{
total_writes_async.Value++;
using (var realm = getRealmInstance())
- await realm.WriteAsync(action);
+ await realm.WriteAsync(() => action(realm));
}
///
diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs
index 11bfd80ec1..5c6e315225 100644
--- a/osu.Game/Graphics/Containers/ScalingContainer.cs
+++ b/osu.Game/Graphics/Containers/ScalingContainer.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Graphics.Containers
///
public class ScalingContainer : Container
{
- private const float duration = 500;
+ internal const float TRANSITION_DURATION = 500;
private Bindable sizeX;
private Bindable sizeY;
@@ -99,7 +99,7 @@ namespace osu.Game.Graphics.Containers
if (applyUIScale)
{
uiScale = osuConfig.GetBindable(OsuSetting.UIScale);
- uiScale.BindValueChanged(args => this.TransformTo(nameof(CurrentScale), args.NewValue, duration, Easing.OutQuart), true);
+ uiScale.BindValueChanged(args => this.TransformTo(nameof(CurrentScale), args.NewValue, TRANSITION_DURATION, Easing.OutQuart), true);
}
}
@@ -163,10 +163,10 @@ namespace osu.Game.Graphics.Containers
backgroundStack.Push(new ScalingBackgroundScreen());
}
- backgroundStack.FadeIn(duration);
+ backgroundStack.FadeIn(TRANSITION_DURATION);
}
else
- backgroundStack?.FadeOut(duration);
+ backgroundStack?.FadeOut(TRANSITION_DURATION);
}
RectangleF targetRect = new RectangleF(Vector2.Zero, Vector2.One);
@@ -195,13 +195,13 @@ namespace osu.Game.Graphics.Containers
if (requiresMasking)
sizableContainer.Masking = true;
- sizableContainer.MoveTo(targetRect.Location, duration, Easing.OutQuart);
- sizableContainer.ResizeTo(targetRect.Size, duration, Easing.OutQuart);
+ sizableContainer.MoveTo(targetRect.Location, TRANSITION_DURATION, Easing.OutQuart);
+ sizableContainer.ResizeTo(targetRect.Size, TRANSITION_DURATION, Easing.OutQuart);
// Of note, this will not work great in the case of nested ScalingContainers where multiple are applying corner radius.
// Masking and corner radius should likely only be applied at one point in the full game stack to fix this.
// An example of how this can occur is when the skin editor is visible and the game screen scaling is set to "Everything".
- sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, duration, requiresMasking ? Easing.OutQuart : Easing.None)
+ sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, TRANSITION_DURATION, requiresMasking ? Easing.OutQuart : Easing.None)
.OnComplete(_ => { sizableContainer.Masking = requiresMasking; });
}
diff --git a/osu.Game/Graphics/UserInterface/BackButton.cs b/osu.Game/Graphics/UserInterface/BackButton.cs
index 1b564ef1b4..2b59ee0282 100644
--- a/osu.Game/Graphics/UserInterface/BackButton.cs
+++ b/osu.Game/Graphics/UserInterface/BackButton.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface
{
Size = TwoLayerButton.SIZE_EXTENDED;
- Child = button = new TwoLayerButton(HoverSampleSet.Submit)
+ Child = button = new TwoLayerButton
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs
index ad69ec4078..69fbd744c9 100644
--- a/osu.Game/Graphics/UserInterface/DialogButton.cs
+++ b/osu.Game/Graphics/UserInterface/DialogButton.cs
@@ -56,8 +56,8 @@ namespace osu.Game.Graphics.UserInterface
private readonly SpriteText spriteText;
private Vector2 hoverSpacing => new Vector2(3f, 0f);
- public DialogButton()
- : base(HoverSampleSet.Submit)
+ public DialogButton(HoverSampleSet sampleSet = HoverSampleSet.Button)
+ : base(sampleSet)
{
RelativeSizeAxes = Axes.X;
diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs
index 0df69a5b54..1730e1478f 100644
--- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs
+++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Graphics.UserInterface
Icon = FontAwesome.Solid.ExternalLinkAlt,
RelativeSizeAxes = Axes.Both
},
- new HoverClickSounds(HoverSampleSet.Submit)
+ new HoverClickSounds()
};
}
diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs
index 12819840e5..ba253a7c71 100644
--- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs
+++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs
@@ -7,6 +7,7 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Extensions;
using osu.Framework.Input.Events;
+using osu.Framework.Utils;
using osuTK.Input;
namespace osu.Game.Graphics.UserInterface
@@ -37,7 +38,10 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnClick(ClickEvent e)
{
if (buttons.Contains(e.Button) && Contains(e.ScreenSpaceMousePosition))
- sampleClick?.Play();
+ {
+ sampleClick.Frequency.Value = 0.99 + RNG.NextDouble(0.02);
+ sampleClick.Play();
+ }
return base.OnClick(e);
}
diff --git a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs
index a5ea6fcfbf..b88f81a143 100644
--- a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs
+++ b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs
@@ -10,9 +10,6 @@ namespace osu.Game.Graphics.UserInterface
[Description("default")]
Default,
- [Description("submit")]
- Submit,
-
[Description("button")]
Button,
diff --git a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs
index 4780270f66..ee59da7279 100644
--- a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs
+++ b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs
@@ -13,6 +13,7 @@ namespace osu.Game.Graphics.UserInterface
{
public class ShearedToggleButton : ShearedButton
{
+ private Sample? sampleClick;
private Sample? sampleOff;
private Sample? sampleOn;
@@ -39,8 +40,9 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
- sampleOn = audio.Samples.Get(@"UI/check-on");
- sampleOff = audio.Samples.Get(@"UI/check-off");
+ sampleClick = audio.Samples.Get(@"UI/default-select");
+ sampleOn = audio.Samples.Get(@"UI/dropdown-open");
+ sampleOff = audio.Samples.Get(@"UI/dropdown-close");
}
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
@@ -67,6 +69,8 @@ namespace osu.Game.Graphics.UserInterface
private void playSample()
{
+ sampleClick?.Play();
+
if (Active.Value)
sampleOn?.Play();
else
diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs
index 3d09d09833..d9dbf4974b 100644
--- a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs
@@ -2,11 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using System.IO;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
+using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
@@ -65,6 +67,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
{
get
{
+ if (BeatmapModelManager.VIDEO_EXTENSIONS.Contains(File.Extension))
+ return FontAwesome.Regular.FileVideo;
+
switch (File.Extension)
{
case @".ogg":
@@ -77,12 +82,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
case @".png":
return FontAwesome.Regular.FileImage;
- case @".mp4":
- case @".avi":
- case @".mov":
- case @".flv":
- return FontAwesome.Regular.FileVideo;
-
default:
return FontAwesome.Regular.File;
}
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index 69ea6b00ca..3da5f3212e 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -80,6 +80,7 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight),
new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode),
new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay),
+ new KeyBinding(new[] { InputKey.T }, GlobalAction.EditorTapForBPM),
new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.EditorFlipHorizontally),
new KeyBinding(new[] { InputKey.Control, InputKey.J }, GlobalAction.EditorFlipVertically),
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.EditorDecreaseDistanceSpacing),
@@ -322,5 +323,8 @@ namespace osu.Game.Input.Bindings
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DeselectAllMods))]
DeselectAllMods,
+
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTapForBPM))]
+ EditorTapForBPM,
}
}
diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs
index f298717c99..0f0f560df9 100644
--- a/osu.Game/Localisation/AudioSettingsStrings.cs
+++ b/osu.Game/Localisation/AudioSettingsStrings.cs
@@ -30,12 +30,12 @@ namespace osu.Game.Localisation
public static LocalisableString OutputDevice => new TranslatableString(getKey(@"output_device"), @"Output device");
///
- /// "Master"
+ /// "Hitsound stereo separation"
///
public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Hitsound stereo separation");
///
- /// "Level"
+ /// "Master"
///
public static LocalisableString MasterVolume => new TranslatableString(getKey(@"master_volume"), @"Master");
@@ -69,6 +69,6 @@ namespace osu.Game.Localisation
///
public static LocalisableString OffsetWizard => new TranslatableString(getKey(@"offset_wizard"), @"Offset wizard");
- private static string getKey(string key) => $"{prefix}:{key}";
+ private static string getKey(string key) => $@"{prefix}:{key}";
}
}
diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs
index e392ae619f..82d03dbb5b 100644
--- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs
+++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs
@@ -174,6 +174,11 @@ namespace osu.Game.Localisation
///
public static LocalisableString EditorTimingMode => new TranslatableString(getKey(@"editor_timing_mode"), @"Timing mode");
+ ///
+ /// "Tap for BPM"
+ ///
+ public static LocalisableString EditorTapForBPM => new TranslatableString(getKey(@"editor_tap_for_bpm"), @"Tap for BPM");
+
///
/// "Cycle grid display mode"
///
@@ -205,7 +210,7 @@ namespace osu.Game.Localisation
public static LocalisableString ToggleInGameInterface => new TranslatableString(getKey(@"toggle_in_game_interface"), @"Toggle in-game interface");
///
- /// "Toggle Mod Select"
+ /// "Toggle mod select"
///
public static LocalisableString ToggleModSelection => new TranslatableString(getKey(@"toggle_mod_selection"), @"Toggle mod select");
@@ -294,6 +299,6 @@ namespace osu.Game.Localisation
///
public static LocalisableString ToggleChatFocus => new TranslatableString(getKey(@"toggle_chat_focus"), @"Toggle chat focus");
- private static string getKey(string key) => $"{prefix}:{key}";
+ private static string getKey(string key) => $@"{prefix}:{key}";
}
}
diff --git a/osu.Game/Localisation/JoystickSettingsStrings.cs b/osu.Game/Localisation/JoystickSettingsStrings.cs
index 410cd0a6f5..976ec1adde 100644
--- a/osu.Game/Localisation/JoystickSettingsStrings.cs
+++ b/osu.Game/Localisation/JoystickSettingsStrings.cs
@@ -15,10 +15,10 @@ namespace osu.Game.Localisation
public static LocalisableString JoystickGamepad => new TranslatableString(getKey(@"joystick_gamepad"), @"Joystick / Gamepad");
///
- /// "Deadzone Threshold"
+ /// "Deadzone"
///
public static LocalisableString DeadzoneThreshold => new TranslatableString(getKey(@"deadzone_threshold"), @"Deadzone");
private static string getKey(string key) => $@"{prefix}:{key}";
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Localisation/LayoutSettingsStrings.cs b/osu.Game/Localisation/LayoutSettingsStrings.cs
index 5ac28f19b3..b4326b8e39 100644
--- a/osu.Game/Localisation/LayoutSettingsStrings.cs
+++ b/osu.Game/Localisation/LayoutSettingsStrings.cs
@@ -24,6 +24,11 @@ namespace osu.Game.Localisation
///
public static LocalisableString UnableToRunExclusiveFullscreen => new TranslatableString(getKey(@"unable_to_run_exclusive_fullscreen"), @"Unable to run exclusive fullscreen. You'll still experience some input latency.");
+ ///
+ /// "Using fullscreen on macOS makes interacting with the menu bar and spaces no longer work, and may lead to freezes if a system dialog is presented. Using borderless is recommended."
+ ///
+ public static LocalisableString FullscreenMacOSNote => new TranslatableString(getKey(@"fullscreen_macos_note"), @"Using fullscreen on macOS makes interacting with the menu bar and spaces no longer work, and may lead to freezes if a system dialog is presented. Using borderless is recommended.");
+
private static string getKey(string key) => $@"{prefix}:{key}";
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Localisation/MaintenanceSettingsStrings.cs b/osu.Game/Localisation/MaintenanceSettingsStrings.cs
index a0e1a9ddab..7a04bcd1ca 100644
--- a/osu.Game/Localisation/MaintenanceSettingsStrings.cs
+++ b/osu.Game/Localisation/MaintenanceSettingsStrings.cs
@@ -29,6 +29,11 @@ namespace osu.Game.Localisation
///
public static LocalisableString DeleteAllBeatmaps => new TranslatableString(getKey(@"delete_all_beatmaps"), @"Delete ALL beatmaps");
+ ///
+ /// "Delete ALL beatmap videos"
+ ///
+ public static LocalisableString DeleteAllBeatmapVideos => new TranslatableString(getKey(@"delete_all_beatmap_videos"), @"Delete ALL beatmap videos");
+
///
/// "Import scores from stable"
///
diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs
index 776ff5fd8f..451ea117d5 100644
--- a/osu.Game/Online/API/APIRequest.cs
+++ b/osu.Game/Online/API/APIRequest.cs
@@ -121,8 +121,16 @@ namespace osu.Game.Online.API
if (isFailing) return;
- Logger.Log($@"Performing request {this}", LoggingTarget.Network);
- WebRequest.Perform();
+ try
+ {
+ Logger.Log($@"Performing request {this}", LoggingTarget.Network);
+ WebRequest.Perform();
+ }
+ catch (OperationCanceledException)
+ {
+ // ignore this. internally Perform is running async and the fail state may have changed since
+ // the last check of `isFailing` above.
+ }
if (isFailing) return;
diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs
index 8356b36667..3b0d049528 100644
--- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs
+++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs
@@ -38,7 +38,6 @@ namespace osu.Game.Online.Chat
}
public DrawableLinkCompiler(IEnumerable parts)
- : base(HoverSampleSet.Submit)
{
Parts = parts.ToList();
}
diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs
index ca6082e19b..fbc5ef79ef 100644
--- a/osu.Game/Online/Chat/MessageNotifier.cs
+++ b/osu.Game/Online/Chat/MessageNotifier.cs
@@ -77,7 +77,7 @@ namespace osu.Game.Online.Chat
if (!messages.Any())
return;
- var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == messages.First().ChannelId);
+ var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id > 0 && c.Id == messages.First().ChannelId);
if (channel == null)
return;
diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs
index b7e1bc999b..f57ffcfe25 100644
--- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs
+++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs
@@ -155,9 +155,6 @@ namespace osu.Game.Online.Chat
{
public Func CreateChatLineAction;
- [Resolved]
- private OsuColour colours { get; set; }
-
public StandAloneDrawableChannel(Channel channel)
: base(channel)
{
@@ -166,25 +163,40 @@ namespace osu.Game.Online.Chat
[BackgroundDependencyLoader]
private void load()
{
+ // TODO: Remove once DrawableChannel & ChatLine padding is fixed
ChatLineFlow.Padding = new MarginPadding { Horizontal = 0 };
}
protected override ChatLine CreateChatLine(Message m) => CreateChatLineAction(m);
- protected override Drawable CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time)
+ protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new StandAloneDaySeparator(time);
+ }
+
+ protected class StandAloneDaySeparator : DaySeparator
+ {
+ protected override float TextSize => 14;
+ protected override float LineHeight => 1;
+ protected override float Spacing => 10;
+ protected override float DateAlign => 120;
+
+ public StandAloneDaySeparator(DateTimeOffset time)
+ : base(time)
{
- TextSize = 14,
- Colour = colours.Yellow,
- LineHeight = 1,
- Padding = new MarginPadding { Horizontal = 10 },
- Margin = new MarginPadding { Vertical = 5 },
- };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ Height = 25;
+ Colour = colours.Yellow;
+ // TODO: Remove once DrawableChannel & ChatLine padding is fixed
+ Padding = new MarginPadding { Horizontal = 10 };
+ }
}
protected class StandAloneMessage : ChatLine
{
protected override float TextSize => 15;
-
protected override float HorizontalPadding => 10;
protected override float MessagePadding => 120;
protected override float TimestampPadding => 50;
diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
index cae675b406..c95f3fa579 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
@@ -197,6 +197,7 @@ namespace osu.Game.Online.Multiplayer
APIRoom.Playlist.Clear();
APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem));
+ APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId);
Debug.Assert(LocalUser != null);
addUserToAPIRoom(LocalUser);
@@ -737,6 +738,7 @@ namespace osu.Game.Online.Multiplayer
APIRoom.Type.Value = Room.Settings.MatchType;
APIRoom.QueueMode.Value = Room.Settings.QueueMode;
APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration;
+ APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId);
RoomUpdated?.Invoke();
}
diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs
index 6ec884d79c..12091bae88 100644
--- a/osu.Game/Online/Rooms/PlaylistItem.cs
+++ b/osu.Game/Online/Rooms/PlaylistItem.cs
@@ -61,7 +61,13 @@ namespace osu.Game.Online.Rooms
/// Used for serialising to the API.
///
[JsonProperty("beatmap_id")]
- private int onlineBeatmapId => Beatmap.OnlineID;
+ private int onlineBeatmapId
+ {
+ get => Beatmap.OnlineID;
+ // This setter is only required for client-side serialise-then-deserialise operations.
+ // Serialisation is supposed to emit only a `beatmap_id`, but a (non-null) `beatmap` is required on deserialise.
+ set => Beatmap = new APIBeatmap { OnlineID = value };
+ }
///
/// A beatmap representing this playlist item.
diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs
index 60c0503ddd..1933267efd 100644
--- a/osu.Game/Online/Rooms/Room.cs
+++ b/osu.Game/Online/Rooms/Room.cs
@@ -162,6 +162,13 @@ namespace osu.Game.Online.Rooms
Password.BindValueChanged(p => HasPassword.Value = !string.IsNullOrEmpty(p.NewValue));
}
+ ///
+ /// Copies values from another into this one.
+ ///
+ ///
+ /// **Beware**: This will store references between s.
+ ///
+ /// The to copy values from.
public void CopyFrom(Room other)
{
RoomID.Value = other.RoomID.Value;
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 785881d97a..b91d523151 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using Humanizer;
using JetBrains.Annotations;
+using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
@@ -658,11 +659,14 @@ namespace osu.Game
}
protected override IDictionary GetFrameworkConfigDefaults()
- => new Dictionary
+ {
+ return new Dictionary
{
- // General expectation that osu! starts in fullscreen by default (also gives the most predictable performance)
- { FrameworkSetting.WindowMode, WindowMode.Fullscreen }
+ // General expectation that osu! starts in fullscreen by default (also gives the most predictable performance).
+ // However, macOS is bound to have issues when using exclusive fullscreen as it takes full control away from OS, therefore borderless is default there.
+ { FrameworkSetting.WindowMode, RuntimeInfo.OS == RuntimeInfo.Platform.macOS ? WindowMode.Borderless : WindowMode.Fullscreen }
};
+ }
protected override void LoadComplete()
{
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 666413004a..5dbdf6f602 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -80,6 +80,9 @@ namespace osu.Game
public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild;
+ internal EndpointConfiguration CreateEndpoints() =>
+ UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration();
+
public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version();
///
@@ -268,7 +271,7 @@ namespace osu.Game
dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler));
dependencies.CacheAs(SkinManager);
- EndpointConfiguration endpoints = UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration();
+ EndpointConfiguration endpoints = CreateEndpoints();
MessageFormatter.WebsiteRootUrl = endpoints.WebsiteRootUrl;
diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
index 1be1321d85..f4f958e4a4 100644
--- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
+++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
@@ -5,6 +5,7 @@ using System;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
+using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
@@ -69,7 +70,7 @@ namespace osu.Game.Overlays.AccountCreation
},
usernameTextBox = new OsuTextBox
{
- PlaceholderText = UsersStrings.LoginUsername,
+ PlaceholderText = UsersStrings.LoginUsername.ToLower(),
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this
},
@@ -91,7 +92,7 @@ namespace osu.Game.Overlays.AccountCreation
},
passwordTextBox = new OsuPasswordTextBox
{
- PlaceholderText = "password",
+ PlaceholderText = UsersStrings.LoginPassword.ToLower(),
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
},
diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs
index 52dfcad2cc..ff43170207 100644
--- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs
+++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Overlays.BeatmapListing
Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular),
Text = LabelFor(Value)
},
- new HoverClickSounds()
+ new HoverClickSounds(HoverSampleSet.TabSelect)
});
Enabled.Value = true;
diff --git a/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs b/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs
index 3b47adc4b7..a5d22f678e 100644
--- a/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs
+++ b/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs
@@ -6,10 +6,6 @@
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
namespace osu.Game.Overlays.Chat
@@ -24,85 +20,19 @@ namespace osu.Game.Overlays.Chat
[BackgroundDependencyLoader]
private void load()
{
+ // TODO: Remove once DrawableChannel & ChatLine padding is fixed
ChatLineFlow.Padding = new MarginPadding(0);
}
- protected override Drawable CreateDaySeparator(DateTimeOffset time) => new ChatOverlayDaySeparator(time);
+ protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new ChatOverlayDaySeparator(time);
- private class ChatOverlayDaySeparator : Container
+ private class ChatOverlayDaySeparator : DaySeparator
{
- private readonly DateTimeOffset time;
-
public ChatOverlayDaySeparator(DateTimeOffset time)
+ : base(time)
{
- this.time = time;
- }
-
- [BackgroundDependencyLoader]
- private void load(OverlayColourProvider colourProvider)
- {
- RelativeSizeAxes = Axes.X;
- AutoSizeAxes = Axes.Y;
- Padding = new MarginPadding { Horizontal = 15, Vertical = 20 };
- Child = new GridContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- ColumnDimensions = new[]
- {
- new Dimension(GridSizeMode.Absolute, 200),
- new Dimension(GridSizeMode.Absolute, 15),
- new Dimension(),
- },
- Content = new[]
- {
- new[]
- {
- new GridContainer
- {
- RelativeSizeAxes = Axes.Both,
- ColumnDimensions = new[]
- {
- new Dimension(),
- new Dimension(GridSizeMode.Absolute, 15),
- new Dimension(GridSizeMode.AutoSize),
- },
- Content = new[]
- {
- new[]
- {
- new Circle
- {
- Anchor = Anchor.CentreRight,
- Origin = Anchor.CentreRight,
- Colour = colourProvider.Background5,
- RelativeSizeAxes = Axes.X,
- Height = 2,
- },
- Drawable.Empty(),
- new OsuSpriteText
- {
- Anchor = Anchor.CentreRight,
- Origin = Anchor.CentreRight,
- Text = time.ToLocalTime().ToString("dd MMMM yyyy").ToUpper(),
- Font = OsuFont.Torus.With(size: 15, weight: FontWeight.SemiBold),
- Colour = colourProvider.Content1,
- },
- },
- },
- },
- Drawable.Empty(),
- new Circle
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Colour = colourProvider.Background5,
- RelativeSizeAxes = Axes.X,
- Height = 2,
- },
- },
- },
- };
+ // TODO: Remove once DrawableChannel & ChatLine padding is fixed
+ Padding = new MarginPadding { Horizontal = 15 };
}
}
}
diff --git a/osu.Game/Overlays/Chat/DaySeparator.cs b/osu.Game/Overlays/Chat/DaySeparator.cs
new file mode 100644
index 0000000000..2e3796151c
--- /dev/null
+++ b/osu.Game/Overlays/Chat/DaySeparator.cs
@@ -0,0 +1,105 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+#nullable enable
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+
+namespace osu.Game.Overlays.Chat
+{
+ public class DaySeparator : Container
+ {
+ protected virtual float TextSize => 15;
+
+ protected virtual float LineHeight => 2;
+
+ protected virtual float DateAlign => 200;
+
+ protected virtual float Spacing => 15;
+
+ private readonly DateTimeOffset time;
+
+ [Resolved(CanBeNull = true)]
+ private OverlayColourProvider? colourProvider { get; set; }
+
+ public DaySeparator(DateTimeOffset time)
+ {
+ this.time = time;
+ Height = 40;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ RelativeSizeAxes = Axes.X;
+ Child = new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ RowDimensions = new[] { new Dimension() },
+ ColumnDimensions = new[]
+ {
+ new Dimension(GridSizeMode.Absolute, DateAlign),
+ new Dimension(GridSizeMode.Absolute, Spacing),
+ new Dimension(),
+ },
+ Content = new[]
+ {
+ new[]
+ {
+ new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ RowDimensions = new[] { new Dimension() },
+ ColumnDimensions = new[]
+ {
+ new Dimension(),
+ new Dimension(GridSizeMode.Absolute, Spacing),
+ new Dimension(GridSizeMode.AutoSize),
+ },
+ Content = new[]
+ {
+ new[]
+ {
+ new Circle
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.X,
+ Height = LineHeight,
+ Colour = colourProvider?.Background5 ?? Colour4.White,
+ },
+ Drawable.Empty(),
+ new OsuSpriteText
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ Text = time.ToLocalTime().ToString("dd MMMM yyyy").ToUpper(),
+ Font = OsuFont.Torus.With(size: TextSize, weight: FontWeight.SemiBold),
+ Colour = colourProvider?.Content1 ?? Colour4.White,
+ },
+ }
+ },
+ },
+ Drawable.Empty(),
+ new Circle
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.X,
+ Height = LineHeight,
+ Colour = colourProvider?.Background5 ?? Colour4.White,
+ },
+ }
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs
index f2d4a3e301..f0d4d12f4f 100644
--- a/osu.Game/Overlays/Chat/DrawableChannel.cs
+++ b/osu.Game/Overlays/Chat/DrawableChannel.cs
@@ -7,14 +7,9 @@ using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Sprites;
-using osu.Game.Graphics;
using osu.Game.Graphics.Cursor;
-using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
using osuTK.Graphics;
@@ -40,9 +35,6 @@ namespace osu.Game.Overlays.Chat
}
}
- [Resolved]
- private OsuColour colours { get; set; }
-
public DrawableChannel(Channel channel)
{
Channel = channel;
@@ -67,7 +59,7 @@ namespace osu.Game.Overlays.Chat
Padding = new MarginPadding { Bottom = 5 },
Child = ChatLineFlow = new FillFlowContainer
{
- Padding = new MarginPadding { Left = 20, Right = 20 },
+ Padding = new MarginPadding { Horizontal = 15 },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
@@ -121,11 +113,7 @@ namespace osu.Game.Overlays.Chat
protected virtual ChatLine CreateChatLine(Message m) => new ChatLine(m);
- protected virtual Drawable CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time)
- {
- Colour = colours.ChatBlue.Lighten(0.7f),
- Margin = new MarginPadding { Vertical = 10 },
- };
+ protected virtual DaySeparator CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time);
private void newMessagesArrived(IEnumerable newMessages) => Schedule(() =>
{
@@ -203,69 +191,5 @@ namespace osu.Game.Overlays.Chat
});
private IEnumerable chatLines => ChatLineFlow.Children.OfType();
-
- public class DaySeparator : Container
- {
- public float TextSize
- {
- get => text.Font.Size;
- set => text.Font = text.Font.With(size: value);
- }
-
- private float lineHeight = 2;
-
- public float LineHeight
- {
- get => lineHeight;
- set => lineHeight = leftBox.Height = rightBox.Height = value;
- }
-
- private readonly SpriteText text;
- private readonly Box leftBox;
- private readonly Box rightBox;
-
- public DaySeparator(DateTimeOffset time)
- {
- RelativeSizeAxes = Axes.X;
- AutoSizeAxes = Axes.Y;
- Child = new GridContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- ColumnDimensions = new[]
- {
- new Dimension(),
- new Dimension(GridSizeMode.AutoSize),
- new Dimension(),
- },
- RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), },
- Content = new[]
- {
- new Drawable[]
- {
- leftBox = new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.X,
- Height = lineHeight,
- },
- text = new OsuSpriteText
- {
- Margin = new MarginPadding { Horizontal = 10 },
- Text = time.ToLocalTime().ToString("dd MMM yyyy"),
- },
- rightBox = new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.X,
- Height = lineHeight,
- },
- }
- }
- };
- }
- }
}
}
diff --git a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs
index 86c81d5d79..2a21d30a4a 100644
--- a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs
+++ b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs
@@ -6,6 +6,8 @@
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -16,6 +18,7 @@ using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Chat;
using osuTK;
@@ -32,6 +35,8 @@ namespace osu.Game.Overlays.Chat.Listing
public IEnumerable FilterTerms => new LocalisableString[] { Channel.Name, Channel.Topic ?? string.Empty };
public bool MatchingFilter { set => this.FadeTo(value ? 1f : 0f, 100); }
+ protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
+
private Box hoverBox = null!;
private SpriteIcon checkbox = null!;
private OsuSpriteText channelText = null!;
@@ -46,14 +51,20 @@ namespace osu.Game.Overlays.Chat.Listing
private const float vertical_margin = 1.5f;
+ private Sample? sampleJoin;
+ private Sample? sampleLeave;
+
public ChannelListingItem(Channel channel)
{
Channel = channel;
}
[BackgroundDependencyLoader]
- private void load()
+ private void load(AudioManager audio)
{
+ sampleJoin = audio.Samples.Get(@"UI/check-on");
+ sampleLeave = audio.Samples.Get(@"UI/check-off");
+
Masking = true;
CornerRadius = 5;
RelativeSizeAxes = Axes.X;
@@ -156,7 +167,19 @@ namespace osu.Game.Overlays.Chat.Listing
}
}, true);
- Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(Channel);
+ Action = () =>
+ {
+ if (channelJoined.Value)
+ {
+ OnRequestLeave?.Invoke(Channel);
+ sampleLeave?.Play();
+ }
+ else
+ {
+ OnRequestJoin?.Invoke(Channel);
+ sampleJoin?.Play();
+ }
+ };
}
protected override bool OnHover(HoverEvent e)
diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs
index 68d0704825..5ce0b9df9c 100644
--- a/osu.Game/Overlays/News/NewsCard.cs
+++ b/osu.Game/Overlays/News/NewsCard.cs
@@ -14,7 +14,6 @@ using osu.Framework.Platform;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
-using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Overlays.News
@@ -29,7 +28,6 @@ namespace osu.Game.Overlays.News
private TextFlowContainer main;
public NewsCard(APINewsPost post)
- : base(HoverSampleSet.Submit)
{
this.post = post;
diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs
index aa83f89689..e2ce25660e 100644
--- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs
+++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs
@@ -18,7 +18,6 @@ using System.Diagnostics;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Platform;
-using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.News.Sidebar
{
@@ -129,7 +128,6 @@ namespace osu.Game.Overlays.News.Sidebar
private readonly APINewsPost post;
public PostButton(APINewsPost post)
- : base(HoverSampleSet.Submit)
{
this.post = post;
diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs
index b4a5d5e31b..4cfdf5cc86 100644
--- a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs
+++ b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
private Sample sampleOpen;
private Sample sampleClose;
- protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
+ protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverClickSounds();
public ExpandDetailsButton()
{
diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs
index 94ef5e5d86..13465f3bf8 100644
--- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs
+++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs
@@ -6,7 +6,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Profile.Sections
{
@@ -18,7 +17,6 @@ namespace osu.Game.Overlays.Profile.Sections
private readonly IBeatmapInfo beatmapInfo;
protected BeatmapMetadataContainer(IBeatmapInfo beatmapInfo)
- : base(HoverSampleSet.Submit)
{
this.beatmapInfo = beatmapInfo;
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index d79ba593f7..05890ad882 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -4,6 +4,7 @@
using System;
using System.Drawing;
using System.Linq;
+using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
@@ -224,6 +225,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
private void updateScreenModeWarning()
{
+ if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS)
+ {
+ if (windowModeDropdown.Current.Value == WindowMode.Fullscreen)
+ windowModeDropdown.SetNoticeText(LayoutSettingsStrings.FullscreenMacOSNote, true);
+ else
+ windowModeDropdown.ClearNoticeText();
+
+ return;
+ }
+
if (windowModeDropdown.Current.Value != WindowMode.Fullscreen)
{
windowModeDropdown.SetNoticeText(GraphicsSettingsStrings.NotFullscreenNote, true);
diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
index be4b0decd9..054de8dbd7 100644
--- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
@@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
private SettingsButton deleteSkinsButton;
private SettingsButton restoreButton;
private SettingsButton undeleteButton;
+ private SettingsButton deleteBeatmapVideosButton;
[BackgroundDependencyLoader(permitNulls: true)]
private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay)
@@ -58,6 +59,19 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
}
});
+ Add(deleteBeatmapVideosButton = new DangerousSettingsButton
+ {
+ Text = MaintenanceSettingsStrings.DeleteAllBeatmapVideos,
+ Action = () =>
+ {
+ dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() =>
+ {
+ deleteBeatmapVideosButton.Enabled.Value = false;
+ Task.Run(beatmaps.DeleteAllVideos).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true));
+ }));
+ }
+ });
+
if (legacyImportManager?.SupportsImportFromStable == true)
{
Add(importScoresButton = new SettingsButton
diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs
new file mode 100644
index 0000000000..fc8c9d497b
--- /dev/null
+++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MassVideoDeleteConfirmationDialog.cs
@@ -0,0 +1,16 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+
+namespace osu.Game.Overlays.Settings.Sections.Maintenance
+{
+ public class MassVideoDeleteConfirmationDialog : MassDeleteConfirmationDialog
+ {
+ public MassVideoDeleteConfirmationDialog(Action deleteAction)
+ : base(deleteAction)
+ {
+ BodyText = "All beatmap videos? This cannot be undone!";
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs
index 4a839b048c..b686f11c13 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs
@@ -20,7 +20,6 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
-using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osuTK;
using osuTK.Graphics;
@@ -83,7 +82,6 @@ namespace osu.Game.Overlays.Toolbar
private RealmAccess realm { get; set; }
protected ToolbarButton()
- : base(HoverSampleSet.Toolbar)
{
Width = Toolbar.HEIGHT;
RelativeSizeAxes = Axes.Y;
diff --git a/osu.Game/Overlays/Toolbar/ToolbarClock.cs b/osu.Game/Overlays/Toolbar/ToolbarClock.cs
index 308359570f..12529da07f 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarClock.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarClock.cs
@@ -11,7 +11,6 @@ using osu.Framework.Input.Events;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
@@ -29,7 +28,6 @@ namespace osu.Game.Overlays.Toolbar
private AnalogClockDisplay analog;
public ToolbarClock()
- : base(HoverSampleSet.Toolbar)
{
RelativeSizeAxes = Axes.Y;
AutoSizeAxes = Axes.X;
diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs
index 929c362bd8..24d9f785f2 100644
--- a/osu.Game/Overlays/Volume/VolumeMeter.cs
+++ b/osu.Game/Overlays/Volume/VolumeMeter.cs
@@ -15,7 +15,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
-using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Framework.Utils;
@@ -28,7 +27,7 @@ using osuTK.Graphics;
namespace osu.Game.Overlays.Volume
{
- public class VolumeMeter : Container, IKeyBindingHandler, IStateful
+ public class VolumeMeter : Container, IStateful
{
private CircularProgress volumeCircle;
private CircularProgress volumeCircleGlow;
@@ -80,7 +79,7 @@ namespace osu.Game.Overlays.Volume
[BackgroundDependencyLoader]
private void load(OsuColour colours, AudioManager audio)
{
- hoverSample = audio.Samples.Get($"UI/{HoverSampleSet.Button.GetDescription()}-hover");
+ hoverSample = audio.Samples.Get($@"UI/{HoverSampleSet.Button.GetDescription()}-hover");
notchSample = audio.Samples.Get(@"UI/notch-tick");
sampleLastPlaybackTime = Time.Current;
@@ -132,7 +131,7 @@ namespace osu.Game.Overlays.Volume
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Name = "Progress under covers for smoothing",
+ Name = @"Progress under covers for smoothing",
RelativeSizeAxes = Axes.Both,
Rotation = 180,
Child = volumeCircle = new CircularProgress
@@ -144,7 +143,7 @@ namespace osu.Game.Overlays.Volume
},
new Circle
{
- Name = "Inner Cover",
+ Name = @"Inner Cover",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
@@ -153,7 +152,7 @@ namespace osu.Game.Overlays.Volume
},
new Container
{
- Name = "Progress overlay for glow",
+ Name = @"Progress overlay for glow",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
@@ -365,27 +364,6 @@ namespace osu.Game.Overlays.Volume
{
}
- public bool OnPressed(KeyBindingPressEvent e)
- {
- if (!IsHovered)
- return false;
-
- switch (e.Action)
- {
- case GlobalAction.SelectPreviousGroup:
- State = SelectionState.Selected;
- adjust(1, false);
- return true;
-
- case GlobalAction.SelectNextGroup:
- State = SelectionState.Selected;
- adjust(-1, false);
- return true;
- }
-
- return false;
- }
-
public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs
index a96949e96f..9d2ed3f837 100644
--- a/osu.Game/Overlays/VolumeOverlay.cs
+++ b/osu.Game/Overlays/VolumeOverlay.cs
@@ -17,6 +17,7 @@ using osu.Game.Input.Bindings;
using osu.Game.Overlays.Volume;
using osuTK;
using osuTK.Graphics;
+using osuTK.Input;
namespace osu.Game.Overlays
{
@@ -171,6 +172,30 @@ namespace osu.Game.Overlays
return base.OnMouseMove(e);
}
+ protected override bool OnKeyDown(KeyDownEvent e)
+ {
+ switch (e.Key)
+ {
+ case Key.Left:
+ Adjust(GlobalAction.PreviousVolumeMeter);
+ return true;
+
+ case Key.Right:
+ Adjust(GlobalAction.NextVolumeMeter);
+ return true;
+
+ case Key.Down:
+ Adjust(GlobalAction.DecreaseVolume);
+ return true;
+
+ case Key.Up:
+ Adjust(GlobalAction.IncreaseVolume);
+ return true;
+ }
+
+ return base.OnKeyDown(e);
+ }
+
protected override bool OnHover(HoverEvent e)
{
schedulePopOut();
diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
index 4a5897c621..df094ddb7c 100644
--- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
@@ -460,6 +460,7 @@ namespace osu.Game.Rulesets.Scoring
currentMaximumScoringValues.BaseScore = maximum.BaseScore;
currentMaximumScoringValues.MaxCombo = maximum.MaxCombo;
+ Combo.Value = frame.Header.Combo;
HighestCombo.Value = frame.Header.MaxCombo;
scoreResultCounts.Clear();
diff --git a/osu.Game/Screens/Edit/Components/EditorSidebar.cs b/osu.Game/Screens/Edit/Components/EditorSidebar.cs
index 4edcef41b1..4e9b1d5222 100644
--- a/osu.Game/Screens/Edit/Components/EditorSidebar.cs
+++ b/osu.Game/Screens/Edit/Components/EditorSidebar.cs
@@ -16,13 +16,15 @@ namespace osu.Game.Screens.Edit.Components
///
internal class EditorSidebar : Container
{
+ public const float WIDTH = 250;
+
private readonly Box background;
protected override Container Content { get; }
public EditorSidebar()
{
- Width = 250;
+ Width = WIDTH;
RelativeSizeAxes = Axes.Y;
InternalChildren = new Drawable[]
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs
index 5550c6a748..e0b21b2e22 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs
@@ -1,99 +1,29 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
-using osu.Framework.Bindables;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Input.Events;
-using osu.Framework.Threading;
-using osu.Game.Graphics;
+using osu.Framework.Allocation;
using osu.Game.Graphics.UserInterface;
-using osuTK;
-using osuTK.Graphics;
-using osuTK.Input;
+using osu.Game.Overlays;
+using osu.Game.Screens.Edit.Timing;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
- public class TimelineButton : CompositeDrawable
+ public class TimelineButton : IconButton
{
- public Action Action;
- public readonly BindableBool Enabled = new BindableBool(true);
-
- public IconUsage Icon
+ [BackgroundDependencyLoader]
+ private void load(OverlayColourProvider colourProvider)
{
- get => button.Icon;
- set => button.Icon = value;
+ // These are using colourProvider but don't match the design.
+ // Just something to fit until someone implements the updated design.
+ IconColour = colourProvider.Background1;
+ IconHoverColour = colourProvider.Content2;
+
+ HoverColour = colourProvider.Background1;
+ FlashColour = colourProvider.Content2;
+
+ Add(new RepeatingButtonBehaviour(this));
}
- private readonly IconButton button;
-
- public TimelineButton()
- {
- InternalChild = button = new TimelineIconButton { Action = () => Action?.Invoke() };
-
- button.Enabled.BindTo(Enabled);
- Width = button.Width;
- }
-
- protected override void Update()
- {
- base.Update();
-
- button.Size = new Vector2(button.Width, DrawHeight);
- }
-
- private class TimelineIconButton : IconButton
- {
- public TimelineIconButton()
- {
- Anchor = Anchor.Centre;
- Origin = Anchor.Centre;
- IconColour = OsuColour.Gray(0.35f);
- IconHoverColour = Color4.White;
- HoverColour = OsuColour.Gray(0.25f);
- FlashColour = OsuColour.Gray(0.5f);
- }
-
- private ScheduledDelegate repeatSchedule;
-
- ///
- /// The initial delay before mouse down repeat begins.
- ///
- private const int repeat_initial_delay = 250;
-
- ///
- /// The delay between mouse down repeats after the initial repeat.
- ///
- private const int repeat_tick_rate = 70;
-
- protected override bool OnClick(ClickEvent e)
- {
- // don't actuate a click since we are manually handling repeats.
- return true;
- }
-
- protected override bool OnMouseDown(MouseDownEvent e)
- {
- if (e.Button == MouseButton.Left)
- {
- Action clickAction = () => base.OnClick(new ClickEvent(e.CurrentState, e.Button));
-
- // run once for initial down
- clickAction();
-
- Scheduler.Add(repeatSchedule = new ScheduledDelegate(clickAction, Clock.CurrentTime + repeat_initial_delay, repeat_tick_rate));
- }
-
- return base.OnMouseDown(e);
- }
-
- protected override void OnMouseUp(MouseUpEvent e)
- {
- repeatSchedule?.Cancel();
- base.OnMouseUp(e);
- }
- }
+ protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
}
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs
index 35d103ddf1..d008368b69 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs
@@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Input.Events;
+using osu.Framework.Layout;
using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Graphics.Containers;
@@ -40,10 +41,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
[Resolved(canBeNull: true)]
private IFrameBasedClock editorClock { get; set; }
+ private readonly LayoutValue zoomedContentWidthCache = new LayoutValue(Invalidation.DrawSize);
+
public ZoomableScrollContainer()
: base(Direction.Horizontal)
{
base.Content.Add(zoomedContent = new Container { RelativeSizeAxes = Axes.Y });
+
+ AddLayout(zoomedContentWidthCache);
}
private float minZoom = 1;
@@ -103,12 +108,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
}
}
- protected override void LoadComplete()
+ protected override void Update()
{
- base.LoadComplete();
+ base.Update();
- // This width only gets updated on the application of a transform, so this needs to be initialized here.
- updateZoomedContentWidth();
+ if (!zoomedContentWidthCache.IsValid)
+ updateZoomedContentWidth();
}
protected override bool OnScroll(ScrollEvent e)
@@ -128,7 +133,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
return base.OnScroll(e);
}
- private void updateZoomedContentWidth() => zoomedContent.Width = DrawWidth * currentZoom;
+ private void updateZoomedContentWidth()
+ {
+ zoomedContent.Width = DrawWidth * currentZoom;
+ zoomedContentWidthCache.Validate();
+ }
private float zoomTarget = 1;
@@ -199,8 +208,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset;
d.currentZoom = newZoom;
-
d.updateZoomedContentWidth();
+
// Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area.
// TODO: Make sure draw size gets invalidated properly on the framework side, and remove this once it is.
d.Invalidate(Invalidation.DrawSize);
diff --git a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs
index aae19396db..fd916894ea 100644
--- a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs
+++ b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+#nullable enable
+
using System;
using System.Collections.Generic;
using System.IO;
@@ -9,6 +11,7 @@ using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
@@ -27,10 +30,10 @@ namespace osu.Game.Screens.Edit.Setup
public IEnumerable HandledExtensions => handledExtensions;
- private readonly Bindable currentFile = new Bindable();
+ private readonly Bindable currentFile = new Bindable();
[Resolved]
- private OsuGameBase game { get; set; }
+ private OsuGameBase game { get; set; } = null!;
public FileChooserLabelledTextBox(params string[] handledExtensions)
{
@@ -45,7 +48,7 @@ namespace osu.Game.Screens.Edit.Setup
currentFile.BindValueChanged(onFileSelected);
}
- private void onFileSelected(ValueChangedEvent file)
+ private void onFileSelected(ValueChangedEvent file)
{
if (file.NewValue == null)
return;
@@ -65,14 +68,16 @@ namespace osu.Game.Screens.Edit.Setup
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
- game.UnregisterImportHandler(this);
+
+ if (game.IsNotNull())
+ game.UnregisterImportHandler(this);
}
public override Popover GetPopover() => new FileChooserPopover(handledExtensions, currentFile);
private class FileChooserPopover : OsuPopover
{
- public FileChooserPopover(string[] handledExtensions, Bindable currentFile)
+ public FileChooserPopover(string[] handledExtensions, Bindable currentFile)
{
Child = new Container
{
diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs
index bb2dd35a9c..f613488aae 100644
--- a/osu.Game/Screens/Edit/Timing/GroupSection.cs
+++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Screens.Edit.Timing
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
- Padding = new MarginPadding(10);
+ Padding = new MarginPadding(10) { Bottom = 0 };
InternalChildren = new Drawable[]
{
diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs
index 4143c5ea55..2ecd66a05f 100644
--- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs
+++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs
@@ -38,6 +38,8 @@ namespace osu.Game.Screens.Edit.Timing
[Resolved]
private OverlayColourProvider overlayColourProvider { get; set; }
+ public bool EnableClicking { get; set; } = true;
+
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
@@ -281,6 +283,9 @@ namespace osu.Game.Screens.Edit.Timing
Schedule(() =>
{
+ if (!EnableClicking)
+ return;
+
var channel = clunk?.GetChannel();
if (channel != null)
diff --git a/osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs b/osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs
new file mode 100644
index 0000000000..595305b20f
--- /dev/null
+++ b/osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs
@@ -0,0 +1,92 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
+using osu.Framework.Threading;
+
+namespace osu.Game.Screens.Edit.Timing
+{
+ ///
+ /// Represents a component that provides the behaviour of triggering button clicks repeatedly while holding with mouse.
+ ///
+ public class RepeatingButtonBehaviour : Component
+ {
+ private const double initial_delay = 300;
+ private const double minimum_delay = 80;
+
+ private readonly Drawable button;
+
+ private Sample sample;
+
+ ///
+ /// An additive modifier for the frequency of the sample played on next actuation.
+ /// This can be adjusted during the button's event to affect the repeat sample playback of that click.
+ ///
+ public double SampleFrequencyModifier { get; set; }
+
+ public RepeatingButtonBehaviour(Drawable button)
+ {
+ this.button = button;
+
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio)
+ {
+ sample = audio.Samples.Get(@"UI/notch-tick");
+ }
+
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ beginRepeat();
+ return true;
+ }
+
+ protected override void OnMouseUp(MouseUpEvent e)
+ {
+ adjustDelegate?.Cancel();
+ base.OnMouseUp(e);
+ }
+
+ private ScheduledDelegate adjustDelegate;
+ private double adjustDelay = initial_delay;
+
+ private void beginRepeat()
+ {
+ adjustDelegate?.Cancel();
+
+ adjustDelay = initial_delay;
+ adjustNext();
+
+ void adjustNext()
+ {
+ if (IsHovered)
+ {
+ button.TriggerClick();
+ adjustDelay = Math.Max(minimum_delay, adjustDelay * 0.9f);
+
+ var channel = sample?.GetChannel();
+
+ if (channel != null)
+ {
+ double repeatModifier = 0.05f * (Math.Abs(adjustDelay - initial_delay) / minimum_delay);
+ channel.Frequency.Value = 1 + repeatModifier + SampleFrequencyModifier;
+ channel.Play();
+ }
+ }
+ else
+ {
+ adjustDelay = initial_delay;
+ }
+
+ adjustDelegate = Scheduler.AddDelayed(adjustNext, adjustDelay);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/Timing/Section.cs b/osu.Game/Screens/Edit/Timing/Section.cs
index 139abfb187..17147c21f4 100644
--- a/osu.Game/Screens/Edit/Timing/Section.cs
+++ b/osu.Game/Screens/Edit/Timing/Section.cs
@@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit.Timing
{
Flow = new FillFlowContainer
{
- Padding = new MarginPadding(20),
+ Padding = new MarginPadding(10) { Top = 0 },
Spacing = new Vector2(20),
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs
new file mode 100644
index 0000000000..a6227cbe27
--- /dev/null
+++ b/osu.Game/Screens/Edit/Timing/TapButton.cs
@@ -0,0 +1,418 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Framework.Threading;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Input.Bindings;
+using osu.Game.Overlays;
+using osuTK;
+using osuTK.Graphics;
+using osuTK.Input;
+
+namespace osu.Game.Screens.Edit.Timing
+{
+ internal class TapButton : CircularContainer, IKeyBindingHandler
+ {
+ public const float SIZE = 140;
+
+ public readonly BindableBool IsHandlingTapping = new BindableBool();
+
+ [Resolved]
+ private OverlayColourProvider colourProvider { get; set; } = null!;
+
+ [Resolved(canBeNull: true)]
+ private Bindable? selectedGroup { get; set; }
+
+ [Resolved(canBeNull: true)]
+ private IBeatSyncProvider? beatSyncSource { get; set; }
+
+ private Circle hoverLayer = null!;
+
+ private CircularContainer innerCircle = null!;
+ private Box innerCircleHighlight = null!;
+
+ private int currentLight;
+
+ private Container scaleContainer = null!;
+ private Container lights = null!;
+ private Container lightsGlow = null!;
+ private OsuSpriteText bpmText = null!;
+ private Container textContainer = null!;
+
+ private bool grabbedMouseDown;
+
+ private ScheduledDelegate? resetDelegate;
+
+ private const int light_count = 8;
+
+ private const int initial_taps_to_ignore = 4;
+
+ private const int max_taps_to_consider = 128;
+
+ private const double transition_length = 500;
+
+ private const float angular_light_gap = 0.007f;
+
+ private readonly List tapTimings = new List();
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Size = new Vector2(SIZE);
+
+ const float ring_width = 22;
+ const float light_padding = 3;
+
+ InternalChild = scaleContainer = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Circle
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = colourProvider.Background4
+ },
+ lights = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ new CircularContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Name = @"outer masking",
+ Masking = true,
+ BorderThickness = light_padding,
+ BorderColour = colourProvider.Background4,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Black,
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true,
+ },
+ }
+ },
+ new Circle
+ {
+ Name = @"inner masking",
+ Size = new Vector2(SIZE - ring_width * 2 + light_padding * 2),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Colour = colourProvider.Background4,
+ },
+ lightsGlow = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ innerCircle = new CircularContainer
+ {
+ Size = new Vector2(SIZE - ring_width * 2),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Masking = true,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = colourProvider.Background2,
+ RelativeSizeAxes = Axes.Both,
+ },
+ innerCircleHighlight = new Box
+ {
+ Colour = colourProvider.Colour3,
+ Blending = BlendingParameters.Additive,
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ },
+ textContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = colourProvider.Background1,
+ Children = new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Font = OsuFont.Torus.With(size: 34, weight: FontWeight.SemiBold),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.BottomCentre,
+ Y = 5,
+ Text = "Tap",
+ },
+ bpmText = new OsuSpriteText
+ {
+ Font = OsuFont.Torus.With(size: 23, weight: FontWeight.Regular),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.TopCentre,
+ Y = -1,
+ },
+ }
+ },
+ hoverLayer = new Circle
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = colourProvider.Background1.Opacity(0.3f),
+ Blending = BlendingParameters.Additive,
+ Alpha = 0,
+ },
+ }
+ },
+ }
+ };
+
+ for (int i = 0; i < light_count; i++)
+ {
+ var light = new Light
+ {
+ Rotation = (i + 1) * (360f / light_count) + 360 * angular_light_gap / 2,
+ };
+
+ lights.Add(light);
+ lightsGlow.Add(light.Glow.CreateProxy());
+ }
+
+ reset();
+ }
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
+ hoverLayer.ReceivePositionalInputAt(screenSpacePos);
+
+ private ColourInfo textColour
+ {
+ get
+ {
+ if (grabbedMouseDown)
+ return colourProvider.Background4;
+
+ if (IsHovered)
+ return colourProvider.Content2;
+
+ return colourProvider.Background1;
+ }
+ }
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ hoverLayer.FadeIn(transition_length, Easing.OutQuint);
+ textContainer.FadeColour(textColour, transition_length, Easing.OutQuint);
+ return true;
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ hoverLayer.FadeOut(transition_length, Easing.OutQuint);
+ textContainer.FadeColour(textColour, transition_length, Easing.OutQuint);
+ base.OnHoverLost(e);
+ }
+
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ const double in_duration = 100;
+
+ grabbedMouseDown = true;
+ IsHandlingTapping.Value = true;
+
+ resetDelegate?.Cancel();
+
+ handleTap();
+
+ textContainer.FadeColour(textColour, in_duration, Easing.OutQuint);
+
+ scaleContainer.ScaleTo(0.99f, in_duration, Easing.OutQuint);
+ innerCircle.ScaleTo(0.96f, in_duration, Easing.OutQuint);
+
+ innerCircleHighlight
+ .FadeIn(50, Easing.OutQuint)
+ .FlashColour(Color4.White, 1000, Easing.OutQuint);
+
+ lights[currentLight % light_count].Hide();
+ lights[(currentLight + light_count / 2) % light_count].Hide();
+
+ currentLight++;
+
+ lights[currentLight % light_count].Show();
+ lights[(currentLight + light_count / 2) % light_count].Show();
+
+ return true;
+ }
+
+ protected override void OnMouseUp(MouseUpEvent e)
+ {
+ const double out_duration = 800;
+
+ grabbedMouseDown = false;
+
+ textContainer.FadeColour(textColour, out_duration, Easing.OutQuint);
+
+ scaleContainer.ScaleTo(1, out_duration, Easing.OutQuint);
+ innerCircle.ScaleTo(1, out_duration, Easing.OutQuint);
+
+ innerCircleHighlight.FadeOut(out_duration, Easing.OutQuint);
+
+ resetDelegate = Scheduler.AddDelayed(reset, 1000);
+
+ base.OnMouseUp(e);
+ }
+
+ public bool OnPressed(KeyBindingPressEvent e)
+ {
+ if (e.Action == GlobalAction.EditorTapForBPM && !e.Repeat)
+ {
+ // Direct through mouse handling to achieve animation
+ OnMouseDown(new MouseDownEvent(e.CurrentState, MouseButton.Left));
+ return true;
+ }
+
+ return false;
+ }
+
+ public void OnReleased(KeyBindingReleaseEvent e)
+ {
+ if (e.Action == GlobalAction.EditorTapForBPM)
+ OnMouseUp(new MouseUpEvent(e.CurrentState, MouseButton.Left));
+ }
+
+ private void handleTap()
+ {
+ tapTimings.Add(Clock.CurrentTime);
+
+ if (tapTimings.Count > initial_taps_to_ignore + max_taps_to_consider)
+ tapTimings.RemoveAt(0);
+
+ if (tapTimings.Count < initial_taps_to_ignore * 2)
+ {
+ bpmText.Text = new string('.', tapTimings.Count);
+ return;
+ }
+
+ double averageBeatLength = (tapTimings.Last() - tapTimings.Skip(initial_taps_to_ignore).First()) / (tapTimings.Count - initial_taps_to_ignore - 1);
+ double clockRate = beatSyncSource?.Clock?.Rate ?? 1;
+
+ double bpm = Math.Round(60000 / averageBeatLength / clockRate);
+
+ bpmText.Text = $"{bpm} BPM";
+
+ var timingPoint = selectedGroup?.Value.ControlPoints.OfType().FirstOrDefault();
+
+ if (timingPoint != null)
+ {
+ // Intentionally use the rounded BPM here.
+ timingPoint.BeatLength = 60000 / bpm;
+ }
+ }
+
+ private void reset()
+ {
+ bpmText.FadeOut(transition_length, Easing.OutQuint);
+
+ using (BeginDelayedSequence(tapTimings.Count > 0 ? transition_length : 0))
+ {
+ Schedule(() => bpmText.Text = "the beat!");
+ bpmText.FadeIn(800, Easing.OutQuint);
+ }
+
+ foreach (var light in lights)
+ light.Hide();
+
+ tapTimings.Clear();
+ currentLight = 0;
+ IsHandlingTapping.Value = false;
+ }
+
+ private class Light : CompositeDrawable
+ {
+ public Drawable Glow { get; private set; } = null!;
+
+ private Container fillContent = null!;
+
+ [Resolved]
+ private OverlayColourProvider colourProvider { get; set; } = null!;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ RelativeSizeAxes = Axes.Both;
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ Size = new Vector2(0.98f); // Avoid bleed into masking edge.
+
+ InternalChildren = new Drawable[]
+ {
+ new CircularProgress
+ {
+ RelativeSizeAxes = Axes.Both,
+ Current = { Value = 1f / light_count - angular_light_gap },
+ Colour = colourProvider.Background2,
+ },
+ fillContent = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ Colour = colourProvider.Colour1,
+ Children = new[]
+ {
+ new CircularProgress
+ {
+ RelativeSizeAxes = Axes.Both,
+ Current = { Value = 1f / light_count - angular_light_gap },
+ Blending = BlendingParameters.Additive
+ },
+ // Please do not try and make sense of this.
+ // Getting the visual effect I was going for relies on what I can only imagine is broken implementation
+ // of `PadExtent`. If that's ever fixed in the future this will likely need to be adjusted.
+ Glow = new CircularProgress
+ {
+ RelativeSizeAxes = Axes.Both,
+ Current = { Value = 1f / light_count - 0.01f },
+ Blending = BlendingParameters.Additive
+ }.WithEffect(new GlowEffect
+ {
+ Colour = colourProvider.Colour1.Opacity(0.4f),
+ BlurSigma = new Vector2(9f),
+ Strength = 10,
+ PadExtent = true
+ }),
+ }
+ },
+ };
+ }
+
+ public override void Show()
+ {
+ fillContent
+ .FadeIn(50, Easing.OutQuint)
+ .FlashColour(Color4.White, 1000, Easing.OutQuint);
+ }
+
+ public override void Hide()
+ {
+ fillContent
+ .FadeOut(300, Easing.OutQuint);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs
index 990f8d2ce0..9b5574d3cb 100644
--- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs
+++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs
@@ -1,33 +1,45 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+#nullable enable
+
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Events;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays;
+using osuTK;
namespace osu.Game.Screens.Edit.Timing
{
public class TapTimingControl : CompositeDrawable
{
[Resolved]
- private EditorClock editorClock { get; set; }
+ private EditorClock editorClock { get; set; } = null!;
[Resolved]
- private EditorBeatmap beatmap { get; set; }
+ private EditorBeatmap beatmap { get; set; } = null!;
[Resolved]
- private Bindable selectedGroup { get; set; }
+ private Bindable selectedGroup { get; set; } = null!;
+
+ private readonly BindableBool isHandlingTapping = new BindableBool();
+
+ private MetronomeDisplay metronome = null!;
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider, OsuColour colours)
{
+ const float padding = 10;
+
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
@@ -48,8 +60,8 @@ namespace osu.Game.Screens.Edit.Timing
RowDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 200),
- new Dimension(GridSizeMode.Absolute, 60),
- new Dimension(GridSizeMode.Absolute, 60),
+ new Dimension(GridSizeMode.Absolute, 50),
+ new Dimension(GridSizeMode.Absolute, TapButton.SIZE + padding),
},
Content = new[]
{
@@ -58,6 +70,7 @@ namespace osu.Game.Screens.Edit.Timing
new Container
{
RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding(padding),
Children = new Drawable[]
{
new GridContainer
@@ -72,7 +85,7 @@ namespace osu.Game.Screens.Edit.Timing
{
new Drawable[]
{
- new MetronomeDisplay
+ metronome = new MetronomeDisplay
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
@@ -89,15 +102,14 @@ namespace osu.Game.Screens.Edit.Timing
new Container
{
RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding(10),
+ Padding = new MarginPadding { Bottom = padding, Horizontal = padding },
Children = new Drawable[]
{
new TimingAdjustButton(1)
{
Text = "Offset",
- RelativeSizeAxes = Axes.X,
- Width = 0.48f,
- Height = 50,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.48f, 1),
Action = adjustOffset,
},
new TimingAdjustButton(0.1)
@@ -105,9 +117,8 @@ namespace osu.Game.Screens.Edit.Timing
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Text = "BPM",
- RelativeSizeAxes = Axes.X,
- Width = 0.48f,
- Height = 50,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.48f, 1),
Action = adjustBpm,
}
}
@@ -118,33 +129,70 @@ namespace osu.Game.Screens.Edit.Timing
new Container
{
RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding(10),
+ Padding = new MarginPadding { Bottom = padding, Horizontal = padding },
Children = new Drawable[]
{
- new RoundedButton
+ new Container
{
- Text = "Reset",
- BackgroundColour = colours.Pink,
- RelativeSizeAxes = Axes.X,
- Width = 0.3f,
- Action = reset,
+ RelativeSizeAxes = Axes.Y,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.CentreRight,
+ Height = 0.98f,
+ Width = TapButton.SIZE / 1.3f,
+ Masking = true,
+ CornerRadius = 15,
+ Children = new Drawable[]
+ {
+ new InlineButton(FontAwesome.Solid.Stop, Anchor.TopLeft)
+ {
+ BackgroundColour = colourProvider.Background1,
+ RelativeSizeAxes = Axes.Both,
+ Height = 0.49f,
+ Action = reset,
+ },
+ new InlineButton(FontAwesome.Solid.Play, Anchor.BottomLeft)
+ {
+ BackgroundColour = colourProvider.Background1,
+ RelativeSizeAxes = Axes.Both,
+ Height = 0.49f,
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Action = start,
+ },
+ },
},
- new RoundedButton
+ new TapButton
{
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- Text = "Play from start",
- RelativeSizeAxes = Axes.X,
- BackgroundColour = colourProvider.Background1,
- Width = 0.68f,
- Action = tap,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ IsHandlingTapping = { BindTarget = isHandlingTapping }
}
}
},
- }
+ },
}
},
};
+
+ isHandlingTapping.BindValueChanged(handling =>
+ {
+ metronome.EnableClicking = !handling.NewValue;
+
+ if (handling.NewValue)
+ start();
+ }, true);
+ }
+
+ private void start()
+ {
+ editorClock.Seek(selectedGroup.Value.Time);
+ editorClock.Start();
+ }
+
+ private void reset()
+ {
+ editorClock.Stop();
+ editorClock.Seek(selectedGroup.Value.Time);
}
private void adjustOffset(double adjust)
@@ -176,16 +224,66 @@ namespace osu.Game.Screens.Edit.Timing
timing.BeatLength = 60000 / (timing.BPM + adjust);
}
- private void tap()
+ private class InlineButton : OsuButton
{
- editorClock.Seek(selectedGroup.Value.Time);
- editorClock.Start();
- }
+ private readonly IconUsage icon;
+ private readonly Anchor anchor;
- private void reset()
- {
- editorClock.Stop();
- editorClock.Seek(selectedGroup.Value.Time);
+ private SpriteIcon spriteIcon = null!;
+
+ [Resolved]
+ private OverlayColourProvider colourProvider { get; set; } = null!;
+
+ public InlineButton(IconUsage icon, Anchor anchor)
+ {
+ this.icon = icon;
+ this.anchor = anchor;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Content.CornerRadius = 0;
+ Content.Masking = false;
+
+ BackgroundColour = colourProvider.Background2;
+
+ Content.Add(new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding(15),
+ Children = new Drawable[]
+ {
+ spriteIcon = new SpriteIcon
+ {
+ Icon = icon,
+ Size = new Vector2(22),
+ Anchor = anchor,
+ Origin = anchor,
+ Colour = colourProvider.Background1,
+ },
+ }
+ });
+ }
+
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ // scale looks bad so don't call base.
+ return false;
+ }
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ spriteIcon.FadeColour(colourProvider.Content2, 200, Easing.OutQuint);
+ return base.OnHover(e);
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ spriteIcon.FadeColour(colourProvider.Background1, 200, Easing.OutQuint);
+ base.OnHoverLost(e);
+ }
}
}
}
diff --git a/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs
index 9fc7e56a3d..9540547d89 100644
--- a/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs
+++ b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs
@@ -4,14 +4,11 @@
using System;
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Audio;
-using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
-using osu.Framework.Threading;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
@@ -26,32 +23,24 @@ namespace osu.Game.Screens.Edit.Timing
public Action Action;
private readonly double adjustAmount;
- private ScheduledDelegate adjustDelegate;
private const int max_multiplier = 10;
-
private const int adjust_levels = 4;
- private const double initial_delay = 300;
-
- private const double minimum_delay = 80;
-
public Container Content { get; set; }
- private double adjustDelay = initial_delay;
-
private readonly Box background;
private readonly OsuSpriteText text;
- private Sample sample;
-
public LocalisableString Text
{
get => text.Text;
set => text.Text = value;
}
+ private readonly RepeatingButtonBehaviour repeatBehaviour;
+
[Resolved]
private OverlayColourProvider colourProvider { get; set; }
@@ -82,13 +71,13 @@ namespace osu.Game.Screens.Edit.Timing
}
}
});
+
+ AddInternal(repeatBehaviour = new RepeatingButtonBehaviour(this));
}
[BackgroundDependencyLoader]
- private void load(AudioManager audio)
+ private void load()
{
- sample = audio.Samples.Get(@"UI/notch-tick");
-
background.Colour = colourProvider.Background3;
for (int i = 1; i <= adjust_levels; i++)
@@ -98,57 +87,22 @@ namespace osu.Game.Screens.Edit.Timing
}
}
- protected override bool OnMouseDown(MouseDownEvent e)
+ protected override bool OnHover(HoverEvent e) => true;
+
+ protected override bool OnClick(ClickEvent e)
{
- beginRepeat();
+ var hoveredBox = Content.OfType().FirstOrDefault(d => d.IsHovered);
+ if (hoveredBox == null)
+ return false;
+
+ Action(adjustAmount * hoveredBox.Multiplier);
+
+ hoveredBox.Flash();
+
+ repeatBehaviour.SampleFrequencyModifier = (hoveredBox.Multiplier / max_multiplier) * 0.2;
return true;
}
- protected override void OnMouseUp(MouseUpEvent e)
- {
- adjustDelegate?.Cancel();
- base.OnMouseUp(e);
- }
-
- private void beginRepeat()
- {
- adjustDelegate?.Cancel();
-
- adjustDelay = initial_delay;
- adjustNext();
-
- void adjustNext()
- {
- var hoveredBox = Content.OfType().FirstOrDefault(d => d.IsHovered);
-
- if (hoveredBox != null)
- {
- Action(adjustAmount * hoveredBox.Multiplier);
-
- adjustDelay = Math.Max(minimum_delay, adjustDelay * 0.9f);
-
- hoveredBox.Flash();
-
- var channel = sample?.GetChannel();
-
- if (channel != null)
- {
- double repeatModifier = 0.05f * (Math.Abs(adjustDelay - initial_delay) / minimum_delay);
- double multiplierModifier = (hoveredBox.Multiplier / max_multiplier) * 0.2f;
-
- channel.Frequency.Value = 1 + multiplierModifier + repeatModifier;
- channel.Play();
- }
- }
- else
- {
- adjustDelay = initial_delay;
- }
-
- adjustDelegate = Scheduler.AddDelayed(adjustNext, adjustDelay);
- }
- }
-
private class IncrementBox : CompositeDrawable
{
public readonly float Multiplier;
@@ -187,7 +141,7 @@ namespace osu.Game.Screens.Edit.Timing
Origin = direction,
Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold),
Text = $"{(index > 0 ? "+" : "-")}{Math.Abs(Multiplier * amount)}",
- Padding = new MarginPadding(5),
+ Padding = new MarginPadding(2),
Alpha = 0,
}
};
diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs
index 40e6e8082a..f498aa917e 100644
--- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs
+++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs
@@ -1,12 +1,14 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Events;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
@@ -51,6 +53,8 @@ namespace osu.Game.Screens.Edit.Timing
private readonly IBindableList controlPointGroups = new BindableList();
+ private RoundedButton addButton;
+
[Resolved]
private EditorClock clock { get; set; }
@@ -105,9 +109,8 @@ namespace osu.Game.Screens.Edit.Timing
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
},
- new RoundedButton
+ addButton = new RoundedButton
{
- Text = "+ Add at current time",
Action = addNew,
Size = new Vector2(160, 30),
Anchor = Anchor.BottomRight,
@@ -122,7 +125,14 @@ namespace osu.Game.Screens.Edit.Timing
{
base.LoadComplete();
- selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true);
+ selectedGroup.BindValueChanged(selected =>
+ {
+ deleteButton.Enabled.Value = selected.NewValue != null;
+
+ addButton.Text = selected.NewValue != null
+ ? "+ Clone to current time"
+ : "+ Add at current time";
+ }, true);
controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups);
controlPointGroups.BindCollectionChanged((sender, args) =>
@@ -132,13 +142,23 @@ namespace osu.Game.Screens.Edit.Timing
}, true);
}
+ protected override bool OnClick(ClickEvent e)
+ {
+ selectedGroup.Value = null;
+ return true;
+ }
+
protected override void Update()
{
base.Update();
trackActivePoint();
+
+ addButton.Enabled.Value = clock.CurrentTimeAccurate != selectedGroup.Value?.Time;
}
+ private Type trackedType;
+
///
/// Given the user has selected a control point group, we want to track any group which is
/// active at the current point in time which matches the type the user has selected.
@@ -149,16 +169,27 @@ namespace osu.Game.Screens.Edit.Timing
private void trackActivePoint()
{
// For simplicity only match on the first type of the active control point.
- var selectedPointType = selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType();
+ if (selectedGroup.Value == null)
+ trackedType = null;
+ else
+ {
+ // If the selected group only has one control point, update the tracking type.
+ if (selectedGroup.Value.ControlPoints.Count == 1)
+ trackedType = selectedGroup.Value?.ControlPoints.Single().GetType();
+ // If the selected group has more than one control point, choose the first as the tracking type
+ // if we don't already have a singular tracked type.
+ else if (trackedType == null)
+ trackedType = selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType();
+ }
- if (selectedPointType != null)
+ if (trackedType != null)
{
// We don't have an efficient way of looking up groups currently, only individual point types.
// To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo.
// Find the next group which has the same type as the selected one.
var found = Beatmap.ControlPointInfo.Groups
- .Where(g => g.ControlPoints.Any(cp => cp.GetType() == selectedPointType))
+ .Where(g => g.ControlPoints.Any(cp => cp.GetType() == trackedType))
.LastOrDefault(g => g.Time <= clock.CurrentTimeAccurate);
if (found != null)
@@ -189,7 +220,7 @@ namespace osu.Game.Screens.Edit.Timing
// Try and create matching types from the currently selected control point.
var selected = selectedGroup.Value;
- if (selected != null)
+ if (selected != null && selected != group)
{
foreach (var controlPoint in selected.ControlPoints)
{
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs
index 35c903eb0c..0015cf8bf9 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
private void load(AudioManager audio)
{
sampleSelect = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
- sampleJoin = audio.Samples.Get($@"UI/{HoverSampleSet.Submit.GetDescription()}-select");
+ sampleJoin = audio.Samples.Get($@"UI/{HoverSampleSet.Button.GetDescription()}-select");
AddRangeInternal(new Drawable[]
{
diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
index 5b3129dad6..b924fbd5df 100644
--- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
+++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
@@ -159,6 +159,8 @@ namespace osu.Game.Screens.Ranking.Expanded
Origin = Anchor.TopCentre,
Text = beatmap.DifficultyName,
Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold),
+ MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
+ Truncate = true,
},
new OsuTextFlowContainer(s => s.Font = OsuFont.Torus.With(size: 12))
{
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index a59f14647d..8a588c519b 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -10,6 +10,7 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Caching;
+using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
@@ -633,7 +634,7 @@ namespace osu.Game.Screens.Select
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
{
// handles the vertical size of the carousel changing (ie. on window resize when aspect ratio has changed).
- if ((invalidation & Invalidation.Layout) > 0)
+ if (invalidation.HasFlagFast(Invalidation.DrawSize))
itemsCache.Invalidate();
return base.OnInvalidate(invalidation, source);
diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
index 9772b1feb3..98b885eb43 100644
--- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
+++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
@@ -8,6 +8,7 @@ using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
@@ -244,7 +245,7 @@ namespace osu.Game.Screens.Select.Carousel
}
if (hideRequested != null)
- items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested(beatmapInfo)));
+ items.Add(new OsuMenuItem(CommonStrings.ButtonsHide.ToSentence(), MenuItemType.Destructive, () => hideRequested(beatmapInfo)));
return items.ToArray();
}
diff --git a/osu.Game/Screens/Select/Filter/SortMode.cs b/osu.Game/Screens/Select/Filter/SortMode.cs
index 5dfa2a2664..3dd3381059 100644
--- a/osu.Game/Screens/Select/Filter/SortMode.cs
+++ b/osu.Game/Screens/Select/Filter/SortMode.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Screens.Select.Filter
[Description("Author")]
Author,
- [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowStatsBpm))]
+ [LocalisableDescription(typeof(SortStrings), nameof(SortStrings.ArtistTracksBpm))]
BPM,
[Description("Date Added")]
@@ -28,10 +28,10 @@ namespace osu.Game.Screens.Select.Filter
Length,
// todo: pending support (https://github.com/ppy/osu/issues/4917)
- // [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchFiltersRank))]
+ // [Description("Rank Achieved")]
// RankAchieved,
- [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoSource))]
+ [Description("Source")]
Source,
[LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingTitle))]
diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs
index 9cb178ca8b..1587f43258 100644
--- a/osu.Game/Screens/Select/FooterButton.cs
+++ b/osu.Game/Screens/Select/FooterButton.cs
@@ -66,7 +66,7 @@ namespace osu.Game.Screens.Select
private readonly Box light;
public FooterButton()
- : base(HoverSampleSet.Button)
+ : base(HoverSampleSet.Toolbar)
{
AutoSizeAxes = Axes.Both;
Shear = SHEAR;
diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs
index 6ecb96f723..df7dd47ef3 100644
--- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs
+++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs
@@ -77,7 +77,7 @@ namespace osu.Game.Screens.Select.Options
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos);
public BeatmapOptionsButton()
- : base(HoverSampleSet.Submit)
+ : base(HoverSampleSet.Button)
{
Width = width;
RelativeSizeAxes = Axes.Y;
diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs
index e36d5ca3c6..095763de18 100644
--- a/osu.Game/Skinning/Editor/SkinEditor.cs
+++ b/osu.Game/Skinning/Editor/SkinEditor.cs
@@ -29,6 +29,8 @@ namespace osu.Game.Skinning.Editor
{
public const double TRANSITION_DURATION = 500;
+ public const float MENU_HEIGHT = 40;
+
public readonly BindableList SelectedComponents = new BindableList();
protected override bool StartHidden => true;
@@ -78,8 +80,6 @@ namespace osu.Game.Skinning.Editor
{
RelativeSizeAxes = Axes.Both;
- const float menu_height = 40;
-
InternalChild = new OsuContextMenuContainer
{
RelativeSizeAxes = Axes.Both,
@@ -102,7 +102,7 @@ namespace osu.Game.Skinning.Editor
Name = "Menu container",
RelativeSizeAxes = Axes.X,
Depth = float.MinValue,
- Height = menu_height,
+ Height = MENU_HEIGHT,
Children = new Drawable[]
{
new EditorMenuBar
@@ -322,7 +322,10 @@ namespace osu.Game.Skinning.Editor
protected override void PopIn()
{
- this.FadeIn(TRANSITION_DURATION, Easing.OutQuint);
+ this
+ // align animation to happen after the majority of the ScalingContainer animation completes.
+ .Delay(ScalingContainer.TRANSITION_DURATION * 0.3f)
+ .FadeIn(TRANSITION_DURATION, Easing.OutQuint);
}
protected override void PopOut()
diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs
index 497283a820..a3110ced24 100644
--- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs
+++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs
@@ -12,6 +12,8 @@ using osu.Framework.Input.Events;
using osu.Game.Graphics.Containers;
using osu.Game.Input.Bindings;
using osu.Game.Screens;
+using osu.Game.Screens.Edit.Components;
+using osuTK;
namespace osu.Game.Skinning.Editor
{
@@ -33,6 +35,8 @@ namespace osu.Game.Skinning.Editor
private OsuScreen lastTargetScreen;
+ private Vector2 lastDrawSize;
+
public SkinEditorOverlay(ScalingContainer scalingContainer)
{
this.scalingContainer = scalingContainer;
@@ -81,15 +85,42 @@ namespace osu.Game.Skinning.Editor
protected override void PopOut() => skinEditor?.Hide();
+ protected override void Update()
+ {
+ base.Update();
+
+ if (game.DrawSize != lastDrawSize)
+ {
+ lastDrawSize = game.DrawSize;
+ updateScreenSizing();
+ }
+ }
+
+ private void updateScreenSizing()
+ {
+ if (skinEditor?.State.Value != Visibility.Visible) return;
+
+ const float padding = 10;
+
+ float relativeSidebarWidth = (EditorSidebar.WIDTH + padding) / DrawWidth;
+ float relativeToolbarHeight = (SkinEditorSceneLibrary.HEIGHT + SkinEditor.MENU_HEIGHT + padding) / DrawHeight;
+
+ var rect = new RectangleF(
+ relativeSidebarWidth,
+ relativeToolbarHeight,
+ 1 - relativeSidebarWidth * 2,
+ 1f - relativeToolbarHeight - padding / DrawHeight);
+
+ scalingContainer.SetCustomRect(rect, true);
+ }
+
private void updateComponentVisibility()
{
Debug.Assert(skinEditor != null);
- const float toolbar_padding_requirement = 0.18f;
-
if (skinEditor.State.Value == Visibility.Visible)
{
- scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.2f, 0.8f - toolbar_padding_requirement, 0.7f), true);
+ Scheduler.AddOnce(updateScreenSizing);
game?.Toolbar.Hide();
game?.CloseAllOverlays();
@@ -127,6 +158,9 @@ namespace osu.Game.Skinning.Editor
private void setTarget(OsuScreen target)
{
+ if (target == null)
+ return;
+
Debug.Assert(skinEditor != null);
if (!target.IsLoaded)
diff --git a/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs
index 2124ba9b6d..dc5a8aefc0 100644
--- a/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs
+++ b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs
@@ -27,6 +27,8 @@ namespace osu.Game.Skinning.Editor
{
public class SkinEditorSceneLibrary : CompositeDrawable
{
+ public const float HEIGHT = BUTTON_HEIGHT + padding * 2;
+
public const float BUTTON_HEIGHT = 40;
private const float padding = 10;
@@ -42,7 +44,7 @@ namespace osu.Game.Skinning.Editor
public SkinEditorSceneLibrary()
{
- Height = BUTTON_HEIGHT + padding * 2;
+ Height = HEIGHT;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index b65ba8b04c..9524d3f615 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -303,8 +303,13 @@ namespace osu.Game.Skinning
if (Configuration.ConfigDictionary.TryGetValue(lookup.ToString(), out string val))
{
// special case for handling skins which use 1 or 0 to signify a boolean state.
+ // ..or in some cases 2 (https://github.com/ppy/osu/issues/18579).
if (typeof(TValue) == typeof(bool))
- val = val == "1" ? "true" : "false";
+ {
+ val = bool.TryParse(val, out bool boolVal)
+ ? Convert.ChangeType(boolVal, typeof(bool)).ToString()
+ : Convert.ChangeType(Convert.ToInt32(val), typeof(bool)).ToString();
+ }
var bindable = new Bindable();
if (val != null)
diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs
index 9481fc7182..34714b9dc5 100644
--- a/osu.Game/Skinning/LegacySkinTransformer.cs
+++ b/osu.Game/Skinning/LegacySkinTransformer.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Skinning
/// The which is being transformed.
///
[NotNull]
- protected internal ISkin Skin { get; }
+ public ISkin Skin { get; }
protected LegacySkinTransformer([NotNull] ISkin skin)
{
diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs
index 8290af8f78..8fea77833e 100644
--- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs
+++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs
@@ -3,7 +3,9 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
+using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
@@ -44,9 +46,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay
switch (request)
{
case CreateRoomRequest createRoomRequest:
- var apiRoom = new Room();
-
- apiRoom.CopyFrom(createRoomRequest.Room);
+ var apiRoom = cloneRoom(createRoomRequest.Room);
// Passwords are explicitly not copied between rooms.
apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value);
@@ -178,12 +178,31 @@ namespace osu.Game.Tests.Visual.OnlinePlay
private Room createResponseRoom(Room room, bool withParticipants)
{
- var responseRoom = new Room();
- responseRoom.CopyFrom(room);
+ var responseRoom = cloneRoom(room);
+
+ // Password is hidden from the response, and is only propagated via HasPassword.
+ bool hadPassword = responseRoom.HasPassword.Value;
responseRoom.Password.Value = null;
+ responseRoom.HasPassword.Value = hadPassword;
+
if (!withParticipants)
responseRoom.RecentParticipants.Clear();
+
return responseRoom;
}
+
+ private Room cloneRoom(Room source)
+ {
+ var result = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source));
+ Debug.Assert(result != null);
+
+ // Playlist item IDs aren't serialised.
+ if (source.CurrentPlaylistItem.Value != null)
+ result.CurrentPlaylistItem.Value.ID = source.CurrentPlaylistItem.Value.ID;
+ for (int i = 0; i < source.Playlist.Count; i++)
+ result.Playlist[i].ID = source.Playlist[i].ID;
+
+ return result;
+ }
}
}
diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs
index 0dd135b500..d85648c078 100644
--- a/osu.Game/Users/Drawables/ClickableAvatar.cs
+++ b/osu.Game/Users/Drawables/ClickableAvatar.cs
@@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Users.Drawables
@@ -74,11 +73,6 @@ namespace osu.Game.Users.Drawables
{
private LocalisableString tooltip = default_tooltip_text;
- public ClickableArea()
- : base(HoverSampleSet.Submit)
- {
- }
-
public override LocalisableString TooltipText
{
get => Enabled.Value ? tooltip : default;
diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs
index e5debc0683..d0ef760e59 100644
--- a/osu.Game/Users/Drawables/UpdateableFlag.cs
+++ b/osu.Game/Users/Drawables/UpdateableFlag.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Users.Drawables
{
RelativeSizeAxes = Axes.Both
},
- new HoverClickSounds(HoverSampleSet.Submit)
+ new HoverClickSounds()
}
};
}
diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
index 248debf1d3..40d70ca406 100644
--- a/osu.Game/Users/UserPanel.cs
+++ b/osu.Game/Users/UserPanel.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Users
protected Drawable Background { get; private set; }
protected UserPanel(APIUser user)
- : base(HoverSampleSet.Submit)
+ : base(HoverSampleSet.Button)
{
if (user == null)
throw new ArgumentNullException(nameof(user));
diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs
index ecc7fc4774..c12fd607b4 100644
--- a/osu.Game/Utils/SentryLogger.cs
+++ b/osu.Game/Utils/SentryLogger.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Utils
sentrySession = SentrySdk.Init(options =>
{
// Not setting the dsn will completely disable sentry.
- if (game.IsDeployedBuild)
+ if (game.IsDeployedBuild && game.CreateEndpoints().WebsiteRootUrl.EndsWith(@".ppy.sh", StringComparison.Ordinal))
options.Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2";
options.AutoSessionTracking = true;
@@ -159,6 +159,7 @@ namespace osu.Game.Utils
Game = game.Clock.CurrentTime,
};
+ scope.SetTag(@"beatmap", $"{beatmap.OnlineID}");
scope.SetTag(@"ruleset", ruleset.ShortName);
scope.SetTag(@"os", $"{RuntimeInfo.OS} ({Environment.OSVersion})");
scope.SetTag(@"processor count", Environment.ProcessorCount.ToString());
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index eb47d0468f..63b8cf4cb5 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -31,12 +31,12 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index ccecad6f82..a0fafa635b 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -61,7 +61,7 @@
-
+
@@ -84,11 +84,11 @@
-
+
-
+
diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings
index 68cf8138e2..286a1eb29f 100644
--- a/osu.sln.DotSettings
+++ b/osu.sln.DotSettings
@@ -790,6 +790,15 @@ See the LICENCE file in the repository root for full licence text.
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
True
True
True